import { Search, X } from "lucide-react"; import { type KeyboardEvent, useCallback, useEffect, useRef, useState, } from "react"; import type { SearchResult } from "../hooks/use-bestiary.js"; import { Input } from "./ui/input.js"; interface BestiarySearchProps { onSelectCreature: (result: SearchResult) => void; onClose: () => void; searchFn: (query: string) => SearchResult[]; } export function BestiarySearch({ onSelectCreature, onClose, searchFn, }: BestiarySearchProps) { const [query, setQuery] = useState(""); const [highlightIndex, setHighlightIndex] = useState(-1); const inputRef = useRef(null); const containerRef = useRef(null); const results = query.length >= 2 ? searchFn(query) : []; useEffect(() => { inputRef.current?.focus(); }, []); useEffect(() => { setHighlightIndex(-1); }, [query]); useEffect(() => { function handleClickOutside(e: MouseEvent) { if ( containerRef.current && !containerRef.current.contains(e.target as Node) ) { onClose(); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [onClose]); const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "Escape") { onClose(); return; } if (e.key === "ArrowDown") { e.preventDefault(); setHighlightIndex((i) => (i < results.length - 1 ? i + 1 : 0)); return; } if (e.key === "ArrowUp") { e.preventDefault(); setHighlightIndex((i) => (i > 0 ? i - 1 : results.length - 1)); return; } if (e.key === "Enter" && highlightIndex >= 0) { e.preventDefault(); onSelectCreature(results[highlightIndex]); } }, [results, highlightIndex, onClose, onSelectCreature], ); return (
setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="Search bestiary..." className="flex-1" />
{query.length >= 2 && (
{results.length === 0 ? (
No creatures found
) : (
    {results.map((result, i) => (
  • ))}
)}
)}
); }