@cimplify/sdk 0.12.2 → 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.d.mts CHANGED
@@ -885,6 +885,12 @@ interface ProductCardProps {
885
885
  alt: string;
886
886
  className?: string;
887
887
  }) => React.ReactNode;
888
+ /** Custom link renderer for page mode (e.g. Next.js Link). */
889
+ renderLink?: (props: {
890
+ href: string;
891
+ className?: string;
892
+ children: React.ReactNode;
893
+ }) => React.ReactElement;
888
894
  /** Replace the entire default card body. */
889
895
  children?: React.ReactNode;
890
896
  /** Image aspect ratio. Default: "4/3". */
@@ -898,7 +904,7 @@ interface ProductCardProps {
898
904
  * - **card** (default): clickable button that opens a native `<dialog>` modal with a ProductSheet
899
905
  * - **page**: a plain `<a>` link for SEO-friendly product pages
900
906
  */
901
- declare function ProductCard({ product, displayMode, href, renderModal, renderImage, children, aspectRatio, className, classNames, }: ProductCardProps): React.ReactElement;
907
+ declare function ProductCard({ product, displayMode, href, renderModal, renderImage, renderLink, children, aspectRatio, className, classNames, }: ProductCardProps): React.ReactElement;
902
908
 
903
909
  interface ProductGridClassNames {
904
910
  root?: string;
package/dist/react.d.ts CHANGED
@@ -885,6 +885,12 @@ interface ProductCardProps {
885
885
  alt: string;
886
886
  className?: string;
887
887
  }) => React.ReactNode;
888
+ /** Custom link renderer for page mode (e.g. Next.js Link). */
889
+ renderLink?: (props: {
890
+ href: string;
891
+ className?: string;
892
+ children: React.ReactNode;
893
+ }) => React.ReactElement;
888
894
  /** Replace the entire default card body. */
889
895
  children?: React.ReactNode;
890
896
  /** Image aspect ratio. Default: "4/3". */
@@ -898,7 +904,7 @@ interface ProductCardProps {
898
904
  * - **card** (default): clickable button that opens a native `<dialog>` modal with a ProductSheet
899
905
  * - **page**: a plain `<a>` link for SEO-friendly product pages
900
906
  */
901
- declare function ProductCard({ product, displayMode, href, renderModal, renderImage, children, aspectRatio, className, classNames, }: ProductCardProps): React.ReactElement;
907
+ declare function ProductCard({ product, displayMode, href, renderModal, renderImage, renderLink, children, aspectRatio, className, classNames, }: ProductCardProps): React.ReactElement;
902
908
 
903
909
  interface ProductGridClassNames {
904
910
  root?: string;
package/dist/react.js CHANGED
@@ -9562,6 +9562,7 @@ function ProductCard({
9562
9562
  href,
9563
9563
  renderModal,
9564
9564
  renderImage,
9565
+ renderLink,
9565
9566
  children,
9566
9567
  aspectRatio = "4/3",
9567
9568
  className,
@@ -9569,13 +9570,18 @@ function ProductCard({
9569
9570
  }) {
9570
9571
  const mode = displayMode ?? product.display_mode ?? "card";
9571
9572
  const [isOpen, setIsOpen] = React3.useState(false);
9573
+ const [shouldFetch, setShouldFetch] = React3.useState(false);
9572
9574
  const dialogRef = React3.useRef(null);
9573
9575
  const { product: productDetails } = useProduct(
9574
9576
  product.slug ?? product.id,
9575
- { enabled: isOpen }
9577
+ { enabled: shouldFetch || isOpen }
9576
9578
  );
9579
+ const handlePrefetch = React3.useCallback(() => {
9580
+ setShouldFetch(true);
9581
+ }, []);
9577
9582
  const handleOpen = React3.useCallback(() => {
9578
9583
  setIsOpen(true);
9584
+ setShouldFetch(true);
9579
9585
  dialogRef.current?.showModal();
9580
9586
  }, []);
9581
9587
  const handleClose = React3.useCallback(() => {
@@ -9660,14 +9666,18 @@ function ProductCard({
9660
9666
  )
9661
9667
  ] });
9662
9668
  if (mode === "page") {
9669
+ const linkHref = href ?? `/menu/${product.slug}`;
9670
+ const linkClassName = cn("block no-underline text-[inherit]", className, classNames?.root);
9671
+ if (renderLink) {
9672
+ return renderLink({ href: linkHref, className: linkClassName, children: cardBody });
9673
+ }
9663
9674
  return /* @__PURE__ */ jsxRuntime.jsx(
9664
9675
  "a",
9665
9676
  {
9666
- href: href ?? `/menu/${product.slug}`,
9677
+ href: linkHref,
9667
9678
  "data-cimplify-product-card": true,
9668
9679
  "data-display-mode": "page",
9669
- className: cn(className, classNames?.root),
9670
- style: { display: "block", textDecoration: "none", color: "inherit" },
9680
+ className: linkClassName,
9671
9681
  children: cardBody
9672
9682
  }
9673
9683
  );
@@ -9678,21 +9688,15 @@ function ProductCard({
9678
9688
  {
9679
9689
  type: "button",
9680
9690
  "aria-haspopup": "dialog",
9691
+ onPointerEnter: handlePrefetch,
9681
9692
  onClick: handleOpen,
9682
9693
  "data-cimplify-product-card": true,
9683
9694
  "data-display-mode": "card",
9684
- className: cn(className, classNames?.root),
9685
- style: {
9686
- display: "block",
9687
- width: "100%",
9688
- textAlign: "inherit",
9689
- background: "none",
9690
- border: "none",
9691
- padding: 0,
9692
- cursor: "pointer",
9693
- font: "inherit",
9694
- color: "inherit"
9695
- },
9695
+ className: cn(
9696
+ "block w-full text-left bg-transparent border-none p-0 cursor-pointer font-[inherit] text-[inherit]",
9697
+ className,
9698
+ classNames?.root
9699
+ ),
9696
9700
  children: cardBody
9697
9701
  }
9698
9702
  ),
@@ -9703,16 +9707,10 @@ function ProductCard({
9703
9707
  onCancel: handleCancel,
9704
9708
  onClick: handleBackdropClick,
9705
9709
  "data-cimplify-product-card-modal": true,
9706
- className: classNames?.modal,
9707
- style: {
9708
- border: "none",
9709
- borderRadius: "0.75rem",
9710
- padding: "1.5rem",
9711
- maxWidth: "32rem",
9712
- width: "100%",
9713
- maxHeight: "90vh",
9714
- overflow: "auto"
9715
- },
9710
+ className: cn(
9711
+ "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",
9712
+ classNames?.modal
9713
+ ),
9716
9714
  children: isOpen && (productDetails ? renderModal ? renderModal(productDetails) : /* @__PURE__ */ jsxRuntime.jsx(
9717
9715
  ProductSheet,
9718
9716
  {
@@ -9725,33 +9723,10 @@ function ProductCard({
9725
9723
  {
9726
9724
  "data-cimplify-product-card-modal-loading": true,
9727
9725
  "aria-busy": "true",
9728
- style: {
9729
- display: "flex",
9730
- flexDirection: "column",
9731
- gap: "1rem"
9732
- },
9726
+ className: "flex flex-col gap-4 p-6",
9733
9727
  children: [
9734
- /* @__PURE__ */ jsxRuntime.jsx(
9735
- "div",
9736
- {
9737
- style: {
9738
- aspectRatio: "4/3",
9739
- backgroundColor: "rgba(0,0,0,0.06)",
9740
- borderRadius: "0.5rem"
9741
- }
9742
- }
9743
- ),
9744
- /* @__PURE__ */ jsxRuntime.jsx(
9745
- "div",
9746
- {
9747
- style: {
9748
- height: "1.5rem",
9749
- width: "60%",
9750
- backgroundColor: "rgba(0,0,0,0.06)",
9751
- borderRadius: "0.25rem"
9752
- }
9753
- }
9754
- )
9728
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[4/3] bg-muted rounded-lg" }),
9729
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6 w-3/5 bg-muted rounded" })
9755
9730
  ]
9756
9731
  }
9757
9732
  ))
package/dist/react.mjs CHANGED
@@ -9556,6 +9556,7 @@ function ProductCard({
9556
9556
  href,
9557
9557
  renderModal,
9558
9558
  renderImage,
9559
+ renderLink,
9559
9560
  children,
9560
9561
  aspectRatio = "4/3",
9561
9562
  className,
@@ -9563,13 +9564,18 @@ function ProductCard({
9563
9564
  }) {
9564
9565
  const mode = displayMode ?? product.display_mode ?? "card";
9565
9566
  const [isOpen, setIsOpen] = useState(false);
9567
+ const [shouldFetch, setShouldFetch] = useState(false);
9566
9568
  const dialogRef = useRef(null);
9567
9569
  const { product: productDetails } = useProduct(
9568
9570
  product.slug ?? product.id,
9569
- { enabled: isOpen }
9571
+ { enabled: shouldFetch || isOpen }
9570
9572
  );
9573
+ const handlePrefetch = useCallback(() => {
9574
+ setShouldFetch(true);
9575
+ }, []);
9571
9576
  const handleOpen = useCallback(() => {
9572
9577
  setIsOpen(true);
9578
+ setShouldFetch(true);
9573
9579
  dialogRef.current?.showModal();
9574
9580
  }, []);
9575
9581
  const handleClose = useCallback(() => {
@@ -9654,14 +9660,18 @@ function ProductCard({
9654
9660
  )
9655
9661
  ] });
9656
9662
  if (mode === "page") {
9663
+ const linkHref = href ?? `/menu/${product.slug}`;
9664
+ const linkClassName = cn("block no-underline text-[inherit]", className, classNames?.root);
9665
+ if (renderLink) {
9666
+ return renderLink({ href: linkHref, className: linkClassName, children: cardBody });
9667
+ }
9657
9668
  return /* @__PURE__ */ jsx(
9658
9669
  "a",
9659
9670
  {
9660
- href: href ?? `/menu/${product.slug}`,
9671
+ href: linkHref,
9661
9672
  "data-cimplify-product-card": true,
9662
9673
  "data-display-mode": "page",
9663
- className: cn(className, classNames?.root),
9664
- style: { display: "block", textDecoration: "none", color: "inherit" },
9674
+ className: linkClassName,
9665
9675
  children: cardBody
9666
9676
  }
9667
9677
  );
@@ -9672,21 +9682,15 @@ function ProductCard({
9672
9682
  {
9673
9683
  type: "button",
9674
9684
  "aria-haspopup": "dialog",
9685
+ onPointerEnter: handlePrefetch,
9675
9686
  onClick: handleOpen,
9676
9687
  "data-cimplify-product-card": true,
9677
9688
  "data-display-mode": "card",
9678
- className: cn(className, classNames?.root),
9679
- style: {
9680
- display: "block",
9681
- width: "100%",
9682
- textAlign: "inherit",
9683
- background: "none",
9684
- border: "none",
9685
- padding: 0,
9686
- cursor: "pointer",
9687
- font: "inherit",
9688
- color: "inherit"
9689
- },
9689
+ className: cn(
9690
+ "block w-full text-left bg-transparent border-none p-0 cursor-pointer font-[inherit] text-[inherit]",
9691
+ className,
9692
+ classNames?.root
9693
+ ),
9690
9694
  children: cardBody
9691
9695
  }
9692
9696
  ),
@@ -9697,16 +9701,10 @@ function ProductCard({
9697
9701
  onCancel: handleCancel,
9698
9702
  onClick: handleBackdropClick,
9699
9703
  "data-cimplify-product-card-modal": true,
9700
- className: classNames?.modal,
9701
- style: {
9702
- border: "none",
9703
- borderRadius: "0.75rem",
9704
- padding: "1.5rem",
9705
- maxWidth: "32rem",
9706
- width: "100%",
9707
- maxHeight: "90vh",
9708
- overflow: "auto"
9709
- },
9704
+ className: cn(
9705
+ "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",
9706
+ classNames?.modal
9707
+ ),
9710
9708
  children: isOpen && (productDetails ? renderModal ? renderModal(productDetails) : /* @__PURE__ */ jsx(
9711
9709
  ProductSheet,
9712
9710
  {
@@ -9719,33 +9717,10 @@ function ProductCard({
9719
9717
  {
9720
9718
  "data-cimplify-product-card-modal-loading": true,
9721
9719
  "aria-busy": "true",
9722
- style: {
9723
- display: "flex",
9724
- flexDirection: "column",
9725
- gap: "1rem"
9726
- },
9720
+ className: "flex flex-col gap-4 p-6",
9727
9721
  children: [
9728
- /* @__PURE__ */ jsx(
9729
- "div",
9730
- {
9731
- style: {
9732
- aspectRatio: "4/3",
9733
- backgroundColor: "rgba(0,0,0,0.06)",
9734
- borderRadius: "0.5rem"
9735
- }
9736
- }
9737
- ),
9738
- /* @__PURE__ */ jsx(
9739
- "div",
9740
- {
9741
- style: {
9742
- height: "1.5rem",
9743
- width: "60%",
9744
- backgroundColor: "rgba(0,0,0,0.06)",
9745
- borderRadius: "0.25rem"
9746
- }
9747
- }
9748
- )
9722
+ /* @__PURE__ */ jsx("div", { className: "aspect-[4/3] bg-muted rounded-lg" }),
9723
+ /* @__PURE__ */ jsx("div", { className: "h-6 w-3/5 bg-muted rounded" })
9749
9724
  ]
9750
9725
  }
9751
9726
  ))
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.container{width:100%}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.w-full{width:100%}.flex-1{flex:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.\[appearance\:textfield\]{appearance:textfield}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-border{border-color:var(--color-border,oklch(90% 0 0))}.border-primary{border-color:var(--color-primary,oklch(50% .1 35))}.border-transparent{border-color:#0000}.bg-primary{background-color:var(--color-primary,oklch(50% .1 35))}.bg-primary\/5{background-color:#934c3a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 5%, transparent)}}.bg-primary\/10{background-color:#934c3a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 10%, transparent)}}.bg-transparent{background-color:#0000}.text-center{text-align:center}.text-left{text-align:left}.text-\[10px\]{font-size:10px}.text-destructive{color:var(--color-destructive,oklch(50% .2 25))}.text-muted-foreground{color:var(--color-muted-foreground,oklch(50% 0 0))}.text-muted-foreground\/60{color:#63636399}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/60{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 60%, transparent)}}.text-muted-foreground\/70{color:#636363b3}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/70{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 70%, transparent)}}.text-primary{color:var(--color-primary,oklch(50% .1 35))}.text-primary-foreground{color:var(--color-primary-foreground,oklch(99% 0 0))}.uppercase{text-transform:uppercase}.opacity-70{opacity:.7}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.outline-none{--tw-outline-style:none;outline-style:none}.\[cimplify\:checkout\]{cimplify:checkout}@media (hover:hover){.hover\:border-primary\/50:hover{border-color:#934c3a80}@supports (color:color-mix(in lab, red, red)){.hover\:border-primary\/50:hover{border-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 50%, transparent)}}.hover\:bg-muted:hover{background-color:var(--color-muted,oklch(95% 0 0))}.hover\:bg-muted\/50:hover{background-color:#eeeeee80}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab, var(--color-muted,oklch(95% 0 0)) 50%, transparent)}}.hover\:bg-primary\/90:hover{background-color:#934c3ae6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 90%, transparent)}}.hover\:text-primary:hover{color:var(--color-primary,oklch(50% .1 35))}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-50:disabled{opacity:.5}.\[\&\:\:-webkit-inner-spin-button\]\:appearance-none::-webkit-inner-spin-button{appearance:none}.\[\&\:\:-webkit-outer-spin-button\]\:appearance-none::-webkit-outer-spin-button{appearance:none}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.container{width:100%}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-\[4\/3\]{aspect-ratio:4/3}.max-h-\[85vh\]{max-height:85vh}.w-3\/5{width:60%}.w-full{width:100%}.flex-1{flex:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.\[appearance\:textfield\]{appearance:textfield}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.rounded{border-radius:var(--radius,.5rem)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-border{border-color:var(--color-border,oklch(90% 0 0))}.border-primary{border-color:var(--color-primary,oklch(50% .1 35))}.border-transparent{border-color:#0000}.bg-background{background-color:var(--color-background,oklch(99% 0 0))}.bg-muted{background-color:var(--color-muted,oklch(95% 0 0))}.bg-primary{background-color:var(--color-primary,oklch(50% .1 35))}.bg-primary\/5{background-color:#934c3a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 5%, transparent)}}.bg-primary\/10{background-color:#934c3a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 10%, transparent)}}.bg-transparent{background-color:#0000}.text-center{text-align:center}.text-left{text-align:left}.font-\[inherit\]{font-family:inherit}.text-\[10px\]{font-size:10px}.text-\[inherit\]{color:inherit}.text-destructive{color:var(--color-destructive,oklch(50% .2 25))}.text-muted-foreground{color:var(--color-muted-foreground,oklch(50% 0 0))}.text-muted-foreground\/60{color:#63636399}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/60{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 60%, transparent)}}.text-muted-foreground\/70{color:#636363b3}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/70{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 70%, transparent)}}.text-primary{color:var(--color-primary,oklch(50% .1 35))}.text-primary-foreground{color:var(--color-primary-foreground,oklch(99% 0 0))}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.opacity-70{opacity:.7}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.outline-none{--tw-outline-style:none;outline-style:none}.\[cimplify\:checkout\]{cimplify:checkout}@media (hover:hover){.hover\:border-primary\/50:hover{border-color:#934c3a80}@supports (color:color-mix(in lab, red, red)){.hover\:border-primary\/50:hover{border-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 50%, transparent)}}.hover\:bg-muted:hover{background-color:var(--color-muted,oklch(95% 0 0))}.hover\:bg-muted\/50:hover{background-color:#eeeeee80}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab, var(--color-muted,oklch(95% 0 0)) 50%, transparent)}}.hover\:bg-primary\/90:hover{background-color:#934c3ae6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 90%, transparent)}}.hover\:text-primary:hover{color:var(--color-primary,oklch(50% .1 35))}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-50:disabled{opacity:.5}.\[\&\:\:-webkit-inner-spin-button\]\:appearance-none::-webkit-inner-spin-button{appearance:none}.\[\&\:\:-webkit-outer-spin-button\]\:appearance-none::-webkit-outer-spin-button{appearance:none}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.12.2",
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 /** 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 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 return (\n <a\n href={href ?? `/menu/${product.slug}`}\n data-cimplify-product-card\n data-display-mode=\"page\"\n className={cn(className, classNames?.root)}\n style={{ display: \"block\", textDecoration: \"none\", color: \"inherit\" }}\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(className, classNames?.root)}\n style={{\n display: \"block\",\n width: \"100%\",\n textAlign: \"inherit\",\n background: \"none\",\n border: \"none\",\n padding: 0,\n cursor: \"pointer\",\n font: \"inherit\",\n color: \"inherit\",\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={classNames?.modal}\n style={{\n border: \"none\",\n borderRadius: \"0.75rem\",\n padding: \"1.5rem\",\n maxWidth: \"32rem\",\n width: \"100%\",\n maxHeight: \"90vh\",\n overflow: \"auto\",\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 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 )\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
  }