@patientos/website-kit 0.2.5 → 0.2.7

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 (43) hide show
  1. package/README.md +86 -0
  2. package/dist/{checkout-block-Cog4H4I4.d.ts → booking-block-D-UOAPx3.d.ts} +1 -49
  3. package/dist/cart-block-B-ytc9us.d.ts +20 -0
  4. package/dist/cart-button-BCLFittB.d.ts +24 -0
  5. package/dist/cart-storage.d.ts +110 -0
  6. package/dist/cart-storage.js +39 -0
  7. package/dist/checkout-block-BeXwpqYe.d.ts +24 -0
  8. package/dist/chunk-2CZFJOKK.js +79 -0
  9. package/dist/{chunk-FSZ726DM.js → chunk-3T7UWKAH.js} +4 -308
  10. package/dist/{chunk-75FJ5AJB.js → chunk-4GKSSM5N.js} +578 -1608
  11. package/dist/chunk-4NF7SSIX.js +367 -0
  12. package/dist/chunk-673YEXAI.js +164 -0
  13. package/dist/chunk-7BUDUYXN.js +80 -0
  14. package/dist/chunk-FZ4WKWIE.js +310 -0
  15. package/dist/{chunk-7UPUEMXX.js → chunk-HYXTDUP2.js} +20 -5
  16. package/dist/chunk-IOC6QLB7.js +159 -0
  17. package/dist/chunk-S3ZQQOQT.js +2057 -0
  18. package/dist/chunk-WQSJ46TP.js +19 -0
  19. package/dist/chunk-ZNH6TSYP.js +167 -0
  20. package/dist/index.d.ts +11 -4
  21. package/dist/index.js +120 -33
  22. package/dist/islands-impl/cart-button.d.ts +19 -0
  23. package/dist/islands-impl/cart-button.js +13 -0
  24. package/dist/islands-impl/cart.d.ts +31 -0
  25. package/dist/islands-impl/cart.js +11 -0
  26. package/dist/islands-impl/checkout.d.ts +6 -0
  27. package/dist/islands-impl/checkout.js +13 -0
  28. package/dist/islands-impl/store.d.ts +22 -0
  29. package/dist/islands-impl/store.js +14 -0
  30. package/dist/islands-impl.d.ts +14 -10
  31. package/dist/islands-impl.js +22 -7
  32. package/dist/islands-registry.js +13 -4
  33. package/dist/{portal-account-8uQc_Ncx.d.ts → portal-account-DRJyr3nz.d.ts} +1 -82
  34. package/dist/{portal-account.client-BBe_q9yC.d.ts → portal-account.client-Bke4NsgW.d.ts} +2 -1
  35. package/dist/portal-booking-client.d.ts +3 -2
  36. package/dist/portal-booking-client.js +2 -1
  37. package/dist/portal-client-my3q5GME.d.ts +83 -0
  38. package/dist/store-block-CFxnsfKU.d.ts +37 -0
  39. package/dist/store-catalog-p7TFOpfk.d.ts +78 -0
  40. package/dist/store-catalog.d.ts +3 -0
  41. package/dist/store-catalog.js +11 -0
  42. package/dist/website-kit.css +196 -1
  43. package/package.json +31 -1
@@ -1,12 +1,21 @@
1
1
  import {
2
2
  ISLANDS,
3
3
  mountIslands
4
- } from "./chunk-7UPUEMXX.js";
5
- import "./chunk-75FJ5AJB.js";
6
- import "./chunk-ZOF22TJA.js";
4
+ } from "./chunk-HYXTDUP2.js";
5
+ import "./chunk-4GKSSM5N.js";
6
+ import "./chunk-3T7UWKAH.js";
7
7
  import "./chunk-QDO3SJWR.js";
8
8
  import "./chunk-THMT43MV.js";
9
- import "./chunk-FSZ726DM.js";
9
+ import "./chunk-2CZFJOKK.js";
10
+ import "./chunk-ZNH6TSYP.js";
11
+ import "./chunk-IOC6QLB7.js";
12
+ import "./chunk-S3ZQQOQT.js";
13
+ import "./chunk-ZOF22TJA.js";
14
+ import "./chunk-673YEXAI.js";
15
+ import "./chunk-WQSJ46TP.js";
16
+ import "./chunk-7BUDUYXN.js";
17
+ import "./chunk-4NF7SSIX.js";
18
+ import "./chunk-FZ4WKWIE.js";
10
19
  import "./chunk-MLKGABMK.js";
11
20
  export {
12
21
  ISLANDS,
@@ -1,86 +1,5 @@
1
- import { ApplicationRun } from '@patientos/public-sdk';
2
1
  import * as React from 'react';
3
2
 
4
- /** The injectable browser transport. */
5
- type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
6
- interface PortalClientConfig {
7
- /** The validated app API origin, e.g. https://my.pocketdoc.com.au. */
8
- apiOrigin?: string | null;
9
- /** The app origin used only to verify whether relative portal API calls belong here. */
10
- portalOrigin?: string | null;
11
- /** Where to send a patient who cannot use the client at all. */
12
- portalUrl?: string | null;
13
- /** Injectable for tests. */
14
- fetchImpl?: FetchLike;
15
- }
16
- /** Who the app says is signed in on this origin. */
17
- type WhoAmI = {
18
- signedIn: false;
19
- } | {
20
- signedIn: true;
21
- givenName: string;
22
- familyName: string;
23
- email: string;
24
- };
25
- /** The patient's own details, for prefill. Every field is independently nullable. */
26
- interface PortalPrefill {
27
- givenName: string | null;
28
- familyName: string | null;
29
- email: string | null;
30
- phone: string | null;
31
- dateOfBirth: string | null;
32
- }
33
- /** A claim token minted for the signed-in patient's corroborated-identity path. */
34
- interface PublicClaim {
35
- contactId: string;
36
- claimToken: string;
37
- expiresAt: string;
38
- }
39
- /** A portal call is always a value, never a thrown transport failure. */
40
- type PortalResult<T> = {
41
- ok: true;
42
- data: T;
43
- } | {
44
- ok: false;
45
- status: number | null;
46
- error: string | null;
47
- body?: unknown;
48
- };
49
- interface PortalClient {
50
- /** Whether this client can honestly offer an inline portal interaction. */
51
- readonly available: boolean;
52
- /** The validated app origin, or '' for relative same-origin calls. */
53
- readonly apiOrigin: string;
54
- /** The configured outbound fallback, or ''. */
55
- readonly portalHref: string;
56
- /** Resolve a constant path against this client's one configured origin. */
57
- url(path: string): string;
58
- /** The transport bound when this client was created; injectable for tests. */
59
- readonly fetch: FetchLike;
60
- /** Who is signed in; unhealthy/unavailable is indistinguishable from signed out. */
61
- whoAmI(): Promise<WhoAmI>;
62
- /** Ask for an oracle-free magic link. */
63
- requestMagicLink(email: string, redirect?: string): Promise<boolean>;
64
- /** Read the signed-in patient's details. */
65
- prefill(): Promise<PortalPrefill | null>;
66
- /** Mint a public claim only for the signed-in patient. */
67
- mintPublicClaim(): Promise<PublicClaim | null>;
68
- /** Redeem a single-use magic-link continuation after patient sign-in. */
69
- redeemApplicationRunHandoff(handoffId: string): Promise<{
70
- run: ApplicationRun;
71
- capabilityToken: string;
72
- } | null>;
73
- /** Page callback, using the app-side return hop when cross-origin. */
74
- returnPath(targetUrl?: string): string;
75
- /** Back-compat account-surface transport; callers still use constant paths. */
76
- get<T>(path: string): Promise<PortalResult<T>>;
77
- /** Back-compat account-surface JSON write; callers still use constant paths. */
78
- send<T>(path: string, method: 'POST' | 'PUT', body: unknown): Promise<PortalResult<T>>;
79
- /** Back-compat account-surface body-less delete. */
80
- delete<T>(path: string): Promise<PortalResult<T>>;
81
- }
82
- declare function createPortalClient(config?: PortalClientConfig): PortalClient;
83
-
84
3
  /** Which portal surface this panel shows. */
85
4
  type PortalSurface = 'appointments' | 'documents' | 'orders' | 'profile';
86
5
  /** JSON-safe island configuration. Crosses the build→runtime boundary — see `./islands`. */
@@ -212,4 +131,4 @@ type PortalAccountMarkerProps = PortalAccountProps & {
212
131
  */
213
132
  declare function PortalAccount({ surface, portalUrl, bookHref, appointmentsHref, funnelHref, portalApiOrigin, className, }: PortalAccountMarkerProps): React.ReactElement;
214
133
 
215
- export { type FetchLike as F, type PortalClient as P, type WhoAmI as W, type PortalPrefill as a, type PublicClaim as b, PortalAccount as c, type PortalAccountMarkerProps as d, type PortalAccountProps as e, type PortalAccountSurface as f, type PortalClientConfig as g, PortalPanel as h, type PortalPanelMarkerProps as i, type PortalPanelProps as j, type PortalResult as k, type PortalSurface as l, createPortalClient as m };
134
+ export { PortalAccount as P, type PortalAccountMarkerProps as a, type PortalAccountProps as b, type PortalAccountSurface as c, PortalPanel as d, type PortalPanelMarkerProps as e, type PortalPanelProps as f, type PortalSurface as g };
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- import { e as PortalAccountProps, F as FetchLike } from './portal-account-8uQc_Ncx.js';
2
+ import { F as FetchLike } from './portal-client-my3q5GME.js';
3
+ import { b as PortalAccountProps } from './portal-account-DRJyr3nz.js';
3
4
 
4
5
  type PortalAddress = {
5
6
  id: string;
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
- import { F as FetchLike, k as PortalResult } from './portal-account-8uQc_Ncx.js';
3
- import { a as PortalAppointment, b as PortalAppointmentsBody } from './portal-account.client-BBe_q9yC.js';
2
+ import { F as FetchLike, d as PortalResult } from './portal-client-my3q5GME.js';
3
+ import { a as PortalAppointment, b as PortalAppointmentsBody } from './portal-account.client-Bke4NsgW.js';
4
4
  import '@patientos/public-sdk';
5
+ import './portal-account-DRJyr3nz.js';
5
6
 
6
7
  /** One bookable instant, collapsing the practitioners free at the same time. */
7
8
  interface TimeSlot {
@@ -22,7 +22,8 @@ import {
22
22
  releasePortalHold,
23
23
  rescheduleContextTypeId,
24
24
  reschedulePortalAppointmentAndReload
25
- } from "./chunk-FSZ726DM.js";
25
+ } from "./chunk-3T7UWKAH.js";
26
+ import "./chunk-FZ4WKWIE.js";
26
27
  import "./chunk-MLKGABMK.js";
27
28
  export {
28
29
  AVAILABILITY_FAILED_COPY,
@@ -0,0 +1,83 @@
1
+ import { ApplicationRun } from '@patientos/public-sdk';
2
+
3
+ /** The injectable browser transport. */
4
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
5
+ interface PortalClientConfig {
6
+ /** The validated app API origin, e.g. https://my.pocketdoc.com.au. */
7
+ apiOrigin?: string | null;
8
+ /** The app origin used only to verify whether relative portal API calls belong here. */
9
+ portalOrigin?: string | null;
10
+ /** Where to send a patient who cannot use the client at all. */
11
+ portalUrl?: string | null;
12
+ /** Injectable for tests. */
13
+ fetchImpl?: FetchLike;
14
+ }
15
+ /** Who the app says is signed in on this origin. */
16
+ type WhoAmI = {
17
+ signedIn: false;
18
+ } | {
19
+ signedIn: true;
20
+ givenName: string;
21
+ familyName: string;
22
+ email: string;
23
+ };
24
+ /** The patient's own details, for prefill. Every field is independently nullable. */
25
+ interface PortalPrefill {
26
+ givenName: string | null;
27
+ familyName: string | null;
28
+ email: string | null;
29
+ phone: string | null;
30
+ dateOfBirth: string | null;
31
+ }
32
+ /** A claim token minted for the signed-in patient's corroborated-identity path. */
33
+ interface PublicClaim {
34
+ contactId: string;
35
+ claimToken: string;
36
+ expiresAt: string;
37
+ }
38
+ /** A portal call is always a value, never a thrown transport failure. */
39
+ type PortalResult<T> = {
40
+ ok: true;
41
+ data: T;
42
+ } | {
43
+ ok: false;
44
+ status: number | null;
45
+ error: string | null;
46
+ body?: unknown;
47
+ };
48
+ interface PortalClient {
49
+ /** Whether this client can honestly offer an inline portal interaction. */
50
+ readonly available: boolean;
51
+ /** The validated app origin, or '' for relative same-origin calls. */
52
+ readonly apiOrigin: string;
53
+ /** The configured outbound fallback, or ''. */
54
+ readonly portalHref: string;
55
+ /** Resolve a constant path against this client's one configured origin. */
56
+ url(path: string): string;
57
+ /** The transport bound when this client was created; injectable for tests. */
58
+ readonly fetch: FetchLike;
59
+ /** Who is signed in; unhealthy/unavailable is indistinguishable from signed out. */
60
+ whoAmI(): Promise<WhoAmI>;
61
+ /** Ask for an oracle-free magic link. */
62
+ requestMagicLink(email: string, redirect?: string): Promise<boolean>;
63
+ /** Read the signed-in patient's details. */
64
+ prefill(): Promise<PortalPrefill | null>;
65
+ /** Mint a public claim only for the signed-in patient. */
66
+ mintPublicClaim(): Promise<PublicClaim | null>;
67
+ /** Redeem a single-use magic-link continuation after patient sign-in. */
68
+ redeemApplicationRunHandoff(handoffId: string): Promise<{
69
+ run: ApplicationRun;
70
+ capabilityToken: string;
71
+ } | null>;
72
+ /** Page callback, using the app-side return hop when cross-origin. */
73
+ returnPath(targetUrl?: string): string;
74
+ /** Back-compat account-surface transport; callers still use constant paths. */
75
+ get<T>(path: string): Promise<PortalResult<T>>;
76
+ /** Back-compat account-surface JSON write; callers still use constant paths. */
77
+ send<T>(path: string, method: 'POST' | 'PUT', body: unknown): Promise<PortalResult<T>>;
78
+ /** Back-compat account-surface body-less delete. */
79
+ delete<T>(path: string): Promise<PortalResult<T>>;
80
+ }
81
+ declare function createPortalClient(config?: PortalClientConfig): PortalClient;
82
+
83
+ export { type FetchLike as F, type PortalClient as P, type WhoAmI as W, type PortalPrefill as a, type PublicClaim as b, type PortalClientConfig as c, type PortalResult as d, createPortalClient as e };
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+
3
+ /** `$12.50` — AUD, GST-inclusive, from the numeric(12,2) string the server sends. */
4
+ declare function formatMoney(value: string): string;
5
+
6
+ /** JSON-safe island configuration. Crosses the build→runtime boundary — see `./islands`. */
7
+ type StoreProps = {
8
+ /** Show one category only. Omitted ⇒ the whole published catalogue. */
9
+ categoryHandle?: string;
10
+ /** Show exactly one product by its stable handle. Takes precedence over category. */
11
+ productHandle?: string;
12
+ /** PatientOS patient/API origin. Omitted keeps the original same-origin contract. */
13
+ storeApiOrigin?: string;
14
+ /** Heading rendered above the grid. */
15
+ title?: string;
16
+ /** Grid columns at the widest breakpoint. */
17
+ columns?: number;
18
+ /**
19
+ * Suppress the block's own inline `Cart (n)` link.
20
+ *
21
+ * For a site that already carries a persistent header cart (`<CartButton/>`) — two
22
+ * cart affordances on one screen is a worse shop, not a more helpful one.
23
+ */
24
+ hideCartLink?: boolean;
25
+ };
26
+ /** Marker-only props: the island config plus presentation the page controls. */
27
+ type StoreMarkerProps = StoreProps & {
28
+ className?: string;
29
+ };
30
+ /**
31
+ * BUILD-TIME marker. Emits the mount point plus a static no-JS fallback — the catalogue
32
+ * itself is fetched at runtime (prices and stock must not be baked into a cached static
33
+ * artifact), so the fallback is a prompt, not a product list.
34
+ */
35
+ declare function Store({ categoryHandle, productHandle, storeApiOrigin, title, columns, hideCartLink, className, }: StoreMarkerProps): React.ReactElement;
36
+
37
+ export { Store as S, type StoreMarkerProps as a, type StoreProps as b, formatMoney as f };
@@ -0,0 +1,78 @@
1
+ import { F as FetchLike } from './portal-client-my3q5GME.js';
2
+
3
+ interface StoreClientConfig {
4
+ /** The validated PatientOS patient/API origin. Empty means same-origin. */
5
+ apiOrigin?: string | null;
6
+ /** Injectable browser transport for tests. */
7
+ fetchImpl?: FetchLike;
8
+ }
9
+ type StoreResult<T> = {
10
+ ok: true;
11
+ data: T;
12
+ } | {
13
+ ok: false;
14
+ status: number | null;
15
+ error: string | null;
16
+ message: string | null;
17
+ };
18
+ interface StoreClient {
19
+ readonly apiOrigin: string;
20
+ url(path: string): string;
21
+ get<T>(path: string): Promise<StoreResult<T>>;
22
+ post<T>(path: string, body: unknown, options?: {
23
+ signal?: AbortSignal;
24
+ }): Promise<StoreResult<T>>;
25
+ /** PatientOS magic-link sign-in that returns to this site's checkout. */
26
+ signInHref(returnPath?: string): string;
27
+ }
28
+ declare function createStoreClient(config?: StoreClientConfig): StoreClient;
29
+
30
+ type StoreVariant = {
31
+ id: string;
32
+ title: string;
33
+ price: string;
34
+ options: string[];
35
+ inStock: boolean;
36
+ };
37
+ type StoreProduct = {
38
+ id: string;
39
+ handle: string;
40
+ title: string;
41
+ description: string | null;
42
+ thumbnailUrl: string | null;
43
+ images: {
44
+ url: string;
45
+ alt: string | null;
46
+ }[];
47
+ categories: {
48
+ handle: string;
49
+ name: string;
50
+ }[];
51
+ gstApplicable: boolean;
52
+ variants: StoreVariant[];
53
+ };
54
+ /** JSON-safe: this is what crosses a route loader into `StoreClient.initialCatalog`. */
55
+ type StoreCatalog = {
56
+ products: StoreProduct[];
57
+ currency: string;
58
+ };
59
+ /** What the two catalogue endpoints answer with. */
60
+ type StoreCatalogResponse = StoreCatalog | {
61
+ product: StoreProduct;
62
+ currency: string;
63
+ };
64
+ /** The exact path the store island reads. Kept here so a loader cannot drift from it. */
65
+ declare function storeCatalogPath(productHandle?: string): string;
66
+ /** Collapse the single-product response into the same shape as the whole catalogue. */
67
+ declare function normalizeCatalogResponse(data: StoreCatalogResponse): StoreCatalog;
68
+ /**
69
+ * Fetch the published catalogue — usable from a SERVER route loader or the browser.
70
+ *
71
+ * Returns `null` on any failure so a loader can render the store island with no seed
72
+ * (it falls back to its own client fetch) rather than failing the page.
73
+ */
74
+ declare function fetchStoreCatalog(client: StoreClient, options?: {
75
+ productHandle?: string;
76
+ }): Promise<StoreCatalog | null>;
77
+
78
+ export { type StoreCatalog as S, type StoreCatalogResponse as a, type StoreClient as b, type StoreClientConfig as c, type StoreProduct as d, type StoreResult as e, type StoreVariant as f, createStoreClient as g, fetchStoreCatalog as h, normalizeCatalogResponse as n, storeCatalogPath as s };
@@ -0,0 +1,3 @@
1
+ export { S as StoreCatalog, a as StoreCatalogResponse, d as StoreProduct, f as StoreVariant, h as fetchStoreCatalog, n as normalizeCatalogResponse, s as storeCatalogPath } from './store-catalog-p7TFOpfk.js';
2
+ import './portal-client-my3q5GME.js';
3
+ import '@patientos/public-sdk';
@@ -0,0 +1,11 @@
1
+ import {
2
+ fetchStoreCatalog,
3
+ normalizeCatalogResponse,
4
+ storeCatalogPath
5
+ } from "./chunk-WQSJ46TP.js";
6
+ import "./chunk-MLKGABMK.js";
7
+ export {
8
+ fetchStoreCatalog,
9
+ normalizeCatalogResponse,
10
+ storeCatalogPath
11
+ };
@@ -2868,7 +2868,9 @@ select.sk-identity__input[data-empty] {
2868
2868
  grid-template-columns: repeat(2, minmax(0, 1fr));
2869
2869
  }
2870
2870
  }
2871
- @media (max-width: 480px) {
2871
+ /* Two catalogue columns fit ordinary 390–430px phones and keep a 54-item shop
2872
+ scannable. Below 380px one column preserves readable product names. */
2873
+ @media (max-width: 379px) {
2872
2874
  .sk-store__grid {
2873
2875
  grid-template-columns: minmax(0, 1fr);
2874
2876
  }
@@ -3581,3 +3583,196 @@ select.sk-identity__input[data-empty] {
3581
3583
  color: #b3261e;
3582
3584
  font-size: var(--sk-font-size-sm);
3583
3585
  }
3586
+
3587
+ /* ── PAT-944: saved delivery addresses + fulfilment notices ──────────────────
3588
+ Appended at the END on purpose (a second agent owns the store blocks in this
3589
+ file's earlier sections). Scoped to the checkout island only. */
3590
+ .sk-checkout__addresses {
3591
+ display: flex;
3592
+ flex-direction: column;
3593
+ gap: calc(var(--sk-space, 0.5rem) * 0.75);
3594
+ }
3595
+ .sk-checkout__addresses-label {
3596
+ font-size: var(--sk-font-size-sm);
3597
+ opacity: 0.8;
3598
+ }
3599
+ .sk-checkout__addresses label {
3600
+ display: flex;
3601
+ align-items: flex-start;
3602
+ gap: calc(var(--sk-space, 0.5rem) * 0.75);
3603
+ overflow-wrap: anywhere;
3604
+ }
3605
+ .sk-checkout__address-actions {
3606
+ display: flex;
3607
+ gap: calc(var(--sk-space, 0.5rem) * 1.5);
3608
+ }
3609
+ .sk-checkout__link-button {
3610
+ align-self: flex-start;
3611
+ padding: 0;
3612
+ border: 0;
3613
+ background: transparent;
3614
+ color: var(--sk-primary, inherit);
3615
+ font: inherit;
3616
+ text-decoration: underline;
3617
+ cursor: pointer;
3618
+ }
3619
+ .sk-checkout__link-button[disabled] {
3620
+ opacity: 0.55;
3621
+ cursor: default;
3622
+ }
3623
+ .sk-checkout__notice {
3624
+ margin: 0;
3625
+ font-size: var(--sk-font-size-sm);
3626
+ }
3627
+
3628
+ /* ══════════════════════════════════════════════════════════════════════════════
3629
+ PAT-944 — store skeleton + the persistent header cart (badge + drawer)
3630
+ ══════════════════════════════════════════════════════════════════════════════ */
3631
+
3632
+ /* ── store: the loading skeleton ───────────────────────────────────────────────
3633
+ A fixed-height grid of card-shaped placeholders. The old "Loading the shop…"
3634
+ line collapsed to one text row and then expanded into a grid — a full-page jump
3635
+ on every load. These cards are the same box the real ones occupy. */
3636
+ .sk-store__placeholder--sr {
3637
+ position: absolute;
3638
+ width: 1px;
3639
+ height: 1px;
3640
+ padding: 0;
3641
+ margin: -1px;
3642
+ overflow: hidden;
3643
+ clip-path: inset(50%);
3644
+ white-space: nowrap;
3645
+ border: 0;
3646
+ }
3647
+ .sk-store__card--skeleton {
3648
+ pointer-events: none;
3649
+ }
3650
+ .sk-store__skeleton-line,
3651
+ .sk-store__skeleton-button {
3652
+ display: block;
3653
+ border-radius: var(--sk-radius, 0.625rem);
3654
+ background: color-mix(in srgb, var(--sk-muted, #5b6b7a) 16%, transparent);
3655
+ }
3656
+ .sk-store__skeleton-line {
3657
+ height: 0.875rem;
3658
+ }
3659
+ .sk-store__skeleton-line--short {
3660
+ width: 45%;
3661
+ }
3662
+ .sk-store__skeleton-button {
3663
+ margin-top: auto;
3664
+ height: 2.5rem;
3665
+ }
3666
+ @media (prefers-reduced-motion: no-preference) {
3667
+ .sk-store__card--skeleton .sk-store__image,
3668
+ .sk-store__skeleton-line,
3669
+ .sk-store__skeleton-button {
3670
+ animation: sk-skeleton-pulse 1.4s ease-in-out infinite;
3671
+ }
3672
+ }
3673
+ @keyframes sk-skeleton-pulse {
3674
+ 0%,
3675
+ 100% {
3676
+ opacity: 1;
3677
+ }
3678
+ 50% {
3679
+ opacity: 0.55;
3680
+ }
3681
+ }
3682
+
3683
+ /* ── the header cart button ──────────────────────────────────────────────────── */
3684
+ .sk-cart-button {
3685
+ display: inline-flex;
3686
+ }
3687
+ .sk-cart-button__trigger {
3688
+ display: inline-flex;
3689
+ align-items: center;
3690
+ gap: calc(var(--sk-space, 0.5rem) * 1);
3691
+ padding: calc(var(--sk-space, 0.5rem) * 0.75) calc(var(--sk-space, 0.5rem) * 1.5);
3692
+ border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
3693
+ border-radius: var(--sk-radius, 0.625rem);
3694
+ background: transparent;
3695
+ color: inherit;
3696
+ font: inherit;
3697
+ font-weight: 600;
3698
+ text-decoration: none;
3699
+ cursor: pointer;
3700
+ }
3701
+ .sk-cart-button__trigger:hover {
3702
+ background: color-mix(in srgb, var(--sk-muted, #5b6b7a) 10%, transparent);
3703
+ }
3704
+ /* The badge is ABSENT at zero — the component never renders "0" — so this only ever
3705
+ paints once the client effect has read the shopper's own stored cart. */
3706
+ .sk-cart-button__badge {
3707
+ display: inline-flex;
3708
+ align-items: center;
3709
+ justify-content: center;
3710
+ min-width: 1.375rem;
3711
+ height: 1.375rem;
3712
+ padding: 0 0.3125rem;
3713
+ border-radius: 999px;
3714
+ background: var(--sk-brand, #1f6feb);
3715
+ color: var(--sk-brand-foreground, #fff);
3716
+ font-size: 0.75rem;
3717
+ font-weight: 700;
3718
+ font-variant-numeric: tabular-nums;
3719
+ line-height: 1;
3720
+ }
3721
+
3722
+ /* ── the cart drawer ──────────────────────────────────────────────────────────
3723
+ Rendered only while open, so a closed drawer costs no markup and — the point of
3724
+ the whole design — no network. */
3725
+ .sk-cart-drawer {
3726
+ position: fixed;
3727
+ inset: 0;
3728
+ z-index: 60;
3729
+ display: flex;
3730
+ justify-content: flex-end;
3731
+ }
3732
+ .sk-cart-drawer__scrim {
3733
+ position: absolute;
3734
+ inset: 0;
3735
+ background: rgb(0 0 0 / 0.4);
3736
+ }
3737
+ .sk-cart-drawer__panel {
3738
+ position: relative;
3739
+ display: flex;
3740
+ flex-direction: column;
3741
+ width: min(26rem, 100%);
3742
+ max-height: 100%;
3743
+ background: var(--sk-background, #fff);
3744
+ color: var(--sk-foreground, inherit);
3745
+ box-shadow: -12px 0 32px rgb(0 0 0 / 0.18);
3746
+ }
3747
+ .sk-cart-drawer__head,
3748
+ .sk-cart-drawer__foot {
3749
+ display: flex;
3750
+ align-items: center;
3751
+ justify-content: space-between;
3752
+ gap: calc(var(--sk-space, 0.5rem) * 2);
3753
+ padding: calc(var(--sk-space, 0.5rem) * 2);
3754
+ border-bottom: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 20%, transparent);
3755
+ }
3756
+ .sk-cart-drawer__foot {
3757
+ border-bottom: 0;
3758
+ border-top: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 20%, transparent);
3759
+ margin-top: auto;
3760
+ }
3761
+ .sk-cart-drawer__body {
3762
+ flex: 1 1 auto;
3763
+ overflow-y: auto;
3764
+ padding: calc(var(--sk-space, 0.5rem) * 2);
3765
+ }
3766
+ .sk-cart-drawer__close {
3767
+ border: 0;
3768
+ background: transparent;
3769
+ color: inherit;
3770
+ font: inherit;
3771
+ text-decoration: underline;
3772
+ cursor: pointer;
3773
+ }
3774
+ @media (max-width: 30rem) {
3775
+ .sk-cart-drawer__panel {
3776
+ width: 100%;
3777
+ }
3778
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patientos/website-kit",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "PatientOS clinic website components and patient-facing interactive islands.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -45,6 +45,36 @@
45
45
  "types": "./dist/islands.d.ts",
46
46
  "default": "./dist/islands.js"
47
47
  },
48
+ "./islands-impl/store": {
49
+ "patientos-source": "./src/store-block.client.tsx",
50
+ "types": "./dist/islands-impl/store.d.ts",
51
+ "default": "./dist/islands-impl/store.js"
52
+ },
53
+ "./islands-impl/cart": {
54
+ "patientos-source": "./src/cart-block.client.tsx",
55
+ "types": "./dist/islands-impl/cart.d.ts",
56
+ "default": "./dist/islands-impl/cart.js"
57
+ },
58
+ "./islands-impl/cart-button": {
59
+ "patientos-source": "./src/cart-button.client.tsx",
60
+ "types": "./dist/islands-impl/cart-button.d.ts",
61
+ "default": "./dist/islands-impl/cart-button.js"
62
+ },
63
+ "./islands-impl/checkout": {
64
+ "patientos-source": "./src/checkout-block.client.tsx",
65
+ "types": "./dist/islands-impl/checkout.d.ts",
66
+ "default": "./dist/islands-impl/checkout.js"
67
+ },
68
+ "./cart-storage": {
69
+ "patientos-source": "./src/cart-storage.ts",
70
+ "types": "./dist/cart-storage.d.ts",
71
+ "default": "./dist/cart-storage.js"
72
+ },
73
+ "./store-catalog": {
74
+ "patientos-source": "./src/store-catalog.ts",
75
+ "types": "./dist/store-catalog.d.ts",
76
+ "default": "./dist/store-catalog.js"
77
+ },
48
78
  "./islands-registry": {
49
79
  "patientos-source": "./src/islands-registry.tsx",
50
80
  "types": "./dist/islands-registry.d.ts",