@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 +7 -1
- package/dist/react.mjs +7 -1
- package/package.json +1 -1
- package/registry/product-card.json +1 -1
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
|
@@ -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 //
|
|
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
|
}
|