{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "product-grid-page",
  "title": "Product Grid Page",
  "type": "registry:page",
  "files": [
    {
      "path": "packages/registry-react/blocks/product-grid-page/ProductGridPage.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  Check,\n  ChevronLeft,\n  ChevronRight,\n  Heart,\n  RotateCcw,\n  ShoppingBag,\n  SlidersHorizontal,\n  Star,\n  X,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from '@/components/ui/breadcrumb'\nimport { Button } from '@/components/ui/button'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Separator } from '@/components/ui/separator'\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'\nimport { CATEGORIES, COLORS, ProductFilterSidebar } from './ProductFilterSidebar'\n\ninterface ProductColor {\n  id: string\n  name: string\n  class: string\n  colorCode: string\n}\n\ninterface Product {\n  id: string\n  name: string\n  brand: string\n  category: string\n  categoryId: string\n  price: number\n  originalPrice?: number\n  rating: number\n  reviewCount: number\n  badge?: {\n    label: string\n    variant: 'default' | 'secondary' | 'destructive' | 'outline'\n  }\n  image: string\n  colors: ProductColor[]\n  createdAt: number\n  featuredRank: number\n}\n\nexport interface ProductGridPageProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst PRODUCTS: Product[] = [\n  {\n    id: 'prod-1',\n    name: 'XT-6 GORE-TEX Trail Running Shoe',\n    brand: 'Salomon',\n    category: 'Running',\n    categoryId: 'running',\n    price: 220,\n    originalPrice: 245,\n    rating: 4.9,\n    reviewCount: 128,\n    badge: { label: 'Best Seller', variant: 'default' },\n    image: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c1', name: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900', colorCode: 'black' },\n      { id: 'c2', name: 'Sage', class: 'bg-chart-2', colorCode: 'sage' },\n      { id: 'c3', name: 'Navy', class: 'bg-slate-900', colorCode: 'navy' },\n    ],\n    createdAt: 1708000000000,\n    featuredRank: 1,\n  },\n  {\n    id: 'prod-2',\n    name: 'Air Max Pulse Premium',\n    brand: 'Nike',\n    category: 'Sneakers',\n    categoryId: 'sneakers',\n    price: 150,\n    originalPrice: 180,\n    rating: 4.8,\n    reviewCount: 94,\n    badge: { label: '-20% OFF', variant: 'destructive' },\n    image: 'https://images.unsplash.com/photo-1552346154-21d32810aba3?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c4', name: 'White', class: 'bg-zinc-100 dark:bg-zinc-200 border border-zinc-300', colorCode: 'white' },\n      { id: 'c5', name: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900', colorCode: 'black' },\n      { id: 'c6', name: 'Amber', class: 'bg-warning', colorCode: 'amber' },\n    ],\n    createdAt: 1708500000000,\n    featuredRank: 2,\n  },\n  {\n    id: 'prod-3',\n    name: '990v6 Made in USA Core Sneaker',\n    brand: 'New Balance',\n    category: 'Sneakers',\n    categoryId: 'sneakers',\n    price: 200,\n    rating: 4.9,\n    reviewCount: 215,\n    badge: { label: 'New', variant: 'secondary' },\n    image: 'https://images.unsplash.com/photo-1539185441755-769473a23570?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c7', name: 'Sage', class: 'bg-chart-2', colorCode: 'sage' },\n      { id: 'c8', name: 'White', class: 'bg-zinc-100 dark:bg-zinc-200 border border-zinc-300', colorCode: 'white' },\n      { id: 'c9', name: 'Navy', class: 'bg-slate-900', colorCode: 'navy' },\n    ],\n    createdAt: 1709000000000,\n    featuredRank: 3,\n  },\n  {\n    id: 'prod-4',\n    name: 'Ultraboost Light Performance Runner',\n    brand: 'Adidas',\n    category: 'Running',\n    categoryId: 'running',\n    price: 190,\n    originalPrice: 210,\n    rating: 4.7,\n    reviewCount: 86,\n    image: 'https://images.unsplash.com/photo-1587563871167-1ee9c731aefb?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c10', name: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900', colorCode: 'black' },\n      { id: 'c11', name: 'White', class: 'bg-zinc-100 dark:bg-zinc-200 border border-zinc-300', colorCode: 'white' },\n    ],\n    createdAt: 1707000000000,\n    featuredRank: 4,\n  },\n  {\n    id: 'prod-5',\n    name: 'Tor Ultra Hi Waterproof Boot',\n    brand: 'Salomon',\n    category: 'Boots',\n    categoryId: 'boots',\n    price: 260,\n    rating: 4.8,\n    reviewCount: 62,\n    badge: { label: 'Trending', variant: 'outline' },\n    image: 'https://images.unsplash.com/photo-1608231387042-66d1773070a5?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c12', name: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900', colorCode: 'black' },\n      { id: 'c13', name: 'Amber', class: 'bg-warning', colorCode: 'amber' },\n    ],\n    createdAt: 1708800000000,\n    featuredRank: 5,\n  },\n  {\n    id: 'prod-6',\n    name: 'Penny Lug Leather Loafer',\n    brand: 'Adidas',\n    category: 'Loafers',\n    categoryId: 'loafers',\n    price: 160,\n    originalPrice: 190,\n    rating: 4.5,\n    reviewCount: 41,\n    badge: { label: 'Sale', variant: 'destructive' },\n    image: 'https://images.unsplash.com/photo-1533867617858-e7b97e060509?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c14', name: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900', colorCode: 'black' },\n      { id: 'c15', name: 'Navy', class: 'bg-slate-900', colorCode: 'navy' },\n    ],\n    createdAt: 1706000000000,\n    featuredRank: 6,\n  },\n  {\n    id: 'prod-7',\n    name: 'Fresh Foam X Trail 574 v2',\n    brand: 'New Balance',\n    category: 'Running',\n    categoryId: 'running',\n    price: 135,\n    originalPrice: 155,\n    rating: 4.6,\n    reviewCount: 78,\n    image: 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c16', name: 'Sage', class: 'bg-chart-2', colorCode: 'sage' },\n      { id: 'c17', name: 'Amber', class: 'bg-warning', colorCode: 'amber' },\n      { id: 'c18', name: 'White', class: 'bg-zinc-100 dark:bg-zinc-200 border border-zinc-300', colorCode: 'white' },\n    ],\n    createdAt: 1707500000000,\n    featuredRank: 7,\n  },\n  {\n    id: 'prod-8',\n    name: 'Tech Fleece Runner Low Boot',\n    brand: 'Nike',\n    category: 'Boots',\n    categoryId: 'boots',\n    price: 185,\n    rating: 4.7,\n    reviewCount: 53,\n    badge: { label: 'New', variant: 'secondary' },\n    image: 'https://images.unsplash.com/photo-1514989940723-e8e51635b782?w=700&auto=format&fit=crop&q=80',\n    colors: [\n      { id: 'c19', name: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900', colorCode: 'black' },\n      { id: 'c20', name: 'Navy', class: 'bg-slate-900', colorCode: 'navy' },\n      { id: 'c21', name: 'Sage', class: 'bg-chart-2', colorCode: 'sage' },\n    ],\n    createdAt: 1708900000000,\n    featuredRank: 8,\n  },\n]\n\nexport function ProductGridPage({ className, ...props }: ProductGridPageProps) {\n  const [selectedCategories, setSelectedCategories] = React.useState<string[]>([])\n  const [priceRange, setPriceRange] = React.useState<number[]>([50, 300])\n  const [selectedColors, setSelectedColors] = React.useState<string[]>([])\n  const [selectedBrands, setSelectedBrands] = React.useState<string[]>([])\n  const [minRating, setMinRating] = React.useState<number>(0)\n  const [sortBy, setSortBy] = React.useState<string>('featured')\n  const [wishlistIds, setWishlistIds] = React.useState<Set<string>>(new Set(['prod-1', 'prod-5']))\n  const [addedToCartIds, setAddedToCartIds] = React.useState<Set<string>>(new Set())\n  const [activeProductColors, setActiveProductColors] = React.useState<Record<string, string>>({})\n  const [page, setPage] = React.useState<number>(1)\n  const [isMobileFilterOpen, setIsMobileFilterOpen] = React.useState<boolean>(false)\n\n  const totalPages = 3\n\n  const toggleCategory = (id: string, checked: boolean) => {\n    setSelectedCategories((prev) => (checked ? [...prev, id] : prev.filter((item) => item !== id)))\n    setPage(1)\n  }\n\n  const toggleBrand = (id: string, checked: boolean) => {\n    setSelectedBrands((prev) => (checked ? [...prev, id] : prev.filter((item) => item !== id)))\n    setPage(1)\n  }\n\n  const toggleColor = (id: string) => {\n    setSelectedColors((prev) => (prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]))\n    setPage(1)\n  }\n\n  const setRating = (val: number) => {\n    setMinRating((prev) => (prev === val ? 0 : val))\n    setPage(1)\n  }\n\n  const toggleWishlist = (id: string) => {\n    setWishlistIds((prev) => {\n      const next = new Set(prev)\n      if (next.has(id)) {\n        next.delete(id)\n      } else {\n        next.add(id)\n      }\n      return next\n    })\n  }\n\n  const addToCart = (id: string) => {\n    setAddedToCartIds((prev) => {\n      const next = new Set(prev)\n      next.add(id)\n      return next\n    })\n    setTimeout(() => {\n      setAddedToCartIds((prev) => {\n        const next = new Set(prev)\n        next.delete(id)\n        return next\n      })\n    }, 1600)\n  }\n\n  const selectProductColor = (productId: string, colorCode: string) => {\n    setActiveProductColors((prev) => ({\n      ...prev,\n      [productId]: colorCode,\n    }))\n  }\n\n  const resetFilters = () => {\n    setSelectedCategories([])\n    setPriceRange([50, 300])\n    setSelectedColors([])\n    setSelectedBrands([])\n    setMinRating(0)\n    setPage(1)\n  }\n\n  const hasActiveFilters =\n    selectedCategories.length > 0 ||\n    selectedBrands.length > 0 ||\n    selectedColors.length > 0 ||\n    minRating > 0 ||\n    priceRange[0] > 50 ||\n    priceRange[1] < 300\n\n  const activeFilterCount =\n    selectedCategories.length +\n    selectedBrands.length +\n    selectedColors.length +\n    (minRating > 0 ? 1 : 0) +\n    (priceRange[0] > 50 || priceRange[1] < 300 ? 1 : 0)\n\n  const getCategoryCount = (catId: string) => PRODUCTS.filter((p) => p.categoryId === catId).length\n  const getBrandCount = (brand: string) => PRODUCTS.filter((p) => p.brand === brand).length\n\n  const filteredProducts = React.useMemo(() => {\n    return PRODUCTS.filter((product) => {\n      if (selectedCategories.length > 0 && !selectedCategories.includes(product.categoryId)) {\n        return false\n      }\n      if (product.price < priceRange[0] || product.price > priceRange[1]) {\n        return false\n      }\n      if (selectedColors.length > 0) {\n        const hasMatchingColor = product.colors.some((c) => selectedColors.includes(c.colorCode))\n        if (!hasMatchingColor) return false\n      }\n      if (selectedBrands.length > 0 && !selectedBrands.includes(product.brand)) {\n        return false\n      }\n      if (minRating > 0 && product.rating < minRating) {\n        return false\n      }\n      return true\n    }).sort((a, b) => {\n      if (sortBy === 'price-asc') return a.price - b.price\n      if (sortBy === 'price-desc') return b.price - a.price\n      if (sortBy === 'rating-desc') return b.rating - a.rating\n      if (sortBy === 'newest') return b.createdAt - a.createdAt\n      return a.featuredRank - b.featuredRank\n    })\n  }, [selectedCategories, priceRange, selectedColors, selectedBrands, minRating, sortBy])\n\n  const filterSidebarProps = {\n    selectedCategories,\n    priceRange,\n    selectedColors,\n    selectedBrands,\n    minRating,\n    onToggleCategory: toggleCategory,\n    onPriceRangeChange: (val: number[]) => setPriceRange(val),\n    onToggleColor: toggleColor,\n    onToggleBrand: toggleBrand,\n    onSelectRating: setRating,\n    getCategoryCount,\n    getBrandCount,\n  }\n\n  return (\n    <div data-slot=\"product-grid-page\" className={cn('w-full space-y-8', className)} {...props}>\n      {/* Breadcrumb */}\n      <Breadcrumb>\n        <BreadcrumbList>\n          <BreadcrumbItem>\n            <BreadcrumbLink href=\"#\" className=\"inline-flex min-h-6 items-center\">\n              Home\n            </BreadcrumbLink>\n          </BreadcrumbItem>\n          <BreadcrumbSeparator />\n          <BreadcrumbItem>\n            <BreadcrumbLink href=\"#\" className=\"inline-flex min-h-6 items-center\">\n              Shop\n            </BreadcrumbLink>\n          </BreadcrumbItem>\n          <BreadcrumbSeparator />\n          <BreadcrumbItem>\n            <BreadcrumbPage>Footwear</BreadcrumbPage>\n          </BreadcrumbItem>\n        </BreadcrumbList>\n      </Breadcrumb>\n\n      {/* Header & Toolbar */}\n      <div className=\"border-border flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-end sm:justify-between\">\n        <div className=\"space-y-1\">\n          <div className=\"flex items-center gap-3\">\n            <h1 className=\"text-foreground text-2xl font-bold tracking-tight sm:text-3xl\">All Footwear</h1>\n            <Badge variant=\"secondary\" className=\"font-normal tabular-nums\">\n              {filteredProducts.length} items\n            </Badge>\n          </div>\n          <p className=\"text-muted-foreground text-xs sm:text-sm\">\n            Engineered for performance, trail endurance, and everyday urban utility.\n          </p>\n        </div>\n\n        <div className=\"flex items-center gap-2\">\n          {/* Mobile Filter Drawer */}\n          <Sheet open={isMobileFilterOpen} onOpenChange={setIsMobileFilterOpen}>\n            <SheetTrigger asChild>\n              <Button variant=\"outline\" size=\"sm\" className=\"gap-2 md:hidden\">\n                <SlidersHorizontal className=\"size-4\" />\n                <span>Filters</span>\n                {activeFilterCount > 0 && (\n                  <Badge variant=\"secondary\" className=\"ml-1 px-1.5 py-0 text-xs\">\n                    {activeFilterCount}\n                  </Badge>\n                )}\n              </Button>\n            </SheetTrigger>\n            <SheetContent side=\"left\" className=\"w-full max-w-xs overflow-y-auto p-6\">\n              <SheetHeader className=\"text-left\">\n                <div className=\"flex items-center justify-between\">\n                  <SheetTitle className=\"text-base font-semibold\">Filters</SheetTitle>\n                  {hasActiveFilters && (\n                    <Button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      className=\"text-muted-foreground hover:text-foreground h-auto p-0 text-xs font-normal\"\n                      onClick={resetFilters}\n                    >\n                      Reset all\n                    </Button>\n                  )}\n                </div>\n                <SheetDescription className=\"text-xs\">\n                  Narrow down footwear by category, price, and specs.\n                </SheetDescription>\n              </SheetHeader>\n              <div className=\"mt-6\">\n                <ProductFilterSidebar idPrefix=\"mobile-\" {...filterSidebarProps} />\n              </div>\n            </SheetContent>\n          </Sheet>\n\n          {/* Sort Select */}\n          <div className=\"flex items-center gap-2\">\n            <span className=\"text-muted-foreground hidden text-xs sm:inline-block\">Sort by:</span>\n            <Select value={sortBy} onValueChange={setSortBy}>\n              <SelectTrigger className=\"h-9 w-44 text-xs sm:text-sm\">\n                <SelectValue placeholder=\"Sort by\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"featured\">Featured</SelectItem>\n                <SelectItem value=\"price-asc\">Price: Low to High</SelectItem>\n                <SelectItem value=\"price-desc\">Price: High to Low</SelectItem>\n                <SelectItem value=\"rating-desc\">Highest Rated</SelectItem>\n                <SelectItem value=\"newest\">Newest</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n        </div>\n      </div>\n\n      {/* 2-Column Main Layout */}\n      <div className=\"grid grid-cols-1 gap-8 md:grid-cols-[240px_1fr] lg:grid-cols-[260px_1fr]\">\n        {/* Left Desktop Sidebar */}\n        <aside className=\"hidden space-y-6 md:block\" aria-label=\"Product filters\">\n          <div className=\"flex items-center justify-between\">\n            <h2 className=\"text-foreground text-sm font-semibold tracking-tight\">Filters</h2>\n            {hasActiveFilters && (\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"text-muted-foreground hover:text-foreground h-auto p-0 text-xs font-normal\"\n                onClick={resetFilters}\n              >\n                Reset all\n              </Button>\n            )}\n          </div>\n\n          <Separator />\n          <ProductFilterSidebar {...filterSidebarProps} />\n        </aside>\n\n        {/* Right Product Grid Area */}\n        <section className=\"min-w-0 space-y-6\">\n          {/* Active Filter Chips */}\n          {hasActiveFilters && (\n            <div className=\"flex flex-wrap items-center gap-2\">\n              {selectedCategories.map((catId) => (\n                <Badge key={catId} variant=\"secondary\" className=\"gap-1.5 py-1 text-xs\">\n                  <span>Category: {CATEGORIES.find((c) => c.id === catId)?.label}</span>\n                  <button\n                    type=\"button\"\n                    className=\"hover:text-foreground text-muted-foreground focus-visible:outline-none\"\n                    aria-label=\"Remove category filter\"\n                    onClick={() => toggleCategory(catId, false)}\n                  >\n                    <X className=\"size-3\" />\n                  </button>\n                </Badge>\n              ))}\n\n              {selectedBrands.map((brand) => (\n                <Badge key={brand} variant=\"secondary\" className=\"gap-1.5 py-1 text-xs\">\n                  <span>Brand: {brand}</span>\n                  <button\n                    type=\"button\"\n                    className=\"hover:text-foreground text-muted-foreground focus-visible:outline-none\"\n                    aria-label=\"Remove brand filter\"\n                    onClick={() => toggleBrand(brand, false)}\n                  >\n                    <X className=\"size-3\" />\n                  </button>\n                </Badge>\n              ))}\n\n              {selectedColors.map((colorId) => (\n                <Badge key={colorId} variant=\"secondary\" className=\"gap-1.5 py-1 text-xs\">\n                  <span>Color: {COLORS.find((c) => c.id === colorId)?.label}</span>\n                  <button\n                    type=\"button\"\n                    className=\"hover:text-foreground text-muted-foreground focus-visible:outline-none\"\n                    aria-label=\"Remove color filter\"\n                    onClick={() => toggleColor(colorId)}\n                  >\n                    <X className=\"size-3\" />\n                  </button>\n                </Badge>\n              ))}\n\n              {minRating > 0 && (\n                <Badge variant=\"secondary\" className=\"gap-1.5 py-1 text-xs\">\n                  <span>Rating: {minRating}+ Stars</span>\n                  <button\n                    type=\"button\"\n                    className=\"hover:text-foreground text-muted-foreground focus-visible:outline-none\"\n                    aria-label=\"Remove rating filter\"\n                    onClick={() => setMinRating(0)}\n                  >\n                    <X className=\"size-3\" />\n                  </button>\n                </Badge>\n              )}\n\n              {(priceRange[0] > 50 || priceRange[1] < 300) && (\n                <Badge variant=\"secondary\" className=\"gap-1.5 py-1 text-xs\">\n                  <span>\n                    Price: ${priceRange[0]} - ${priceRange[1]}\n                  </span>\n                  <button\n                    type=\"button\"\n                    className=\"hover:text-foreground text-muted-foreground focus-visible:outline-none\"\n                    aria-label=\"Reset price filter\"\n                    onClick={() => setPriceRange([50, 300])}\n                  >\n                    <X className=\"size-3\" />\n                  </button>\n                </Badge>\n              )}\n\n              <Button\n                variant=\"link\"\n                size=\"sm\"\n                className=\"text-muted-foreground hover:text-foreground h-auto px-1 text-xs\"\n                onClick={resetFilters}\n              >\n                Clear all\n              </Button>\n            </div>\n          )}\n\n          {/* Empty State */}\n          {filteredProducts.length === 0 ? (\n            <div className=\"border-border flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center\">\n              <div className=\"bg-muted text-muted-foreground flex size-12 items-center justify-center rounded-full\">\n                <RotateCcw className=\"size-6\" />\n              </div>\n              <h3 className=\"text-foreground mt-4 text-base font-semibold\">No matching footwear found</h3>\n              <p className=\"text-muted-foreground mt-1 max-w-sm text-xs sm:text-sm\">\n                Try adjusting your price range, clearing selected colors, or resetting your filters to see more results.\n              </p>\n              <Button variant=\"outline\" size=\"sm\" className=\"mt-4 gap-2\" onClick={resetFilters}>\n                <RotateCcw className=\"size-3.5\" />\n                <span>Reset filters</span>\n              </Button>\n            </div>\n          ) : (\n            /* 3-Column Product Grid */\n            <div className=\"grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3\">\n              {filteredProducts.map((product) => (\n                <div\n                  key={product.id}\n                  className=\"group border-border bg-card hover:border-border/80 relative flex flex-col overflow-hidden rounded-xl border p-3 shadow-xs transition-colors duration-200 hover:shadow-md\"\n                >\n                  {/* Image Aspect Square Container */}\n                  <div className=\"bg-muted/40 relative aspect-square w-full overflow-hidden rounded-lg\">\n                    <img\n                      src={product.image}\n                      alt={product.name}\n                      className=\"h-full w-full object-cover object-center transition-transform duration-500 ease-out group-hover:scale-105\"\n                      loading=\"lazy\"\n                    />\n\n                    {/* Top Left Badge */}\n                    {product.badge && (\n                      <div className=\"absolute top-2.5 left-2.5 z-10\">\n                        <Badge variant={product.badge.variant} className=\"text-xs shadow-xs\">\n                          {product.badge.label}\n                        </Badge>\n                      </div>\n                    )}\n\n                    {/* Top Right Wishlist Heart */}\n                    <button\n                      type=\"button\"\n                      aria-label=\"Toggle wishlist\"\n                      className={cn(\n                        'bg-background/80 hover:bg-background text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute top-2.5 right-2.5 z-10 flex size-8 items-center justify-center rounded-full shadow-xs backdrop-blur-xs transition-colors focus-visible:ring-2 focus-visible:outline-none',\n                        wishlistIds.has(product.id) && 'text-destructive hover:text-destructive',\n                      )}\n                      onClick={(e) => {\n                        e.stopPropagation()\n                        toggleWishlist(product.id)\n                      }}\n                    >\n                      <Heart\n                        className={cn(\n                          'size-4 transition-transform active:scale-90',\n                          wishlistIds.has(product.id) && 'fill-current',\n                        )}\n                      />\n                    </button>\n\n                    {/* Quick Add Button (Visible on hover & focus) */}\n                    <div className=\"absolute inset-x-2.5 bottom-2.5 z-10 opacity-100 sm:translate-y-2 sm:opacity-0 sm:transition-colors sm:duration-200 sm:group-hover:translate-y-0 sm:group-hover:opacity-100\">\n                      <Button\n                        size=\"sm\"\n                        className=\"w-full gap-1.5 text-xs font-medium shadow-sm\"\n                        variant=\"default\"\n                        onClick={(e) => {\n                          e.stopPropagation()\n                          addToCart(product.id)\n                        }}\n                      >\n                        {addedToCartIds.has(product.id) ? (\n                          <>\n                            <Check className=\"size-3.5\" />\n                            <span>Added to Cart</span>\n                          </>\n                        ) : (\n                          <>\n                            <ShoppingBag className=\"size-3.5\" />\n                            <span>Quick Add</span>\n                          </>\n                        )}\n                      </Button>\n                    </div>\n                  </div>\n\n                  {/* Card Body */}\n                  <div className=\"mt-3 flex flex-1 flex-col justify-between space-y-2\">\n                    <div>\n                      {/* Color swatches preview */}\n                      <div className=\"flex items-center gap-1.5\">\n                        {product.colors.map((color) => (\n                          <button\n                            key={color.id}\n                            type=\"button\"\n                            title={color.name}\n                            className={cn(\n                              'size-6 rounded-full transition-opacity hover:opacity-90 focus-visible:outline-none',\n                              color.class,\n                              activeProductColors[product.id] === color.colorCode\n                                ? 'ring-1.5 ring-primary ring-offset-background ring-offset-1'\n                                : '',\n                            )}\n                            onClick={(e) => {\n                              e.stopPropagation()\n                              selectProductColor(product.id, color.colorCode)\n                            }}\n                          >\n                            <span className=\"sr-only\">{color.name}</span>\n                          </button>\n                        ))}\n                        <span className=\"text-muted-foreground ml-1 text-xs\">\n                          {product.colors.length} {product.colors.length === 1 ? 'color' : 'colors'}\n                        </span>\n                      </div>\n\n                      {/* Brand & Category */}\n                      <div className=\"text-muted-foreground mt-2 flex items-center justify-between text-xs\">\n                        <span>\n                          {product.brand} · {product.category}\n                        </span>\n                        <div className=\"flex items-center gap-1\">\n                          <Star className=\"fill-warning text-warning size-3\" />\n                          <span className=\"text-foreground font-medium\">{product.rating}</span>\n                          <span className=\"text-muted-foreground\">({product.reviewCount})</span>\n                        </div>\n                      </div>\n\n                      {/* Title */}\n                      <h3\n                        className=\"text-foreground group-hover:text-primary mt-1 line-clamp-1 cursor-pointer text-sm font-medium transition-colors\"\n                        title={product.name}\n                      >\n                        {product.name}\n                      </h3>\n                    </div>\n\n                    {/* Price Row */}\n                    <div className=\"flex min-w-0 flex-wrap items-baseline gap-2 pt-1\">\n                      <span className=\"text-foreground text-base font-semibold\">${product.price.toFixed(2)}</span>\n                      {product.originalPrice && (\n                        <span className=\"text-muted-foreground text-xs line-through\">\n                          ${product.originalPrice.toFixed(2)}\n                        </span>\n                      )}\n                      {product.originalPrice && (\n                        <span className=\"text-success text-xs font-medium\">\n                          Save ${(product.originalPrice - product.price).toFixed(2)}\n                        </span>\n                      )}\n                    </div>\n                  </div>\n                </div>\n              ))}\n            </div>\n          )}\n\n          {/* Pagination Footer */}\n          {filteredProducts.length > 0 && (\n            <nav className=\"border-border flex items-center justify-between border-t pt-6\" aria-label=\"Pagination\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"gap-1 text-xs\"\n                disabled={page === 1}\n                onClick={() => setPage((p) => Math.max(1, p - 1))}\n              >\n                <ChevronLeft className=\"size-4\" />\n                <span>Previous</span>\n              </Button>\n\n              <div className=\"flex items-center gap-1\">\n                {Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => (\n                  <Button\n                    key={n}\n                    variant={n === page ? 'outline' : 'ghost'}\n                    size=\"sm\"\n                    className=\"size-8 p-0 text-xs tabular-nums\"\n                    aria-current={n === page ? 'page' : undefined}\n                    onClick={() => setPage(n)}\n                  >\n                    {n}\n                  </Button>\n                ))}\n              </div>\n\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"gap-1 text-xs\"\n                disabled={page === totalPages}\n                onClick={() => setPage((p) => Math.min(totalPages, p + 1))}\n              >\n                <span>Next</span>\n                <ChevronRight className=\"size-4\" />\n              </Button>\n            </nav>\n          )}\n        </section>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:page",
      "target": "~/components/blocks/ProductGridPage.tsx"
    },
    {
      "path": "packages/registry-react/blocks/product-grid-page/ProductFilterSidebar.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Check, Star } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/components/ui/checkbox'\nimport { Separator } from '@/components/ui/separator'\nimport { Slider } from '@/components/ui/slider'\n\nexport interface CategoryOption {\n  id: string\n  label: string\n}\n\nexport interface BrandOption {\n  id: string\n  label: string\n}\n\nexport interface ColorOption {\n  id: string\n  label: string\n  class: string\n}\n\nexport const CATEGORIES: CategoryOption[] = [\n  { id: 'sneakers', label: 'Sneakers' },\n  { id: 'running', label: 'Running' },\n  { id: 'boots', label: 'Boots' },\n  { id: 'loafers', label: 'Loafers' },\n]\n\nexport const BRANDS: BrandOption[] = [\n  { id: 'Nike', label: 'Nike' },\n  { id: 'Adidas', label: 'Adidas' },\n  { id: 'New Balance', label: 'New Balance' },\n  { id: 'Salomon', label: 'Salomon' },\n]\n\nexport const COLORS: ColorOption[] = [\n  { id: 'black', label: 'Black', class: 'bg-zinc-950 dark:bg-zinc-900 border-zinc-800' },\n  { id: 'white', label: 'White', class: 'bg-zinc-100 dark:bg-zinc-200 border-zinc-300' },\n  { id: 'sage', label: 'Sage', class: 'bg-chart-2 border-chart-2' },\n  { id: 'navy', label: 'Navy', class: 'bg-slate-900 border-slate-800' },\n  { id: 'amber', label: 'Amber', class: 'bg-warning border-warning' },\n]\n\nexport const RATING_OPTIONS = [\n  { value: 4.8, label: '4.8 stars & above' },\n  { value: 4.5, label: '4.5 stars & above' },\n  { value: 4.0, label: '4.0 stars & above' },\n]\n\nexport interface ProductFilterSidebarProps {\n  idPrefix?: string\n  selectedCategories: string[]\n  priceRange: number[]\n  selectedColors: string[]\n  selectedBrands: string[]\n  minRating: number\n  onToggleCategory: (id: string, checked: boolean) => void\n  onPriceRangeChange: (value: number[]) => void\n  onToggleColor: (id: string) => void\n  onToggleBrand: (id: string, checked: boolean) => void\n  onSelectRating: (value: number) => void\n  getCategoryCount: (id: string) => number\n  getBrandCount: (brand: string) => number\n}\n\nexport function ProductFilterSidebar({\n  idPrefix = '',\n  selectedCategories,\n  priceRange,\n  selectedColors,\n  selectedBrands,\n  minRating,\n  onToggleCategory,\n  onPriceRangeChange,\n  onToggleColor,\n  onToggleBrand,\n  onSelectRating,\n  getCategoryCount,\n  getBrandCount,\n}: ProductFilterSidebarProps) {\n  return (\n    <div className=\"space-y-6\">\n      {/* Categories */}\n      <div className=\"space-y-3\">\n        <h3 className=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Category</h3>\n        <div className=\"space-y-2.5\">\n          {CATEGORIES.map((cat) => (\n            <div key={cat.id} className=\"flex items-center justify-between\">\n              <label\n                htmlFor={`${idPrefix}cat-${cat.id}`}\n                className=\"text-foreground flex cursor-pointer items-center gap-2 text-sm\"\n              >\n                <Checkbox\n                  id={`${idPrefix}cat-${cat.id}`}\n                  checked={selectedCategories.includes(cat.id)}\n                  onCheckedChange={(checked) => onToggleCategory(cat.id, checked === true)}\n                />\n                <span>{cat.label}</span>\n              </label>\n              <span className=\"text-muted-foreground text-xs tabular-nums\">{getCategoryCount(cat.id)}</span>\n            </div>\n          ))}\n        </div>\n      </div>\n\n      <Separator />\n\n      {/* Price Range */}\n      <div className=\"space-y-3\">\n        <div className=\"flex items-center justify-between\">\n          <h3 className=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Price Range</h3>\n          <span className=\"text-foreground text-xs font-medium tabular-nums\">\n            ${priceRange[0]} – ${priceRange[1]}\n          </span>\n        </div>\n        <Slider\n          value={priceRange}\n          onValueChange={(val) => onPriceRangeChange(val)}\n          min={50}\n          max={300}\n          step={5}\n          range\n          className=\"py-2\"\n        />\n        <div className=\"text-muted-foreground flex items-center justify-between text-xs\">\n          <span>$50</span>\n          <span>$300</span>\n        </div>\n      </div>\n\n      <Separator />\n\n      {/* Colors */}\n      <div className=\"space-y-3\">\n        <h3 className=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Color</h3>\n        <div className=\"flex flex-wrap gap-2\">\n          {COLORS.map((color) => (\n            <button\n              key={color.id}\n              type=\"button\"\n              title={color.label}\n              className={cn(\n                'focus-visible:ring-ring relative flex size-7 items-center justify-center rounded-full border transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:outline-none',\n                color.class,\n                selectedColors.includes(color.id)\n                  ? 'ring-primary ring-offset-background ring-2 ring-offset-2'\n                  : 'border-border',\n              )}\n              onClick={() => onToggleColor(color.id)}\n            >\n              {selectedColors.includes(color.id) && (\n                <Check className={cn('size-3.5', color.id === 'white' ? 'text-zinc-950' : 'text-white')} />\n              )}\n              <span className=\"sr-only\">{color.label}</span>\n            </button>\n          ))}\n        </div>\n      </div>\n\n      <Separator />\n\n      {/* Brands */}\n      <div className=\"space-y-3\">\n        <h3 className=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Brand</h3>\n        <div className=\"space-y-2.5\">\n          {BRANDS.map((b) => (\n            <div key={b.id} className=\"flex items-center justify-between\">\n              <label\n                htmlFor={`${idPrefix}brand-${b.id}`}\n                className=\"text-foreground flex cursor-pointer items-center gap-2 text-sm\"\n              >\n                <Checkbox\n                  id={`${idPrefix}brand-${b.id}`}\n                  checked={selectedBrands.includes(b.id)}\n                  onCheckedChange={(checked) => onToggleBrand(b.id, checked === true)}\n                />\n                <span>{b.label}</span>\n              </label>\n              <span className=\"text-muted-foreground text-xs tabular-nums\">{getBrandCount(b.id)}</span>\n            </div>\n          ))}\n        </div>\n      </div>\n\n      <Separator />\n\n      {/* Rating */}\n      <div className=\"space-y-3\">\n        <h3 className=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Customer Rating</h3>\n        <div className=\"space-y-1.5\">\n          {RATING_OPTIONS.map((opt) => (\n            <button\n              key={opt.value}\n              type=\"button\"\n              className={cn(\n                'hover:bg-muted/60 flex w-full items-center justify-between rounded-md px-2 py-1.5 text-left text-xs transition-colors',\n                minRating === opt.value ? 'bg-muted text-foreground font-medium' : 'text-muted-foreground',\n              )}\n              onClick={() => onSelectRating(opt.value)}\n            >\n              <div className=\"flex items-center gap-1.5\">\n                <Star className=\"fill-warning text-warning size-3.5\" />\n                <span>{opt.label}</span>\n              </div>\n              {minRating === opt.value && <Check className=\"text-primary size-3.5\" />}\n            </button>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:page",
      "target": "~/components/blocks/ProductFilterSidebar.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/breadcrumb.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/checkbox.json",
    "https://uipkge.dev/r/react/select.json",
    "https://uipkge.dev/r/react/separator.json",
    "https://uipkge.dev/r/react/sheet.json",
    "https://uipkge.dev/r/react/slider.json"
  ],
  "description": "E-commerce product listing page (PLP) featuring breadcrumb navigation, category header with sorting and mobile filter drawer, a 2-column layout with a multi-facet sidebar (categories, price range slider, color swatches, brands, ratings), and a responsive 3-column product grid with image hover zoom, badges, wishlist toggle, color swatches, quick add to cart, and pagination.",
  "categories": [
    "commerce",
    "ecommerce"
  ]
}