{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "product-card",
  "type": "registry:ui",
  "title": "Product Card",
  "description": "A Product Card component for displaying product information.",
  "files": [
    {
      "path": "src/registry/ui/product-card.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport Image from \"next/image\";\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { Heart, ShoppingBag, Star } from \"lucide-react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ProductColor = { name: string; value: string };\n\nexport type ProductCardProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  image: { src: string; alt: string };\n  title: React.ReactNode;\n  category?: React.ReactNode;\n  /** Pre-formatted price, e.g. \"$89.99\". */\n  price: React.ReactNode;\n  /** Pre-formatted original price, shown struck through. */\n  compareAtPrice?: React.ReactNode;\n  rating?: number;\n  reviewCount?: number;\n  /** Badges shown over the top-left of the image. */\n  badges?: React.ReactNode[];\n  colors?: ProductColor[];\n  /** Selected color name (controlled). */\n  color?: string;\n  defaultColor?: string;\n  onColorChange?: (color: string) => void;\n  inStock?: boolean;\n  /** Small note under the price, e.g. \"Free shipping\". */\n  stockNote?: React.ReactNode;\n  onAddToCart?: () => void;\n  addToCartLabel?: React.ReactNode;\n  outOfStockLabel?: React.ReactNode;\n  wishlisted?: boolean;\n  defaultWishlisted?: boolean;\n  onWishlistChange?: (wishlisted: boolean) => void;\n  wishlistLabel?: string;\n  imageSizes?: string;\n};\n\nexport function ProductCard({\n  image,\n  title,\n  category,\n  price,\n  compareAtPrice,\n  rating,\n  reviewCount,\n  badges,\n  colors,\n  color,\n  defaultColor,\n  onColorChange,\n  inStock = true,\n  stockNote,\n  onAddToCart,\n  addToCartLabel = \"Add to cart\",\n  outOfStockLabel = \"Out of stock\",\n  wishlisted,\n  defaultWishlisted = false,\n  onWishlistChange,\n  wishlistLabel = \"Add to wishlist\",\n  imageSizes = \"(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw\",\n  className,\n  ...props\n}: ProductCardProps) {\n  const [uncontrolledColor, setUncontrolledColor] = useState(\n    defaultColor ?? colors?.[0]?.name\n  );\n  const selectedColor = color ?? uncontrolledColor;\n  const setColor = (next: string) => {\n    if (color === undefined) setUncontrolledColor(next);\n    onColorChange?.(next);\n  };\n\n  const [uncontrolledWishlisted, setUncontrolledWishlisted] = useState(defaultWishlisted);\n  const isWishlisted = wishlisted ?? uncontrolledWishlisted;\n  const toggleWishlist = () => {\n    const next = !isWishlisted;\n    if (wishlisted === undefined) setUncontrolledWishlisted(next);\n    onWishlistChange?.(next);\n  };\n\n  return (\n    <div\n      data-slot=\"product-card\"\n      className={cn(\n        \"group flex w-full max-w-xs flex-col overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"relative aspect-square overflow-hidden bg-muted\">\n        <Image\n          src={image.src}\n          alt={image.alt}\n          fill\n          sizes={imageSizes}\n          className=\"object-cover transition-transform duration-500 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100\"\n        />\n\n        {badges && badges.length > 0 && (\n          <div className=\"absolute top-3 left-3 flex flex-col items-start gap-1.5\">\n            {badges.map((badge, i) => (\n              <Badge key={i} variant=\"secondary\">\n                {badge}\n              </Badge>\n            ))}\n          </div>\n        )}\n\n        <Button\n          type=\"button\"\n          variant=\"secondary\"\n          size=\"icon\"\n          aria-label={wishlistLabel}\n          aria-pressed={isWishlisted}\n          onClick={toggleWishlist}\n          className=\"absolute top-3 right-3 size-8 rounded-full shadow-sm\"\n        >\n          <Heart className={cn(\"size-4\", isWishlisted && \"fill-current text-destructive\")} />\n        </Button>\n      </div>\n\n      <div className=\"flex flex-1 flex-col gap-3 p-4\">\n        <div className=\"space-y-1\">\n          {(category || rating !== undefined) && (\n            <div className=\"flex items-center justify-between gap-2 text-xs\">\n              {category && <span className=\"text-muted-foreground\">{category}</span>}\n              {rating !== undefined && (\n                <span className=\"ml-auto flex items-center gap-1 text-foreground\">\n                  <Star aria-hidden className=\"size-3.5 fill-current\" />\n                  <span className=\"sr-only\">Rated </span>\n                  {rating.toFixed(1)}\n                  {reviewCount !== undefined && (\n                    <span className=\"text-muted-foreground\">\n                      ({reviewCount}\n                      <span className=\"sr-only\"> reviews</span>)\n                    </span>\n                  )}\n                </span>\n              )}\n            </div>\n          )}\n          <h3 className=\"line-clamp-2 font-medium\">{title}</h3>\n        </div>\n\n        {colors && colors.length > 0 && (\n          <RadioGroupPrimitive.Root\n            aria-label=\"Color\"\n            value={selectedColor}\n            onValueChange={setColor}\n            orientation=\"horizontal\"\n            className=\"flex flex-wrap items-center gap-2\"\n          >\n            {colors.map((c) => (\n              <RadioGroupPrimitive.Item\n                key={c.name}\n                value={c.name}\n                aria-label={c.name}\n                title={c.name}\n                className=\"size-6 rounded-full border shadow-sm outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[state=checked]:ring-2 data-[state=checked]:ring-primary data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-background\"\n                style={{ backgroundColor: c.value }}\n              />\n            ))}\n          </RadioGroupPrimitive.Root>\n        )}\n\n        <div className=\"mt-auto flex items-end justify-between gap-3 pt-1\">\n          <div className=\"min-w-0\">\n            <div className=\"flex items-baseline gap-2\">\n              <span className=\"text-lg font-semibold\">{price}</span>\n              {compareAtPrice && (\n                <s className=\"text-sm text-muted-foreground\">\n                  <span className=\"sr-only\">Original price </span>\n                  {compareAtPrice}\n                </s>\n              )}\n            </div>\n            {stockNote && <p className=\"text-xs text-muted-foreground\">{stockNote}</p>}\n          </div>\n          <Button type=\"button\" size=\"sm\" disabled={!inStock} onClick={onAddToCart}>\n            {inStock && <ShoppingBag aria-hidden />}\n            {inStock ? addToCartLabel : outOfStockLabel}\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
    }
  ],
  "dependencies": [
    "@radix-ui/react-radio-group",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "button"
  ]
}
