More
This commit is contained in:
162
src/components/product-list.tsx
Normal file
162
src/components/product-list.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
'use client';
|
||||
|
||||
import { SearchIcon, SlidersHorizontal } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
|
||||
// Mock product data
|
||||
const products = [
|
||||
{ id: 1, name: 'Laptop', category: 'Electronics', price: 999.99 },
|
||||
{ id: 2, name: 'Smartphone', category: 'Electronics', price: 699.99 },
|
||||
{ id: 3, name: 'Headphones', category: 'Electronics', price: 199.99 },
|
||||
{ id: 4, name: 'T-shirt', category: 'Clothing', price: 24.99 },
|
||||
{ id: 5, name: 'Jeans', category: 'Clothing', price: 49.99 },
|
||||
{ id: 6, name: 'Sneakers', category: 'Footwear', price: 89.99 },
|
||||
{ id: 7, name: 'Watch', category: 'Accessories', price: 149.99 },
|
||||
{ id: 8, name: 'Backpack', category: 'Accessories', price: 79.99 },
|
||||
];
|
||||
|
||||
const categories = ['Electronics', 'Clothing', 'Footwear', 'Accessories'];
|
||||
|
||||
export function ProductListComponent() {
|
||||
const [priceRange, setPriceRange] = useState([0, 1000]);
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
|
||||
const handleCategoryChange = (category: string) => {
|
||||
setSelectedCategories((prev) =>
|
||||
prev.includes(category)
|
||||
? prev.filter((c) => c !== category)
|
||||
: [...prev, category],
|
||||
);
|
||||
};
|
||||
|
||||
const filteredProducts = products
|
||||
.filter(
|
||||
(product) =>
|
||||
(selectedCategories.length === 0 ||
|
||||
selectedCategories.includes(product.category)) &&
|
||||
product.price >= priceRange[0] &&
|
||||
product.price <= priceRange[1] &&
|
||||
product.name.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
if (sortBy === 'price') return a.price - b.price;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='container mx-auto px-4 py-8'>
|
||||
<div className='flex flex-col md:flex-row gap-8'>
|
||||
{/* Sidebar with filters */}
|
||||
<aside className='w-full md:w-1/4 space-y-6'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Filters</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-6'>
|
||||
<div>
|
||||
<h3 className='text-lg font-semibold mb-2'>Categories</h3>
|
||||
{categories.map((category) => (
|
||||
<div key={category} className='flex items-center space-x-2'>
|
||||
<Checkbox
|
||||
id={category}
|
||||
checked={selectedCategories.includes(category)}
|
||||
onCheckedChange={() => handleCategoryChange(category)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={category}
|
||||
className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
||||
>
|
||||
{category}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className='text-lg font-semibold mb-2'>Price Range</h3>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1000}
|
||||
step={10}
|
||||
value={priceRange}
|
||||
onValueChange={setPriceRange}
|
||||
/>
|
||||
<div className='flex justify-between mt-2'>
|
||||
<span>${priceRange[0]}</span>
|
||||
<span>${priceRange[1]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</aside>
|
||||
|
||||
{/* Main content */}
|
||||
<main className='w-full md:w-3/4'>
|
||||
<div className='flex flex-col sm:flex-row justify-between items-center mb-6 gap-4'>
|
||||
<div className='relative w-full sm:w-auto'>
|
||||
<SearchIcon className='absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400' />
|
||||
<Input
|
||||
type='search'
|
||||
placeholder='Search products...'
|
||||
className='pl-8'
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center space-x-2 w-full sm:w-auto'>
|
||||
<Select value={sortBy} onValueChange={setSortBy}>
|
||||
<SelectTrigger className='w-full sm:w-[180px]'>
|
||||
<SelectValue placeholder='Sort by' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='name'>Name</SelectItem>
|
||||
<SelectItem value='price'>Price</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant='outline' size='icon'>
|
||||
<SlidersHorizontal className='h-4 w-4' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6'>
|
||||
{filteredProducts.map((product) => (
|
||||
<Card key={product.id}>
|
||||
<CardHeader>
|
||||
<CardTitle>{product.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Category: {product.category}</p>
|
||||
<p className='font-bold mt-2'>${product.price.toFixed(2)}</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className='w-full'>Add to Cart</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user