@godaddy/gd-commerce-storefront 0.5.0 → 0.6.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/README.md CHANGED
@@ -55,6 +55,8 @@ Use the providers your application already has; do not create another router or
55
55
  | `productPath` | `/products` | Same-origin prefix for product links; register `${productPath}/:productId` |
56
56
  | `checkoutSuccessPath` | unset | Same-origin return route; enables checkout UI only when supplied |
57
57
  | `onItemAdded` | unset | Called with `{ name, quantity, isSample }` after each add, live or sample; use it to confirm adds on a site with a cart page instead of a drawer |
58
+ | `onEvent` | unset | Called with a [`CommerceEvent`](#events) for every cart change and failure, such as for analytics |
59
+ | `locale` | `en-US` | BCP 47 locale for formatting prices, such as `de-DE`; an invalid tag falls back to `en-US` with a development warning. It never selects UI text |
58
60
  | `theme` | inherited text and transparent wrappers; neutral control palette | CSS custom properties applied to storefront surfaces and the portalled drawer |
59
61
 
60
62
  Provide root-relative paths without a trailing slash. Enable `checkoutSuccessPath` only after your server supports checkout and validates merchant readiness. Mount a corresponding return page. A redirect back from checkout is **not proof of payment**; that page must obtain authoritative payment status from your server. This package does not provide a payment receipt page or merchant onboarding.
@@ -85,9 +87,9 @@ A connection failure leaves the surrounding application and its state mounted. C
85
87
 
86
88
  The stylesheet includes all required utilities and scopes them to the package's surfaces. The build removes CSS layer wrappers in their declared order, so the exported CSS can pass through a host Tailwind v3 PostCSS pipeline without `@tailwind` directives. Import it directly; consumers do not need to copy or rewrite the CSS. It does not add a global reset or require dependency scanning by a host Tailwind build. The `theme` prop reaches the drawer even though it is portalled into `document.body`.
87
89
 
88
- By default, `.commerce-storefront` wrappers do not paint a background and inherit the host's text color. Set page and section backgrounds on the host application's layout elements. `--commerce-surface` colors controls and the opaque cart drawer, and supplies the base for muted colors; it does not color component wrappers. Set `--commerce-surface` and `--commerce-text` together to adapt controls and the drawer to a dark theme. Avoid setting wrapper backgrounds through CSS or the `theme` prop's `background`, `backgroundColor`, or `backgroundImage` properties, which can still override the transparent default. Descendants inherit the surface text and body font instead of global heading styles. Keep text, controls and focus indicators accessible when changing colors. Internal utility class names and markup are not a customization API; use the [product](#custom-product-layouts), [catalog](#custom-catalog-layouts) and [cart](#custom-cart-layouts) parts to change a layout.
90
+ By default, `.commerce-storefront` wrappers do not paint a background and inherit the host's text color. Set page and section backgrounds on the host application's layout elements. `--commerce-surface` colors controls and the opaque cart drawer, and supplies the base for muted colors; it does not color component wrappers. Set `--commerce-surface` and `--commerce-text` together to adapt controls and the drawer to a dark theme. Avoid setting wrapper backgrounds through CSS or the `theme` prop's `background`, `backgroundColor`, or `backgroundImage` properties, which can still override the transparent default. Descendants inherit the surface text and body font instead of global heading styles. Keep text, controls and focus indicators accessible when changing colors. Internal utility class names and markup are not a customization API; use the [product](#custom-product-layouts), [catalog](#custom-catalog-layouts) and [cart](#custom-cart-layouts) parts to change a layout, and the [styling hooks](#styling-hooks) to style inside a part.
89
91
 
90
- The first release uses English UI text and `en-US` currency formatting. The catalog title and description are configurable through `CatalogHeader`. Full localization is outside this initial API.
92
+ Prices follow the provider's `locale`, which defaults to `en-US`. UI text is English. The catalog title and description are configurable through `CatalogHeader`, and many parts take [replacement text](#replacing-text). Full localization is outside this initial API. When it arrives, translated text will be a separate prop that a site opts into; `locale` will keep formatting only, so a site that sets it for prices never gets new wording in a minor release. Replacement text will still take priority over translations.
91
93
 
92
94
  ## Compatibility
93
95
 
@@ -100,17 +102,19 @@ The repository example uses React Router 7. Follow each router version's own Rea
100
102
  | Export | Purpose |
101
103
  | --- | --- |
102
104
  | `CommerceStorefront` | Recommended integration: provider and one drawer |
103
- | `Catalog` | Six products per cursor page. Its `title`, `description`, and `showHeader` props are deprecated; compose `CatalogRoot` instead |
105
+ | `Catalog` | Six products per cursor page, or `pageSize`. Its `title`, `description`, and `showHeader` props are deprecated; compose `CatalogRoot` instead |
104
106
  | `CatalogRoot` and catalog parts | Build a [custom catalog or card layout](#custom-catalog-layouts) with the same rules as `Catalog` |
105
- | `ProductDetails` | Reads `:productId`; validates URL option selections against catalog results |
107
+ | `ProductDetails` | Reads `:productId`; validates URL option selections against catalog results. `ProductDetailsRoot` also takes a `productId` prop |
106
108
  | `ProductDetailsRoot` and product parts | Build a [custom product layout](#custom-product-layouts) with the same rules as `ProductDetails` |
107
109
  | `ProductCard` | Renders one `SKUGroup` with direct add or a product-details link; `ProductCardRoot` and card parts customize it |
108
110
  | `CartDrawer` | The cart dialog; `CommerceStorefront` mounts one |
109
111
  | `CartDrawerRoot`, `CartRoot` and cart parts | Build a [custom drawer or a cart page](#custom-cart-layouts) with the same rules as `CartDrawer` |
110
- | `CartButton` | Opens the shared drawer and displays item count; `useCart()` builds a custom one |
111
- | `AddToCartButton` | Adds a verified `sku`, `name`, and optional integer `quantity`; while disabled, its accessible description says why |
112
+ | `CartButton` | Opens the shared drawer and displays item count; its children replace the label, and `useCart()` builds a custom one |
113
+ | `AddToCartButton` | Adds a verified `sku`, `name`, and optional integer `quantity`; while disabled, its accessible description says why. Its children replace the label |
112
114
  | `CommerceStatus` | Connection progress/error/retry for custom layouts |
113
115
  | `useCommerce` | Cart, connection, pending/error state and serialized cart actions |
116
+ | `useMoney` | Formats minor units in the provider's `locale`, like the package's own prices, for custom parts |
117
+ | `ProductImage` | A product image with the package's placeholder for a missing or broken image, for custom parts |
114
118
  | `CommerceProvider` | Lower-level composition when the recommended wrapper does not fit, such as a custom drawer; mount it once |
115
119
 
116
120
  The package exports TypeScript catalog/cart response types and selection/summary helpers for custom product layouts. `useCommerce` actions return `Promise<boolean>`: `false` means an operation failed or its connection became stale. Inspect `error` for active-session failures. Do not automatically retry a failed mutation: the server may have committed it before the response failed. `applyDiscount(code)` is available to custom layouts; the default drawer does not render a promotion form.
@@ -119,7 +123,7 @@ The package exports TypeScript catalog/cart response types and selection/summary
119
123
 
120
124
  ## Custom product layouts
121
125
 
122
- `ProductDetails` is the recommended product page. When its layout does not fit, compose the same parts inside `ProductDetailsRoot`. The root loads the route's `:productId` and shows the connection, loading, error and not-found states itself, so parts render only once the product exists. This is the packaged layout, written out:
126
+ `ProductDetails` is the recommended product page. When its layout does not fit, compose the same parts inside `ProductDetailsRoot`. The root loads the route's `:productId`, or its `productId` prop, and shows the connection, loading, error and not-found states itself, so parts render only once the product exists. This is the packaged layout, written out:
123
127
 
124
128
  ```tsx
125
129
  import {
@@ -155,6 +159,23 @@ Add your own elements between parts, reorder them, or leave out display parts. `
155
159
 
156
160
  Every part is named `ProductDetails*` because it works only inside `ProductDetailsRoot`. `AddToCartButton` is the standalone button for a SKU you already have.
157
161
 
162
+ The root keeps the selection in the page URL, as `option.<attribute>` and `sku`, so a reload or a shared link keeps it. To show a product outside its route, such as in a quick view or next to another product, pass `productId` and set `urlState={false}`. That root keeps its selection to itself, and it ends when the root unmounts. Give every root but one on a page `urlState={false}`; otherwise the roots share one selection. A root with no `productId` on a route without `:productId` shows the not-found state and, in development builds, warns.
163
+
164
+ ```tsx
165
+ {/* Your own dialog, opened from a card with the product's ID. */}
166
+ <QuickViewDialog open={quickViewId !== null} onClose={() => setQuickViewId(null)}>
167
+ {quickViewId && (
168
+ <ProductDetailsRoot productId={quickViewId} urlState={false} loading={<p role='status'>Loading…</p>}>
169
+ <ProductDetailsTitle />
170
+ <ProductDetailsPrice />
171
+ <ProductDetailsPurchase />
172
+ </ProductDetailsRoot>
173
+ )}
174
+ </QuickViewDialog>
175
+ ```
176
+
177
+ `loading` and `notFound` replace those states. `error` replaces the load failure; as a function, it receives `{ message, retry }`. Replacement content renders inside the root's element, which keeps its `className`, and the error keeps `role='alert'`; a replacement loading message should have its own `role='status'`.
178
+
158
179
  Parts read state from the root and take no product, SKU, price or quantity props. Whatever the layout, only a server-verified SKU can be added, and changing a selection resets the quantity and image. Every part throws if it is rendered outside `ProductDetailsRoot`. In development builds, if no control is rendered for an attribute the product requires, a console warning names the missing ones. A control that mounts later, such as one in a closed accordion, can trigger it; the product becomes purchasable once the control renders. Products without attributes but with several SKUs need `ProductDetailsOptions` or `ProductDetailsPurchase`. Parts can be rendered more than once, such as in a sticky mobile purchase bar; each copy gets its own element IDs.
159
180
 
160
181
  Every part accepts `className`, which is added to the part's outer element, a wrapper the package's CSS reset leaves alone. Host classes there control margin, padding, border, color, width, display, gap and grid placement, including layered utilities such as Tailwind v4's. In `ProductDetailsOptions` and `ProductDetailsPurchase`, each control keeps a 24px bottom margin, even in a host grid. The exception is a class name the package also uses, such as `grid-cols-1` or `p-6`: the package's rule wins, even over the host's responsive variants on the same element, such as `md:grid-cols-4`. The packaged layout sets its spacing and grid placement this way. Elements inside the part, such as the title's `<h1>`, keep the package's own styles: a host font size on `ProductDetailsTitle` does not change the heading's size, but a host color does reach it, because the heading inherits its color.
@@ -196,7 +217,7 @@ Parts are flat named exports, not properties such as `ProductDetails.Title`, so
196
217
 
197
218
  ## Custom catalog layouts
198
219
 
199
- `Catalog` is the recommended catalog. When its layout or its cards do not fit, compose the same parts inside `CatalogRoot`. The root loads one cursor page of six products, or the built-in samples when `sampleProducts` is set and no store is connected, and shows the connection state itself. This is `<Catalog sampleProducts />` written out with its packaged card:
220
+ `Catalog` is the recommended catalog. When its layout or its cards do not fit, compose the same parts inside `CatalogRoot`. The root loads one cursor page of six products, or `pageSize` products, or the built-in samples when `sampleProducts` is set and no store is connected, and shows the connection state itself. This is `<Catalog sampleProducts />` written out with its packaged card:
200
221
 
201
222
  ```tsx
202
223
  import {
@@ -233,12 +254,15 @@ As with product layouts, the grid, spacing and border classes come from your own
233
254
  | `CatalogHeader` | The `title` as the page's H1 and a `description`; leave it out when the host page owns the H1 |
234
255
  | `CatalogSummary` | The loading, failure or product-count status line |
235
256
  | `CatalogProducts` | One card per product. Its children are the card, rendered once for each product; without children, the packaged card |
257
+ | `CatalogEmpty` | Only when the store's catalog has no products: "No products available.", or its own children instead. While one is mounted, `CatalogProducts` leaves out its own empty message. Samples are never empty |
236
258
  | `CatalogPagination` | First-page and next-page buttons; nothing for samples |
237
259
  | `ProductCardImage`, `ProductCardTitle` | The first image and the name as an H2, each linked to the product page. With a title in the card, keyboard and screen reader users get only the title link; without one, the image link takes its place once the page is interactive |
238
260
  | `ProductCardDescription`, `ProductCardPrice` | Up to three lines of description (nothing without one), and the price or price range |
239
- | `ProductCardAction` | Adds the product directly only when it has one SKU and no options; otherwise links to the product page to choose |
261
+ | `ProductCardAction` | Adds the product directly only when it has one SKU and no options; otherwise links to the product page to choose. Its children replace the label; as a function, they receive the `action`: `add`, `choose-options` or `view-product` |
262
+
263
+ `className` on `CatalogProducts` goes on the element whose direct children are the cards, so a host grid places them, and `className` on `ProductCardRoot` goes on each card, so classes such as `col-span-2` work. While products load, that element holds three placeholder cards, so they follow the host's grid. The error with its retry button and, without a `CatalogEmpty`, the empty message render just before it. Card parts take no product, SKU or price props. Inside `CatalogProducts`, `ProductCardRoot` takes no `product` prop either and throws if it gets one, so a card always shows the product it is rendered for. Samples use the same card: they show a placeholder image, no description and no links, and add only to the sample cart. Card and catalog parts throw if they are rendered outside their root. Packaged cards fill their grid row, with the price and action at the bottom of each card.
240
264
 
241
- `className` on `CatalogProducts` goes on the element whose direct children are the cards, so a host grid places them, and `className` on `ProductCardRoot` goes on each card, so classes such as `col-span-2` work. While products load, that element holds three placeholder cards, so they follow the host's grid. The error with its retry button and the empty message render just before it. Card parts take no product, SKU or price props. Inside `CatalogProducts`, `ProductCardRoot` takes no `product` prop either and throws if it gets one, so a card always shows the product it is rendered for. Samples use the same card: they show a placeholder image, no description and no links, and add only to the sample cart. Card and catalog parts throw if they are rendered outside their root. Packaged cards fill their grid row, with the price and action at the bottom of each card.
265
+ The root keeps its page cursor in the page URL, as `after`. For a second catalog on the same page, such as a featured row above the main grid, set `urlState={false}` on it so the two do not share one cursor.
242
266
 
243
267
  For a product the host already has, such as a featured product, use `ProductCard` or `ProductCardRoot` with a `product` prop outside the catalog. `useProductCard()` returns read-only `product` (`null` for samples), `name`, `href` (`null` for samples) and `isSample` for building custom card parts. Every part accepts `className` on an outer wrapper that the package's CSS reset leaves alone, with the same limits as [product parts](#custom-product-layouts).
244
268
 
@@ -332,6 +356,60 @@ Two hooks support custom cart markup:
332
356
  - `useCart()` works anywhere inside `CommerceProvider`. It returns read-only `items`, `itemCount`, `subtotal` (minor units, or `null` when the server has not priced the cart), `currency`, `isSample` and `loading`. Combine `itemCount` with `useCommerce().setOpen(true)` for a custom cart button.
333
357
  - `useCartItem()` returns the line inside `CartItemRoot`: `id`, `name`, `quantity`, `imageUrl`, `options`, `subtotal` and `currency`.
334
358
 
359
+ ## Replacing text
360
+
361
+ The package has one rule for replacing content, so a layout never has to guess:
362
+
363
+ - **A part's own text** is the part's children, such as `<CartTitle>Your bag</CartTitle>`.
364
+ - **A state inside a loaded root**, such as an empty cart or an empty catalog, is its own part that renders only in that state: `CartEmpty` and `CatalogEmpty`. Place it anywhere in the layout.
365
+ - **A state before the root has anything to show**, such as loading, not found or a load failure, is a root prop: `ProductDetailsRoot`'s `loading`, `notFound` and `error`. Like `<Suspense fallback>`, it replaces the root's content, because the root's children render only once its data exists. That is why `useProductDetails()` can always return a product.
366
+
367
+ Parts whose wording a site often changes take replacement content as children. The part keeps its element, accessibility and rules: a replaced checkout label still waits for the cart total and still blocks a second click. As a function, the children receive the part's state, so the label can change with it. Content that is empty, such as `null`, `false`, blank text or an empty fragment, falls back to the package text. The package cannot see what a component of yours renders, so a label component that returns `null` leaves the control unlabeled. Make that choice in the children function instead, which can return `null` to keep the package text.
368
+
369
+ ```tsx
370
+ <CartCheckout>{({ busy }) => (busy ? 'Redirecting…' : 'Pay securely')}</CartCheckout>
371
+ <CartButton>{({ itemCount }) => `Bag (${itemCount})`}</CartButton>
372
+ <ProductDetailsAddToCart>{({ soldOut }) => (soldOut ? 'Sold out' : 'Add to bag')}</ProductDetailsAddToCart>
373
+ ```
374
+
375
+ | Part | Function receives |
376
+ | --- | --- |
377
+ | `CartTitle`, `CartDescription` | `{ isSample }` |
378
+ | `CartCheckout`, `CartItemRemove` | `{ busy }` |
379
+ | `CartButton` | `{ itemCount }`. Keep the count in the label |
380
+ | `AddToCartButton`, `ProductDetailsAddToCart` | `{ busy, soldOut }` |
381
+ | `ProductCardAction` | `{ action, busy, soldOut }` |
382
+ | `ProductDetailsBackLink` | Content only |
383
+
384
+ A replaced label stays the start of the control's accessible name, so voice control users can say what they see. Where the package adds the product name for screen readers, it follows as visually hidden text: `<CartItemRemove>Delete</CartItemRemove>` is named "Delete Mug". Controls without replacement text, such as "Increase quantity of Mug", keep English names.
385
+
386
+ ## Styling hooks
387
+
388
+ Every part's outer element has a `data-commerce-part` attribute with the part's name in kebab case, such as `cart-checkout` or `product-details-option`. Roots have one too, such as `product-details-root`, including while they show the connection, loading or error state. `CartButton` and add-to-cart buttons have `cart-button` and `add-to-cart-button` on the button itself. These names, and the controls' standard state attributes (`disabled`, `aria-busy`, `aria-pressed`, `aria-invalid`, `aria-expanded`), are a stable styling API; utility classes and other markup are not.
389
+
390
+ ```css
391
+ .commerce-storefront [data-commerce-part='cart-checkout'] button { border-radius: 9999px; }
392
+ .commerce-storefront [data-commerce-part='cart-checkout'] button[aria-busy='true'] { opacity: 0.7; }
393
+ .commerce-storefront [data-commerce-part='product-details-option'][data-commerce-attribute='color'] { display: none; }
394
+ ```
395
+
396
+ `ProductDetailsOption` also has `data-commerce-attribute` with its attribute name. Controls inside a part that has more than one have `data-commerce-control`: `decrease` and `increase` in `CartItemQuantity`, and `thumbnail` in `ProductDetailsGallery`. The package's utilities are unlayered and have the specificity of two classes, such as `.commerce-storefront .w-full`, or three with a state such as `:hover`. To override one, write unlayered CSS that starts with `.commerce-storefront` and a part attribute, and add the same state when the utility has one, as above. Layered host CSS, such as Tailwind v4 utilities, loses to the package's unlayered rules whatever its specificity.
397
+
398
+ ## Events
399
+
400
+ `onEvent` on `CommerceProvider` or `CommerceStorefront` receives every cart change once the server has returned the updated cart, and every cart failure. Sample-cart changes have `isSample: true` and never reach the store.
401
+
402
+ | `type` | Fields |
403
+ | --- | --- |
404
+ | `item-added` | `skuId` (`null` for samples), `name`, `quantity`, `isSample` |
405
+ | `quantity-changed` | `itemId`, `quantity`, `isSample` |
406
+ | `item-removed` | `itemId`, `isSample` |
407
+ | `discount-applied` | `code` |
408
+ | `checkout-started` | `cartId`; sent just before the browser leaves for checkout, so use `navigator.sendBeacon` or a keepalive request |
409
+ | `error` | `operation` (`add`, `change-quantity`, `remove`, `discount`, `refresh` or `checkout`) and the `message` the cart shows |
410
+
411
+ New event types may be added in minor releases, so ignore types you do not handle. A callback that throws or returns a rejected promise, including `onItemAdded`, is logged to the console and does not fail the cart operation. Async callbacks are not awaited, so a slow analytics call never delays the cart.
412
+
335
413
  ## How it works
336
414
 
337
415
  Products are SKU groups. The package purchases only an unambiguous SKU, verifies all selected attributes through the server, uses SKU prices/images, and distinguishes untracked inventory from sold-out inventory. Server-side inventory and pricing checks remain mandatory.
package/dist/index.d.ts CHANGED
@@ -13,7 +13,8 @@ declare class ApiError extends Error {
13
13
  readonly status: number;
14
14
  constructor(message: string, status: number, options?: ErrorOptions);
15
15
  }
16
- declare function money(value: number, currency: string): string;
16
+ /** Formats minor units, such as cents, as currency. Inside the provider, prefer `useMoney`, which follows its `locale`. */
17
+ declare function money(value: number, currency: string, locale?: string): string;
17
18
  //#endregion
18
19
  //#region src/cart-model.d.ts
19
20
  interface Money {
@@ -304,17 +305,41 @@ declare function getProductAttributes(skuGroup: SKUGroup | null | undefined): St
304
305
  /** The product's display name: its label, then its name. */
305
306
  declare function getProductName(skuGroup: SKUGroup): string;
306
307
  //#endregion
308
+ //#region src/part.d.ts
309
+ /** Replacement content for a part's text: content, or a function of the part's state. */
310
+ type PartLabel<State> = ReactNode | ((state: State) => ReactNode);
311
+ //#endregion
307
312
  //#region src/cart.d.ts
308
- declare function CartButton(): ReactElement;
309
- declare function AddToCartButton({
310
- sku,
311
- name,
312
- quantity
313
- }: {
313
+ interface CartButtonProps {
314
+ /** Replaces the label and count. A function receives the item count. Keep the count in the label. */
315
+ children?: PartLabel<{
316
+ itemCount: number;
317
+ }>;
318
+ }
319
+ /** Opens the cart drawer and shows the item count. */
320
+ declare function CartButton({
321
+ children
322
+ }?: CartButtonProps): ReactElement;
323
+ /** What an add-to-cart label can depend on. */
324
+ interface AddToCartLabelState {
325
+ /** The add is running. */
326
+ readonly busy: boolean;
327
+ /** Tracked stock is used up, so the button stays disabled. */
328
+ readonly soldOut: boolean;
329
+ }
330
+ interface AddToCartButtonProps {
314
331
  sku: SKU;
315
332
  name: string;
316
333
  quantity?: number;
317
- }): ReactElement;
334
+ /** Replaces the label. A function receives `busy` and `soldOut`; the button keeps its rules either way. */
335
+ children?: PartLabel<AddToCartLabelState>;
336
+ }
337
+ declare function AddToCartButton({
338
+ sku,
339
+ name,
340
+ quantity,
341
+ children
342
+ }: AddToCartButtonProps): ReactElement;
318
343
  //#endregion
319
344
  //#region src/cart-state.d.ts
320
345
  /** One cart line, for custom cart parts. */
@@ -351,6 +376,20 @@ declare function useCart(): CartState;
351
376
  interface CartPartProps {
352
377
  className?: string;
353
378
  }
379
+ /** Props for a cart part whose text the host can replace, with or without the sample wording. */
380
+ interface CartTextPartProps extends CartPartProps {
381
+ /** Replaces the text. A function receives whether the cart is the sample cart. */
382
+ children?: PartLabel<{
383
+ isSample: boolean;
384
+ }>;
385
+ }
386
+ /** Props for a cart button whose label the host can replace. The button keeps its behavior and state. */
387
+ interface CartActionPartProps extends CartPartProps {
388
+ /** Replaces the button label. A function receives whether the action is running. */
389
+ children?: PartLabel<{
390
+ busy: boolean;
391
+ }>;
392
+ }
354
393
  interface CartRootProps {
355
394
  children: ReactNode;
356
395
  /** Added to the root `<section>`. */
@@ -380,12 +419,14 @@ declare function CartDrawerRoot({
380
419
  }: CartDrawerRootProps): ReactElement;
381
420
  /** "Your cart", or "Sample cart" before a store is connected, as an H2. In the drawer it names the dialog. */
382
421
  declare function CartTitle({
383
- className
384
- }: CartPartProps): ReactElement;
422
+ className,
423
+ children
424
+ }: CartTextPartProps): ReactElement;
385
425
  /** One line about the cart. In the drawer it describes the dialog. */
386
426
  declare function CartDescription({
387
- className
388
- }: CartPartProps): ReactElement;
427
+ className,
428
+ children
429
+ }: CartTextPartProps): ReactElement;
389
430
  /** The drawer's close button. Renders nothing outside the drawer. */
390
431
  declare function CartClose({
391
432
  className
@@ -440,8 +481,9 @@ declare function CartItemQuantity({
440
481
  }: CartPartProps): ReactElement;
441
482
  /** Removes the line. */
442
483
  declare function CartItemRemove({
443
- className
444
- }: CartPartProps): ReactElement;
484
+ className,
485
+ children
486
+ }: CartActionPartProps): ReactElement;
445
487
  interface CartEmptyProps extends CartPartProps {
446
488
  /** Replaces the default message and link. */
447
489
  children?: ReactNode;
@@ -475,8 +517,9 @@ declare function CartCheckoutNote({
475
517
  * Samples never check out. Renders nothing for an empty cart.
476
518
  */
477
519
  declare function CartCheckout({
478
- className
479
- }: CartPartProps): ReactElement | null;
520
+ className,
521
+ children
522
+ }: CartActionPartProps): ReactElement | null;
480
523
  /** The packaged cart drawer. Compose `CartDrawerRoot` and the cart parts for a custom drawer. */
481
524
  declare function CartDrawer(): ReactElement;
482
525
  //#endregion
@@ -491,12 +534,21 @@ interface CatalogRootProps {
491
534
  className?: string;
492
535
  /** Permit built-in examples only while the host confirms this site is unbound. */
493
536
  sampleProducts?: boolean;
537
+ /** Products per page, a whole number of 1 or more. Defaults to 6. Samples are always one page of six. */
538
+ pageSize?: number;
539
+ /**
540
+ * Keep the page cursor in the page URL, as `after`, so a reload or a shared link keeps the page. Defaults to
541
+ * `true`. Set `false` for every other catalog on the same page, so the catalogs do not share one cursor.
542
+ */
543
+ urlState?: boolean;
494
544
  }
495
545
  /** Loads one cursor page of products, or the built-in samples, and provides them to catalog parts. */
496
546
  declare function CatalogRoot({
497
547
  children,
498
548
  className,
499
- sampleProducts
549
+ sampleProducts,
550
+ pageSize,
551
+ urlState
500
552
  }: CatalogRootProps): ReactElement;
501
553
  interface CatalogHeaderProps extends CatalogPartProps {
502
554
  title?: string;
@@ -518,12 +570,24 @@ interface CatalogProductsProps extends CatalogPartProps {
518
570
  }
519
571
  /**
520
572
  * The product cards. `className` goes on the element that contains the cards, such as a grid. While loading, that
521
- * element holds placeholder cards. The error with its retry and the empty message render just before it.
573
+ * element holds placeholder cards. The error with its retry and, without a `CatalogEmpty`, the empty message render just before it.
522
574
  */
523
575
  declare function CatalogProducts({
524
576
  className,
525
577
  children
526
578
  }: CatalogProductsProps): ReactElement;
579
+ interface CatalogEmptyProps extends CatalogPartProps {
580
+ /** Replaces the default message. */
581
+ children?: ReactNode;
582
+ }
583
+ /**
584
+ * Renders only when a connected store's catalog has no products: "No products available.", or its children. While
585
+ * one is mounted, `CatalogProducts` leaves out its own empty message. Samples are never empty.
586
+ */
587
+ declare function CatalogEmpty({
588
+ className,
589
+ children
590
+ }: CatalogEmptyProps): ReactElement | null;
527
591
  /** First-page and next-page buttons. Renders nothing for samples, which have one page. */
528
592
  declare function CatalogPagination({
529
593
  className
@@ -537,13 +601,16 @@ interface CatalogProps {
537
601
  showHeader?: boolean;
538
602
  /** Permit built-in examples only while the host confirms this site is unbound. */
539
603
  sampleProducts?: boolean;
604
+ /** Products per page. Defaults to 6. */
605
+ pageSize?: number;
540
606
  }
541
607
  /** The packaged catalog. Compose `CatalogRoot` and the catalog parts for a custom layout. */
542
608
  declare function Catalog({
543
609
  title,
544
610
  description,
545
611
  showHeader,
546
- sampleProducts
612
+ sampleProducts,
613
+ pageSize
547
614
  }: CatalogProps): ReactElement;
548
615
  //#endregion
549
616
  //#region src/commerce-provider.d.ts
@@ -553,6 +620,8 @@ interface CommerceContextValue {
553
620
  retryConnection: () => void;
554
621
  theme: StorefrontTheme;
555
622
  config: StorefrontConfig;
623
+ /** The BCP 47 locale that prices are formatted in, such as `en-US`. */
624
+ locale: string;
556
625
  cart: CartOrder | null;
557
626
  /** Whether the cart drawer is open. Always `false` when no `CartDrawer` or `CartDrawerRoot` is mounted. */
558
627
  open: boolean;
@@ -573,6 +642,11 @@ interface CommerceContextValue {
573
642
  checkout: () => Promise<boolean>;
574
643
  }
575
644
  declare function useCommerce(): CommerceContextValue;
645
+ /**
646
+ * Formats minor units, such as cents, in the provider's `locale`, the way the package's own parts do. Uses the
647
+ * store's currency unless `currency` is given.
648
+ */
649
+ declare function useMoney(): (value: number, currency?: string) => string;
576
650
  type StorefrontTheme = CSSProperties & {
577
651
  '--commerce-accent'?: string;
578
652
  '--commerce-accent-hover'?: string;
@@ -589,12 +663,54 @@ interface CommerceProviderProps {
589
663
  theme?: StorefrontTheme;
590
664
  /** Set only when your server has enabled checkout and this return route exists. */
591
665
  checkoutSuccessPath?: string;
666
+ /**
667
+ * The BCP 47 locale for formatting prices, such as `de-DE`. Defaults to `en-US`; an invalid tag falls back to it.
668
+ * It never selects UI text: localized text will be a separate, opt-in prop, so setting `locale` cannot change wording.
669
+ */
670
+ locale?: string;
592
671
  /**
593
672
  * Called after an item is added, live or sample. With a drawer mounted, the drawer also opens. Without one, such as
594
673
  * on a site with a cart page, use it to confirm the add.
595
674
  */
596
675
  onItemAdded?: (item: AddedCartItem) => void;
676
+ /** Called for every cart change and failure, such as for analytics. See `CommerceEvent`. */
677
+ onEvent?: (event: CommerceEvent) => void;
597
678
  }
679
+ /** A cart operation that can fail. */
680
+ type CommerceOperation = 'add' | 'change-quantity' | 'remove' | 'discount' | 'refresh' | 'checkout';
681
+ /**
682
+ * What `onEvent` reports. Live changes are reported once the server returns the updated cart; sample changes have
683
+ * `isSample: true` and never reach the store. New event types may be added, so ignore types you do not handle.
684
+ */
685
+ type CommerceEvent = {
686
+ readonly type: 'item-added';
687
+ /** `null` for a sample product, which is not a SKU. */
688
+ readonly skuId: string | null;
689
+ readonly name: string;
690
+ readonly quantity: number;
691
+ readonly isSample: boolean;
692
+ } | {
693
+ readonly type: 'quantity-changed';
694
+ readonly itemId: string;
695
+ readonly quantity: number;
696
+ readonly isSample: boolean;
697
+ } | {
698
+ readonly type: 'item-removed';
699
+ readonly itemId: string;
700
+ readonly isSample: boolean;
701
+ } | {
702
+ readonly type: 'discount-applied';
703
+ readonly code: string;
704
+ }
705
+ /** Sent just before the browser leaves for checkout. */ | {
706
+ readonly type: 'checkout-started';
707
+ readonly cartId: string;
708
+ }
709
+ /** `message` is the one the cart shows. */ | {
710
+ readonly type: 'error';
711
+ readonly operation: CommerceOperation;
712
+ readonly message: string;
713
+ };
598
714
  /** The item passed to `onItemAdded`. */
599
715
  interface AddedCartItem {
600
716
  readonly name: string;
@@ -608,7 +724,9 @@ declare function CommerceProvider({
608
724
  productPath,
609
725
  checkoutSuccessPath,
610
726
  theme,
611
- onItemAdded
727
+ locale,
728
+ onItemAdded,
729
+ onEvent
612
730
  }: CommerceProviderProps): ReactElement;
613
731
  //#endregion
614
732
  //#region src/commerce-storefront.d.ts
@@ -619,6 +737,22 @@ declare function CommerceStorefront({
619
737
  }: CommerceProviderProps): react_jsx_runtime0.JSX.Element;
620
738
  //#endregion
621
739
  //#region src/product-card.d.ts
740
+ interface ProductImageProps {
741
+ url?: string | null;
742
+ /** The alt text. Pass `''` for a decorative image, such as a thumbnail next to the product name. */
743
+ name: string;
744
+ /** Added to the image, or to its placeholder. */
745
+ className?: string;
746
+ }
747
+ /**
748
+ * A product image that fills its container, with a labeled placeholder when there is no URL or it fails to load.
749
+ * Use it in custom parts so they handle missing images like the package's own.
750
+ */
751
+ declare function ProductImage({
752
+ url,
753
+ name,
754
+ className
755
+ }: ProductImageProps): ReactElement;
622
756
  /** Read-only card state for custom parts rendered inside `ProductCardRoot`. */
623
757
  interface ProductCardState {
624
758
  /** `null` for a built-in sample, which is not a catalog product. */
@@ -666,13 +800,23 @@ declare function ProductCardDescription({
666
800
  declare function ProductCardPrice({
667
801
  className
668
802
  }: ProductCardPartProps): ReactElement;
803
+ /** What a card action label can depend on. Only `add` can be busy or sold out. */
804
+ interface ProductCardActionLabelState extends AddToCartLabelState {
805
+ /** `add` adds directly; `choose-options` and `view-product` link to the product page. */
806
+ readonly action: 'add' | 'choose-options' | 'view-product';
807
+ }
808
+ interface ProductCardActionProps extends ProductCardPartProps {
809
+ /** Replaces the button or link label. A function receives the action and its state. */
810
+ children?: PartLabel<ProductCardActionLabelState>;
811
+ }
669
812
  /**
670
813
  * Adds the product directly when it has exactly one SKU and no options; otherwise links to its page to choose.
671
814
  * Samples add to the sample cart.
672
815
  */
673
816
  declare function ProductCardAction({
674
- className
675
- }: ProductCardPartProps): ReactElement;
817
+ className,
818
+ children
819
+ }: ProductCardActionProps): ReactElement;
676
820
  interface ProductCardProps {
677
821
  product: SKUGroup;
678
822
  }
@@ -724,19 +868,45 @@ interface ProductDetailsRootProps {
724
868
  children: ReactNode;
725
869
  /** Added to the root `<article>`. */
726
870
  className?: string;
727
- }
728
- /** Loads the product for the route's `:productId` and provides state to product parts. */
871
+ /** The product to show. Defaults to the route's `:productId` parameter. */
872
+ productId?: string;
873
+ /**
874
+ * Keep the selected options and SKU in the page URL, as `option.<attribute>` and `sku`, so a reload or a shared link
875
+ * keeps them. Defaults to `true`. Set `false` for every other root on the same page, such as a quick view, so the
876
+ * roots do not share one selection.
877
+ */
878
+ urlState?: boolean;
879
+ /** Replaces the loading message. */
880
+ loading?: ReactNode;
881
+ /** Replaces the not-found heading and link. */
882
+ notFound?: ReactNode;
883
+ /** Replaces the load failure. A function receives the failure message and a `retry` for the product request. */
884
+ error?: PartLabel<ProductDetailsErrorState>;
885
+ }
886
+ /** What a replacement for the product load failure receives. */
887
+ interface ProductDetailsErrorState {
888
+ readonly message: string;
889
+ readonly retry: () => void;
890
+ }
891
+ /** Loads one product, by default the route's `:productId`, and provides state to product parts. */
729
892
  declare function ProductDetailsRoot({
730
- children,
731
- className
893
+ productId: productIdProp,
894
+ className,
895
+ ...props
732
896
  }: ProductDetailsRootProps): ReactElement;
733
897
  //#endregion
734
898
  //#region src/product-details.d.ts
735
899
  /** The packaged product page for `${productPath}/:productId`. Compose the parts for a custom layout. */
736
900
  declare function ProductDetails(): ReactElement;
901
+ interface ProductDetailsBackLinkProps extends ProductDetailsPartProps {
902
+ /** Replaces "← Back to shop". */
903
+ children?: ReactNode;
904
+ }
905
+ /** A link to the catalog. */
737
906
  declare function ProductDetailsBackLink({
738
- className
739
- }: ProductDetailsPartProps): ReactElement;
907
+ className,
908
+ children
909
+ }: ProductDetailsBackLinkProps): ReactElement;
740
910
  /** Main image with thumbnails; the selected SKU's images replace the product's when present. */
741
911
  declare function ProductDetailsGallery({
742
912
  className
@@ -784,12 +954,17 @@ declare function ProductDetailsAvailability({
784
954
  declare function ProductDetailsQuantity({
785
955
  className
786
956
  }: ProductDetailsPartProps): ReactElement | null;
957
+ interface ProductDetailsAddToCartProps extends ProductDetailsPartProps {
958
+ /** Replaces the button label. A function receives `busy` and `soldOut`; the button keeps its rules either way. */
959
+ children?: PartLabel<AddToCartLabelState>;
960
+ }
787
961
  /** Adds the verified SKU and quantity. While disabled, its accessible description says why. */
788
962
  declare function ProductDetailsAddToCart({
789
- className
790
- }: ProductDetailsPartProps): ReactElement;
963
+ className,
964
+ children
965
+ }: ProductDetailsAddToCartProps): ReactElement;
791
966
  //#endregion
792
967
  //#region src/storefront-surface.d.ts
793
968
  declare function CommerceStatus(): ReactElement | null;
794
969
  //#endregion
795
- export { AddToCartButton, AddToCartItemInput, type AddedCartItem, ApiError, CartAddress, CartButton, CartCheckout, CartCheckoutNote, CartClose, CartDescription, CartDiscount, CartDrawer, CartDrawerRoot, type CartDrawerRootProps, CartEmpty, type CartEmptyProps, CartItemImage, CartItemName, CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, type CartItemRootProps, type CartItemState, CartItems, type CartItemsProps, CartLineItem, CartLineItemDetails, CartNote, CartNotices, CartOrder, type CartPartProps, CartRoot, type CartRootProps, CartSelectedAddon, CartSelectedAddonValue, CartSelectedOption, CartShippingInfo, type CartState, CartSubtotal, CartSummary, type CartSummaryProps, CartSummaryTotals, CartTax, CartTitle, Catalog, CatalogHeader, type CatalogHeaderProps, CatalogPagination, type CatalogPartProps, CatalogProducts, type CatalogProductsProps, type CatalogProps, CatalogRoot, type CatalogRootProps, CatalogSummary, type CommerceContextValue, CommerceProvider, type CommerceProviderProps, CommerceStatus, CommerceStorefront, Connection, ConnectionEdge, DraftOrderContext, InventoryCount, LabelFilter, LineItemTotals, MediaObject, Money, OrderTotals, PageInfo, PriceRange, ProductCard, ProductCardAction, ProductCardDescription, ProductCardImage, type ProductCardPartProps, ProductCardPrice, type ProductCardProps, ProductCardRoot, type ProductCardRootProps, type ProductCardState, ProductCardTitle, ProductDetails, ProductDetailsAddToCart, ProductDetailsAvailability, ProductDetailsBackLink, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsOption, type ProductDetailsOptionProps, type ProductDetailsOptionState, type ProductDetailsOptionValueState, ProductDetailsOptions, type ProductDetailsPartProps, ProductDetailsPrice, ProductDetailsPurchase, ProductDetailsPurchaseStatus, ProductDetailsQuantity, ProductDetailsRoot, type ProductDetailsRootProps, type ProductDetailsState, type ProductDetailsStatus, ProductDetailsTitle, ProductDetailsVariablesInput, ProductGridVariablesInput, SKU, SKUAttributeValue, SKUGroup, SKUGroupAttribute, SKUGroupAttributeValue, SKUGroupSKU, SKUPrice, SkuGroupResult, SkuGroupVariables, SkuGroupsResult, SkuGroupsVariables, SkuResult, SkuVariables, type StorefrontConfig, StorefrontProductAttribute, StorefrontProductAttributeValue, type StorefrontTheme, StringInFilter, addToCart, getAvailableInventoryQuantity, getCartSummaryTotals, getImageUrls, getLabeledSkuOptions, getPrimaryImageUrl, getProductAttributes, getProductName, getSingleMatchedSku, getSingleMatchedSkuId, money, useCart, useCartItem, useCommerce, useProductCard, useProductDetails, useProductDetailsOption };
970
+ export { AddToCartButton, type AddToCartButtonProps, AddToCartItemInput, type AddToCartLabelState, type AddedCartItem, ApiError, type CartActionPartProps, CartAddress, CartButton, type CartButtonProps, CartCheckout, CartCheckoutNote, CartClose, CartDescription, CartDiscount, CartDrawer, CartDrawerRoot, type CartDrawerRootProps, CartEmpty, type CartEmptyProps, CartItemImage, CartItemName, CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, type CartItemRootProps, type CartItemState, CartItems, type CartItemsProps, CartLineItem, CartLineItemDetails, CartNote, CartNotices, CartOrder, type CartPartProps, CartRoot, type CartRootProps, CartSelectedAddon, CartSelectedAddonValue, CartSelectedOption, CartShippingInfo, type CartState, CartSubtotal, CartSummary, type CartSummaryProps, CartSummaryTotals, CartTax, type CartTextPartProps, CartTitle, Catalog, CatalogEmpty, type CatalogEmptyProps, CatalogHeader, type CatalogHeaderProps, CatalogPagination, type CatalogPartProps, CatalogProducts, type CatalogProductsProps, type CatalogProps, CatalogRoot, type CatalogRootProps, CatalogSummary, type CommerceContextValue, type CommerceEvent, type CommerceOperation, CommerceProvider, type CommerceProviderProps, CommerceStatus, CommerceStorefront, Connection, ConnectionEdge, DraftOrderContext, InventoryCount, LabelFilter, LineItemTotals, MediaObject, Money, OrderTotals, PageInfo, type PartLabel, PriceRange, ProductCard, ProductCardAction, type ProductCardActionLabelState, type ProductCardActionProps, ProductCardDescription, ProductCardImage, type ProductCardPartProps, ProductCardPrice, type ProductCardProps, ProductCardRoot, type ProductCardRootProps, type ProductCardState, ProductCardTitle, ProductDetails, ProductDetailsAddToCart, type ProductDetailsAddToCartProps, ProductDetailsAvailability, ProductDetailsBackLink, type ProductDetailsBackLinkProps, ProductDetailsDescription, type ProductDetailsErrorState, ProductDetailsGallery, ProductDetailsOption, type ProductDetailsOptionProps, type ProductDetailsOptionState, type ProductDetailsOptionValueState, ProductDetailsOptions, type ProductDetailsPartProps, ProductDetailsPrice, ProductDetailsPurchase, ProductDetailsPurchaseStatus, ProductDetailsQuantity, ProductDetailsRoot, type ProductDetailsRootProps, type ProductDetailsState, type ProductDetailsStatus, ProductDetailsTitle, ProductDetailsVariablesInput, ProductGridVariablesInput, ProductImage, type ProductImageProps, SKU, SKUAttributeValue, SKUGroup, SKUGroupAttribute, SKUGroupAttributeValue, SKUGroupSKU, SKUPrice, SkuGroupResult, SkuGroupVariables, SkuGroupsResult, SkuGroupsVariables, SkuResult, SkuVariables, type StorefrontConfig, StorefrontProductAttribute, StorefrontProductAttributeValue, type StorefrontTheme, StringInFilter, addToCart, getAvailableInventoryQuantity, getCartSummaryTotals, getImageUrls, getLabeledSkuOptions, getPrimaryImageUrl, getProductAttributes, getProductName, getSingleMatchedSku, getSingleMatchedSkuId, money, useCart, useCartItem, useCommerce, useMoney, useProductCard, useProductDetails, useProductDetailsOption };