'use client'; import { useState, useEffect, useRef } from 'react'; import { Search, X } from 'lucide-react'; import Fuse from 'fuse.js'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { getAllMeasures, getUnitsForMeasure, getUnitInfo, formatMeasureName, getCategoryColor, getCategoryColorHex, type Measure, type UnitInfo, } from '@/lib/units'; import { cn } from '@/lib/utils'; interface SearchResult { unitInfo: UnitInfo; measure: Measure; } interface SearchUnitsProps { onSelectUnit: (unit: string, measure: Measure) => void; } export default function SearchUnits({ onSelectUnit }: SearchUnitsProps) { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [isOpen, setIsOpen] = useState(false); const inputRef = useRef(null); const containerRef = useRef(null); // Build search index const searchIndex = useRef | null>(null); useEffect(() => { // Build comprehensive search data const allData: SearchResult[] = []; const measures = getAllMeasures(); for (const measure of measures) { const units = getUnitsForMeasure(measure); for (const unit of units) { const unitInfo = getUnitInfo(unit); if (unitInfo) { allData.push({ unitInfo, measure, }); } } } // Initialize Fuse.js for fuzzy search searchIndex.current = new Fuse(allData, { keys: [ { name: 'unitInfo.abbr', weight: 2 }, { name: 'unitInfo.singular', weight: 1.5 }, { name: 'unitInfo.plural', weight: 1.5 }, { name: 'measure', weight: 1 }, ], threshold: 0.3, includeScore: true, }); }, []); // Perform search useEffect(() => { if (!query.trim() || !searchIndex.current) { setResults([]); setIsOpen(false); return; } const searchResults = searchIndex.current.search(query); setResults(searchResults.map(r => r.item).slice(0, 10)); setIsOpen(true); }, [query]); // Handle click outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( containerRef.current && !containerRef.current.contains(event.target as Node) ) { setIsOpen(false); } } document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Keyboard shortcut: / to focus search useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if (e.key === '/' && !e.ctrlKey && !e.metaKey) { const activeElement = document.activeElement; if ( activeElement?.tagName !== 'INPUT' && activeElement?.tagName !== 'TEXTAREA' ) { e.preventDefault(); inputRef.current?.focus(); } } if (e.key === 'Escape') { setIsOpen(false); inputRef.current?.blur(); } } document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, []); const handleSelectUnit = (unit: string, measure: Measure) => { onSelectUnit(unit, measure); setQuery(''); setIsOpen(false); inputRef.current?.blur(); }; const clearSearch = () => { setQuery(''); setIsOpen(false); }; return (
setQuery(e.target.value)} onFocus={() => query && setIsOpen(true)} className="pl-10 pr-10" /> {query && ( )}
{/* Results dropdown */} {isOpen && results.length > 0 && (
{results.map((result, index) => ( ))}
)} {isOpen && query && results.length === 0 && (
No units found for "{query}"
)}
); }