"use client"; import * as React from "react"; import * as SelectPrimitive from "@radix-ui/react-select"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon, Search, } from "lucide-react"; import { cn } from "~/lib/utils"; function Select({ ...props }: React.ComponentProps) { return ; } function SelectGroup({ ...props }: React.ComponentProps) { return ; } function SelectValue({ ...props }: React.ComponentProps) { return ; } function SelectTrigger({ className, size = "default", children, ...props }: React.ComponentProps & { size?: "sm" | "default"; }) { return ( {children} ); } function SelectContent({ className, children, position = "popper", ...props }: React.ComponentProps) { return ( {children} ); } function SelectLabel({ className, ...props }: React.ComponentProps) { return ( ); } function SelectItem({ className, children, ...props }: React.ComponentProps) { return ( {children} ); } function SelectSeparator({ className, ...props }: React.ComponentProps) { return ( ); } function SelectScrollUpButton({ className, ...props }: React.ComponentProps) { return ( ); } function SelectScrollDownButton({ className, ...props }: React.ComponentProps) { return ( ); } // Enhanced SelectContent with search functionality function SelectContentWithSearch({ className, children, position = "popper", searchPlaceholder = "Search...", onSearchChange, searchValue, isOpen, filteredOptions, ...props }: React.ComponentProps & { searchPlaceholder?: string; onSearchChange?: (value: string) => void; searchValue?: string; isOpen?: boolean; filteredOptions?: { value: string; label: string }[]; }) { const searchInputRef = React.useRef(null); const wasOpen = React.useRef(false); React.useEffect(() => { // Only focus when dropdown transitions from closed to open if (isOpen && !wasOpen.current && searchInputRef.current) { searchInputRef.current.focus(); } wasOpen.current = !!isOpen; }, [isOpen]); return ( { // Prevent escape from closing the dropdown when typing if (searchValue) { e.preventDefault(); } }} onPointerDownOutside={(e) => { // Prevent closing when clicking inside the search input if (searchInputRef.current?.contains(e.target as Node)) { e.preventDefault(); } }} {...props} > {onSearchChange && (
onSearchChange(e.target.value)} onKeyDown={(e) => { // Prevent the dropdown from closing when typing if (e.key === "Escape") { e.stopPropagation(); } // Prevent arrow keys from moving focus away from search if ( ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes( e.key, ) ) { e.stopPropagation(); } }} onFocus={(e) => { // Ensure the search input stays focused e.target.select(); }} autoFocus />
)} {filteredOptions && filteredOptions.length === 0 ? (
No results found
) : ( children )}
); } // Searchable Select component interface SearchableSelectProps { value?: string; onValueChange?: (value: string) => void; placeholder?: string; options: { value: string; label: string; disabled?: boolean }[]; searchPlaceholder?: string; className?: string; disabled?: boolean; renderOption?: (option: { value: string; label: string; disabled?: boolean; }) => React.ReactNode; isOptionDisabled?: (option: { value: string; label: string; disabled?: boolean; }) => boolean; id?: string; } function SearchableSelect({ value, onValueChange, placeholder, options, searchPlaceholder = "Search...", className, disabled, renderOption, isOptionDisabled, id, }: SearchableSelectProps) { const [searchValue, setSearchValue] = React.useState(""); const [isOpen, setIsOpen] = React.useState(false); const filteredOptions = React.useMemo(() => { if (!searchValue) return options; return options.filter((option) => { // Don't filter out dividers, disabled options, or placeholder if (option.value?.startsWith("divider-")) return true; if (option.value === "__placeholder__") return true; return option.label.toLowerCase().includes(searchValue.toLowerCase()); }); }, [options, searchValue]); // Convert empty string to placeholder value for display const displayValue = value === "" ? "__placeholder__" : value; // Convert placeholder value back to empty string when selected const handleValueChange = (newValue: string) => { const actualValue = newValue === "__placeholder__" ? "" : newValue; onValueChange?.(actualValue); // Clear search when an option is selected setSearchValue(""); setIsOpen(false); }; return ( ); } export { Select, SelectContent, SelectContentWithSearch, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SearchableSelect, };