import { memo, useCallback, useEffect, useRef, useState } from "react"; import { IoIosArrowDown, IoIosArrowUp, IoIosCloseCircle } from "react-icons/io"; const CellSelect = memo(({ value, onChange, onKeyDown, dataCell, options, hasError, placeholder = "Select option", onClick }) => { const [isOpen, setIsOpen] = useState(false); const [dropdownDirection, setDropdownDirection] = useState("down"); const [searchTerm, setSearchTerm] = useState(""); const selectRef = useRef(null); const dropdownRef = useRef(null); const searchInputRef = useRef(null); // Filter options based on search term const filteredOptions = options?.filter(option => option.label.toLowerCase().includes(searchTerm.toLowerCase()) ) || []; // Calculate dropdown position const calculateDropdownPosition = useCallback(() => { if (!selectRef.current) return "down"; const rect = selectRef.current.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; return (spaceBelow < 200 && spaceAbove > spaceBelow) ? "up" : "down"; }, []); // Close dropdown on outside click or scroll useEffect(() => { if (!isOpen) return; const handler = e => { if ( !dropdownRef.current?.contains(e.target) && !selectRef.current?.contains(e.target) ) { setIsOpen(false); // Reset search term when closing dropdown setSearchTerm(""); } }; const scrollHandler = e => { if ( dropdownRef.current && !dropdownRef.current.contains(e.target) && selectRef.current && !selectRef.current.contains(e.target) ) { setIsOpen(false); setSearchTerm(""); } }; document.addEventListener("mousedown", handler); window.addEventListener("scroll", scrollHandler, true); return () => { document.removeEventListener("mousedown", handler); window.removeEventListener("scroll", scrollHandler, true); }; }, [isOpen]); // Focus search input when dropdown opens useEffect(() => { if (isOpen && searchInputRef.current) { searchInputRef.current.focus(); } }, [isOpen]); // Update dropdown direction useEffect(() => { if (isOpen) setDropdownDirection(calculateDropdownPosition()); }, [isOpen, calculateDropdownPosition]); // Keyboard navigation const handleKeyDown = e => { if (!isOpen && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) { e.preventDefault(); onKeyDown(e); return; } if (isOpen && ["ArrowLeft", "ArrowRight"].includes(e.key)) { e.preventDefault(); return; } if (e.key === "Enter") { e.preventDefault(); if (isOpen && filteredOptions.length > 0) { // Select the first option when pressing Enter handleSelectChange(filteredOptions[0].value); } else { setIsOpen(v => !v); } return; } if (e.key === "Escape") { e.preventDefault(); setIsOpen(false); setSearchTerm(""); return; } // Allow typing to search when dropdown is open if (isOpen && e.key.length === 1 && !e.ctrlKey && !e.metaKey) { setSearchTerm(prev => prev + e.key); e.preventDefault(); return; } onKeyDown(e); }; const handleSelectChange = v => { onChange({ target: { value: v } }); setIsOpen(false); setSearchTerm(""); // Clear search term after selection }; const handleSearchChange = e => { setSearchTerm(e.target.value); }; const clearSearch = () => { setSearchTerm(""); if (searchInputRef.current) { searchInputRef.current.focus(); } }; const selectedOption = options?.find(opt => opt.value === value); const displayValue = selectedOption ? selectedOption.label : placeholder; return (
{ setIsOpen(v => !v); onClick?.(e); }} onKeyDown={handleKeyDown} data-cell={dataCell} tabIndex={0} > {isOpen ? ( e.stopPropagation()} placeholder={selectedOption?.label || placeholder} /> ) : ( displayValue )} {isOpen ? : }
{isOpen && (
{filteredOptions.length === 0 ? (
{searchTerm ? "No matches found" : "No data"}
) : ( filteredOptions.map(option => (
handleSelectChange(option.value)} > {option.label}
)) )}
)}
); }); export default CellSelect;