'use client'; import { Combobox as HeadlessCombobox, ComboboxProps as HeadlessComboboxProps, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions, Field, Label } from '@headlessui/react'; import { AnchorProps } from '@headlessui/react/dist/internal/floating'; import { ChevronDownIcon } from '@heroicons/react/16/solid'; import { focusInput } from 'lib/utils'; import get from 'lodash.get'; import { useCallback, useState } from 'react'; import { tv } from 'tailwind-variants'; const combobox = tv({ slots: { root: '', label: [ 'text-sm leading-none', 'text-content-strong font-medium', 'data-[disabled]:text-gray-400' ], input: [ // base 'w-full relative block rounded-md border-0 shadow-sm outline-none transition sm:text-sm sm:leading-6', 'mt-2 px-2.5 py-1.5', // border color 'border-gray-300', // text color 'text-gray-900', // ring 'ring-1 ring-inset ring-gray-300', // placeholder color 'placeholder-gray-400', // background color 'bg-white', // disabled 'data-[disabled]:border-gray-300 data-[disabled]:bg-gray-100 data-[disabled]:text-gray-400', // focus focusInput, // invalid 'data-[invalid]:ring-2 data-[invalid]:ring-red-200 data-[invalid]:border-red-500' ], button: [ 'group absolute inset-y-0 right-0 px-2.5 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50' ], options: [ 'z-10 w-[var(--input-width)] rounded-xl border border-gray-200 bg-white p-1 [--anchor-gap:6px] empty:hidden' ], option: [ 'flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-1.5 text-sm/6 data-[focus]:bg-secondary/10' ] } }); interface ComboboxProps, TMultiple extends boolean | undefined> extends HeadlessComboboxProps { options: T[]; label: string; labelHidden?: boolean; autoFocus?: boolean; displayKey: keyof T; required?: boolean; className?: string; anchor?: AnchorProps; } const Combobox = , TMultiple extends boolean | undefined>({ options, value, onChange, label, labelHidden, displayKey, disabled, autoFocus, by, className, required, anchor, ...props }: ComboboxProps) => { const { root, label: labelStyles, input, button, options: optionsStyles, option: optionStyles } = combobox(); const [query, setQuery] = useState(''); const getDisplayValue = useCallback( (option: T | null) => { if (!option) return ''; if (typeof option[displayKey] === 'string') { return option[displayKey] as string; } return get(option, `${displayKey as string}.value`) as string; }, [displayKey] ); const filteredOptions = query === '' ? options : options.filter((option) => { return getDisplayValue(option).toLocaleLowerCase().includes(query.toLowerCase()); }); return ( {!labelHidden && ( )} setQuery('')} disabled={disabled} by={by} {...props} >
setQuery(event.target.value)} className={input()} autoFocus={autoFocus} />
{filteredOptions.map((option) => ( {getDisplayValue(option)} ))}
); }; export default Combobox;