@godaddy/gd-commerce-storefront 0.6.0 → 0.8.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 +137 -81
- package/dist/index.d.ts +150 -23
- package/dist/index.js +899 -263
- package/dist/styles.css +542 -179
- package/docs/server-api.md +13 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,10 +58,11 @@ Use the providers your application already has; do not create another router or
|
|
|
58
58
|
| `onEvent` | unset | Called with a [`CommerceEvent`](#events) for every cart change and failure, such as for analytics |
|
|
59
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 |
|
|
60
60
|
| `theme` | inherited text and transparent wrappers; neutral control palette | CSS custom properties applied to storefront surfaces and the portalled drawer |
|
|
61
|
+
| `getSwatch` | unset | Swatches for attribute values, such as colors. Returns a CSS `background` for a value, or nothing. See [swatches](#swatches) |
|
|
61
62
|
|
|
62
63
|
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.
|
|
63
64
|
|
|
64
|
-
`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
|
|
65
|
+
`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 a row saying 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.
|
|
65
66
|
|
|
66
67
|
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`.
|
|
67
68
|
|
|
@@ -78,6 +79,8 @@ A connection failure leaves the surrounding application and its state mounted. C
|
|
|
78
79
|
'--commerce-surface': '#ffffff',
|
|
79
80
|
'--commerce-text': '#171717',
|
|
80
81
|
'--commerce-radius': '0.75rem',
|
|
82
|
+
'--commerce-sale': '#b42318',
|
|
83
|
+
'--commerce-on-sale': '#ffffff',
|
|
81
84
|
}}
|
|
82
85
|
>
|
|
83
86
|
{/* Register /collection and /item/:productId in your router. */}
|
|
@@ -87,7 +90,7 @@ A connection failure leaves the surrounding application and its state mounted. C
|
|
|
87
90
|
|
|
88
91
|
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`.
|
|
89
92
|
|
|
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
|
|
93
|
+
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. Muted text, hairlines, image placeholders and the quieter outlined buttons (cart button, product card actions, pagination) derive from the inherited text color, so they read on a light or dark page without configuration. `--commerce-surface` colors form inputs and the opaque cart drawer, paired with `--commerce-text`; it does not color component wrappers. Error and warning messages bring their own light background. The Sale badge fills with `--commerce-sale` behind `--commerce-on-sale` text, so it reads over an image and on any page; sale prices keep the surrounding text color, with the compare-at price struck through beside them. Images have square corners; `--commerce-radius` rounds buttons, inputs and option buttons. Primary buttons use `--commerce-accent` and are ringed in it on focus, so choose an accent that contrasts with the page. 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.
|
|
91
94
|
|
|
92
95
|
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.
|
|
93
96
|
|
|
@@ -107,9 +110,10 @@ The repository example uses React Router 7. Follow each router version's own Rea
|
|
|
107
110
|
| `ProductDetails` | Reads `:productId`; validates URL option selections against catalog results. `ProductDetailsRoot` also takes a `productId` prop |
|
|
108
111
|
| `ProductDetailsRoot` and product parts | Build a [custom product layout](#custom-product-layouts) with the same rules as `ProductDetails` |
|
|
109
112
|
| `ProductCard` | Renders one `SKUGroup` with direct add or a product-details link; `ProductCardRoot` and card parts customize it |
|
|
113
|
+
| `RelatedProducts` | Optional: other products from the catalog's first page, for after the product page. See [related products](#related-products) |
|
|
110
114
|
| `CartDrawer` | The cart dialog; `CommerceStorefront` mounts one |
|
|
111
115
|
| `CartDrawerRoot`, `CartRoot` and cart parts | Build a [custom drawer or a cart page](#custom-cart-layouts) with the same rules as `CartDrawer` |
|
|
112
|
-
| `CartButton` | Opens the shared drawer
|
|
116
|
+
| `CartButton` | Opens the shared drawer: a bag icon with a count badge, named "Cart, 2 items". Its children replace the icon with a label, and `useCart()` builds a custom one |
|
|
113
117
|
| `AddToCartButton` | Adds a verified `sku`, `name`, and optional integer `quantity`; while disabled, its accessible description says why. Its children replace the label |
|
|
114
118
|
| `CommerceStatus` | Connection progress/error/retry for custom layouts |
|
|
115
119
|
| `useCommerce` | Cart, connection, pending/error state and serialized cart actions |
|
|
@@ -127,35 +131,41 @@ The package exports TypeScript catalog/cart response types and selection/summary
|
|
|
127
131
|
|
|
128
132
|
```tsx
|
|
129
133
|
import {
|
|
130
|
-
ProductDetailsBackLink, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsPrice,
|
|
134
|
+
ProductDetailsBackLink, ProductDetailsBadge, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsPrice,
|
|
131
135
|
ProductDetailsPurchase, ProductDetailsRoot, ProductDetailsTitle,
|
|
132
136
|
} from '@godaddy/gd-commerce-storefront';
|
|
133
137
|
|
|
134
138
|
<ProductDetailsRoot>
|
|
135
|
-
<ProductDetailsBackLink className='mb-
|
|
136
|
-
<div className='grid items-start gap-8
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
+
<ProductDetailsBackLink className='mb-4 sm:mb-6' />
|
|
140
|
+
<div className='grid items-start gap-8 md:grid-cols-[minmax(0,1.15fr)_minmax(0,1fr)] lg:gap-12'>
|
|
141
|
+
<div className='relative min-w-0'>
|
|
142
|
+
<ProductDetailsGallery />
|
|
143
|
+
<ProductDetailsBadge className='pointer-events-none absolute left-4 top-4' />
|
|
144
|
+
</div>
|
|
145
|
+
<div className='min-w-0 md:pt-2'>
|
|
139
146
|
<ProductDetailsTitle />
|
|
140
|
-
<ProductDetailsPrice className='mt-
|
|
141
|
-
<ProductDetailsDescription className='
|
|
142
|
-
<ProductDetailsPurchase className='border-t border-
|
|
147
|
+
<ProductDetailsPrice className='mt-2' />
|
|
148
|
+
<ProductDetailsDescription className='mt-6' />
|
|
149
|
+
<ProductDetailsPurchase className='mt-6 border-t border-commerce-line pt-6' />
|
|
143
150
|
</div>
|
|
144
151
|
</div>
|
|
145
152
|
</ProductDetailsRoot>
|
|
146
153
|
```
|
|
147
154
|
|
|
148
|
-
The grid, spacing and border classes come from your own stylesheet in a custom layout. Without them, the parts stack in one column with no space between them.
|
|
155
|
+
The grid, spacing and border classes come from your own stylesheet in a custom layout. Without them, the parts stack in one column with no space between them. `border-commerce-line` and `divide-commerce-line` stand for the package's hairline, the surrounding text color at 14% opacity, which reads on light and dark pages alike; use your own border color in its place.
|
|
149
156
|
|
|
150
|
-
Add your own elements between parts, reorder them, or leave out display parts. `ProductDetailsPurchase` renders the
|
|
157
|
+
Add your own elements between parts, reorder them, or leave out display parts. `ProductDetailsBadge` shows "Sale" when the verified SKU's compare-at price is above its price, or before then when some SKU's compare-at price is above that SKU's own price, and nothing otherwise. `ProductDetailsPrice` shows "From" and the starting price until a SKU is verified. It strikes through a compare-at price beside it only when the cheapest SKUs all have that compare-at price, so a strike-through never pairs one SKU's price with another's compare-at price. `ProductDetailsPurchase` renders the options and status, then the quantity and add button in one row, then the availability. Replace it with individual parts to place options apart from the add button:
|
|
151
158
|
|
|
152
159
|
| Part | Renders |
|
|
153
160
|
| --- | --- |
|
|
154
|
-
| `ProductDetailsOptions` | A
|
|
155
|
-
| `ProductDetailsOption` |
|
|
156
|
-
| `ProductDetailsPurchaseStatus` | The one message explaining why
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
161
|
+
| `ProductDetailsOptions` | A control for every attribute, or the SKU list for products without attributes |
|
|
162
|
+
| `ProductDetailsOption` | One attribute, by `attribute` name, as a group of buttons (radios); as swatches when [`getSwatch`](#swatches) gives every value one; or as a select when it has more than eight values. Nothing when the product lacks it |
|
|
163
|
+
| `ProductDetailsPurchaseStatus` | The one message explaining why a full selection is not purchasable, such as an unavailable combination; nothing when ready or before every option is chosen |
|
|
164
|
+
| `ProductDetailsQuantity` | A quantity stepper, capped at the verified SKU's stock and disabled when it is sold out. It shows a message when a typed quantity cannot be added |
|
|
165
|
+
| `ProductDetailsAvailability` | Stock for the verified SKU; nothing before verification |
|
|
166
|
+
| `ProductDetailsAddToCart` | Adds the verified SKU and quantity, labeled with the total, such as "Add to cart · $64.00". Until every option is chosen it names the next one, such as "Select size", and pressing it shows an error on each missing option and focuses the first. While a selection is checked or unavailable, it is disabled and its accessible description gives the reason |
|
|
167
|
+
|
|
168
|
+
Values whose SKUs are all sold out, given the shopper's other selections, are crossed out and announced as "sold out", but stay selectable, so the shopper can change options in any order. This needs every SKU with its attribute values in the product response, which `@godaddy/gd-commerce-server` returns for up to 50 SKUs; for a product with more, no value is marked, and the add button still says "Out of stock" once a sold-out SKU is chosen.
|
|
159
169
|
|
|
160
170
|
Every part is named `ProductDetails*` because it works only inside `ProductDetailsRoot`. `AddToCartButton` is the standalone button for a SKU you already have.
|
|
161
171
|
|
|
@@ -176,7 +186,7 @@ The root keeps the selection in the page URL, as `option.<attribute>` and `sku`,
|
|
|
176
186
|
|
|
177
187
|
`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
188
|
|
|
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
|
|
189
|
+
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 image but keeps the quantity. A quantity above the new SKU's stock shows an error under the field and disables the add button. 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.
|
|
180
190
|
|
|
181
191
|
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.
|
|
182
192
|
|
|
@@ -195,7 +205,7 @@ A part that renders nothing, such as `ProductDetailsAvailability` before a SKU i
|
|
|
195
205
|
Two hooks support custom parts inside the root:
|
|
196
206
|
|
|
197
207
|
- `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`.
|
|
198
|
-
- `useProductDetailsOption(attribute)` returns `{ name, label, selected, values, select }` for building swatches or buttons, or `null` when the product has no such attribute. `select(value)` accepts only that attribute's value names, or `null` to clear. Other values are ignored. A hook-based control counts toward the layout check.
|
|
208
|
+
- `useProductDetailsOption(attribute)` returns `{ name, label, selected, missing, values, select }` for building swatches or buttons, or `null` when the product has no such attribute. Each value has `name`, `label`, `selected`, `soldOut` and `swatch` (from `getSwatch`, or `null`). `missing` is `true` once the shopper has pressed add without choosing this option, so the control can show an error; mark the control's element with `data-commerce-missing` to have add focus it. `select(value)` accepts only that attribute's value names, or `null` to clear. Other values are ignored. A hook-based control counts toward the layout check.
|
|
199
209
|
|
|
200
210
|
```tsx
|
|
201
211
|
function ColorSwatches() {
|
|
@@ -206,6 +216,7 @@ function ColorSwatches() {
|
|
|
206
216
|
{color.values.map((value) => (
|
|
207
217
|
<button key={value.name} type='button' aria-pressed={value.selected} onClick={() => color.select(value.name)}>
|
|
208
218
|
{value.label}
|
|
219
|
+
{value.soldOut && ' (sold out)'}
|
|
209
220
|
</button>
|
|
210
221
|
))}
|
|
211
222
|
</div>
|
|
@@ -215,35 +226,64 @@ function ColorSwatches() {
|
|
|
215
226
|
|
|
216
227
|
Parts are flat named exports, not properties such as `ProductDetails.Title`, so each one is an ordinary module export.
|
|
217
228
|
|
|
229
|
+
### Swatches
|
|
230
|
+
|
|
231
|
+
Swatches are off by default: attribute values have no color or image in the catalog. Pass the provider a `getSwatch` function that returns a CSS `background` for a value, such as `'#3f6352'` or `'url(/swatches/linen.png) center / cover'`. An attribute whose values all get one renders as swatches on the product page, with the value's label as its accessible name, and catalog cards show it as dots. Any other attribute keeps its buttons.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
const colors: Record<string, string> = { forest: '#3f6352', black: '#1a1a1a', white: '#f7f7f5' };
|
|
235
|
+
|
|
236
|
+
<CommerceStorefront getSwatch={({ attribute, name }) => (attribute === 'color' ? colors[name] : undefined)}>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Related products
|
|
240
|
+
|
|
241
|
+
`RelatedProducts` is optional and separate from `ProductDetails`. Place it where it fits, such as after the product page. It shows packaged cards for the catalog's first products, leaving out the route's `:productId` or its `productId` prop. There is no related-products API, so the products are not chosen for the current one.
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
<Route
|
|
245
|
+
path='/products/:productId'
|
|
246
|
+
element={
|
|
247
|
+
<>
|
|
248
|
+
<ProductDetails />
|
|
249
|
+
<RelatedProducts className='mt-16 border-t border-commerce-line pt-12' />
|
|
250
|
+
</>
|
|
251
|
+
}
|
|
252
|
+
/>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`title` replaces "You may also like", `limit` sets the most products, from 1 to 24 (default 4), and children replace the card, as in `CatalogProducts`. It renders nothing until the products load, when no others are left, if the request fails, or while no store is connected.
|
|
256
|
+
|
|
218
257
|
## Custom catalog layouts
|
|
219
258
|
|
|
220
259
|
`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:
|
|
221
260
|
|
|
222
261
|
```tsx
|
|
223
262
|
import {
|
|
224
|
-
CatalogHeader, CatalogPagination, CatalogProducts, CatalogRoot, CatalogSummary, ProductCardAction,
|
|
225
|
-
|
|
263
|
+
CatalogHeader, CatalogPagination, CatalogProducts, CatalogRoot, CatalogSummary, ProductCardAction, ProductCardBadge,
|
|
264
|
+
ProductCardImage, ProductCardPrice, ProductCardRoot, ProductCardSwatches, ProductCardTitle,
|
|
226
265
|
} from '@godaddy/gd-commerce-storefront';
|
|
227
266
|
|
|
228
267
|
<CatalogRoot sampleProducts>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
268
|
+
<div className='flex flex-wrap items-end justify-between gap-x-4 gap-y-2 pb-6 pt-4 sm:pb-8 sm:pt-8'>
|
|
269
|
+
<CatalogHeader />
|
|
270
|
+
<CatalogSummary />
|
|
271
|
+
</div>
|
|
272
|
+
<CatalogProducts className='grid grid-cols-2 gap-x-4 gap-y-10 sm:gap-x-6 md:grid-cols-[repeat(auto-fill,minmax(240px,1fr))] lg:gap-y-12'>
|
|
273
|
+
<ProductCardRoot className='flex min-w-0 flex-col'>
|
|
274
|
+
<div className='relative'>
|
|
275
|
+
<ProductCardImage />
|
|
276
|
+
<ProductCardBadge className='pointer-events-none absolute left-3 top-3' />
|
|
277
|
+
</div>
|
|
278
|
+
<div className='flex flex-1 flex-col pt-3'>
|
|
235
279
|
<ProductCardTitle />
|
|
236
|
-
<
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
<ProductCardPrice className='min-w-0 flex-1' />
|
|
240
|
-
<ProductCardAction className='max-w-full' />
|
|
241
|
-
</div>
|
|
242
|
-
</div>
|
|
280
|
+
<ProductCardPrice className='mt-0.5' />
|
|
281
|
+
<ProductCardSwatches className='mt-2' />
|
|
282
|
+
<ProductCardAction className='mt-auto pt-4' />
|
|
243
283
|
</div>
|
|
244
284
|
</ProductCardRoot>
|
|
245
285
|
</CatalogProducts>
|
|
246
|
-
<CatalogPagination className='border-t border-
|
|
286
|
+
<CatalogPagination className='mt-12 border-t border-commerce-line pt-6' />
|
|
247
287
|
</CatalogRoot>
|
|
248
288
|
```
|
|
249
289
|
|
|
@@ -255,12 +295,15 @@ As with product layouts, the grid, spacing and border classes come from your own
|
|
|
255
295
|
| `CatalogSummary` | The loading, failure or product-count status line |
|
|
256
296
|
| `CatalogProducts` | One card per product. Its children are the card, rendered once for each product; without children, the packaged card |
|
|
257
297
|
| `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 |
|
|
258
|
-
| `CatalogPagination` | First-page and next-page buttons; nothing for samples |
|
|
298
|
+
| `CatalogPagination` | First-page and next-page buttons; nothing when there is one page, or for samples |
|
|
259
299
|
| `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 |
|
|
260
|
-
| `
|
|
300
|
+
| `ProductCardBadge` | "Sale" when some SKU's compare-at price is above that SKU's price. The catalog list has no SKU prices, so there it shows only when the lowest compare-at price is above the highest price |
|
|
301
|
+
| `ProductCardPrice` | The price or price range. A single price on sale shows its compare-at price struck through; a range does not |
|
|
302
|
+
| `ProductCardSwatches` | Dots for the first attribute whose values all have a [swatch](#swatches); nothing without `getSwatch` |
|
|
303
|
+
| `ProductCardDescription` | Up to two lines of description; nothing without one. The packaged card leaves it out |
|
|
261
304
|
| `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
305
|
|
|
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
|
|
306
|
+
`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 badge, description or 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.
|
|
264
307
|
|
|
265
308
|
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.
|
|
266
309
|
|
|
@@ -272,51 +315,59 @@ For a product the host already has, such as a featured product, use `ProductCard
|
|
|
272
315
|
|
|
273
316
|
```tsx
|
|
274
317
|
import {
|
|
275
|
-
CartCheckout, CartCheckoutNote, CartClose, CartDescription, CartDrawerRoot, CartEmpty,
|
|
276
|
-
CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, CartItems,
|
|
277
|
-
CartSummary, CartTitle, CommerceProvider,
|
|
318
|
+
CartCheckout, CartCheckoutNote, CartClose, CartContinueShopping, CartDescription, CartDrawerRoot, CartEmpty,
|
|
319
|
+
CartItemImage, CartItemName, CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, CartItems,
|
|
320
|
+
CartNotices, CartSubtotal, CartSummary, CartTitle, CommerceProvider, useCart,
|
|
278
321
|
} from '@godaddy/gd-commerce-storefront';
|
|
279
322
|
|
|
323
|
+
// Only the sample cart needs a description: it says nothing can be bought.
|
|
324
|
+
function SampleDescription() {
|
|
325
|
+
return useCart().isSample ? <CartDescription className='mt-0.5' /> : null;
|
|
326
|
+
}
|
|
327
|
+
|
|
280
328
|
<CommerceProvider>
|
|
281
329
|
{/* Header and routes */}
|
|
282
|
-
<CartDrawerRoot className='flex w-full max-w-
|
|
283
|
-
<div className='flex items-start justify-between border-b border-
|
|
284
|
-
<div>
|
|
330
|
+
<CartDrawerRoot className='flex w-full max-w-md flex-col bg-white text-neutral-900 shadow-2xl'>
|
|
331
|
+
<div className='flex items-start justify-between gap-4 border-b border-commerce-line py-3 pl-6 pr-3'>
|
|
332
|
+
<div className='min-w-0 pt-2.5'>
|
|
285
333
|
<CartTitle />
|
|
286
|
-
<
|
|
334
|
+
<SampleDescription />
|
|
287
335
|
</div>
|
|
288
336
|
<CartClose />
|
|
289
337
|
</div>
|
|
290
|
-
<div className='flex-1 overflow-y-auto
|
|
291
|
-
<CartNotices className='
|
|
292
|
-
<CartItems className='divide-y divide-
|
|
293
|
-
<CartItemRoot className='py-5
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
<div className='
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
338
|
+
<div className='flex-1 overflow-y-auto px-6'>
|
|
339
|
+
<CartNotices className='mt-6' />
|
|
340
|
+
<CartItems className='divide-y divide-commerce-line'>
|
|
341
|
+
<CartItemRoot className='flex gap-4 py-5'>
|
|
342
|
+
<CartItemImage className='shrink-0' />
|
|
343
|
+
<div className='flex min-w-0 flex-1 flex-col'>
|
|
344
|
+
<div className='flex items-start justify-between gap-4'>
|
|
345
|
+
<div className='min-w-0'>
|
|
346
|
+
<CartItemName />
|
|
347
|
+
<CartItemOptions />
|
|
348
|
+
</div>
|
|
349
|
+
<CartItemPrice className='shrink-0' />
|
|
350
|
+
</div>
|
|
351
|
+
<div className='mt-3 flex items-center justify-between gap-2'>
|
|
352
|
+
<CartItemQuantity />
|
|
353
|
+
<CartItemRemove />
|
|
300
354
|
</div>
|
|
301
|
-
</div>
|
|
302
|
-
<div className='mt-4 flex items-center justify-between gap-2'>
|
|
303
|
-
<CartItemQuantity />
|
|
304
|
-
<CartItemRemove />
|
|
305
355
|
</div>
|
|
306
356
|
</CartItemRoot>
|
|
307
357
|
</CartItems>
|
|
308
|
-
<CartEmpty className='py-
|
|
358
|
+
<CartEmpty className='py-20 text-center' />
|
|
309
359
|
</div>
|
|
310
|
-
<CartSummary className='border-t border-
|
|
360
|
+
<CartSummary className='border-t border-commerce-line px-6 pb-6 pt-4'>
|
|
311
361
|
<CartSubtotal />
|
|
312
|
-
<CartCheckoutNote className='mt-
|
|
313
|
-
<CartCheckout />
|
|
362
|
+
<CartCheckoutNote className='mt-1' />
|
|
363
|
+
<CartCheckout className='mt-5' />
|
|
364
|
+
<CartContinueShopping className='mt-2' />
|
|
314
365
|
</CartSummary>
|
|
315
366
|
</CartDrawerRoot>
|
|
316
367
|
</CommerceProvider>
|
|
317
368
|
```
|
|
318
369
|
|
|
319
|
-
`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.
|
|
370
|
+
`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. Give the panel a background and a text color together, as `bg-white text-neutral-900` do here with `--commerce-surface` and `--commerce-text`, so the parts inside can derive their muted text and lines from it. The packaged drawer also slides in, unless the visitor prefers reduced motion. 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.
|
|
320
371
|
|
|
321
372
|
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.
|
|
322
373
|
|
|
@@ -325,10 +376,10 @@ For a cart page instead of, or as well as, the drawer, compose the same parts in
|
|
|
325
376
|
<CartRoot className='grid gap-8 lg:grid-cols-3'>
|
|
326
377
|
<div className='lg:col-span-2'>
|
|
327
378
|
<CartNotices className='mb-4' />
|
|
328
|
-
<CartItems className='divide-y divide-
|
|
379
|
+
<CartItems className='divide-y divide-commerce-line' />
|
|
329
380
|
<CartEmpty className='py-16 text-center' />
|
|
330
381
|
</div>
|
|
331
|
-
<CartSummary className='rounded-lg border border-
|
|
382
|
+
<CartSummary className='rounded-lg border border-commerce-line p-6'>
|
|
332
383
|
<CartSubtotal />
|
|
333
384
|
<CartCheckoutNote className='mt-3' />
|
|
334
385
|
<CartCheckout className='mt-6' />
|
|
@@ -338,16 +389,17 @@ For a cart page instead of, or as well as, the drawer, compose the same parts in
|
|
|
338
389
|
|
|
339
390
|
| Part | Renders |
|
|
340
391
|
| --- | --- |
|
|
341
|
-
| `CartTitle`, `CartDescription` | "Your cart" as an H2 and one line about it; sample wording before a store is connected |
|
|
392
|
+
| `CartTitle`, `CartDescription` | "Your cart" as an H2, with the item count once there are items, and one line about it; sample wording before a store is connected |
|
|
342
393
|
| `CartClose` | The drawer's close button; nothing outside the drawer |
|
|
343
394
|
| `CartNotices` | A storage warning, and a cart error with a refresh button; nothing when there are neither |
|
|
344
395
|
| `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 |
|
|
345
|
-
| `CartItemImage`, `CartItemName`, `CartItemOptions`, `CartItemPrice` | The line's image (nothing without one), name as an H3, selected options (nothing without any) and line total |
|
|
396
|
+
| `CartItemImage`, `CartItemName`, `CartItemOptions`, `CartItemPrice` | The line's image (nothing without one), name as an H3, selected options on one line, such as "Color: Blue · Size: M" (nothing without any), and line total |
|
|
346
397
|
| `CartItemQuantity`, `CartItemRemove` | Decrease and increase buttons around the quantity, and the remove button |
|
|
347
|
-
| `CartEmpty` | Only when the cart is empty: a message and a link to the catalog, or its own children instead |
|
|
398
|
+
| `CartEmpty` | Only when the cart is empty: a bag icon, a message and a link to the catalog, or its own children instead |
|
|
348
399
|
| `CartSummary` | A container that renders only when the cart has items |
|
|
349
|
-
| `CartSubtotal`, `CartCheckoutNote` | The subtotal, and
|
|
400
|
+
| `CartSubtotal`, `CartCheckoutNote` | The subtotal, and a row saying shipping, taxes and discounts are calculated at checkout. The note's outer element keeps the `commerce-cart-checkout-adjustments-note` class |
|
|
350
401
|
| `CartCheckout` | The checkout button when `checkoutSuccessPath` is set; otherwise a message that checkout is unavailable. Samples never check out |
|
|
402
|
+
| `CartContinueShopping` | "Continue shopping", which closes the drawer, or links to the catalog on a cart page; nothing for an empty cart |
|
|
351
403
|
|
|
352
404
|
`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).
|
|
353
405
|
|
|
@@ -374,10 +426,10 @@ Parts whose wording a site often changes take replacement content as children. T
|
|
|
374
426
|
|
|
375
427
|
| Part | Function receives |
|
|
376
428
|
| --- | --- |
|
|
377
|
-
| `CartTitle`, `CartDescription` | `{ isSample }` |
|
|
429
|
+
| `CartTitle`, `CartDescription`, `CartContinueShopping` | `{ isSample }` |
|
|
378
430
|
| `CartCheckout`, `CartItemRemove` | `{ busy }` |
|
|
379
|
-
| `CartButton` | `{ itemCount }`.
|
|
380
|
-
| `AddToCartButton`, `ProductDetailsAddToCart` | `{ busy, soldOut }` |
|
|
431
|
+
| `CartButton` | `{ itemCount }`. Replaces the icon and badge with a visible label; keep the count in it |
|
|
432
|
+
| `AddToCartButton`, `ProductDetailsAddToCart` | `{ busy, soldOut }`. A `ProductDetailsAddToCart` label also replaces "Select size" and the total |
|
|
381
433
|
| `ProductCardAction` | `{ action, busy, soldOut }` |
|
|
382
434
|
| `ProductDetailsBackLink` | Content only |
|
|
383
435
|
|
|
@@ -393,7 +445,7 @@ Every part's outer element has a `data-commerce-part` attribute with the part's
|
|
|
393
445
|
.commerce-storefront [data-commerce-part='product-details-option'][data-commerce-attribute='color'] { display: none; }
|
|
394
446
|
```
|
|
395
447
|
|
|
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.
|
|
448
|
+
`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 `ProductDetailsQuantity`, `choice` on each option radio, and `thumbnail` in `ProductDetailsGallery`. An option the shopper skipped before pressing add has `data-commerce-missing` on its part, and its controls have `aria-invalid`. 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
449
|
|
|
398
450
|
## Events
|
|
399
451
|
|
|
@@ -473,12 +525,16 @@ mutation methods apply only to the connected store; sample cart data is internal
|
|
|
473
525
|
without `sampleProducts`, and direct product-detail visits, show the unconnected state.
|
|
474
526
|
Sample grids and drawers have the attribute `data-commerce-source="sample"`.
|
|
475
527
|
|
|
476
|
-
The provider reads configuration on mount and focus
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
528
|
+
The provider reads configuration on mount and focus. While the page is visible and the state is
|
|
529
|
+
`unbound` or `connecting`, it also polls every five seconds for five minutes after the page loads,
|
|
530
|
+
then every minute, and stops thirty minutes after load. Reloading, retrying or a change in the
|
|
531
|
+
reported state starts it again. A page still `connecting` when polling stops shows an error with
|
|
532
|
+
"Retry connection". The first
|
|
533
|
+
failed refresh hides samples and clears the sample cart; a later `unbound` response starts an
|
|
534
|
+
empty sample cart. While connecting, refresh failures keep the connecting
|
|
535
|
+
state for up to 60 seconds before showing the error. Once polling slows to every minute, the next
|
|
536
|
+
failure is already 60 seconds after the last success, so it shows the error at once. Polling
|
|
537
|
+
stops when the state is `ready`; focus and explicit retry still refresh configuration.
|
|
482
538
|
|
|
483
539
|
Use a server that reports connection state before you enable `sampleProducts`. Successful
|
|
484
540
|
config responses without `state` still mean ready. A server that does not report `unbound`
|