@springbrand/gravel 0.1.3-alpha.1 → 0.1.3-alpha.2

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.
Files changed (32) hide show
  1. package/dist/_virtual/_commonjsHelpers.js +8 -0
  2. package/dist/_virtual/timezone.js +4 -0
  3. package/dist/_virtual/utc.js +4 -0
  4. package/dist/components/runtime-site/action/index.js +57 -0
  5. package/dist/components/runtime-site/floating-booking-bubble/api/booking-public.js +147 -0
  6. package/dist/components/runtime-site/floating-booking-bubble/booking-form-field.js +102 -0
  7. package/dist/components/runtime-site/floating-booking-bubble/bubble-container.js +89 -0
  8. package/dist/components/runtime-site/floating-booking-bubble/bubble-trigger.js +41 -0
  9. package/dist/components/runtime-site/floating-booking-bubble/dayjs-tz.js +9 -0
  10. package/dist/components/runtime-site/floating-booking-bubble/index.js +146 -0
  11. package/dist/components/runtime-site/floating-booking-bubble/public-appointment-model.js +92 -0
  12. package/dist/components/runtime-site/floating-booking-bubble/public-availability-model.js +14 -0
  13. package/dist/components/runtime-site/floating-booking-bubble/public-service-detail-model.js +33 -0
  14. package/dist/components/runtime-site/floating-booking-bubble/public-service-model.js +19 -0
  15. package/dist/components/runtime-site/floating-booking-bubble/step-1-service-picker.js +83 -0
  16. package/dist/components/runtime-site/floating-booking-bubble/step-2-time-picker.js +151 -0
  17. package/dist/components/runtime-site/floating-booking-bubble/step-3-details-form.js +142 -0
  18. package/dist/components/runtime-site/floating-booking-bubble/step-4-confirm.js +122 -0
  19. package/dist/components/runtime-site/floating-booking-bubble/use-booking-flow.js +287 -0
  20. package/dist/components/runtime-site/runtime-host-provider/index.js +20 -0
  21. package/dist/index.js +9 -1
  22. package/dist/node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/timezone.js +67 -0
  23. package/dist/node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/utc.js +79 -0
  24. package/package.json +12 -4
  25. package/dist/components/ui-base/index.d.ts +0 -1
  26. package/dist/components/ui-base/responsive-dialog/index.d.ts +0 -55
  27. package/dist/components/ui-block/background/index.d.ts +0 -8
  28. package/dist/components/ui-block/cover/index.d.ts +0 -17
  29. package/dist/components/ui-block/index.d.ts +0 -4
  30. package/dist/components/ui-block/sparkles/index.d.ts +0 -13
  31. package/dist/components/ui-block/spotlight/index.d.ts +0 -13
  32. package/dist/index.d.ts +0 -2
@@ -0,0 +1,8 @@
1
+ var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
2
+ function getDefaultExportFromCjs(x) {
3
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
+ }
5
+ export {
6
+ commonjsGlobal,
7
+ getDefaultExportFromCjs
8
+ };
@@ -0,0 +1,4 @@
1
+ var timezone = { exports: {} };
2
+ export {
3
+ timezone as __module
4
+ };
@@ -0,0 +1,4 @@
1
+ var utc = { exports: {} };
2
+ export {
3
+ utc as __module
4
+ };
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { Children, cloneElement } from "react";
3
+ import { useRuntimeHost } from "../runtime-host-provider/index.js";
4
+ function createRuntimeActionContext(trigger, event) {
5
+ const element = event.currentTarget;
6
+ const ownerDocument = element.ownerDocument;
7
+ return {
8
+ trigger,
9
+ event,
10
+ element,
11
+ ownerDocument,
12
+ ownerWindow: ownerDocument.defaultView
13
+ };
14
+ }
15
+ function Action({
16
+ children,
17
+ id,
18
+ input,
19
+ trigger = "click"
20
+ }) {
21
+ const runtimeHost = useRuntimeHost();
22
+ const child = Children.only(children);
23
+ const originalHandler = trigger === "click" ? child.props.onClick : trigger === "pointerDown" ? child.props.onPointerDown : child.props.onSubmit;
24
+ const actionHandler = (event) => {
25
+ var _a, _b;
26
+ originalHandler == null ? void 0 : originalHandler(event);
27
+ if (event.defaultPrevented || !id || !runtimeHost.actions) {
28
+ return;
29
+ }
30
+ const context = createRuntimeActionContext(trigger, event);
31
+ try {
32
+ const result = runtimeHost.actions.runAction(id, input, context);
33
+ void Promise.resolve(result).catch((error) => {
34
+ var _a2, _b2;
35
+ (_b2 = (_a2 = runtimeHost.actions) == null ? void 0 : _a2.onError) == null ? void 0 : _b2.call(_a2, error, context);
36
+ });
37
+ } catch (error) {
38
+ (_b = (_a = runtimeHost.actions).onError) == null ? void 0 : _b.call(_a, error, context);
39
+ }
40
+ };
41
+ if (trigger === "pointerDown") {
42
+ return cloneElement(child, {
43
+ onPointerDown: actionHandler
44
+ });
45
+ }
46
+ if (trigger === "submit") {
47
+ return cloneElement(child, {
48
+ onSubmit: actionHandler
49
+ });
50
+ }
51
+ return cloneElement(child, {
52
+ onClick: actionHandler
53
+ });
54
+ }
55
+ export {
56
+ Action
57
+ };
@@ -0,0 +1,147 @@
1
+ import { PublicServiceModel } from "../public-service-model.js";
2
+ import { PublicServiceDetailModel } from "../public-service-detail-model.js";
3
+ import { PublicAvailabilityModel } from "../public-availability-model.js";
4
+ import { PublicAppointmentModel } from "../public-appointment-model.js";
5
+ class BookingApiError extends Error {
6
+ constructor(kind, code, httpStatus, message, details) {
7
+ super(message);
8
+ this.kind = kind;
9
+ this.code = code;
10
+ this.httpStatus = httpStatus;
11
+ this.details = details;
12
+ this.name = "BookingApiError";
13
+ }
14
+ }
15
+ function unwrapPublicRpc(wire, httpStatus) {
16
+ if ((wire == null ? void 0 : wire.success) === true && wire.msg !== void 0) {
17
+ return wire.msg;
18
+ }
19
+ throw new BookingApiError(
20
+ "biz",
21
+ (wire == null ? void 0 : wire.code) ?? "BOOKING_UNKNOWN_ERROR",
22
+ httpStatus,
23
+ (wire == null ? void 0 : wire.errMsg) ?? "Booking request failed",
24
+ wire
25
+ );
26
+ }
27
+ function normalizeBaseUrl(domain) {
28
+ return (domain ?? "").replace(/\/$/, "");
29
+ }
30
+ async function publicFetch(domain, path, init) {
31
+ const baseUrl = normalizeBaseUrl(domain);
32
+ let res;
33
+ try {
34
+ res = await fetch(`${baseUrl}${path}`, {
35
+ ...init,
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ ...(init == null ? void 0 : init.headers) ?? {}
39
+ }
40
+ });
41
+ } catch (err) {
42
+ throw new BookingApiError(
43
+ "network",
44
+ "NETWORK_ERROR",
45
+ null,
46
+ err instanceof Error ? err.message : "Network error"
47
+ );
48
+ }
49
+ if (!res.ok) {
50
+ let body = null;
51
+ try {
52
+ body = await res.json();
53
+ } catch {
54
+ }
55
+ const code = res.status === 429 ? "RATE_LIMITED" : "INTERNAL_ERROR";
56
+ throw new BookingApiError(
57
+ "http",
58
+ code,
59
+ res.status,
60
+ `HTTP ${res.status}`,
61
+ body
62
+ );
63
+ }
64
+ let wire;
65
+ try {
66
+ wire = await res.json();
67
+ } catch (err) {
68
+ throw new BookingApiError(
69
+ "network",
70
+ "INVALID_JSON",
71
+ res.status,
72
+ err instanceof Error ? err.message : "Invalid JSON"
73
+ );
74
+ }
75
+ return unwrapPublicRpc(wire, res.status);
76
+ }
77
+ async function fetchServices(domain, brandSlug) {
78
+ const wire = await publicFetch(
79
+ domain,
80
+ `/api/public/booking/${encodeURIComponent(brandSlug)}/services`
81
+ );
82
+ return {
83
+ services: wire.services.map(PublicServiceModel.fromWire),
84
+ timezone: wire.timezone
85
+ };
86
+ }
87
+ async function fetchServiceDetail(domain, brandSlug, serviceId) {
88
+ const wire = await publicFetch(
89
+ domain,
90
+ `/api/public/booking/${encodeURIComponent(brandSlug)}/services/${serviceId}`
91
+ );
92
+ return {
93
+ service: PublicServiceDetailModel.fromWire(wire.service),
94
+ timezone: wire.timezone
95
+ };
96
+ }
97
+ async function fetchAvailability(domain, brandSlug, params) {
98
+ const qs = new URLSearchParams({
99
+ service_id: String(params.service_id),
100
+ from: params.from,
101
+ to: params.to
102
+ });
103
+ const wire = await publicFetch(
104
+ domain,
105
+ `/api/public/booking/${encodeURIComponent(brandSlug)}/availability?${qs.toString()}`
106
+ );
107
+ return PublicAvailabilityModel.fromWire(wire);
108
+ }
109
+ async function submitAppointment(domain, brandSlug, body) {
110
+ const wireBody = {
111
+ service_id: body.service_id,
112
+ scheduled_at: body.scheduled_at,
113
+ contact: {
114
+ name: [body.customer.first_name, body.customer.last_name].filter(Boolean).join(" ").trim(),
115
+ email: body.customer.email,
116
+ phone: body.customer.phone,
117
+ metadata: {
118
+ first_name: body.customer.first_name,
119
+ last_name: body.customer.last_name,
120
+ address: body.customer.address
121
+ }
122
+ },
123
+ custom_answers: body.custom_answers.map((a) => ({
124
+ question_id: a.questionId,
125
+ label: a.label,
126
+ answer: a.answer
127
+ })),
128
+ source_page: body.source_page
129
+ };
130
+ const wire = await publicFetch(
131
+ domain,
132
+ `/api/public/booking/${encodeURIComponent(brandSlug)}/appointments`,
133
+ {
134
+ method: "POST",
135
+ body: JSON.stringify(wireBody)
136
+ }
137
+ );
138
+ return PublicAppointmentModel.fromWire(wire.appointment);
139
+ }
140
+ export {
141
+ BookingApiError,
142
+ fetchAvailability,
143
+ fetchServiceDetail,
144
+ fetchServices,
145
+ submitAppointment,
146
+ unwrapPublicRpc
147
+ };
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { Input } from "@springbrand/site-block/components/shadcn/input";
4
+ import { Label } from "@springbrand/site-block/components/shadcn/label";
5
+ import { Textarea } from "@springbrand/site-block/components/shadcn/textarea";
6
+ import { RadioGroup, RadioGroupItem } from "@springbrand/site-block/components/shadcn/radio-group";
7
+ import { Checkbox } from "@springbrand/site-block/components/shadcn/checkbox";
8
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@springbrand/site-block/components/shadcn/select";
9
+ import { cn } from "@springbrand/site-block/lib/utils";
10
+ function BookingFormField({
11
+ question,
12
+ value,
13
+ onChange,
14
+ error
15
+ }) {
16
+ const fieldId = `q-${question.id}`;
17
+ const renderControl = () => {
18
+ switch (question.type) {
19
+ case "one_line":
20
+ return /* @__PURE__ */ jsx(
21
+ Input,
22
+ {
23
+ id: fieldId,
24
+ value: value ?? "",
25
+ onChange: (e) => onChange(e.target.value),
26
+ "aria-invalid": !!error || void 0
27
+ }
28
+ );
29
+ case "multi_line":
30
+ return /* @__PURE__ */ jsx(
31
+ Textarea,
32
+ {
33
+ id: fieldId,
34
+ value: value ?? "",
35
+ onChange: (e) => onChange(e.target.value),
36
+ "aria-invalid": !!error || void 0
37
+ }
38
+ );
39
+ case "radio":
40
+ return /* @__PURE__ */ jsx(
41
+ RadioGroup,
42
+ {
43
+ value: value ?? "",
44
+ onValueChange: (v) => onChange(v),
45
+ className: "gap-2",
46
+ children: (question.options ?? []).map((opt) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
47
+ /* @__PURE__ */ jsx(RadioGroupItem, { id: `${fieldId}-${opt.id}`, value: opt.id }),
48
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${fieldId}-${opt.id}`, className: "font-normal", children: opt.label })
49
+ ] }, opt.id))
50
+ }
51
+ );
52
+ case "checkboxes": {
53
+ const arr = Array.isArray(value) ? value : [];
54
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: (question.options ?? []).map((opt) => {
55
+ const checked = arr.includes(opt.id);
56
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
57
+ /* @__PURE__ */ jsx(
58
+ Checkbox,
59
+ {
60
+ id: `${fieldId}-${opt.id}`,
61
+ checked,
62
+ onCheckedChange: (c) => {
63
+ const next = c ? [...arr, opt.id] : arr.filter((id) => id !== opt.id);
64
+ onChange(next);
65
+ }
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${fieldId}-${opt.id}`, className: "font-normal", children: opt.label })
69
+ ] }, opt.id);
70
+ }) });
71
+ }
72
+ case "dropdown": {
73
+ const options = question.options ?? [];
74
+ return /* @__PURE__ */ jsxs(
75
+ Select,
76
+ {
77
+ value: value ?? "",
78
+ items: options.map((opt) => ({ label: opt.label, value: opt.id })),
79
+ onValueChange: (v) => onChange(v),
80
+ children: [
81
+ /* @__PURE__ */ jsx(SelectTrigger, { id: fieldId, "aria-invalid": !!error || void 0, children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select…" }) }),
82
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.id, children: opt.label }, opt.id)) })
83
+ ]
84
+ }
85
+ );
86
+ }
87
+ default:
88
+ return null;
89
+ }
90
+ };
91
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", error && "[&>label]:text-destructive"), children: [
92
+ /* @__PURE__ */ jsxs(Label, { htmlFor: fieldId, children: [
93
+ question.label,
94
+ question.required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-destructive", children: "*" })
95
+ ] }),
96
+ renderControl(),
97
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
98
+ ] });
99
+ }
100
+ export {
101
+ BookingFormField
102
+ };
@@ -0,0 +1,89 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { X } from "lucide-react";
4
+ import { useState, useEffect } from "react";
5
+ import { Sheet, SheetContent } from "@springbrand/site-block/components/shadcn/sheet";
6
+ import { Button } from "@springbrand/site-block/components/shadcn/button";
7
+ import { cn } from "@springbrand/site-block/lib/utils";
8
+ function BubbleContainer({
9
+ open,
10
+ onOpenChange,
11
+ title,
12
+ children,
13
+ onBack,
14
+ stepIndicator,
15
+ className,
16
+ container
17
+ }) {
18
+ const [isDesktop, setIsDesktop] = useState(false);
19
+ const headerTitle = title ?? (onBack ? null : "Book");
20
+ useEffect(() => {
21
+ const targetWindow = resolveContainerWindow(container);
22
+ if (!targetWindow) return;
23
+ const mq = targetWindow.matchMedia("(min-width: 768px)");
24
+ const update = () => setIsDesktop(mq.matches);
25
+ update();
26
+ mq.addEventListener("change", update);
27
+ return () => mq.removeEventListener("change", update);
28
+ }, [container]);
29
+ return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
30
+ SheetContent,
31
+ {
32
+ side: isDesktop ? "right" : "bottom",
33
+ showCloseButton: false,
34
+ container,
35
+ className: cn(
36
+ "z-[1001] p-0 flex flex-col gap-0 overflow-hidden",
37
+ isDesktop ? (
38
+ // 桌面 popover 与气泡按钮的相对位置:
39
+ // - bottom-28 (112px) 让对话框底端高于按钮顶部 (72px) 约 40px,留出明显呼吸感
40
+ // - right-8 (32px) 把对话框右边再让出一点,避免和屏幕右沿/按钮贴在一起
41
+ // SheetContent 基类带 data-[side=right]:{inset-y-0,h-full,w-3/4},
42
+ // 必须用 ! important 覆盖 top / bottom / 高宽,否则会被钉在 top:0 全高。
43
+ "w-[400px]! sm:max-w-[420px] right-8 top-auto! bottom-28! h-[600px]! max-h-[calc(100vh-9rem)] rounded-2xl border shadow-2xl"
44
+ ) : "h-[min(85svh,calc(100dvh-env(safe-area-inset-top)-0.75rem))] w-full rounded-t-2xl border-t",
45
+ className
46
+ ),
47
+ overlayClassName: "z-[1000]",
48
+ children: [
49
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b px-4 h-12 shrink-0", children: [
50
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
51
+ onBack && /* @__PURE__ */ jsx(
52
+ Button,
53
+ {
54
+ type: "button",
55
+ variant: "ghost",
56
+ size: "sm",
57
+ className: "-ml-2 h-8 px-2",
58
+ onClick: onBack,
59
+ children: "Back"
60
+ }
61
+ ),
62
+ headerTitle && /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm truncate", children: headerTitle })
63
+ ] }),
64
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
65
+ stepIndicator && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: stepIndicator }),
66
+ /* @__PURE__ */ jsx(
67
+ "button",
68
+ {
69
+ type: "button",
70
+ "aria-label": "Close",
71
+ onClick: () => onOpenChange(false),
72
+ className: "rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
73
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
74
+ }
75
+ )
76
+ ] })
77
+ ] }),
78
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain [-webkit-overflow-scrolling:touch]", children })
79
+ ]
80
+ }
81
+ ) });
82
+ }
83
+ function resolveContainerWindow(container) {
84
+ const element = container && "current" in container ? container.current : container;
85
+ return (element == null ? void 0 : element.ownerDocument.defaultView) ?? null;
86
+ }
87
+ export {
88
+ BubbleContainer
89
+ };
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Calendar } from "lucide-react";
4
+ import { Button } from "@springbrand/site-block/components/shadcn/button";
5
+ import { cn } from "@springbrand/site-block/lib/utils";
6
+ function BubbleTrigger({
7
+ label = "Book now",
8
+ onClick,
9
+ editorMode,
10
+ className,
11
+ style,
12
+ ...domProps
13
+ }) {
14
+ return /* @__PURE__ */ jsx(
15
+ "div",
16
+ {
17
+ ...domProps,
18
+ className: cn(
19
+ "fixed bottom-[calc(env(safe-area-inset-bottom)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)] z-50 print:hidden sm:bottom-6 sm:right-6",
20
+ editorMode && "group cursor-pointer outline-primary/40 rounded-full"
21
+ ),
22
+ onClick,
23
+ children: /* @__PURE__ */ jsxs(
24
+ Button,
25
+ {
26
+ type: "button",
27
+ size: "lg",
28
+ className: cn("rounded-full shadow-lg gap-2 px-5 h-12", className),
29
+ style,
30
+ children: [
31
+ /* @__PURE__ */ jsx(Calendar, { className: "h-5 w-5" }),
32
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: label })
33
+ ]
34
+ }
35
+ )
36
+ }
37
+ );
38
+ }
39
+ export {
40
+ BubbleTrigger
41
+ };
@@ -0,0 +1,9 @@
1
+ import dayjs from "dayjs";
2
+ import { default as default2 } from "dayjs";
3
+ import utc from "../../../node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/utc.js";
4
+ import timezone from "../../../node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/timezone.js";
5
+ dayjs.extend(utc);
6
+ dayjs.extend(timezone);
7
+ export {
8
+ default2 as default
9
+ };
@@ -0,0 +1,146 @@
1
+ "use client";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
+ import { useState, useRef } from "react";
4
+ import { useRuntimeHost } from "../runtime-host-provider/index.js";
5
+ import { BubbleContainer } from "./bubble-container.js";
6
+ import { BubbleTrigger } from "./bubble-trigger.js";
7
+ import { Step1ServicePicker } from "./step-1-service-picker.js";
8
+ import { Step2TimePicker } from "./step-2-time-picker.js";
9
+ import { Step3DetailsForm } from "./step-3-details-form.js";
10
+ import { Step4Confirm } from "./step-4-confirm.js";
11
+ import { useBookingFlow } from "./use-booking-flow.js";
12
+ function FloatingBookingBubble({
13
+ text = "Book now",
14
+ styles: _styles,
15
+ onClick,
16
+ ...triggerProps
17
+ } = {}) {
18
+ var _a, _b;
19
+ const { isEdit, bookingBubbleVisible } = useRuntimeHost();
20
+ const flow = useBookingFlow();
21
+ const [open, setOpen] = useState(false);
22
+ const portalAnchorRef = useRef(null);
23
+ if (bookingBubbleVisible !== true) {
24
+ return null;
25
+ }
26
+ const phase = flow.phase;
27
+ if (isEdit) {
28
+ return /* @__PURE__ */ jsx(
29
+ BubbleTrigger,
30
+ {
31
+ ...triggerProps,
32
+ label: text,
33
+ editorMode: true,
34
+ onClick: (e) => {
35
+ onClick == null ? void 0 : onClick(e);
36
+ e.preventDefault();
37
+ e.stopPropagation();
38
+ }
39
+ }
40
+ );
41
+ }
42
+ const stepIndex = (() => {
43
+ switch (phase) {
44
+ case "step1":
45
+ return 1;
46
+ case "step2":
47
+ return 2;
48
+ case "step3":
49
+ return 3;
50
+ case "step4":
51
+ case "success":
52
+ return 4;
53
+ default:
54
+ return null;
55
+ }
56
+ })();
57
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
58
+ /* @__PURE__ */ jsx(
59
+ BubbleTrigger,
60
+ {
61
+ ...triggerProps,
62
+ label: text,
63
+ onClick: (e) => {
64
+ onClick == null ? void 0 : onClick(e);
65
+ flow.open();
66
+ setOpen(true);
67
+ }
68
+ }
69
+ ),
70
+ /* @__PURE__ */ jsx("div", { ref: portalAnchorRef }),
71
+ /* @__PURE__ */ jsxs(
72
+ BubbleContainer,
73
+ {
74
+ open: open && phase !== "idle",
75
+ onOpenChange: (next) => {
76
+ setOpen(next);
77
+ if (!next) flow.close();
78
+ },
79
+ title: phase === "success" ? "Booking Confirmed" : void 0,
80
+ onBack: phase === "step2" || phase === "step3" || phase === "step4" ? flow.back : void 0,
81
+ stepIndicator: stepIndex ? `${stepIndex} / 4` : void 0,
82
+ container: portalAnchorRef,
83
+ children: [
84
+ phase === "step1" && /* @__PURE__ */ jsx(
85
+ Step1ServicePicker,
86
+ {
87
+ services: flow.services,
88
+ loading: flow.servicesLoading,
89
+ error: flow.servicesError,
90
+ onPick: flow.pickService,
91
+ onRetry: () => flow.open()
92
+ }
93
+ ),
94
+ phase === "step2" && flow.selectedService && /* @__PURE__ */ jsx(
95
+ Step2TimePicker,
96
+ {
97
+ visibleMonth: flow.visibleMonth,
98
+ availability: flow.availability,
99
+ timezone: ((_a = flow.availability) == null ? void 0 : _a.timezone) ?? "",
100
+ loading: flow.availabilityLoading,
101
+ error: flow.availabilityError,
102
+ onChangeMonth: flow.changeMonth,
103
+ onPickSlot: flow.pickSlot,
104
+ onRetry: () => flow.changeMonth(flow.visibleMonth)
105
+ }
106
+ ),
107
+ phase === "step3" && flow.selectedService && /* @__PURE__ */ jsx(
108
+ Step3DetailsForm,
109
+ {
110
+ service: flow.selectedService,
111
+ form: flow.form,
112
+ onChange: flow.setForm,
113
+ onNext: () => flow.submit()
114
+ }
115
+ ),
116
+ (phase === "step4" || phase === "success") && flow.selectedService && flow.selectedSlot && /* @__PURE__ */ jsx(
117
+ Step4Confirm,
118
+ {
119
+ service: flow.selectedService,
120
+ slot: flow.selectedSlot,
121
+ timezone: ((_b = flow.availability) == null ? void 0 : _b.timezone) ?? "",
122
+ form: flow.form,
123
+ submitting: flow.submitting,
124
+ submitError: flow.submitError,
125
+ confirmation: flow.confirmation,
126
+ onSubmit: flow.submit,
127
+ onClose: () => {
128
+ setOpen(false);
129
+ flow.close();
130
+ }
131
+ }
132
+ )
133
+ ]
134
+ }
135
+ )
136
+ ] });
137
+ }
138
+ const FloatingBookingBubbleDefaults = {
139
+ text: "Book now",
140
+ styles: {}
141
+ };
142
+ export {
143
+ FloatingBookingBubble,
144
+ FloatingBookingBubbleDefaults,
145
+ FloatingBookingBubble as default
146
+ };