import * as React from 'react'; import { Input } from '../input'; import { SelectOptionItem } from './SelectOptionItem'; import type { GroupedOptions, SelectOption } from './types'; interface SelectDropdownContentProps { searchable: boolean; search: string; onSearchChange: (v: string) => void; searchInputRef: React.RefObject; filteredOptions: GroupedOptions; multiple: boolean; isSelected: (value: string) => boolean; onSelect: (value: string) => void; ariaLabel?: string; name?: string; placeholder?: string; } export function SelectDropdownContent({ searchable, search, onSearchChange, searchInputRef, filteredOptions, multiple, isSelected, onSelect, ariaLabel, name, placeholder, }: SelectDropdownContentProps) { const listboxId = React.useId(); const [highlightedIndex, setHighlightedIndex] = React.useState(-1); // Flatten all options for keyboard navigation const allOptions = React.useMemo(() => { const options: SelectOption[] = [...filteredOptions.ungrouped]; Object.values(filteredOptions.groups).forEach((groupOptions) => { options.push(...groupOptions); }); return options.filter((o) => !o.disabled); }, [filteredOptions]); const highlightedOptionId = highlightedIndex >= 0 && highlightedIndex < allOptions.length ? `${listboxId}-option-${allOptions[highlightedIndex]?.value}` : undefined; const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); setHighlightedIndex((prev) => prev < allOptions.length - 1 ? prev + 1 : 0, ); break; case 'ArrowUp': e.preventDefault(); setHighlightedIndex((prev) => prev > 0 ? prev - 1 : allOptions.length - 1, ); break; case 'Enter': case ' ': if (highlightedIndex >= 0 && highlightedIndex < allOptions.length) { e.preventDefault(); const opt = allOptions[highlightedIndex]; if (opt) onSelect(opt.value); } break; case 'Home': e.preventDefault(); setHighlightedIndex(0); break; case 'End': e.preventDefault(); setHighlightedIndex(allOptions.length - 1); break; } }; const hasOptions = filteredOptions.ungrouped.length > 0 || Object.keys(filteredOptions.groups).length > 0; return (
{searchable && (
} type="text" placeholder="Search..." value={search} onChange={(e) => onSearchChange(e.target.value)} onClick={(e) => e.stopPropagation()} onKeyDown={handleKeyDown} className="w-full" />
)} {filteredOptions.ungrouped.length > 0 && (
{filteredOptions.ungrouped.map((option) => ( = 0 && highlightedIndex < allOptions.length && allOptions[highlightedIndex]?.value === option.value } multiple={multiple} onSelect={onSelect} optionId={`${listboxId}-option-${option.value}`} /> ))}
)} {Object.entries(filteredOptions.groups).map(([groupLabel, groupOptions]) => (
{groupLabel}
{groupOptions.map((option) => ( = 0 && highlightedIndex < allOptions.length && allOptions[highlightedIndex]?.value === option.value } multiple={multiple} onSelect={onSelect} optionId={`${listboxId}-option-${option.value}`} /> ))}
))} {!hasOptions && (
No options found
)}
); }