@godaddy/gd-commerce-storefront 0.3.0 → 0.5.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
@@ -54,11 +54,12 @@ Use the providers your application already has; do not create another router or
54
54
  | `catalogPath` | `/shop` | Same-origin catalog route |
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
+ | `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 |
57
58
  | `theme` | inherited text and transparent wrappers; neutral control palette | CSS custom properties applied to storefront surfaces and the portalled drawer |
58
59
 
59
60
  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.
60
61
 
61
- `GET /api/commerce/config` supplies the currency and opaque cart scope. The scope must change when the store, channel, or currency binding changes. The draft-order cart displays its subtotal and explains that shipping, taxes, and discounts are calculated at checkout. The explanation has the stable `commerce-cart-checkout-adjustments-note` class so applications can hide it when needed. The cart does not create a checkout session or calculate adjustments. Applications do not pass store IDs or credentials into the browser package. One storefront binding is supported per page and query client.
62
+ `GET /api/commerce/config` supplies the currency and opaque cart scope. The scope must change when the store, channel, or currency binding changes. The draft-order cart displays its subtotal and explains that shipping, taxes, and discounts are calculated at checkout. The explanation's outer element has the stable `commerce-cart-checkout-adjustments-note` class so applications can hide it when needed. The cart does not create a checkout session or calculate adjustments. Applications do not pass store IDs or credentials into the browser package. One storefront binding is supported per page and query client.
62
63
 
63
64
  A connection failure leaves the surrounding application and its state mounted. Catalog and product surfaces show the connection error and retry action. Custom integrations can render `CommerceStatus` or inspect `useCommerce().connection`.
64
65
 
@@ -78,15 +79,15 @@ A connection failure leaves the surrounding application and its state mounted. C
78
79
  }}
79
80
  >
80
81
  {/* Register /collection and /item/:productId in your router. */}
81
- <Catalog title='Our collection' description='Made for everyday use.' />
82
+ <Catalog />
82
83
  </CommerceStorefront>
83
84
  ```
84
85
 
85
86
  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`.
86
87
 
87
- 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 parts](#custom-product-layouts) to change a product page's layout.
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.
88
89
 
89
- The first release uses English UI text and `en-US` currency formatting. Catalog title and description are configurable. Full localization is outside this initial API.
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.
90
91
 
91
92
  ## Compatibility
92
93
 
@@ -99,19 +100,22 @@ The repository example uses React Router 7. Follow each router version's own Rea
99
100
  | Export | Purpose |
100
101
  | --- | --- |
101
102
  | `CommerceStorefront` | Recommended integration: provider and one drawer |
102
- | `Catalog` | Six products per cursor page, with `title`, `description`, and `showHeader` props |
103
+ | `Catalog` | Six products per cursor page. Its `title`, `description`, and `showHeader` props are deprecated; compose `CatalogRoot` instead |
104
+ | `CatalogRoot` and catalog parts | Build a [custom catalog or card layout](#custom-catalog-layouts) with the same rules as `Catalog` |
103
105
  | `ProductDetails` | Reads `:productId`; validates URL option selections against catalog results |
104
106
  | `ProductDetailsRoot` and product parts | Build a [custom product layout](#custom-product-layouts) with the same rules as `ProductDetails` |
105
- | `ProductCard` | Renders one `SKUGroup` with direct add or a product-details link |
106
- | `CartButton` | Opens the shared drawer and displays item count |
107
- | `AddToCartButton` | Adds a verified `sku`, `name`, and optional integer `quantity` |
107
+ | `ProductCard` | Renders one `SKUGroup` with direct add or a product-details link; `ProductCardRoot` and card parts customize it |
108
+ | `CartDrawer` | The cart dialog; `CommerceStorefront` mounts one |
109
+ | `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 |
108
112
  | `CommerceStatus` | Connection progress/error/retry for custom layouts |
109
113
  | `useCommerce` | Cart, connection, pending/error state and serialized cart actions |
110
- | `CommerceProvider`, `CartDrawer` | Lower-level composition when the recommended wrapper does not fit; mount each once |
114
+ | `CommerceProvider` | Lower-level composition when the recommended wrapper does not fit, such as a custom drawer; mount it once |
111
115
 
112
116
  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.
113
117
 
114
- `Catalog` renders its `title` as an H1 by default. When the host page owns its semantic heading, render that page H1 and pass `showHeader={false}` so the document still has exactly one H1.
118
+ `Catalog` renders its title as an H1. When the host page owns its semantic heading, compose `CatalogRoot` without `CatalogHeader` (see [custom catalog layouts](#custom-catalog-layouts)) so the document still has exactly one H1. `Catalog`'s `title`, `description` and `showHeader` props still work but are deprecated: pass the copy to `CatalogHeader`, or leave `CatalogHeader` out.
115
119
 
116
120
  ## Custom product layouts
117
121
 
@@ -146,14 +150,26 @@ Add your own elements between parts, reorder them, or leave out display parts. `
146
150
  | `ProductDetailsOptions` | A select for every attribute, or the SKU list for products without attributes |
147
151
  | `ProductDetailsOption` | A select for one attribute, by `attribute` name; nothing when the product lacks it |
148
152
  | `ProductDetailsPurchaseStatus` | The one message explaining why the selection is not purchasable yet; nothing when ready |
149
- | `ProductDetailsAvailability`, `ProductDetailsQuantity` | Stock and quantity for the verified SKU; nothing before verification |
153
+ | `ProductDetailsAvailability`, `ProductDetailsQuantity` | Stock and quantity for the verified SKU; nothing before verification. The quantity shows a message when it cannot be added, such as more than the stock |
150
154
  | `ProductDetailsAddToCart` | Adds the verified SKU and quantity; before a SKU is verified, its accessible description gives the reason |
151
155
 
152
156
  Every part is named `ProductDetails*` because it works only inside `ProductDetailsRoot`. `AddToCartButton` is the standalone button for a SKU you already have.
153
157
 
154
158
  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.
155
159
 
156
- 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 and grid placement, including layered utilities such as Tailwind v4's. 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.
160
+ 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.
161
+
162
+ Your own elements inside a root are a limit for now. The package's unlayered reset matches every element inside a root that is not a part's outer element, so layered host styles lose there:
163
+ - **Color, font family and border** on any element.
164
+ - **Margin, padding, font size and weight** on headings, paragraphs, lists and fieldsets.
165
+ - **Font, color, background and border radius** on buttons, inputs and selects. Custom controls such as the swatches below are affected.
166
+
167
+ Until the package scopes its reset, you can work around it:
168
+ - Keep page headings outside the root.
169
+ - Put colors on a part's `className`, which its children inherit.
170
+ - Mark the affected declarations `!important`; a layered `!important` beats unlayered CSS.
171
+
172
+ A part that renders nothing, such as `ProductDetailsAvailability` before a SKU is verified, leaves no grid cell, so the cells after it move up in a host grid. Wrap such parts in your own element or use named grid areas.
157
173
 
158
174
  Two hooks support custom parts inside the root:
159
175
 
@@ -178,6 +194,144 @@ function ColorSwatches() {
178
194
 
179
195
  Parts are flat named exports, not properties such as `ProductDetails.Title`, so each one is an ordinary module export.
180
196
 
197
+ ## Custom catalog layouts
198
+
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:
200
+
201
+ ```tsx
202
+ import {
203
+ CatalogHeader, CatalogPagination, CatalogProducts, CatalogRoot, CatalogSummary, ProductCardAction,
204
+ ProductCardDescription, ProductCardImage, ProductCardPrice, ProductCardRoot, ProductCardTitle,
205
+ } from '@godaddy/gd-commerce-storefront';
206
+
207
+ <CatalogRoot sampleProducts>
208
+ <CatalogHeader className='border-b border-neutral-200 pb-10 pt-4 sm:pb-14 sm:pt-8' />
209
+ <CatalogSummary className='border-b border-neutral-200 py-6 sm:py-8' />
210
+ <CatalogProducts className='grid grid-cols-1 gap-6 py-8 sm:grid-cols-2 lg:grid-cols-3 lg:gap-8 lg:py-10'>
211
+ <ProductCardRoot className='flex min-w-0 flex-col overflow-hidden rounded-lg border border-neutral-200'>
212
+ <ProductCardImage />
213
+ <div className='flex flex-1 flex-col p-6'>
214
+ <ProductCardTitle />
215
+ <ProductCardDescription className='mt-3' />
216
+ <div className='mt-auto pt-6'>
217
+ <div className='flex flex-wrap items-end justify-between gap-4 border-t border-neutral-200 pt-5'>
218
+ <ProductCardPrice className='min-w-0 flex-1' />
219
+ <ProductCardAction className='max-w-full' />
220
+ </div>
221
+ </div>
222
+ </div>
223
+ </ProductCardRoot>
224
+ </CatalogProducts>
225
+ <CatalogPagination className='border-t border-neutral-200 pt-6' />
226
+ </CatalogRoot>
227
+ ```
228
+
229
+ As with product layouts, the grid, spacing and border classes come from your own stylesheet in a custom layout. Without them, the cards stack in one column.
230
+
231
+ | Part | Renders |
232
+ | --- | --- |
233
+ | `CatalogHeader` | The `title` as the page's H1 and a `description`; leave it out when the host page owns the H1 |
234
+ | `CatalogSummary` | The loading, failure or product-count status line |
235
+ | `CatalogProducts` | One card per product. Its children are the card, rendered once for each product; without children, the packaged card |
236
+ | `CatalogPagination` | First-page and next-page buttons; nothing for samples |
237
+ | `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
+ | `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 |
240
+
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.
242
+
243
+ 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
+
245
+ ## Custom cart layouts
246
+
247
+ `CartDrawer` is the recommended cart. `CommerceStorefront` mounts it. When its layout does not fit, use `CommerceProvider` and mount your own `CartDrawerRoot` once in its place. Development builds warn when more than one drawer is mounted, because every add would open all of them. It opens when an item is added or a `CartButton` is pressed and returns focus when it closes, like the packaged drawer. This is `CartDrawer` written out:
248
+
249
+ ```tsx
250
+ import {
251
+ CartCheckout, CartCheckoutNote, CartClose, CartDescription, CartDrawerRoot, CartEmpty, CartItemImage, CartItemName,
252
+ CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, CartItems, CartNotices, CartSubtotal,
253
+ CartSummary, CartTitle, CommerceProvider,
254
+ } from '@godaddy/gd-commerce-storefront';
255
+
256
+ <CommerceProvider>
257
+ {/* Header and routes */}
258
+ <CartDrawerRoot className='flex w-full max-w-lg flex-col bg-white text-neutral-900 shadow-xl'>
259
+ <div className='flex items-start justify-between border-b border-neutral-200 p-6'>
260
+ <div>
261
+ <CartTitle />
262
+ <CartDescription className='mt-1' />
263
+ </div>
264
+ <CartClose />
265
+ </div>
266
+ <div className='flex-1 overflow-y-auto p-6'>
267
+ <CartNotices className='mb-4' />
268
+ <CartItems className='divide-y divide-neutral-200'>
269
+ <CartItemRoot className='py-5 first:pt-0'>
270
+ <div className='flex gap-4'>
271
+ <CartItemImage />
272
+ <div className='min-w-0 flex-1'>
273
+ <CartItemName />
274
+ <CartItemOptions />
275
+ <CartItemPrice className='mt-1' />
276
+ </div>
277
+ </div>
278
+ <div className='mt-4 flex items-center justify-between gap-2'>
279
+ <CartItemQuantity />
280
+ <CartItemRemove />
281
+ </div>
282
+ </CartItemRoot>
283
+ </CartItems>
284
+ <CartEmpty className='py-16 text-center' />
285
+ </div>
286
+ <CartSummary className='border-t border-neutral-200 p-6'>
287
+ <CartSubtotal />
288
+ <CartCheckoutNote className='mt-3' />
289
+ <CartCheckout />
290
+ </CartSummary>
291
+ </CartDrawerRoot>
292
+ </CommerceProvider>
293
+ ```
294
+
295
+ `className` on `CartDrawerRoot` goes on the dialog panel, which sits at the right edge at full height. The packaged width, background and column layout are the classes above; without them the panel is only as wide as its content and has no background. Add `mr-auto` to open it from the left. Keep `CartTitle` in a drawer: it names the dialog, and the dialog library logs an error without it. `CartDescription` is optional and describes the dialog when present. Render each of them once per drawer, because they use the dialog's own element IDs.
296
+
297
+ For a cart page instead of, or as well as, the drawer, compose the same parts inside `CartRoot` on a route such as `/cart`. It shows the connection state itself, and `CartClose` renders nothing there. If the site has no drawer, use `CommerceProvider` without `CartDrawerRoot` and link to the page instead of rendering `CartButton`. Adds then open nothing, and `useCommerce().open` stays `false`. Confirm adds with the provider's `onItemAdded`, for example with a toast that links to the cart.
298
+
299
+ ```tsx
300
+ <h1>Your cart</h1>
301
+ <CartRoot className='grid gap-8 lg:grid-cols-3'>
302
+ <div className='lg:col-span-2'>
303
+ <CartNotices className='mb-4' />
304
+ <CartItems className='divide-y divide-neutral-200' />
305
+ <CartEmpty className='py-16 text-center' />
306
+ </div>
307
+ <CartSummary className='rounded-lg border border-neutral-200 p-6'>
308
+ <CartSubtotal />
309
+ <CartCheckoutNote className='mt-3' />
310
+ <CartCheckout className='mt-6' />
311
+ </CartSummary>
312
+ </CartRoot>
313
+ ```
314
+
315
+ | Part | Renders |
316
+ | --- | --- |
317
+ | `CartTitle`, `CartDescription` | "Your cart" as an H2 and one line about it; sample wording before a store is connected |
318
+ | `CartClose` | The drawer's close button; nothing outside the drawer |
319
+ | `CartNotices` | A storage warning, and a cart error with a refresh button; nothing when there are neither |
320
+ | `CartItems` | The cart lines as a list. Its child is the item, rendered once for each line; without one, the packaged item. "Loading your cart…" renders just before the list while the saved cart loads |
321
+ | `CartItemImage`, `CartItemName`, `CartItemOptions`, `CartItemPrice` | The line's image (nothing without one), name as an H3, selected options (nothing without any) and line total |
322
+ | `CartItemQuantity`, `CartItemRemove` | Decrease and increase buttons around the quantity, and the remove button |
323
+ | `CartEmpty` | Only when the cart is empty: a message and a link to the catalog, or its own children instead |
324
+ | `CartSummary` | A container that renders only when the cart has items |
325
+ | `CartSubtotal`, `CartCheckoutNote` | The subtotal, and the note that shipping, taxes and discounts come at checkout. The note's outer element keeps the `commerce-cart-checkout-adjustments-note` class |
326
+ | `CartCheckout` | The checkout button when `checkoutSuccessPath` is set; otherwise a message that checkout is unavailable. Samples never check out |
327
+
328
+ `className` on `CartItems` goes on the list element whose direct children are the items, and `className` on `CartItemRoot` goes on each item. The list is a `role='list'` element rather than a `<ul>`, so it has no browser margin or indent for a package rule to reset. Cart and item parts take no item, quantity or price props, and they throw if they are rendered outside their root. In development builds, an item template without a `CartItemRoot` logs a warning, because the list would have no items. `CartItemImage` and `CartItemOptions` render nothing for a line without an image or options, which moves the line's other cells in a host grid. Every cart action goes through the shared provider's queue, whatever the layout, and while no store is connected the parts show the sample cart, which never reaches the store. 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).
329
+
330
+ Two hooks support custom cart markup:
331
+
332
+ - `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
+ - `useCartItem()` returns the line inside `CartItemRoot`: `id`, `name`, `quantity`, `imageUrl`, `options`, `subtotal` and `currency`.
334
+
181
335
  ## How it works
182
336
 
183
337
  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
@@ -301,6 +301,8 @@ declare function getAvailableInventoryQuantity(item: InventoryCountContainer | n
301
301
  declare function getImageUrls(item: MediaObjectContainer | null | undefined): string[];
302
302
  declare function getPrimaryImageUrl(item: MediaObjectContainer | null | undefined): string | null;
303
303
  declare function getProductAttributes(skuGroup: SKUGroup | null | undefined): StorefrontProductAttribute[];
304
+ /** The product's display name: its label, then its name. */
305
+ declare function getProductName(skuGroup: SKUGroup): string;
304
306
  //#endregion
305
307
  //#region src/cart.d.ts
306
308
  declare function CartButton(): ReactElement;
@@ -313,22 +315,236 @@ declare function AddToCartButton({
313
315
  name: string;
314
316
  quantity?: number;
315
317
  }): ReactElement;
318
+ //#endregion
319
+ //#region src/cart-state.d.ts
320
+ /** One cart line, for custom cart parts. */
321
+ interface CartItemState {
322
+ /** Empty when the server returned a line without an ID. Such a line cannot be changed or removed. */
323
+ readonly id: string;
324
+ readonly name: string;
325
+ readonly quantity: number;
326
+ readonly imageUrl: string | null;
327
+ readonly options: readonly {
328
+ readonly attribute: string;
329
+ readonly values: readonly string[];
330
+ }[];
331
+ /** The line total in minor units, or `null` when the server did not price it. */
332
+ readonly subtotal: number | null;
333
+ readonly currency: string;
334
+ }
335
+ /** Read-only cart contents: the connected store's cart, or the sample cart while no store is connected. */
336
+ interface CartState {
337
+ readonly items: readonly CartItemState[];
338
+ readonly itemCount: number;
339
+ /** The cart subtotal in minor units, or `null` when the server did not price it. */
340
+ readonly subtotal: number | null;
341
+ readonly currency: string;
342
+ readonly isSample: boolean;
343
+ /** `true` while the saved cart loads. */
344
+ readonly loading: boolean;
345
+ }
346
+ /** Read the cart from anywhere inside `CommerceProvider`, for example to build a custom cart button. */
347
+ declare function useCart(): CartState;
348
+ //#endregion
349
+ //#region src/cart-parts.d.ts
350
+ /** Props shared by every cart part. `className` is added to the part's outer element. */
351
+ interface CartPartProps {
352
+ className?: string;
353
+ }
354
+ interface CartRootProps {
355
+ children: ReactNode;
356
+ /** Added to the root `<section>`. */
357
+ className?: string;
358
+ }
359
+ /**
360
+ * Provides the cart to cart parts on a page, such as a `/cart` route. While no store is connected it shows the sample
361
+ * cart. Use `CartDrawerRoot` for the drawer.
362
+ */
363
+ declare function CartRoot({
364
+ children,
365
+ className
366
+ }: CartRootProps): ReactElement;
367
+ interface CartDrawerRootProps {
368
+ children: ReactNode;
369
+ /** Added to the drawer panel. The packaged drawer passes its width, background and column layout here. */
370
+ className?: string;
371
+ }
372
+ /**
373
+ * The cart dialog. Opens when an item is added or a `CartButton` is pressed, and returns focus when it closes.
374
+ * The panel sits at the right edge at full height; `mr-auto` on it moves it to the left. Mount one per provider,
375
+ * in place of `CartDrawer`.
376
+ */
377
+ declare function CartDrawerRoot({
378
+ children,
379
+ className
380
+ }: CartDrawerRootProps): ReactElement;
381
+ /** "Your cart", or "Sample cart" before a store is connected, as an H2. In the drawer it names the dialog. */
382
+ declare function CartTitle({
383
+ className
384
+ }: CartPartProps): ReactElement;
385
+ /** One line about the cart. In the drawer it describes the dialog. */
386
+ declare function CartDescription({
387
+ className
388
+ }: CartPartProps): ReactElement;
389
+ /** The drawer's close button. Renders nothing outside the drawer. */
390
+ declare function CartClose({
391
+ className
392
+ }: CartPartProps): ReactElement | null;
393
+ /** A storage warning, and a cart error with a refresh button. Renders nothing when there are neither. */
394
+ declare function CartNotices({
395
+ className
396
+ }: CartPartProps): ReactElement | null;
397
+ /** Read the line from inside `CartItemRoot`, for custom item parts. */
398
+ declare function useCartItem(): CartItemState;
399
+ interface CartItemsProps extends CartPartProps {
400
+ /** One item, rendered for every cart line. Defaults to the packaged item. */
401
+ children?: ReactNode;
402
+ }
403
+ /**
404
+ * The cart lines as a list. `className` goes on the list element whose children are the items. While the saved cart
405
+ * loads, a status message renders just before it.
406
+ */
407
+ declare function CartItems({
408
+ className,
409
+ children
410
+ }: CartItemsProps): ReactElement;
411
+ interface CartItemRootProps {
412
+ children: ReactNode;
413
+ /** Added to the item's list item element. */
414
+ className?: string;
415
+ }
416
+ /** One cart line. Render it as the child of `CartItems`, which renders it once for every line. */
417
+ declare function CartItemRoot({
418
+ children,
419
+ className
420
+ }: CartItemRootProps): ReactElement;
421
+ /** The line's product image. Renders nothing without one. */
422
+ declare function CartItemImage({
423
+ className
424
+ }: CartPartProps): ReactElement | null;
425
+ /** The product name as an H3. */
426
+ declare function CartItemName({
427
+ className
428
+ }: CartPartProps): ReactElement;
429
+ /** The selected options, one per line, such as "Color: Blue". Renders nothing without options. */
430
+ declare function CartItemOptions({
431
+ className
432
+ }: CartPartProps): ReactElement | null;
433
+ /** The line total. */
434
+ declare function CartItemPrice({
435
+ className
436
+ }: CartPartProps): ReactElement;
437
+ /** Decrease and increase buttons around the line's quantity. */
438
+ declare function CartItemQuantity({
439
+ className
440
+ }: CartPartProps): ReactElement;
441
+ /** Removes the line. */
442
+ declare function CartItemRemove({
443
+ className
444
+ }: CartPartProps): ReactElement;
445
+ interface CartEmptyProps extends CartPartProps {
446
+ /** Replaces the default message and link. */
447
+ children?: ReactNode;
448
+ }
449
+ /** Renders only when the cart is empty: a message and a link to the catalog, or its children. */
450
+ declare function CartEmpty({
451
+ className,
452
+ children
453
+ }: CartEmptyProps): ReactElement | null;
454
+ interface CartSummaryProps extends CartPartProps {
455
+ children: ReactNode;
456
+ }
457
+ /** A container for the subtotal and checkout. Renders only when the cart has items. */
458
+ declare function CartSummary({
459
+ className,
460
+ children
461
+ }: CartSummaryProps): ReactElement | null;
462
+ /** The subtotal, labeled "Sample subtotal" for samples. Renders nothing until the server prices the cart. */
463
+ declare function CartSubtotal({
464
+ className
465
+ }: CartPartProps): ReactElement | null;
466
+ /**
467
+ * "Shipping, taxes, and discounts are calculated at checkout." Has the stable
468
+ * `commerce-cart-checkout-adjustments-note` class. Renders nothing for samples or an empty cart.
469
+ */
470
+ declare function CartCheckoutNote({
471
+ className
472
+ }: CartPartProps): ReactElement | null;
473
+ /**
474
+ * The checkout button when `checkoutSuccessPath` is set; otherwise a message that checkout is unavailable.
475
+ * Samples never check out. Renders nothing for an empty cart.
476
+ */
477
+ declare function CartCheckout({
478
+ className
479
+ }: CartPartProps): ReactElement | null;
480
+ /** The packaged cart drawer. Compose `CartDrawerRoot` and the cart parts for a custom drawer. */
316
481
  declare function CartDrawer(): ReactElement;
317
482
  //#endregion
318
483
  //#region src/catalog.d.ts
319
- declare function ProductCard({
320
- product
321
- }: {
322
- product: SKUGroup;
323
- }): ReactElement;
484
+ /** Props shared by every catalog part. `className` is added to the part's outer element. */
485
+ interface CatalogPartProps {
486
+ className?: string;
487
+ }
488
+ interface CatalogRootProps {
489
+ children: ReactNode;
490
+ /** Added to the root `<section>`. */
491
+ className?: string;
492
+ /** Permit built-in examples only while the host confirms this site is unbound. */
493
+ sampleProducts?: boolean;
494
+ }
495
+ /** Loads one cursor page of products, or the built-in samples, and provides them to catalog parts. */
496
+ declare function CatalogRoot({
497
+ children,
498
+ className,
499
+ sampleProducts
500
+ }: CatalogRootProps): ReactElement;
501
+ interface CatalogHeaderProps extends CatalogPartProps {
502
+ title?: string;
503
+ description?: string;
504
+ }
505
+ /** The catalog title as the page's H1, with a description. */
506
+ declare function CatalogHeader({
507
+ title,
508
+ description,
509
+ className
510
+ }: CatalogHeaderProps): ReactElement;
511
+ /** Announces loading, failure, or how many products the page shows. */
512
+ declare function CatalogSummary({
513
+ className
514
+ }: CatalogPartProps): ReactElement;
515
+ interface CatalogProductsProps extends CatalogPartProps {
516
+ /** One card, rendered for every product. Defaults to the packaged card. */
517
+ children?: ReactNode;
518
+ }
519
+ /**
520
+ * 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.
522
+ */
523
+ declare function CatalogProducts({
524
+ className,
525
+ children
526
+ }: CatalogProductsProps): ReactElement;
527
+ /** First-page and next-page buttons. Renders nothing for samples, which have one page. */
528
+ declare function CatalogPagination({
529
+ className
530
+ }: CatalogPartProps): ReactElement | null;
324
531
  interface CatalogProps {
532
+ /** @deprecated Compose `CatalogRoot` with `<CatalogHeader title='…' />` instead. */
325
533
  title?: string;
534
+ /** @deprecated Compose `CatalogRoot` with `<CatalogHeader description='…' />` instead. */
326
535
  description?: string;
536
+ /** @deprecated To render the page's own H1, compose `CatalogRoot` without `CatalogHeader` instead. */
327
537
  showHeader?: boolean;
328
538
  /** Permit built-in examples only while the host confirms this site is unbound. */
329
539
  sampleProducts?: boolean;
330
540
  }
331
- declare function Catalog(props: CatalogProps): ReactElement;
541
+ /** The packaged catalog. Compose `CatalogRoot` and the catalog parts for a custom layout. */
542
+ declare function Catalog({
543
+ title,
544
+ description,
545
+ showHeader,
546
+ sampleProducts
547
+ }: CatalogProps): ReactElement;
332
548
  //#endregion
333
549
  //#region src/commerce-provider.d.ts
334
550
  interface CommerceContextValue {
@@ -338,7 +554,9 @@ interface CommerceContextValue {
338
554
  theme: StorefrontTheme;
339
555
  config: StorefrontConfig;
340
556
  cart: CartOrder | null;
557
+ /** Whether the cart drawer is open. Always `false` when no `CartDrawer` or `CartDrawerRoot` is mounted. */
341
558
  open: boolean;
559
+ /** Opens or closes the cart drawer. Opening does nothing without a mounted drawer. */
342
560
  setOpen: (open: boolean) => void;
343
561
  /** Restores the initiating control after the cart drawer closes. */
344
562
  restoreFocus: () => void;
@@ -371,13 +589,26 @@ interface CommerceProviderProps {
371
589
  theme?: StorefrontTheme;
372
590
  /** Set only when your server has enabled checkout and this return route exists. */
373
591
  checkoutSuccessPath?: string;
592
+ /**
593
+ * Called after an item is added, live or sample. With a drawer mounted, the drawer also opens. Without one, such as
594
+ * on a site with a cart page, use it to confirm the add.
595
+ */
596
+ onItemAdded?: (item: AddedCartItem) => void;
597
+ }
598
+ /** The item passed to `onItemAdded`. */
599
+ interface AddedCartItem {
600
+ readonly name: string;
601
+ readonly quantity: number;
602
+ /** `true` for the sample cart, before a store is connected. */
603
+ readonly isSample: boolean;
374
604
  }
375
605
  declare function CommerceProvider({
376
606
  children,
377
607
  catalogPath,
378
608
  productPath,
379
609
  checkoutSuccessPath,
380
- theme
610
+ theme,
611
+ onItemAdded
381
612
  }: CommerceProviderProps): ReactElement;
382
613
  //#endregion
383
614
  //#region src/commerce-storefront.d.ts
@@ -387,6 +618,69 @@ declare function CommerceStorefront({
387
618
  ...props
388
619
  }: CommerceProviderProps): react_jsx_runtime0.JSX.Element;
389
620
  //#endregion
621
+ //#region src/product-card.d.ts
622
+ /** Read-only card state for custom parts rendered inside `ProductCardRoot`. */
623
+ interface ProductCardState {
624
+ /** `null` for a built-in sample, which is not a catalog product. */
625
+ readonly product: SKUGroup | null;
626
+ readonly name: string;
627
+ /** The product page, or `null` for a sample, which has none. */
628
+ readonly href: string | null;
629
+ readonly isSample: boolean;
630
+ }
631
+ /** Props shared by every card part. `className` is added to the part's outer element. */
632
+ interface ProductCardPartProps {
633
+ className?: string;
634
+ }
635
+ /** Read the card's product, name, and link from inside `ProductCardRoot`. */
636
+ declare function useProductCard(): ProductCardState;
637
+ interface ProductCardRootProps {
638
+ children: ReactNode;
639
+ /** The catalog product to show. Leave it out inside `CatalogProducts`, which supplies each product. */
640
+ product?: SKUGroup;
641
+ /** Added to the root `<article>`. */
642
+ className?: string;
643
+ }
644
+ /** Provides one product to card parts. Inside `CatalogProducts` it renders the current catalog entry. */
645
+ declare function ProductCardRoot({
646
+ children,
647
+ product,
648
+ className
649
+ }: ProductCardRootProps): ReactElement;
650
+ /**
651
+ * The product's first image, linked to its page. Samples show a placeholder. With a `ProductCardTitle` in the card,
652
+ * the title is the one link keyboard and screen reader users get; without one, the image link is.
653
+ */
654
+ declare function ProductCardImage({
655
+ className
656
+ }: ProductCardPartProps): ReactElement;
657
+ /** The product name as an H2, linked to its page. */
658
+ declare function ProductCardTitle({
659
+ className
660
+ }: ProductCardPartProps): ReactElement;
661
+ /** Up to three lines of the product description. Renders nothing without one. */
662
+ declare function ProductCardDescription({
663
+ className
664
+ }: ProductCardPartProps): ReactElement | null;
665
+ /** The product's price, or its price range when variants differ. */
666
+ declare function ProductCardPrice({
667
+ className
668
+ }: ProductCardPartProps): ReactElement;
669
+ /**
670
+ * Adds the product directly when it has exactly one SKU and no options; otherwise links to its page to choose.
671
+ * Samples add to the sample cart.
672
+ */
673
+ declare function ProductCardAction({
674
+ className
675
+ }: ProductCardPartProps): ReactElement;
676
+ interface ProductCardProps {
677
+ product: SKUGroup;
678
+ }
679
+ /** The packaged card for one `SKUGroup`. Compose `ProductCardRoot` and the card parts for a custom card. */
680
+ declare function ProductCard({
681
+ product
682
+ }: ProductCardProps): ReactElement;
683
+ //#endregion
390
684
  //#region src/product-details-context.d.ts
391
685
  /** Where the shopper is in choosing a purchasable SKU. Only `ready` has a verified `selectedSku`. */
392
686
  type ProductDetailsStatus = 'incomplete' | 'checking' | 'variant-error' | 'unavailable' | 'not-purchasable' | 'ready';
@@ -486,7 +780,7 @@ declare function ProductDetailsPurchaseStatus({
486
780
  declare function ProductDetailsAvailability({
487
781
  className
488
782
  }: ProductDetailsPartProps): ReactElement | null;
489
- /** Quantity for the verified SKU. Renders nothing until a SKU is verified. */
783
+ /** Quantity for the verified SKU, with a message when it cannot be added. Renders nothing until a SKU is verified. */
490
784
  declare function ProductDetailsQuantity({
491
785
  className
492
786
  }: ProductDetailsPartProps): ReactElement | null;
@@ -498,4 +792,4 @@ declare function ProductDetailsAddToCart({
498
792
  //#region src/storefront-surface.d.ts
499
793
  declare function CommerceStatus(): ReactElement | null;
500
794
  //#endregion
501
- export { AddToCartButton, AddToCartItemInput, ApiError, CartAddress, CartButton, CartDiscount, CartDrawer, CartLineItem, CartLineItemDetails, CartNote, CartOrder, CartSelectedAddon, CartSelectedAddonValue, CartSelectedOption, CartShippingInfo, CartSummaryTotals, CartTax, Catalog, type CatalogProps, type CommerceContextValue, CommerceProvider, type CommerceProviderProps, CommerceStatus, CommerceStorefront, Connection, ConnectionEdge, DraftOrderContext, InventoryCount, LabelFilter, LineItemTotals, MediaObject, Money, OrderTotals, PageInfo, PriceRange, ProductCard, 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, getSingleMatchedSku, getSingleMatchedSkuId, money, useCommerce, useProductDetails, useProductDetailsOption };
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 };