{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "product-card", "title": "Product Card", "description": "Responsive product card with badges, rating, price, and quick actions.", "dependencies": [ "class-variance-authority", "lucide-react" ], "registryDependencies": [ "https://lumo-www.vercel.app/r/lib.json", "https://lumo-www.vercel.app/r/price-display.json", "https://lumo-www.vercel.app/r/rating-stars.json" ], "files": [ { "path": "packages/core/src/components/lumo/product-card.tsx", "content": "import * as React from \"react\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\nimport { ShoppingCart, Heart, Eye } from \"lucide-react\"\r\nimport type { Product, Variant } from \"@/lib/lumo/types\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\nimport { PriceDisplay } from \"@/components/lumo/price-display\"\r\nimport { RatingStars } from \"@/components/lumo/rating-stars\"\r\n\r\nconst productCardVariants = cva(\r\n \"group relative flex flex-col overflow-hidden rounded-lg border bg-card text-card-foreground transition-all duration-200 hover:shadow-lg\",\r\n {\r\n variants: {\r\n layout: {\r\n default: \"\",\r\n compact: \"\",\r\n featured: \"sm:flex-row\",\r\n },\r\n },\r\n defaultVariants: {\r\n layout: \"default\",\r\n },\r\n }\r\n)\r\n\r\nexport interface ProductCardProps\r\n extends Omit, \"onClick\">,\r\n VariantProps {\r\n product: Product\r\n onAddToCart?: (product: Product, variant: Variant) => void\r\n onQuickView?: (product: Product) => void\r\n onToggleWishlist?: (product: Product) => void\r\n isInWishlist?: boolean\r\n showQuickActions?: boolean\r\n locale?: string\r\n}\r\n\r\n/** Pick the variant a card shows: the default, else the first available, else the first. */\r\nfunction resolveVariant(product: Product): Variant | undefined {\r\n if (product.defaultVariantId) {\r\n const match = product.variants.find((variant) => variant.id === product.defaultVariantId)\r\n if (match) return match\r\n }\r\n return product.variants.find((variant) => variant.available) ?? product.variants[0]\r\n}\r\n\r\nfunction badgeClass(badge: string): string {\r\n switch (badge.toLowerCase()) {\r\n case \"sale\":\r\n return \"bg-destructive text-destructive-foreground\"\r\n case \"new\":\r\n return \"bg-emerald-500 text-white\"\r\n case \"limited\":\r\n return \"bg-amber-500 text-black\"\r\n default:\r\n return \"bg-secondary text-secondary-foreground\"\r\n }\r\n}\r\n\r\nconst ProductCard = React.forwardRef(\r\n (\r\n {\r\n className,\r\n layout,\r\n product,\r\n onAddToCart,\r\n onQuickView,\r\n onToggleWishlist,\r\n isInWishlist = false,\r\n showQuickActions = true,\r\n locale,\r\n ...props\r\n },\r\n ref\r\n ) => {\r\n const variant = resolveVariant(product)\r\n const image = product.images[0] ?? variant?.image\r\n const soldOut = !variant?.available\r\n\r\n return (\r\n
\r\n \r\n {image && (\r\n \r\n )}\r\n\r\n {(product.badges?.length || soldOut) && (\r\n
\r\n {soldOut && (\r\n \r\n Sold out\r\n \r\n )}\r\n {product.badges?.map((badge) => (\r\n \r\n {badge}\r\n \r\n ))}\r\n
\r\n )}\r\n\r\n {showQuickActions && (\r\n
\r\n {onToggleWishlist && (\r\n onToggleWishlist(product)}\r\n className=\"rounded-full bg-background/90 p-2 backdrop-blur-sm transition-[color,background-color,transform] hover:bg-background active:scale-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\r\n aria-label={isInWishlist ? \"Remove from wishlist\" : \"Add to wishlist\"}\r\n aria-pressed={isInWishlist}\r\n >\r\n \r\n \r\n )}\r\n {onQuickView && (\r\n onQuickView(product)}\r\n className=\"rounded-full bg-background/90 p-2 backdrop-blur-sm transition-[color,background-color,transform] hover:bg-background active:scale-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\r\n aria-label={`Quick view ${product.title}`}\r\n >\r\n \r\n \r\n )}\r\n
\r\n )}\r\n
\r\n\r\n
\r\n

{product.title}

\r\n\r\n {product.description && layout === \"featured\" && (\r\n

{product.description}

\r\n )}\r\n\r\n {product.rating && (\r\n \r\n )}\r\n\r\n
\r\n {variant && (\r\n \r\n )}\r\n\r\n {onAddToCart && variant && (\r\n onAddToCart(product, variant)}\r\n disabled={soldOut}\r\n className=\"rounded-md bg-primary p-2 text-primary-foreground transition-[color,background-color,transform] hover:bg-primary/90 active:scale-90 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\r\n aria-label={`Add ${product.title} to cart`}\r\n >\r\n \r\n \r\n )}\r\n
\r\n
\r\n \r\n )\r\n }\r\n)\r\nProductCard.displayName = \"ProductCard\"\r\n\r\nexport { ProductCard, productCardVariants }\r\n", "type": "registry:ui", "target": "components/lumo/product-card.tsx" } ], "type": "registry:ui" }