@zappie/foundations 0.1.0 → 0.1.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 (74) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +30 -253
  3. package/dist/account/index.js +49 -0
  4. package/dist/account/index.js.map +7 -0
  5. package/dist/cart/index.js +168 -0
  6. package/dist/cart/index.js.map +7 -0
  7. package/dist/fulfilment/address-picker.js +168 -0
  8. package/dist/fulfilment/address-picker.js.map +7 -0
  9. package/dist/fulfilment/branch-picker.js +54 -0
  10. package/dist/fulfilment/branch-picker.js.map +7 -0
  11. package/dist/fulfilment/schedule.js +146 -0
  12. package/dist/fulfilment/schedule.js.map +7 -0
  13. package/dist/fulfilment/switch.js +54 -0
  14. package/dist/fulfilment/switch.js.map +7 -0
  15. package/dist/index.d.ts +2 -4
  16. package/dist/index.js +49 -2113
  17. package/dist/index.js.map +7 -1
  18. package/dist/internal/combobox.js +131 -0
  19. package/dist/internal/combobox.js.map +7 -0
  20. package/dist/internal/data-attrs.js +8 -0
  21. package/dist/internal/data-attrs.js.map +7 -0
  22. package/dist/internal/ids.js +10 -0
  23. package/dist/internal/ids.js.map +7 -0
  24. package/dist/{chunk-D3J7S33L.js → internal/slot.js} +13 -6
  25. package/dist/internal/slot.js.map +7 -0
  26. package/dist/internal/use-controllable-state.js +29 -0
  27. package/dist/internal/use-controllable-state.js.map +7 -0
  28. package/dist/item-form/index.js +13 -0
  29. package/dist/item-form/index.js.map +7 -0
  30. package/dist/item-form/option-group.js +268 -0
  31. package/dist/item-form/option-group.js.map +7 -0
  32. package/dist/item-form/options.js +30 -0
  33. package/dist/item-form/options.js.map +7 -0
  34. package/dist/item-form/quantity.js +131 -0
  35. package/dist/item-form/quantity.js.map +7 -0
  36. package/dist/item-form/root.js +151 -0
  37. package/dist/item-form/root.js.map +7 -0
  38. package/dist/media/image.js +30 -0
  39. package/dist/media/image.js.map +7 -0
  40. package/dist/money.js +20 -0
  41. package/dist/money.js.map +7 -0
  42. package/dist/next.js +8 -6
  43. package/dist/next.js.map +7 -1
  44. package/dist/otp/input.js +110 -0
  45. package/dist/otp/input.js.map +7 -0
  46. package/dist/overlay/engine.js +16 -8
  47. package/dist/overlay/engine.js.map +7 -1
  48. package/dist/{chunk-BY2WQGLO.js → overlay/history.js} +9 -4
  49. package/dist/overlay/history.js.map +7 -0
  50. package/dist/overlay/react.js +261 -0
  51. package/dist/overlay/react.js.map +7 -0
  52. package/dist/payment/methods.js +103 -0
  53. package/dist/payment/methods.js.map +7 -0
  54. package/dist/rich-text/banners.js +9 -0
  55. package/dist/rich-text/banners.js.map +7 -0
  56. package/dist/rich-text/browser.js +3 -3
  57. package/dist/rich-text/browser.js.map +7 -1
  58. package/dist/rich-text/core.js +75 -0
  59. package/dist/rich-text/core.js.map +7 -0
  60. package/dist/rich-text/index.js +3 -79
  61. package/dist/rich-text/index.js.map +7 -1
  62. package/dist/search/engine.js +93 -0
  63. package/dist/search/engine.js.map +7 -0
  64. package/dist/search/index.js +175 -0
  65. package/dist/search/index.js.map +7 -0
  66. package/dist/status/hours.js +177 -0
  67. package/dist/status/hours.js.map +7 -0
  68. package/dist/status/open-status.js +45 -0
  69. package/dist/status/open-status.js.map +7 -0
  70. package/dist/version.js +6 -0
  71. package/dist/version.js.map +7 -0
  72. package/package.json +5 -4
  73. package/dist/chunk-BY2WQGLO.js.map +0 -1
  74. package/dist/chunk-D3J7S33L.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # @zappie/foundations
2
+
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d3c5f98: Keep each runtime module separate so your app can split package code across routes. Check and verify accept compatible package updates without pinning you to one patch version.
8
+ - Updated dependencies [d3c5f98]
9
+ - @zappie/sdk@0.1.2
10
+
11
+ ## 0.1.1
12
+
13
+ ### Patch Changes
14
+
15
+ - 38f23ac: `VERSION` (`@zappie/sdk`) and `FOUNDATIONS_VERSION` (`@zappie/foundations`) report the installed version. In 0.1.0 they said `"0.0.0"`. Verify reports now carry the real `verifyVersion`, and the test-mode debug channel the real SDK version.
16
+ - 1e6f420: Each package now has a README written for people installing it from npm: what it does, how to install and use it, its entry points, and links to docs.zappie.ai. `@zappie/kit` gains its first README.
17
+ - Updated dependencies [38f23ac]
18
+ - Updated dependencies [1e6f420]
19
+ - @zappie/sdk@0.1.1
20
+
21
+ ## 0.1.0
22
+
23
+ ### Minor Changes
24
+
25
+ - - `@zappie/tokens`: Publish the semantic storefront token contract, CSS aliases, validation and compilation tools.
26
+ - `@zappie/sdk`: Publish the framework-agnostic storefront client and its React, server, checkout, customer, places, visits and URL entries.
27
+ - `@zappie/foundations`: Publish accessible, unstyled React primitives for storefront catalogue, item, fulfilment and cart experiences.
28
+ - `@zappie/theme-base`: Publish the reusable base storefront theme and its catalogue, item and cart surfaces.
29
+ - `@zappie/kit`: Publish the built component registry consumed by the Zappie CLI.
30
+ - `zappie`: Publish the component CLI with `add`, `list` and `diff`.
31
+
32
+ ### Patch Changes
33
+
34
+ - Updated dependencies
35
+ - @zappie/sdk@0.1.0
package/README.md CHANGED
@@ -1,274 +1,51 @@
1
1
  # @zappie/foundations
2
2
 
3
- Unstyled React commerce primitives for Open Storefronts PRD 05. They use the public SDK; they do not import Next, host UI, internal storefront packages, CSS or icons. React 18.2/19 is a peer. The package remains private until the planned public release.
3
+ Unstyled React parts for storefronts. They handle the behaviour, state and accessibility of commerce UI: overlays, item options, quantities, the cart and its totals, fulfilment, addresses, schedules, search, payment methods, one-time codes, images, money and opening hours. You bring the styles.
4
4
 
5
- ## Composition and state
6
-
7
- Wrap commerce primitives in `ZappieProvider` from `@zappie/sdk/react`. Load/hydrate the store and branch menu using the SDK. Overlays, Quantity, PaymentMethods, Address and OTP can also run without a provider.
8
-
9
- Every part accepts DOM attributes, a render-prop child, or `asChild` with exactly one element (including a render prop returning that element). Input parts accept a render-prop child returning one React element; their input props/ref are composed into that element. DOM handlers compose: the consumer runs first and can `preventDefault()` to cancel the default action. `asChild` preserves the child's handlers/ref. Components and custom children must forward their ref and supplied accessibility props. Use native buttons for button parts; replacing a button with a div makes the consumer responsible for native keyboard activation.
10
-
11
- The default elements have no classes, styles, icons or visible text. `Money` supplies its formatted value. Input values, accessible names from catalogue data and OTP digit labels are semantic defaults. Labels/buttons/help/error copy are supplied by the template. Render-prop state is never spread onto the DOM automatically.
12
-
13
- Controlled inputs use `value`/`onValueChange`; uncontrolled inputs use `defaultValue`. Search and Address use `query`/`onQueryChange`/`defaultQuery`. Overlay state is always the URL, so there is no competing `open` boolean.
14
-
15
- Boolean data attributes are present as empty strings only when true. Parts expose `data-state` and relevant `data-disabled`, `data-invalid`, `data-selected`, `data-sold-out`. Custom classes/styles pass through unchanged. The only global styling performed internally is the overlay engine's page scroll lock while a modal is open, removed when the last modal closes.
16
-
17
- ## Money
18
-
19
- Anatomy: `<Money value={money} />` renders `<data value="450000">…</data>`. Props: branded SDK `Money`, optional `formatOptions`, DOM props. Render props expose `{ formatted }`. No state attributes or keyboard handling.
20
-
21
- ```tsx
22
- <Money value={line.lineSubtotal} formatOptions={{ locale: "en-NG" }} />
23
- ```
24
-
25
- ## Quantity
26
-
27
- Anatomy: `Quantity.Root` → `Decrement`, `Value`, `Increment`. Root accepts value/defaultValue/onValueChange, min (1), max (99), step (1), disabled. Render props expose value/setValue/min/max/step/disabled. `Value` is a text input with spinbutton semantics; the buttons never add their own text. Disabled parts expose `data-disabled`.
28
-
29
- Arrow Up/Down moves one step; Home/End reaches the first/last allowed value. Typed numbers snap to the configured step and bounds. Provide an accessible name for the input and both buttons. For SDK item quantities, use a minimum divisible by the item's step; `Cart.Quantity` applies that rule automatically.
30
-
31
- ```tsx
32
- <Quantity.Root min={1} max={10} onValueChange={setQuantity}>
33
- <Quantity.Decrement aria-label="Decrease quantity" />
34
- <Quantity.Value aria-label="Quantity" />
35
- <Quantity.Increment aria-label="Increase quantity" />
36
- </Quantity.Root>
37
- ```
38
-
39
- ## ItemForm, OptionGroup and Choice
40
-
41
- Anatomy: `ItemForm.Root` → `ItemForm.Options` → `OptionGroup` → `Choice` / `OptionGroup.Input`; `ItemForm.Add` submits the configuration. Root props: item, optional full CartLineInput value/onValueChange, partial defaultValue, defaultQuantity, onAdded. Key the root by item ID when switching items to start a fresh configuration.
42
-
43
- Root/Options render props expose item, effective groups, quantity/setQuantity, value/setValue, unitPrice/total, issues, canAdd, add and status (`incomplete|valid|added`). `add()` returns a CartLine or null, invokes SDK cart validation, and focuses the first invalid group. Add stays actionable for incomplete configurations so it can explain errors. Runtime failures other than cart validation errors propagate to the caller.
44
-
45
- `@zappie/sdk` supplies the item preview: it reuses cart conditional groups, exact variant resolution and estimate math. It remains covered by the SDK core size budget. Preview values are estimates, not validated or authoritative quote totals. The SDK remains the sole owner of commerce validation.
46
-
47
- Groups expose `data-state="valid|invalid"` and `data-invalid`, with radiogroup semantics for one choice and grouped checkboxes otherwise. Choice exposes checked/unchecked, selected, disabled and sold-out. Radio arrows/Home/End select available choices with roving tabindex; Space activates native buttons. Optional single-choice groups can be cleared by activating the selected choice. Input handles text/number/date/time/toggle and informational free responses, enforces maxLength (default 500), and clears mutually exclusive selected options. Choice clears a group's free response. Choice's render state also exposes `quantity` (0 when unselected), `min`, `max` and `setQuantity(n)`, clamped to the choice's bounds (0 deselects), so templates can build per-choice steppers. Groups show `data-invalid` only after a failed add; from then on they follow live validation, so fixing a group clears it immediately.
48
-
49
- ```tsx
50
- <ItemForm.Root item={item} onAdded={onAdded}>
51
- {({ groups, total }) => (
52
- <>
53
- {groups.map((group) => (
54
- <OptionGroup key={group.id} group={group}>
55
- {group.kind === "select" ? (
56
- group.choices.map((choice) => (
57
- <Choice key={choice.id} choice={choice}>
58
- {choice.name}
59
- </Choice>
60
- ))
61
- ) : (
62
- <OptionGroup.Input />
63
- )}
64
- </OptionGroup>
65
- ))}
66
- <Money value={total} />
67
- <ItemForm.Add>Add to cart</ItemForm.Add>
68
- </>
69
- )}
70
- </ItemForm.Root>
71
- ```
72
-
73
- ## Cart and Totals
74
-
75
- Anatomy: `Cart.Root` → `Line`, `Quantity`, `Remove`, `Empty`, `Issues`. Root subscribes to the SDK snapshot and hydrates after mounting; SSR starts empty. Root render props include lines, count, subtotal, state, issues, fulfilment and actions. Root state is loading/invalid/valid.
76
-
77
- Line takes a CartLine and exposes the current line, current catalogue item, edit(patch), remove(). Cart.Quantity takes lineId and supplies a Quantity.Root bounded by that item's rules. Remove takes lineId. Empty renders only after hydration when there are no lines. Issues exposes issues/applyFix and has `aria-live="polite"`; interactive fixes (choose option/address/schedule) must open the appropriate editor rather than call applyFix without a selection.
78
-
79
- Totals independently subscribes to the cart and exposes quote plus loading/stale/ready/offline status. It observes quote expiry and quote_failed network events, uses a polite live region and aria-busy while loading. It does not request a quote just by rendering. SDK mutation debouncing and explicit `cart.quote()` own requests. No quote means stale, not a fabricated zero total. Quote line prices may be null when the upstream total cannot be allocated to lines.
80
-
81
- ```tsx
82
- <Cart.Root>{({ lines }) => <>
83
- <Cart.Empty>Your cart is empty</Cart.Empty>
84
- {lines.map(line => <Cart.Line key={line.id} line={line}>
85
- <Cart.Quantity lineId={line.id}><Quantity.Value aria-label="Quantity" /></Cart.Quantity>
86
- <Cart.Remove lineId={line.id}>Remove {line.name}</Cart.Remove>
87
- </Cart.Line>)}
88
- <Cart.Issues>{({ issues }) => issues.map((issue, i) => <p key={i}>{issue.message}</p>)}</Cart.Issues>
89
- </>}</Cart.Root>
90
- <Totals>{({ quote, status }) => quote ? <Money value={quote.total} /> : <span>{status}</span>}</Totals>
91
- ```
92
-
93
- ## Fulfilment.Switch
94
-
95
- A group with options (only modes offered by the current branch), value and setValue. Props: optional value/defaultValue/onValueChange. Selection writes the SDK cart and checks branch/mode; delivery requires a complete address object. Keyboard behavior belongs to the native buttons/radios supplied by the template. No default visible text or state attribute.
96
-
97
- ```tsx
98
- <Fulfilment.Switch aria-label="Fulfilment">
99
- {({ options, setValue }) =>
100
- options.includes("pickup") && (
101
- <button onClick={() => setValue({ type: "pickup", branchId })}>
102
- Pickup
103
- </button>
104
- )
105
- }
106
- </Fulfilment.Switch>
107
- ```
108
-
109
- ## Branch.Picker
110
-
111
- Props: branches, optional controlled branch-ID value, onSelect. Render props expose branches, selected, isOpenNow(branch), async select(branch), isLoading and error. select calls SDK cart.moveTo before onSelect; failure is exposed without reporting a successful selection. It renders a group with aria-busy and loading/invalid/valid state. Supply buttons or a labelled native select for keyboard operation.
112
-
113
- ```tsx
114
- <Branch.Picker branches={branches} aria-label="Branch">
115
- {({ branches, select }) =>
116
- branches.map((branch) => (
117
- <button key={branch.id} onClick={() => void select(branch)}>
118
- {branch.name}
119
- </button>
120
- ))
121
- }
122
- </Branch.Picker>
123
- ```
124
-
125
- ## Address.Picker
126
-
127
- Anatomy: Address.Picker → Input, Suggestions → Suggestion. Props: getSuggestions(query, optionalAbortSignal), onSelect, controlled/uncontrolled query, debounceMs (200). Suggestions are `{ id, label, address: DeliveryAddress }`; no geocoding provider or API endpoint is built in. Search and Address announce how many options are listed through a visually hidden polite status region (the only inline style foundations renders); pass `announcement={(count) => ...}` to localise it. The region is omitted when the root uses `asChild`.
128
-
129
- Render props expose query/setQuery, suggestions, loading/ready/error status, error, activeIndex and combobox props/actions. Old responses are ignored and requests are aborted when query/provider changes or on unmount. Input keeps DOM focus; Up/Down changes active descendant, Enter selects, Escape collapses, blur outside closes. Suggestions exposes aria-busy, Suggestion exposes data-selected. Root data-state is loading/invalid/valid.
130
-
131
- ```tsx
132
- <Address.Picker getSuggestions={getSuggestions} onSelect={selectAddress}>
133
- <Address.Input aria-label="Delivery address" />
134
- <Address.Suggestions aria-label="Addresses">
135
- {({ suggestions }) =>
136
- suggestions.map((item, index) => (
137
- <Address.Suggestion key={item.id} index={index}>
138
- {item.label}
139
- </Address.Suggestion>
140
- ))
141
- }
142
- </Address.Suggestions>
143
- </Address.Picker>
144
- ```
145
-
146
- ## Schedule
147
-
148
- Props: branch, optional timezone (branch then store timezone), controlled/uncontrolled ISO value (`null` means ASAP), onValueChange, intervalMinutes (30), maxDays (14, at most 90), noticeMinutes (0). Render props: days, slots, value/setValue, isAsap, canAsap, isValid. Slots include ISO value, local date and a label with timezone offset (distinguishing repeated DST times).
149
-
150
- Scheduling must be offered by the branch. Slots intersect branch hours and the loaded menu's top-level schedule, honour special hours/closedUntil and preorder notice/horizon. Missing DST wall times are skipped and repeated times retain distinct instants. A stale or unavailable selection is invalid. ASAP is disabled for closed branches and preorder menus. This is a presentation of known schedule facts; the server quote remains authoritative for item-specific restrictions. State is valid/invalid. Supply native buttons/selects for keyboard handling.
151
-
152
- ```tsx
153
- <Schedule branch={branch} onValueChange={setScheduledFor} aria-label="Schedule">
154
- {({ slots, setValue }) => (
155
- <select
156
- aria-label="Time slot"
157
- onChange={(event) => setValue(event.target.value)}
158
- >
159
- {slots.map((slot) => (
160
- <option key={slot.value} value={slot.value}>
161
- {slot.date} {slot.label}
162
- </option>
163
- ))}
164
- </select>
165
- )}
166
- </Schedule>
167
- ```
168
-
169
- ## PaymentMethods
170
-
171
- Anatomy: Root → Item. Root takes methods (SDK method kinds or `{ value, disabled, reason }`), value/defaultValue/onValueChange; Item takes value. Display/preference only: it never charges or starts payment. Root is a labelled radiogroup. Items expose checked/unchecked, selected and disabled; reason is aria-description and also available to render props. Arrows/Home/End skip disabled methods; Space selects native buttons.
172
-
173
- ```tsx
174
- <PaymentMethods.Root
175
- methods={["card", "bank_transfer"]}
176
- aria-label="Preferred payment"
177
- >
178
- <PaymentMethods.Item value="card">Card</PaymentMethods.Item>
179
- <PaymentMethods.Item value="bank_transfer">Bank transfer</PaymentMethods.Item>
180
- </PaymentMethods.Root>
181
- ```
182
-
183
- ## Overlay
184
-
185
- Anatomy: Root → Trigger, Content → Title, Close. Root requires param/id and accepts informational presentation (dialog/drawer/sheet), fallback query and omitParams. Every rendered Root registers only its exact ID by matching the URL; unknown IDs render no content. `useOverlay(param, id)` exposes isOpen/open(query?)/close(). IDs and query values remain data, never executable content.
186
-
187
- URL search is the sole state source via useSyncExternalStore. Open pushes a marked history entry; close steps back through its stack, or replaces a deep link without leaving the site. Back closes the inner overlay, Forward reopens it. Root/Trigger expose open/closed; Content exposes open and data-presentation. Content is a labelled `aria-modal` overlay. Its mechanics come from Base UI, driven by the URL: it portals to `document.body` (or `container`), contains focus (an explicit `[data-autofocus]`, else the first focusable, else the overlay itself), restores focus to the trigger, hides the page behind from assistive technology, locks page scroll, and closes the top layer on Escape or an outside press (`onEscape` and `onInteractOutside` can `preventDefault()` to keep it open).
188
-
189
- `presentation` chooses the behaviour: `"dialog"`, a swipeable `"drawer"` (swipe down to dismiss by default) or `"sheet"` (swipe right by default); `swipeDirection` overrides it. It is responsive: `presentation={{ base: "drawer", md: "dialog" }}` uses Tailwind's breakpoints (`sm` 640, `md` 768, `lg` 1024, `xl` 1280) through media queries, and the server renders `base`. Drawers keep focused fields above the on-screen keyboard. Root and Content expose `data-presentation`.
190
-
191
- The engine is a separate chunk. Root prefetches it when the browser is idle and on trigger hover or focus, so it is normally ready before the first open. On a cold first open it loads before the overlay appears, and the package's initial entry stays within its size gate (`pnpm --filter @zappie/foundations size`).
192
-
193
- Content must be positioned, as any modal is (for example `position: fixed`): the engine keeps an inert layer behind it to catch outside presses. Each modal also renders an unstyled backdrop marked `data-zappie-backdrop` (with `data-state` and `data-presentation`) for templates to style. Pass `forceMount` to keep content mounted while closed so exit animations can run on `data-state="closed"`. Give every overlay an `Overlay.Title` (visually hidden if need be) or an `aria-label`. Templates supply placement and a visible close button. Mount nested roots inside the outer content.
194
-
195
- ```tsx
196
- <Overlay.Root param="modal" id="cart" presentation="drawer">
197
- <Overlay.Trigger>Open cart</Overlay.Trigger>
198
- <Overlay.Content>
199
- <Overlay.Title>Cart</Overlay.Title>
200
- <Overlay.Close>Close cart</Overlay.Close>
201
- </Overlay.Content>
202
- </Overlay.Root>
203
- ```
204
-
205
- In Next, call `useNextOverlayBridge()` from `@zappie/foundations/next` once in the client shell. It captures same-page shallow history moves before Next's listener; no Next dependency enters the package. Plain React and React Router need no bridge. Native writes preserve history metadata and bypass framework wrappers; page-changing entries clear stale Next markers.
206
-
207
- ## OpenStatus
208
-
209
- Props: branch, optional timezone override. Exposes isOpen, label and nextChange; renders no visible text unless the template chooses it. `data-state="open|closed"`. No keyboard behavior. Uses Intl in the branch/store timezone, merged weekly sessions, overnight hours and calendar exceptions. A timer updates at the next opening/closing boundary, with focus refresh for suspended tabs. Pure helpers getOpenStatus/formatOpeningHours/parseWallClock/formatWallClock can be used outside React.
210
-
211
- ```tsx
212
- <OpenStatus branch={branch}>{({ label }) => label}</OpenStatus>
5
+ ```sh
6
+ npm install @zappie/foundations @zappie/sdk react react-dom
213
7
  ```
214
8
 
215
- ## Search
9
+ React 18.2 or 19 is a peer dependency.
216
10
 
217
- Anatomy: Root → Input, Results → Result. Root takes query/defaultQuery/onQueryChange and onSelect(result). Results include item/category discriminated unions and scores. Render props expose query/setQuery, results, activeIndex and combobox props/actions. Normalize case/diacritics; token prefixes plus Damerau–Levenshtein distance ≤1 for query tokens of at least four characters. Name weighs above tags above description. Index rebuilds only when the branch menu changes. The SDK emits `search { query, resultCount }` after 600ms of stable nonempty input; no analytics network transport is added.
11
+ ## Use it
218
12
 
219
- Keyboard: Up/Down, Enter, Escape with DOM focus retained on the input; blur outside closes. Root data-state is loading/stale/valid; results expose data-selected.
13
+ The parts read Zappie data through `@zappie/sdk`, so wrap them in its `ZappieProvider`. Each part renders a plain element you can style, or takes a render function:
220
14
 
221
15
  ```tsx
222
- <Search.Root onSelect={openSearchResult}>
223
- <Search.Input aria-label="Search menu" />
224
- <Search.Results aria-label="Results">
225
- {({ results }) =>
226
- results.map((result, index) => (
227
- <Search.Result key={index} index={index}>
228
- {result.type === "item" ? result.item.name : result.category.name}
229
- </Search.Result>
230
- ))
231
- }
232
- </Search.Results>
233
- </Search.Root>
234
- ```
235
-
236
- ## Account stubs
237
-
238
- Account.SignIn, SignOut and Customer are deliberate PRD 13 placeholders. They expose status `not_configured`; signIn(options)/signOut(options) reject with AccountNotConfiguredError (`code="not_configured"`). Customer is null. They never read host credentials or perform navigation/network auth. Sign-in options and the future customer shape match PRD 13. DOM state is invalid; supply your own markup, usually omit auth UI until configured.
16
+ import type { Branch } from "@zappie/sdk";
17
+ import { OpenStatus } from "@zappie/foundations";
239
18
 
240
- ```tsx
241
- <Account.Customer>
242
- {({ customer }) => customer?.givenName ?? "Guest"}
243
- </Account.Customer>
19
+ export function Hours({ branch }: { branch: Branch }) {
20
+ return (
21
+ <OpenStatus branch={branch} className="hours">
22
+ {(status) => status.label}
23
+ </OpenStatus>
24
+ );
25
+ }
244
26
  ```
245
27
 
246
- ## Checkout/accounts-only OTP
28
+ The label reads like "Open until 10pm", "Opens 8am tomorrow" or "Closed", and it updates when the branch opens or closes. Parts expose their state as `data-*` attributes, such as `data-state="open"`, so you can style each state in CSS.
247
29
 
248
- Otp.Input is exported for the checkout and accounts applications, not an alternative storefront login flow. Props: length (6, range 1–12), value/defaultValue/onValueChange/onComplete/disabled. Default markup is a group of labelled text inputs with numeric input mode, one-time-code autocomplete on the first input, paste/autofill, left/right movement and backspace across slots. No value is logged. A render prop can supply all slots through getSlotProps(index), preserving their data-otp-slot attribute. State attribute: data-disabled.
30
+ Want ready-made, styled components instead? Copy them into your project from the kit with `npx zappie add <name>`.
249
31
 
250
- ```tsx
251
- <Otp.Input length={6} aria-label="Verification code" onComplete={verifyCode} />
252
- ```
32
+ ## Entry points
253
33
 
254
- ## Build or adopt
34
+ | Import | For |
35
+ | ------------------------------- | --------------------------------------------------------------- |
36
+ | `@zappie/foundations` | Every part |
37
+ | `@zappie/foundations/next` | Keeping overlays in step with Back and Forward in a Next.js app |
38
+ | `@zappie/foundations/rich-text` | Sanitising and parsing merchant rich text before you render it |
255
39
 
256
- Base UI already ships as the lazily loaded overlay engine, but the other primitives are built in-house on purpose: they render with the page, and the Base UI equivalents of the generic patterns alone are about 75 KB gzipped against 12 KB for all of foundations. Adopt the Base UI component for a single primitive only when the in-house one keeps producing accessibility or device bugs that aren't cheaply fixable, or when the primitive only ever renders inside an overlay (it then loads with the engine chunk). Commerce behaviour and the `data-*` contract stay in foundations either way. The rule, the measurements and the current status of each primitive are in open-storefronts PRD 05 §4.7.
40
+ ## Docs
257
41
 
258
- ## Examples and verification
42
+ - [Foundations reference](https://docs.zappie.ai/foundations/core)
43
+ - [Build a storefront](https://docs.zappie.ai/guides/build-a-storefront)
259
44
 
260
- [examples/commerce.tsx](examples/commerce.tsx) and [examples/overlay.tsx](examples/overlay.tsx) are executable, synthetic examples. The Next host exposes `/foundations-lab` only outside production; the standalone Vite fixture runs on loopback.
45
+ ## Status
261
46
 
262
- ```sh
263
- pnpm exec vite packages/foundations/examples --config packages/foundations/examples/vite.config.ts
264
- pnpm exec vitest run packages/foundations
265
- pnpm exec vitest run --config vitest.foundations-browser.config.ts
266
- pnpm exec playwright test --config playwright.foundations.config.ts
267
- pnpm packages:build
268
- pnpm sdk:size
269
- pnpm check-types
270
- ```
47
+ Foundations is at `0.x`. Its API can change in a minor release while it settles. Each change is listed in `CHANGELOG.md`.
271
48
 
272
- Component tests use RTL and axe in jsdom (color contrast is disabled there because jsdom has no layout/canvas). The browser example test runs axe in Chromium. The separate Playwright matrix exercises Vite and the real Next host on Chromium, WebKit and Mobile Safari. Tests use synthetic data and loopback servers, not production APIs or real payments.
49
+ ## License
273
50
 
274
- Keyboard behavior follows the WAI-ARIA APG [dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), [combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/), [radio](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) and spinbutton patterns. Consumers must retain semantics and provide meaningful visible/accessibility labels.
51
+ MIT
@@ -0,0 +1,49 @@
1
+ // src/account/index.tsx
2
+ import { forwardRef } from "react";
3
+ import { part } from "../internal/slot.js";
4
+ var AccountNotConfiguredError = class extends Error {
5
+ constructor() {
6
+ super("Account authentication requires SDK auth (Open Storefronts PRD 13)");
7
+ this.code = "not_configured";
8
+ this.name = "AccountNotConfiguredError";
9
+ }
10
+ };
11
+ async function signIn(_options) {
12
+ throw new AccountNotConfiguredError();
13
+ }
14
+ async function signOut(_options) {
15
+ throw new AccountNotConfiguredError();
16
+ }
17
+ var SignIn = forwardRef(function SignIn2(props, ref) {
18
+ return part(
19
+ "div",
20
+ props,
21
+ { signIn, status: "not_configured" },
22
+ { "data-state": "invalid" },
23
+ ref
24
+ );
25
+ });
26
+ var SignOut = forwardRef(function SignOut2(props, ref) {
27
+ return part(
28
+ "div",
29
+ props,
30
+ { signOut, status: "not_configured" },
31
+ { "data-state": "invalid" },
32
+ ref
33
+ );
34
+ });
35
+ var CustomerPart = forwardRef(function Customer(props, ref) {
36
+ return part(
37
+ "div",
38
+ props,
39
+ { customer: null, status: "not_configured" },
40
+ { "data-state": "invalid" },
41
+ ref
42
+ );
43
+ });
44
+ var Account = { SignIn, SignOut, Customer: CustomerPart };
45
+ export {
46
+ Account,
47
+ AccountNotConfiguredError
48
+ };
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/account/index.tsx"],
4
+ "sourcesContent": ["import { forwardRef } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\n/** Account stubs that do not read credentials or host authentication. */\nexport type Customer = { id: string; givenName?: string; phoneMasked: string };\n/**\n * Sign-in options reserved by the account contract.\n * The current Account sign-in stub rejects with AccountNotConfiguredError.\n */\nexport type SignInOptions = {\n returnTo?: string;\n prompt?: \"none\" | \"login\";\n display?: \"popup\" | \"redirect\" | \"auto\";\n scopes?: (\n | \"openid\"\n | \"phone\"\n | \"profile\"\n | \"addresses\"\n | \"orders:read\"\n | \"offline_access\"\n | \"cart\"\n )[];\n};\n/**\n * An error from Account sign-in and sign-out when authentication is not configured.\n */\nexport class AccountNotConfiguredError extends Error {\n readonly code = \"not_configured\";\n constructor() {\n super(\"Account authentication requires SDK auth (Open Storefronts PRD 13)\");\n this.name = \"AccountNotConfiguredError\";\n }\n}\nasync function signIn(_options?: SignInOptions): Promise<void> {\n throw new AccountNotConfiguredError();\n}\nasync function signOut(_options?: { everywhere?: boolean }): Promise<void> {\n throw new AccountNotConfiguredError();\n}\nconst SignIn = forwardRef<\n HTMLElement,\n PartProps<{ signIn: typeof signIn; status: \"not_configured\" }>\n>(function SignIn(props, ref) {\n return part(\n \"div\",\n props,\n { signIn, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\nconst SignOut = forwardRef<\n HTMLElement,\n PartProps<{ signOut: typeof signOut; status: \"not_configured\" }>\n>(function SignOut(props, ref) {\n return part(\n \"div\",\n props,\n { signOut, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\nconst CustomerPart = forwardRef<\n HTMLElement,\n PartProps<{ customer: Customer | null; status: \"not_configured\" }>\n>(function Customer(props, ref) {\n return part(\n \"div\",\n props,\n { customer: null, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\n/**\n * Account UI stubs that expose a not_configured status and a null customer.\n * SignIn and SignOut reject with AccountNotConfiguredError. They do not authenticate.\n */\nexport const Account = { SignIn, SignOut, Customer: CustomerPart };\n"],
5
+ "mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AAwB9B,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAEnD,cAAc;AACZ,UAAM,oEAAoE;AAF5E,SAAS,OAAO;AAGd,SAAK,OAAO;AAAA,EACd;AACF;AACA,eAAe,OAAO,UAAyC;AAC7D,QAAM,IAAI,0BAA0B;AACtC;AACA,eAAe,QAAQ,UAAoD;AACzE,QAAM,IAAI,0BAA0B;AACtC;AACA,IAAM,SAAS,WAGb,SAASA,QAAO,OAAO,KAAK;AAC5B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,QAAQ,iBAAiB;AAAA,IACnC,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AACD,IAAM,UAAU,WAGd,SAASC,SAAQ,OAAO,KAAK;AAC7B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,SAAS,QAAQ,iBAAiB;AAAA,IACpC,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AACD,IAAM,eAAe,WAGnB,SAAS,SAAS,OAAO,KAAK;AAC9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,UAAU,MAAM,QAAQ,iBAAiB;AAAA,IAC3C,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AAKM,IAAM,UAAU,EAAE,QAAQ,SAAS,UAAU,aAAa;",
6
+ "names": ["SignIn", "SignOut"]
7
+ }
@@ -0,0 +1,168 @@
1
+ "use client";
2
+
3
+ // src/cart/index.tsx
4
+ import { useCart, useCartCatalogue, useZappie } from "@zappie/sdk/react";
5
+ import {
6
+ createContext,
7
+ forwardRef,
8
+ useContext,
9
+ useEffect,
10
+ useState
11
+ } from "react";
12
+ import { part } from "../internal/slot.js";
13
+ import { Quantity } from "../item-form/quantity.js";
14
+ import { jsx } from "react/jsx-runtime";
15
+ var Context = createContext(null);
16
+ function useContextValue() {
17
+ const value = useContext(Context);
18
+ if (!value) {
19
+ throw new Error("Cart parts require Cart.Root");
20
+ }
21
+ return value;
22
+ }
23
+ function Root(props) {
24
+ const state = useCart();
25
+ return /* @__PURE__ */ jsx(Context.Provider, { value: state, children: part("div", props, state, {
26
+ "data-state": !state.isHydrated ? "loading" : state.issues.some((issue) => issue.severity === "blocking") ? "invalid" : "valid"
27
+ }) });
28
+ }
29
+ var Line = forwardRef(function Line2({ line, ...props }, ref) {
30
+ const cart = useContextValue();
31
+ const item = useCartCatalogue()?.items[line.itemId];
32
+ const current = cart.lines.find((candidate) => candidate.id === line.id);
33
+ if (!current) {
34
+ return null;
35
+ }
36
+ const context = {
37
+ line: current,
38
+ item,
39
+ edit: (patch) => cart.actions.update(line.id, patch),
40
+ remove: () => cart.actions.remove(line.id)
41
+ };
42
+ return part(
43
+ "div",
44
+ props,
45
+ context,
46
+ {
47
+ "data-zappie-cart-line": line.id,
48
+ "data-invalid": cart.issues.some(
49
+ (issue) => issue.lineId === line.id && issue.severity === "blocking"
50
+ ) ? "" : void 0
51
+ },
52
+ ref
53
+ );
54
+ });
55
+ function CartQuantity({
56
+ lineId,
57
+ ...props
58
+ }) {
59
+ const cart = useContextValue();
60
+ const line = cart.lines.find((candidate) => candidate.id === lineId);
61
+ const item = useCartCatalogue()?.items[line?.itemId ?? ""];
62
+ if (!line) {
63
+ return null;
64
+ }
65
+ const step = item?.quantity.step ?? 1;
66
+ const min = Math.ceil((item?.quantity.min ?? 1) / step) * step;
67
+ return /* @__PURE__ */ jsx(
68
+ Quantity.Root,
69
+ {
70
+ ...props,
71
+ value: line.quantity,
72
+ min,
73
+ max: Math.min(item?.quantity.max ?? 99, 99),
74
+ step,
75
+ onValueChange: (quantity) => cart.actions.update(lineId, { quantity })
76
+ }
77
+ );
78
+ }
79
+ var Remove = forwardRef(
80
+ function Remove2({ lineId, ...props }, ref) {
81
+ const cart = useContextValue();
82
+ return part(
83
+ "button",
84
+ props,
85
+ {},
86
+ { type: "button", onClick: () => cart.actions.remove(lineId) },
87
+ ref
88
+ );
89
+ }
90
+ );
91
+ var Empty = forwardRef(function Empty2(props, ref) {
92
+ const cart = useContextValue();
93
+ return cart.isHydrated && !cart.lines.length ? part("div", props, {}, {}, ref) : null;
94
+ });
95
+ var Issues = forwardRef(function Issues2(props, ref) {
96
+ const cart = useContextValue();
97
+ return part(
98
+ "div",
99
+ props,
100
+ { issues: cart.issues, applyFix: cart.actions.applyFix },
101
+ {
102
+ "aria-live": "polite",
103
+ "data-state": cart.issues.length ? "invalid" : "valid"
104
+ },
105
+ ref
106
+ );
107
+ });
108
+ var Cart = {
109
+ Root,
110
+ Line,
111
+ Quantity: CartQuantity,
112
+ Remove,
113
+ Empty,
114
+ Issues
115
+ };
116
+ var Totals = forwardRef(
117
+ function Totals2(props, ref) {
118
+ const cart = useCart();
119
+ const client = useZappie();
120
+ const [failure, setFailure] = useState(null);
121
+ const [expired, setExpired] = useState(false);
122
+ const quote = "quote" in cart.state ? cart.state.quote : void 0;
123
+ useEffect(
124
+ () => client.on(
125
+ "quote_failed",
126
+ ({ code }) => setFailure(
127
+ code === "network_error" || code === "timeout" ? "offline" : "stale"
128
+ )
129
+ ),
130
+ [client]
131
+ );
132
+ useEffect(() => {
133
+ if (cart.state.status === "quoting" || quote) {
134
+ setFailure(null);
135
+ }
136
+ setExpired(Boolean(quote && Date.parse(quote.expiresAt) <= Date.now()));
137
+ if (!quote) {
138
+ return;
139
+ }
140
+ const timeout = setTimeout(
141
+ () => setExpired(true),
142
+ Math.max(
143
+ 0,
144
+ Math.min(2147483647, Date.parse(quote.expiresAt) - Date.now())
145
+ )
146
+ );
147
+ return () => clearTimeout(timeout);
148
+ }, [quote, cart.state.status]);
149
+ const pending = cart.state.status === "ready" && !quote && cart.lines.length > 0;
150
+ const status = failure === "offline" ? "offline" : cart.state.status === "quoting" || !cart.isHydrated ? "loading" : failure === "stale" || expired ? "stale" : pending ? "loading" : !quote ? "stale" : "ready";
151
+ return part(
152
+ "div",
153
+ props,
154
+ { quote, status },
155
+ {
156
+ "aria-live": "polite",
157
+ "aria-busy": status === "loading",
158
+ "data-state": status === "ready" ? "valid" : status === "offline" ? "stale" : status
159
+ },
160
+ ref
161
+ );
162
+ }
163
+ );
164
+ export {
165
+ Cart,
166
+ Totals
167
+ };
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/cart/index.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport type {\n CartFix,\n CartIssue,\n CartLine,\n CartLineInput,\n Quote,\n} from \"@zappie/sdk\";\nimport { useCart, useCartCatalogue, useZappie } from \"@zappie/sdk/react\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { Quantity, type QuantityProps } from \"../item-form/quantity\";\ntype CartContext = ReturnType<typeof useCart>;\nconst Context = createContext<CartContext | null>(null);\nfunction useContextValue() {\n const value = useContext(Context);\n if (!value) {\n throw new Error(\"Cart parts require Cart.Root\");\n }\n return value;\n}\nfunction Root(props: PartProps<CartContext>) {\n const state = useCart();\n return (\n <Context.Provider value={state}>\n {part(\"div\", props, state, {\n \"data-state\": !state.isHydrated\n ? \"loading\"\n : state.issues.some((issue) => issue.severity === \"blocking\")\n ? \"invalid\"\n : \"valid\",\n })}\n </Context.Provider>\n );\n}\ntype LineContext = {\n line: CartLine;\n item: import(\"@zappie/sdk\").Item | undefined;\n edit: (patch: Partial<Omit<CartLineInput, \"itemId\">>) => CartLine;\n remove: () => void;\n};\nconst Line = forwardRef<\n HTMLElement,\n PartProps<LineContext> & { line: CartLine }\n>(function Line({ line, ...props }, ref) {\n const cart = useContextValue();\n const item = useCartCatalogue()?.items[line.itemId];\n const current = cart.lines.find((candidate) => candidate.id === line.id);\n if (!current) {\n return null;\n }\n const context = {\n line: current,\n item,\n edit: (patch: Partial<Omit<CartLineInput, \"itemId\">>) =>\n cart.actions.update(line.id, patch),\n remove: () => cart.actions.remove(line.id),\n };\n return part(\n \"div\",\n props,\n context,\n {\n \"data-zappie-cart-line\": line.id,\n \"data-invalid\": cart.issues.some(\n (issue) => issue.lineId === line.id && issue.severity === \"blocking\",\n )\n ? \"\"\n : undefined,\n },\n ref,\n );\n});\nfunction CartQuantity({\n lineId,\n ...props\n}: Omit<QuantityProps, \"value\" | \"defaultValue\" | \"onValueChange\"> & {\n lineId: string;\n}) {\n const cart = useContextValue();\n const line = cart.lines.find((candidate) => candidate.id === lineId);\n const item = useCartCatalogue()?.items[line?.itemId ?? \"\"];\n if (!line) {\n return null;\n }\n const step = item?.quantity.step ?? 1;\n const min = Math.ceil((item?.quantity.min ?? 1) / step) * step;\n return (\n <Quantity.Root\n {...props}\n value={line.quantity}\n min={min}\n max={Math.min(item?.quantity.max ?? 99, 99)}\n step={step}\n onValueChange={(quantity) => cart.actions.update(lineId, { quantity })}\n />\n );\n}\nconst Remove = forwardRef<HTMLElement, PartProps & { lineId: string }>(\n function Remove({ lineId, ...props }, ref) {\n const cart = useContextValue();\n return part(\n \"button\",\n props,\n {},\n { type: \"button\", onClick: () => cart.actions.remove(lineId) },\n ref,\n );\n },\n);\nconst Empty = forwardRef<HTMLElement, PartProps>(function Empty(props, ref) {\n const cart = useContextValue();\n return cart.isHydrated && !cart.lines.length\n ? part(\"div\", props, {}, {}, ref)\n : null;\n});\nconst Issues = forwardRef<\n HTMLElement,\n PartProps<{\n issues: readonly CartIssue[];\n applyFix: (issue: CartIssue, fix?: CartFix) => void;\n }>\n>(function Issues(props, ref) {\n const cart = useContextValue();\n return part(\n \"div\",\n props,\n { issues: cart.issues, applyFix: cart.actions.applyFix },\n {\n \"aria-live\": \"polite\",\n \"data-state\": cart.issues.length ? \"invalid\" : \"valid\",\n },\n ref,\n );\n});\n/**\n * Composable cart parts for lines, quantity changes, removal, empty state and validation issues.\n * Wrap the parts in Cart.Root inside your ZappieProvider.\n */\nexport const Cart = {\n Root,\n Line,\n Quantity: CartQuantity,\n Remove,\n Empty,\n Issues,\n};\n/**\n * The quote and its display status: loading, stale, ready or offline.\n */\nexport type TotalsState = {\n quote?: Quote | undefined;\n status: \"loading\" | \"stale\" | \"ready\" | \"offline\";\n};\n/**\n * Expose the cart quote and its freshness or network status to your render function.\n * Subscribes to the cart and quote failures through your ZappieProvider.\n */\nexport const Totals = forwardRef<HTMLElement, PartProps<TotalsState>>(\n function Totals(props, ref) {\n const cart = useCart();\n const client = useZappie();\n const [failure, setFailure] = useState<\"offline\" | \"stale\" | null>(null);\n const [expired, setExpired] = useState(false);\n const quote = \"quote\" in cart.state ? cart.state.quote : undefined;\n useEffect(\n () =>\n client.on(\"quote_failed\", ({ code }) =>\n setFailure(\n code === \"network_error\" || code === \"timeout\"\n ? \"offline\"\n : \"stale\",\n ),\n ),\n [client],\n );\n useEffect(() => {\n if (cart.state.status === \"quoting\" || quote) {\n setFailure(null);\n }\n setExpired(Boolean(quote && Date.parse(quote.expiresAt) <= Date.now()));\n if (!quote) {\n return;\n }\n const timeout = setTimeout(\n () => setExpired(true),\n Math.max(\n 0,\n Math.min(2147483647, Date.parse(quote.expiresAt) - Date.now()),\n ),\n );\n return () => clearTimeout(timeout);\n }, [quote, cart.state.status]);\n // The SDK re-quotes shortly after every change, so a cart with lines and\n // no quote yet is waiting for one rather than stale.\n const pending =\n cart.state.status === \"ready\" && !quote && cart.lines.length > 0;\n const status: TotalsState[\"status\"] =\n failure === \"offline\"\n ? \"offline\"\n : cart.state.status === \"quoting\" || !cart.isHydrated\n ? \"loading\"\n : failure === \"stale\" || expired\n ? \"stale\"\n : pending\n ? \"loading\"\n : !quote\n ? \"stale\"\n : \"ready\";\n return part(\n \"div\",\n props,\n { quote, status },\n {\n \"aria-live\": \"polite\",\n \"aria-busy\": status === \"loading\",\n \"data-state\":\n status === \"ready\"\n ? \"valid\"\n : status === \"offline\"\n ? \"stale\"\n : status,\n },\n ref,\n );\n },\n);\n"],
5
+ "mappings": "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",
6
+ "names": ["Line", "Remove", "Empty", "Issues", "Totals"]
7
+ }