@cimplify/sdk 0.13.1 → 0.14.0
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 +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +70 -64
- package/dist/react.mjs +70 -64
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/registry/add-on-selector.json +1 -1
- package/registry/product-card.json +1 -1
- package/registry/variant-selector.json +1 -1
package/dist/react.d.mts
CHANGED
|
@@ -877,8 +877,8 @@ interface ProductCardProps {
|
|
|
877
877
|
displayMode?: "card" | "page";
|
|
878
878
|
/** Link href for page mode. Default: `/menu/${product.slug}` */
|
|
879
879
|
href?: string;
|
|
880
|
-
/** Custom modal content renderer. Receives the fully-loaded product. */
|
|
881
|
-
renderModal?: (product: ProductWithDetails) => React.ReactNode;
|
|
880
|
+
/** Custom modal content renderer. Receives the fully-loaded product and a close callback. */
|
|
881
|
+
renderModal?: (product: ProductWithDetails, onClose: () => void) => React.ReactNode;
|
|
882
882
|
/** Custom image renderer (e.g. Next.js Image). */
|
|
883
883
|
renderImage?: (props: {
|
|
884
884
|
src: string;
|
package/dist/react.d.ts
CHANGED
|
@@ -877,8 +877,8 @@ interface ProductCardProps {
|
|
|
877
877
|
displayMode?: "card" | "page";
|
|
878
878
|
/** Link href for page mode. Default: `/menu/${product.slug}` */
|
|
879
879
|
href?: string;
|
|
880
|
-
/** Custom modal content renderer. Receives the fully-loaded product. */
|
|
881
|
-
renderModal?: (product: ProductWithDetails) => React.ReactNode;
|
|
880
|
+
/** Custom modal content renderer. Receives the fully-loaded product and a close callback. */
|
|
881
|
+
renderModal?: (product: ProductWithDetails, onClose: () => void) => React.ReactNode;
|
|
882
882
|
/** Custom image renderer (e.g. Next.js Image). */
|
|
883
883
|
renderImage?: (props: {
|
|
884
884
|
src: string;
|
package/dist/react.js
CHANGED
|
@@ -7978,7 +7978,7 @@ function VariantSelector({
|
|
|
7978
7978
|
if (!variants || variants.length <= 1) {
|
|
7979
7979
|
return null;
|
|
7980
7980
|
}
|
|
7981
|
-
|
|
7981
|
+
basePrice != null ? parsePrice(basePrice) : 0;
|
|
7982
7982
|
if (variantAxes && variantAxes.length > 0) {
|
|
7983
7983
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cimplify-variant-selector": true, className: cn("space-y-5", className, classNames?.root), children: variantAxes.map((axis) => {
|
|
7984
7984
|
const labelId = `${idPrefix}-axis-${axis.id}`;
|
|
@@ -8029,14 +8029,16 @@ function VariantSelector({
|
|
|
8029
8029
|
}) });
|
|
8030
8030
|
}
|
|
8031
8031
|
const listLabelId = `${idPrefix}-variant-list`;
|
|
8032
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-variant-selector": true, className: cn(
|
|
8033
|
-
/* @__PURE__ */ jsxRuntime.
|
|
8034
|
-
"
|
|
8032
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-variant-selector": true, className: cn(className, classNames?.root), children: [
|
|
8033
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
8034
|
+
"div",
|
|
8035
8035
|
{
|
|
8036
|
-
|
|
8037
|
-
"
|
|
8038
|
-
|
|
8039
|
-
|
|
8036
|
+
"data-cimplify-variant-list-header": true,
|
|
8037
|
+
className: cn("flex items-center justify-between py-3", classNames?.listLabel),
|
|
8038
|
+
children: [
|
|
8039
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { id: listLabelId, className: "text-base font-bold", children: "Options" }),
|
|
8040
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-destructive bg-destructive/10 px-2.5 py-1 rounded", children: "Required" })
|
|
8041
|
+
]
|
|
8040
8042
|
}
|
|
8041
8043
|
),
|
|
8042
8044
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -8049,11 +8051,10 @@ function VariantSelector({
|
|
|
8049
8051
|
onVariantChange(variant?.id, variant);
|
|
8050
8052
|
},
|
|
8051
8053
|
"data-cimplify-variant-list": true,
|
|
8052
|
-
className: cn("
|
|
8054
|
+
className: cn("divide-y divide-border", classNames?.list),
|
|
8053
8055
|
children: variants.map((variant) => {
|
|
8054
8056
|
const isSelected = selectedVariantId === variant.id;
|
|
8055
8057
|
const adjustment = parsePrice(variant.price_adjustment);
|
|
8056
|
-
const effectivePrice = basePriceNum + adjustment;
|
|
8057
8058
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8058
8059
|
radio.Radio.Root,
|
|
8059
8060
|
{
|
|
@@ -8061,35 +8062,32 @@ function VariantSelector({
|
|
|
8061
8062
|
"data-cimplify-variant-option": true,
|
|
8062
8063
|
"data-selected": isSelected || void 0,
|
|
8063
8064
|
className: cn(
|
|
8064
|
-
"w-full flex items-center
|
|
8065
|
-
isSelected && "bg-primary/5 border-primary",
|
|
8065
|
+
"w-full flex items-center gap-3 py-4 transition-colors cursor-pointer",
|
|
8066
8066
|
isSelected ? classNames?.optionSelected : classNames?.option
|
|
8067
8067
|
),
|
|
8068
8068
|
children: [
|
|
8069
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8070
|
+
"span",
|
|
8071
|
+
{
|
|
8072
|
+
"data-cimplify-variant-radio": true,
|
|
8073
|
+
className: cn(
|
|
8074
|
+
"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
|
|
8075
|
+
isSelected ? "border-primary" : "border-muted-foreground/30"
|
|
8076
|
+
),
|
|
8077
|
+
children: isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
|
|
8078
|
+
}
|
|
8079
|
+
),
|
|
8069
8080
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8070
8081
|
"span",
|
|
8071
8082
|
{
|
|
8072
8083
|
"data-cimplify-variant-name": true,
|
|
8073
|
-
className: cn("
|
|
8084
|
+
className: cn("flex-1 min-w-0 text-sm", classNames?.name),
|
|
8074
8085
|
children: getVariantDisplayName(variant, productName)
|
|
8075
8086
|
}
|
|
8076
8087
|
),
|
|
8077
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { "data-cimplify-variant-pricing": true, className: cn("text-sm
|
|
8078
|
-
adjustment
|
|
8079
|
-
|
|
8080
|
-
{
|
|
8081
|
-
"data-cimplify-variant-adjustment": true,
|
|
8082
|
-
className: cn(
|
|
8083
|
-
adjustment > 0 ? "text-muted-foreground" : "text-green-600",
|
|
8084
|
-
classNames?.adjustment
|
|
8085
|
-
),
|
|
8086
|
-
children: [
|
|
8087
|
-
adjustment > 0 ? "+" : "",
|
|
8088
|
-
/* @__PURE__ */ jsxRuntime.jsx(Price, { amount: variant.price_adjustment })
|
|
8089
|
-
]
|
|
8090
|
-
}
|
|
8091
|
-
),
|
|
8092
|
-
/* @__PURE__ */ jsxRuntime.jsx(Price, { amount: effectivePrice, className: "text-muted-foreground" })
|
|
8088
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { "data-cimplify-variant-pricing": true, className: cn("text-sm text-muted-foreground", classNames?.pricing), children: [
|
|
8089
|
+
adjustment > 0 ? "+" : adjustment < 0 ? "" : "+",
|
|
8090
|
+
/* @__PURE__ */ jsxRuntime.jsx(Price, { amount: variant.price_adjustment })
|
|
8093
8091
|
] })
|
|
8094
8092
|
]
|
|
8095
8093
|
},
|
|
@@ -8148,54 +8146,46 @@ function AddOnSelector({
|
|
|
8148
8146
|
(id) => addOn.options.some((o) => o.id === id)
|
|
8149
8147
|
).length;
|
|
8150
8148
|
const minMet = !addOn.min_selections || currentSelections >= addOn.min_selections;
|
|
8149
|
+
const isSingleSelect = addOn.is_mutually_exclusive || !addOn.is_multiple_allowed;
|
|
8151
8150
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8152
8151
|
"div",
|
|
8153
8152
|
{
|
|
8154
8153
|
"data-cimplify-addon-group": true,
|
|
8155
|
-
className: cn(
|
|
8154
|
+
className: cn(classNames?.group),
|
|
8156
8155
|
children: [
|
|
8157
8156
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
8158
8157
|
"div",
|
|
8159
8158
|
{
|
|
8160
8159
|
"data-cimplify-addon-header": true,
|
|
8161
|
-
className: cn("flex items-center justify-between
|
|
8160
|
+
className: cn("flex items-center justify-between py-3", classNames?.header),
|
|
8162
8161
|
children: [
|
|
8163
8162
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
8164
|
-
/* @__PURE__ */ jsxRuntime.
|
|
8163
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8165
8164
|
"span",
|
|
8166
8165
|
{
|
|
8167
8166
|
"data-cimplify-addon-name": true,
|
|
8168
|
-
className: cn("text-
|
|
8169
|
-
children:
|
|
8170
|
-
addOn.name,
|
|
8171
|
-
addOn.is_required && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8172
|
-
"span",
|
|
8173
|
-
{
|
|
8174
|
-
"data-cimplify-addon-required": true,
|
|
8175
|
-
className: cn("text-destructive ml-1", classNames?.required),
|
|
8176
|
-
children: [
|
|
8177
|
-
" ",
|
|
8178
|
-
"*"
|
|
8179
|
-
]
|
|
8180
|
-
}
|
|
8181
|
-
)
|
|
8182
|
-
]
|
|
8167
|
+
className: cn("text-base font-bold", classNames?.name),
|
|
8168
|
+
children: addOn.name
|
|
8183
8169
|
}
|
|
8184
8170
|
),
|
|
8185
8171
|
(addOn.min_selections || addOn.max_selections) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8186
8172
|
"span",
|
|
8187
8173
|
{
|
|
8188
8174
|
"data-cimplify-addon-constraint": true,
|
|
8189
|
-
className: cn("text-xs text-muted-foreground
|
|
8175
|
+
className: cn("block text-xs text-muted-foreground mt-0.5", classNames?.constraint),
|
|
8190
8176
|
children: addOn.min_selections && addOn.max_selections ? `Choose ${addOn.min_selections}\u2013${addOn.max_selections}` : addOn.min_selections ? `Choose at least ${addOn.min_selections}` : `Choose up to ${addOn.max_selections}`
|
|
8191
8177
|
}
|
|
8192
8178
|
)
|
|
8193
8179
|
] }),
|
|
8194
|
-
!minMet && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8180
|
+
(addOn.is_required || !minMet) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8195
8181
|
"span",
|
|
8196
8182
|
{
|
|
8197
|
-
"data-cimplify-addon-
|
|
8198
|
-
className: cn(
|
|
8183
|
+
"data-cimplify-addon-required": true,
|
|
8184
|
+
className: cn(
|
|
8185
|
+
"text-xs font-semibold px-2.5 py-1 rounded",
|
|
8186
|
+
!minMet ? "text-destructive bg-destructive/10" : "text-destructive bg-destructive/10",
|
|
8187
|
+
classNames?.required
|
|
8188
|
+
),
|
|
8199
8189
|
children: "Required"
|
|
8200
8190
|
}
|
|
8201
8191
|
)
|
|
@@ -8206,7 +8196,7 @@ function AddOnSelector({
|
|
|
8206
8196
|
"div",
|
|
8207
8197
|
{
|
|
8208
8198
|
"data-cimplify-addon-options": true,
|
|
8209
|
-
className: cn("
|
|
8199
|
+
className: cn("divide-y divide-border", classNames?.options),
|
|
8210
8200
|
children: addOn.options.map((option) => {
|
|
8211
8201
|
const isSelected = isOptionSelected(option.id);
|
|
8212
8202
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -8218,8 +8208,7 @@ function AddOnSelector({
|
|
|
8218
8208
|
"data-cimplify-addon-option": true,
|
|
8219
8209
|
"data-selected": isSelected || void 0,
|
|
8220
8210
|
className: cn(
|
|
8221
|
-
"w-full flex items-center gap-3
|
|
8222
|
-
isSelected && "bg-primary/5 border-primary",
|
|
8211
|
+
"w-full flex items-center gap-3 py-4 transition-colors text-left cursor-pointer",
|
|
8223
8212
|
isSelected ? classNames?.optionSelected : classNames?.option
|
|
8224
8213
|
),
|
|
8225
8214
|
children: [
|
|
@@ -8230,25 +8219,42 @@ function AddOnSelector({
|
|
|
8230
8219
|
keepMounted: false
|
|
8231
8220
|
}
|
|
8232
8221
|
),
|
|
8222
|
+
isSingleSelect ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
8223
|
+
"span",
|
|
8224
|
+
{
|
|
8225
|
+
"data-cimplify-addon-radio": true,
|
|
8226
|
+
className: cn(
|
|
8227
|
+
"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
|
|
8228
|
+
isSelected ? "border-primary" : "border-muted-foreground/30"
|
|
8229
|
+
),
|
|
8230
|
+
children: isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
|
|
8231
|
+
}
|
|
8232
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
8233
|
+
"span",
|
|
8234
|
+
{
|
|
8235
|
+
"data-cimplify-addon-checkbox": true,
|
|
8236
|
+
className: cn(
|
|
8237
|
+
"w-5 h-5 rounded-sm border-2 flex items-center justify-center shrink-0 transition-colors",
|
|
8238
|
+
isSelected ? "border-primary bg-primary" : "border-muted-foreground/30"
|
|
8239
|
+
),
|
|
8240
|
+
children: isSelected && /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 12 12", className: "w-3 h-3 text-primary-foreground", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
8241
|
+
}
|
|
8242
|
+
),
|
|
8233
8243
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8234
8244
|
"span",
|
|
8235
8245
|
{
|
|
8236
8246
|
"data-cimplify-addon-option-name": true,
|
|
8237
8247
|
className: cn(
|
|
8238
|
-
"flex-1 min-w-0 text-sm
|
|
8239
|
-
isSelected && "text-primary",
|
|
8248
|
+
"flex-1 min-w-0 text-sm",
|
|
8240
8249
|
classNames?.optionName
|
|
8241
8250
|
),
|
|
8242
8251
|
children: option.name
|
|
8243
8252
|
}
|
|
8244
8253
|
),
|
|
8245
|
-
option.default_price != null &&
|
|
8246
|
-
|
|
8247
|
-
{
|
|
8248
|
-
|
|
8249
|
-
prefix: "+"
|
|
8250
|
-
}
|
|
8251
|
-
)
|
|
8254
|
+
option.default_price != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
8255
|
+
"+",
|
|
8256
|
+
/* @__PURE__ */ jsxRuntime.jsx(Price, { amount: option.default_price })
|
|
8257
|
+
] })
|
|
8252
8258
|
]
|
|
8253
8259
|
},
|
|
8254
8260
|
option.id
|
|
@@ -9711,7 +9717,7 @@ function ProductCard({
|
|
|
9711
9717
|
"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
9718
|
classNames?.modal
|
|
9713
9719
|
),
|
|
9714
|
-
children: isOpen && (productDetails ? renderModal ? renderModal(productDetails) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9720
|
+
children: isOpen && (productDetails ? renderModal ? renderModal(productDetails, handleClose) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9715
9721
|
ProductSheet,
|
|
9716
9722
|
{
|
|
9717
9723
|
product: productDetails,
|
package/dist/react.mjs
CHANGED
|
@@ -7972,7 +7972,7 @@ function VariantSelector({
|
|
|
7972
7972
|
if (!variants || variants.length <= 1) {
|
|
7973
7973
|
return null;
|
|
7974
7974
|
}
|
|
7975
|
-
|
|
7975
|
+
basePrice != null ? parsePrice(basePrice) : 0;
|
|
7976
7976
|
if (variantAxes && variantAxes.length > 0) {
|
|
7977
7977
|
return /* @__PURE__ */ jsx("div", { "data-cimplify-variant-selector": true, className: cn("space-y-5", className, classNames?.root), children: variantAxes.map((axis) => {
|
|
7978
7978
|
const labelId = `${idPrefix}-axis-${axis.id}`;
|
|
@@ -8023,14 +8023,16 @@ function VariantSelector({
|
|
|
8023
8023
|
}) });
|
|
8024
8024
|
}
|
|
8025
8025
|
const listLabelId = `${idPrefix}-variant-list`;
|
|
8026
|
-
return /* @__PURE__ */ jsxs("div", { "data-cimplify-variant-selector": true, className: cn(
|
|
8027
|
-
/* @__PURE__ */
|
|
8028
|
-
"
|
|
8026
|
+
return /* @__PURE__ */ jsxs("div", { "data-cimplify-variant-selector": true, className: cn(className, classNames?.root), children: [
|
|
8027
|
+
/* @__PURE__ */ jsxs(
|
|
8028
|
+
"div",
|
|
8029
8029
|
{
|
|
8030
|
-
|
|
8031
|
-
"
|
|
8032
|
-
|
|
8033
|
-
|
|
8030
|
+
"data-cimplify-variant-list-header": true,
|
|
8031
|
+
className: cn("flex items-center justify-between py-3", classNames?.listLabel),
|
|
8032
|
+
children: [
|
|
8033
|
+
/* @__PURE__ */ jsx("label", { id: listLabelId, className: "text-base font-bold", children: "Options" }),
|
|
8034
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-destructive bg-destructive/10 px-2.5 py-1 rounded", children: "Required" })
|
|
8035
|
+
]
|
|
8034
8036
|
}
|
|
8035
8037
|
),
|
|
8036
8038
|
/* @__PURE__ */ jsx(
|
|
@@ -8043,11 +8045,10 @@ function VariantSelector({
|
|
|
8043
8045
|
onVariantChange(variant?.id, variant);
|
|
8044
8046
|
},
|
|
8045
8047
|
"data-cimplify-variant-list": true,
|
|
8046
|
-
className: cn("
|
|
8048
|
+
className: cn("divide-y divide-border", classNames?.list),
|
|
8047
8049
|
children: variants.map((variant) => {
|
|
8048
8050
|
const isSelected = selectedVariantId === variant.id;
|
|
8049
8051
|
const adjustment = parsePrice(variant.price_adjustment);
|
|
8050
|
-
const effectivePrice = basePriceNum + adjustment;
|
|
8051
8052
|
return /* @__PURE__ */ jsxs(
|
|
8052
8053
|
Radio.Root,
|
|
8053
8054
|
{
|
|
@@ -8055,35 +8056,32 @@ function VariantSelector({
|
|
|
8055
8056
|
"data-cimplify-variant-option": true,
|
|
8056
8057
|
"data-selected": isSelected || void 0,
|
|
8057
8058
|
className: cn(
|
|
8058
|
-
"w-full flex items-center
|
|
8059
|
-
isSelected && "bg-primary/5 border-primary",
|
|
8059
|
+
"w-full flex items-center gap-3 py-4 transition-colors cursor-pointer",
|
|
8060
8060
|
isSelected ? classNames?.optionSelected : classNames?.option
|
|
8061
8061
|
),
|
|
8062
8062
|
children: [
|
|
8063
|
+
/* @__PURE__ */ jsx(
|
|
8064
|
+
"span",
|
|
8065
|
+
{
|
|
8066
|
+
"data-cimplify-variant-radio": true,
|
|
8067
|
+
className: cn(
|
|
8068
|
+
"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
|
|
8069
|
+
isSelected ? "border-primary" : "border-muted-foreground/30"
|
|
8070
|
+
),
|
|
8071
|
+
children: isSelected && /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
|
|
8072
|
+
}
|
|
8073
|
+
),
|
|
8063
8074
|
/* @__PURE__ */ jsx(
|
|
8064
8075
|
"span",
|
|
8065
8076
|
{
|
|
8066
8077
|
"data-cimplify-variant-name": true,
|
|
8067
|
-
className: cn("
|
|
8078
|
+
className: cn("flex-1 min-w-0 text-sm", classNames?.name),
|
|
8068
8079
|
children: getVariantDisplayName(variant, productName)
|
|
8069
8080
|
}
|
|
8070
8081
|
),
|
|
8071
|
-
/* @__PURE__ */ jsxs("span", { "data-cimplify-variant-pricing": true, className: cn("text-sm
|
|
8072
|
-
adjustment
|
|
8073
|
-
|
|
8074
|
-
{
|
|
8075
|
-
"data-cimplify-variant-adjustment": true,
|
|
8076
|
-
className: cn(
|
|
8077
|
-
adjustment > 0 ? "text-muted-foreground" : "text-green-600",
|
|
8078
|
-
classNames?.adjustment
|
|
8079
|
-
),
|
|
8080
|
-
children: [
|
|
8081
|
-
adjustment > 0 ? "+" : "",
|
|
8082
|
-
/* @__PURE__ */ jsx(Price, { amount: variant.price_adjustment })
|
|
8083
|
-
]
|
|
8084
|
-
}
|
|
8085
|
-
),
|
|
8086
|
-
/* @__PURE__ */ jsx(Price, { amount: effectivePrice, className: "text-muted-foreground" })
|
|
8082
|
+
/* @__PURE__ */ jsxs("span", { "data-cimplify-variant-pricing": true, className: cn("text-sm text-muted-foreground", classNames?.pricing), children: [
|
|
8083
|
+
adjustment > 0 ? "+" : adjustment < 0 ? "" : "+",
|
|
8084
|
+
/* @__PURE__ */ jsx(Price, { amount: variant.price_adjustment })
|
|
8087
8085
|
] })
|
|
8088
8086
|
]
|
|
8089
8087
|
},
|
|
@@ -8142,54 +8140,46 @@ function AddOnSelector({
|
|
|
8142
8140
|
(id) => addOn.options.some((o) => o.id === id)
|
|
8143
8141
|
).length;
|
|
8144
8142
|
const minMet = !addOn.min_selections || currentSelections >= addOn.min_selections;
|
|
8143
|
+
const isSingleSelect = addOn.is_mutually_exclusive || !addOn.is_multiple_allowed;
|
|
8145
8144
|
return /* @__PURE__ */ jsxs(
|
|
8146
8145
|
"div",
|
|
8147
8146
|
{
|
|
8148
8147
|
"data-cimplify-addon-group": true,
|
|
8149
|
-
className: cn(
|
|
8148
|
+
className: cn(classNames?.group),
|
|
8150
8149
|
children: [
|
|
8151
8150
|
/* @__PURE__ */ jsxs(
|
|
8152
8151
|
"div",
|
|
8153
8152
|
{
|
|
8154
8153
|
"data-cimplify-addon-header": true,
|
|
8155
|
-
className: cn("flex items-center justify-between
|
|
8154
|
+
className: cn("flex items-center justify-between py-3", classNames?.header),
|
|
8156
8155
|
children: [
|
|
8157
8156
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
8158
|
-
/* @__PURE__ */
|
|
8157
|
+
/* @__PURE__ */ jsx(
|
|
8159
8158
|
"span",
|
|
8160
8159
|
{
|
|
8161
8160
|
"data-cimplify-addon-name": true,
|
|
8162
|
-
className: cn("text-
|
|
8163
|
-
children:
|
|
8164
|
-
addOn.name,
|
|
8165
|
-
addOn.is_required && /* @__PURE__ */ jsxs(
|
|
8166
|
-
"span",
|
|
8167
|
-
{
|
|
8168
|
-
"data-cimplify-addon-required": true,
|
|
8169
|
-
className: cn("text-destructive ml-1", classNames?.required),
|
|
8170
|
-
children: [
|
|
8171
|
-
" ",
|
|
8172
|
-
"*"
|
|
8173
|
-
]
|
|
8174
|
-
}
|
|
8175
|
-
)
|
|
8176
|
-
]
|
|
8161
|
+
className: cn("text-base font-bold", classNames?.name),
|
|
8162
|
+
children: addOn.name
|
|
8177
8163
|
}
|
|
8178
8164
|
),
|
|
8179
8165
|
(addOn.min_selections || addOn.max_selections) && /* @__PURE__ */ jsx(
|
|
8180
8166
|
"span",
|
|
8181
8167
|
{
|
|
8182
8168
|
"data-cimplify-addon-constraint": true,
|
|
8183
|
-
className: cn("text-xs text-muted-foreground
|
|
8169
|
+
className: cn("block text-xs text-muted-foreground mt-0.5", classNames?.constraint),
|
|
8184
8170
|
children: addOn.min_selections && addOn.max_selections ? `Choose ${addOn.min_selections}\u2013${addOn.max_selections}` : addOn.min_selections ? `Choose at least ${addOn.min_selections}` : `Choose up to ${addOn.max_selections}`
|
|
8185
8171
|
}
|
|
8186
8172
|
)
|
|
8187
8173
|
] }),
|
|
8188
|
-
!minMet && /* @__PURE__ */ jsx(
|
|
8174
|
+
(addOn.is_required || !minMet) && /* @__PURE__ */ jsx(
|
|
8189
8175
|
"span",
|
|
8190
8176
|
{
|
|
8191
|
-
"data-cimplify-addon-
|
|
8192
|
-
className: cn(
|
|
8177
|
+
"data-cimplify-addon-required": true,
|
|
8178
|
+
className: cn(
|
|
8179
|
+
"text-xs font-semibold px-2.5 py-1 rounded",
|
|
8180
|
+
!minMet ? "text-destructive bg-destructive/10" : "text-destructive bg-destructive/10",
|
|
8181
|
+
classNames?.required
|
|
8182
|
+
),
|
|
8193
8183
|
children: "Required"
|
|
8194
8184
|
}
|
|
8195
8185
|
)
|
|
@@ -8200,7 +8190,7 @@ function AddOnSelector({
|
|
|
8200
8190
|
"div",
|
|
8201
8191
|
{
|
|
8202
8192
|
"data-cimplify-addon-options": true,
|
|
8203
|
-
className: cn("
|
|
8193
|
+
className: cn("divide-y divide-border", classNames?.options),
|
|
8204
8194
|
children: addOn.options.map((option) => {
|
|
8205
8195
|
const isSelected = isOptionSelected(option.id);
|
|
8206
8196
|
return /* @__PURE__ */ jsxs(
|
|
@@ -8212,8 +8202,7 @@ function AddOnSelector({
|
|
|
8212
8202
|
"data-cimplify-addon-option": true,
|
|
8213
8203
|
"data-selected": isSelected || void 0,
|
|
8214
8204
|
className: cn(
|
|
8215
|
-
"w-full flex items-center gap-3
|
|
8216
|
-
isSelected && "bg-primary/5 border-primary",
|
|
8205
|
+
"w-full flex items-center gap-3 py-4 transition-colors text-left cursor-pointer",
|
|
8217
8206
|
isSelected ? classNames?.optionSelected : classNames?.option
|
|
8218
8207
|
),
|
|
8219
8208
|
children: [
|
|
@@ -8224,25 +8213,42 @@ function AddOnSelector({
|
|
|
8224
8213
|
keepMounted: false
|
|
8225
8214
|
}
|
|
8226
8215
|
),
|
|
8216
|
+
isSingleSelect ? /* @__PURE__ */ jsx(
|
|
8217
|
+
"span",
|
|
8218
|
+
{
|
|
8219
|
+
"data-cimplify-addon-radio": true,
|
|
8220
|
+
className: cn(
|
|
8221
|
+
"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
|
|
8222
|
+
isSelected ? "border-primary" : "border-muted-foreground/30"
|
|
8223
|
+
),
|
|
8224
|
+
children: isSelected && /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
|
|
8225
|
+
}
|
|
8226
|
+
) : /* @__PURE__ */ jsx(
|
|
8227
|
+
"span",
|
|
8228
|
+
{
|
|
8229
|
+
"data-cimplify-addon-checkbox": true,
|
|
8230
|
+
className: cn(
|
|
8231
|
+
"w-5 h-5 rounded-sm border-2 flex items-center justify-center shrink-0 transition-colors",
|
|
8232
|
+
isSelected ? "border-primary bg-primary" : "border-muted-foreground/30"
|
|
8233
|
+
),
|
|
8234
|
+
children: isSelected && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", className: "w-3 h-3 text-primary-foreground", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
8235
|
+
}
|
|
8236
|
+
),
|
|
8227
8237
|
/* @__PURE__ */ jsx(
|
|
8228
8238
|
"span",
|
|
8229
8239
|
{
|
|
8230
8240
|
"data-cimplify-addon-option-name": true,
|
|
8231
8241
|
className: cn(
|
|
8232
|
-
"flex-1 min-w-0 text-sm
|
|
8233
|
-
isSelected && "text-primary",
|
|
8242
|
+
"flex-1 min-w-0 text-sm",
|
|
8234
8243
|
classNames?.optionName
|
|
8235
8244
|
),
|
|
8236
8245
|
children: option.name
|
|
8237
8246
|
}
|
|
8238
8247
|
),
|
|
8239
|
-
option.default_price != null &&
|
|
8240
|
-
|
|
8241
|
-
{
|
|
8242
|
-
|
|
8243
|
-
prefix: "+"
|
|
8244
|
-
}
|
|
8245
|
-
)
|
|
8248
|
+
option.default_price != null && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
8249
|
+
"+",
|
|
8250
|
+
/* @__PURE__ */ jsx(Price, { amount: option.default_price })
|
|
8251
|
+
] })
|
|
8246
8252
|
]
|
|
8247
8253
|
},
|
|
8248
8254
|
option.id
|
|
@@ -9705,7 +9711,7 @@ function ProductCard({
|
|
|
9705
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",
|
|
9706
9712
|
classNames?.modal
|
|
9707
9713
|
),
|
|
9708
|
-
children: isOpen && (productDetails ? renderModal ? renderModal(productDetails) : /* @__PURE__ */ jsx(
|
|
9714
|
+
children: isOpen && (productDetails ? renderModal ? renderModal(productDetails, handleClose) : /* @__PURE__ */ jsx(
|
|
9709
9715
|
ProductSheet,
|
|
9710
9716
|
{
|
|
9711
9717
|
product: productDetails,
|
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}.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}
|
|
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-divide-y-reverse:0;--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}.shrink-0{flex-shrink:0}.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}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--color-border,oklch(90% 0 0))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.rounded{border-radius:var(--radius,.5rem)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.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-muted-foreground\/30{border-color:#6363634d}@supports (color:color-mix(in lab, red, red)){.border-muted-foreground\/30{border-color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 30%, transparent)}}.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-destructive\/10{background-color:#bb061e1a}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--color-destructive,oklch(50% .2 25)) 10%, transparent)}}.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-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "add-on-selector.tsx",
|
|
12
|
-
"content": "\"use client\";\n\nimport React, { useCallback } from \"react\";\nimport { Checkbox } from \"@base-ui/react/checkbox\";\nimport type { AddOnWithOptions } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface AddOnSelectorClassNames {\n root?: string;\n group?: string;\n header?: string;\n name?: string;\n required?: string;\n constraint?: string;\n validation?: string;\n options?: string;\n option?: string;\n optionSelected?: string;\n optionName?: string;\n optionDescription?: string;\n}\n\nexport interface AddOnSelectorProps {\n addOns: AddOnWithOptions[];\n selectedOptions: string[];\n onOptionsChange: (optionIds: string[]) => void;\n className?: string;\n classNames?: AddOnSelectorClassNames;\n}\n\nexport function AddOnSelector({\n addOns,\n selectedOptions,\n onOptionsChange,\n className,\n classNames,\n}: AddOnSelectorProps): React.ReactElement | null {\n const isOptionSelected = useCallback(\n (optionId: string) => selectedOptions.includes(optionId),\n [selectedOptions],\n );\n\n const handleCheckedChange = useCallback(\n (addOn: AddOnWithOptions, optionId: string, checked: boolean) => {\n const isSelected = selectedOptions.includes(optionId);\n\n if (addOn.is_mutually_exclusive || !addOn.is_multiple_allowed) {\n const groupOptionIds = new Set(addOn.options.map((o) => o.id));\n const withoutGroup = selectedOptions.filter((id) => !groupOptionIds.has(id));\n\n if (isSelected) {\n if (!addOn.is_required) {\n onOptionsChange(withoutGroup);\n }\n } else {\n onOptionsChange([...withoutGroup, optionId]);\n }\n } else {\n if (isSelected) {\n onOptionsChange(selectedOptions.filter((id) => id !== optionId));\n } else {\n const currentCount = selectedOptions.filter((id) =>\n addOn.options.some((o) => o.id === id),\n ).length;\n\n if (addOn.max_selections && currentCount >= addOn.max_selections) {\n return;\n }\n\n onOptionsChange([...selectedOptions, optionId]);\n }\n }\n },\n [selectedOptions, onOptionsChange],\n );\n\n if (!addOns || addOns.length === 0) {\n return null;\n }\n\n return (\n <div data-cimplify-addon-selector className={cn(\"space-y-6\", className, classNames?.root)}>\n {addOns.map((addOn) => {\n const currentSelections = selectedOptions.filter((id) =>\n addOn.options.some((o) => o.id === id),\n ).length;\n const minMet = !addOn.min_selections || currentSelections >= addOn.min_selections;\n\n return (\n <div\n key={addOn.id}\n data-cimplify-addon-group\n className={cn(
|
|
12
|
+
"content": "\"use client\";\n\nimport React, { useCallback } from \"react\";\nimport { Checkbox } from \"@base-ui/react/checkbox\";\nimport type { AddOnWithOptions } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface AddOnSelectorClassNames {\n root?: string;\n group?: string;\n header?: string;\n name?: string;\n required?: string;\n constraint?: string;\n validation?: string;\n options?: string;\n option?: string;\n optionSelected?: string;\n optionName?: string;\n optionDescription?: string;\n}\n\nexport interface AddOnSelectorProps {\n addOns: AddOnWithOptions[];\n selectedOptions: string[];\n onOptionsChange: (optionIds: string[]) => void;\n className?: string;\n classNames?: AddOnSelectorClassNames;\n}\n\nexport function AddOnSelector({\n addOns,\n selectedOptions,\n onOptionsChange,\n className,\n classNames,\n}: AddOnSelectorProps): React.ReactElement | null {\n const isOptionSelected = useCallback(\n (optionId: string) => selectedOptions.includes(optionId),\n [selectedOptions],\n );\n\n const handleCheckedChange = useCallback(\n (addOn: AddOnWithOptions, optionId: string, checked: boolean) => {\n const isSelected = selectedOptions.includes(optionId);\n\n if (addOn.is_mutually_exclusive || !addOn.is_multiple_allowed) {\n const groupOptionIds = new Set(addOn.options.map((o) => o.id));\n const withoutGroup = selectedOptions.filter((id) => !groupOptionIds.has(id));\n\n if (isSelected) {\n if (!addOn.is_required) {\n onOptionsChange(withoutGroup);\n }\n } else {\n onOptionsChange([...withoutGroup, optionId]);\n }\n } else {\n if (isSelected) {\n onOptionsChange(selectedOptions.filter((id) => id !== optionId));\n } else {\n const currentCount = selectedOptions.filter((id) =>\n addOn.options.some((o) => o.id === id),\n ).length;\n\n if (addOn.max_selections && currentCount >= addOn.max_selections) {\n return;\n }\n\n onOptionsChange([...selectedOptions, optionId]);\n }\n }\n },\n [selectedOptions, onOptionsChange],\n );\n\n if (!addOns || addOns.length === 0) {\n return null;\n }\n\n return (\n <div data-cimplify-addon-selector className={cn(\"space-y-6\", className, classNames?.root)}>\n {addOns.map((addOn) => {\n const currentSelections = selectedOptions.filter((id) =>\n addOn.options.some((o) => o.id === id),\n ).length;\n const minMet = !addOn.min_selections || currentSelections >= addOn.min_selections;\n const isSingleSelect = addOn.is_mutually_exclusive || !addOn.is_multiple_allowed;\n\n return (\n <div\n key={addOn.id}\n data-cimplify-addon-group\n className={cn(classNames?.group)}\n >\n <div\n data-cimplify-addon-header\n className={cn(\"flex items-center justify-between py-3\", classNames?.header)}\n >\n <div>\n <span\n data-cimplify-addon-name\n className={cn(\"text-base font-bold\", classNames?.name)}\n >\n {addOn.name}\n </span>\n {(addOn.min_selections || addOn.max_selections) && (\n <span\n data-cimplify-addon-constraint\n className={cn(\"block text-xs text-muted-foreground mt-0.5\", classNames?.constraint)}\n >\n {addOn.min_selections && addOn.max_selections\n ? `Choose ${addOn.min_selections}\\u2013${addOn.max_selections}`\n : addOn.min_selections\n ? `Choose at least ${addOn.min_selections}`\n : `Choose up to ${addOn.max_selections}`}\n </span>\n )}\n </div>\n {(addOn.is_required || !minMet) && (\n <span\n data-cimplify-addon-required\n className={cn(\n \"text-xs font-semibold px-2.5 py-1 rounded\",\n !minMet\n ? \"text-destructive bg-destructive/10\"\n : \"text-destructive bg-destructive/10\",\n classNames?.required,\n )}\n >\n Required\n </span>\n )}\n </div>\n\n <div\n data-cimplify-addon-options\n className={cn(\"divide-y divide-border\", classNames?.options)}\n >\n {addOn.options.map((option) => {\n const isSelected = isOptionSelected(option.id);\n\n return (\n <Checkbox.Root\n key={option.id}\n checked={isSelected}\n onCheckedChange={(checked) =>\n handleCheckedChange(addOn, option.id, checked)\n }\n value={option.id}\n data-cimplify-addon-option\n data-selected={isSelected || undefined}\n className={cn(\n \"w-full flex items-center gap-3 py-4 transition-colors text-left cursor-pointer\",\n isSelected ? classNames?.optionSelected : classNames?.option,\n )}\n >\n <Checkbox.Indicator\n className=\"hidden\"\n keepMounted={false}\n />\n\n {/* Visual indicator: radio circle for single-select, checkbox square for multi-select */}\n {isSingleSelect ? (\n <span\n data-cimplify-addon-radio\n className={cn(\n \"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors\",\n isSelected ? \"border-primary\" : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && <span className=\"w-2.5 h-2.5 rounded-full bg-primary\" />}\n </span>\n ) : (\n <span\n data-cimplify-addon-checkbox\n className={cn(\n \"w-5 h-5 rounded-sm border-2 flex items-center justify-center shrink-0 transition-colors\",\n isSelected ? \"border-primary bg-primary\" : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <svg viewBox=\"0 0 12 12\" className=\"w-3 h-3 text-primary-foreground\" fill=\"none\">\n <path d=\"M2 6l3 3 5-5\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n </svg>\n )}\n </span>\n )}\n\n <span\n data-cimplify-addon-option-name\n className={cn(\n \"flex-1 min-w-0 text-sm\",\n classNames?.optionName,\n )}\n >\n {option.name}\n </span>\n\n {option.default_price != null && (\n <span className=\"text-sm text-muted-foreground\">\n +<Price amount={option.default_price} />\n </span>\n )}\n </Checkbox.Root>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
|
@@ -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 [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"
|
|
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 and a close callback. */\n renderModal?: (product: ProductWithDetails, onClose: () => void) => 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, handleClose)\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
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "variant-selector.tsx",
|
|
12
|
-
"content": "\"use client\";\n\nimport React, { useState, useEffect, useRef, useId } from \"react\";\nimport { RadioGroup } from \"@base-ui/react/radio-group\";\nimport { Radio } from \"@base-ui/react/radio\";\nimport type { ProductVariant, VariantAxisWithValues } from \"@cimplify/sdk\";\nimport type { Money } from \"@cimplify/sdk\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { getVariantDisplayName } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface VariantSelectorClassNames {\n root?: string;\n axisLabel?: string;\n axisOptions?: string;\n option?: string;\n optionSelected?: string;\n listLabel?: string;\n list?: string;\n name?: string;\n pricing?: string;\n adjustment?: string;\n}\n\nexport interface VariantSelectorProps {\n variants: ProductVariant[];\n variantAxes?: VariantAxisWithValues[];\n basePrice?: Money;\n selectedVariantId?: string;\n onVariantChange: (variantId: string | undefined, variant: ProductVariant | undefined) => void;\n productName?: string;\n className?: string;\n classNames?: VariantSelectorClassNames;\n}\n\nexport function VariantSelector({\n variants,\n variantAxes,\n basePrice,\n selectedVariantId,\n onVariantChange,\n productName,\n className,\n classNames,\n}: VariantSelectorProps): React.ReactElement | null {\n const [axisSelections, setAxisSelections] = useState<Record<string, string>>({});\n const initialized = useRef(false);\n const idPrefix = useId();\n\n useEffect(() => {\n initialized.current = false;\n }, [variants]);\n\n useEffect(() => {\n if (initialized.current) return;\n if (!variants || variants.length === 0) return;\n\n const defaultVariant = variants.find((v) => v.is_default) || variants[0];\n if (!defaultVariant) return;\n\n initialized.current = true;\n onVariantChange(defaultVariant.id, defaultVariant);\n\n if (defaultVariant.display_attributes) {\n const initial: Record<string, string> = {};\n for (const attr of defaultVariant.display_attributes) {\n initial[attr.axis_id] = attr.value_id;\n }\n setAxisSelections(initial);\n }\n }, [variants, onVariantChange]);\n\n useEffect(() => {\n if (!initialized.current) return;\n if (!variantAxes || variantAxes.length === 0) return;\n\n const match = variants.find((v) => {\n if (!v.display_attributes) return false;\n return v.display_attributes.every(\n (attr) => axisSelections[attr.axis_id] === attr.value_id,\n );\n });\n\n if (match && match.id !== selectedVariantId) {\n onVariantChange(match.id, match);\n }\n }, [axisSelections, variants, variantAxes, selectedVariantId, onVariantChange]);\n\n if (!variants || variants.length <= 1) {\n return null;\n }\n\n const basePriceNum = basePrice != null ? parsePrice(basePrice) : 0;\n\n if (variantAxes && variantAxes.length > 0) {\n return (\n <div data-cimplify-variant-selector className={cn(\"space-y-5\", className, classNames?.root)}>\n {variantAxes.map((axis) => {\n const labelId = `${idPrefix}-axis-${axis.id}`;\n return (\n <div key={axis.id} data-cimplify-variant-axis>\n <label\n id={labelId}\n data-cimplify-variant-axis-label\n className={cn(\"block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3\", classNames?.axisLabel)}\n >\n {axis.name}\n </label>\n <RadioGroup\n aria-labelledby={labelId}\n value={axisSelections[axis.id] ?? \"\"}\n onValueChange={(value) => {\n setAxisSelections((prev) => ({\n ...prev,\n [axis.id]: value,\n }));\n }}\n data-cimplify-variant-axis-options\n className={cn(\"flex flex-wrap gap-2\", classNames?.axisOptions)}\n >\n {axis.values.map((value) => {\n const isSelected = axisSelections[axis.id] === value.id;\n return (\n <Radio.Root\n key={value.id}\n value={value.id}\n data-cimplify-variant-option\n data-selected={isSelected || undefined}\n className={cn(\n \"px-4 py-2 border text-sm font-medium transition-colors border-border hover:border-primary/50\",\n isSelected && \"bg-primary text-primary-foreground border-primary\",\n isSelected ? classNames?.optionSelected : classNames?.option,\n )}\n >\n {value.name}\n </Radio.Root>\n );\n })}\n </RadioGroup>\n </div>\n );\n })}\n </div>\n );\n }\n\n const listLabelId = `${idPrefix}-variant-list`;\n\n return (\n <div data-cimplify-variant-selector className={cn(
|
|
12
|
+
"content": "\"use client\";\n\nimport React, { useState, useEffect, useRef, useId } from \"react\";\nimport { RadioGroup } from \"@base-ui/react/radio-group\";\nimport { Radio } from \"@base-ui/react/radio\";\nimport type { ProductVariant, VariantAxisWithValues } from \"@cimplify/sdk\";\nimport type { Money } from \"@cimplify/sdk\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { getVariantDisplayName } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface VariantSelectorClassNames {\n root?: string;\n axisLabel?: string;\n axisOptions?: string;\n option?: string;\n optionSelected?: string;\n listLabel?: string;\n list?: string;\n name?: string;\n pricing?: string;\n adjustment?: string;\n}\n\nexport interface VariantSelectorProps {\n variants: ProductVariant[];\n variantAxes?: VariantAxisWithValues[];\n basePrice?: Money;\n selectedVariantId?: string;\n onVariantChange: (variantId: string | undefined, variant: ProductVariant | undefined) => void;\n productName?: string;\n className?: string;\n classNames?: VariantSelectorClassNames;\n}\n\nexport function VariantSelector({\n variants,\n variantAxes,\n basePrice,\n selectedVariantId,\n onVariantChange,\n productName,\n className,\n classNames,\n}: VariantSelectorProps): React.ReactElement | null {\n const [axisSelections, setAxisSelections] = useState<Record<string, string>>({});\n const initialized = useRef(false);\n const idPrefix = useId();\n\n useEffect(() => {\n initialized.current = false;\n }, [variants]);\n\n useEffect(() => {\n if (initialized.current) return;\n if (!variants || variants.length === 0) return;\n\n const defaultVariant = variants.find((v) => v.is_default) || variants[0];\n if (!defaultVariant) return;\n\n initialized.current = true;\n onVariantChange(defaultVariant.id, defaultVariant);\n\n if (defaultVariant.display_attributes) {\n const initial: Record<string, string> = {};\n for (const attr of defaultVariant.display_attributes) {\n initial[attr.axis_id] = attr.value_id;\n }\n setAxisSelections(initial);\n }\n }, [variants, onVariantChange]);\n\n useEffect(() => {\n if (!initialized.current) return;\n if (!variantAxes || variantAxes.length === 0) return;\n\n const match = variants.find((v) => {\n if (!v.display_attributes) return false;\n return v.display_attributes.every(\n (attr) => axisSelections[attr.axis_id] === attr.value_id,\n );\n });\n\n if (match && match.id !== selectedVariantId) {\n onVariantChange(match.id, match);\n }\n }, [axisSelections, variants, variantAxes, selectedVariantId, onVariantChange]);\n\n if (!variants || variants.length <= 1) {\n return null;\n }\n\n const basePriceNum = basePrice != null ? parsePrice(basePrice) : 0;\n\n if (variantAxes && variantAxes.length > 0) {\n return (\n <div data-cimplify-variant-selector className={cn(\"space-y-5\", className, classNames?.root)}>\n {variantAxes.map((axis) => {\n const labelId = `${idPrefix}-axis-${axis.id}`;\n return (\n <div key={axis.id} data-cimplify-variant-axis>\n <label\n id={labelId}\n data-cimplify-variant-axis-label\n className={cn(\"block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3\", classNames?.axisLabel)}\n >\n {axis.name}\n </label>\n <RadioGroup\n aria-labelledby={labelId}\n value={axisSelections[axis.id] ?? \"\"}\n onValueChange={(value) => {\n setAxisSelections((prev) => ({\n ...prev,\n [axis.id]: value,\n }));\n }}\n data-cimplify-variant-axis-options\n className={cn(\"flex flex-wrap gap-2\", classNames?.axisOptions)}\n >\n {axis.values.map((value) => {\n const isSelected = axisSelections[axis.id] === value.id;\n return (\n <Radio.Root\n key={value.id}\n value={value.id}\n data-cimplify-variant-option\n data-selected={isSelected || undefined}\n className={cn(\n \"px-4 py-2 border text-sm font-medium transition-colors border-border hover:border-primary/50\",\n isSelected && \"bg-primary text-primary-foreground border-primary\",\n isSelected ? classNames?.optionSelected : classNames?.option,\n )}\n >\n {value.name}\n </Radio.Root>\n );\n })}\n </RadioGroup>\n </div>\n );\n })}\n </div>\n );\n }\n\n const listLabelId = `${idPrefix}-variant-list`;\n\n return (\n <div data-cimplify-variant-selector className={cn(className, classNames?.root)}>\n <div\n data-cimplify-variant-list-header\n className={cn(\"flex items-center justify-between py-3\", classNames?.listLabel)}\n >\n <label id={listLabelId} className=\"text-base font-bold\">\n Options\n </label>\n <span className=\"text-xs font-semibold text-destructive bg-destructive/10 px-2.5 py-1 rounded\">\n Required\n </span>\n </div>\n <RadioGroup\n aria-labelledby={listLabelId}\n value={selectedVariantId ?? \"\"}\n onValueChange={(value) => {\n const variant = variants.find((v) => v.id === value);\n onVariantChange(variant?.id, variant);\n }}\n data-cimplify-variant-list\n className={cn(\"divide-y divide-border\", classNames?.list)}\n >\n {variants.map((variant) => {\n const isSelected = selectedVariantId === variant.id;\n const adjustment = parsePrice(variant.price_adjustment);\n\n return (\n <Radio.Root\n key={variant.id}\n value={variant.id}\n data-cimplify-variant-option\n data-selected={isSelected || undefined}\n className={cn(\n \"w-full flex items-center gap-3 py-4 transition-colors cursor-pointer\",\n isSelected ? classNames?.optionSelected : classNames?.option,\n )}\n >\n <span\n data-cimplify-variant-radio\n className={cn(\n \"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors\",\n isSelected ? \"border-primary\" : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && <span className=\"w-2.5 h-2.5 rounded-full bg-primary\" />}\n </span>\n <span\n data-cimplify-variant-name\n className={cn(\"flex-1 min-w-0 text-sm\", classNames?.name)}\n >\n {getVariantDisplayName(variant, productName)}\n </span>\n <span data-cimplify-variant-pricing className={cn(\"text-sm text-muted-foreground\", classNames?.pricing)}>\n {adjustment > 0 ? \"+\" : adjustment < 0 ? \"\" : \"+\"}\n <Price amount={variant.price_adjustment} />\n </span>\n </Radio.Root>\n );\n })}\n </RadioGroup>\n </div>\n );\n}\n"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|