"use client"; import { SelectHTMLAttributes, forwardRef, useState, useRef, useEffect } from "react"; interface SelectOption { value: string; label: string; } interface SearchableSelectProps extends Omit, 'onChange'> { label?: string; error?: string; helperText?: string; leftIcon?: string; options: SelectOption[]; placeholder?: string; onChange?: (value: string) => void; value?: string; } const SearchableSelect = forwardRef( ( { label, error, helperText, leftIcon, options, placeholder, className = "", onChange, value, required, ...props }, ref ) => { const [isOpen, setIsOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [selectedOption, setSelectedOption] = useState( options.find(opt => opt.value === value) || null ); const containerRef = useRef(null); const searchInputRef = useRef(null); const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchTerm.toLowerCase()) ); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); useEffect(() => { if (isOpen && searchInputRef.current) { searchInputRef.current.focus(); } }, [isOpen]); useEffect(() => { if (value) { const option = options.find(opt => opt.value === value); if (option) { setSelectedOption(option); } } }, [value, options]); const handleSelect = (option: SelectOption) => { setSelectedOption(option); setIsOpen(false); setSearchTerm(""); if (onChange) { onChange(option.value); } }; return ( {/* Hidden select for form compatibility */} { const option = options.find(opt => opt.value === e.target.value); if (option) handleSelect(option); }} className="hidden" required={required} {...props} > {placeholder || "Selecione uma opção"} {options.map((option) => ( {option.label} ))} {label && ( {label} {required && *} )} {leftIcon && ( )} {/* Custom trigger */} setIsOpen(!isOpen)} className={` w-full px-3.5 py-3 text-[14px] font-normal border rounded-md bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white text-left transition-all cursor-pointer ${leftIcon ? "pl-11" : ""} pr-11 ${error ? "border-[#FF3A05]" : "border-zinc-200 dark:border-zinc-700 focus:border-[#FF3A05]" } outline-none ring-0 focus:ring-0 shadow-none focus:shadow-none ${className} `} > {selectedOption ? selectedOption.label : ( {placeholder || "Selecione uma opção"} )} {/* Dropdown */} {isOpen && ( {/* Search input */} setSearchTerm(e.target.value)} placeholder="Buscar..." className="w-full pl-9 pr-3 py-2 text-[14px] border border-zinc-200 dark:border-zinc-700 rounded-md outline-none focus:border-[#FF3A05] shadow-none bg-white dark:bg-zinc-900 text-zinc-900 dark:text-white placeholder:text-zinc-500 dark:placeholder:text-zinc-400" /> {/* Options list */} {filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( handleSelect(option)} className={` w-full px-4 py-2.5 text-left text-[14px] transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-700 cursor-pointer ${selectedOption?.value === option.value ? 'bg-[#FF3A05]/10 text-[#FF3A05] font-medium' : 'text-zinc-900 dark:text-white'} `} > {option.label} )) ) : ( Nenhum resultado encontrado )} )} {helperText && !error && ( {helperText} )} {error && ( {error} )} ); } ); SearchableSelect.displayName = "SearchableSelect"; export default SearchableSelect;
{helperText}
{error}