import { Input, Listbox, ListboxButton, ListboxOption, ListboxOptions, } from "@headlessui/react"; import { ChevronDownIcon, MagnifyingGlassIcon, ChevronRightIcon, } from "@heroicons/react/24/outline"; import clsx from "clsx"; import { type ReactNode, type ChangeEvent, useState, useCallback, useMemo, } from "react"; import { Accordion, AccordionButton, AccordionPanel } from "../Accordion"; type Props = { value: T; onChange: (newValue: T) => void; renderOption: (option: T) => ReactNode; renderButton?: (selected: T) => ReactNode; renderButtonContents?: (selected: T) => ReactNode; filter?: ((options: readonly T[], value: string) => readonly T[]) | undefined; className?: string | undefined; } & ( | { options: readonly T[]; } | { optionGroups: { name: string; options: readonly T[] }[]; } ); export const Select = ({ value, onChange, renderOption, renderButton, renderButtonContents, filter, className, ...props }: Props) => { const [filterValue, setFilter] = useState(""); const updateFilter = useCallback( (event: ChangeEvent) => { event.stopPropagation(); setFilter(event.target.value); }, [setFilter], ); return ( {renderButton ? ( renderButton(value) ) : ( {renderButtonContents ? ( renderButtonContents(value) ) : (
{renderOption(value)}
)}
)}
{filter && (
{ e.stopPropagation(); }} />
)} {"options" in props ? ( ) : ( props.optionGroups.map((group, i) => ( )) )}
); }; type OptionGroupProps = { filter?: ((options: readonly T[], value: string) => readonly T[]) | undefined; filterValue: string; options: readonly T[]; name: string; renderOption: (option: T) => ReactNode; }; const OptionGroup = ({ filter, filterValue, options, name, renderOption, }: OptionGroupProps) => { const filteredOptions = useMemo( () => filter && filterValue !== "" ? filter(options, filterValue) : options, [filter, filterValue, options], ); return filteredOptions.length > 0 ? (
{name}
) : // eslint-disable-next-line unicorn/no-null null; }; type OptionsProps = { options: readonly T[]; renderOption: (option: T) => ReactNode; }; const Options = ({ options, renderOption }: OptionsProps) => options.map((option, i) => ( {renderOption(option)} ));