import React, { useState, useEffect, useRef, useCallback } from 'react' const SEARCH_API = '/api/search/artworks' const DEBOUNCE_MS = 280 function useDebounce(value, delay) { const [debounced, setDebounced] = useState(value) useEffect(() => { const id = setTimeout(() => setDebounced(value), delay) return () => clearTimeout(id) }, [value, delay]) return debounced } export default function SearchBar({ placeholder = 'Search artworks, artists, tags…' }) { const [query, setQuery] = useState('') const [suggestions, setSuggestions] = useState([]) const [loading, setLoading] = useState(false) const [open, setOpen] = useState(false) const inputRef = useRef(null) const wrapperRef = useRef(null) const abortRef = useRef(null) const debouncedQuery = useDebounce(query, DEBOUNCE_MS) const fetchSuggestions = useCallback(async (q) => { if (!q || q.length < 2) { setSuggestions([]) setOpen(false) return } if (abortRef.current) abortRef.current.abort() abortRef.current = new AbortController() setLoading(true) try { const url = `${SEARCH_API}?q=${encodeURIComponent(q)}&per_page=6` const res = await fetch(url, { signal: abortRef.current.signal }) if (!res.ok) return const json = await res.json() const items = json.data ?? json ?? [] setSuggestions(Array.isArray(items) ? items.slice(0, 6) : []) setOpen(true) } catch (e) { if (e.name !== 'AbortError') console.error('SearchBar fetch error', e) } finally { setLoading(false) } }, []) useEffect(() => { fetchSuggestions(debouncedQuery) }, [debouncedQuery, fetchSuggestions]) // Close suggestions on outside click useEffect(() => { function handler(e) { if (wrapperRef.current && !wrapperRef.current.contains(e.target)) { setOpen(false) } } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, []) function handleSubmit(e) { e.preventDefault() if (query.trim()) { window.location.href = `/search?q=${encodeURIComponent(query.trim())}` } } function handleSelect(item) { window.location.href = item.urls?.web ?? `/${item.slug ?? ''}` } function handleKeyDown(e) { if (e.key === 'Escape') { setOpen(false) inputRef.current?.blur() } } return (
setQuery(e.target.value)} onFocus={() => suggestions.length > 0 && setOpen(true)} onKeyDown={handleKeyDown} placeholder={placeholder} aria-label="Search" autoComplete="off" className="w-full bg-nova-900 border border-nova-800 rounded-lg py-2.5 pl-3.5 pr-10 text-white placeholder-soft outline-none focus:border-accent transition-colors" />
{open && suggestions.length > 0 && ( )}
) }