@cimplify/sdk 0.13.1 → 0.14.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.mjs CHANGED
@@ -7972,7 +7972,7 @@ function VariantSelector({
7972
7972
  if (!variants || variants.length <= 1) {
7973
7973
  return null;
7974
7974
  }
7975
- const basePriceNum = basePrice != null ? parsePrice(basePrice) : 0;
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("space-y-5", className, classNames?.root), children: [
8027
- /* @__PURE__ */ jsx(
8028
- "label",
8026
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-variant-selector": true, className: cn(className, classNames?.root), children: [
8027
+ /* @__PURE__ */ jsxs(
8028
+ "div",
8029
8029
  {
8030
- id: listLabelId,
8031
- "data-cimplify-variant-list-label": true,
8032
- className: cn("block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", classNames?.listLabel),
8033
- children: "Options"
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("space-y-2", classNames?.list),
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 justify-between px-4 py-3 border transition-colors border-border hover:border-primary/50",
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("font-medium", isSelected && "text-primary", classNames?.name),
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 flex items-center gap-2", classNames?.pricing), children: [
8072
- adjustment !== 0 && /* @__PURE__ */ jsxs(
8073
- "span",
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("border border-border p-5", classNames?.group),
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 mb-4", classNames?.header),
8154
+ className: cn("flex items-center justify-between py-3", classNames?.header),
8156
8155
  children: [
8157
8156
  /* @__PURE__ */ jsxs("div", { children: [
8158
- /* @__PURE__ */ jsxs(
8157
+ /* @__PURE__ */ jsx(
8159
8158
  "span",
8160
8159
  {
8161
8160
  "data-cimplify-addon-name": true,
8162
- className: cn("text-xs font-medium uppercase tracking-wider text-muted-foreground", classNames?.name),
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/70 mt-1", classNames?.constraint),
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-validation": true,
8192
- className: cn("text-xs text-destructive font-medium", classNames?.validation),
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("space-y-1", classNames?.options),
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 px-4 py-3 border transition-colors text-left border-transparent hover:bg-muted/50",
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 font-medium",
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 && parsePrice(option.default_price) !== 0 && /* @__PURE__ */ jsx(
8240
- Price,
8241
- {
8242
- amount: option.default_price,
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
@@ -8328,14 +8334,14 @@ function BundleSelector({
8328
8334
  }
8329
8335
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-bundle-selector": true, className: cn("space-y-4", className, classNames?.root), children: [
8330
8336
  /* @__PURE__ */ jsx(
8331
- "span",
8337
+ "div",
8332
8338
  {
8333
8339
  "data-cimplify-bundle-heading": true,
8334
- className: cn("text-xs font-medium uppercase tracking-wider text-muted-foreground", classNames?.heading),
8335
- children: "Included in this bundle"
8340
+ className: cn("flex items-center justify-between py-3", classNames?.heading),
8341
+ children: /* @__PURE__ */ jsx("span", { className: "text-base font-bold", children: "Included in this bundle" })
8336
8342
  }
8337
8343
  ),
8338
- /* @__PURE__ */ jsx("div", { "data-cimplify-bundle-components": true, className: cn("space-y-3", classNames?.components), children: components.map((comp) => /* @__PURE__ */ jsx(
8344
+ /* @__PURE__ */ jsx("div", { "data-cimplify-bundle-components": true, className: cn("divide-y divide-border", classNames?.components), children: components.map((comp) => /* @__PURE__ */ jsx(
8339
8345
  BundleComponentCard,
8340
8346
  {
8341
8347
  component: comp,
@@ -8398,20 +8404,20 @@ function BundleComponentCard({
8398
8404
  "div",
8399
8405
  {
8400
8406
  "data-cimplify-bundle-component": true,
8401
- className: cn("border border-border p-4", classNames?.component),
8407
+ className: cn("py-4", classNames?.component),
8402
8408
  children: [
8403
8409
  /* @__PURE__ */ jsxs(
8404
8410
  "div",
8405
8411
  {
8406
8412
  "data-cimplify-bundle-component-header": true,
8407
- className: cn("flex items-start justify-between gap-3", classNames?.componentHeader),
8413
+ className: cn("flex items-center justify-between gap-3", classNames?.componentHeader),
8408
8414
  children: [
8409
- /* @__PURE__ */ jsxs("div", { children: [
8415
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
8410
8416
  component.quantity > 1 && /* @__PURE__ */ jsxs(
8411
8417
  "span",
8412
8418
  {
8413
8419
  "data-cimplify-bundle-component-qty": true,
8414
- className: cn("text-xs font-medium text-primary bg-primary/10 px-1.5 py-0.5", classNames?.componentQty),
8420
+ className: cn("text-xs font-medium text-primary bg-primary/10 px-1.5 py-0.5 rounded", classNames?.componentQty),
8415
8421
  children: [
8416
8422
  "\xD7",
8417
8423
  component.quantity
@@ -8423,12 +8429,12 @@ function BundleComponentCard({
8423
8429
  {
8424
8430
  id: labelId,
8425
8431
  "data-cimplify-bundle-component-name": true,
8426
- className: cn("font-medium text-sm", classNames?.componentName),
8432
+ className: cn("text-sm", classNames?.componentName),
8427
8433
  children: component.product_name
8428
8434
  }
8429
8435
  )
8430
8436
  ] }),
8431
- /* @__PURE__ */ jsx(Price, { amount: displayPrice })
8437
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: /* @__PURE__ */ jsx(Price, { amount: displayPrice }) })
8432
8438
  ]
8433
8439
  }
8434
8440
  ),
@@ -8441,7 +8447,7 @@ function BundleComponentCard({
8441
8447
  onVariantChange(value);
8442
8448
  },
8443
8449
  "data-cimplify-bundle-variant-picker": true,
8444
- className: cn("mt-3 flex flex-wrap gap-2", classNames?.variantPicker),
8450
+ className: cn("mt-3 divide-y divide-border", classNames?.variantPicker),
8445
8451
  children: component.available_variants.map((variant) => {
8446
8452
  const isSelected = selectedVariantId === variant.id;
8447
8453
  const adjustment = parsePrice(variant.price_adjustment);
@@ -8452,17 +8458,26 @@ function BundleComponentCard({
8452
8458
  "data-cimplify-bundle-variant-option": true,
8453
8459
  "data-selected": isSelected || void 0,
8454
8460
  className: cn(
8455
- "px-3 py-1.5 border text-xs font-medium transition-colors border-border hover:border-primary/50",
8456
- isSelected && "bg-primary text-primary-foreground border-primary",
8461
+ "w-full flex items-center gap-3 py-3 transition-colors cursor-pointer",
8457
8462
  isSelected ? classNames?.variantOptionSelected : classNames?.variantOption
8458
8463
  ),
8459
8464
  children: [
8460
- variant.display_name,
8465
+ /* @__PURE__ */ jsx(
8466
+ "span",
8467
+ {
8468
+ className: cn(
8469
+ "w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
8470
+ isSelected ? "border-primary" : "border-muted-foreground/30"
8471
+ ),
8472
+ children: isSelected && /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
8473
+ }
8474
+ ),
8475
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm", children: variant.display_name }),
8461
8476
  adjustment !== 0 && /* @__PURE__ */ jsxs(
8462
8477
  "span",
8463
8478
  {
8464
8479
  "data-cimplify-bundle-variant-adjustment": true,
8465
- className: cn("ml-1 opacity-70", classNames?.variantAdjustment),
8480
+ className: cn("text-sm text-muted-foreground", classNames?.variantAdjustment),
8466
8481
  children: [
8467
8482
  adjustment > 0 ? "+" : "",
8468
8483
  /* @__PURE__ */ jsx(Price, { amount: variant.price_adjustment })
@@ -8619,41 +8634,28 @@ function CompositeSelector({
8619
8634
  "div",
8620
8635
  {
8621
8636
  "data-cimplify-composite-group": true,
8622
- className: cn("border border-border p-5", classNames?.group),
8637
+ className: cn(classNames?.group),
8623
8638
  children: [
8624
8639
  /* @__PURE__ */ jsxs(
8625
8640
  "div",
8626
8641
  {
8627
8642
  "data-cimplify-composite-group-header": true,
8628
- className: cn("flex items-center justify-between mb-4", classNames?.groupHeader),
8643
+ className: cn("flex items-center justify-between py-3", classNames?.groupHeader),
8629
8644
  children: [
8630
8645
  /* @__PURE__ */ jsxs("div", { children: [
8631
- /* @__PURE__ */ jsxs(
8646
+ /* @__PURE__ */ jsx(
8632
8647
  "span",
8633
8648
  {
8634
8649
  "data-cimplify-composite-group-name": true,
8635
- className: cn("text-xs font-medium uppercase tracking-wider text-muted-foreground", classNames?.groupName),
8636
- children: [
8637
- group.name,
8638
- group.min_selections > 0 && /* @__PURE__ */ jsxs(
8639
- "span",
8640
- {
8641
- "data-cimplify-composite-required": true,
8642
- className: cn("text-destructive ml-1", classNames?.required),
8643
- children: [
8644
- " ",
8645
- "*"
8646
- ]
8647
- }
8648
- )
8649
- ]
8650
+ className: cn("text-base font-bold", classNames?.groupName),
8651
+ children: group.name
8650
8652
  }
8651
8653
  ),
8652
8654
  group.description && /* @__PURE__ */ jsx(
8653
8655
  "span",
8654
8656
  {
8655
8657
  "data-cimplify-composite-group-description": true,
8656
- className: cn("text-xs text-muted-foreground/70 mt-0.5", classNames?.groupDescription),
8658
+ className: cn("block text-xs text-muted-foreground mt-0.5", classNames?.groupDescription),
8657
8659
  children: group.description
8658
8660
  }
8659
8661
  ),
@@ -8661,16 +8663,20 @@ function CompositeSelector({
8661
8663
  "span",
8662
8664
  {
8663
8665
  "data-cimplify-composite-group-constraint": true,
8664
- className: cn("text-xs text-muted-foreground/70 mt-1", classNames?.groupConstraint),
8666
+ className: cn("block text-xs text-muted-foreground mt-0.5", classNames?.groupConstraint),
8665
8667
  children: group.min_selections > 0 && group.max_selections ? `Choose ${group.min_selections}\u2013${group.max_selections}` : group.min_selections > 0 ? `Choose at least ${group.min_selections}` : group.max_selections ? `Choose up to ${group.max_selections}` : "Choose as many as you like"
8666
8668
  }
8667
8669
  )
8668
8670
  ] }),
8669
- !minMet && /* @__PURE__ */ jsx(
8671
+ group.min_selections > 0 && /* @__PURE__ */ jsx(
8670
8672
  "span",
8671
8673
  {
8672
- "data-cimplify-composite-validation": true,
8673
- className: cn("text-xs text-destructive font-medium", classNames?.validation),
8674
+ "data-cimplify-composite-required": true,
8675
+ className: cn(
8676
+ "text-xs font-semibold px-2.5 py-1 rounded shrink-0",
8677
+ !minMet ? "text-destructive bg-destructive/10" : "text-destructive bg-destructive/10",
8678
+ classNames?.required
8679
+ ),
8674
8680
  children: "Required"
8675
8681
  }
8676
8682
  )
@@ -8683,7 +8689,7 @@ function CompositeSelector({
8683
8689
  "data-cimplify-composite-components": true,
8684
8690
  role: isSingleSelect ? "radiogroup" : "group",
8685
8691
  "aria-label": group.name,
8686
- className: cn("space-y-1", classNames?.components),
8692
+ className: cn("divide-y divide-border", classNames?.components),
8687
8693
  children: group.components.filter((c) => c.is_available && !c.is_archived).sort((a, b) => a.display_order - b.display_order).map((component) => {
8688
8694
  const qty = groupSels[component.id] || 0;
8689
8695
  const isSelected = qty > 0;
@@ -8697,8 +8703,7 @@ function CompositeSelector({
8697
8703
  "data-cimplify-composite-component": true,
8698
8704
  "data-selected": isSelected || void 0,
8699
8705
  className: cn(
8700
- "w-full flex items-center gap-3 px-4 py-3 border transition-colors text-left border-transparent hover:bg-muted/50",
8701
- isSelected && "bg-primary/5 border-primary",
8706
+ "w-full flex items-center gap-3 py-4 transition-colors text-left cursor-pointer",
8702
8707
  isSelected ? classNames?.componentSelected : classNames?.component
8703
8708
  ),
8704
8709
  children: [
@@ -8709,6 +8714,27 @@ function CompositeSelector({
8709
8714
  keepMounted: false
8710
8715
  }
8711
8716
  ),
8717
+ isSingleSelect ? /* @__PURE__ */ jsx(
8718
+ "span",
8719
+ {
8720
+ "data-cimplify-composite-radio": true,
8721
+ className: cn(
8722
+ "w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
8723
+ isSelected ? "border-primary" : "border-muted-foreground/30"
8724
+ ),
8725
+ children: isSelected && /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
8726
+ }
8727
+ ) : /* @__PURE__ */ jsx(
8728
+ "span",
8729
+ {
8730
+ "data-cimplify-composite-checkbox": true,
8731
+ className: cn(
8732
+ "w-5 h-5 rounded-sm border-2 flex items-center justify-center shrink-0 transition-colors",
8733
+ isSelected ? "border-primary bg-primary" : "border-muted-foreground/30"
8734
+ ),
8735
+ 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" }) })
8736
+ }
8737
+ ),
8712
8738
  /* @__PURE__ */ jsxs(
8713
8739
  "div",
8714
8740
  {
@@ -8719,7 +8745,7 @@ function CompositeSelector({
8719
8745
  "span",
8720
8746
  {
8721
8747
  "data-cimplify-composite-component-name": true,
8722
- className: cn("text-sm font-medium", isSelected && "text-primary", classNames?.componentName),
8748
+ className: cn("text-sm", classNames?.componentName),
8723
8749
  children: displayName
8724
8750
  }
8725
8751
  ),
@@ -8743,7 +8769,7 @@ function CompositeSelector({
8743
8769
  "span",
8744
8770
  {
8745
8771
  "data-cimplify-composite-component-description": true,
8746
- className: cn("text-xs text-muted-foreground truncate", classNames?.componentDescription),
8772
+ className: cn("block text-xs text-muted-foreground truncate", classNames?.componentDescription),
8747
8773
  children: component.display_description
8748
8774
  }
8749
8775
  ),
@@ -8751,7 +8777,7 @@ function CompositeSelector({
8751
8777
  "span",
8752
8778
  {
8753
8779
  "data-cimplify-composite-component-calories": true,
8754
- className: cn("text-xs text-muted-foreground/60", classNames?.componentCalories),
8780
+ className: cn("block text-xs text-muted-foreground/60", classNames?.componentCalories),
8755
8781
  children: [
8756
8782
  component.calories,
8757
8783
  " cal"
@@ -8808,7 +8834,10 @@ function CompositeSelector({
8808
8834
  )
8809
8835
  }
8810
8836
  ),
8811
- component.price != null && parsePrice(component.price) !== 0 && /* @__PURE__ */ jsx(Price, { amount: component.price, prefix: "+" })
8837
+ component.price != null && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground shrink-0", children: [
8838
+ "+",
8839
+ /* @__PURE__ */ jsx(Price, { amount: component.price })
8840
+ ] })
8812
8841
  ]
8813
8842
  },
8814
8843
  component.id
@@ -9566,6 +9595,14 @@ function ProductCard({
9566
9595
  const [isOpen, setIsOpen] = useState(false);
9567
9596
  const [shouldFetch, setShouldFetch] = useState(false);
9568
9597
  const dialogRef = useRef(null);
9598
+ useEffect(() => {
9599
+ if (!isOpen) return;
9600
+ const original = document.body.style.overflow;
9601
+ document.body.style.overflow = "hidden";
9602
+ return () => {
9603
+ document.body.style.overflow = original;
9604
+ };
9605
+ }, [isOpen]);
9569
9606
  const { product: productDetails } = useProduct(
9570
9607
  product.slug ?? product.id,
9571
9608
  { enabled: shouldFetch || isOpen }
@@ -9705,7 +9742,7 @@ function ProductCard({
9705
9742
  "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
9743
  classNames?.modal
9707
9744
  ),
9708
- children: isOpen && (productDetails ? renderModal ? renderModal(productDetails) : /* @__PURE__ */ jsx(
9745
+ children: isOpen && (productDetails ? renderModal ? renderModal(productDetails, handleClose) : /* @__PURE__ */ jsx(
9709
9746
  ProductSheet,
9710
9747
  {
9711
9748
  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}.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))}.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\/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-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}.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-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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",
@@ -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(\"border border-border p-5\", classNames?.group)}\n >\n <div\n data-cimplify-addon-header\n className={cn(\"flex items-center justify-between mb-4\", classNames?.header)}\n >\n <div>\n <span\n data-cimplify-addon-name\n className={cn(\"text-xs font-medium uppercase tracking-wider text-muted-foreground\", classNames?.name)}\n >\n {addOn.name}\n {addOn.is_required && (\n <span\n data-cimplify-addon-required\n className={cn(\"text-destructive ml-1\", classNames?.required)}\n >\n {\" \"}*\n </span>\n )}\n </span>\n {(addOn.min_selections || addOn.max_selections) && (\n <span\n data-cimplify-addon-constraint\n className={cn(\"text-xs text-muted-foreground/70 mt-1\", 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 {!minMet && (\n <span\n data-cimplify-addon-validation\n className={cn(\"text-xs text-destructive font-medium\", classNames?.validation)}\n >\n Required\n </span>\n )}\n </div>\n\n <div\n data-cimplify-addon-options\n className={cn(\"space-y-1\", 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 px-4 py-3 border transition-colors text-left border-transparent hover:bg-muted/50\",\n isSelected && \"bg-primary/5 border-primary\",\n isSelected ? classNames?.optionSelected : classNames?.option,\n )}\n >\n <Checkbox.Indicator\n className=\"hidden\"\n keepMounted={false}\n />\n\n <span\n data-cimplify-addon-option-name\n className={cn(\n \"flex-1 min-w-0 text-sm font-medium\",\n isSelected && \"text-primary\",\n classNames?.optionName,\n )}\n >\n {option.name}\n </span>\n\n {option.default_price != null && parsePrice(option.default_price) !== 0 && (\n <Price\n amount={option.default_price}\n prefix=\"+\"\n />\n )}\n </Checkbox.Root>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n"
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
  }