import type { RefObject } from 'react'; import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'; import { cn } from '@/lib/utils'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSearchInput, DropdownMenuSeparator, DropdownMenuTrigger, } from './Dropdown'; interface SearchableDropdownProps { /** Array of filtered items to display */ filteredItems: T[]; /** Currently selected value (matched against getItemKey) */ selectedValue?: string | null; /** Extract unique key from item */ getItemKey: (item: T) => string; /** Extract display label from item */ getItemLabel: (item: T) => string; /** Called when an item is selected */ onSelect: (item: T) => void; /** Trigger element (uses asChild pattern) */ trigger: React.ReactNode; /** Search state */ searchTerm: string; onSearchTermChange: (value: string) => void; /** Highlight state */ highlightedIndex: number | null; onHighlightedIndexChange: (index: number | null) => void; /** Open state */ open: boolean; onOpenChange: (open: boolean) => void; /** Keyboard handler */ onKeyDown: (e: React.KeyboardEvent) => void; /** Virtuoso ref for scrolling */ virtuosoRef: RefObject; /** Class name for dropdown content */ contentClassName?: string; /** Placeholder text for search input */ placeholder?: string; /** Message shown when no items match */ emptyMessage?: string; /** Optional badge text for each item */ getItemBadge?: (item: T) => string | undefined; } export function SearchableDropdown({ filteredItems, selectedValue, getItemKey, getItemLabel, onSelect, trigger, searchTerm, onSearchTermChange, highlightedIndex, onHighlightedIndexChange, open, onOpenChange, onKeyDown, virtuosoRef, contentClassName, placeholder = 'Search', emptyMessage = 'No items found', getItemBadge, }: SearchableDropdownProps) { return ( {trigger} {filteredItems.length === 0 ? (
{emptyMessage}
) : ( } style={{ height: '16rem' }} totalCount={filteredItems.length} computeItemKey={(idx) => getItemKey(filteredItems[idx]) ?? String(idx) } itemContent={(idx) => { const item = filteredItems[idx]; const key = getItemKey(item); const isHighlighted = idx === highlightedIndex; const isSelected = selectedValue === key; return ( onSelect(item)} onMouseEnter={() => onHighlightedIndexChange(idx)} preventFocusOnHover badge={getItemBadge?.(item)} className={cn( isSelected && 'bg-secondary', isHighlighted && 'bg-secondary' )} > {getItemLabel(item)} ); }} /> )}
); }