152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
/* -------------------------------------------------------------------------- */
|
|
/* Imports */
|
|
/* -------------------------------------------------------------------------- */
|
|
import React from "react";
|
|
import { Fragment } from "react";
|
|
import { Listbox, Transition } from "@headlessui/react";
|
|
import classNames from "classnames";
|
|
import "../index.css";
|
|
import { ReactComponent as SelectIcon } from "../assets/svg/caret-down.svg";
|
|
import { Scrollbar } from "react-scrollbars-custom";
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Component props */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
type Props<T> = {
|
|
inGroup?: boolean;
|
|
options?: T[];
|
|
disabled?: boolean;
|
|
className?: string;
|
|
value: T;
|
|
displayValueResolver?: (element: T) => any;
|
|
onChange: (element: T) => void;
|
|
maxScrollSize?: number;
|
|
elementScrollSize?: number;
|
|
} & Omit<React.ComponentPropsWithRef<"select">, "value" | "onChange">;
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* styles */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
const SelectButtonStyle = `
|
|
relative w-full
|
|
cursor-default
|
|
rounded
|
|
border border-gray-50
|
|
outline-8
|
|
bg-white
|
|
py-2 pl-3 pr-10 text-left
|
|
hover:border-gray-300
|
|
focus:outline-1
|
|
focus-visible:border-gray-500
|
|
sm:text-sm
|
|
`;
|
|
|
|
const SelectOptionsStyle = `
|
|
absolute z-10 mt-1 w-full max-h-56
|
|
bg-white shadow-lg
|
|
rounded py-1
|
|
overflow-hidden
|
|
focus:outline-none
|
|
text-base
|
|
sm:text-sm
|
|
`;
|
|
|
|
const SelectIconStyle = `
|
|
pointer-events-none
|
|
absolute inset-y-0 right-0
|
|
flex items-center pr-2
|
|
`;
|
|
|
|
const inputInGroup = [
|
|
` border-none
|
|
hover:none
|
|
active:none
|
|
focus:none
|
|
`,
|
|
];
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Component implementation */
|
|
/* -------------------------------------------------------------------------- */
|
|
function Select<T>({
|
|
inGroup = false, // We should use this flag to choose how we will style our component
|
|
className,
|
|
options = [],
|
|
value,
|
|
onChange,
|
|
displayValueResolver,
|
|
disabled,
|
|
maxScrollSize = 140,
|
|
elementScrollSize = 36,
|
|
...props
|
|
}: Props<T>): JSX.Element {
|
|
return (
|
|
<div className={classNames("top-16 w-60", className)}>
|
|
<Listbox value={value} {...props} onChange={onChange}>
|
|
<div className="relative">
|
|
<Listbox.Button
|
|
className={classNames([
|
|
[`${SelectButtonStyle}`],
|
|
{ [`${inputInGroup}`]: inGroup },
|
|
className,
|
|
])}
|
|
>
|
|
{({ open }) => (
|
|
<>
|
|
<span className="block truncate">{`${
|
|
displayValueResolver ? displayValueResolver(value) : value
|
|
}`}</span>
|
|
<span className={`${SelectIconStyle}`}>
|
|
<SelectIcon
|
|
className={`${
|
|
open ? "rotate-180 transform" : "font-normal"
|
|
} h-3 w-4 fill-black hover:fill-black stroke-black`}
|
|
/>
|
|
</span>
|
|
</>
|
|
)}
|
|
</Listbox.Button>
|
|
<Transition
|
|
as={Fragment}
|
|
leave="transition ease-in duration-100"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<Listbox.Options className={`${SelectOptionsStyle}`}>
|
|
{/* <Scrollbar
|
|
style={{
|
|
height: options.length * elementScrollSize,
|
|
maxHeight: maxScrollSize,
|
|
}}
|
|
> */}
|
|
{options.map((option, id) => (
|
|
<Listbox.Option
|
|
key={id}
|
|
className={({ active, selected }) =>
|
|
classNames(
|
|
active ? "text-gray-900 bg-blue-50" : "font-normal ",
|
|
"cursor-default select-none relative py-2 pl-3 pr-9",
|
|
selected ? "text-gray-900 bg-blue-100" : "font-normal "
|
|
)
|
|
}
|
|
value={option}
|
|
>
|
|
{`${
|
|
displayValueResolver
|
|
? displayValueResolver(option)
|
|
: option
|
|
}`}
|
|
</Listbox.Option>
|
|
))}
|
|
{/* </Scrollbar> */}
|
|
</Listbox.Options>
|
|
</Transition>
|
|
</div>
|
|
</Listbox>
|
|
</div>
|
|
//
|
|
);
|
|
}
|
|
export default Select;
|