'use client'; import { search, SearchResult } from '@/app/(authenticated)/lib/actions'; import { truncateWord } from '@/utils/text'; import { MagnifyingGlassIcon, DocumentTextIcon, Squares2X2Icon, } from '@heroicons/react/24/outline'; import Link from 'next/link'; import { useEffect, useRef, useState } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { useTranslations } from 'next-intl'; export default function NavSearch() { const t = useTranslations('NavSearch'); const [open, setOpen] = useState(false); const searchInput = useRef(null); const resultsRef = useRef(null); const [results, setResults] = useState([]); const [query, setQuery] = useState(''); const [loading, setLoading] = useState(false); useEffect(() => { const DEBOUNCE_MS = 350; if (!query) { setResults([]); return; } let cancelled = false; const handle = setTimeout(async () => { try { setLoading(true); const res = await search(query); if (!cancelled) setResults(res); } catch (e) { console.error(e); if (!cancelled) setResults([]); } finally { setLoading(false); } }, DEBOUNCE_MS); return () => { cancelled = true; clearTimeout(handle); }; }, [query]); useEffect(() => { if (open) { setTimeout(() => searchInput.current?.focus(), 100); } else { setQuery(''); setResults([]); } }, [open]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.altKey && e.key === 'k') { e.preventDefault(); setOpen(true); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, []); const handleKeyDown = (key: string) => { if (!resultsRef.current) return; const anchors = Array.from( resultsRef.current.querySelectorAll('a'), ); const active = document.activeElement as HTMLAnchorElement | null; const currentIndex = anchors.findIndex((a) => a === active); if (key === 'ArrowDown') { const nextIndex = currentIndex < anchors.length - 1 ? currentIndex + 1 : 0; anchors[nextIndex]?.focus(); } else if (key === 'ArrowUp') { const prevIndex = currentIndex > 0 ? currentIndex - 1 : anchors.length - 1; anchors[prevIndex]?.focus(); } else if (key === 'Backspace') { searchInput.current?.focus(); } }; return ( <> {t('title')}
setQuery(e.target.value)} onKeyDown={(e) => handleKeyDown(e.key)} />
handleKeyDown(e.key)} > {results.length === 0 ? ( loading ? (
{t('searching')}
) : query ? (
{t('noResults', { query })}
) : (
{t('emptyState')}
) ) : (
{results.map((item, index) => ( setOpen(false)} >
{item.type === 'note' ? (
) : (
)}
{truncateWord(item.title, 80)} {item.type === 'note' ? t('noteType') : t('boardType')}
{item.matchContent && (

{item.matchContent}

)} ))}
)}
{results.length > 0 && (
↑↓ {t('navigate')} Enter {t('open')}
{t('results', { count: results.length })}
)}
); }