|
|
|
@@ -5,7 +5,6 @@ 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() {
|
|
|
|
@@ -54,12 +53,34 @@ export function PredictiveSearch() {
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<form onSubmit={submit} className="relative w-full">
|
|
|
|
|
<div className="rounded-full bg-gradient-to-r from-cyan-400 via-violet-500 to-fuchsia-500 p-[2px] shadow-lg shadow-violet-500/10">
|
|
|
|
|
<div className="relative rounded-full bg-white">
|
|
|
|
|
<Search className="pointer-events-none absolute left-5 top-1/2 z-10 size-5 -translate-y-1/2 text-violet-600" />
|
|
|
|
|
<Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Busca juguetes, mochilas, personajes…" className="h-[52px] border-0 bg-transparent pl-[52px] pr-12 shadow-none focus-visible:ring-violet-300/30" />
|
|
|
|
|
{query && <button type="button" aria-label="Limpiar búsqueda" className="absolute right-4 top-1/2 -translate-y-1/2 rounded-full p-1 text-slate-400 hover:bg-slate-100" onClick={() => setQuery("")}><X className="size-4" /></button>}
|
|
|
|
|
</div>
|
|
|
|
|
<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 propio (no el <Input> compartido) -- ese componente trae su
|
|
|
|
|
propio focus:ring-4 focus:ring-cyan-300/30 hardcodeado que no se
|
|
|
|
|
anulaba de forma confiable via tailwind-merge dentro de este
|
|
|
|
|
contenedor compuesto (input + boton circular), causando un
|
|
|
|
|
segundo ring visible al hacer foco. Mas simple y robusto no
|
|
|
|
|
heredar clases de foco que hay que pelear despues.
|
|
|
|
|
|
|
|
|
|
focus-visible:outline-none (no outline-none a secas): globals.css
|
|
|
|
|
define un outline de accesibilidad global por teclado
|
|
|
|
|
(input:focus-visible, azul de marca) que le gana en
|
|
|
|
|
especificidad a un outline-none plano. El wrapper de este
|
|
|
|
|
componente ya tiene su propio indicador de foco visible
|
|
|
|
|
(focus-within:border-ari-marino + ring amarillo, mismo patron
|
|
|
|
|
que button.tsx) que cumple el mismo proposito de accesibilidad
|
|
|
|
|
sin duplicarlo -- variante focus-visible: en vez de la base
|
|
|
|
|
iguala la especificidad de la regla global (mismo truco que ya
|
|
|
|
|
usa button.tsx) y evita el doble indicador de foco. */}
|
|
|
|
|
<input
|
|
|
|
|
value={query}
|
|
|
|
|
onChange={(event) => setQuery(event.target.value)}
|
|
|
|
|
placeholder="Busca juguetes, mochilas, personajes…"
|
|
|
|
|
className="h-[48px] w-full border-0 bg-transparent p-0 text-sm text-slate-950 focus-visible:outline-none"
|
|
|
|
|
/>
|
|
|
|
|
{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">
|
|
|
|
@@ -69,12 +90,12 @@ export function PredictiveSearch() {
|
|
|
|
|
{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-violet-50" onClick={() => setQuery("")}>
|
|
|
|
|
<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-slate-950 px-4 py-3 text-sm font-black text-white hover:bg-violet-700">Buscar “{query.trim()}” en todo el catálogo</button>
|
|
|
|
|
<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>
|
|
|
|
|