'use client'; import Link from 'next/link'; import { ExternalLink, Loader2, Search } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; type Result = { id: string; type: 'group' | 'topic' | 'person' | 'message' | 'link'; title: string; subtitle: string; href: string; external?: boolean; }; type SearchResponse = { ok: boolean; results: Result[]; }; const TYPE_LABEL: Record = { group: '群', topic: '话题', person: '人', message: '消息', link: '链接', }; export default function GlobalSearch() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const boxRef = useRef(null); useEffect(() => { const onDown = (e: MouseEvent) => { if (!boxRef.current?.contains(e.target as Node)) setOpen(false); }; document.addEventListener('mousedown', onDown); return () => document.removeEventListener('mousedown', onDown); }, []); useEffect(() => { const q = query.trim(); if (q.length < 2) { return; } const ctl = new AbortController(); const timer = window.setTimeout(async () => { setLoading(true); try { const r = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { cache: 'no-store', signal: ctl.signal, }); const j = (await r.json()) as SearchResponse; if (j.ok) { setResults(j.results); setOpen(true); } } catch (e) { if (!(e instanceof DOMException && e.name === 'AbortError')) console.error(e); } finally { setLoading(false); } }, 220); return () => { window.clearTimeout(timer); ctl.abort(); }; }, [query]); return (
setQuery(e.target.value)} onFocus={() => query.trim().length >= 2 && setOpen(true)} className="min-w-0 flex-1 bg-transparent text-[12px] outline-none placeholder:text-[var(--text-3)]" placeholder="搜索群、话题、人、关键词" /> {loading && }
{open && query.trim().length >= 2 && (
{results.length === 0 && !loading ? (
没找到匹配结果
) : (
{results.map((item) => ( setOpen(false)} /> ))}
)}
)}
); } function SearchItem({ item, onClick }: { item: Result; onClick: () => void }) { const inner = ( <> {TYPE_LABEL[item.type]} {item.title} {item.subtitle} {item.external && } ); const className = 'flex items-start gap-2 rounded-md px-2.5 py-2 transition-colors hover:bg-[var(--surface-2)]'; if (item.external) { return ( {inner} ); } return ( {inner} ); }