@cimplify/sdk 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -9570,13 +9570,18 @@ function ProductCard({
9570
9570
  }) {
9571
9571
  const mode = displayMode ?? product.display_mode ?? "card";
9572
9572
  const [isOpen, setIsOpen] = React3.useState(false);
9573
+ const [shouldFetch, setShouldFetch] = React3.useState(false);
9573
9574
  const dialogRef = React3.useRef(null);
9574
9575
  const { product: productDetails } = useProduct(
9575
9576
  product.slug ?? product.id,
9576
- { enabled: isOpen }
9577
+ { enabled: shouldFetch || isOpen }
9577
9578
  );
9579
+ const handlePrefetch = React3.useCallback(() => {
9580
+ setShouldFetch(true);
9581
+ }, []);
9578
9582
  const handleOpen = React3.useCallback(() => {
9579
9583
  setIsOpen(true);
9584
+ setShouldFetch(true);
9580
9585
  dialogRef.current?.showModal();
9581
9586
  }, []);
9582
9587
  const handleClose = React3.useCallback(() => {
@@ -9683,6 +9688,7 @@ function ProductCard({
9683
9688
  {
9684
9689
  type: "button",
9685
9690
  "aria-haspopup": "dialog",
9691
+ onPointerEnter: handlePrefetch,
9686
9692
  onClick: handleOpen,
9687
9693
  "data-cimplify-product-card": true,
9688
9694
  "data-display-mode": "card",
package/dist/react.mjs CHANGED
@@ -9564,13 +9564,18 @@ function ProductCard({
9564
9564
  }) {
9565
9565
  const mode = displayMode ?? product.display_mode ?? "card";
9566
9566
  const [isOpen, setIsOpen] = useState(false);
9567
+ const [shouldFetch, setShouldFetch] = useState(false);
9567
9568
  const dialogRef = useRef(null);
9568
9569
  const { product: productDetails } = useProduct(
9569
9570
  product.slug ?? product.id,
9570
- { enabled: isOpen }
9571
+ { enabled: shouldFetch || isOpen }
9571
9572
  );
9573
+ const handlePrefetch = useCallback(() => {
9574
+ setShouldFetch(true);
9575
+ }, []);
9572
9576
  const handleOpen = useCallback(() => {
9573
9577
  setIsOpen(true);
9578
+ setShouldFetch(true);
9574
9579
  dialogRef.current?.showModal();
9575
9580
  }, []);
9576
9581
  const handleClose = useCallback(() => {
@@ -9677,6 +9682,7 @@ function ProductCard({
9677
9682
  {
9678
9683
  type: "button",
9679
9684
  "aria-haspopup": "dialog",
9685
+ onPointerEnter: handlePrefetch,
9680
9686
  onClick: handleOpen,
9681
9687
  "data-cimplify-product-card": true,
9682
9688
  "data-display-mode": "card",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  {
13
13
  "path": "product-card.tsx",
14
- "content": "\"use client\";\n\nimport React, { useCallback, useRef, useState } from \"react\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { ProductSheet } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nconst ASPECT_STYLES: Record<string, React.CSSProperties> = {\n square: { aspectRatio: \"1/1\" },\n \"4/3\": { aspectRatio: \"4/3\" },\n \"16/10\": { aspectRatio: \"16/10\" },\n \"3/4\": { aspectRatio: \"3/4\" },\n};\n\nexport interface ProductCardClassNames {\n root?: string;\n imageContainer?: string;\n image?: string;\n body?: string;\n name?: string;\n description?: string;\n price?: string;\n badges?: string;\n badge?: string;\n modal?: string;\n modalOverlay?: string;\n}\n\nexport interface ProductCardProps {\n /** The product to display. */\n product: Product;\n /** Display mode: \"card\" opens a modal, \"page\" renders as a link. Auto-detected from product.display_mode. */\n displayMode?: \"card\" | \"page\";\n /** Link href for page mode. Default: `/menu/${product.slug}` */\n href?: string;\n /** Custom modal content renderer. Receives the fully-loaded product. */\n renderModal?: (product: ProductWithDetails) => React.ReactNode;\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 /** Custom link renderer for page mode (e.g. Next.js Link). */\n renderLink?: (props: {\n href: string;\n className?: string;\n children: React.ReactNode;\n }) => React.ReactElement;\n /** Replace the entire default card body. */\n children?: React.ReactNode;\n /** Image aspect ratio. Default: \"4/3\". */\n aspectRatio?: \"square\" | \"4/3\" | \"16/10\" | \"3/4\";\n className?: string;\n classNames?: ProductCardClassNames;\n}\n\n/**\n * ProductCard — a product display card with two modes:\n *\n * - **card** (default): clickable button that opens a native `<dialog>` modal with a ProductSheet\n * - **page**: a plain `<a>` link for SEO-friendly product pages\n */\nexport function ProductCard({\n product,\n displayMode,\n href,\n renderModal,\n renderImage,\n renderLink,\n children,\n aspectRatio = \"4/3\",\n className,\n classNames,\n}: ProductCardProps): React.ReactElement {\n const mode = displayMode ?? product.display_mode ?? \"card\";\n const [isOpen, setIsOpen] = useState(false);\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n // Lazy-fetch product details only when modal is open\n const { product: productDetails } = useProduct(\n product.slug ?? product.id,\n { enabled: isOpen },\n );\n\n const handleOpen = useCallback(() => {\n setIsOpen(true);\n dialogRef.current?.showModal();\n }, []);\n\n const handleClose = useCallback(() => {\n dialogRef.current?.close();\n setIsOpen(false);\n }, []);\n\n const handleCancel = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (e.target === dialogRef.current) {\n handleClose();\n }\n },\n [handleClose],\n );\n\n const imageUrl = product.image_url || product.images?.[0];\n\n const cardBody = children ?? (\n <>\n {/* Image */}\n {imageUrl && (\n <div\n data-cimplify-product-card-image-container\n className={classNames?.imageContainer}\n style={{\n overflow: \"hidden\",\n ...ASPECT_STYLES[aspectRatio],\n }}\n >\n {renderImage ? (\n renderImage({\n src: imageUrl,\n alt: product.name,\n className: classNames?.image,\n })\n ) : (\n <img\n src={imageUrl}\n alt={product.name}\n className={classNames?.image}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\" }}\n data-cimplify-product-card-image\n />\n )}\n </div>\n )}\n\n {/* Body */}\n <div\n data-cimplify-product-card-body\n className={classNames?.body}\n >\n <span\n data-cimplify-product-card-name\n className={classNames?.name}\n >\n {product.name}\n </span>\n {product.description && (\n <span\n data-cimplify-product-card-description\n className={classNames?.description}\n style={{\n display: \"-webkit-box\",\n WebkitLineClamp: 2,\n WebkitBoxOrient: \"vertical\",\n overflow: \"hidden\",\n }}\n >\n {product.description}\n </span>\n )}\n <Price\n amount={product.default_price}\n className={classNames?.price}\n />\n </div>\n </>\n );\n\n // Page mode — render as a link\n if (mode === \"page\") {\n const linkHref = href ?? `/menu/${product.slug}`;\n const linkClassName = cn(\"block no-underline text-[inherit]\", className, classNames?.root);\n\n if (renderLink) {\n return renderLink({ href: linkHref, className: linkClassName, children: cardBody });\n }\n\n return (\n <a\n href={linkHref}\n data-cimplify-product-card\n data-display-mode=\"page\"\n className={linkClassName}\n >\n {cardBody}\n </a>\n );\n }\n\n // Card mode — render as button + native dialog\n return (\n <>\n <button\n type=\"button\"\n aria-haspopup=\"dialog\"\n onClick={handleOpen}\n data-cimplify-product-card\n data-display-mode=\"card\"\n className={cn(\n \"block w-full text-left bg-transparent border-none p-0 cursor-pointer font-[inherit] text-[inherit]\",\n className,\n classNames?.root,\n )}\n >\n {cardBody}\n </button>\n\n <dialog\n ref={dialogRef}\n onCancel={handleCancel}\n onClick={handleBackdropClick}\n data-cimplify-product-card-modal\n className={cn(\n \"border-none rounded-2xl p-0 max-w-lg w-full max-h-[85vh] overflow-auto bg-background shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:slide-in-from-bottom-4\",\n classNames?.modal,\n )}\n >\n {isOpen && (\n productDetails ? (\n renderModal ? (\n renderModal(productDetails)\n ) : (\n <ProductSheet\n product={productDetails}\n onClose={handleClose}\n renderImage={renderImage}\n />\n )\n ) : (\n <div\n data-cimplify-product-card-modal-loading\n aria-busy=\"true\"\n className=\"flex flex-col gap-4 p-6\"\n >\n <div className=\"aspect-[4/3] bg-muted rounded-lg\" />\n <div className=\"h-6 w-3/5 bg-muted rounded\" />\n </div>\n )\n )}\n </dialog>\n </>\n );\n}\n"
14
+ "content": "\"use client\";\n\nimport React, { useCallback, useRef, useState } from \"react\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { ProductSheet } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nconst ASPECT_STYLES: Record<string, React.CSSProperties> = {\n square: { aspectRatio: \"1/1\" },\n \"4/3\": { aspectRatio: \"4/3\" },\n \"16/10\": { aspectRatio: \"16/10\" },\n \"3/4\": { aspectRatio: \"3/4\" },\n};\n\nexport interface ProductCardClassNames {\n root?: string;\n imageContainer?: string;\n image?: string;\n body?: string;\n name?: string;\n description?: string;\n price?: string;\n badges?: string;\n badge?: string;\n modal?: string;\n modalOverlay?: string;\n}\n\nexport interface ProductCardProps {\n /** The product to display. */\n product: Product;\n /** Display mode: \"card\" opens a modal, \"page\" renders as a link. Auto-detected from product.display_mode. */\n displayMode?: \"card\" | \"page\";\n /** Link href for page mode. Default: `/menu/${product.slug}` */\n href?: string;\n /** Custom modal content renderer. Receives the fully-loaded product. */\n renderModal?: (product: ProductWithDetails) => React.ReactNode;\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 /** Custom link renderer for page mode (e.g. Next.js Link). */\n renderLink?: (props: {\n href: string;\n className?: string;\n children: React.ReactNode;\n }) => React.ReactElement;\n /** Replace the entire default card body. */\n children?: React.ReactNode;\n /** Image aspect ratio. Default: \"4/3\". */\n aspectRatio?: \"square\" | \"4/3\" | \"16/10\" | \"3/4\";\n className?: string;\n classNames?: ProductCardClassNames;\n}\n\n/**\n * ProductCard — a product display card with two modes:\n *\n * - **card** (default): clickable button that opens a native `<dialog>` modal with a ProductSheet\n * - **page**: a plain `<a>` link for SEO-friendly product pages\n */\nexport function ProductCard({\n product,\n displayMode,\n href,\n renderModal,\n renderImage,\n renderLink,\n children,\n aspectRatio = \"4/3\",\n className,\n classNames,\n}: ProductCardProps): React.ReactElement {\n const mode = displayMode ?? product.display_mode ?? \"card\";\n const [isOpen, setIsOpen] = useState(false);\n const [shouldFetch, setShouldFetch] = useState(false);\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n // Prefetch on pointer enter, always fetch when open\n const { product: productDetails } = useProduct(\n product.slug ?? product.id,\n { enabled: shouldFetch || isOpen },\n );\n\n const handlePrefetch = useCallback(() => {\n setShouldFetch(true);\n }, []);\n\n const handleOpen = useCallback(() => {\n setIsOpen(true);\n setShouldFetch(true);\n dialogRef.current?.showModal();\n }, []);\n\n const handleClose = useCallback(() => {\n dialogRef.current?.close();\n setIsOpen(false);\n }, []);\n\n const handleCancel = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (e.target === dialogRef.current) {\n handleClose();\n }\n },\n [handleClose],\n );\n\n const imageUrl = product.image_url || product.images?.[0];\n\n const cardBody = children ?? (\n <>\n {/* Image */}\n {imageUrl && (\n <div\n data-cimplify-product-card-image-container\n className={classNames?.imageContainer}\n style={{\n overflow: \"hidden\",\n ...ASPECT_STYLES[aspectRatio],\n }}\n >\n {renderImage ? (\n renderImage({\n src: imageUrl,\n alt: product.name,\n className: classNames?.image,\n })\n ) : (\n <img\n src={imageUrl}\n alt={product.name}\n className={classNames?.image}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\" }}\n data-cimplify-product-card-image\n />\n )}\n </div>\n )}\n\n {/* Body */}\n <div\n data-cimplify-product-card-body\n className={classNames?.body}\n >\n <span\n data-cimplify-product-card-name\n className={classNames?.name}\n >\n {product.name}\n </span>\n {product.description && (\n <span\n data-cimplify-product-card-description\n className={classNames?.description}\n style={{\n display: \"-webkit-box\",\n WebkitLineClamp: 2,\n WebkitBoxOrient: \"vertical\",\n overflow: \"hidden\",\n }}\n >\n {product.description}\n </span>\n )}\n <Price\n amount={product.default_price}\n className={classNames?.price}\n />\n </div>\n </>\n );\n\n // Page mode — render as a link\n if (mode === \"page\") {\n const linkHref = href ?? `/menu/${product.slug}`;\n const linkClassName = cn(\"block no-underline text-[inherit]\", className, classNames?.root);\n\n if (renderLink) {\n return renderLink({ href: linkHref, className: linkClassName, children: cardBody });\n }\n\n return (\n <a\n href={linkHref}\n data-cimplify-product-card\n data-display-mode=\"page\"\n className={linkClassName}\n >\n {cardBody}\n </a>\n );\n }\n\n // Card mode — render as button + native dialog\n return (\n <>\n <button\n type=\"button\"\n aria-haspopup=\"dialog\"\n onPointerEnter={handlePrefetch}\n onClick={handleOpen}\n data-cimplify-product-card\n data-display-mode=\"card\"\n className={cn(\n \"block w-full text-left bg-transparent border-none p-0 cursor-pointer font-[inherit] text-[inherit]\",\n className,\n classNames?.root,\n )}\n >\n {cardBody}\n </button>\n\n <dialog\n ref={dialogRef}\n onCancel={handleCancel}\n onClick={handleBackdropClick}\n data-cimplify-product-card-modal\n className={cn(\n \"border-none rounded-2xl p-0 max-w-lg w-full max-h-[85vh] overflow-auto bg-background shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:slide-in-from-bottom-4\",\n classNames?.modal,\n )}\n >\n {isOpen && (\n productDetails ? (\n renderModal ? (\n renderModal(productDetails)\n ) : (\n <ProductSheet\n product={productDetails}\n onClose={handleClose}\n renderImage={renderImage}\n />\n )\n ) : (\n <div\n data-cimplify-product-card-modal-loading\n aria-busy=\"true\"\n className=\"flex flex-col gap-4 p-6\"\n >\n <div className=\"aspect-[4/3] bg-muted rounded-lg\" />\n <div className=\"h-6 w-3/5 bg-muted rounded\" />\n </div>\n )\n )}\n </dialog>\n </>\n );\n}\n"
15
15
  }
16
16
  ]
17
17
  }