{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "product-list",
  "version": "2.1.0",
  "category": "list",
  "meta": {
    "preview": "https://ui.manifest.build/previews/product-list.png",
    "version": "2.1.0",
    "changelog": {
      "1.0.0": "Initial release with list, grid, carousel and picker variants",
      "2.0.0": "BREAKING: Removed id from Product interface. Use array index for selection tracking.",
      "2.0.1": "Added aria-labels to carousel navigation buttons and dots for accessibility",
      "2.0.2": "Moved demo data to separate file for cleaner component code",
      "2.0.3": "Added comprehensive JSDoc documentation",
      "2.0.4": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "2.0.5": "Fixed defensive property access to handle empty objects and null values",
      "2.0.6": "Fixed circular dependency by adding types.ts and demo/data.ts to registry",
      "2.0.7": "Removed default content data - component only renders explicitly provided data",
      "2.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with list, grid, carousel and picker variants",
    "2.0.0": "BREAKING: Removed id from Product interface. Use array index for selection tracking.",
    "2.0.1": "Added aria-labels to carousel navigation buttons and dots for accessibility",
    "2.0.2": "Moved demo data to separate file for cleaner component code",
    "2.0.3": "Added comprehensive JSDoc documentation",
    "2.0.4": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "2.0.5": "Fixed defensive property access to handle empty objects and null values",
    "2.0.6": "Fixed circular dependency by adding types.ts and demo/data.ts to registry",
    "2.0.7": "Removed default content data - component only renders explicitly provided data",
    "2.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
  },
  "title": "Product List",
  "author": "MNFST, Inc",
  "description": "Product list with list, grid, carousel, and picker variants.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "https://ui.manifest.build/r/manifest-types.json"
  ],
  "files": [
    {
      "path": "registry/list/product-list.tsx",
      "content": "'use client'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\nimport { Check, ChevronLeft, ChevronRight, ShoppingCart, Star } from 'lucide-react'\nimport { useCallback, useState } from 'react'\n\n// Import types from shared types file to avoid circular dependencies\nimport type { Product } from './types'\n// Re-export for backward compatibility\nexport type { Product } from './types'\n\nimport { demoProducts } from './demo/list'\n\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * ProductListProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for configuring a versatile product list component with list, grid,\n * carousel, and picker variants.\n */\nexport interface ProductListProps {\n  data?: {\n    /** Array of products to display in the list. */\n    products?: Product[]\n  }\n  actions?: {\n    /** Called when a user selects a product from the list. */\n    onSelectProduct?: (product: Product) => void\n    /** Called when products are added to cart (picker variant only). */\n    onAddToCart?: (products: Product[]) => void\n  }\n  appearance?: {\n    /**\n     * Layout variant for displaying products.\n     * @default \"list\"\n     */\n    variant?: 'list' | 'grid' | 'carousel' | 'picker'\n    /**\n     * Currency code for price formatting (e.g., \"USD\", \"EUR\").\n     * @default \"EUR\"\n     */\n    currency?: string\n    /**\n     * Number of columns for grid variant.\n     * @default 4\n     */\n    columns?: 3 | 4\n    /**\n     * Custom label for the add to cart button (picker variant).\n     * @default \"Add to cart\"\n     */\n    buttonLabel?: string\n  }\n  control?: {\n    /** Index of the currently selected product. */\n    selectedProductIndex?: number\n  }\n}\n\n// Horizontal card for list variant\nfunction ProductHorizontalCard({\n  product,\n  selected,\n  onSelect,\n  formatCurrency\n}: {\n  product: Product\n  selected: boolean\n  onSelect: () => void\n  formatCurrency: (value: number) => string\n}) {\n  return (\n    <button\n      onClick={onSelect}\n      disabled={!product.inStock}\n      className={cn(\n        'w-full flex items-center gap-3 rounded-[12px] border p-2 text-left transition-all cursor-pointer',\n        selected\n          ? 'bg-card border-foreground ring-1 ring-foreground'\n          : 'bg-card border-border hover:border-foreground/50',\n        !product.inStock && 'opacity-50 !cursor-not-allowed'\n      )}\n    >\n      <div className=\"relative h-16 w-16 flex-shrink-0 rounded-md overflow-hidden\">\n        {product.image ? (\n          <img\n            src={product.image}\n            alt={product.name}\n            className=\"h-full w-full object-contain bg-muted/30\"\n          />\n        ) : (\n          <div className=\"h-full w-full bg-muted\" />\n        )}\n        {product.badge && (\n          <span\n            className={cn(\n              'absolute top-1 left-1 px-1 py-0.5 text-[8px] font-medium rounded',\n              product.badge.startsWith('-')\n                ? 'bg-foreground text-background'\n                : 'bg-background text-foreground border border-border'\n            )}\n          >\n            {product.badge}\n          </span>\n        )}\n      </div>\n      <div className=\"flex-1 min-w-0 space-y-0.5\">\n        {product.name && <p className=\"text-sm font-medium truncate\">{product.name}</p>}\n        {product.description && (\n          <p className=\"text-xs truncate text-muted-foreground\">\n            {product.description}\n          </p>\n        )}\n        <div className=\"flex items-center gap-2\">\n          {product.price !== undefined && (\n            <span className=\"text-sm font-semibold\">\n              {formatCurrency(product.price)}\n            </span>\n          )}\n          {product.originalPrice && (\n            <span className=\"text-xs line-through text-muted-foreground\">\n              {formatCurrency(product.originalPrice)}\n            </span>\n          )}\n        </div>\n      </div>\n      <ChevronRight className=\"h-4 w-4 flex-shrink-0 text-muted-foreground\" />\n    </button>\n  )\n}\n\n// List variant\nfunction ListVariant({\n  products,\n  selected,\n  onSelect,\n  formatCurrency\n}: {\n  products: Product[]\n  selected: number | undefined\n  onSelect: (product: Product, index: number) => void\n  formatCurrency: (value: number) => string\n}) {\n  return (\n    <div className=\"w-full space-y-2 p-1 sm:p-0\">\n      {products.slice(0, 4).map((product, index) => (\n        <ProductHorizontalCard\n          key={index}\n          product={product}\n          selected={selected === index}\n          onSelect={() => onSelect(product, index)}\n          formatCurrency={formatCurrency}\n        />\n      ))}\n    </div>\n  )\n}\n\n// Grid variant\nfunction GridVariant({\n  products,\n  selected,\n  onSelect,\n  formatCurrency,\n  columns\n}: {\n  products: Product[]\n  selected: number | undefined\n  onSelect: (product: Product, index: number) => void\n  formatCurrency: (value: number) => string\n  columns: 3 | 4\n}) {\n  const displayProducts = products.slice(0, columns)\n\n  return (\n    <div className=\"w-full p-1 sm:p-0\">\n      <div\n        className={cn(\n          'grid gap-2 sm:gap-3 grid-cols-2',\n          columns === 4 ? 'sm:grid-cols-4' : 'sm:grid-cols-3'\n        )}\n      >\n        {displayProducts.map((product, index) => (\n          <button\n            key={index}\n            onClick={() => onSelect(product, index)}\n            disabled={!product.inStock}\n            className={cn(\n              'rounded-[12px] border text-left transition-all overflow-hidden cursor-pointer',\n              selected === index\n                ? 'bg-card border-foreground ring-1 ring-foreground'\n                : 'bg-card border-border hover:border-foreground/50',\n              !product.inStock && 'opacity-50 !cursor-not-allowed'\n            )}\n          >\n            <div className=\"relative\">\n              {product.image ? (\n                <img\n                  src={product.image}\n                  alt={product.name}\n                  className=\"aspect-square lg:h-28 lg:aspect-auto w-full object-contain bg-muted/30\"\n                />\n              ) : (\n                <div className=\"aspect-square lg:h-28 lg:aspect-auto w-full bg-muted\" />\n              )}\n              {product.badge && (\n                <span\n                  className={cn(\n                    'absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-medium rounded',\n                    product.badge.startsWith('-')\n                      ? 'bg-foreground text-background'\n                      : 'bg-background text-foreground border border-border'\n                  )}\n                >\n                  {product.badge}\n                </span>\n              )}\n            </div>\n            <div className=\"p-2 sm:p-3 space-y-0.5 sm:space-y-1\">\n              {product.name && (\n                <p className=\"text-xs sm:text-sm font-medium line-clamp-1\">\n                  {product.name}\n                </p>\n              )}\n              {product.description && (\n                <p className=\"text-[10px] sm:text-xs line-clamp-1 text-muted-foreground\">\n                  {product.description}\n                </p>\n              )}\n              <div className=\"flex items-center justify-between\">\n                <div className=\"flex items-baseline gap-1\">\n                  {product.price !== undefined && (\n                    <span className=\"text-xs sm:text-sm font-semibold\">\n                      {formatCurrency(product.price)}\n                    </span>\n                  )}\n                  {product.originalPrice && (\n                    <span className=\"text-[10px] sm:text-xs line-through text-muted-foreground\">\n                      {formatCurrency(product.originalPrice)}\n                    </span>\n                  )}\n                </div>\n                {product.rating && (\n                  <div className=\"hidden sm:flex items-center gap-0.5 text-xs text-muted-foreground\">\n                    <Star className=\"h-3 w-3 fill-yellow-400 text-yellow-400\" />\n                    {product.rating}\n                  </div>\n                )}\n              </div>\n              {!product.inStock && (\n                <p className=\"text-[10px] sm:text-xs text-destructive\">\n                  Out of stock\n                </p>\n              )}\n            </div>\n          </button>\n        ))}\n      </div>\n    </div>\n  )\n}\n\n// Carousel variant\nfunction CarouselVariant({\n  products,\n  selected,\n  onSelect,\n  formatCurrency\n}: {\n  products: Product[]\n  selected: number | undefined\n  onSelect: (product: Product, index: number) => void\n  formatCurrency: (value: number) => string\n}) {\n  const [currentIndex, setCurrentIndex] = useState(0)\n\n  const CARD_WIDTH = 160\n  const GAP = 12\n  const desktopTransform = currentIndex * (CARD_WIDTH + GAP)\n  const tabletMaxIndex = Math.max(0, products.length - 2)\n\n  const goLeft = () => {\n    if (currentIndex > 0) {\n      setCurrentIndex(currentIndex - 1)\n    }\n  }\n\n  // Horizontal card for mobile/tablet\n  const HorizontalCard = ({ product, index }: { product: Product; index: number }) => (\n    <button\n      type=\"button\"\n      onClick={() => onSelect(product, index)}\n      disabled={!product.inStock}\n      className={cn(\n        'w-full rounded-[12px] border text-left cursor-pointer',\n        'flex items-center gap-3 p-2',\n        selected === index\n          ? 'bg-card border-foreground shadow-[0_0_0_1px] shadow-foreground'\n          : 'bg-card border-border hover:border-foreground/50',\n        !product.inStock && 'opacity-50 !cursor-not-allowed'\n      )}\n    >\n      <div className=\"relative h-16 w-16 flex-shrink-0 rounded overflow-hidden bg-muted/30\">\n        {product.image && (\n          <img\n            src={product.image}\n            alt={product.name}\n            className=\"h-full w-full object-contain\"\n          />\n        )}\n        {product.badge && (\n          <span\n            className={cn(\n              'absolute top-1 left-1 px-1 py-0.5 text-[8px] font-medium rounded',\n              product.badge.startsWith('-')\n                ? 'bg-foreground text-background'\n                : 'bg-background text-foreground border'\n            )}\n          >\n            {product.badge}\n          </span>\n        )}\n      </div>\n      <div className=\"flex-1 min-w-0\">\n        {product.name && <p className=\"text-sm font-medium truncate\">{product.name}</p>}\n        {product.description && (\n          <p className=\"text-xs text-muted-foreground truncate\">\n            {product.description}\n          </p>\n        )}\n        {product.price !== undefined && <p className=\"text-sm font-semibold\">{formatCurrency(product.price)}</p>}\n      </div>\n    </button>\n  )\n\n  // Dots component\n  const Dots = ({\n    count,\n    active,\n    onDotClick\n  }: {\n    count: number\n    active: number\n    onDotClick: (i: number) => void\n  }) => (\n    <div className=\"flex justify-center gap-1.5 mt-3\">\n      {Array.from({ length: count }).map((_, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={() => onDotClick(i)}\n          aria-label={`Go to slide ${i + 1}`}\n          className={cn(\n            'h-1.5 rounded-full transition-all duration-300 cursor-pointer',\n            i === active\n              ? 'w-4 bg-foreground'\n              : 'w-1.5 bg-foreground/30 hover:bg-foreground/50'\n          )}\n        />\n      ))}\n    </div>\n  )\n\n  const mobileProduct = products[currentIndex]\n  const tabletProducts = [\n    products[Math.min(currentIndex, tabletMaxIndex)],\n    products[Math.min(currentIndex, tabletMaxIndex) + 1]\n  ].filter(Boolean)\n\n  return (\n    <div className=\"w-full\">\n      {/* Mobile: 1 card + dots */}\n      <div className=\"sm:hidden px-0.5\">\n        <div\n          key={currentIndex}\n          className=\"w-full animate-in fade-in slide-in-from-right-4 duration-300\"\n        >\n          {mobileProduct && <HorizontalCard product={mobileProduct} index={currentIndex} />}\n        </div>\n        <Dots\n          count={products.length}\n          active={currentIndex}\n          onDotClick={(i) => setCurrentIndex(i)}\n        />\n      </div>\n\n      {/* Tablet: 2 cards + dots */}\n      <div className=\"hidden sm:block lg:hidden px-0.5\">\n        <div\n          key={Math.min(currentIndex, tabletMaxIndex)}\n          className=\"grid grid-cols-2 gap-2 animate-in fade-in slide-in-from-right-4 duration-300\"\n        >\n          {tabletProducts.map((product, i) => {\n            const productIndex = Math.min(currentIndex, tabletMaxIndex) + i\n            return <HorizontalCard key={productIndex} product={product} index={productIndex} />\n          })}\n        </div>\n        <Dots\n          count={tabletMaxIndex + 1}\n          active={Math.min(currentIndex, tabletMaxIndex)}\n          onDotClick={(i) => setCurrentIndex(i)}\n        />\n      </div>\n\n      {/* Desktop: multi-card carousel */}\n      {(() => {\n        const desktopMaxIndex = Math.max(0, products.length - 4)\n        const isAtEnd = currentIndex >= desktopMaxIndex\n        return (\n          <div className=\"hidden lg:block relative\">\n            <button\n              type=\"button\"\n              onClick={goLeft}\n              disabled={currentIndex === 0}\n              aria-label=\"Previous product\"\n              className={cn(\n                'absolute left-2 top-1/2 -translate-y-1/2 z-10 h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm border shadow-sm flex items-center justify-center cursor-pointer',\n                currentIndex === 0 ? 'opacity-0' : 'hover:bg-background'\n              )}\n            >\n              <ChevronLeft className=\"h-4 w-4\" />\n            </button>\n\n            <button\n              type=\"button\"\n              onClick={() => {\n                if (currentIndex < desktopMaxIndex) {\n                  setCurrentIndex(currentIndex + 1)\n                }\n              }}\n              disabled={isAtEnd}\n              aria-label=\"Next product\"\n              className={cn(\n                'absolute right-2 top-1/2 -translate-y-1/2 z-10 h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm border shadow-sm flex items-center justify-center cursor-pointer',\n                isAtEnd ? 'opacity-0' : 'hover:bg-background'\n              )}\n            >\n              <ChevronRight className=\"h-4 w-4\" />\n            </button>\n\n            <div className=\"overflow-hidden py-1 -mx-1\">\n              <div\n                className=\"flex gap-3 transition-transform duration-300 ease-out px-1\"\n                style={{ transform: `translateX(-${desktopTransform}px)` }}\n              >\n                {products.map((product, index) => (\n                  <button\n                    type=\"button\"\n                    key={index}\n                    onClick={() => onSelect(product, index)}\n                    disabled={!product.inStock}\n                    className={cn(\n                      'flex-shrink-0 w-40 rounded-[12px] border text-left cursor-pointer',\n                      selected === index\n                        ? 'bg-card border-foreground ring-1 ring-foreground'\n                        : 'bg-card border-border hover:border-foreground/50',\n                      !product.inStock && 'opacity-50 !cursor-not-allowed'\n                    )}\n                  >\n                    <div className=\"relative h-28 w-full bg-muted/30 rounded-t-[11px] overflow-hidden\">\n                      {product.image && (\n                        <img\n                          src={product.image}\n                          alt={product.name}\n                          className=\"h-full w-full object-contain\"\n                        />\n                      )}\n                      {product.badge && (\n                        <span\n                          className={cn(\n                            'absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-medium rounded',\n                            product.badge.startsWith('-')\n                              ? 'bg-foreground text-background'\n                              : 'bg-background text-foreground border'\n                          )}\n                        >\n                          {product.badge}\n                        </span>\n                      )}\n                    </div>\n                    <div className=\"p-3 space-y-1\">\n                      {product.name && (\n                        <p className=\"text-sm font-medium truncate\">\n                          {product.name}\n                        </p>\n                      )}\n                      {product.description && (\n                        <p className=\"text-xs text-muted-foreground truncate\">\n                          {product.description}\n                        </p>\n                      )}\n                      {product.price !== undefined && (\n                        <p className=\"text-sm font-semibold\">\n                          {formatCurrency(product.price)}\n                        </p>\n                      )}\n                    </div>\n                  </button>\n                ))}\n              </div>\n            </div>\n          </div>\n        )\n      })()}\n    </div>\n  )\n}\n\n// Picker variant (multi-select with add to cart)\nfunction PickerVariant({\n  products,\n  formatCurrency,\n  onAddToCart,\n  buttonLabel = 'Add to cart'\n}: {\n  products: Product[]\n  formatCurrency: (value: number) => string\n  onAddToCart?: (products: Product[]) => void\n  buttonLabel?: string\n}) {\n  const [selectedIndexes, setSelectedIndexes] = useState<Set<number>>(new Set())\n\n  const handleSelect = useCallback((index: number, product: Product) => {\n    if (!product.inStock) return\n\n    setSelectedIndexes((prev) => {\n      const newSet = new Set(prev)\n      if (newSet.has(index)) {\n        newSet.delete(index)\n      } else {\n        newSet.add(index)\n      }\n      return newSet\n    })\n  }, [])\n\n  const handleSelectAll = useCallback(() => {\n    const availableIndexes = products\n      .map((p, i) => (p.inStock ? i : -1))\n      .filter((i) => i !== -1)\n    const allSelected = availableIndexes.every((i) => selectedIndexes.has(i))\n\n    if (allSelected) {\n      setSelectedIndexes(new Set())\n    } else {\n      setSelectedIndexes(new Set(availableIndexes))\n    }\n  }, [products, selectedIndexes])\n\n  const handleAddToCart = useCallback(() => {\n    const selectedProducts = products.filter((_, i) => selectedIndexes.has(i))\n    onAddToCart?.(selectedProducts)\n  }, [products, selectedIndexes, onAddToCart])\n\n  const availableIndexes = products\n    .map((p, i) => (p.inStock ? i : -1))\n    .filter((i) => i !== -1)\n  const allSelected =\n    availableIndexes.length > 0 &&\n    availableIndexes.every((i) => selectedIndexes.has(i))\n\n  const totalPrice = products\n    .filter((_, i) => selectedIndexes.has(i))\n    .reduce((sum, p) => sum + (p.price ?? 0), 0)\n\n  return (\n    <div className=\"w-full space-y-3 rounded-md sm:rounded-lg p-4 sm:p-0\">\n      {/* Mobile: Card view */}\n      <div className=\"sm:hidden space-y-2 px-0.5\">\n        {products.map((product, index) => (\n          <button\n            key={index}\n            type=\"button\"\n            onClick={() => handleSelect(index, product)}\n            disabled={!product.inStock}\n            className={cn(\n              'w-full flex items-center gap-3 rounded-md sm:rounded-lg border bg-card p-2 text-left transition-all cursor-pointer',\n              selectedIndexes.has(index)\n                ? 'border-foreground ring-1 ring-foreground'\n                : 'border-border hover:border-foreground/30',\n              !product.inStock && 'opacity-50 !cursor-not-allowed'\n            )}\n          >\n            {/* Checkbox */}\n            <div\n              className={cn(\n                'flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border transition-colors',\n                selectedIndexes.has(index)\n                  ? 'bg-foreground border-foreground text-background'\n                  : 'border-border'\n              )}\n            >\n              {selectedIndexes.has(index) && <Check className=\"h-3 w-3\" />}\n            </div>\n\n            {/* Image */}\n            <div className=\"h-12 w-12 flex-shrink-0 rounded overflow-hidden bg-muted/30\">\n              {product.image && (\n                <img\n                  src={product.image}\n                  alt={product.name}\n                  className=\"h-full w-full object-contain\"\n                />\n              )}\n            </div>\n\n            {/* Content */}\n            <div className=\"flex-1 min-w-0\">\n              {product.name && <p className=\"text-sm font-medium truncate\">{product.name}</p>}\n              {product.description && (\n                <p className=\"text-xs text-muted-foreground truncate\">\n                  {product.description}\n                </p>\n              )}\n            </div>\n\n            {/* Price */}\n            <div className=\"text-right flex-shrink-0\">\n              {product.price !== undefined && (\n                <p className=\"text-sm font-semibold\">\n                  {formatCurrency(product.price)}\n                </p>\n              )}\n              {product.originalPrice && (\n                <p className=\"text-xs text-muted-foreground line-through\">\n                  {formatCurrency(product.originalPrice)}\n                </p>\n              )}\n            </div>\n          </button>\n        ))}\n      </div>\n\n      {/* Desktop: Table view */}\n      <div className=\"hidden sm:block overflow-x-auto rounded-md sm:rounded-lg mb-0\">\n        <table className=\"w-full text-sm\">\n          <thead className=\"border-b bg-muted/50\">\n            <tr>\n              <th className=\"w-10 px-3 py-3\">\n                <button\n                  type=\"button\"\n                  onClick={handleSelectAll}\n                  className={cn(\n                    'flex h-4 w-4 items-center justify-center rounded border transition-colors',\n                    allSelected\n                      ? 'bg-foreground border-foreground text-background'\n                      : 'border-border hover:border-foreground/50'\n                  )}\n                  aria-label=\"Select all products\"\n                >\n                  {allSelected && <Check className=\"h-3 w-3\" />}\n                </button>\n              </th>\n              <th className=\"px-3 py-3 text-left font-medium text-muted-foreground\">\n                Product\n              </th>\n              <th className=\"px-3 py-3 text-right font-medium text-muted-foreground\">\n                Price\n              </th>\n            </tr>\n          </thead>\n          <tbody>\n            {products.map((product, index) => (\n              <tr\n                key={index}\n                onClick={() => handleSelect(index, product)}\n                className={cn(\n                  'border-b border-border last:border-0 transition-colors',\n                  product.inStock\n                    ? 'cursor-pointer hover:bg-muted/30'\n                    : 'opacity-50 cursor-not-allowed'\n                )}\n              >\n                <td className=\"px-3 py-3\">\n                  <div\n                    className={cn(\n                      'flex h-4 w-4 items-center justify-center rounded border transition-colors',\n                      selectedIndexes.has(index)\n                        ? 'bg-foreground border-foreground text-background'\n                        : 'border-border'\n                    )}\n                  >\n                    {selectedIndexes.has(index) && (\n                      <Check className=\"h-3 w-3\" />\n                    )}\n                  </div>\n                </td>\n                <td className=\"px-3 py-3\">\n                  <div className=\"flex items-center gap-3\">\n                    <div className=\"h-10 w-10 flex-shrink-0 rounded overflow-hidden bg-muted/30\">\n                      {product.image && (\n                        <img\n                          src={product.image}\n                          alt={product.name}\n                          className=\"h-full w-full object-contain\"\n                        />\n                      )}\n                    </div>\n                    <div className=\"min-w-0\">\n                      {product.name && <p className=\"font-medium truncate\">{product.name}</p>}\n                      {product.description && (\n                        <p className=\"text-xs text-muted-foreground truncate\">\n                          {product.description}\n                        </p>\n                      )}\n                      {!product.inStock && (\n                        <p className=\"text-xs text-destructive\">Out of stock</p>\n                      )}\n                    </div>\n                  </div>\n                </td>\n                <td className=\"px-3 py-3 text-right\">\n                  {product.price !== undefined && (\n                    <p className=\"font-semibold\">\n                      {formatCurrency(product.price)}\n                    </p>\n                  )}\n                  {product.originalPrice && (\n                    <p className=\"text-xs text-muted-foreground line-through\">\n                      {formatCurrency(product.originalPrice)}\n                    </p>\n                  )}\n                </td>\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n\n      {/* Add to cart button */}\n      <div className=\"flex items-center justify-between gap-4 p-3 border-t-1\">\n        <div className=\"text-xs sm:text-sm text-muted-foreground\">\n          {selectedIndexes.size > 0 ? (\n            <span>\n              {selectedIndexes.size} item{selectedIndexes.size !== 1 ? 's' : ''}{' '}\n              selected\n              {' · '}\n              <span className=\"font-medium text-foreground\">\n                {formatCurrency(totalPrice)}\n              </span>\n            </span>\n          ) : (\n            <span>Select products to add to cart</span>\n          )}\n        </div>\n        <Button\n          onClick={handleAddToCart}\n          disabled={selectedIndexes.size === 0}\n          size=\"sm\"\n        >\n          <ShoppingCart className=\"h-4 w-4 mr-1.5\" />\n          {buttonLabel}\n        </Button>\n      </div>\n    </div>\n  )\n}\n\n/**\n * A versatile product list component with list, grid, carousel, and picker variants.\n * Supports product selection, badges, ratings, and add to cart functionality.\n *\n * Features:\n * - Multiple layout variants (list, grid, carousel, picker)\n * - Product cards with images, badges, and pricing\n * - Original price strikethrough for discounts\n * - Star ratings display\n * - Out of stock indication\n * - Single product selection (list/grid/carousel)\n * - Multi-select with cart total (picker)\n * - Responsive layouts for all screen sizes\n * - Animated carousel navigation\n *\n * @component\n * @example\n * ```tsx\n * <ProductList\n *   data={{\n *     products: [\n *       { id: \"1\", name: \"Sneakers\", price: 99, image: \"/shoe.jpg\", rating: 4.5 },\n *       { id: \"2\", name: \"Boots\", price: 149, originalPrice: 199, badge: \"-25%\" }\n *     ]\n *   }}\n *   actions={{\n *     onSelectProduct: (product) => console.log(\"Selected:\", product),\n *     onAddToCart: (products) => console.log(\"Cart:\", products)\n *   }}\n *   appearance={{\n *     variant: \"grid\",\n *     currency: \"USD\",\n *     columns: 4\n *   }}\n * />\n * ```\n */\nexport function ProductList({ data, actions, appearance, control }: ProductListProps) {\n  const resolved: NonNullable<ProductListProps['data']> = data ?? { products: demoProducts }\n  const products = resolved.products ?? []\n  const onSelectProduct = actions?.onSelectProduct\n  const onAddToCart = actions?.onAddToCart\n  const variant = appearance?.variant ?? 'list'\n  const currency = appearance?.currency ?? 'EUR'\n  const columns = appearance?.columns ?? 4\n  const buttonLabel = appearance?.buttonLabel\n  const selectedProductIndex = control?.selectedProductIndex\n  const [selected, setSelected] = useState<number | undefined>(selectedProductIndex)\n\n  const formatCurrency = (value: number) => {\n    return new Intl.NumberFormat('en-US', {\n      style: 'currency',\n      currency,\n      minimumFractionDigits: 0\n    }).format(value)\n  }\n\n  const handleSelect = (product: Product, index: number) => {\n    setSelected(index)\n    onSelectProduct?.(product)\n  }\n\n  if (variant === 'grid') {\n    return (\n      <GridVariant\n        products={products}\n        selected={selected}\n        onSelect={handleSelect}\n        formatCurrency={formatCurrency}\n        columns={columns}\n      />\n    )\n  }\n\n  if (variant === 'carousel') {\n    return (\n      <CarouselVariant\n        products={products}\n        selected={selected}\n        onSelect={handleSelect}\n        formatCurrency={formatCurrency}\n      />\n    )\n  }\n\n  if (variant === 'picker') {\n    return (\n      <PickerVariant\n        products={products}\n        formatCurrency={formatCurrency}\n        onAddToCart={onAddToCart}\n        buttonLabel={buttonLabel}\n      />\n    )\n  }\n\n  return (\n    <ListVariant\n      products={products}\n      selected={selected}\n      onSelect={handleSelect}\n      formatCurrency={formatCurrency}\n    />\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/product-list.tsx"
    },
    {
      "path": "registry/list/demo/list.ts",
      "content": "// Demo data for List category components\n// This file contains sample data used for component previews and documentation\n\nimport type { Product } from '../types'\n\n// Default products for ProductList\nexport const demoProducts: Product[] = [\n  {\n    name: \"Air Force 1 '07\",\n    description: 'Nike',\n    price: 119,\n    image: 'https://ui.manifest.build/demo/shoe-1.png',\n    rating: 4.9,\n    badge: 'New',\n    inStock: true\n  },\n  {\n    name: 'Air Max 90',\n    description: 'Nike',\n    price: 140,\n    image: 'https://ui.manifest.build/demo/shoe-2.png',\n    rating: 4.8,\n    inStock: true\n  },\n  {\n    name: 'Air Max Plus',\n    description: 'Nike',\n    price: 170,\n    originalPrice: 190,\n    image: 'https://ui.manifest.build/demo/shoe-4.png',\n    rating: 4.7,\n    badge: '-10%',\n    inStock: true\n  },\n  {\n    name: 'Dunk Low',\n    description: 'Nike',\n    price: 115,\n    image: 'https://ui.manifest.build/demo/shoe-3.png',\n    rating: 4.6,\n    inStock: true\n  },\n  {\n    name: 'Jordan 1 Low',\n    description: 'Nike',\n    price: 135,\n    image: 'https://ui.manifest.build/demo/shoe-1.png',\n    rating: 4.8,\n    inStock: true\n  },\n  {\n    name: 'Blazer Mid',\n    description: 'Nike',\n    price: 105,\n    image: 'https://ui.manifest.build/demo/shoe-2.png',\n    rating: 4.5,\n    inStock: true\n  },\n]\n\n// Table columns\nexport const demoTableColumns = [\n  { header: 'Name', accessor: 'name' },\n  { header: 'Email', accessor: 'email' },\n  { header: 'Status', accessor: 'status' },\n]\n\n// Table rows\nexport const demoTableRows = [\n  { name: 'John Doe', email: 'john@example.com', status: 'Active' },\n  { name: 'Jane Smith', email: 'jane@example.com', status: 'Pending' },\n  { name: 'Bob Johnson', email: 'bob@example.com', status: 'Active' },\n]\n\n// Table variant: API Usage (default)\nexport const demoApiUsageColumns = [\n  { header: 'Model', accessor: 'model', sortable: true },\n  { header: 'Total Tokens', accessor: 'totalTokens', sortable: true, align: 'right' as const },\n]\n\nexport const demoApiUsageRows = [\n  { model: 'gpt-5', totalTokens: 2267482 },\n  { model: 'claude-3.5-sonnet', totalTokens: 647528 },\n  { model: 'gemini-pro', totalTokens: 428190 },\n  { model: 'llama-3', totalTokens: 312475 },\n]\n\n// Table variant: Models (single select)\nexport const demoModelsColumns = [\n  { header: 'Model', accessor: 'model', sortable: true },\n  { header: 'Provider', accessor: 'provider', sortable: true },\n  { header: 'Context Window', accessor: 'contextWindow', sortable: true, align: 'right' as const },\n]\n\nexport const demoModelsRows = [\n  { model: 'GPT-5', provider: 'OpenAI', contextWindow: '128K' },\n  { model: 'Claude 3.5 Sonnet', provider: 'Anthropic', contextWindow: '200K' },\n  { model: 'Gemini Pro', provider: 'Google', contextWindow: '1M' },\n  { model: 'Llama 3', provider: 'Meta', contextWindow: '128K' },\n]\n\n// Table variant: Export Data (multi select)\nexport const demoExportColumns = [\n  { header: 'Date', accessor: 'date', sortable: true },\n  { header: 'Event', accessor: 'event', sortable: true },\n  { header: 'Users', accessor: 'users', sortable: true, align: 'right' as const },\n]\n\nexport const demoExportRows = [\n  { date: '2025-01-15', event: 'Page View', users: 1243 },\n  { date: '2025-01-15', event: 'Sign Up', users: 87 },\n  { date: '2025-01-14', event: 'Page View', users: 1105 },\n  { date: '2025-01-14', event: 'Purchase', users: 42 },\n]\n",
      "type": "registry:lib",
      "target": "components/ui/demo/list.ts"
    }
  ],
  "categories": [
    "list"
  ],
  "type": "registry:block"
}