predictive-search.tsx nunca estuvo en el alcance de ninguna sub-fase de ADR 0008 (sub-fase 2 solo tocaba navbar.tsx/footer.tsx), asi que seguia con el borde degradado cian/violeta/fucsia y acentos violeta del esquema arcoiris original -- confirmado visualmente en produccion (captura del usuario) y en el codigo. Reemplaza el wrapper de gradiente por un input con borde solido y un boton circular azul (--color-ari-marino) con la lupa a la derecha, en vez del icono flotando a la izquierda sobre el degradado. Foco usa ring amarillo (--color-ari-coral-claro), igual que el resto de componentes interactivos del sitio (button.tsx). Hover/foco del panel de resultados y del boton 'Buscar en todo el catalogo' pasan de violeta a los tokens de marca.
83 lines
3.8 KiB
TypeScript
83 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { Search, X } from "lucide-react";
|
|
import { FormEvent, useEffect, useState } from "react";
|
|
import { Input } from "@/components/ui/input";
|
|
import type { Product } from "@/lib/headless/types";
|
|
|
|
export function PredictiveSearch() {
|
|
const router = useRouter();
|
|
const [query, setQuery] = useState("");
|
|
const [matches, setMatches] = useState<Product[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const normalized = query.trim();
|
|
|
|
useEffect(() => {
|
|
if (normalized.length < 2) {
|
|
setMatches([]);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const controller = new AbortController();
|
|
setLoading(true);
|
|
const timer = setTimeout(() => {
|
|
fetch(`/api/search?q=${encodeURIComponent(normalized)}`, {
|
|
signal: controller.signal,
|
|
})
|
|
.then((response) => response.json())
|
|
.then((data: { products?: Product[] }) => {
|
|
setMatches((data.products ?? []).slice(0, 6));
|
|
})
|
|
.catch((error) => {
|
|
if ((error as Error).name !== "AbortError") setMatches([]);
|
|
})
|
|
.finally(() => setLoading(false));
|
|
}, 250);
|
|
|
|
return () => {
|
|
clearTimeout(timer);
|
|
controller.abort();
|
|
};
|
|
}, [normalized]);
|
|
|
|
const submit = (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
const value = query.trim();
|
|
if (!value) return;
|
|
router.push(`/catalog?q=${encodeURIComponent(value)}`);
|
|
setQuery("");
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={submit} className="relative w-full">
|
|
<div className="flex items-center rounded-full border-2 border-slate-200 bg-white pl-5 pr-1.5 shadow-sm transition focus-within:border-ari-marino focus-within:ring-4 focus-within:ring-ari-coral-claro/30">
|
|
<Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Busca juguetes, mochilas, personajes…" className="h-[48px] border-0 bg-transparent p-0 shadow-none focus-visible:ring-0" />
|
|
{query && <button type="button" aria-label="Limpiar búsqueda" className="mr-1 shrink-0 rounded-full p-1.5 text-slate-400 hover:bg-slate-100" onClick={() => setQuery("")}><X className="size-4" /></button>}
|
|
<button type="submit" aria-label="Buscar" className="grid size-10 shrink-0 place-items-center rounded-full bg-ari-marino text-white transition hover:brightness-110">
|
|
<Search className="size-4.5" />
|
|
</button>
|
|
</div>
|
|
{loading && normalized.length >= 2 && matches.length === 0 && (
|
|
<div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 rounded-2xl border border-slate-200 bg-white p-4 text-center text-sm text-slate-500 shadow-2xl">
|
|
Buscando…
|
|
</div>
|
|
)}
|
|
{matches.length > 0 && (
|
|
<div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 overflow-hidden rounded-2xl border border-slate-200 bg-white p-2 shadow-2xl">
|
|
{matches.map((product) => (
|
|
<Link key={product.id} href={`/product/${product.slug}`} prefetch={false} className="grid grid-cols-[54px_1fr] items-center gap-3 rounded-xl p-2 hover:bg-ari-marino/5" onClick={() => setQuery("")}>
|
|
<div className="relative aspect-square overflow-hidden rounded-lg bg-slate-100"><Image src={product.thumbnail} alt="" fill sizes="54px" className="object-cover" /></div>
|
|
<div><strong className="line-clamp-1 text-sm">{product.name}</strong><span className="text-xs text-slate-500">{product.category} · {product.collection}</span></div>
|
|
</Link>
|
|
))}
|
|
<button type="submit" className="mt-1 w-full rounded-xl bg-ari-marino px-4 py-3 text-sm font-black text-white hover:brightness-110">Buscar “{query.trim()}” en todo el catálogo</button>
|
|
</div>
|
|
)}
|
|
</form>
|
|
);
|
|
}
|