"use client"; import * as React from "react"; import { Check, ChevronDown } from "lucide-react"; import { cn } from "@/lib/utils"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; export type DropdownSelectOption = { value: string; label: string; disabled?: boolean; icon?: React.ElementType<{ className?: string }>; iconClassName?: string; }; type DropdownSelectProps = { value?: string; onValueChange: (value: string) => void; options: DropdownSelectOption[]; placeholder?: string; ariaLabel: string; disabled?: boolean; triggerClassName?: string; contentClassName?: string; itemClassName?: string; searchEnabled?: boolean; searchPlaceholder?: string; emptyMessage?: string; }; const resolvePlaceholder = (ariaLabel: string, placeholder?: string) => { if (placeholder) { return placeholder; } const trimmed = ariaLabel.trim(); if (!trimmed) { return "Select an option"; } return trimmed.endsWith("...") ? trimmed : `${trimmed}...`; }; const resolveSearchPlaceholder = ( ariaLabel: string, searchPlaceholder?: string, ) => { if (searchPlaceholder) { return searchPlaceholder; } const cleaned = ariaLabel.replace(/^select\\s+/i, "").trim(); if (!cleaned) { return "Search..."; } const value = `Search ${cleaned}`; return value.endsWith("...") ? value : `${value}...`; }; export default function DropdownSelect({ value, onValueChange, options, placeholder, ariaLabel, disabled = false, triggerClassName, contentClassName, itemClassName, searchEnabled = true, searchPlaceholder, emptyMessage, }: DropdownSelectProps) { const [open, setOpen] = React.useState(false); const [searchValue, setSearchValue] = React.useState(""); const listRef = React.useRef(null); const selectedOption = options.find((option) => option.value === value); const resolvedPlaceholder = resolvePlaceholder(ariaLabel, placeholder); const triggerLabel = selectedOption?.label ?? value ?? ""; const SelectedIcon = selectedOption?.icon; const selectedIconClassName = selectedOption?.iconClassName; const showPlaceholder = !triggerLabel; const resolvedSearchPlaceholder = searchEnabled ? resolveSearchPlaceholder(ariaLabel, searchPlaceholder) : ""; const handleOpenChange = (nextOpen: boolean) => { setOpen(nextOpen); if (!nextOpen) { setSearchValue(""); } }; const handleSelect = (nextValue: string) => { if (nextValue !== value) { onValueChange(nextValue); } handleOpenChange(false); }; React.useEffect(() => { if (!open) { return; } if (listRef.current) { listRef.current.scrollTop = 0; } }, [open, searchValue]); return ( {searchEnabled ? ( ) : null} {emptyMessage ?? "No results found."} {options.map((option) => { const isSelected = value === option.value; const OptionIcon = option.icon; return ( {OptionIcon ? ( ) : null} {option.label} {isSelected ? ( ) : null} ); })} ); }