create
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { getProducts } from "@/lib/api";
|
||||
import type { ApiProduct } from "@/lib/types";
|
||||
import { formatToman } from "@/lib/format";
|
||||
import { SearchIcon } from "./icons";
|
||||
|
||||
const MIN_QUERY_LENGTH = 3;
|
||||
const DEBOUNCE_MS = 350;
|
||||
|
||||
export default function SearchBox({
|
||||
placeholder = "جستجوی محصولات...",
|
||||
inputHeightClassName = "h-11",
|
||||
}: {
|
||||
placeholder?: string;
|
||||
inputHeightClassName?: string;
|
||||
}) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [results, setResults] = useState<ApiProduct[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function handleQueryChange(value: string) {
|
||||
setQuery(value);
|
||||
if (value.trim().length < MIN_QUERY_LENGTH) {
|
||||
setResults([]);
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
setLoading(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const term = query.trim();
|
||||
if (term.length < MIN_QUERY_LENGTH) return;
|
||||
|
||||
const handle = setTimeout(() => {
|
||||
getProducts({ name: term, pageSize: 6 })
|
||||
.then((res) => {
|
||||
setResults(res.items);
|
||||
setOpen(true);
|
||||
})
|
||||
.catch(() => setResults([]))
|
||||
.finally(() => setLoading(false));
|
||||
}, DEBOUNCE_MS);
|
||||
|
||||
return () => clearTimeout(handle);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
function onClickOutside(event: MouseEvent) {
|
||||
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", onClickOutside);
|
||||
return () => document.removeEventListener("mousedown", onClickOutside);
|
||||
}, []);
|
||||
|
||||
const term = query.trim();
|
||||
const showDropdown = open && term.length >= MIN_QUERY_LENGTH;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative w-full">
|
||||
<div
|
||||
className={`flex w-full items-center gap-2 rounded-full border border-line bg-surface px-4 ${inputHeightClassName}`}
|
||||
>
|
||||
<SearchIcon className="size-4.5 text-body shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => handleQueryChange(e.target.value)}
|
||||
onFocus={() => term.length >= MIN_QUERY_LENGTH && setOpen(true)}
|
||||
placeholder={placeholder}
|
||||
className="flex-1 bg-transparent outline-none text-sm placeholder:text-body/70"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showDropdown && (
|
||||
<div className="absolute top-full mt-2 start-0 w-full bg-white border border-line rounded-xl shadow-lg z-50 max-h-96 overflow-y-auto">
|
||||
{loading && <p className="p-4 text-sm text-body text-center">در حال جستجو...</p>}
|
||||
|
||||
{!loading && results.length === 0 && (
|
||||
<p className="p-4 text-sm text-body text-center">نتیجهای یافت نشد</p>
|
||||
)}
|
||||
|
||||
{!loading &&
|
||||
results.map((product) => (
|
||||
<Link
|
||||
key={product.id}
|
||||
href={`/products/${product.slug}`}
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex items-center gap-3 p-3 hover:bg-surface transition-colors border-b border-line last:border-0"
|
||||
>
|
||||
<img
|
||||
src={product.thumbnailUrl || product.image || "/images/products/product-1.svg"}
|
||||
alt=""
|
||||
className="size-12 rounded-md object-cover shrink-0 bg-surface"
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs md:text-sm text-ink line-clamp-1">
|
||||
{product.nameFa || product.title}
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs font-bold text-brand">
|
||||
{product.store?.minPrice != null
|
||||
? formatToman(product.store.minPrice)
|
||||
: "تماس بگیرید"}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user