143 lines
5.4 KiB
TypeScript
143 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Check, ChevronDown, LayoutGrid, Search } from 'lucide-react';
|
|
import { catalogService } from '@/services/api/catalogService';
|
|
|
|
export const SearchBar: React.FC = () => {
|
|
const router = useRouter();
|
|
const [category, setCategory] = useState('All Categories');
|
|
const [categories, setCategories] = useState<string[]>(['All Categories']);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [open, setOpen] = useState(false);
|
|
const [catQuery, setCatQuery] = useState('');
|
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
catalogService.getCategories().then((res) => {
|
|
if (res && res.length > 0) {
|
|
setCategories(['All Categories', ...res.map((c) => c.name)]);
|
|
}
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const onDoc = (event: MouseEvent) => {
|
|
if (!rootRef.current?.contains(event.target as Node)) setOpen(false);
|
|
};
|
|
const onKey = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape') setOpen(false);
|
|
};
|
|
document.addEventListener('mousedown', onDoc);
|
|
document.addEventListener('keydown', onKey);
|
|
return () => {
|
|
document.removeEventListener('mousedown', onDoc);
|
|
document.removeEventListener('keydown', onKey);
|
|
};
|
|
}, []);
|
|
|
|
const filtered = catQuery.trim()
|
|
? categories.filter((name) =>
|
|
name.toLowerCase().includes(catQuery.trim().toLowerCase())
|
|
)
|
|
: categories;
|
|
|
|
const handleSearchSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const catQueryParam =
|
|
category === 'All Categories' ? '' : `category=${encodeURIComponent(category)}`;
|
|
const searchQueryStr = searchQuery.trim()
|
|
? `search=${encodeURIComponent(searchQuery.trim())}`
|
|
: '';
|
|
const queryParts = [catQueryParam, searchQueryStr].filter(Boolean).join('&');
|
|
const path = queryParts ? `/shop?${queryParts}` : '/shop';
|
|
router.push(path);
|
|
};
|
|
|
|
return (
|
|
<form
|
|
onSubmit={handleSearchSubmit}
|
|
className="flex flex-1 max-w-[720px] items-center bg-white rounded-lg border border-gray-200 overflow-visible shadow-sm h-[46px] relative"
|
|
>
|
|
<div ref={rootRef} className="relative h-full hidden lg:block">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen((value) => !value)}
|
|
className={`h-full min-w-[160px] max-w-[200px] px-3.5 border-r border-gray-200 rounded-l-lg inline-flex items-center gap-2 text-left transition ${
|
|
open
|
|
? 'bg-primary/5 text-primary'
|
|
: 'bg-gray-50 text-[#1a1a1a] hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<LayoutGrid className="w-3.5 h-3.5 shrink-0 text-primary" />
|
|
<span className="flex-1 text-[12px] font-semibold truncate">{category}</span>
|
|
<ChevronDown
|
|
size={14}
|
|
className={`shrink-0 transition-transform ${
|
|
open ? 'rotate-180 text-primary' : 'text-gray-400'
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute left-0 top-[calc(100%+8px)] w-[280px] bg-white border border-gray-200 rounded-xl shadow-[0_12px_32px_rgba(15,23,42,0.12)] overflow-hidden z-[130]">
|
|
<div className="p-2 border-b border-gray-100">
|
|
<div className="relative">
|
|
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
|
<input
|
|
type="text"
|
|
value={catQuery}
|
|
onChange={(e) => setCatQuery(e.target.value)}
|
|
placeholder="Search categories..."
|
|
className="w-full h-9 pl-8 pr-3 rounded-lg bg-gray-50 border border-gray-200 text-[12px] outline-none focus:border-primary focus:ring-2 focus:ring-primary/15"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<ul className="max-h-72 overflow-y-auto py-1">
|
|
{filtered.map((cat) => {
|
|
const isSelected = category === cat;
|
|
return (
|
|
<li key={cat}>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setCategory(cat);
|
|
setOpen(false);
|
|
setCatQuery('');
|
|
}}
|
|
className={`w-full px-3.5 py-2.5 flex items-center gap-2.5 text-left text-[13px] transition ${
|
|
isSelected
|
|
? 'bg-primary/5 text-primary font-semibold'
|
|
: 'text-[#1a1a1a] hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
<span className="flex-1 truncate">{cat}</span>
|
|
{isSelected ? <Check className="w-4 h-4 shrink-0" /> : null}
|
|
</button>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="Search products..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="flex-1 px-4 text-[13px] text-gray-800 placeholder-gray-400 focus:outline-none h-full font-medium"
|
|
/>
|
|
|
|
<button
|
|
type="submit"
|
|
className="h-full bg-primary hover:brightness-95 text-white px-7 flex items-center justify-center font-bold text-sm transition-colors rounded-r-lg"
|
|
title="Search"
|
|
>
|
|
Search
|
|
</button>
|
|
</form>
|
|
);
|
|
};
|