auto-claude: subtask-1-2 - Create CategoryFilter component with category buttons/dropdown
Created CategoryFilter component with the following features: - Client component with 'use client' directive and framer-motion animations - Props: categories, selectedCategory, onCategoryChange, className - Uses lucide-react Tag icon - Responsive design: horizontal scrollable on mobile, grid layout on desktop - Active category highlighted with bg-[#697565] text-white - Includes 'All' option to clear filter - Follows established patterns from SearchBar and ContactForm - Consistent styling with bg-zinc-900/50 and border-zinc-800 - Smooth transitions and hover states Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import { Tag } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface CategoryFilterProps {
|
||||
categories: string[];
|
||||
selectedCategory: string | null;
|
||||
onCategoryChange: (category: string | null) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function CategoryFilter({
|
||||
categories,
|
||||
selectedCategory,
|
||||
onCategoryChange,
|
||||
className = ''
|
||||
}: CategoryFilterProps) {
|
||||
const allCategories = ['All', ...categories];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className={`${className}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Tag className="h-5 w-5 text-zinc-400 flex-shrink-0" />
|
||||
<h3 className="text-sm font-medium text-zinc-300">Filter by Category</h3>
|
||||
</div>
|
||||
|
||||
{/* Mobile: Horizontal scrollable */}
|
||||
<div className="md:hidden overflow-x-auto pb-2 -mx-4 px-4">
|
||||
<div className="flex gap-2 min-w-max">
|
||||
{allCategories.map((category) => {
|
||||
const isActive = category === 'All'
|
||||
? selectedCategory === null
|
||||
: selectedCategory === category;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => onCategoryChange(category === 'All' ? null : category)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
|
||||
isActive
|
||||
? 'bg-[#697565] text-white'
|
||||
: 'bg-zinc-900/50 text-zinc-400 border border-zinc-800 hover:bg-zinc-800/50 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop: Grid */}
|
||||
<div className="hidden md:grid md:grid-cols-4 lg:grid-cols-6 gap-2">
|
||||
{allCategories.map((category) => {
|
||||
const isActive = category === 'All'
|
||||
? selectedCategory === null
|
||||
: selectedCategory === category;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => onCategoryChange(category === 'All' ? null : category)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
||||
isActive
|
||||
? 'bg-[#697565] text-white'
|
||||
: 'bg-zinc-900/50 text-zinc-400 border border-zinc-800 hover:bg-zinc-800/50 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user