import { useEffect, useRef, useState } from 'react' import { api, splitSnippet, type SearchResult } from '../../api/client' interface Props { // Called when a result is chosen — opens that document. onSelect: (id: string) => void } const DEBOUNCE_MS = 220 // SearchBox is the cross-document finder at the top of the sidebar. Typing // debounces a full-text query; results drop in below with a highlighted snippet. // Clearing (× or empty) returns the sidebar to the normal document list. export function SearchBox({ onSelect }: Props) { const [q, setQ] = useState('') const [results, setResults] = useState(null) const [busy, setBusy] = useState(false) const runRef = useRef(0) const debounceRef = useRef>(undefined) useEffect(() => { clearTimeout(debounceRef.current) const term = q.trim() if (!term) { setResults(null) setBusy(false) return } setBusy(true) const run = ++runRef.current debounceRef.current = setTimeout(async () => { try { const hits = await api.search(term) if (run === runRef.current) setResults(hits) } catch (err) { console.error('search failed', err) if (run === runRef.current) setResults([]) } finally { if (run === runRef.current) setBusy(false) } }, DEBOUNCE_MS) return () => clearTimeout(debounceRef.current) }, [q]) const clear = () => { setQ('') setResults(null) } return (
🔍 setQ(e.target.value)} placeholder="搜索 · Search" aria-label="Search documents" className="petal-tap w-full bg-transparent pl-9 pr-8 text-sm focus:outline-none" style={{ height: 40, borderRadius: 'var(--radius-pill)', border: '1px solid var(--color-border)', background: 'var(--color-surface)', color: 'var(--color-plum)', }} /> {q && ( )}
{results !== null && (
{busy && results.length === 0 ? (

查找中… · Searching…

) : results.length === 0 ? (

没有找到 · No matches

) : ( results.map((r) => ( )) )}
)}
) }