{
  "name": "product-sheet",
  "title": "ProductSheet",
  "description": "Full product detail view with gallery, header, and customizer.",
  "type": "component",
  "registryDependencies": [
    "price",
    "product-image-gallery",
    "product-customizer",
    "rich-text",
    "cn"
  ],
  "files": [
    {
      "path": "product-sheet.tsx",
      "content": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport type { Product, ProductWithDetails, VariantView } from \"@cimplify/sdk\";\nimport type { AddToCartOptions } from \"@cimplify/sdk/react\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { getProductCurrency } from \"@cimplify/sdk\";\nimport { ProductImageGallery } from \"@cimplify/sdk/react\";\nimport { ProductCustomizer } from \"@cimplify/sdk/react\";\nimport { RichText } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface ProductSheetClassNames {\n  root?: string;\n  image?: string;\n  header?: string;\n  name?: string;\n  price?: string;\n  description?: string;\n  customizer?: string;\n  loading?: string;\n}\n\nexport interface ProductSheetProps {\n  /** A slim Product (triggers lazy-fetch) or a fully-loaded ProductWithDetails. */\n  product: Product | ProductWithDetails;\n  /** Called when the sheet should close. */\n  onClose?: () => void;\n  /** Override the default add-to-cart behavior. */\n  onAddToCart?: (\n    product: ProductWithDetails,\n    quantity: number,\n    options: AddToCartOptions,\n  ) => void | Promise<void>;\n  /** Custom image renderer (e.g. Next.js Image). */\n  renderImage?: (props: {\n    src: string;\n    alt: string;\n    className?: string;\n  }) => React.ReactNode;\n  className?: string;\n  classNames?: ProductSheetClassNames;\n}\n\nfunction isProductWithDetails(\n  product: Product | ProductWithDetails,\n): product is ProductWithDetails {\n  return \"variants\" in product;\n}\n\n/**\n * ProductSheet — full product detail view composing gallery, header, and customizer.\n *\n * When given a slim `Product`, it lazy-fetches the full details via `useProduct`.\n * When given a `ProductWithDetails`, it skips the fetch entirely.\n */\nexport function ProductSheet({\n  product,\n  onClose,\n  onAddToCart,\n  renderImage,\n  className,\n  classNames,\n}: ProductSheetProps): React.ReactElement {\n  const needsFetch = !isProductWithDetails(product);\n  const { product: fetched, isLoading } = useProduct(\n    product.slug ?? product.id,\n    { enabled: needsFetch },\n  );\n  const fullProduct = needsFetch ? fetched : (product as ProductWithDetails);\n\n  // Loading state\n  if (isLoading && !fullProduct) {\n    return (\n      <div\n        data-cimplify-product-sheet\n        aria-busy=\"true\"\n        className={cn(className, classNames?.root, classNames?.loading)}\n      >\n        <div\n          data-cimplify-product-sheet-skeleton\n          style={{\n            display: \"flex\",\n            flexDirection: \"column\",\n            gap: \"1rem\",\n          }}\n        >\n          <div\n            style={{\n              aspectRatio: \"4/3\",\n              backgroundColor: \"rgba(0,0,0,0.06)\",\n              borderRadius: \"0.5rem\",\n            }}\n          />\n          <div\n            style={{\n              height: \"1.5rem\",\n              width: \"60%\",\n              backgroundColor: \"rgba(0,0,0,0.06)\",\n              borderRadius: \"0.25rem\",\n            }}\n          />\n          <div\n            style={{\n              height: \"1rem\",\n              width: \"30%\",\n              backgroundColor: \"rgba(0,0,0,0.06)\",\n              borderRadius: \"0.25rem\",\n            }}\n          />\n        </div>\n      </div>\n    );\n  }\n\n  // Error state\n  if (!fullProduct) {\n    return (\n      <div\n        data-cimplify-product-sheet\n        className={cn(className, classNames?.root)}\n      >\n        <p>Product not found.</p>\n      </div>\n    );\n  }\n\n  const [selectedVariant, setSelectedVariant] = useState<VariantView | undefined>(\n    undefined,\n  );\n\n  const variantImages = selectedVariant?.images?.filter(Boolean) ?? [];\n  const productImages: string[] = [];\n  if (fullProduct.images && fullProduct.images.length > 0) {\n    productImages.push(...fullProduct.images.filter(Boolean));\n  } else if (fullProduct.image_url) {\n    productImages.push(fullProduct.image_url);\n  }\n  const images: string[] =\n    variantImages.length > 0 ? variantImages : productImages;\n\n  const hasMultipleImages = images.length > 1;\n  const singleImage = images[0];\n\n  return (\n    <div\n      data-cimplify-product-sheet\n      className={cn(className, classNames?.root)}\n      style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}\n    >\n      {/* Image area */}\n      {hasMultipleImages ? (\n        <ProductImageGallery\n          images={images}\n          productName={fullProduct.name}\n          className={classNames?.image}\n        />\n      ) : singleImage ? (\n        <div data-cimplify-product-sheet-image className={classNames?.image}>\n          {renderImage ? (\n            renderImage({ src: singleImage, alt: fullProduct.name })\n          ) : (\n            <img\n              src={singleImage}\n              alt={fullProduct.name}\n              style={{ width: \"100%\", height: \"auto\", objectFit: \"cover\" }}\n            />\n          )}\n        </div>\n      ) : null}\n\n      {/* Header */}\n      <div data-cimplify-product-sheet-header className={classNames?.header}>\n        <h2\n          data-cimplify-product-sheet-name\n          className={classNames?.name}\n          style={{ margin: 0 }}\n        >\n          {fullProduct.name}\n        </h2>\n        <Price\n          amount={fullProduct.default_price}\n          currency={getProductCurrency(fullProduct)}\n          className={classNames?.price}\n        />\n      </div>\n\n      {/* Description — merchant-authored HTML from the catalogue. */}\n      <RichText\n        html={fullProduct.description}\n        data-cimplify-product-sheet-description\n        className={cn(\"text-sm\", classNames?.description)}\n      />\n\n      {/* Customizer */}\n      <ProductCustomizer\n        product={fullProduct}\n        onAddToCart={onAddToCart}\n        onVariantChange={(_id, variant) => setSelectedVariant(variant)}\n        className={classNames?.customizer}\n      />\n    </div>\n  );\n}\n"
    }
  ]
}
