"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; import { Search, X } from "lucide-react"; import { cn } from "@/lib/utils"; /** * HeaderSearchButton — a search icon in the page header that expands * into a full search input on click. Typing a query and pressing Enter * (or clicking the search icon again) navigates to /search?q=... * * The expansion/collapse is animated with framer-motion. * Pressing Escape or clicking outside collapses the input. * Keyboard shortcut: "/" focuses the search from anywhere on the page. */ export function HeaderSearchButton() { const [expanded, setExpanded] = useState(false); const [query, setQuery] = useState(""); const inputRef = useRef(null); const containerRef = useRef(null); const router = useRouter(); // Focus input when expanded useEffect(() => { if (expanded && inputRef.current) { // Small delay to let the animation start before focusing const timer = setTimeout(() => inputRef.current?.focus(), 50); return () => clearTimeout(timer); } }, [expanded]); // Close on outside click useEffect(() => { if (!expanded) return; function handleClick(e: MouseEvent) { if ( containerRef.current && !containerRef.current.contains(e.target as Node) ) { setExpanded(false); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [expanded]); // Global "/" shortcut to open search useEffect(() => { function handleKeyDown(e: KeyboardEvent) { // Don't trigger if user is typing in an input/textarea const target = e.target as HTMLElement; if ( target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable ) { return; } if (e.key === "/") { e.preventDefault(); setExpanded(true); } } document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, []); const handleSubmit = useCallback(() => { const trimmed = query.trim(); if (trimmed) { router.push(`/search?q=${encodeURIComponent(trimmed)}`); setExpanded(false); setQuery(""); } else { router.push("/search"); setExpanded(false); } }, [query, router]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); handleSubmit(); } else if (e.key === "Escape") { e.preventDefault(); setExpanded(false); setQuery(""); } }, [handleSubmit], ); return (
{expanded ? ( setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="Search..." className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none min-w-0" /> ) : ( setExpanded(true)} aria-label="Search (press /)" title="Search (press /)" className={cn( "size-9 inline-flex items-center justify-center rounded-lg", "text-muted-foreground hover:text-foreground hover:bg-muted", "transition-colors outline-none", "focus-visible:ring-2 focus-visible:ring-ring", )} > )}
); }