"use client"; import React, { useMemo, useState, useRef, useEffect } from "react"; import Link from "next/link"; import { blogPosts } from "@/data/blog"; /** * Client-side blog search. The site is a static export, so there is no search * server. We filter the known posts in the browser and link straight to the * matching /blog/[slug] routes. */ export default function BlogSearch() { const [query, setQuery] = useState(""); const [open, setOpen] = useState(false); const containerRef = useRef(null); const results = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return []; return blogPosts .filter( (p) => p.title.toLowerCase().includes(q) || p.category.toLowerCase().includes(q) || p.excerpt.toLowerCase().includes(q) ) .slice(0, 6); }, [query]); // Close the results panel on outside click. useEffect(() => { function onDocClick(e: MouseEvent) { if ( containerRef.current && !containerRef.current.contains(e.target as Node) ) { setOpen(false); } } document.addEventListener("mousedown", onDocClick); return () => document.removeEventListener("mousedown", onDocClick); }, []); const showPanel = open && query.trim().length > 0; return (
e.preventDefault()} > { setQuery(e.target.value); setOpen(true); }} onFocus={() => setOpen(true)} aria-controls="dm-blog-search-results" />
{showPanel && (
{results.length === 0 ? (

No articles match “{query.trim()}”.

) : (
    {results.map((p) => (
  • setOpen(false)} > {p.category} {p.title}
  • ))}
)}
)}
); }