@patientos/website-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,484 @@
1
+ import {
2
+ ISLANDS,
3
+ mountIslands
4
+ } from "./chunk-Q4URDZAK.js";
5
+ import {
6
+ PortalAccount,
7
+ PortalPanel,
8
+ getAppointmentTypes,
9
+ getClinic,
10
+ getPublicApi,
11
+ getServices,
12
+ getThemeTokens
13
+ } from "./chunk-TOPPLOH2.js";
14
+ import {
15
+ BPOINT_SCRIPT_ORIGIN,
16
+ BPOINT_SCRIPT_URL,
17
+ CONSULT_CHUNK_PATH,
18
+ THIRD_PARTY_SCRIPT_ORIGINS,
19
+ TURNSTILE_SCRIPT_ORIGIN,
20
+ TURNSTILE_SCRIPT_URL
21
+ } from "./chunk-ZOF22TJA.js";
22
+ import {
23
+ evaluateShowWhen,
24
+ visibleQuestions
25
+ } from "./chunk-QDO3SJWR.js";
26
+ import {
27
+ ISLAND_ATTR,
28
+ ISLAND_PROPS_ATTR,
29
+ Island,
30
+ mountIslandsWith,
31
+ readIslandProps
32
+ } from "./chunk-THMT43MV.js";
33
+ import "./chunk-G3U6EXJM.js";
34
+ import {
35
+ contrastRatio,
36
+ deriveForeground,
37
+ derivePrimaryPair,
38
+ parseHexColor,
39
+ relativeLuminance
40
+ } from "./chunk-LCHW6XF4.js";
41
+ import {
42
+ patientSurfaceTheme
43
+ } from "./chunk-JTZ6GYA7.js";
44
+ import "./chunk-MLKGABMK.js";
45
+
46
+ // src/tokens.tsx
47
+ import { jsx } from "react/jsx-runtime";
48
+ var defaultTheme = {
49
+ // palette
50
+ brand: "#1f6feb",
51
+ brandForeground: "#ffffff",
52
+ neutral: "#f4f6f8",
53
+ neutralForeground: "#1b2733",
54
+ accent: "#0fb39a",
55
+ background: "#ffffff",
56
+ foreground: "#1b2733",
57
+ muted: "#5b6b7a",
58
+ // type scale
59
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
60
+ fontFamilyHeading: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
61
+ fontSizeBase: "1rem",
62
+ fontSizeSm: "0.875rem",
63
+ fontSizeLg: "1.25rem",
64
+ fontSizeH2: "2rem",
65
+ fontSizeH1: "3rem",
66
+ // geometry
67
+ spacingUnit: "0.5rem",
68
+ radius: "0.625rem"
69
+ };
70
+ function themeToCssVars(theme) {
71
+ return {
72
+ "--sk-brand": theme.brand,
73
+ "--sk-brand-foreground": theme.brandForeground,
74
+ "--sk-neutral": theme.neutral,
75
+ "--sk-neutral-foreground": theme.neutralForeground,
76
+ "--sk-accent": theme.accent,
77
+ "--sk-background": theme.background,
78
+ "--sk-foreground": theme.foreground,
79
+ "--sk-muted": theme.muted,
80
+ "--sk-font": theme.fontFamily,
81
+ "--sk-font-heading": theme.fontFamilyHeading,
82
+ "--sk-font-size-base": theme.fontSizeBase,
83
+ "--sk-font-size-sm": theme.fontSizeSm,
84
+ "--sk-font-size-lg": theme.fontSizeLg,
85
+ "--sk-font-size-h2": theme.fontSizeH2,
86
+ "--sk-font-size-h1": theme.fontSizeH1,
87
+ "--sk-space": theme.spacingUnit,
88
+ "--sk-radius": theme.radius
89
+ };
90
+ }
91
+ function resolveTheme(theme) {
92
+ const merged = theme ? { ...defaultTheme, ...theme } : defaultTheme;
93
+ return { ...merged, brandForeground: deriveForeground(merged.brand) };
94
+ }
95
+ function ThemeProvider({
96
+ theme,
97
+ children,
98
+ className,
99
+ ...rest
100
+ }) {
101
+ const resolved = resolveTheme(theme);
102
+ const style = themeToCssVars(resolved);
103
+ return /* @__PURE__ */ jsx("div", { "data-sk-theme": "", "data-sk-root": "", className, style, ...rest, children });
104
+ }
105
+
106
+ // src/layout.tsx
107
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
108
+ function cx(...parts) {
109
+ return parts.filter(Boolean).join(" ");
110
+ }
111
+ function Container({
112
+ className,
113
+ children,
114
+ ...rest
115
+ }) {
116
+ return /* @__PURE__ */ jsx2("div", { className: cx("sk-container", className), ...rest, children });
117
+ }
118
+ function Row({
119
+ className,
120
+ children,
121
+ ...rest
122
+ }) {
123
+ return /* @__PURE__ */ jsx2("div", { className: cx("sk-row", className), ...rest, children });
124
+ }
125
+ function Stack({
126
+ className,
127
+ children,
128
+ ...rest
129
+ }) {
130
+ return /* @__PURE__ */ jsx2("div", { className: cx("sk-stack", className), ...rest, children });
131
+ }
132
+ function WebsiteShell({
133
+ theme,
134
+ title,
135
+ children
136
+ }) {
137
+ const resolved = resolveTheme(theme);
138
+ const vars = themeToCssVars(resolved);
139
+ const rootCss = ":root{" + Object.entries(vars).map(([k, v]) => `${k}:${v};`).join("") + "}";
140
+ return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
141
+ /* @__PURE__ */ jsxs("head", { children: [
142
+ /* @__PURE__ */ jsx2("meta", { charSet: "utf-8" }),
143
+ /* @__PURE__ */ jsx2("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
144
+ title ? /* @__PURE__ */ jsx2("title", { children: title }) : null,
145
+ /* @__PURE__ */ jsx2("link", { rel: "stylesheet", href: "/website-kit.css" }),
146
+ /* @__PURE__ */ jsx2("style", { dangerouslySetInnerHTML: { __html: guardStyleCss(rootCss) } }),
147
+ /* @__PURE__ */ jsx2("script", { type: "module", src: "/main.js" })
148
+ ] }),
149
+ /* @__PURE__ */ jsx2("body", { children: /* @__PURE__ */ jsx2("div", { "data-sk-theme": "", "data-sk-root": "", children }) })
150
+ ] });
151
+ }
152
+ function guardStyleCss(css) {
153
+ return css.replace(/<\/(style)/gi, "<\\/$1");
154
+ }
155
+
156
+ // src/components.tsx
157
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
158
+ function cx2(...parts) {
159
+ return parts.filter(Boolean).join(" ");
160
+ }
161
+ function Button({
162
+ href,
163
+ variant = "solid",
164
+ className,
165
+ children,
166
+ target,
167
+ rel,
168
+ ...rest
169
+ }) {
170
+ const cls = cx2("sk-button", `sk-button--${variant}`, className);
171
+ if (href) {
172
+ return /* @__PURE__ */ jsx3(
173
+ "a",
174
+ {
175
+ className: cls,
176
+ href,
177
+ target,
178
+ rel,
179
+ ...rest,
180
+ children
181
+ }
182
+ );
183
+ }
184
+ return /* @__PURE__ */ jsx3("button", { className: cls, ...rest, children });
185
+ }
186
+ function Hero({
187
+ heading,
188
+ subheading,
189
+ image,
190
+ cta,
191
+ className,
192
+ style,
193
+ ...rest
194
+ }) {
195
+ const mergedStyle = image ? { ...style, ["--sk-hero-image"]: `url("${image}")` } : style ?? {};
196
+ return /* @__PURE__ */ jsx3(
197
+ "section",
198
+ {
199
+ className: cx2("sk-hero", image && "sk-hero--image", className),
200
+ style: mergedStyle,
201
+ ...rest,
202
+ children: /* @__PURE__ */ jsxs2("div", { className: "sk-hero__inner", children: [
203
+ /* @__PURE__ */ jsx3("h1", { className: "sk-hero__heading", children: heading }),
204
+ subheading ? /* @__PURE__ */ jsx3("p", { className: "sk-hero__subheading", children: subheading }) : null,
205
+ cta ? /* @__PURE__ */ jsx3("div", { className: "sk-hero__cta", children: cta }) : null
206
+ ] })
207
+ }
208
+ );
209
+ }
210
+ function Section({
211
+ title,
212
+ intro,
213
+ children,
214
+ className,
215
+ ...rest
216
+ }) {
217
+ return /* @__PURE__ */ jsx3("section", { className: cx2("sk-section", className), ...rest, children: /* @__PURE__ */ jsxs2("div", { className: "sk-section__inner", children: [
218
+ title ? /* @__PURE__ */ jsx3("h2", { className: "sk-section__title", children: title }) : null,
219
+ intro ? /* @__PURE__ */ jsx3("p", { className: "sk-section__intro", children: intro }) : null,
220
+ children
221
+ ] }) });
222
+ }
223
+ function ServiceGrid({
224
+ services,
225
+ className,
226
+ ...rest
227
+ }) {
228
+ return /* @__PURE__ */ jsx3("ul", { className: cx2("sk-service-grid", className), ...rest, children: services.map((s, i) => /* @__PURE__ */ jsxs2("li", { className: "sk-service-grid__item", children: [
229
+ s.icon ? /* @__PURE__ */ jsx3("img", { className: "sk-service-grid__icon", src: s.icon, alt: "", "aria-hidden": "true" }) : null,
230
+ /* @__PURE__ */ jsx3("h3", { className: "sk-service-grid__title", children: s.title }),
231
+ /* @__PURE__ */ jsx3("p", { className: "sk-service-grid__desc", children: s.description })
232
+ ] }, `${s.title}-${i}`)) });
233
+ }
234
+ function TeamGrid({
235
+ members,
236
+ className,
237
+ ...rest
238
+ }) {
239
+ return /* @__PURE__ */ jsx3("ul", { className: cx2("sk-team-grid", className), ...rest, children: members.map((m, i) => /* @__PURE__ */ jsxs2("li", { className: "sk-team-grid__item", children: [
240
+ m.photo ? /* @__PURE__ */ jsx3(
241
+ "img",
242
+ {
243
+ className: "sk-team-grid__photo",
244
+ src: m.photo,
245
+ alt: `Photo of ${m.name}`
246
+ }
247
+ ) : /* @__PURE__ */ jsx3("div", { className: "sk-team-grid__photo sk-team-grid__photo--placeholder", "aria-hidden": "true" }),
248
+ /* @__PURE__ */ jsx3("p", { className: "sk-team-grid__name", children: m.name }),
249
+ /* @__PURE__ */ jsx3("p", { className: "sk-team-grid__role", children: m.role })
250
+ ] }, `${m.name}-${i}`)) });
251
+ }
252
+ function Hours({ rows, className, ...rest }) {
253
+ return /* @__PURE__ */ jsx3("dl", { className: cx2("sk-hours", className), ...rest, children: rows.map((r, i) => /* @__PURE__ */ jsxs2("div", { className: "sk-hours__row", children: [
254
+ /* @__PURE__ */ jsx3("dt", { className: "sk-hours__day", children: r.day }),
255
+ /* @__PURE__ */ jsx3("dd", { className: "sk-hours__time", children: r.hours })
256
+ ] }, `${r.day}-${i}`)) });
257
+ }
258
+ function MapEmbed({
259
+ src,
260
+ address,
261
+ height = "320px",
262
+ title = "Map",
263
+ className,
264
+ style,
265
+ ...rest
266
+ }) {
267
+ const resolvedSrc = src ?? (address ? `https://www.google.com/maps?q=${encodeURIComponent(address)}&output=embed` : void 0);
268
+ return /* @__PURE__ */ jsx3(
269
+ "div",
270
+ {
271
+ className: cx2("sk-map", className),
272
+ style: { ...style, ["--sk-map-height"]: height },
273
+ ...rest,
274
+ children: resolvedSrc ? /* @__PURE__ */ jsx3(
275
+ "iframe",
276
+ {
277
+ className: "sk-map__frame",
278
+ src: resolvedSrc,
279
+ title,
280
+ loading: "lazy",
281
+ referrerPolicy: "no-referrer-when-downgrade"
282
+ }
283
+ ) : null
284
+ }
285
+ );
286
+ }
287
+ function FAQ({ items, className, ...rest }) {
288
+ return /* @__PURE__ */ jsx3("div", { className: cx2("sk-faq", className), ...rest, children: items.map((it, i) => /* @__PURE__ */ jsxs2("details", { className: "sk-faq__item", children: [
289
+ /* @__PURE__ */ jsx3("summary", { className: "sk-faq__q", children: it.q }),
290
+ /* @__PURE__ */ jsx3("div", { className: "sk-faq__a", children: it.a })
291
+ ] }, `${it.q}-${i}`)) });
292
+ }
293
+
294
+ // src/certificate-funnel.tsx
295
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
296
+ function CertificateFunnel({
297
+ serviceKeys,
298
+ serviceKey,
299
+ serviceCopy,
300
+ heading,
301
+ className
302
+ }) {
303
+ const publicApi = getPublicApi();
304
+ const clinic = getClinic();
305
+ const theme = patientSurfaceTheme(getThemeTokens());
306
+ const snapshotServices = getServices().filter((s) => !!s.key).map((s) => ({ key: s.key, label: serviceCopy?.[s.key] ?? s.label }));
307
+ const services = serviceKeys ? serviceKeys.map((k) => snapshotServices.find((s) => s.key === k)).filter((s) => !!s) : snapshotServices;
308
+ const offered = serviceKey ? services.filter((s) => s.key === serviceKey) : services;
309
+ const title = heading ?? "Medical certificates";
310
+ return /* @__PURE__ */ jsxs3(
311
+ Island,
312
+ {
313
+ name: "certificate-funnel",
314
+ props: {
315
+ serviceKey,
316
+ heading: title,
317
+ publicApi,
318
+ services: offered,
319
+ clinicName: clinic.name,
320
+ clinicPhone: clinic.phone
321
+ },
322
+ className: ["sk-certificate-funnel", className].filter(Boolean).join(" "),
323
+ style: themeStyle(theme),
324
+ children: [
325
+ /* @__PURE__ */ jsx4("h2", { className: "sk-certificate-funnel__heading", children: title }),
326
+ offered.length > 0 && /* @__PURE__ */ jsx4("ul", { className: "sk-certificate-funnel__services", children: offered.map((s) => /* @__PURE__ */ jsx4("li", { children: s.label }, s.key)) }),
327
+ /* @__PURE__ */ jsxs3("p", { className: "sk-certificate-funnel__placeholder", children: [
328
+ "Request one online.",
329
+ " ",
330
+ clinic.phone ? `Book by phone: ${clinic.phone}` : "Or call the clinic to book."
331
+ ] })
332
+ ]
333
+ }
334
+ );
335
+ }
336
+ function themeStyle(theme) {
337
+ const style = {};
338
+ if (theme.primary) {
339
+ const { bg, fg } = derivePrimaryPair(theme.primary);
340
+ style["--sk-island-primary"] = bg;
341
+ style["--sk-island-primary-fg"] = fg;
342
+ }
343
+ if (theme.accent) style["--sk-island-accent"] = theme.accent;
344
+ if (theme.radius) style["--sk-island-radius"] = theme.radius;
345
+ if (theme.fontFamily) style["--sk-island-font"] = theme.fontFamily;
346
+ return style;
347
+ }
348
+
349
+ // src/booking-block.tsx
350
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
351
+ function BookingBlock({
352
+ appointmentTypeId,
353
+ practitionerId,
354
+ appointmentTypeIds,
355
+ heading,
356
+ className
357
+ }) {
358
+ const publicApi = getPublicApi();
359
+ const clinic = getClinic();
360
+ const theme = patientSurfaceTheme(getThemeTokens());
361
+ const all = getAppointmentTypes().filter((t) => !!t.id).map((t) => ({
362
+ id: t.id,
363
+ label: t.label,
364
+ durationMinutes: t.durationMinutes,
365
+ modality: t.modality
366
+ }));
367
+ const filtered = appointmentTypeIds ? appointmentTypeIds.map((id) => all.find((t) => t.id === id)).filter((t) => !!t) : all;
368
+ const offered = appointmentTypeId ? filtered.filter((t) => t.id === appointmentTypeId) : filtered;
369
+ const title = heading ?? "Book an appointment";
370
+ return /* @__PURE__ */ jsxs4(
371
+ Island,
372
+ {
373
+ name: "booking",
374
+ props: {
375
+ appointmentTypeId,
376
+ practitionerId,
377
+ heading: title,
378
+ publicApi,
379
+ appointmentTypes: offered,
380
+ clinicName: clinic.name,
381
+ clinicPhone: clinic.phone
382
+ },
383
+ className: ["sk-booking", className].filter(Boolean).join(" "),
384
+ style: themeStyle(theme),
385
+ children: [
386
+ /* @__PURE__ */ jsx5("h2", { className: "sk-booking__heading", children: title }),
387
+ offered.length > 0 && /* @__PURE__ */ jsx5("ul", { className: "sk-booking__types", children: offered.map((t) => /* @__PURE__ */ jsxs4("li", { children: [
388
+ t.label,
389
+ t.durationMinutes ? ` \xB7 ${t.durationMinutes} min` : ""
390
+ ] }, t.id)) }),
391
+ /* @__PURE__ */ jsx5("p", { className: "sk-booking__placeholder", children: clinic.phone ? `Book online, or call the clinic on ${clinic.phone}.` : "Book online, or call the clinic." })
392
+ ]
393
+ }
394
+ );
395
+ }
396
+
397
+ // src/store-block.tsx
398
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
399
+ function Store({
400
+ categoryHandle,
401
+ title,
402
+ columns,
403
+ className
404
+ }) {
405
+ return /* @__PURE__ */ jsxs5(
406
+ Island,
407
+ {
408
+ name: "store",
409
+ props: { categoryHandle, title, columns },
410
+ className: ["sk-store", className].filter(Boolean).join(" "),
411
+ children: [
412
+ /* @__PURE__ */ jsx6("h2", { className: "sk-store__heading", children: title ?? "Shop" }),
413
+ /* @__PURE__ */ jsx6("p", { className: "sk-store__placeholder", children: "Our online shop needs JavaScript. Please contact the clinic to order." })
414
+ ]
415
+ }
416
+ );
417
+ }
418
+
419
+ // src/cart-block.tsx
420
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
421
+ function Cart({ className }) {
422
+ return /* @__PURE__ */ jsxs6(Island, { name: "cart", className: ["sk-cart", className].filter(Boolean).join(" "), children: [
423
+ /* @__PURE__ */ jsx7("h2", { className: "sk-cart__heading", children: "Your cart" }),
424
+ /* @__PURE__ */ jsx7("p", { className: "sk-cart__placeholder", children: "Your cart needs JavaScript to load." })
425
+ ] });
426
+ }
427
+
428
+ // src/checkout-block.tsx
429
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
430
+ function Checkout({ className }) {
431
+ return /* @__PURE__ */ jsxs7(Island, { name: "checkout", className: ["sk-checkout", className].filter(Boolean).join(" "), children: [
432
+ /* @__PURE__ */ jsx8("h2", { className: "sk-checkout__heading", children: "Checkout" }),
433
+ /* @__PURE__ */ jsx8("p", { className: "sk-checkout__placeholder", children: "Checkout needs JavaScript to load." })
434
+ ] });
435
+ }
436
+
437
+ // src/index.ts
438
+ var WEBSITE_KIT_VERSION = "0.1.0";
439
+ export {
440
+ BPOINT_SCRIPT_ORIGIN,
441
+ BPOINT_SCRIPT_URL,
442
+ BookingBlock,
443
+ Button,
444
+ CONSULT_CHUNK_PATH,
445
+ Cart,
446
+ CertificateFunnel,
447
+ Checkout,
448
+ Container,
449
+ FAQ,
450
+ Hero,
451
+ Hours,
452
+ ISLANDS,
453
+ ISLAND_ATTR,
454
+ ISLAND_PROPS_ATTR,
455
+ Island,
456
+ MapEmbed,
457
+ PortalAccount,
458
+ PortalPanel,
459
+ Row,
460
+ Section,
461
+ ServiceGrid,
462
+ Stack,
463
+ Store,
464
+ THIRD_PARTY_SCRIPT_ORIGINS,
465
+ TURNSTILE_SCRIPT_ORIGIN,
466
+ TURNSTILE_SCRIPT_URL,
467
+ TeamGrid,
468
+ ThemeProvider,
469
+ WEBSITE_KIT_VERSION,
470
+ WebsiteShell,
471
+ contrastRatio,
472
+ defaultTheme,
473
+ deriveForeground,
474
+ evaluateShowWhen,
475
+ mountIslands,
476
+ mountIslandsWith,
477
+ parseHexColor,
478
+ patientSurfaceTheme,
479
+ readIslandProps,
480
+ relativeLuminance,
481
+ resolveTheme,
482
+ themeToCssVars,
483
+ visibleQuestions
484
+ };
@@ -0,0 +1,68 @@
1
+ import * as React from 'react';
2
+ import { b as BookingBlockProps, h as CertificateFunnelProps, n as StoreProps, e as CartProps, k as CheckoutProps } from './checkout-block-CrO7OxI9.js';
3
+ import { f as PortalPanelProps } from './portal-account-EhOtLV5u.js';
4
+ import { F as FetchLike } from './portal-account.client-CTget1-3.js';
5
+ export { P as PortalAccountClient } from './portal-account.client-CTget1-3.js';
6
+
7
+ declare function BookingBlockClient(props: BookingBlockProps): React.ReactElement;
8
+
9
+ declare function CertificateFunnelClient(props: CertificateFunnelProps): React.ReactElement;
10
+
11
+ type SummaryItem = {
12
+ id: string;
13
+ title?: string | null;
14
+ status?: string | null;
15
+ /** appointments */
16
+ start?: string | null;
17
+ /** documents */
18
+ date?: string | null;
19
+ /** orders */
20
+ placedAt?: string | null;
21
+ total?: string | null;
22
+ currency?: string | null;
23
+ locationName?: string | null;
24
+ practitionerName?: string | null;
25
+ };
26
+ /**
27
+ * Everything the panel can be showing. A discriminated union, derived once by
28
+ * `loadPortalPanel`, so the render is a pure function of state — and so the whole
29
+ * behaviour is testable without a DOM.
30
+ */
31
+ type PortalPanelState = {
32
+ kind: 'loading';
33
+ }
34
+ /** No patient session on this host — show the inline magic-link ramp. */
35
+ | {
36
+ kind: 'signed-out';
37
+ }
38
+ /** A link was requested; we never say whether the address matched an account. */
39
+ | {
40
+ kind: 'link-sent';
41
+ email: string;
42
+ } | {
43
+ kind: 'signed-in';
44
+ givenName: string | null;
45
+ items: SummaryItem[];
46
+ }
47
+ /**
48
+ * The panel cannot answer here: no portal on this host (404), the surface is off, or
49
+ * the network failed. Degrades to a plain link out — never an error dump.
50
+ */
51
+ | {
52
+ kind: 'link-out';
53
+ };
54
+ type PortalPanelClientProps = PortalPanelProps & {
55
+ /** Test seam: the initial state, so the view is renderable without effects. */
56
+ initialState?: PortalPanelState;
57
+ /** Test seam: the fetch used for whoami/summary/magic-link. Defaults to global. */
58
+ fetchImpl?: FetchLike;
59
+ };
60
+ declare function PortalPanelClient({ surface, portalUrl, initialState, fetchImpl, }: PortalPanelClientProps): React.ReactElement;
61
+
62
+ declare function StoreClient({ categoryHandle, title, columns }: StoreProps): React.ReactElement;
63
+
64
+ declare function CartClient(_props: CartProps): React.ReactElement;
65
+
66
+ declare function CheckoutClient(_props: CheckoutProps): React.ReactElement;
67
+
68
+ export { BookingBlockClient, CartClient, CertificateFunnelClient, CheckoutClient, PortalPanelClient, StoreClient };
@@ -0,0 +1,23 @@
1
+ import {
2
+ BookingBlockClient,
3
+ CartClient,
4
+ CertificateFunnelClient,
5
+ CheckoutClient,
6
+ PortalAccountClient,
7
+ PortalPanelClient,
8
+ StoreClient
9
+ } from "./chunk-TOPPLOH2.js";
10
+ import "./chunk-ZOF22TJA.js";
11
+ import "./chunk-QDO3SJWR.js";
12
+ import "./chunk-THMT43MV.js";
13
+ import "./chunk-G3U6EXJM.js";
14
+ import "./chunk-MLKGABMK.js";
15
+ export {
16
+ BookingBlockClient,
17
+ CartClient,
18
+ CertificateFunnelClient,
19
+ CheckoutClient,
20
+ PortalAccountClient,
21
+ PortalPanelClient,
22
+ StoreClient
23
+ };
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+ import { IslandRegistry } from './islands.js';
3
+
4
+ /** island name → implementation. THE allow-list. */
5
+ declare const ISLANDS: IslandRegistry;
6
+ type IslandName = keyof typeof ISLANDS;
7
+ /**
8
+ * Mount every island marker in `root` against the WHOLE registry.
9
+ *
10
+ * A convenience binding of `mountIslandsWith` for callers that want the full set (the
11
+ * app, tests). Published sites use the bundler's pruned per-site registry instead.
12
+ */
13
+ declare function mountIslands(root: ParentNode, createRoot: (el: Element) => {
14
+ render: (node: React.ReactNode) => void;
15
+ }): number;
16
+
17
+ export { ISLANDS, type IslandName, mountIslands };
@@ -0,0 +1,14 @@
1
+ import {
2
+ ISLANDS,
3
+ mountIslands
4
+ } from "./chunk-Q4URDZAK.js";
5
+ import "./chunk-TOPPLOH2.js";
6
+ import "./chunk-ZOF22TJA.js";
7
+ import "./chunk-QDO3SJWR.js";
8
+ import "./chunk-THMT43MV.js";
9
+ import "./chunk-G3U6EXJM.js";
10
+ import "./chunk-MLKGABMK.js";
11
+ export {
12
+ ISLANDS,
13
+ mountIslands
14
+ };
@@ -0,0 +1,57 @@
1
+ import * as React from 'react';
2
+
3
+ /** Attribute carrying the island name on a build-emitted mount point. */
4
+ declare const ISLAND_ATTR = "data-pos-island";
5
+ /** Attribute carrying the island's JSON-serialised props. */
6
+ declare const ISLAND_PROPS_ATTR = "data-pos-island-props";
7
+ /** name → implementation. The per-site registries the bundler emits have this shape. */
8
+ type IslandRegistry = Record<string, React.ComponentType<never>>;
9
+ interface IslandProps {
10
+ /** Registry key. Must exist in the mounting registry or the marker mounts nothing. */
11
+ name: string;
12
+ /** JSON-safe configuration handed to the implementation at mount time. */
13
+ props?: Record<string, unknown>;
14
+ /**
15
+ * Static content rendered INSIDE the mount point at build time. It ships in the
16
+ * prerendered HTML and is replaced when the island mounts, so it is the right
17
+ * home for a no-JS fallback ("call the clinic to book") or a skeleton — the page
18
+ * is SEO-indexed and read by clients that never run the island.
19
+ */
20
+ children?: React.ReactNode;
21
+ className?: string;
22
+ /**
23
+ * Inline style on the mount point, resolved at BUILD time. Its only sanctioned use
24
+ * is the patient-surface theme vars (`patientSurfaceTheme`): the island's CSS reads
25
+ * them off its own mount point, so the clinic's brand reaches our components without
26
+ * the clinic being able to restyle them. Not a general styling hook.
27
+ */
28
+ style?: React.CSSProperties;
29
+ }
30
+ /**
31
+ * The build-time half of an island. Renders an empty, inert mount point.
32
+ *
33
+ * Clinic pages never use this directly — each island exports its own thin wrapper
34
+ * (e.g. `BookingBlock`) so the clinic writes a named component with typed props.
35
+ */
36
+ declare function Island({ name, props, children, className, style, }: IslandProps): React.ReactElement;
37
+ /** Parse a marker's props attribute. A malformed value yields `{}`, never a throw. */
38
+ declare function readIslandProps(el: Element): Record<string, unknown>;
39
+ /**
40
+ * Mount every island marker in `root` using the given registry. Called by the
41
+ * bundler-generated client entry; this is the entire client-side lifecycle of a
42
+ * published clinic site.
43
+ *
44
+ * Deliberately TOTAL: an unknown island name or a bad props blob is skipped with a
45
+ * console warning, never thrown. A published brochure must not white-screen because one
46
+ * island was renamed or because this build's registry was pruned to the islands its
47
+ * pages mount — the rest of the page is static HTML that is already correct on screen.
48
+ *
49
+ * `createRoot` (not `hydrateRoot`): the marker ships EMPTY of island output, so there is
50
+ * no server-rendered tree to reconcile against. Any `children` fallback is replaced on
51
+ * mount, which is the intent.
52
+ */
53
+ declare function mountIslandsWith(registry: IslandRegistry, root: ParentNode, createRoot: (el: Element) => {
54
+ render: (node: React.ReactNode) => void;
55
+ }): number;
56
+
57
+ export { ISLAND_ATTR, ISLAND_PROPS_ATTR, Island, type IslandProps, type IslandRegistry, mountIslandsWith, readIslandProps };
@@ -0,0 +1,15 @@
1
+ import {
2
+ ISLAND_ATTR,
3
+ ISLAND_PROPS_ATTR,
4
+ Island,
5
+ mountIslandsWith,
6
+ readIslandProps
7
+ } from "./chunk-THMT43MV.js";
8
+ import "./chunk-MLKGABMK.js";
9
+ export {
10
+ ISLAND_ATTR,
11
+ ISLAND_PROPS_ATTR,
12
+ Island,
13
+ mountIslandsWith,
14
+ readIslandProps
15
+ };