@thorprovider/blocks 0.1.3 → 2.0.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/CHANGELOG.md +155 -0
- package/README.md +228 -68
- package/docs/standards/data-contracts.md +13 -2
- package/package.json +26 -10
- package/scripts/fix-esm-extensions.mjs +40 -0
- package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
- package/src/blocks/application/login-forms/login-page.tsx +240 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
- package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
- package/src/blocks/application/verification/verify-1.tsx +2 -2
- package/src/blocks/application/viewport-ownership.test.tsx +41 -0
- package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
- package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
- package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
- package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
- package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
- package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
- package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
- package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
- package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
- package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
- package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
- package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
- package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
- package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
- package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
- package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
- package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
- package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
- package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
- package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
- package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
- package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
- package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
- package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
- package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
- package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
- package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
- package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
- package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
- package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
- package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
- package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
- package/src/blocks/marketing/features/feature.test.tsx +153 -0
- package/src/blocks/marketing/features/feature.tsx +195 -0
- package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
- package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
- package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
- package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
- package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
- package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
- package/src/blocks/marketing/footers/footer-section.tsx +532 -0
- package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
- package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
- package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
- package/src/blocks/marketing/navbars/navbar.tsx +735 -0
- package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
- package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
- package/src/index.ts +156 -128
- package/src/lib/contracts-curated.test.ts +47 -29
- package/src/lib/contracts.test.ts +198 -2
- package/src/lib/contracts.ts +410 -16
- package/src/ui/button-group.tsx +1 -1
- package/src/ui/carousel.tsx +1 -1
- package/src/ui/dialog.tsx +1 -1
- package/src/ui/field.tsx +2 -2
- package/src/ui/input-group.tsx +3 -3
- package/src/ui/pagination.tsx +1 -1
- package/src/ui/sidebar.tsx +7 -7
- package/src/ui/toggle-group.tsx +1 -1
- package/tsconfig.build.json +12 -0
- package/tsup.config.ts +11 -7
- package/.turbo/turbo-build.log +0 -24
- package/.turbo/turbo-test.log +0 -47
- package/.turbo/turbo-type-check.log +0 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/contracts.d.mts +0 -1732
- package/dist/contracts.d.ts +0 -1732
- package/dist/contracts.js +0 -423
- package/dist/contracts.js.map +0 -1
- package/dist/contracts.mjs +0 -365
- package/dist/contracts.mjs.map +0 -1
- package/dist/index.d.mts +0 -3068
- package/dist/index.d.ts +0 -3068
- package/dist/index.js +0 -17834
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -17740
- package/dist/index.mjs.map +0 -1
- package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
- package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
- package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
- package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
- package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
- package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
- package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
- package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
- package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
- package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
- package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
- package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
- package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
- package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
- package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
- package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
- package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
- package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
- package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
- package/src/blocks/marketing/features/feature-1.tsx +0 -97
- package/src/blocks/marketing/features/feature-2.tsx +0 -82
- package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
- package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
- package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
- package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
- package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
- package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
- package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
- package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# @thorprovider/blocks
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- dc82f71: Consolidate the category filter family into one canonical `CategoryListing` block.
|
|
8
|
+
|
|
9
|
+
- Adds `CategoryListing` (export + `CategoryListingProps` and the `CategoryListing*` option, facet, status, filter and active-filter types). It is the single controlled listing controller: products, pagination and the selected query are props; `on*Change` / `onClearFilters` / `onRepair` / `onRetry` report intent; the block never fetches, parses the URL, or keeps a private query.
|
|
10
|
+
- Two bounded presentations share one contract: `sidebar` (default; persistent desktop panel plus mobile sheet) and `toolbar` (sticky mobile toolbar, filter/sort drawers and chip rail). Presentation changes chrome only.
|
|
11
|
+
- Results render through the canonical `ProductCard` (`minimal`), so the listing owns no card markup. Facets are generic option-only groups supplied by the host; a group renders only from real metadata, a real `0` count is displayed, and an absent/invalid count is omitted. Price renders only from a real finite range, sort only from advertised options, and no result total is fabricated.
|
|
12
|
+
- `ProductCard` gains `hideAddToCart` for listing cells and renders `data-slot='product-image-placeholder'` instead of an image with an empty source.
|
|
13
|
+
- Runtime: `toFilterProduct` → `toListingProduct` (returns canonical `ProductPresentation` with `Money`), `CategoryFilterPresentation(Input)` → `CategoryListingPresentation(Input)`, `mapCatalogResultsToFilterProps` → `mapCatalogResultsToCategoryListing`, and `resolveFilterPresentationSupport` → `resolveCategoryListingSupport` plus a separate `resolveCategoryFacetSupport`. The old numbered presentation metadata is gone.
|
|
14
|
+
|
|
15
|
+
BREAKING: removes the `CategoryFilter1`, `CategoryFilter2` and `CategoryFilter4` exports, their types, and the `category-filter-4-*` helpers. Consumers must migrate to `CategoryListing`:
|
|
16
|
+
|
|
17
|
+
| Removed | Migration |
|
|
18
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `CategoryFilter1` | `CategoryListing` with `presentation='sidebar'`; replace counted categories/price bands with real `categories` / `priceRange` metadata. |
|
|
20
|
+
| `CategoryFilter2` | `CategoryListing` with `presentation='sidebar'` (the default). Products become canonical `ProductPresentation` items with `Money` prices; `filterPrice` string prices are no longer accepted. |
|
|
21
|
+
| `CategoryFilter4` | `CategoryListing` with `presentation='toolbar'`; replace bike-specific facets/frame/model fields with generic `facets` groups and remove its private grid/state. |
|
|
22
|
+
|
|
23
|
+
- 28a3bd7: Consolidate the numbered checkout-form family into one canonical page-level `Checkout` block.
|
|
24
|
+
|
|
25
|
+
- Adds `Checkout` (export plus `CheckoutProps`, `CheckoutValues`, `CheckoutPresentation`, `CheckoutStepId`, `CheckoutLabels`, `CheckoutValuesFieldErrors`, `CheckoutFieldDescriptions`, `CheckoutChangeHandler`, `CheckoutOrderSummary`, `CheckoutOrderLine`, `CheckoutTotals`, `CheckoutTotalsState`, `CheckoutCountryOption`, `CheckoutShippingOption`) with a required bounded `presentation` (`single-page` | `stepper`) and an orthogonal `showReviewStep` flow option. `single-page` is the former `CheckoutForm2` structure; `stepper` is the former `CheckoutForm1` wizard; `stepper` plus the review option is the former `CheckoutForm3`. Field values are controlled (`values` + `onChange`), the step index is ephemeral block state, and `validateStep(step: CheckoutStepId)` is injected — the block contains no validation logic.
|
|
26
|
+
- Totals are canonical `Money` only and render verbatim from the host; the legacy numeric `price`/`shipping`/`tax`/`discount`/`promoDiscount` fields and their fallback summation are removed. A missing or malformed required total renders as unavailable with recovery, never as zero.
|
|
27
|
+
- Removes raw card capture: `cardNumber`, `expiryDate`, `cvv`, `cardName`, the `collectCardFields` escape hatch, the CVV visibility toggle, and `card-formatting.ts`. Payment methods remain data (`PaymentMethod[]`); card collection belongs to the host payment-provider surface.
|
|
28
|
+
- Removes the numbered exports `CheckoutForm1`, `CheckoutForm2`, `CheckoutForm3` and their `CheckoutForm1*`, `CheckoutForm2*`, `CheckoutForm3*` types.
|
|
29
|
+
|
|
30
|
+
BREAKING: consumers importing a numbered checkout must migrate to `Checkout` with the `presentation` prop (and `showReviewStep` for the review flow). Values lose the card fields and `sameAsBilling`; totals must be supplied as backend `Money`; `validateStep` now receives a `CheckoutStepId` instead of a step number.
|
|
31
|
+
|
|
32
|
+
- 06333a8: Consolidate the navigation family into two canonical components and retire the numbered navbars.
|
|
33
|
+
|
|
34
|
+
- `Navbar` is the canonical page-level header with bounded `bar`, `mega`, and `floating` presentations. Content is serializable (`links`, `items`, `featured`, `signIn`, `cta`, `logoText`, `logoHref`, `currentPath`); icons resolve through a host-bound `iconMap`; `floating` uses `sticky` so it reserves its own space.
|
|
35
|
+
- `StorefrontNavbar` is the canonical commerce shell (formerly `Navbar4`) and keeps its full storefront contract: utility strip, category navigation, optional search, account states, and the controlled cart sheet with canonical `Money`.
|
|
36
|
+
- Removed identifiers: `Navbar1`, `Navbar2`, `Navbar3`, `Navbar4`, `navbar4PropsSchema`, `Navbar4SpecProps`, `Navbar4LikeButton`, `Navbar4ProductGrid`, and the `navbar-4-*` helper modules (now `storefront-navbar-cart-sheet`, `storefront-navbar-account-menu`, `storefront-navbar-search-bar`, `storefront-navbar-mobile-nav`).
|
|
37
|
+
- The page-level header no longer owns cart state: `cartCount` and `onOpenCart` are gone. Use the commerce shell for the cart affordance.
|
|
38
|
+
|
|
39
|
+
- 65fac4b: Remove the numbered compatibility adapters `Testimonial1`, `Testimonial2`, `LoginPage1`, `LoginPage2`, `FooterSection1`, `FooterSection3`, and `FooterSection4`, including their deep module paths and legacy prop types. Migrate testimonials to `TestimonialSection` with `carousel` or `bento`, login pages to `LoginPage` with `card` or `social`, and footers to `FooterSection` with `brand-columns`, `newsletter-grid`, or `contact`. Select native footer link behavior explicitly where browser navigation is intended.
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- 5d32763: The auth blocks render into the host page instead of owning it.
|
|
44
|
+
|
|
45
|
+
`LoginPage` (both `card` and `social`), `SignUpPage1`, `ForgotPasswordPage1` and `Verify1` no longer paint a full-viewport surface. They drop the `min-h-dvh` canvas, the page landmark (the `social` presentation no longer renders a root `<main>`), the `bg-linear-to-br` backdrop with `isolate`/`overflow-hidden`, the vertical centring and the page-level padding. Each block now renders a neutral, content-sized root that keeps its width cap (`max-w-md` for login and recovery, `max-w-lg` for registration) and its card styling, including the shadow.
|
|
46
|
+
|
|
47
|
+
Why: a generated storefront mounts these blocks inside the default chrome (notice + banner + navbar + footer). A viewport-height canvas inside that chrome centres the form roughly 100 px low and, for the `social` presentation, emits a second `<main>` landmark inside the page's own `<main>`.
|
|
48
|
+
|
|
49
|
+
No prop, type or export changed, so existing call sites keep compiling. What changes is visual ownership: a host that relied on the block's canvas must now supply one (a route shell, a modal, a split screen, or a template the host owns), using the same theme tokens. `@thorprovider/storefront` does exactly that for the generated `account` view, which keeps the shopper-visible result unchanged.
|
|
50
|
+
|
|
51
|
+
## 1.0.0
|
|
52
|
+
|
|
53
|
+
### Major Changes
|
|
54
|
+
|
|
55
|
+
- f011717: Consolidate the `home.benefits` feature family into one canonical `Feature` block.
|
|
56
|
+
|
|
57
|
+
- Adds `Feature` (export + `featurePropsSchema` / `FeatureProps` in the contracts surface) with a bounded `presentation` prop (`cards` | `split`, default `cards`). `cards` is the former header-badge card grid with per-item learn-more and an optional section CTA; `split` is the former split layout with the optional grayscale image column. `split` deliberately ignores `badge` and per-item learn-more; an absent or empty image `src` renders no image column (never a blank `src`).
|
|
58
|
+
- The storefront catalog declares the two presentations as the `home.benefits` variants with `legacyAliases` (`Feature1` → `cards`, `Feature2` → `split`), so existing manifests storing the numbered names keep regenerating to the same presentation; the benefits feature's `compact` / `detailed` configuration values are unchanged and map onto `cards` / `split`.
|
|
59
|
+
|
|
60
|
+
BREAKING: removes the `Feature1` and `Feature2` exports (components and their `Feature1Props`/`Feature1Item`/`Feature2Props`/`Feature2Item` types) that this canonical block supersedes. Consumers must migrate to `Feature` with the `presentation` prop.
|
|
61
|
+
|
|
62
|
+
- f011717: Consolidate the product-card family into one canonical `ProductCard` block.
|
|
63
|
+
|
|
64
|
+
- Adds `ProductCard` (export + `productCardPropsSchema` / `ProductCardSpecProps` in the contracts surface) with a bounded `presentation` prop (`classic` | `minimal` | `rich`, default `classic`). `classic` is the former `ProductCard1` structure (badge, optional rating, compare-at, add-to-cart footer, reduced-motion-safe motion); `minimal` is the former `ProductCard2` lean look; `rich` is the former `ProductCard3` overlay actions (wishlist heart with host-owned controlled state or uncontrolled visual fallback, and view-similar rendered only when a handler is supplied). The comparison-table surface of the former `ProductCard5` is deliberately retired (a listing controller, not an item cell).
|
|
65
|
+
- Items are the canonical `ProductPresentation` contract only (identity, title, optional image with alt, canonical `Money` price, optional `compareAtPrice`/`rating`/`badge`). An unresolved price renders an unavailable state with add-to-cart disabled — never a zero. Ratings, badges and crossed-out prices render only from host-supplied data; the block invents no evidence. Prices render through the canonical `formatMoney` path (no hardcoded currency symbol) and all user-facing copy is overridable with the previous Spanish defaults.
|
|
66
|
+
- Removes the numbered exports `ProductCard1`, `ProductCard2`, `ProductCard3`, `ProductCard5`, their types `ProductCard1Product` / `ProductCard1Props`, the contracts surface `productCard1PropsSchema` / `ProductCard1SpecProps`, and the `product-card-3-reaction-toggle` / `product-card-5-reaction-toggle` / `product-card-5-checkbox-check` subcomponents.
|
|
67
|
+
- Migration: supply item data in the canonical presentation contract (canonical `Money` amounts as decimal major-unit strings plus `currencyCode`). The former numeric-price props (`price: number`, `originalPrice?: number`, mandatory demo merchandising fields) are not retained; map them to `Money` at your boundary. Consumer code importing a numbered card selects the equivalent bounded presentation (`1` → `classic`, `2` → `minimal`, `3` → `rich`); the former comparison table has no replacement in this block.
|
|
68
|
+
|
|
69
|
+
- f011717: Consolidate the `home.categories` family into one canonical `ProductCategory` block.
|
|
70
|
+
|
|
71
|
+
- Adds `ProductCategory` (export + `productCategoryPropsSchema` / `ProductCategoryProps`) built on the canonical `CategoryCard` shape. It exposes a bounded `presentation` prop (`grid` | `icons` | `list`); optional per-category data (`icon` as a serializable key, `subcategories`, `featured`, `itemCount`) degrades gracefully when absent.
|
|
72
|
+
- Interaction is controlled: `onBrowse` / `onViewAll` / `onQueryChange` report from handlers with no private query state; a category count is omitted when absent or invalid and a real zero is preserved; a missing image renders a neutral placeholder, never `src=""`; no copy, currency, or demo metrics are hardcoded.
|
|
73
|
+
|
|
74
|
+
BREAKING: removes the `ProductCategory1`, `ProductCategory2`, `ProductCategory4`, and `ProductCategory5` exports (and the `product-categories/avatar-group` helper) that this canonical block supersedes. Consumers must migrate to `ProductCategory` with the `presentation` prop.
|
|
75
|
+
|
|
76
|
+
- ece154d: Consolidate the `product.overview` family into one canonical `ProductOverview` block.
|
|
77
|
+
|
|
78
|
+
- Adds `ProductOverview` (export + `productOverviewPropsSchema` / `ProductOverviewProps`) built on the canonical `ProductDetailProduct` projection and canonical `Money`. It exposes a bounded `presentation` prop (`standard` | `gallery` | `rich`); `rich` accepts optional `specifications` / `features` / `reviews` props and degrades gracefully when they are absent.
|
|
79
|
+
- Selection and quantity are controlled; `onAddToCart` emits the explicitly selected backend variant id and quantity and never substitutes the first variant. Missing price renders as unavailable, never `$0`; currency is never hardcoded.
|
|
80
|
+
|
|
81
|
+
BREAKING: removes the `ProductOverview1`, `ProductOverview2`, and `ProductOverview5` exports (and the `product-overview-5-*` subcomponents) that this canonical block supersedes. Consumers must migrate to `ProductOverview` with the `presentation` prop.
|
|
82
|
+
|
|
83
|
+
- b41cc50: Consolidate the numbered shopping-cart family into one canonical `ShoppingCart` block.
|
|
84
|
+
|
|
85
|
+
- Adds `ShoppingCart` (export + `shoppingCartPropsSchema` / `ShoppingCartSpecProps` in the contracts surface) with a bounded `presentation` prop (`compact` | `page`). `compact` is the former `ShoppingCart4` review card; `page` is the same contract with the page-level structure. The optional promo-entry affordance reports the entered code via `onApplyPromo(code)` and renders only authoritative promotion/discount data — it is hidden when the callback is absent, and the block never validates promotion codes itself. Host-supplied `recentlyViewed` suggestions replace the former `ShoppingCart2` recommendations, priced with canonical `Money`.
|
|
86
|
+
- Items are the canonical contract only: identity, title, quantity, optional image (omitted when absent, never `src=""`), and canonical `Money` amounts. Each line carries the backend-authoritative `lineTotal` (rendered verbatim; a malformed amount renders as unavailable — never a zero or free-shipping claim) and an optional `unitPrice` carried verbatim but never used for local arithmetic. `totals` come from the backend and are rendered verbatim; no local tax/shipping/threshold math, no fabricated merchandising, no hardcoded currency.
|
|
87
|
+
- Removes the numbered exports `ShoppingCart1`, `ShoppingCart2`, `ShoppingCart3`, `ShoppingCart4` (and their `ShoppingCart1CartItem`, `ShoppingCart2Props`/`ShoppingCart2Item`, `ShoppingCart3Props`/`ShoppingCart3CartItem`, `ShoppingCart4Props`/`ShoppingCart4Item` types), the contracts surface `shoppingCart4PropsSchema` / `ShoppingCart4SpecProps`, and the `shopping-cart-{1,2,3,4}-spinning-counter`, `shopping-cart-2-recommendations`, and `shopping-cart-2-item-row` subcomponents.
|
|
88
|
+
|
|
89
|
+
BREAKING: consumers importing a numbered cart must migrate to `ShoppingCart` with the `presentation` prop. The former numeric-price props (`price: number`, local `taxRate`/shipping-threshold arithmetic) are not retained; map item amounts to canonical `Money` (`{ amount, currencyCode }`) at your boundary and pass backend `totals` verbatim.
|
|
90
|
+
|
|
91
|
+
- f011717: Consolidate the `home.hero` family into one canonical `StorefrontHero` block.
|
|
92
|
+
|
|
93
|
+
- Adds `StorefrontHero` (export + `storefrontHeroPropsSchema` / `StorefrontHeroProps` / `StorefrontHeroProduct` in the contracts surface) with a bounded `presentation` prop (`compact` | `showcase` | `editorial` | `promotional`) reusing the registry's archetype intent vocabulary. `compact` is the former `StorefrontHero1` text-forward hero with product grid and editorial image seam; `showcase` the former `StorefrontHero2` search + rotating product carousel (the search input renders only when `onSearch` is supplied); `editorial` the former `StorefrontHero5` department grid with optional featured deals and store locator; `promotional` the former `StorefrontHero6` deals bar + category grid.
|
|
94
|
+
- Truthful evidence only: ratings, review counts, category/department counts, deal descriptions, and countdowns render only from host-supplied data; discount treatments render only from a real `originalPrice > price` pair (`isOnSale` is dropped); `currency` (ISO 4217) formats all amounts via `Intl.NumberFormat` — no hardcoded `$`. Rotation respects reduced motion and is presentation-internal.
|
|
95
|
+
- The storefront catalog declares `StorefrontHero` as the single `home.hero` variant with `legacyAliases` (`StorefrontHero1`/`2`/`5`/`6` → the canonical), so existing manifests storing the numbered names keep regenerating to a working composition; `archetypes.json` intents map each intent to its matching presentation.
|
|
96
|
+
|
|
97
|
+
BREAKING: removes the `StorefrontHero1`, `StorefrontHero2`, `StorefrontHero5`, and `StorefrontHero6` exports (and the `StorefrontHero1Props` type) that this canonical block supersedes. The `FeaturedProduct` type is now exported from the canonical source and its evidence fields (`brand`, `colors`, `rating`, `reviewCount`, `originalPrice`) are optional. Consumers must migrate to `StorefrontHero` with the `presentation` prop.
|
|
98
|
+
|
|
99
|
+
- dc2b11b: Split the category collection into an item cell and a listing.
|
|
100
|
+
|
|
101
|
+
- `CategoryCard` is the item cell: one `category` at a time, with the existing `grid`, `icons` and `list` presentations. It renders no section, heading, search, CTA or empty state.
|
|
102
|
+
- `CategoryGrid` is the listing: it owns the collection, `maxItems`, section copy, controlled search (list presentation), loading skeletons and the empty state, and renders `CategoryCard` for every item.
|
|
103
|
+
- BREAKING: `ProductCategory` (collection) is replaced by `CategoryGrid`; the single-item surface is `CategoryCard`. `ProductCategoryProps` is replaced by `CategoryGridProps` / `CategoryCardProps`.
|
|
104
|
+
|
|
105
|
+
Migration: replace `<ProductCategory categories={items} ... />` with `<CategoryGrid categories={items} ... />`. For a single category, render `<CategoryCard category={item} ... />`.
|
|
106
|
+
|
|
107
|
+
- 3e43eb0: BREAKING: `ProductCard` is now an item cell and a new `ProductGrid` listing owns the collection.
|
|
108
|
+
|
|
109
|
+
- `ProductCard` (`packages/blocks/src/blocks/e-commerce/product-list/product-card.tsx`) now takes a single `item: ProductPresentation` (plus `presentation?` and `wishlisted?`) and renders exactly one cell — no section wrapper, no section heading, no grid, and no item cap. The `items`, `heading`, and `maxItems` props and the `wishlistedIds` collection prop are removed; `wishlisted` is now a single host-resolved boolean.
|
|
110
|
+
- The serializable contract `productCardPropsSchema` / `ProductCardSpecProps` changes accordingly (`{ item, presentation?, wishlisted? }`), still without callbacks or label strings.
|
|
111
|
+
- Adds `ProductGrid` (export + `productGridPropsSchema` / `ProductGridSpecProps`) as the canonical listing: it takes `items: ProductPresentation[]`, an optional `presentation`, `heading`, `maxItems`, `wishlistedIds`, and owns the `loading` (skeletons), `empty` (overridable copy), and ready states. It renders `ProductCard` cells (single source of cell markup) and reports actions through the same callbacks. `error` remains the host's responsibility.
|
|
112
|
+
- Migration: pass a listing's data to `ProductGrid` with `items`, `heading`, and `maxItems`; pass a single item to `ProductCard` via `item`. Resolve rich-presentation wishlist state per cell through `wishlistedIds` on the grid or `wishlisted` on the cell. The delimited showcase `ProductShowcase1` is unchanged.
|
|
113
|
+
|
|
114
|
+
- b41cc50: Require an image for the `Feature` block's `split` presentation.
|
|
115
|
+
|
|
116
|
+
- `featureSplitSpecSchema` now requires `presentation: 'split'` and a non-empty, trimmed `image.src`; `featureCardsSpecSchema` keeps `image` out of its contract. `featurePropsSchema` is the union of both branches, so TypeScript narrows `image` only after checking `presentation`.
|
|
117
|
+
- The component throws a configuration error when an untyped caller renders `split` without a non-empty image, instead of silently dropping the media column. `cards` is unchanged.
|
|
118
|
+
- Editorial fallbacks belong to the consumer, not the block library. The showcase now owns `public/demo/editorial-fallback.svg`; the storefront template seeds a neutral image in user-owned `content.ts`.
|
|
119
|
+
|
|
120
|
+
Migration: pass `{ image: { src, alt } }` when using `presentation="split"`, or switch to `presentation="cards"` if the section is icon-only. Replace the storefront's neutral seed with real editorial imagery.
|
|
121
|
+
|
|
122
|
+
### Minor Changes
|
|
123
|
+
|
|
124
|
+
- 80d4137: Add optional, backward-compatible brand customization to existing surfaces (M5):
|
|
125
|
+
|
|
126
|
+
- `@thorprovider/blocks`: `Navbar4` and `FooterSection1` accept an optional wordmark accent `{ text, className }` (trailing segment of the brand name) and an optional surface `className`; a supplied surface `className` overrides the default surface paint (default background/backdrop not emitted) so a branded surface paints correctly. `StorefrontHero1` accepts an optional editorial `heroImage { src, alt }` rendered with a native, guarded image. `ProductCategory2` accepts an optional `itemCount`: an unknown count is omitted (no fabricated `0`), a real `0` is preserved, and non-finite/negative values render no count. All props are optional and omit to the current rendering, so existing consumers are unchanged; no new block type or variant is added, and the blocks stay framework-, backend-, and data-free.
|
|
127
|
+
- `@thorprovider/storefront`: the scaffold generates a user-owned `lib/<name>/shell/{content,props}.ts` seam (brand accents, surface classes, footer description/links, newsletter) that the derived spec composes last, so `storefront sync` regenerates only derived wiring and preserves user-owned edits. The scaffold ships no newsletter submit callback, so the newsletter is omitted by default rather than rendering a fake form; default footer copy makes no delivery/shipping promise and links only to routes the storefront serves (no assumed route). No new identity CLI command is added — `create --site-name` already covers the site name.
|
|
128
|
+
|
|
129
|
+
This changeset adds release metadata only; it does not bump versions or publish packages.
|
|
130
|
+
|
|
131
|
+
- 3b3c24c: Add validated single-store commerce contracts, explicit real/mock policy, directed variant pricing, persistent guest carts, backend-authoritative totals, manual TEST checkout, and capability-authorized receipt reconciliation in generated storefronts.
|
|
132
|
+
|
|
133
|
+
Breaking migration: `@thorprovider/types` is now entirely type-only. Import `SupportedProviderType`, `PROVIDER_METADATA`, `isSupportedProviderType`, `getProviderMetadata`, and `StorefrontConfigError` from `@thorprovider/adapters` instead. The pre-1.0 curated blocks/runtime APIs also change: product detail uses canonical options/variants/Money and explicit variant selection; confirmation totals and lines use Money; `formatMoney` returns `null` for unavailable values. Real-mode failures no longer silently return mock data.
|
|
134
|
+
|
|
135
|
+
The local packed MVP candidate retains its exact current version pins until a separate release updates the compatible toolset. This changeset does not publish packages or establish public-sales readiness.
|
|
136
|
+
|
|
137
|
+
- b41cc50: Deliver the global shopper account experience (M5, change `storefront-navbar-account-menu`):
|
|
138
|
+
|
|
139
|
+
- `@thorprovider/blocks`: `Navbar4` renders a session-driven account affordance from consumer props only. An authenticated `user` (optional `avatar`) with `accountMenuItems` gets an account menu (identity + configured items + consumer-supplied sign-out); no `user` with a configured `accountHref` gets a signed-out entry point on desktop and mobile; neither configured renders no account affordance or empty container. Optional `accountPending`/`accountUnavailable` drive a pending/disabled state and an accessible unavailable/retry state (`onAccountRetry`) without presenting either as anonymous, and sign-out is suppressed while pending. Absent avatar uses the initials fallback (no empty image `src`). All callbacks stay consumer bindings outside the serializable JSON prop schema; the block never authenticates.
|
|
140
|
+
- `@thorprovider/storefront`: a mounted `customer-auth` resolves the shopper server-side per request and seeds an allowlisted display identity (`id`, `email`, `firstName`, `lastName`; the auth token stays in its httpOnly cookie). One shared client lifecycle (`account-lifecycle.client.tsx`) owns identity, guarded claims, login/register/logout, and retry with no identity fetch on mount or navigation; it reconciles same-store tabs through non-secret store-scoped signals (`BroadcastChannel` with a storage-event fallback) and on foreground/resume, discards stale generations, and shows a neutral pending state instead of stale identity/orders or a fake sign-out. `/account` is the only account surface — sign-in, registration, identity, sign-out, and real paginated orders + detail (route-scoped commerce, no direct HTTP); no address book/profile/wishlist/social/recovery/cart-merge. Account wiring is delivered through derived files (`account-session*.ts`, `account-lifecycle/account-boundary/account-mutation/account-reconcile.client.*`, `account-private.core.ts`, `account-user.core.ts`, `projection-ownership*.ts`, `registry.tsx`, `schema.ts`, `spec/index.ts`); user-owned files (`views/*/{schema,content,props,data}.ts`, `mappers.ts`, `icon-map.ts`, `shell/**`) survive `sync`. `storefront feature add|remove customer-auth` toggles the `/account` route, session reads, listeners, and navbar chrome together; without the feature the generated registry carries no account-lifecycle/account-reconcile reference and reads no session.
|
|
141
|
+
- `@thorprovider/adapters`: `getCurrentCustomer()` resolves `null` only for a confirmed unauthenticated response (HTTP 401 / `"Unauthorized"`) and throws a typed `ProviderAPIError` (`GET_CURRENT_CUSTOMER_FAILED`) on operational failures (network, timeout, 5xx), so a host can distinguish an anonymous shopper from a backend outage and render the unavailable state instead of a false sign-out. `isAuthenticated()` delegates to it and can reject. Signature and framework-agnostic boundary are unchanged.
|
|
142
|
+
|
|
143
|
+
This changeset adds release metadata only; it does not bump versions or publish packages.
|
|
144
|
+
|
|
145
|
+
- ece154d: Gate the `ProductOverview` wishlist affordance on a wired handler.
|
|
146
|
+
|
|
147
|
+
- `ProductOverview` renders its wishlist button only when `onToggleWishlist` is supplied; an unwired button is a dead control and is no longer rendered in any presentation (`standard`, `gallery`, `rich`).
|
|
148
|
+
- The storefront product template and the demo/electrostore registries drop their no-op `onToggleWishlist` wiring, so a storefront without wishlist backend support no longer shows a non-functional heart.
|
|
149
|
+
|
|
150
|
+
### Patch Changes
|
|
151
|
+
|
|
152
|
+
- ece154d: Align the `Breadcrumb` block with the page content: the trail now renders inside the shared responsive container (`mx-auto w-full max-w-7xl px-4 py-4 sm:px-6 lg:px-8`) that sibling blocks use, instead of sitting flush against the viewport edge. The container is merged with the incoming `className` via `cn`, so a custom `className` can still override it.
|
|
153
|
+
- Updated dependencies [23f876f]
|
|
154
|
+
- Updated dependencies [3b3c24c]
|
|
155
|
+
- @thorprovider/types@6.0.0
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "@thorprovider/blocks"
|
|
3
3
|
purpose: "Reusable, framework-agnostic storefront blocks built from ShadcnStore UI, decoupled from data and backend"
|
|
4
|
-
lastReviewed: "2026-
|
|
4
|
+
lastReviewed: "2026-10-21"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# @thorprovider/blocks
|
|
@@ -30,7 +30,7 @@ src/
|
|
|
30
30
|
│ └── hero-sections/hero-section-1.tsx
|
|
31
31
|
└── e-commerce/
|
|
32
32
|
├── product-list/product-card-1.tsx
|
|
33
|
-
└── storefront-hero/storefront-hero
|
|
33
|
+
└── storefront-hero/storefront-hero.tsx
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Blocks follow `src/blocks/<category>/<section>/<block>.tsx`. Each block co-locates its own prop types unless a contract is shared by two or more blocks.
|
|
@@ -39,72 +39,159 @@ Blocks follow `src/blocks/<category>/<section>/<block>.tsx`. Each block co-locat
|
|
|
39
39
|
|
|
40
40
|
| Export | Category | Description |
|
|
41
41
|
| --- | --- | --- |
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `Navbar3` | Marketing | Floating navbar that condenses into a rounded pill on scroll |
|
|
45
|
-
| `Navbar4` | Marketing | Storefront navbar: utility bar, category links, search, account menu, cart sheet |
|
|
42
|
+
| `Navbar` | Marketing | Page-level navigation header: `bar`, `mega` (grouped flyouts plus optional featured promo) or `floating` (self-reserving pinned pill) |
|
|
43
|
+
| `StorefrontNavbar` | E-commerce | Commerce navigation shell: utility strip, category mega menu, optional search, account menu, cart sheet |
|
|
46
44
|
| `Banner1` | Marketing | Announcement banner (bar or pill) with dismiss |
|
|
47
45
|
| `HeroSection1` | Marketing | Hero with badge, headline, CTA, reviewer avatars, rating and image |
|
|
48
46
|
| `CtaSection1` | Marketing | Full-width call-to-action with badge, headline and two CTAs |
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `Testimonial1` | Marketing | Testimonial carousel with rating stars |
|
|
52
|
-
| `Testimonial2` | Marketing | Bento testimonials grid |
|
|
47
|
+
| `Feature` | Marketing | Canonical benefits section: `cards` (icon feature grid) or `split` (editorial split with a required image) presentation |
|
|
48
|
+
| `TestimonialSection` | Marketing | Canonical testimonial collection section with `carousel` and `bento` presentations |
|
|
53
49
|
| `FaqBlock1` | Marketing | FAQ accordion (single or multiple open) |
|
|
54
50
|
| `PricingSection1` | Marketing | Pricing tiers with popular badge |
|
|
55
51
|
| `Statistic1` | Marketing | Metrics grid with progress bars |
|
|
56
52
|
| `LogoCloud1` | Marketing | Logo cloud with skeleton fallback |
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `ProductCard1` | E-commerce | Deals grid of product cards with add-to-cart |
|
|
60
|
-
| `ProductCard2` | E-commerce | Clean three-up grid with ratings |
|
|
61
|
-
| `ProductCard3` | E-commerce | Deals grid with badges, low-stock and wishlist |
|
|
62
|
-
| `ProductCard5` | E-commerce | Table-style comparison with sorting and filtering |
|
|
53
|
+
| `FooterSection` | Marketing | Canonical page-level footer with `brand-columns`, `newsletter-grid` or `contact` presentation |
|
|
54
|
+
| `ProductCard` | E-commerce | Canonical product card (item cell of a listing); `presentation` `classic`/`minimal`/`rich`, canonical `ProductPresentation` items with `Money` prices, evidence-only merchandising |
|
|
63
55
|
| `ProductShowcase1` | E-commerce | Combined grid-and-carousel of product cards with manual paging |
|
|
64
|
-
| `
|
|
56
|
+
| `ProductOverview` | E-commerce | Canonical product detail; bounded `presentation` `standard`/`gallery`/`rich` (rich adds optional specs/features/reviews tabs) |
|
|
65
57
|
| `ProductQuickView` | E-commerce | Quick-view dialog with gallery, variants and quantity |
|
|
66
58
|
| `ReviewRating1` | E-commerce | Review list with rating summary |
|
|
67
59
|
| `ReviewRating2` | E-commerce | Rating summary with distribution and review carousel |
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `CategoryFilter1` | E-commerce | Category filter bar |
|
|
73
|
-
| `CategoryFilter2` | E-commerce | Filter sidebar with mobile sheet |
|
|
74
|
-
| `ShoppingCart1` | E-commerce | Detailed cart summary |
|
|
75
|
-
| `ShoppingCart2` | E-commerce | Cart with promo code and shipping |
|
|
76
|
-
| `ShoppingCart4` | E-commerce | Cart review with backend-authoritative totals; `presentation` `compact` (collapsible card) or `page` (full-width review) |
|
|
77
|
-
| `CheckoutForm1` | E-commerce | Essential checkout fields |
|
|
78
|
-
| `CheckoutForm2` | E-commerce | Single-page checkout with order summary beside |
|
|
60
|
+
| `ProductCategory` | E-commerce | Canonical category block; bounded `presentation` `grid` (image cards) / `icons` (compact icon grid) / `list` (rows with subcategory chips and optional controlled search) |
|
|
61
|
+
| `CategoryListing` | E-commerce | Canonical category listing (results + pagination) with a controlled query and bounded `presentation` `sidebar` (persistent desktop panel + mobile sheet) / `toolbar` (sticky mobile toolbar + drawers); generic facet groups render only from real metadata |
|
|
62
|
+
| `ShoppingCart` | E-commerce | Canonical cart; backend-authoritative `Money` totals and item lines, optional promo entry and recently-viewed suggestions; `presentation` `compact` (collapsible card) or `page` (full-width review) |
|
|
63
|
+
| `Checkout` | E-commerce | Canonical page-level checkout; controlled values, backend-authoritative `Money` totals, `PaymentMethod[]` data with no raw card input; `presentation` `single-page` or `stepper`, optional `showReviewStep` |
|
|
79
64
|
| `CheckoutConfirmation` | E-commerce | Order confirmation with fulfilment timeline |
|
|
80
65
|
| `OrderHistory1` | E-commerce | Orders list |
|
|
81
66
|
| `OrderHistory2` | E-commerce | Order cards with carousel items |
|
|
82
|
-
| `
|
|
83
|
-
| `StorefrontHero2` | E-commerce | Search field above a trending product carousel |
|
|
84
|
-
| `StorefrontHero5` | E-commerce | Departments grid with featured deals |
|
|
85
|
-
| `StorefrontHero6` | E-commerce | Rotating deals bar beside category cards |
|
|
67
|
+
| `StorefrontHero` | E-commerce | Canonical storefront hero; `presentation` `compact` (gradient headline, CTAs, product grid), `showcase` (search + rotating product carousel), `editorial` (departments, featured deals, store locator) or `promotional` (rotating deals bar, category cards) |
|
|
86
68
|
| `AppShell1` | Application | Application shell with navigation slots |
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `SignUpPage1` | Application | Registration card with social options |
|
|
69
|
+
| `LoginPage` | Application | Canonical sign-in form with explicit `card` or `social` presentation |
|
|
70
|
+
| `SignUpPage1` | Application | Registration form with social options (same profile as `LoginPage`) |
|
|
90
71
|
| `ForgotPasswordPage1` | Application | Four-step password recovery wizard |
|
|
91
72
|
| `Verify1` | Application | Two-factor verification (OTP or backup code) |
|
|
92
73
|
|
|
93
|
-
Interactions are exposed as callbacks (e.g. `onAddToCart`, `onBuyNow`, `onSelectColor`).
|
|
74
|
+
Interactions are exposed as callbacks (e.g. `onAddToCart`, `onBuyNow`, `onSelectColor`). `StorefrontHero` and `LoginPage` render optional affordances only when their handlers are supplied; other blocks may keep elements in the DOM with no-op behavior when a callback is absent.
|
|
75
|
+
|
|
76
|
+
### Category listing
|
|
77
|
+
|
|
78
|
+
`CategoryListing` is the single controlled listing controller. Products, pagination, the selected query values, active-filter chips and the status are props; the block emits `on*Change`/`onClearFilters`/`onRepair`/`onRetry` and never fetches, parses the URL, or keeps a private query. `presentation` changes only the chrome: `sidebar` keeps a persistent desktop panel plus a mobile sheet, `toolbar` adds a sticky mobile toolbar with filter/sort drawers. Results render through the canonical `ProductCard`, so the listing never owns card markup. Facets are option-only groups (`{ id, label, options: [{ value, label, count? }] }`) supplied by the host; a group renders only when real metadata provides it, a real `0` count is displayed, and an absent/invalid count is omitted rather than invented.
|
|
79
|
+
|
|
80
|
+
### Footer sections
|
|
81
|
+
|
|
82
|
+
`FooterSection` is the canonical page-level footer. Its required `presentation`
|
|
83
|
+
is `brand-columns`, `newsletter-grid`, or `contact`; provide serializable text,
|
|
84
|
+
links, and social records as props. Social records use `{ label, href?, icon }`
|
|
85
|
+
with an optional `ariaLabel`; callbacks and visible fallback text keep using the
|
|
86
|
+
raw `label`. Bind icon IDs to `LucideIcon` components through `socialIcons`, and
|
|
87
|
+
keep that map, `logo` nodes, and callbacks outside JSON fixtures.
|
|
88
|
+
|
|
89
|
+
Column/legal and social links have separate `linkBehavior` and
|
|
90
|
+
`socialLinkBehavior` props, both defaulting to `host`. Host mode requires the
|
|
91
|
+
matching callback and renders callback-less destinations as text; `native`
|
|
92
|
+
preserves browser navigation. Contact address, phone, and email hrefs are native.
|
|
93
|
+
Newsletter controls stay visible but disabled without `onNewsletterSubmit`;
|
|
94
|
+
the callback receives the entered email and the block performs no network request.
|
|
95
|
+
|
|
96
|
+
The numbered `FooterSection1`, `FooterSection3`, and `FooterSection4` exports,
|
|
97
|
+
deep module paths, and legacy prop contracts have been removed. Migrate to
|
|
98
|
+
`FooterSection` by supplying an explicit presentation and canonical serializable
|
|
99
|
+
content props. Select `linkBehavior="native"` or
|
|
100
|
+
`socialLinkBehavior="native"` when browser navigation is intended; both default
|
|
101
|
+
to host-controlled behavior. Canonical link types are exported as
|
|
102
|
+
`CanonicalFooterLink` and `CanonicalFooterLinkColumn` to avoid collisions with
|
|
103
|
+
other package contracts.
|
|
104
|
+
|
|
105
|
+
### Login page
|
|
106
|
+
|
|
107
|
+
`LoginPage` is a sign-in composition with one shared credential form and a
|
|
108
|
+
required `presentation` of `card` or `social`. Both presentations submit the
|
|
109
|
+
same `{ email, password, remember }` payload. `remember` starts as `false` and
|
|
110
|
+
is reported to the host; the block does not decide session persistence.
|
|
111
|
+
|
|
112
|
+
The block renders into the host page and does not own it: no viewport height, no
|
|
113
|
+
page landmark, no page backdrop, no vertical centring. `card` caps its width at
|
|
114
|
+
`max-w-md` and `social` at the same cap, and the mounting page supplies the
|
|
115
|
+
canvas — a route shell, a modal, a split screen, or a template the host owns.
|
|
116
|
+
Before the shell was removed, each of these blocks painted a full-viewport
|
|
117
|
+
surface; a host that relied on that canvas must now provide it.
|
|
118
|
+
|
|
119
|
+
Provider data stays serializable. Pass provider IDs and labels separately from
|
|
120
|
+
the icon components, and provide a selection callback so provider buttons are
|
|
121
|
+
only rendered when they can act. IDs must be unique within the list; an
|
|
122
|
+
unmapped icon falls back to the visible provider label:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { AtSign } from 'lucide-react'
|
|
126
|
+
import { LoginPage } from '@thorprovider/blocks'
|
|
127
|
+
|
|
128
|
+
<LoginPage
|
|
129
|
+
presentation='social'
|
|
130
|
+
providers={[{ id: 'email', label: 'Email' }]}
|
|
131
|
+
providerIcons={{ email: AtSign }}
|
|
132
|
+
onProviderSelect={providerId => startProviderLogin(providerId)}
|
|
133
|
+
onSubmit={({ email, password, remember }) => signIn({ email, password, remember })}
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The numbered `LoginPage1` and `LoginPage2` exports, deep module paths, and legacy
|
|
138
|
+
prop contracts have been removed. Migrate to `LoginPage` and select `card` or
|
|
139
|
+
`social` explicitly; both presentations use the canonical
|
|
140
|
+
`{ email, password, remember }` host callback contract.
|
|
141
|
+
|
|
142
|
+
## Testimonials
|
|
143
|
+
|
|
144
|
+
`TestimonialSection` renders a collection of testimonials in one of two explicit
|
|
145
|
+
presentations: `carousel` or `bento`. Each item requires a stable, collection-unique
|
|
146
|
+
`id`, `name`, and `content`. `role`, `avatar`, and `rating` are optional and are
|
|
147
|
+
rendered only when supplied; the block never invents attribution or ratings and
|
|
148
|
+
does not emit an empty image source.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { TestimonialSection } from '@thorprovider/blocks'
|
|
152
|
+
|
|
153
|
+
const testimonials = [
|
|
154
|
+
{ id: 't-1', name: 'Rosa Díaz', role: 'Propietaria', content: 'Excelente servicio.' },
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
<TestimonialSection
|
|
158
|
+
presentation="carousel"
|
|
159
|
+
items={testimonials}
|
|
160
|
+
title="Lo que dicen nuestros clientes"
|
|
161
|
+
carouselLabels={{
|
|
162
|
+
previous: 'Testimonio anterior',
|
|
163
|
+
next: 'Testimonio siguiente',
|
|
164
|
+
slide: 'Ir al testimonio {n}',
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
For `carousel`, `carouselLabels` is required and supplies localized previous,
|
|
170
|
+
next, and slide-button labels; `{n}` is replaced with the one-based item number.
|
|
171
|
+
The native buttons support keyboard activation, and the selected item is exposed
|
|
172
|
+
with `aria-current`. For `bento`, omit `carouselLabels`; one-to-three items render
|
|
173
|
+
without empty cards, and collections larger than four repeat the four-tile visual
|
|
174
|
+
rhythm without dropping entries. An empty `items` array renders no section or
|
|
175
|
+
controls. `title`, `description`, and `className` are optional for either
|
|
176
|
+
presentation.
|
|
177
|
+
|
|
178
|
+
The numbered `Testimonial1` and `Testimonial2` exports, deep module paths, and
|
|
179
|
+
legacy prop contracts have been removed. Migrate to `TestimonialSection` with
|
|
180
|
+
shared testimonial items and select `carousel` or `bento` explicitly.
|
|
94
181
|
|
|
95
182
|
## Optional brand props
|
|
96
183
|
|
|
97
184
|
Three existing blocks expose an optional brand seam. Omitting the prop renders
|
|
98
185
|
the block's current output unchanged; no demo content is added.
|
|
99
186
|
|
|
100
|
-
- `
|
|
187
|
+
- `StorefrontNavbar` — `logoAccent?: { text: string; className?: string }` accents a
|
|
101
188
|
trailing segment of `logoText`. With `logoText="ElectroCuba"` and
|
|
102
189
|
`logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }}`, the block
|
|
103
190
|
renders `Electro` plus an accented `Cuba` span. If `text` is not a trailing
|
|
104
191
|
segment of `logoText`, the wordmark renders unchanged.
|
|
105
|
-
- `
|
|
192
|
+
- `FooterSection` — `brandAccent?: { text: string; className?: string }` applies
|
|
106
193
|
the same trailing-segment accent to `brandName`.
|
|
107
|
-
- `
|
|
194
|
+
- `StorefrontHero` — `heroImage?: { src: string; alt: string }` renders a native
|
|
108
195
|
`<img>` when `src` is a non-empty string, and nothing when the prop is absent
|
|
109
196
|
or `src` is blank (never an empty `src`). The product grid is unaffected.
|
|
110
197
|
|
|
@@ -113,13 +200,13 @@ the library hardcodes no brand color.
|
|
|
113
200
|
|
|
114
201
|
## Custom surface `className`
|
|
115
202
|
|
|
116
|
-
`Navbar4` (outer `<header>`) and `
|
|
203
|
+
`Navbar4` (outer `<header>`) and `FooterSection` (outer `<footer>`) accept an
|
|
117
204
|
optional `className`, merged with the existing `cn` helper. It is purely
|
|
118
205
|
presentational: no layout, structure, or behavior prop, and no hardcoded color.
|
|
119
206
|
|
|
120
207
|
- **Omitted `className`** — the block renders its default surface unchanged.
|
|
121
208
|
`Navbar4` keeps `bg-background/95` **and** its
|
|
122
|
-
`supports-backdrop-filter:bg-background/80` variant; `
|
|
209
|
+
`supports-backdrop-filter:bg-background/80` variant; `FooterSection` keeps
|
|
123
210
|
`bg-secondary`.
|
|
124
211
|
- **Supplied `className`** — the consumer owns the surface. The default
|
|
125
212
|
background **and its backdrop-filter variant** are not applied, so a semantic
|
|
@@ -128,31 +215,50 @@ presentational: no layout, structure, or behavior prop, and no hardcoded color.
|
|
|
128
215
|
it). Layout, border, sticky, and `backdrop-blur-md` classes stay internal.
|
|
129
216
|
|
|
130
217
|
The consumer is responsible for the text, transparency, and context tokens of a
|
|
131
|
-
supplied surface. `
|
|
218
|
+
supplied surface. `StorefrontNavbar` adapts its primary nav links: they inherit the
|
|
132
219
|
surface text color and add `hover:text-foreground` so `hover:bg-muted` never
|
|
133
220
|
paints white-on-light. No magic colors are introduced by the block.
|
|
134
221
|
|
|
222
|
+
## StorefrontNavbar account affordance
|
|
223
|
+
|
|
224
|
+
`StorefrontNavbar` renders a **session-driven** account affordance exclusively from
|
|
225
|
+
consumer-supplied props — it never resolves a session or authenticates itself.
|
|
226
|
+
Given a `user` (`StoreUser`; `avatar` is optional) and `accountMenuItems`, it
|
|
227
|
+
renders an authenticated account menu with the identity, the configured items,
|
|
228
|
+
and the consumer's sign-out action on desktop and mobile. Given no `user` but a
|
|
229
|
+
configured `accountHref`, it renders a signed-out account entry point on both
|
|
230
|
+
viewports; with neither configured it renders no account affordance and no empty
|
|
231
|
+
container. An absent `avatar` uses the initials fallback and emits no empty image
|
|
232
|
+
`src`. Optional `accountPending` and `accountUnavailable` props drive a
|
|
233
|
+
pending/disabled state and an accessible unavailable/retry state (via
|
|
234
|
+
`onAccountRetry`) without presenting either as an anonymous session, and sign-out
|
|
235
|
+
is suppressed while pending. All callbacks (`onSignOut`, `onAccountRetry`,
|
|
236
|
+
`onHelp`) are consumer bindings kept outside the serializable JSON prop schema;
|
|
237
|
+
the sign-out action is supplied by the consumer, so the block never performs
|
|
238
|
+
authentication.
|
|
239
|
+
|
|
135
240
|
## Footer newsletter
|
|
136
241
|
|
|
137
|
-
`
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
242
|
+
`FooterSection` renders the newsletter form disabled when
|
|
243
|
+
`onNewsletterSubmit` is absent. When supplied, the callback receives the entered
|
|
244
|
+
email; the block performs no network request. Omit `newsletter` to hide the
|
|
245
|
+
newsletter block.
|
|
141
246
|
|
|
142
247
|
## Cart presentation (controlled)
|
|
143
248
|
|
|
144
249
|
The cart is presented in two shapes that share one data contract, so a change in
|
|
145
250
|
either is visible in the other.
|
|
146
251
|
|
|
147
|
-
- `
|
|
252
|
+
- `ShoppingCart` is the cart block. Its optional `presentation` prop selects
|
|
148
253
|
`'compact'` (default: a collapsible `max-w-md` card with an internal scroller —
|
|
149
254
|
the navbar lateral) or `'page'` (always expanded, full-width `max-w-3xl`, no
|
|
150
255
|
collapse control, ordinary page scrolling — the `/cart` review route).
|
|
151
256
|
Omitting `presentation` renders the compact card byte-identically to before.
|
|
152
|
-
- Line amounts
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
257
|
+
- Line amounts render the authoritative `lineTotal: Money` verbatim (backend
|
|
258
|
+
line total, never a local `unitPrice × quantity` computation); a malformed
|
|
259
|
+
amount renders the `amountUnavailableLabel` (never a fabricated zero or a
|
|
260
|
+
currency fallback). An optional `unitPrice: Money` is carried verbatim but
|
|
261
|
+
never used for any local arithmetic.
|
|
156
262
|
- `totalsState` (`'idle' | 'pending' | 'error'`) plus `onRetryTotals` make the
|
|
157
263
|
totals controlled: pending retains the last confirmed totals and disables
|
|
158
264
|
conflicting mutations; error shows the error label and a retry.
|
|
@@ -160,28 +266,78 @@ either is visible in the other.
|
|
|
160
266
|
by backend line id) and `onRemove(id)` removes by the same id.
|
|
161
267
|
`checkoutUnavailable` hides the checkout action (e.g. no checkout installed)
|
|
162
268
|
and `checkoutDisabled` disables it (e.g. pending or invalid totals).
|
|
163
|
-
|
|
164
|
-
|
|
269
|
+
- Promo entry is an optional affordance: with `onApplyPromo(code)` it renders an
|
|
270
|
+
input that reports the entered code and shows only the authoritative applied
|
|
271
|
+
promotion/discount already present in `totals`; without the callback it is not
|
|
272
|
+
rendered, and the block never validates or resolves a code itself.
|
|
273
|
+
`onApplyPromo` never gates `recentlyViewed`, an optional host-supplied
|
|
274
|
+
suggestion list (name, optional image, optional `Money` price) rendered only
|
|
275
|
+
when supplied.
|
|
276
|
+
|
|
277
|
+
`StorefrontNavbar` owns the lateral. Pass `cartOpen` + `onCartOpenChange` to control the
|
|
165
278
|
sheet's visibility from host state (omit both to keep the legacy uncontrolled
|
|
166
279
|
sheet); the block keeps `aria-expanded`/`data-state` in sync. Its cart sheet
|
|
167
|
-
(`
|
|
280
|
+
(`StorefrontNavbarCartSheet`) takes an optional `canonical` mode where each line shows the
|
|
168
281
|
canonical `lineTotal` (never the placeholder numeric price), plus
|
|
169
282
|
`checkoutUnavailable`/`viewCartUnavailable` to hide actions the store cannot
|
|
170
283
|
serve, and a `state` (`'idle' | 'pending' | 'error'`) with retry for read/mutation
|
|
171
284
|
feedback.
|
|
172
285
|
|
|
173
|
-
##
|
|
286
|
+
## Product card (canonical)
|
|
287
|
+
|
|
288
|
+
`ProductCard` is the item cell of a product listing and the canonical member of
|
|
289
|
+
the product-card family. Its items are `ProductPresentation` only (canonical
|
|
290
|
+
`Money`): a missing price renders the `unavailablePriceLabel` with add-to-cart
|
|
291
|
+
disabled — never a zero amount — and optional image, rating, badge, and
|
|
292
|
+
compare-at price are omitted when absent (no empty `src`, no empty stars, no
|
|
293
|
+
empty chip). A compare-at price renders only when it is valid and strictly
|
|
294
|
+
higher than the selling price.
|
|
295
|
+
|
|
296
|
+
The bounded `presentation` option selects the structure — `classic` (boxed card
|
|
297
|
+
with an add-to-cart footer), `minimal` (lean image-forward tile with an
|
|
298
|
+
icon-only cart action), `rich` (overlay actions: wishlist heart and
|
|
299
|
+
view-similar) — while the data and action contract stays identical. Ratings,
|
|
300
|
+
badges, and scarcity copy render only from host-supplied data (the `badge`
|
|
301
|
+
copy and `compareAtPrice` are the evidence channels); the block never invents
|
|
302
|
+
stars, chips, or urgency.
|
|
303
|
+
|
|
304
|
+
The wishlist heart is host-owned: pass `wishlistedIds` to control it, and
|
|
305
|
+
`onToggleWishlist(item, nextState)` receives every toggle from the click
|
|
306
|
+
handler (never from an effect). Without host state the heart keeps an
|
|
307
|
+
uncontrolled visual fallback and still reports. `onViewSimilar` renders its
|
|
308
|
+
affordance only when supplied. Prices render through the canonical
|
|
309
|
+
`formatMoney` path (no hardcoded currency), and every label is overridable
|
|
310
|
+
with Spanish defaults (`Agregar al carrito`, `Precio no disponible`, …).
|
|
311
|
+
|
|
312
|
+
## Checkout block (page-level, controlled)
|
|
313
|
+
|
|
314
|
+
`Checkout` is the single public checkout surface; it replaces the numbered `CheckoutForm1`, `CheckoutForm2`, and `CheckoutForm3` blocks. It is a **controlled component**: field values arrive through `values` and every edit is reported through `onChange`, so the host owns all commercial state. The block owns only the ephemeral step index of the `stepper` presentation.
|
|
315
|
+
|
|
316
|
+
- `presentation: 'single-page' | 'stepper'` (required) — `single-page` renders every section visible at once (summary first in DOM order on mobile); `stepper` shows one step at a time with an accessible progress indicator. Both accept the same props and emit the same callbacks.
|
|
317
|
+
- `showReviewStep?: boolean` — appends a confirmation step (`stepper`) or section (`single-page`) before submission.
|
|
318
|
+
- `values: CheckoutValues` — `email`, `firstName`, `lastName`, `phone`, `address`, `city`, `state`, `zipCode`, `country`, `paymentMethod`, `saveInfo`, `newsletter`, `promoCode`. There are no card fields.
|
|
319
|
+
- `onChange: (field, value) => void` — reported per edit. The block holds no private copy of the form data and applies no masking.
|
|
320
|
+
- `orderSummary?: { items, totals? }` — line identity, name, quantity, optional variant/image, optional canonical `Money` `lineTotal` and `unitPrice`, plus backend `totals` (`subtotal`, optional `discount`/`tax`/`shipping`, `total`). Amounts render verbatim; required totals that are absent or malformed render as unavailable with the retry action, never as zero. No local arithmetic, tax rate, shipping cost, threshold, or hardcoded currency.
|
|
321
|
+
- `totalsState?: 'idle' | 'pending' | 'error'` and `onRetryTotals?` — controlled reads; pending and failed states render status/alert copy without inventing amounts.
|
|
322
|
+
- `countries?: { value, label }[]`, `shippingOptions?`, `selectedShippingOptionId?`, `onSelectShippingOption?` — host-owned options and selection. With no country list the block renders a text input; with no shipping options it renders no method section; nothing is pre-selected.
|
|
323
|
+
- `paymentMethods?: PaymentMethod[]` — payment methods as data (canonical `@thorprovider/types`). One option per method, the selected method's description, an empty state for an empty list, and a non-selectable rendering for `isEnabled === false`. The block renders **no raw card fields**: card collection belongs to the host payment-provider surface.
|
|
324
|
+
- `labels?: Partial<CheckoutLabels>` and `formatMoney?: (money: Money) => string` — every user-facing string is overridable (neutral English defaults) and amounts format through canonical `Money` with no hardcoded symbol.
|
|
325
|
+
- `fieldErrors?` / `fieldDescriptions?` — per-field messages and helper text, rendered with `role="alert"` and `aria-invalid`/`aria-describedby` wiring.
|
|
326
|
+
- `validateStep?: (step: CheckoutStepId) => boolean` and `onStepChange?` — the block consults `validateStep` before advancing and for every applicable step before submitting; `onStepChange` is a notification only, never a controlled value. Without the callback the block advances and leaves submit protection to the host.
|
|
327
|
+
- `onSubmit?`, `submitting?`, `submitError?`, `onRetrySubmit?` — submission is reported, with a pending guard and a recoverable error state; the block never creates an order, clears a cart, or claims success.
|
|
328
|
+
|
|
329
|
+
Validation itself is **not** implemented in the block. Declare checks in your store spec's validation configuration (built-in `required`/`email`/`pattern`/`minLength`/`maxLength`/cross-field `requiredIf`/`equalTo`/`lessThan`/`greaterThan`, plus catalog-registered custom functions) and evaluate them with the renderer; the messages live in the spec so they are per-store and per-language.
|
|
330
|
+
|
|
331
|
+
The order summary is an internal projection of `Checkout`, not a separate block. Order receipt and fulfilment progress remain `CheckoutConfirmation`.
|
|
332
|
+
|
|
333
|
+
### Migrating from the numbered checkout blocks
|
|
334
|
+
|
|
335
|
+
| Retired | Replacement |
|
|
336
|
+
| --- | --- |
|
|
337
|
+
| `CheckoutForm1` | `<Checkout presentation="stepper" />` — same controlled values minus `cardNumber`/`expiryDate`/`cvv`/`cardName`/`sameAsBilling`; `validateStep` now receives a `CheckoutStepId` (`'contact' \| 'shipping' \| 'payment' \| 'review'`) instead of a number |
|
|
338
|
+
| `CheckoutForm2` | `<Checkout presentation="single-page" />` — the host supplies `Money` totals; the block no longer computes tax, shipping, or free-shipping thresholds |
|
|
339
|
+
| `CheckoutForm3` | `<Checkout presentation="stepper" showReviewStep />` — values become controlled; `apartment`, card fields, per-step icons, and internal `isStepComplete` state are gone |
|
|
174
340
|
|
|
175
|
-
`CheckoutForm1` (3-step wizard) and `CheckoutForm2` (single page) are **controlled components**: they receive the current form values through a `values` prop and report every edit through `onChange`. They hold no internal form state, so a renderer's validation context can read field values from its own state model. `CheckoutForm3` (4-step wizard) keeps the same contract with internal step state.
|
|
176
|
-
|
|
177
|
-
- `paymentMethods?: PaymentMethod[]` — available payment methods as data (canonical `@thorprovider/types` `PaymentMethod`). The blocks render a radio list from this prop, show the selected method's name + `description`, and only render the card fields (`cardNumber`, `expiryDate`, `cvv`, `cardName`) when the selected method's `type === 'card'`. No payment method is hardcoded; pass an empty list to trigger the empty state.
|
|
178
|
-
- `values: CheckoutForm1FormData` / `CheckoutForm2Data` — current field values (including `cardNumber`, `expiryDate`, `cvv`, `cardName`, and `paymentMethod` for `CheckoutForm1`/`CheckoutForm3`).
|
|
179
|
-
- `onChange: (field, value) => void` — reported per edit; card grouping and MM/YY masking are applied by the block before the value reaches `onChange` (presentation only, no validation logic in the block).
|
|
180
|
-
- `fieldErrors?: Partial<Record<FieldKey, Array<{ message?: string }>>>` — per-field error messages rendered via `FieldError` (`role="alert"`, `aria-invalid`/`aria-describedby` wiring). The same channel carries async/server errors (e.g. payment decline, invalid promo).
|
|
181
|
-
- `fieldDescriptions?: Partial<Record<FieldKey, string>>` — helper text rendered via `FieldDescription`.
|
|
182
|
-
- `validateStep?: (step: number) => boolean` (wizard only) — the block calls it on "Next"; advance only happens when it returns `true`, and the final submit only fires when every step validates.
|
|
183
|
-
|
|
184
|
-
Validation itself is **not** implemented in the blocks. Declare checks in your store spec's validation configuration (built-in `required`/`email`/`pattern`/`minLength`/`maxLength`/cross-field `requiredIf`/`equalTo`/`lessThan`/`greaterThan`, plus catalog-registered custom functions like `luhn`/`cvv`/`expiryDate`) and evaluate them with the renderer; the messages live in the spec so they are per-store and per-language. Without a validation provider the blocks render with no inline errors.
|
|
185
341
|
|
|
186
342
|
## Usage
|
|
187
343
|
|
|
@@ -229,11 +385,11 @@ unique by contract, not by convention. See
|
|
|
229
385
|
rules and the review checklist.
|
|
230
386
|
|
|
231
387
|
Optional numeric projections are omitted rather than fabricated: e.g.
|
|
232
|
-
`
|
|
388
|
+
`ProductCategoryItem.itemCount` renders a count only when it is a supplied
|
|
233
389
|
finite non-negative number (a real `0` is preserved); an unknown count renders
|
|
234
390
|
no count text, never a fabricated `0`.
|
|
235
391
|
|
|
236
|
-
Product presentations (`
|
|
392
|
+
Product presentations (`ProductGrid`, `ProductShowcase1`) accept either the
|
|
237
393
|
legacy numeric-price props or a canonical `ProductPresentation` (`price`/
|
|
238
394
|
`compareAtPrice` as `Money`, `{ amount: string, currencyCode: string }` in
|
|
239
395
|
decimal major units). The canonical path formats with the store currency and
|
|
@@ -242,6 +398,10 @@ locale, renders a truthful "unavailable" state for an unresolved price (never
|
|
|
242
398
|
valid and strictly higher. Optional rating/badge/image appear only when
|
|
243
399
|
supplied; a missing image is omitted, never `src=""`.
|
|
244
400
|
|
|
401
|
+
The canonical `ProductCard` accepts only the canonical `ProductPresentation`
|
|
402
|
+
contract — the legacy numeric-price props of the numbered cards are retired
|
|
403
|
+
(migrate item data to canonical `Money`).
|
|
404
|
+
|
|
245
405
|
## Block interaction contracts
|
|
246
406
|
|
|
247
407
|
State and events between a block, its host registry, and the store must follow
|
|
@@ -251,4 +411,4 @@ callbacks (`useCallback`); a fresh object literal always notifies a reference-on
|
|
|
251
411
|
store guard; and every registered action completes its round-trip (handler → set +
|
|
252
412
|
emit → spec `on` → recompute → bind). See
|
|
253
413
|
[`docs/standards/interaction-contracts.md`](docs/standards/interaction-contracts.md)
|
|
254
|
-
for the rules and the review checklist.
|
|
414
|
+
for the rules and the review checklist.
|