Skip to content

Commit 5b8a859

Browse files
Add search and filter functionality
1 parent 5401cce commit 5b8a859

File tree

3 files changed

+317
-81
lines changed

3 files changed

+317
-81
lines changed

src/components/layout/Navbar.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { Link, useLocation } from "react-router-dom";
44
import { ShoppingCart, Menu, X, Search } from "lucide-react";
55
import { Button } from "@/components/ui/button";
66
import { useCart } from "@/context/CartContext";
7+
import { SearchDialog } from "@/components/ui/SearchDialog";
78

89
const Navbar = () => {
910
const [isScrolled, setIsScrolled] = useState(false);
1011
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
12+
const [isSearchOpen, setIsSearchOpen] = useState(false);
1113
const { toggleCart, totalItems } = useCart();
1214
const location = useLocation();
1315

@@ -72,7 +74,12 @@ const Navbar = () => {
7274

7375
{/* Right Side - Search & Cart */}
7476
<div className="flex items-center space-x-4 relative z-10">
75-
<Button variant="ghost" size="icon" className="rounded-full">
77+
<Button
78+
variant="ghost"
79+
size="icon"
80+
className="rounded-full"
81+
onClick={() => setIsSearchOpen(true)}
82+
>
7683
<Search className="h-5 w-5" />
7784
</Button>
7885

@@ -140,6 +147,8 @@ const Navbar = () => {
140147
</div>
141148
</div>
142149
</div>
150+
151+
<SearchDialog open={isSearchOpen} onOpenChange={setIsSearchOpen} />
143152
</header>
144153
);
145154
};

src/components/ui/SearchDialog.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { Search, X } from "lucide-react";
4+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
5+
import { Input } from "@/components/ui/input";
6+
import { products } from "@/lib/products";
7+
8+
interface SearchDialogProps {
9+
open: boolean;
10+
onOpenChange: (open: boolean) => void;
11+
}
12+
13+
export const SearchDialog = ({ open, onOpenChange }: SearchDialogProps) => {
14+
const [searchQuery, setSearchQuery] = useState("");
15+
const navigate = useNavigate();
16+
17+
const filteredProducts = products.filter(
18+
(product) =>
19+
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
20+
product.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
21+
product.category.toLowerCase().includes(searchQuery.toLowerCase())
22+
);
23+
24+
const handleProductClick = (productId: string) => {
25+
navigate(`/product/${productId}`);
26+
onOpenChange(false);
27+
setSearchQuery("");
28+
};
29+
30+
return (
31+
<Dialog open={open} onOpenChange={onOpenChange}>
32+
<DialogContent className="sm:max-w-2xl max-h-[80vh] p-0">
33+
<DialogHeader className="p-6 pb-0">
34+
<DialogTitle className="sr-only">Buscar productos</DialogTitle>
35+
<div className="relative">
36+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
37+
<Input
38+
placeholder="Buscar productos..."
39+
value={searchQuery}
40+
onChange={(e) => setSearchQuery(e.target.value)}
41+
className="pl-10 pr-10 h-12 text-base"
42+
autoFocus
43+
/>
44+
{searchQuery && (
45+
<button
46+
onClick={() => setSearchQuery("")}
47+
className="absolute right-3 top-1/2 -translate-y-1/2"
48+
>
49+
<X className="h-4 w-4 text-muted-foreground hover:text-foreground" />
50+
</button>
51+
)}
52+
</div>
53+
</DialogHeader>
54+
55+
<div className="overflow-y-auto max-h-[calc(80vh-120px)] px-6 pb-6">
56+
{searchQuery && (
57+
<div className="mt-4">
58+
{filteredProducts.length > 0 ? (
59+
<div className="space-y-2">
60+
{filteredProducts.map((product) => (
61+
<button
62+
key={product.id}
63+
onClick={() => handleProductClick(product.id)}
64+
className="w-full flex items-center gap-4 p-3 rounded-lg hover:bg-accent transition-colors text-left"
65+
>
66+
<img
67+
src={product.image}
68+
alt={product.name}
69+
className="w-16 h-16 object-cover rounded"
70+
/>
71+
<div className="flex-1 min-w-0">
72+
<h3 className="font-medium text-sm truncate">
73+
{product.name}
74+
</h3>
75+
<p className="text-sm text-muted-foreground truncate">
76+
{product.description}
77+
</p>
78+
<p className="text-sm font-medium mt-1">
79+
${product.price.toFixed(2)}
80+
</p>
81+
</div>
82+
</button>
83+
))}
84+
</div>
85+
) : (
86+
<div className="text-center py-8 text-muted-foreground">
87+
No se encontraron productos
88+
</div>
89+
)}
90+
</div>
91+
)}
92+
93+
{!searchQuery && (
94+
<div className="text-center py-8 text-muted-foreground">
95+
Escribe para buscar productos
96+
</div>
97+
)}
98+
</div>
99+
</DialogContent>
100+
</Dialog>
101+
);
102+
};

0 commit comments

Comments
 (0)