@godaddy/gd-commerce-storefront 0.4.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,20 +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 |
103
104
  | `CatalogRoot` and catalog parts | Build a [custom catalog or card layout](#custom-catalog-layouts) with the same rules as `Catalog` |
104
105
  | `ProductDetails` | Reads `:productId`; validates URL option selections against catalog results |
105
106
  | `ProductDetailsRoot` and product parts | Build a [custom product layout](#custom-product-layouts) with the same rules as `ProductDetails` |
106
107
  | `ProductCard` | Renders one `SKUGroup` with direct add or a product-details link; `ProductCardRoot` and card parts customize it |
107
- | `CartButton` | Opens the shared drawer and displays item count |
108
- | `AddToCartButton` | Adds a verified `sku`, `name`, and optional integer `quantity` |
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 |
109
112
  | `CommerceStatus` | Connection progress/error/retry for custom layouts |
110
113
  | `useCommerce` | Cart, connection, pending/error state and serialized cart actions |
111
- | `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 |
112
115
 
113
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.
114
117
 
115
- `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.
116
119
 
117
120
  ## Custom product layouts
118
121
 
@@ -147,7 +150,7 @@ Add your own elements between parts, reorder them, or leave out display parts. `
147
150
  | `ProductDetailsOptions` | A select for every attribute, or the SKU list for products without attributes |
148
151
  | `ProductDetailsOption` | A select for one attribute, by `attribute` name; nothing when the product lacks it |
149
152
  | `ProductDetailsPurchaseStatus` | The one message explaining why the selection is not purchasable yet; nothing when ready |
150
- | `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 |
151
154
  | `ProductDetailsAddToCart` | Adds the verified SKU and quantity; before a SKU is verified, its accessible description gives the reason |
152
155
 
153
156
  Every part is named `ProductDetails*` because it works only inside `ProductDetailsRoot`. `AddToCartButton` is the standalone button for a SKU you already have.
@@ -156,6 +159,18 @@ Parts read state from the root and take no product, SKU, price or quantity props
156
159
 
157
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.
158
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.
173
+
159
174
  Two hooks support custom parts inside the root:
160
175
 
161
176
  - `useProductDetails()` returns read-only `product`, `name`, `selectedSku`, `quantity`, `availableQuantity`, `images` and `status` (`incomplete`, `checking`, `variant-error`, `unavailable`, `not-purchasable` or `ready`). `selectedSku` is set only when `status` is `ready`.
@@ -223,10 +238,100 @@ As with product layouts, the grid, spacing and border classes come from your own
223
238
  | `ProductCardDescription`, `ProductCardPrice` | Up to three lines of description (nothing without one), and the price or price range |
224
239
  | `ProductCardAction` | Adds the product directly only when it has one SKU and no options; otherwise links to the product page to choose |
225
240
 
226
- `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. The loading placeholder (three cards, one to three columns), the error with its retry button, and the empty message render just before that element, whatever the layout. 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.
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.
227
242
 
228
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).
229
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
+
230
335
  ## How it works
231
336
 
232
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
@@ -315,6 +315,169 @@ declare function AddToCartButton({
315
315
  name: string;
316
316
  quantity?: number;
317
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. */
318
481
  declare function CartDrawer(): ReactElement;
319
482
  //#endregion
320
483
  //#region src/catalog.d.ts
@@ -354,8 +517,8 @@ interface CatalogProductsProps extends CatalogPartProps {
354
517
  children?: ReactNode;
355
518
  }
356
519
  /**
357
- * The product cards. `className` goes on the element that contains the cards, such as a grid. The loading
358
- * placeholder, the error with its retry, and the empty message render just before that element.
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.
359
522
  */
360
523
  declare function CatalogProducts({
361
524
  className,
@@ -366,8 +529,11 @@ declare function CatalogPagination({
366
529
  className
367
530
  }: CatalogPartProps): ReactElement | null;
368
531
  interface CatalogProps {
532
+ /** @deprecated Compose `CatalogRoot` with `<CatalogHeader title='…' />` instead. */
369
533
  title?: string;
534
+ /** @deprecated Compose `CatalogRoot` with `<CatalogHeader description='…' />` instead. */
370
535
  description?: string;
536
+ /** @deprecated To render the page's own H1, compose `CatalogRoot` without `CatalogHeader` instead. */
371
537
  showHeader?: boolean;
372
538
  /** Permit built-in examples only while the host confirms this site is unbound. */
373
539
  sampleProducts?: boolean;
@@ -388,7 +554,9 @@ interface CommerceContextValue {
388
554
  theme: StorefrontTheme;
389
555
  config: StorefrontConfig;
390
556
  cart: CartOrder | null;
557
+ /** Whether the cart drawer is open. Always `false` when no `CartDrawer` or `CartDrawerRoot` is mounted. */
391
558
  open: boolean;
559
+ /** Opens or closes the cart drawer. Opening does nothing without a mounted drawer. */
392
560
  setOpen: (open: boolean) => void;
393
561
  /** Restores the initiating control after the cart drawer closes. */
394
562
  restoreFocus: () => void;
@@ -421,13 +589,26 @@ interface CommerceProviderProps {
421
589
  theme?: StorefrontTheme;
422
590
  /** Set only when your server has enabled checkout and this return route exists. */
423
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;
424
604
  }
425
605
  declare function CommerceProvider({
426
606
  children,
427
607
  catalogPath,
428
608
  productPath,
429
609
  checkoutSuccessPath,
430
- theme
610
+ theme,
611
+ onItemAdded
431
612
  }: CommerceProviderProps): ReactElement;
432
613
  //#endregion
433
614
  //#region src/commerce-storefront.d.ts
@@ -599,7 +780,7 @@ declare function ProductDetailsPurchaseStatus({
599
780
  declare function ProductDetailsAvailability({
600
781
  className
601
782
  }: ProductDetailsPartProps): ReactElement | null;
602
- /** 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. */
603
784
  declare function ProductDetailsQuantity({
604
785
  className
605
786
  }: ProductDetailsPartProps): ReactElement | null;
@@ -611,4 +792,4 @@ declare function ProductDetailsAddToCart({
611
792
  //#region src/storefront-surface.d.ts
612
793
  declare function CommerceStatus(): ReactElement | null;
613
794
  //#endregion
614
- export { AddToCartButton, AddToCartItemInput, ApiError, CartAddress, CartButton, CartDiscount, CartDrawer, CartLineItem, CartLineItemDetails, CartNote, CartOrder, CartSelectedAddon, CartSelectedAddonValue, CartSelectedOption, CartShippingInfo, CartSummaryTotals, CartTax, 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, useCommerce, useProductCard, 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 };