"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { cn } from "@/lib/utils"; type SearchResult = { title: string; href: string; section: string; snippet: string; }; export function Search() { const router = useRouter(); const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [activeIndex, setActiveIndex] = useState(0); const inputRef = useRef(null); const listRef = useRef(null); const abortRef = useRef(null); const navigate = useCallback( (href: string) => { setOpen(false); setQuery(""); setResults([]); router.push(href); }, [router], ); useEffect(() => { function onKeyDown(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setOpen((prev) => !prev); } } document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, []); useEffect(() => { if (open) { setTimeout(() => inputRef.current?.focus(), 0); } else { setQuery(""); setResults([]); } }, [open]); useEffect(() => { const q = query.trim(); if (!q) { setResults([]); setLoading(false); return; } setLoading(true); abortRef.current?.abort(); const controller = new AbortController(); abortRef.current = controller; const timeout = setTimeout(async () => { try { const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: controller.signal, }); if (res.ok) { const data = await res.json(); setResults(data.results); } } catch { // aborted or network error } finally { if (!controller.signal.aborted) { setLoading(false); } } }, 150); return () => { clearTimeout(timeout); controller.abort(); }; }, [query]); useEffect(() => { setActiveIndex(0); }, [results]); function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "ArrowDown") { e.preventDefault(); setActiveIndex((i) => Math.min(i + 1, results.length - 1)); } else if (e.key === "ArrowUp") { e.preventDefault(); setActiveIndex((i) => Math.max(i - 1, 0)); } else if (e.key === "Enter" && results[activeIndex]) { e.preventDefault(); navigate(results[activeIndex].href); } } useEffect(() => { const active = listRef.current?.querySelector("[data-active='true']"); active?.scrollIntoView({ block: "nearest" }); }, [activeIndex]); const hasQuery = query.trim().length > 0; return ( <> Search documentation
setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="Search docs..." className="flex-1 bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground" /> {query && ( )}
{loading && hasQuery ? (
) : hasQuery && results.length === 0 ? (

No results found.

) : !hasQuery ? (

Type to search documentation...

) : ( results.map((item, i) => ( )) )}
); }