{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"ecommerce-category-filters-filter-demo","type":"registry:block","title":"Filter Demo","files":[{"path":"components/blocks/ecommerce/category-filters/filter-demo.tsx","content":"import * as React from 'react';\nimport { startTransition, useState, useCallback, useMemo, memo } from 'react';\nimport {\n  Check,\n  ChevronDown,\n  ChevronUp,\n  Heart,\n  ShoppingCart,\n  Star,\n  X,\n} from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport { Badge } from '@/components/ui/badge';\nimport { Checkbox } from '@/components/ui/checkbox';\nimport { Separator } from '@/components/ui/separator';\nimport { ScrollArea } from '@/components/ui/scroll-area';\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from '@/components/ui/collapsible';\n\ninterface Product {\n  id: number;\n  name: string;\n  category: string;\n  price: number;\n  originalPrice: number;\n  rating: number;\n  reviews: number;\n  image: string;\n  brand: string;\n  color: string;\n  size: string;\n  features: string[];\n}\n\ninterface FilterState {\n  categories: string[];\n  brands: string[];\n  colors: string[];\n  sizes: string[];\n  ratings: number[];\n}\n\n// Memoized product card component to prevent unnecessary re-renders\nconst ProductCard = memo(({ product }: { product: Product }) => (\n  <div className=\"group overflow-hidden rounded-lg border\">\n    <div className=\"relative aspect-square overflow-hidden bg-gray-100\">\n      <img\n        src={product.image}\n        alt={product.name}\n        className=\"h-full w-full object-cover transition-transform group-hover:scale-105\"\n      />\n      <Button\n        size=\"icon\"\n        variant=\"ghost\"\n        className=\"absolute top-2 right-2 h-8 w-8 rounded-full bg-white/80 backdrop-blur-sm\"\n      >\n        <Heart className=\"h-4 w-4\" />\n      </Button>\n      {product.originalPrice > product.price && (\n        <Badge className=\"absolute top-2 left-2\" variant=\"destructive\">\n          Sale\n        </Badge>\n      )}\n    </div>\n    <div className=\"p-4\">\n      <div className=\"flex justify-between\">\n        <div>\n          <h3 className=\"font-medium\">{product.name}</h3>\n          <p className=\"text-muted-foreground text-sm\">{product.category}</p>\n        </div>\n        <div className=\"text-right\">\n          <div className=\"flex items-center gap-1\">\n            <Star className=\"fill-primary text-primary h-4 w-4\" />\n            <span className=\"text-sm font-medium\">{product.rating}</span>\n          </div>\n          <p className=\"text-muted-foreground text-xs\">\n            {product.reviews} reviews\n          </p>\n        </div>\n      </div>\n      <div className=\"mt-4 flex items-center justify-between\">\n        <div className=\"flex items-center gap-2\">\n          <span className=\"font-medium\">${product.price}</span>\n          {product.originalPrice > product.price && (\n            <span className=\"text-muted-foreground text-sm line-through\">\n              ${product.originalPrice}\n            </span>\n          )}\n        </div>\n        <Button size=\"sm\" className=\"h-8\">\n          <ShoppingCart className=\"mr-2 h-4 w-4\" />\n          Add\n        </Button>\n      </div>\n    </div>\n  </div>\n));\nProductCard.displayName = 'ProductCard';\n\nexport default function FilterDemo() {\n  const [filters, setFilters] = useState<FilterState>({\n    categories: [],\n    brands: [],\n    colors: [],\n    sizes: [],\n    ratings: [],\n  });\n\n  // Sample product data\n  const products: Product[] = [\n    {\n      id: 1,\n      name: 'Classic Leather Jacket',\n      category: 'Outerwear',\n      price: 299,\n      originalPrice: 399,\n      rating: 4.8,\n      reviews: 128,\n      image:\n        'https://images.unsplash.com/photo-1551028719-00167b16eac5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80',\n      brand: 'Levi&apos;s',\n      color: 'Black',\n      size: 'M',\n      features: ['Water Resistant', 'Genuine Leather'],\n    },\n    {\n      id: 2,\n      name: 'Sport Running Shoes',\n      category: 'Shoes',\n      price: 149,\n      originalPrice: 199,\n      rating: 4.9,\n      reviews: 256,\n      image:\n        'https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80',\n      brand: 'Nike',\n      color: 'Red',\n      size: 'L',\n      features: ['Breathable', 'Cushioned Sole'],\n    },\n    {\n      id: 3,\n      name: 'Casual Cotton T-Shirt',\n      category: 'Clothing',\n      price: 29,\n      originalPrice: 39,\n      rating: 4.5,\n      reviews: 192,\n      image:\n        'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80',\n      brand: 'H&M',\n      color: 'White',\n      size: 'S',\n      features: ['100% Cotton', 'Machine Washable'],\n    },\n    {\n      id: 4,\n      name: 'Fitness Tracker Watch',\n      category: 'Accessories',\n      price: 199,\n      originalPrice: 249,\n      rating: 4.7,\n      reviews: 156,\n      image:\n        'https://images.unsplash.com/photo-1523275335684-37898b6baf30?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80',\n      brand: 'Under Armour',\n      color: 'Black',\n      size: 'M',\n      features: ['Heart Rate Monitor', 'GPS', 'Water Resistant'],\n    },\n    {\n      id: 5,\n      name: 'Winter Puffer Jacket',\n      category: 'Outerwear',\n      price: 179,\n      originalPrice: 229,\n      rating: 4.6,\n      reviews: 98,\n      image:\n        'https://images.unsplash.com/photo-1539533018447-63fcce2678e3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80',\n      brand: 'New Balance',\n      color: 'Blue',\n      size: 'XL',\n      features: ['Insulated', 'Water Resistant', 'Windproof'],\n    },\n    {\n      id: 6,\n      name: 'Slim Fit Jeans',\n      category: 'Clothing',\n      price: 79,\n      originalPrice: 99,\n      rating: 4.4,\n      reviews: 112,\n      image:\n        'https://images.unsplash.com/photo-1541099649105-f69ad21f3246?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80',\n      brand: 'Levi&apos;s',\n      color: 'Blue',\n      size: 'M',\n      features: ['Stretch Denim', 'Slim Fit'],\n    },\n  ];\n\n  // Filter products based on selected filters - memoized to prevent recalculation\n  const filteredProducts = useMemo(() => {\n    return products.filter((product) => {\n      // Category filter\n      if (\n        filters.categories.length > 0 &&\n        !filters.categories.includes(product.category)\n      ) {\n        return false;\n      }\n\n      // Brand filter\n      if (\n        filters.brands.length > 0 &&\n        !filters.brands.includes(product.brand)\n      ) {\n        return false;\n      }\n\n      // Color filter\n      if (\n        filters.colors.length > 0 &&\n        !filters.colors.includes(product.color)\n      ) {\n        return false;\n      }\n\n      // Size filter\n      if (filters.sizes.length > 0 && !filters.sizes.includes(product.size)) {\n        return false;\n      }\n\n      // Rating filter\n      if (\n        filters.ratings.length > 0 &&\n        !filters.ratings.some((rating) => product.rating >= rating)\n      ) {\n        return false;\n      }\n\n      return true;\n    });\n  }, [filters]);\n\n  const handleFilterChange = useCallback((newFilters: FilterState) => {\n    // Use requestAnimationFrame to schedule the update for the next frame\n    // This helps prevent UI jank during rapid updates\n    requestAnimationFrame(() => {\n      setFilters(newFilters);\n    });\n  }, []);\n\n  return (\n    <div className=\"mx-auto w-full max-w-7xl p-4 md:p-6\">\n      <h1 className=\"mb-6 text-2xl font-bold\">Shop Products</h1>\n\n      <div className=\"flex flex-col gap-8 lg:flex-row\">\n        {/* Sidebar Filters */}\n        <SidebarFilters onFilterChange={handleFilterChange} />\n\n        {/* Product Grid */}\n        <div className=\"flex-1\">\n          <div className=\"text-muted-foreground mb-4 text-sm\">\n            {filteredProducts.length} products found\n          </div>\n\n          <div className=\"grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3\">\n            {filteredProducts.map((product) => (\n              <ProductCard key={product.id} product={product} />\n            ))}\n          </div>\n\n          {filteredProducts.length === 0 && (\n            <div className=\"py-12 text-center\">\n              <h3 className=\"text-lg font-medium\">No products found</h3>\n              <p className=\"text-muted-foreground mt-1\">\n                Try adjusting your filters to find what you&apos;re looking for.\n              </p>\n            </div>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\n// Sidebar Filters Component\nfunction SidebarFilters({\n  onFilterChange,\n}: {\n  onFilterChange?: (filters: FilterState) => void;\n}) {\n  const [filters, setFilters] = useState<FilterState>({\n    categories: [],\n    brands: [],\n    colors: [],\n    sizes: [],\n    ratings: [],\n  });\n\n  const [activeFiltersCount, setActiveFiltersCount] = useState(0);\n  const [showMobileFilters, setShowMobileFilters] = useState(false);\n\n  const toggleFilter = useCallback(\n    (type: keyof FilterState, value: string | number) => {\n      setFilters((prev) => {\n        const currentValues = prev[type] as (string | number)[];\n        const newValues = currentValues.includes(value)\n          ? currentValues.filter((v) => v !== value)\n          : [...currentValues, value];\n\n        const updated = { ...prev, [type]: newValues };\n\n        // Count active filters\n        let count = 0;\n        if (updated.categories.length) count += updated.categories.length;\n        if (updated.brands.length) count += updated.brands.length;\n        if (updated.colors.length) count += updated.colors.length;\n        if (updated.sizes.length) count += updated.sizes.length;\n        if (updated.ratings.length) count += updated.ratings.length;\n\n        setActiveFiltersCount(count);\n\n        // Use startTransition to avoid blocking the UI during updates\n        startTransition(() => {\n          onFilterChange?.(updated);\n        });\n\n        return updated;\n      });\n    },\n    [onFilterChange]\n  );\n\n  const clearFilters = useCallback(() => {\n    const resetFilters: FilterState = {\n      categories: [],\n      brands: [],\n      colors: [],\n      sizes: [],\n      ratings: [],\n    };\n    setFilters(resetFilters);\n    setActiveFiltersCount(0);\n    onFilterChange?.(resetFilters);\n  }, [onFilterChange]);\n\n  const categories = [\n    'Clothing',\n    'Shoes',\n    'Accessories',\n    'Sportswear',\n    'Outerwear',\n    'Formal Wear',\n    'Casual Wear',\n  ];\n\n  const brands = [\n    'Nike',\n    'Adidas',\n    'Puma',\n    'Under Armour',\n    'New Balance',\n    'Levi&apos;s',\n    'H&M',\n    'Zara',\n  ];\n\n  const colors = [\n    { name: 'Black', value: '#000000' },\n    { name: 'White', value: '#FFFFFF' },\n    { name: 'Red', value: '#FF0000' },\n    { name: 'Blue', value: '#0000FF' },\n    { name: 'Green', value: '#00FF00' },\n    { name: 'Yellow', value: '#FFFF00' },\n    { name: 'Purple', value: '#800080' },\n    { name: 'Orange', value: '#FFA500' },\n  ];\n\n  const sizes = ['XS', 'S', 'M', 'L', 'XL', 'XXL'];\n\n  const ratings = [4, 3, 2, 1];\n\n  const FilterSection = ({\n    title,\n    children,\n    defaultOpen = true,\n  }: {\n    title: string;\n    children: React.ReactNode;\n    defaultOpen?: boolean;\n  }) => {\n    const [isOpen, setIsOpen] = useState(defaultOpen);\n\n    return (\n      <Collapsible open={isOpen} onOpenChange={setIsOpen} className=\"w-full\">\n        <div className=\"flex items-center justify-between py-2\">\n          <h3 className=\"text-sm font-medium\">{title}</h3>\n          <CollapsibleTrigger asChild>\n            <Button variant=\"ghost\" size=\"sm\" className=\"h-8 w-8 p-0\">\n              {isOpen ? (\n                <ChevronUp className=\"h-4 w-4\" />\n              ) : (\n                <ChevronDown className=\"h-4 w-4\" />\n              )}\n            </Button>\n          </CollapsibleTrigger>\n        </div>\n        <CollapsibleContent className=\"pb-4\">{children}</CollapsibleContent>\n        <Separator />\n      </Collapsible>\n    );\n  };\n\n  return (\n    <>\n      {/* Mobile Filter Button */}\n      <div className=\"mb-4 flex items-center justify-between lg:hidden\">\n        <Button\n          variant=\"outline\"\n          onClick={() => setShowMobileFilters(true)}\n          className=\"flex items-center gap-2\"\n        >\n          Filters\n          {activeFiltersCount > 0 && (\n            <Badge variant=\"secondary\" className=\"ml-1\">\n              {activeFiltersCount}\n            </Badge>\n          )}\n        </Button>\n        {activeFiltersCount > 0 && (\n          <Button variant=\"ghost\" size=\"sm\" onClick={clearFilters}>\n            Clear all\n          </Button>\n        )}\n      </div>\n\n      {/* Mobile Filters Overlay */}\n      {showMobileFilters && (\n        <div className=\"bg-background/80 fixed inset-0 z-50 backdrop-blur-sm lg:hidden\">\n          <div className=\"bg-background fixed inset-y-0 right-0 z-50 w-full max-w-xs p-6 shadow-lg\">\n            <div className=\"mb-4 flex items-center justify-between\">\n              <h2 className=\"text-lg font-semibold\">Filters</h2>\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                onClick={() => setShowMobileFilters(false)}\n              >\n                <X className=\"h-4 w-4\" />\n              </Button>\n            </div>\n\n            <ScrollArea className=\"h-[calc(100vh-8rem)]\">\n              <div className=\"pr-4\">\n                {/* Filter Sections */}\n                <FilterContent />\n              </div>\n            </ScrollArea>\n\n            <div className=\"mt-6 flex items-center justify-between border-t pt-4\">\n              <Button variant=\"outline\" onClick={clearFilters}>\n                Clear all\n              </Button>\n              <Button onClick={() => setShowMobileFilters(false)}>\n                Apply filters\n              </Button>\n            </div>\n          </div>\n        </div>\n      )}\n\n      {/* Desktop Filters */}\n      <div className=\"hidden w-64 shrink-0 lg:block\">\n        <div className=\"mb-4 flex items-center justify-between\">\n          <h2 className=\"text-lg font-semibold\">Filters</h2>\n          {activeFiltersCount > 0 && (\n            <Button variant=\"ghost\" size=\"sm\" onClick={clearFilters}>\n              Clear all\n            </Button>\n          )}\n        </div>\n        <FilterContent />\n      </div>\n    </>\n  );\n\n  function FilterContent() {\n    return (\n      <div className=\"space-y-1\">\n        <FilterSection title=\"Categories\">\n          <div className=\"space-y-2\">\n            {categories.map((category) => (\n              <div key={category} className=\"flex items-center space-x-2\">\n                <Checkbox\n                  id={`category-${category}`}\n                  checked={filters.categories.includes(category)}\n                  onCheckedChange={() => toggleFilter('categories', category)}\n                />\n                <label\n                  htmlFor={`category-${category}`}\n                  className=\"text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n                >\n                  {category}\n                </label>\n              </div>\n            ))}\n          </div>\n        </FilterSection>\n\n        <FilterSection title=\"Brands\">\n          <div className=\"space-y-2\">\n            {brands.map((brand) => (\n              <div key={brand} className=\"flex items-center space-x-2\">\n                <Checkbox\n                  id={`brand-${brand}`}\n                  checked={filters.brands.includes(brand)}\n                  onCheckedChange={() => toggleFilter('brands', brand)}\n                />\n                <label\n                  htmlFor={`brand-${brand}`}\n                  className=\"text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n                >\n                  {brand}\n                </label>\n              </div>\n            ))}\n          </div>\n        </FilterSection>\n\n        <FilterSection title=\"Colors\">\n          <div className=\"grid grid-cols-4 gap-2\">\n            {colors.map((color) => (\n              <div\n                key={color.name}\n                className=\"flex flex-col items-center gap-1\"\n              >\n                <button\n                  className={cn(\n                    'border-input flex h-8 w-8 items-center justify-center rounded-full border',\n                    filters.colors.includes(color.name) && 'ring-primary ring-2'\n                  )}\n                  style={{ backgroundColor: color.value }}\n                  onClick={() => toggleFilter('colors', color.name)}\n                >\n                  {filters.colors.includes(color.name) && (\n                    <Check\n                      className={cn(\n                        'h-4 w-4',\n                        ['White', 'Yellow'].includes(color.name)\n                          ? 'text-black'\n                          : 'text-white'\n                      )}\n                    />\n                  )}\n                </button>\n                <span className=\"text-xs\">{color.name}</span>\n              </div>\n            ))}\n          </div>\n        </FilterSection>\n\n        <FilterSection title=\"Sizes\">\n          <div className=\"grid grid-cols-3 gap-2\">\n            {sizes.map((size) => (\n              <Button\n                key={size}\n                variant={filters.sizes.includes(size) ? 'default' : 'outline'}\n                size=\"sm\"\n                onClick={() => toggleFilter('sizes', size)}\n                className=\"h-8\"\n              >\n                {size}\n              </Button>\n            ))}\n          </div>\n        </FilterSection>\n\n        <FilterSection title=\"Rating\">\n          <div className=\"space-y-2\">\n            {ratings.map((rating) => (\n              <div key={rating} className=\"flex items-center space-x-2\">\n                <Checkbox\n                  id={`rating-${rating}`}\n                  checked={filters.ratings.includes(rating)}\n                  onCheckedChange={() => toggleFilter('ratings', rating)}\n                />\n                <label\n                  htmlFor={`rating-${rating}`}\n                  className=\"flex items-center text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n                >\n                  {Array.from({ length: rating }).map((_, i) => (\n                    <svg\n                      key={i}\n                      className=\"h-4 w-4 fill-current text-yellow-400\"\n                      viewBox=\"0 0 24 24\"\n                    >\n                      <path d=\"M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.63L12 2L9.19 8.63L2 9.24L7.46 13.97L5.82 21L12 17.27Z\" />\n                    </svg>\n                  ))}\n                  <span className=\"ml-1\">& Up</span>\n                </label>\n              </div>\n            ))}\n          </div>\n        </FilterSection>\n      </div>\n    );\n  }\n}\n","type":"registry:component","target":"components/blocks/ecommerce/category-filters/filter-demo.tsx"},{"path":"components/ui/button.tsx","content":"import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n        destructive:\n          \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5\",\n        lg: \"h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        icon: \"size-9\",\n        \"icon-xs\":\n          \"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\":\n          \"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md\",\n        \"icon-lg\": \"size-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean\n  }) {\n  const Comp = asChild ? Slot.Root : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n","type":"registry:ui","target":"components/ui/button.tsx"},{"path":"components/ui/badge.tsx","content":"import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        secondary:\n          \"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\",\n        destructive:\n          \"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20\",\n        outline:\n          \"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Badge({\n  className,\n  variant = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"span\"> &\n  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"span\"\n\n  return (\n    <Comp\n      data-slot=\"badge\"\n      data-variant={variant}\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nexport { Badge, badgeVariants }\n","type":"registry:ui","target":"components/ui/badge.tsx"},{"path":"components/ui/checkbox.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Checkbox as CheckboxPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CheckIcon } from \"lucide-react\"\n\nfunction Checkbox({\n  className,\n  ...props\n}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        \"peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n      >\n        <CheckIcon\n        />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n","type":"registry:ui","target":"components/ui/checkbox.tsx"},{"path":"components/ui/separator.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { Separator as SeparatorPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px \",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n","type":"registry:ui","target":"components/ui/separator.tsx"},{"path":"components/ui/scroll-area.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ScrollArea({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {\n  return (\n    <ScrollAreaPrimitive.Root\n      data-slot=\"scroll-area\"\n      className={cn(\"relative\", className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        data-slot=\"scroll-area-viewport\"\n        className=\"size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      <ScrollBar />\n      <ScrollAreaPrimitive.Corner />\n    </ScrollAreaPrimitive.Root>\n  )\n}\n\nfunction ScrollBar({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {\n  return (\n    <ScrollAreaPrimitive.ScrollAreaScrollbar\n      data-slot=\"scroll-area-scrollbar\"\n      data-orientation={orientation}\n      orientation={orientation}\n      className={cn(\n        \"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent\",\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.ScrollAreaThumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"relative flex-1 rounded-full bg-border\"\n      />\n    </ScrollAreaPrimitive.ScrollAreaScrollbar>\n  )\n}\n\nexport { ScrollArea, ScrollBar }\n","type":"registry:ui","target":"components/ui/scroll-area.tsx"},{"path":"components/ui/collapsible.tsx","content":"\"use client\"\n\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\"\n\nfunction Collapsible({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />\n}\n\nfunction CollapsibleTrigger({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleTrigger\n      data-slot=\"collapsible-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction CollapsibleContent({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleContent\n      data-slot=\"collapsible-content\"\n      {...props}\n    />\n  )\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent }\n","type":"registry:ui","target":"components/ui/collapsible.tsx"}],"description":"A sidebar of category filters with checkboxes, price sliders, and color swatches beside a product grid. Refines large catalogs on a shop listing page.","dependencies":["class-variance-authority","lucide-react","radix-ui"]}