@cimplify/sdk 0.47.0 → 0.48.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
@@ -14,6 +14,9 @@ import { RadioGroup } from '@base-ui/react/radio-group';
14
14
  import { Radio } from '@base-ui/react/radio';
15
15
  import { Checkbox } from '@base-ui/react/checkbox';
16
16
  import { Button } from '@base-ui/react/button';
17
+ import { Popover } from '@base-ui/react/popover';
18
+ import { DayPicker } from 'react-day-picker';
19
+ import { format, parseISO, isValid } from 'date-fns';
17
20
  import { Tabs } from '@base-ui/react/tabs';
18
21
  import { Dialog } from '@base-ui/react/dialog';
19
22
  import { Field } from '@base-ui/react/field';
@@ -1333,16 +1336,10 @@ function useCimplifyClient() {
1333
1336
  function useOptionalCimplifyClient() {
1334
1337
  return useContext(CimplifyClientContext);
1335
1338
  }
1336
- var SPACE = { sm: 8};
1337
- function shellColors(isDark, primaryColor) {
1339
+ function shellColors(isDark) {
1338
1340
  return {
1339
- text: isDark ? "#f4f4f5" : "#1a1a1a",
1340
1341
  textSecondary: isDark ? "#a1a1aa" : "#52525b",
1341
- textMuted: isDark ? "#71717a" : "#a1a1aa",
1342
- border: isDark ? "#27272a" : "#e4e4e7",
1343
- surface: isDark ? "#18181b" : "#fafafa",
1344
- error: "#dc2626",
1345
- primary: primaryColor
1342
+ error: "#dc2626"
1346
1343
  };
1347
1344
  }
1348
1345
  function statusToLabel(status) {
@@ -1427,7 +1424,6 @@ function CimplifyCheckout({
1427
1424
  fxOptionsRef.current = fxOptions;
1428
1425
  const resolvedCartRef = useRef(resolvedCart);
1429
1426
  resolvedCartRef.current = resolvedCart;
1430
- const primaryColor = appearance?.variables?.primaryColor || "#0a2540";
1431
1427
  const isDark = appearance?.theme === "dark";
1432
1428
  const emitStatus = React10.useEffectEvent(
1433
1429
  (nextStatus, context = {}) => {
@@ -1630,7 +1626,7 @@ function CimplifyCheckout({
1630
1626
  checkoutElement.setCart(transformToCheckoutCart(resolvedCart, fxOptions));
1631
1627
  }
1632
1628
  }, [resolvedCart, fxOptions]);
1633
- const colors = shellColors(isDark ?? false, primaryColor);
1629
+ const colors = shellColors(isDark ?? false);
1634
1630
  if (isInitializing) {
1635
1631
  return /* @__PURE__ */ jsx("div", { className, "data-cimplify-checkout": "", children: /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { fontSize: 13, color: colors.textSecondary }, children: "Preparing checkout..." }) });
1636
1632
  }
@@ -1652,8 +1648,8 @@ function CimplifyCheckout({
1652
1648
  }
1653
1649
  ),
1654
1650
  /* @__PURE__ */ jsx("div", { "data-cimplify-section": "checkout", children: /* @__PURE__ */ jsx("div", { ref: checkoutMountRef }) }),
1655
- status && /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.textSecondary }, children: statusText || statusToLabel(status) }),
1656
- errorMessage && /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.error }, children: errorMessage })
1651
+ status && /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { marginTop: 8, fontSize: 13, color: colors.textSecondary }, children: statusText || statusToLabel(status) }),
1652
+ errorMessage && /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { marginTop: 8, fontSize: 13, color: colors.error }, children: errorMessage })
1657
1653
  ] });
1658
1654
  }
1659
1655
  function cn(...inputs) {
@@ -5001,6 +4997,262 @@ function VolumePricing({
5001
4997
  }
5002
4998
  );
5003
4999
  }
5000
+ function parseValue(value) {
5001
+ if (!value) return void 0;
5002
+ const date = parseISO(value);
5003
+ return isValid(date) ? date : void 0;
5004
+ }
5005
+ function formatValue(date) {
5006
+ return format(date, "yyyy-MM-dd");
5007
+ }
5008
+ function DatePicker({
5009
+ value,
5010
+ onChange,
5011
+ placeholder = "Select a date",
5012
+ minDate,
5013
+ maxDate,
5014
+ disabled,
5015
+ triggerDisabled,
5016
+ name,
5017
+ required,
5018
+ className,
5019
+ classNames,
5020
+ "aria-label": ariaLabel
5021
+ }) {
5022
+ const [open, setOpen] = useState(false);
5023
+ const selected = parseValue(value);
5024
+ const disabledMatchers = [];
5025
+ if (minDate) disabledMatchers.push({ before: minDate });
5026
+ if (maxDate) disabledMatchers.push({ after: maxDate });
5027
+ if (Array.isArray(disabled)) {
5028
+ disabledMatchers.push(...disabled);
5029
+ } else if (disabled) {
5030
+ disabledMatchers.push(disabled);
5031
+ }
5032
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-date-picker": true, className: cn(className, classNames?.root), children: [
5033
+ /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
5034
+ /* @__PURE__ */ jsxs(
5035
+ Popover.Trigger,
5036
+ {
5037
+ type: "button",
5038
+ "aria-label": ariaLabel ?? placeholder,
5039
+ disabled: triggerDisabled,
5040
+ className: cn(
5041
+ "w-full inline-flex items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-left text-sm transition-colors hover:bg-muted/40 focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
5042
+ classNames?.trigger
5043
+ ),
5044
+ children: [
5045
+ /* @__PURE__ */ jsx("span", { className: cn(!selected && "text-muted-foreground"), children: selected ? format(selected, "EEE, MMM d, yyyy") : placeholder }),
5046
+ /* @__PURE__ */ jsxs(
5047
+ "svg",
5048
+ {
5049
+ width: "16",
5050
+ height: "16",
5051
+ viewBox: "0 0 24 24",
5052
+ fill: "none",
5053
+ stroke: "currentColor",
5054
+ strokeWidth: "2",
5055
+ strokeLinecap: "round",
5056
+ strokeLinejoin: "round",
5057
+ "aria-hidden": "true",
5058
+ className: "shrink-0 text-muted-foreground",
5059
+ children: [
5060
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
5061
+ /* @__PURE__ */ jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
5062
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
5063
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" })
5064
+ ]
5065
+ }
5066
+ )
5067
+ ]
5068
+ }
5069
+ ),
5070
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { sideOffset: 6, align: "start", children: /* @__PURE__ */ jsx(
5071
+ Popover.Popup,
5072
+ {
5073
+ className: cn(
5074
+ "z-50 rounded-lg border border-border bg-background p-3 shadow-lg outline-none",
5075
+ classNames?.popup
5076
+ ),
5077
+ children: /* @__PURE__ */ jsx(
5078
+ DayPicker,
5079
+ {
5080
+ mode: "single",
5081
+ selected,
5082
+ onSelect: (date) => {
5083
+ onChange?.(date ? formatValue(date) : "");
5084
+ if (date) setOpen(false);
5085
+ },
5086
+ disabled: disabledMatchers.length > 0 ? disabledMatchers : void 0,
5087
+ showOutsideDays: true,
5088
+ weekStartsOn: 0,
5089
+ className: cn("p-0", classNames?.calendar),
5090
+ classNames: {
5091
+ months: "flex flex-col gap-3",
5092
+ month: "flex flex-col gap-3",
5093
+ caption: "flex items-center justify-between px-1",
5094
+ caption_label: "text-sm font-semibold",
5095
+ nav: "flex items-center gap-1",
5096
+ nav_button: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted transition-colors",
5097
+ table: "w-full border-collapse",
5098
+ head_row: "flex",
5099
+ head_cell: "w-9 text-center text-[10px] font-medium uppercase tracking-wider text-muted-foreground",
5100
+ row: "flex w-full mt-1",
5101
+ cell: "w-9 h-9 text-center text-sm p-0 relative",
5102
+ day: cn(
5103
+ "inline-flex h-9 w-9 items-center justify-center rounded-md text-sm font-normal text-foreground hover:bg-muted transition-colors",
5104
+ "aria-selected:bg-foreground aria-selected:text-background aria-selected:hover:bg-foreground/90"
5105
+ ),
5106
+ day_selected: cn(
5107
+ "bg-foreground text-background hover:bg-foreground/90 focus:bg-foreground",
5108
+ classNames?.daySelected
5109
+ ),
5110
+ day_today: cn(
5111
+ "ring-1 ring-inset ring-primary/40",
5112
+ classNames?.dayToday
5113
+ ),
5114
+ day_outside: "text-muted-foreground/50",
5115
+ day_disabled: cn(
5116
+ "text-muted-foreground/40 cursor-not-allowed line-through",
5117
+ classNames?.dayDisabled
5118
+ )
5119
+ }
5120
+ }
5121
+ )
5122
+ }
5123
+ ) }) })
5124
+ ] }),
5125
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: value ?? "", required }) : null
5126
+ ] });
5127
+ }
5128
+ function parseHHmm(value) {
5129
+ if (!value) return null;
5130
+ const match = /^(\d{1,2}):(\d{2})$/.exec(value);
5131
+ if (!match) return null;
5132
+ const hour = Number.parseInt(match[1] ?? "", 10);
5133
+ const minute = Number.parseInt(match[2] ?? "", 10);
5134
+ if (Number.isNaN(hour) || Number.isNaN(minute)) return null;
5135
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
5136
+ return { hour, minute };
5137
+ }
5138
+ function formatHHmm(hour, minute) {
5139
+ return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
5140
+ }
5141
+ function formatDisplay(hour, minute, displayFormat) {
5142
+ if (displayFormat === "24h") {
5143
+ return formatHHmm(hour, minute);
5144
+ }
5145
+ const period = hour >= 12 ? "PM" : "AM";
5146
+ const displayHour = hour % 12 || 12;
5147
+ return `${displayHour}:${String(minute).padStart(2, "0")} ${period}`;
5148
+ }
5149
+ function buildOptions(minTime, maxTime, stepMinutes) {
5150
+ const min = parseHHmm(minTime) ?? { hour: 0, minute: 0 };
5151
+ const max = parseHHmm(maxTime) ?? { hour: 23, minute: 59 };
5152
+ const minTotal = min.hour * 60 + min.minute;
5153
+ const maxTotal = max.hour * 60 + max.minute;
5154
+ const step = Math.max(1, stepMinutes);
5155
+ const out = [];
5156
+ for (let total = minTotal; total <= maxTotal; total += step) {
5157
+ out.push(formatHHmm(Math.floor(total / 60), total % 60));
5158
+ }
5159
+ return out;
5160
+ }
5161
+ function TimePicker({
5162
+ value,
5163
+ onChange,
5164
+ placeholder = "Select a time",
5165
+ minTime = "00:00",
5166
+ maxTime = "23:30",
5167
+ stepMinutes = 30,
5168
+ displayFormat = "12h",
5169
+ triggerDisabled,
5170
+ name,
5171
+ required,
5172
+ className,
5173
+ classNames,
5174
+ "aria-label": ariaLabel
5175
+ }) {
5176
+ const [open, setOpen] = useState(false);
5177
+ const parsed = parseHHmm(value);
5178
+ const options = useMemo(
5179
+ () => buildOptions(minTime, maxTime, stepMinutes),
5180
+ [minTime, maxTime, stepMinutes]
5181
+ );
5182
+ const triggerLabel = parsed ? formatDisplay(parsed.hour, parsed.minute, displayFormat) : placeholder;
5183
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-time-picker": true, className: cn(className, classNames?.root), children: [
5184
+ /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
5185
+ /* @__PURE__ */ jsxs(
5186
+ Popover.Trigger,
5187
+ {
5188
+ type: "button",
5189
+ "aria-label": ariaLabel ?? placeholder,
5190
+ disabled: triggerDisabled,
5191
+ className: cn(
5192
+ "w-full inline-flex items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-left text-sm transition-colors hover:bg-muted/40 focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
5193
+ classNames?.trigger
5194
+ ),
5195
+ children: [
5196
+ /* @__PURE__ */ jsx("span", { className: cn(!parsed && "text-muted-foreground"), children: triggerLabel }),
5197
+ /* @__PURE__ */ jsxs(
5198
+ "svg",
5199
+ {
5200
+ width: "16",
5201
+ height: "16",
5202
+ viewBox: "0 0 24 24",
5203
+ fill: "none",
5204
+ stroke: "currentColor",
5205
+ strokeWidth: "2",
5206
+ strokeLinecap: "round",
5207
+ strokeLinejoin: "round",
5208
+ "aria-hidden": "true",
5209
+ className: "shrink-0 text-muted-foreground",
5210
+ children: [
5211
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
5212
+ /* @__PURE__ */ jsx("polyline", { points: "12 7 12 12 15 14" })
5213
+ ]
5214
+ }
5215
+ )
5216
+ ]
5217
+ }
5218
+ ),
5219
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { sideOffset: 6, align: "start", children: /* @__PURE__ */ jsx(
5220
+ Popover.Popup,
5221
+ {
5222
+ className: cn(
5223
+ "z-50 max-h-64 overflow-y-auto rounded-lg border border-border bg-background p-1 shadow-lg outline-none",
5224
+ classNames?.popup
5225
+ ),
5226
+ children: /* @__PURE__ */ jsx("ul", { role: "listbox", "aria-label": ariaLabel ?? placeholder, className: "flex flex-col", children: options.map((option) => {
5227
+ const isSelected = option === value;
5228
+ const opt = parseHHmm(option);
5229
+ if (!opt) return null;
5230
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
5231
+ "button",
5232
+ {
5233
+ type: "button",
5234
+ role: "option",
5235
+ "aria-selected": isSelected,
5236
+ onClick: () => {
5237
+ onChange?.(option);
5238
+ setOpen(false);
5239
+ },
5240
+ className: cn(
5241
+ "w-full rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-muted",
5242
+ isSelected && "bg-foreground text-background hover:bg-foreground/90",
5243
+ classNames?.option,
5244
+ isSelected && classNames?.optionSelected
5245
+ ),
5246
+ children: formatDisplay(opt.hour, opt.minute, displayFormat)
5247
+ }
5248
+ ) }, option);
5249
+ }) })
5250
+ }
5251
+ ) }) })
5252
+ ] }),
5253
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: value ?? "", required }) : null
5254
+ ] });
5255
+ }
5004
5256
  function CustomerInputFields({
5005
5257
  fields,
5006
5258
  values,
@@ -5187,13 +5439,13 @@ function FieldInput({
5187
5439
  );
5188
5440
  case INPUT_FIELD_TYPE.Date:
5189
5441
  return /* @__PURE__ */ jsx(
5190
- "input",
5442
+ DatePicker,
5191
5443
  {
5192
- type: "date",
5193
5444
  value: typeof value === "string" ? value : "",
5194
- onChange: (e) => onValueChange(e.target.value || void 0),
5445
+ onChange: (next) => onValueChange(next || void 0),
5195
5446
  required: field.is_required,
5196
- className: inputClass
5447
+ placeholder: field.placeholder ?? "Select a date",
5448
+ "aria-label": field.name
5197
5449
  }
5198
5450
  );
5199
5451
  case INPUT_FIELD_TYPE.File:
@@ -5221,34 +5473,53 @@ function FieldInput({
5221
5473
  }
5222
5474
  );
5223
5475
  case INPUT_FIELD_TYPE.DateTime: {
5224
- const dtLocalValue = typeof value === "string" && value.includes("T") ? value.slice(0, 16) : typeof value === "string" ? value : "";
5225
- return /* @__PURE__ */ jsx(
5226
- "input",
5227
- {
5228
- type: "datetime-local",
5229
- value: dtLocalValue,
5230
- onChange: (e) => {
5231
- if (!e.target.value) {
5232
- onValueChange(void 0);
5233
- return;
5234
- }
5235
- const date = new Date(e.target.value);
5236
- onValueChange(Number.isNaN(date.getTime()) ? e.target.value : date.toISOString());
5237
- },
5238
- required: field.is_required,
5239
- className: inputClass
5476
+ const stringValue = typeof value === "string" ? value : "";
5477
+ const [datePart, timePartRaw] = stringValue.includes("T") ? stringValue.split("T", 2) : [stringValue, ""];
5478
+ const timePart = (timePartRaw ?? "").slice(0, 5);
5479
+ const commit = (nextDate, nextTime) => {
5480
+ if (!nextDate && !nextTime) {
5481
+ onValueChange(void 0);
5482
+ return;
5240
5483
  }
5241
- );
5484
+ if (!nextDate) {
5485
+ onValueChange(`${nextTime}`);
5486
+ return;
5487
+ }
5488
+ const combined = `${nextDate}T${nextTime || "00:00"}`;
5489
+ const parsed = new Date(combined);
5490
+ onValueChange(Number.isNaN(parsed.getTime()) ? combined : parsed.toISOString());
5491
+ };
5492
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-[1fr_auto] gap-2", children: [
5493
+ /* @__PURE__ */ jsx(
5494
+ DatePicker,
5495
+ {
5496
+ value: datePart ?? "",
5497
+ onChange: (next) => commit(next, timePart),
5498
+ placeholder: field.placeholder ?? "Select a date",
5499
+ "aria-label": `${field.name} date`,
5500
+ required: field.is_required
5501
+ }
5502
+ ),
5503
+ /* @__PURE__ */ jsx(
5504
+ TimePicker,
5505
+ {
5506
+ value: timePart,
5507
+ onChange: (next) => commit(datePart ?? "", next),
5508
+ placeholder: "Time",
5509
+ "aria-label": `${field.name} time`
5510
+ }
5511
+ )
5512
+ ] });
5242
5513
  }
5243
5514
  case INPUT_FIELD_TYPE.Time:
5244
5515
  return /* @__PURE__ */ jsx(
5245
- "input",
5516
+ TimePicker,
5246
5517
  {
5247
- type: "time",
5248
5518
  value: typeof value === "string" ? value : "",
5249
- onChange: (e) => onValueChange(e.target.value || void 0),
5519
+ onChange: (next) => onValueChange(next || void 0),
5250
5520
  required: field.is_required,
5251
- className: inputClass
5521
+ placeholder: field.placeholder ?? "Select a time",
5522
+ "aria-label": field.name
5252
5523
  }
5253
5524
  );
5254
5525
  case INPUT_FIELD_TYPE.MultiSelect:
@@ -5268,8 +5539,7 @@ function FieldInput({
5268
5539
  field,
5269
5540
  value,
5270
5541
  onValueChange,
5271
- classNames,
5272
- inputClass
5542
+ classNames
5273
5543
  }
5274
5544
  );
5275
5545
  case INPUT_FIELD_TYPE.Address:
@@ -5438,38 +5708,39 @@ function DateRangeInput({
5438
5708
  field,
5439
5709
  value,
5440
5710
  onValueChange,
5441
- classNames,
5442
- inputClass
5711
+ classNames
5443
5712
  }) {
5444
5713
  const range = value && typeof value === "object" ? value : {};
5445
5714
  const update = (key, v) => {
5446
5715
  const next = { ...range, [key]: v };
5447
5716
  onValueChange(next.start || next.end ? next : void 0);
5448
5717
  };
5449
- return /* @__PURE__ */ jsxs("div", { className: cn("grid grid-cols-2 gap-3", classNames?.dateRangeInput), children: [
5718
+ return /* @__PURE__ */ jsxs("div", { className: cn("grid grid-cols-1 sm:grid-cols-2 gap-3", classNames?.dateRangeInput), children: [
5450
5719
  /* @__PURE__ */ jsxs("div", { children: [
5451
5720
  /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground mb-1 block", children: "Start" }),
5452
5721
  /* @__PURE__ */ jsx(
5453
- "input",
5722
+ DatePicker,
5454
5723
  {
5455
- type: "date",
5456
5724
  value: range.start || "",
5457
- onChange: (e) => update("start", e.target.value),
5725
+ onChange: (next) => update("start", next),
5458
5726
  required: field.is_required,
5459
- className: inputClass
5727
+ placeholder: "Start date",
5728
+ "aria-label": `${field.name} start date`,
5729
+ maxDate: range.end ? /* @__PURE__ */ new Date(`${range.end}T00:00`) : void 0
5460
5730
  }
5461
5731
  )
5462
5732
  ] }),
5463
5733
  /* @__PURE__ */ jsxs("div", { children: [
5464
5734
  /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground mb-1 block", children: "End" }),
5465
5735
  /* @__PURE__ */ jsx(
5466
- "input",
5736
+ DatePicker,
5467
5737
  {
5468
- type: "date",
5469
5738
  value: range.end || "",
5470
- onChange: (e) => update("end", e.target.value),
5739
+ onChange: (next) => update("end", next),
5471
5740
  required: field.is_required,
5472
- className: inputClass
5741
+ placeholder: "End date",
5742
+ "aria-label": `${field.name} end date`,
5743
+ minDate: range.start ? /* @__PURE__ */ new Date(`${range.start}T00:00`) : void 0
5473
5744
  }
5474
5745
  )
5475
5746
  ] })
@@ -5913,6 +6184,39 @@ function formatTime(timeStr) {
5913
6184
  }
5914
6185
  return timeStr;
5915
6186
  }
6187
+ function pluralizeUnit(unit, value) {
6188
+ if (!unit) return value === 1 ? "day" : "days";
6189
+ const v = value ?? 1;
6190
+ if (unit === "minutes") return v === 1 ? "minute" : "minutes";
6191
+ if (unit === "hours") return v === 1 ? "hour" : "hours";
6192
+ if (unit === "days") return v === 1 ? "day" : "days";
6193
+ if (unit === "weeks") return v === 1 ? "week" : "weeks";
6194
+ if (unit === "months") return v === 1 ? "month" : "months";
6195
+ return unit;
6196
+ }
6197
+ function formatStaySummary(slot, durationUnit, durationValue) {
6198
+ const start = new Date(slot.start_time);
6199
+ const end = new Date(slot.end_time);
6200
+ const startLabel = start.toLocaleString(void 0, {
6201
+ weekday: "short",
6202
+ month: "short",
6203
+ day: "numeric",
6204
+ hour: "numeric",
6205
+ minute: "2-digit"
6206
+ });
6207
+ const endLabel = end.toLocaleString(void 0, {
6208
+ weekday: "short",
6209
+ month: "short",
6210
+ day: "numeric",
6211
+ hour: "numeric",
6212
+ minute: "2-digit"
6213
+ });
6214
+ const unitLabel = pluralizeUnit(durationUnit, durationValue);
6215
+ if (durationValue !== void 0) {
6216
+ return `${durationValue} ${unitLabel}: ${startLabel} \u2192 ${endLabel}`;
6217
+ }
6218
+ return `${startLabel} \u2192 ${endLabel}`;
6219
+ }
5916
6220
  function slotToValue(slot) {
5917
6221
  return `${slot.start_time}|${slot.end_time}`;
5918
6222
  }
@@ -5925,10 +6229,14 @@ function SlotPicker({
5925
6229
  onSlotSelect,
5926
6230
  groupByTimeOfDay = true,
5927
6231
  showPrice = true,
6232
+ schedulingMode = "intraday",
6233
+ durationUnit,
6234
+ durationValue,
5928
6235
  emptyMessage = "No available slots",
5929
6236
  className,
5930
6237
  classNames
5931
6238
  }) {
6239
+ const isMultiDay = schedulingMode === "multi_day";
5932
6240
  const { slots: fetched, isLoading } = useAvailableSlots(
5933
6241
  serviceId ?? null,
5934
6242
  date ?? null,
@@ -5959,7 +6267,7 @@ function SlotPicker({
5959
6267
  }
5960
6268
  );
5961
6269
  }
5962
- const groups = groupByTimeOfDay ? groupSlots(slots) : [{ label: "", slots }];
6270
+ const groups = groupByTimeOfDay && !isMultiDay ? groupSlots(slots) : [{ label: "", slots }];
5963
6271
  const slotsByValue = /* @__PURE__ */ new Map();
5964
6272
  for (const slot of slots) {
5965
6273
  slotsByValue.set(slotToValue(slot), slot);
@@ -5969,37 +6277,74 @@ function SlotPicker({
5969
6277
  RadioGroup,
5970
6278
  {
5971
6279
  "data-cimplify-slot-picker": true,
5972
- className: cn(className, classNames?.root),
6280
+ className: cn("flex flex-col gap-4", className, classNames?.root),
5973
6281
  value: selectedValue,
5974
6282
  onValueChange: (value) => {
5975
6283
  const slot = slotsByValue.get(value);
5976
- if (slot?.is_available) {
6284
+ if (slot && slot.is_available !== false) {
5977
6285
  onSlotSelect?.(slot);
5978
6286
  }
5979
6287
  },
5980
- children: groups.map((group) => /* @__PURE__ */ jsxs("div", { "data-cimplify-slot-group": true, className: classNames?.group, children: [
5981
- group.label && /* @__PURE__ */ jsx("div", { "data-cimplify-slot-group-label": true, className: classNames?.groupLabel, children: group.label }),
5982
- group.slots.map((slot) => {
5983
- const value = slotToValue(slot);
5984
- const isSelected = selectedSlot?.start_time === slot.start_time && selectedSlot?.end_time === slot.end_time;
5985
- return /* @__PURE__ */ jsxs(
5986
- Radio.Root,
5987
- {
5988
- value,
5989
- disabled: !slot.is_available,
5990
- "data-cimplify-slot": true,
5991
- "data-selected": isSelected || void 0,
5992
- "data-unavailable": !slot.is_available || void 0,
5993
- className: classNames?.slot,
5994
- children: [
5995
- /* @__PURE__ */ jsx("span", { "data-cimplify-slot-time": true, className: classNames?.slotTime, children: formatTime(slot.start_time) }),
5996
- showPrice && slot.price && /* @__PURE__ */ jsx("span", { "data-cimplify-slot-price": true, className: classNames?.slotPrice, children: /* @__PURE__ */ jsx(Price, { amount: slot.price }) })
5997
- ]
5998
- },
5999
- value
6000
- );
6001
- })
6002
- ] }, group.label || "all"))
6288
+ children: groups.map((group) => /* @__PURE__ */ jsxs(
6289
+ "div",
6290
+ {
6291
+ "data-cimplify-slot-group": true,
6292
+ className: cn("flex flex-col gap-2", classNames?.group),
6293
+ children: [
6294
+ group.label && /* @__PURE__ */ jsx(
6295
+ "div",
6296
+ {
6297
+ "data-cimplify-slot-group-label": true,
6298
+ className: cn(
6299
+ "text-xs font-medium uppercase tracking-[0.12em] text-muted-foreground",
6300
+ classNames?.groupLabel
6301
+ ),
6302
+ children: group.label
6303
+ }
6304
+ ),
6305
+ /* @__PURE__ */ jsx(
6306
+ "div",
6307
+ {
6308
+ className: cn(
6309
+ isMultiDay ? "flex flex-col gap-2" : "grid grid-cols-3 sm:grid-cols-4 gap-2"
6310
+ ),
6311
+ children: group.slots.map((slot) => {
6312
+ const value = slotToValue(slot);
6313
+ const isSelected = selectedSlot?.start_time === slot.start_time && selectedSlot?.end_time === slot.end_time;
6314
+ return /* @__PURE__ */ jsxs(
6315
+ Radio.Root,
6316
+ {
6317
+ value,
6318
+ disabled: slot.is_available === false,
6319
+ "data-cimplify-slot": true,
6320
+ "data-selected": isSelected || void 0,
6321
+ "data-unavailable": slot.is_available === false || void 0,
6322
+ className: cn(
6323
+ "inline-flex items-center justify-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:border-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[selected]:border-foreground data-[selected]:bg-foreground data-[selected]:text-background data-[unavailable]:cursor-not-allowed data-[unavailable]:opacity-40 data-[unavailable]:line-through",
6324
+ isMultiDay && "justify-between text-left",
6325
+ classNames?.slot
6326
+ ),
6327
+ children: [
6328
+ /* @__PURE__ */ jsx("span", { "data-cimplify-slot-time": true, className: classNames?.slotTime, children: isMultiDay ? formatStaySummary(slot, durationUnit, durationValue) : formatTime(slot.start_time) }),
6329
+ showPrice && slot.price && /* @__PURE__ */ jsx(
6330
+ "span",
6331
+ {
6332
+ "data-cimplify-slot-price": true,
6333
+ className: cn("text-xs opacity-70", classNames?.slotPrice),
6334
+ children: /* @__PURE__ */ jsx(Price, { amount: slot.price })
6335
+ }
6336
+ )
6337
+ ]
6338
+ },
6339
+ value
6340
+ );
6341
+ })
6342
+ }
6343
+ )
6344
+ ]
6345
+ },
6346
+ group.label || "all"
6347
+ ))
6003
6348
  }
6004
6349
  );
6005
6350
  }
@@ -6023,6 +6368,9 @@ function DateSlotPicker({
6023
6368
  onSlotSelect,
6024
6369
  availability: availabilityProp,
6025
6370
  showPrice = true,
6371
+ schedulingMode,
6372
+ durationUnit,
6373
+ durationValue,
6026
6374
  className,
6027
6375
  classNames
6028
6376
  }) {
@@ -6081,55 +6429,80 @@ function DateSlotPicker({
6081
6429
  value: selectedDate,
6082
6430
  onValueChange: handleDateChange,
6083
6431
  "data-cimplify-date-slot-picker": true,
6084
- className: cn(className, classNames?.root),
6432
+ className: cn("flex flex-col gap-4", className, classNames?.root),
6085
6433
  children: [
6086
- /* @__PURE__ */ jsxs("div", { "data-cimplify-date-nav": true, className: classNames?.nav, children: [
6087
- /* @__PURE__ */ jsx(
6088
- "button",
6089
- {
6090
- type: "button",
6091
- onClick: handlePrev,
6092
- disabled: offset === 0,
6093
- "data-cimplify-date-nav-prev": true,
6094
- className: classNames?.navButton,
6095
- children: "\u2190"
6096
- }
6097
- ),
6098
- /* @__PURE__ */ jsx(
6099
- "button",
6100
- {
6101
- type: "button",
6102
- onClick: handleNext,
6103
- "data-cimplify-date-nav-next": true,
6104
- className: classNames?.navButton,
6105
- children: "\u2192"
6106
- }
6107
- )
6108
- ] }),
6109
- /* @__PURE__ */ jsx(Tabs.List, { "data-cimplify-date-strip": true, className: classNames?.dateStrip, children: dateRange.dates.map((date) => {
6110
- const dayInfo = availabilityMap.get(date);
6111
- const hasAvailability = dayInfo?.has_availability !== false;
6112
- const isSelected = selectedDate === date;
6113
- return /* @__PURE__ */ jsx(
6114
- Tabs.Tab,
6115
- {
6116
- value: date,
6117
- "data-cimplify-date-button": true,
6118
- "data-selected": isSelected || void 0,
6119
- "data-available": hasAvailability || void 0,
6120
- "data-fully-booked": !hasAvailability || void 0,
6121
- className: classNames?.dateButton,
6122
- children: formatDate(date)
6123
- },
6124
- date
6125
- );
6126
- }) }),
6434
+ /* @__PURE__ */ jsxs(
6435
+ "div",
6436
+ {
6437
+ "data-cimplify-date-nav": true,
6438
+ className: cn("flex items-center justify-end gap-2", classNames?.nav),
6439
+ children: [
6440
+ /* @__PURE__ */ jsx(
6441
+ "button",
6442
+ {
6443
+ type: "button",
6444
+ onClick: handlePrev,
6445
+ disabled: offset === 0,
6446
+ "aria-label": "Previous dates",
6447
+ "data-cimplify-date-nav-prev": true,
6448
+ className: cn(
6449
+ "grid place-items-center w-8 h-8 rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40",
6450
+ classNames?.navButton
6451
+ ),
6452
+ children: "\u2190"
6453
+ }
6454
+ ),
6455
+ /* @__PURE__ */ jsx(
6456
+ "button",
6457
+ {
6458
+ type: "button",
6459
+ onClick: handleNext,
6460
+ "aria-label": "Next dates",
6461
+ "data-cimplify-date-nav-next": true,
6462
+ className: cn(
6463
+ "grid place-items-center w-8 h-8 rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
6464
+ classNames?.navButton
6465
+ ),
6466
+ children: "\u2192"
6467
+ }
6468
+ )
6469
+ ]
6470
+ }
6471
+ ),
6472
+ /* @__PURE__ */ jsx(
6473
+ Tabs.List,
6474
+ {
6475
+ "data-cimplify-date-strip": true,
6476
+ className: cn("grid grid-cols-7 gap-1 sm:gap-2", classNames?.dateStrip),
6477
+ children: dateRange.dates.map((date) => {
6478
+ const dayInfo = availabilityMap.get(date);
6479
+ const hasAvailability = dayInfo?.has_availability !== false;
6480
+ const isSelected = selectedDate === date;
6481
+ return /* @__PURE__ */ jsx(
6482
+ Tabs.Tab,
6483
+ {
6484
+ value: date,
6485
+ "data-cimplify-date-button": true,
6486
+ "data-selected": isSelected || void 0,
6487
+ "data-available": hasAvailability || void 0,
6488
+ "data-fully-booked": !hasAvailability || void 0,
6489
+ className: cn(
6490
+ "flex flex-col items-center justify-center rounded-md border border-border bg-background px-1 py-2 text-center text-xs font-medium text-foreground transition-colors hover:border-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[selected]:border-foreground data-[selected]:bg-foreground data-[selected]:text-background data-[fully-booked]:cursor-not-allowed data-[fully-booked]:opacity-40",
6491
+ classNames?.dateButton
6492
+ ),
6493
+ children: formatDate(date)
6494
+ },
6495
+ date
6496
+ );
6497
+ })
6498
+ }
6499
+ ),
6127
6500
  availabilityLoading && /* @__PURE__ */ jsx(
6128
6501
  "div",
6129
6502
  {
6130
6503
  "data-cimplify-date-slot-loading": true,
6131
6504
  "aria-busy": "true",
6132
- className: classNames?.loading
6505
+ className: cn("h-32 rounded-md bg-muted/40 animate-pulse", classNames?.loading)
6133
6506
  }
6134
6507
  ),
6135
6508
  /* @__PURE__ */ jsx("div", { "data-cimplify-date-slots": true, className: classNames?.slots, children: /* @__PURE__ */ jsx(
@@ -6140,7 +6513,10 @@ function DateSlotPicker({
6140
6513
  participantCount,
6141
6514
  selectedSlot,
6142
6515
  onSlotSelect: handleSlotSelect,
6143
- showPrice
6516
+ showPrice,
6517
+ schedulingMode,
6518
+ durationUnit,
6519
+ durationValue
6144
6520
  }
6145
6521
  ) })
6146
6522
  ]
@@ -7099,12 +7475,11 @@ function ProductSheet({
7099
7475
  )
7100
7476
  ] }),
7101
7477
  fullProduct.description && /* @__PURE__ */ jsx(
7102
- "p",
7478
+ "div",
7103
7479
  {
7104
7480
  "data-cimplify-product-sheet-description": true,
7105
- className: classNames?.description,
7106
- style: { margin: 0 },
7107
- children: fullProduct.description
7481
+ className: cn("text-sm leading-relaxed text-muted-foreground [&_p]:m-0 [&_p+p]:mt-2", classNames?.description),
7482
+ dangerouslySetInnerHTML: { __html: fullProduct.description }
7108
7483
  }
7109
7484
  ),
7110
7485
  /* @__PURE__ */ jsx(
@@ -7135,14 +7510,14 @@ function CardImage({
7135
7510
  "16/9": "aspect-[16/9]"
7136
7511
  }[aspectRatio];
7137
7512
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-card-image": true, className: cn("relative overflow-hidden bg-muted", aspectClass, className), children: [
7138
- renderImage ? renderImage({ src, alt, className: "w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.04]" }) : /* @__PURE__ */ jsx(
7513
+ renderImage ? renderImage({ src, alt, className: "w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.04]" }) : src ? /* @__PURE__ */ jsx(
7139
7514
  "img",
7140
7515
  {
7141
7516
  src,
7142
7517
  alt,
7143
7518
  className: "w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.04]"
7144
7519
  }
7145
- ),
7520
+ ) : null,
7146
7521
  children
7147
7522
  ] });
7148
7523
  }
@@ -7594,7 +7969,7 @@ function CompactServiceCard({
7594
7969
  const hasBillingPlans = product.billing_plans && product.billing_plans.length > 0;
7595
7970
  const href = `/products/${product.slug}`;
7596
7971
  const content = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 p-3", children: [
7597
- /* @__PURE__ */ jsx("div", { className: "w-[72px] h-[72px] rounded-xl overflow-hidden bg-muted shrink-0", children: renderImage ? renderImage({ src: image, alt: product.name, className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsx("img", { src: image, alt: product.name, className: "w-full h-full object-cover" }) }),
7972
+ /* @__PURE__ */ jsx("div", { className: "w-[72px] h-[72px] rounded-xl overflow-hidden bg-muted shrink-0", children: renderImage ? renderImage({ src: image, alt: product.name, className: "w-full h-full object-cover" }) : image ? /* @__PURE__ */ jsx("img", { src: image, alt: product.name, className: "w-full h-full object-cover" }) : null }),
7598
7973
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
7599
7974
  /* @__PURE__ */ jsx("h3", { className: "text-[14px] font-semibold text-foreground leading-tight truncate", children: product.name }),
7600
7975
  product.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] text-muted-foreground mt-0.5 truncate", children: product.description }),
@@ -13226,4 +13601,4 @@ function SparkleIcon({ className }) {
13226
13601
  );
13227
13602
  }
13228
13603
 
13229
- export { AccommodationCard, Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingsPage, BundleProductCard, BundleProductLayout, BundleSelector, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatWidget, CheckoutPage, CimplifyAccount, CimplifyCheckout, CimplifyProvider, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, CurrencySelector, CustomAttributesTable, CustomerInputFields, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, FeaturedCollectionLayout, FoodProductCard, FoodProductLayout, InventoryBadge, LeaseServiceCard, LocationPicker, MetadataStringList, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderSummary, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, SaleBadge, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, ServiceProductLayout, SessionMessageBanner, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, StoreNav, SubscriptionCard, TagPills, TwoColumnGrid, VariantSelector, VolumePricing, WholesaleProductCard, WholesaleProductLayout, WishlistButton, cn, roomToResource, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCollection, useCollections, useDeals, useDeliveryFee, useElements, useElementsReady, useFxRate, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useSubscription, useSubscriptions, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };
13604
+ export { AccommodationCard, Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingsPage, BundleProductCard, BundleProductLayout, BundleSelector, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatWidget, CheckoutPage, CimplifyAccount, CimplifyCheckout, CimplifyProvider, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, CurrencySelector, CustomAttributesTable, CustomerInputFields, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, FeaturedCollectionLayout, FoodProductCard, FoodProductLayout, InventoryBadge, LeaseServiceCard, LocationPicker, MetadataStringList, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderSummary, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, SaleBadge, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, ServiceProductLayout, SessionMessageBanner, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, StoreNav, SubscriptionCard, TagPills, TimePicker, TwoColumnGrid, VariantSelector, VolumePricing, WholesaleProductCard, WholesaleProductLayout, WishlistButton, cn, roomToResource, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCollection, useCollections, useDeals, useDeliveryFee, useElements, useElementsReady, useFxRate, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useSubscription, useSubscriptions, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };