import { useEffect, useId, useRef, useState } from 'react'; export type SelectOption = { value: string; label: string; }; export type SelectOptionGroup = { label: string; options: SelectOption[]; }; type SelectFieldProps = { id?: string; label: string; value: string; options: SelectOption[]; groups?: SelectOptionGroup[]; disabled?: boolean; onChange: (value: string) => void; }; export function SelectField({ id, label, value, options, groups = [], disabled = false, onChange, }: SelectFieldProps) { const generatedId = useId(); const fieldId = id ?? generatedId; const [isOpen, setIsOpen] = useState(false); const rootRef = useRef(null); const selectedOption = findOption(value, options, groups); useEffect(() => { function handlePointerDown(event: PointerEvent) { if (!rootRef.current?.contains(event.target as Node)) { setIsOpen(false); } } function handleKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') { setIsOpen(false); } } document.addEventListener('pointerdown', handlePointerDown); document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('pointerdown', handlePointerDown); document.removeEventListener('keydown', handleKeyDown); }; }, []); function handleSelect(nextValue: string) { onChange(nextValue); setIsOpen(false); } return (
{isOpen ? (
{options.map((option) => ( ))} {groups.map((group) => (
{group.label}
{group.options.map((option) => ( ))}
))}
) : null}
); } function SelectItem({ option, isSelected, onSelect, }: { option: SelectOption; isSelected: boolean; onSelect: (value: string) => void; }) { return ( ); } function findOption( value: string, options: SelectOption[], groups: SelectOptionGroup[], ): SelectOption | undefined { return ( options.find((option) => option.value === value) ?? groups.flatMap((group) => group.options).find((option) => option.value === value) ); }