@godaddy/gd-commerce-storefront 0.9.0 → 0.11.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 +100 -9
- package/dist/index.d.ts +256 -163
- package/dist/index.js +756 -249
- package/dist/messages/fr-FR.d.ts +7 -0
- package/dist/messages/fr-FR.js +143 -0
- package/dist/messages-CHl-bWx9.d.ts +152 -0
- package/dist/styles.css +6 -0
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ Use the providers your application already has; do not create another router or
|
|
|
57
57
|
| `onItemAdded` | unset | Called with `{ name, quantity, isSample }` after each add, live or sample; use it to confirm adds on a site with a cart page instead of a drawer |
|
|
58
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
|
+
| `messages` | English | The package's text, such as `frFR`. Keys left out stay English. See [translations](#translations) |
|
|
60
61
|
| `theme` | inherited text and transparent wrappers; neutral control palette | CSS custom properties applied to storefront surfaces and the portalled drawer |
|
|
61
62
|
| `getSwatch` | unset | Swatches for attribute values, such as colors. Returns a CSS `background` for a value, or nothing. See [swatches](#swatches) |
|
|
62
63
|
|
|
@@ -81,6 +82,7 @@ A connection failure leaves the surrounding application and its state mounted. C
|
|
|
81
82
|
'--commerce-radius': '0.75rem',
|
|
82
83
|
'--commerce-sale': '#b42318',
|
|
83
84
|
'--commerce-on-sale': '#ffffff',
|
|
85
|
+
'--commerce-card-image-aspect': '1 / 1',
|
|
84
86
|
}}
|
|
85
87
|
>
|
|
86
88
|
{/* Register /collection and /item/:productId in your router. */}
|
|
@@ -92,7 +94,7 @@ The stylesheet includes all required utilities and scopes them to the package's
|
|
|
92
94
|
|
|
93
95
|
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.
|
|
94
96
|
|
|
95
|
-
Prices follow the provider's `locale`, which defaults to `en-US`. UI text is English
|
|
97
|
+
Prices follow the provider's `locale`, which defaults to `en-US`. UI text is English unless the site sets [`messages`](#translations).
|
|
96
98
|
|
|
97
99
|
## Compatibility
|
|
98
100
|
|
|
@@ -107,6 +109,8 @@ The repository example uses React Router 7. Follow each router version's own Rea
|
|
|
107
109
|
| `CommerceStorefront` | Recommended integration: provider and one drawer |
|
|
108
110
|
| `Catalog` | Six products per cursor page, or `pageSize`. Its `title`, `description`, and `showHeader` props are deprecated; compose `CatalogRoot` instead |
|
|
109
111
|
| `CatalogRoot` and catalog parts | Build a [custom catalog or card layout](#custom-catalog-layouts) with the same rules as `Catalog` |
|
|
112
|
+
| `useProductCard`, `useCatalog` | Card and catalog state for your own card parts, such as a category line or a product count |
|
|
113
|
+
| `SampleProduct` | The type of [your own sample products](#your-own-sample-products), for a template shown before a store is connected |
|
|
110
114
|
| `ProductDetails` | Reads `:productId`; validates URL option selections against catalog results. `ProductDetailsRoot` also takes a `productId` prop |
|
|
111
115
|
| `ProductDetailsRoot` and product parts | Build a [custom product layout](#custom-product-layouts) with the same rules as `ProductDetails` |
|
|
112
116
|
| `ProductCard` | Renders one `SKUGroup` with direct add or a product-details link; `ProductCardRoot` and card parts customize it |
|
|
@@ -117,7 +121,8 @@ The repository example uses React Router 7. Follow each router version's own Rea
|
|
|
117
121
|
| `AddToCartButton` | Adds a verified `sku`, `name`, and optional integer `quantity`; while disabled, its accessible description says why. Its children replace the label |
|
|
118
122
|
| `CommerceStatus` | Connection progress/error/retry for custom layouts |
|
|
119
123
|
| `useCommerce` | Cart, connection, pending/error state and serialized cart actions |
|
|
120
|
-
| `useMoney` | Formats minor units in the provider's `locale`, like the package's own prices, for custom parts |
|
|
124
|
+
| `useMoney` | Formats minor units in the provider's `locale`, like the package's own prices, for custom parts. `useMoneyRange` formats a range |
|
|
125
|
+
| `useMessage` | The provider's [text](#translations) by key, for custom parts |
|
|
121
126
|
| `ProductImage` | A product image with the package's placeholder for a missing or broken image, for custom parts |
|
|
122
127
|
| `CommerceProvider` | Lower-level composition when the recommended wrapper does not fit, such as a custom drawer; mount it once |
|
|
123
128
|
|
|
@@ -303,11 +308,25 @@ As with product layouts, the grid, spacing and border classes come from your own
|
|
|
303
308
|
| `ProductCardDescription` | Up to two lines of description; nothing without one. The packaged card leaves it out |
|
|
304
309
|
| `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` |
|
|
305
310
|
|
|
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
|
|
311
|
+
`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 and add only to the sample cart. They have no product page, so their title and image are not links. The packaged samples show a placeholder image and no badge or description; [your own samples](#your-own-sample-products) show their image, description and sale badge. Card images and their loading placeholders are square; set `--commerce-card-image-aspect`, such as `3 / 4`, in the `theme` or on any element around the cards to change that. 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.
|
|
307
312
|
|
|
308
313
|
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.
|
|
309
314
|
|
|
310
|
-
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.
|
|
315
|
+
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), `sample` (the sample as you passed it, or `null` for a product), `name`, `href` (`null` for samples) and `isSample` for building custom card parts. `useCatalog()` returns the catalog's `count`, `isSample` and `status` (`loading`, `error` or `ready`) inside `CatalogRoot`, for a custom product count.
|
|
316
|
+
|
|
317
|
+
Elements that are only display, such as a "Bestseller" label, a category line or a "View" link, are your own components, written with these hooks. Write such a component once and read its data where you place it, so the same label works on a card and in the cart:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
function CategoryLine() {
|
|
321
|
+
const { sample, product } = useProductCard<RoutineSample>();
|
|
322
|
+
// Samples carry your own fields; live products come from your catalog data.
|
|
323
|
+
// The type is not checked, so a sample from another catalog, such as the packaged ones, may lack your fields.
|
|
324
|
+
const category = sample ? sample.category : categoryFor(product);
|
|
325
|
+
return category ? <p className='eyebrow'>{category}</p> : null;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
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).
|
|
311
330
|
|
|
312
331
|
## Custom cart layouts
|
|
313
332
|
|
|
@@ -406,7 +425,7 @@ For a cart page instead of, or as well as, the drawer, compose the same parts in
|
|
|
406
425
|
Two hooks support custom cart markup:
|
|
407
426
|
|
|
408
427
|
- `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.
|
|
409
|
-
- `useCartItem()` returns the line inside `CartItemRoot`: `id`, `name`, `quantity`, `imageUrl`, `options`, `subtotal` and `
|
|
428
|
+
- `useCartItem()` returns the line inside `CartItemRoot`: `id`, `name`, `quantity`, `imageUrl`, `options`, `subtotal`, `currency` and `sample` (the sample as it was added, or `null` for a store line).
|
|
410
429
|
|
|
411
430
|
## Replacing text
|
|
412
431
|
|
|
@@ -433,7 +452,30 @@ Parts whose wording a site often changes take replacement content as children. T
|
|
|
433
452
|
| `ProductCardAction` | `{ action, busy, soldOut }` |
|
|
434
453
|
| `ProductDetailsBackLink` | Content only |
|
|
435
454
|
|
|
436
|
-
A replaced label stays the start of the control's accessible name, so voice control users can say what they see. Where the package adds the product name for screen readers, it follows as visually hidden text: `<CartItemRemove>Delete</CartItemRemove>` is named "Delete Mug". Controls without replacement text, such as "Increase quantity of Mug",
|
|
455
|
+
A replaced label stays the start of the control's accessible name, so voice control users can say what they see. Where the package adds the product name for screen readers, it follows as visually hidden text: `<CartItemRemove>Delete</CartItemRemove>` is named "Delete Mug". Controls without replacement text, such as "Increase quantity of Mug", take their names from [`messages`](#translations).
|
|
456
|
+
|
|
457
|
+
## Translations
|
|
458
|
+
|
|
459
|
+
The package's text comes from the provider's `messages`. Set it with `locale` for a translated store; the two are separate, so a site that sets `locale` for prices never gets new wording.
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
import { CommerceStorefront } from '@godaddy/gd-commerce-storefront';
|
|
463
|
+
import { frFR } from '@godaddy/gd-commerce-storefront/messages/fr-FR';
|
|
464
|
+
|
|
465
|
+
<CommerceStorefront locale='fr-FR' messages={frFR}>
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
French is its own import, so English-only sites do not load it. Pass only the keys to change, and the rest stay as they were, English by default: `messages={{ ...frFR, checkout: 'Commander' }}`. The `StorefrontMessages` type lists every key, and `enUS` has the English text. New keys may be added in minor releases and stay English until translated, so type a translation of your own as `Partial<StorefrontMessages>`.
|
|
469
|
+
|
|
470
|
+
Messages are plain data, so they can come from JSON, a translation service, or a server component. `{name}` placeholders are filled in, and numbers in them are formatted for the messages' `locale`. A message with a count can have plural forms, chosen by the language's rules; French counts 0 as singular:
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
{ productCount: { one: '{count} produit affiché', other: '{count} produits affichés' } }
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
The server's failure messages are English. With English text the storefront shows them as they are; with any other language it shows the text in `errorCodes` for the response's `code`, such as `scope_mismatch`, or the generic `requestFailed`. Product names, descriptions and attribute names come from the catalog as the merchant entered them, and are not translated. Sample prices stay in USD.
|
|
477
|
+
|
|
478
|
+
[Replacement text](#replacing-text) and props such as `CatalogHeader`'s `title` take priority over `messages`. For custom parts, `useMessage()` returns the provider's text by key, `useMoney()` formats prices, and `useMoneyRange()` formats a range the way the `locale` writes it, such as `$20.00 – $25.00` or `20,00–25,00 €`.
|
|
437
479
|
|
|
438
480
|
## Styling hooks
|
|
439
481
|
|
|
@@ -458,7 +500,7 @@ Every part's outer element has a `data-commerce-part` attribute with the part's
|
|
|
458
500
|
| `item-removed` | `itemId`, `isSample` |
|
|
459
501
|
| `discount-applied` | `code` |
|
|
460
502
|
| `checkout-started` | `cartId`; sent just before the browser leaves for checkout, so use `navigator.sendBeacon` or a keepalive request |
|
|
461
|
-
| `error` | `operation` (`add`, `change-quantity`, `remove`, `discount`, `refresh` or `checkout`)
|
|
503
|
+
| `error` | `operation` (`add`, `change-quantity`, `remove`, `discount`, `refresh` or `checkout`), the `message` the cart shows in the provider's language at the time of the failure, and the server's `code`, such as `scope_mismatch`, when it sent one. The cart's own notice follows a later change of `messages`; the event's text does not, so log `code` to group failures |
|
|
462
504
|
|
|
463
505
|
New event types may be added in minor releases, so ignore types you do not handle. A callback that throws or returns a rejected promise, including `onItemAdded`, is logged to the console and does not fail the cart operation. Async callbacks are not awaited, so a slow analytics call never delays the cart.
|
|
464
506
|
|
|
@@ -506,8 +548,8 @@ A site can mount the same components before it has a connected store:
|
|
|
506
548
|
</CommerceStorefront>
|
|
507
549
|
```
|
|
508
550
|
|
|
509
|
-
`Catalog.sampleProducts` defaults to `false`.
|
|
510
|
-
(Product 1–6, $10–$60 USD) **only** when `/api/commerce/config` reports `state: 'unbound'`.
|
|
551
|
+
`Catalog.sampleProducts` defaults to `false`. `true` allows six built-in sample products
|
|
552
|
+
(Product 1–6, $10–$60 USD, without images) **only** when `/api/commerce/config` reports `state: 'unbound'`.
|
|
511
553
|
When a store is connected, the server reports a different state and the components show real
|
|
512
554
|
products. You do not need to change props or the component tree. The prop has no effect while
|
|
513
555
|
a store is connected. Real empty catalogs and errors never fall back to samples.
|
|
@@ -536,6 +578,55 @@ state for up to 60 seconds before showing the error. Once polling slows to every
|
|
|
536
578
|
failure is already 60 seconds after the last success, so it shows the error at once. Polling
|
|
537
579
|
stops when the state is `ready`; focus and explicit retry still refresh configuration.
|
|
538
580
|
|
|
581
|
+
#### Your own sample products
|
|
582
|
+
|
|
583
|
+
A template can show its own samples instead, with the same rule. Pass them to `sampleProducts` on
|
|
584
|
+
`Catalog` or `CatalogRoot`:
|
|
585
|
+
|
|
586
|
+
```tsx
|
|
587
|
+
interface RoutineSample extends SampleProduct {
|
|
588
|
+
category: string;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const samples: RoutineSample[] = [
|
|
592
|
+
{
|
|
593
|
+
key: 'daily-cleanser',
|
|
594
|
+
name: 'Daily Cleanser',
|
|
595
|
+
price: 3200,
|
|
596
|
+
description: 'Gentle, pH-balanced foam that removes without stripping.',
|
|
597
|
+
imageUrl: '/images/cleanser.jpg',
|
|
598
|
+
category: 'Cleanse',
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
|
|
602
|
+
<Catalog sampleProducts={samples} />
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Each sample needs a unique `key`, a `name` and a `price` in minor units of USD. `compareAtPrice`,
|
|
606
|
+
`description` and `imageUrl` are optional. `imageUrl` is any URL the browser can load, such as a
|
|
607
|
+
file in the site's own public folder; the package does not upload or check it. An image that
|
|
608
|
+
fails to load shows the image placeholder on the card and no image in the cart. `CatalogRoot`
|
|
609
|
+
throws, while no store is connected, for an empty list, an entry that is not an object, a
|
|
610
|
+
duplicate or empty key, a packaged sample's key (`sample-1` to `sample-6`), an empty name, a
|
|
611
|
+
`description` or `imageUrl` that is not a string, or a price or compare-at price that is not a
|
|
612
|
+
whole number of 0 or more. Once a store is connected the samples are not shown, so development
|
|
613
|
+
builds log the problem instead and the store's catalog still renders. A list written inline is
|
|
614
|
+
compared by its values, so it does not rebuild the cards when it is unchanged. In a card with
|
|
615
|
+
`ProductCardTitle`, a sample's image is hidden from screen readers, which read the name from the
|
|
616
|
+
title.
|
|
617
|
+
|
|
618
|
+
The card parts show a sample's image, description, price, and a Sale badge and struck-through
|
|
619
|
+
price when `compareAtPrice` is above `price`. Samples have no product page. Fields of your own,
|
|
620
|
+
such as `category`, come back unchanged from `useProductCard().sample` and, once added, from
|
|
621
|
+
`useCartItem().sample`. The type you pass, as in `useProductCard<RoutineSample>()`, is not
|
|
622
|
+
checked, so where other samples can appear, read your own fields as possibly missing. The sample
|
|
623
|
+
cart keeps each sample as it was added, with its image, so the cart still shows it on a page
|
|
624
|
+
without the catalog. While a catalog with a sample of the same key is mounted, the cart shows
|
|
625
|
+
that sample's current fields, so a price or name loaded later matches the card. The cart lists
|
|
626
|
+
packaged samples in catalog order and your own in the order they were added, after them. Once a
|
|
627
|
+
store is connected, the same card tree shows the store's products, so build the card for both:
|
|
628
|
+
your own parts read `sample` before a store is connected and `product` after.
|
|
629
|
+
|
|
539
630
|
Use a server that reports connection state before you enable `sampleProducts`. Successful
|
|
540
631
|
config responses without `state` still mean ready. A server that does not report `unbound`
|
|
541
632
|
never shows samples. See the [server contract](docs/server-api.md) for the host's
|