Frontend/src/components/Select.tsx
2022-09-01 23:56:47 +03:00

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;