@thorprovider/blocks 0.1.2 → 1.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 +107 -0
- package/README.md +108 -23
- package/docs/standards/data-contracts.md +13 -2
- package/package.json +26 -10
- package/scripts/fix-esm-extensions.mjs +40 -0
- package/src/blocks/e-commerce/navigation/back-to-top.test.tsx +27 -0
- package/src/blocks/e-commerce/navigation/back-to-top.tsx +71 -0
- package/src/blocks/e-commerce/navigation/breadcrumb.test.tsx +174 -0
- package/src/blocks/e-commerce/navigation/breadcrumb.tsx +130 -0
- package/src/blocks/e-commerce/navigation/mobile-bottom-nav.test.tsx +74 -0
- package/src/blocks/e-commerce/navigation/mobile-bottom-nav.tsx +78 -0
- package/src/blocks/e-commerce/navigation/skip-to-content.test.tsx +35 -0
- package/src/blocks/e-commerce/navigation/skip-to-content.tsx +37 -0
- 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 +424 -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-list/product-showcase-1.test.tsx +78 -0
- package/src/blocks/e-commerce/product-list/product-showcase-1.tsx +277 -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} +204 -109
- 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/navbars/navbar-4-account-menu.test.tsx +56 -0
- package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +30 -14
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.test.tsx +150 -0
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +102 -9
- package/src/blocks/marketing/navbars/navbar-4.test.tsx +154 -2
- package/src/blocks/marketing/navbars/navbar-4.tsx +152 -32
- package/src/index.ts +93 -46
- package/src/lib/contracts-curated.test.ts +42 -24
- package/src/lib/contracts.test.ts +143 -2
- package/src/lib/contracts.ts +395 -13
- 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 -37
- package/.turbo/turbo-type-check.log +0 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/contracts.d.mts +0 -1584
- package/dist/contracts.d.ts +0 -1584
- package/dist/contracts.js +0 -390
- package/dist/contracts.js.map +0 -1
- package/dist/contracts.mjs +0 -334
- package/dist/contracts.mjs.map +0 -1
- package/dist/index.d.mts +0 -2786
- package/dist/index.d.ts +0 -2786
- package/dist/index.js +0 -17139
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -17059
- package/dist/index.mjs.map +0 -1
- 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.tsx +0 -131
- 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 -137
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -65
- 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/lib/contracts.ts
CHANGED
|
@@ -95,13 +95,31 @@ export const productDetailProductSchema = z.object({
|
|
|
95
95
|
})
|
|
96
96
|
export type ProductDetailProductSpec = z.infer<typeof productDetailProductSchema>
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Canonical ProductOverview contract. Uses the canonical product projection and adds the
|
|
100
|
+
* bounded `presentation` option plus the `rich` presentation's optional data. Callbacks are not
|
|
101
|
+
* part of the schema (they are runtime props).
|
|
102
|
+
*/
|
|
103
|
+
export const productOverviewReviewSchema = z.object({
|
|
104
|
+
id: z.number(),
|
|
105
|
+
author: z.string(),
|
|
106
|
+
rating: z.number(),
|
|
107
|
+
comment: z.string(),
|
|
108
|
+
date: z.string(),
|
|
109
|
+
helpful: z.number().optional(),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
export const productOverviewPropsSchema = z.object({
|
|
99
113
|
product: productDetailProductSchema,
|
|
114
|
+
presentation: z.enum(['standard', 'gallery', 'rich']).optional(),
|
|
100
115
|
selectedVariantId: z.string().nullable().optional(),
|
|
101
116
|
quantity: z.number().optional(),
|
|
102
117
|
rating: z.object({ value: z.number(), count: z.number() }).optional(),
|
|
103
118
|
deliveryEstimate: z.string().optional(),
|
|
104
119
|
shipping: moneySchema.optional(),
|
|
120
|
+
specifications: z.record(z.string(), z.string()).optional(),
|
|
121
|
+
features: z.array(z.string()).optional(),
|
|
122
|
+
reviews: z.array(productOverviewReviewSchema).optional(),
|
|
105
123
|
sizesLabel: z.string().optional(),
|
|
106
124
|
variantLabel: z.string().optional(),
|
|
107
125
|
reviewsLabel: z.string().optional(),
|
|
@@ -112,8 +130,24 @@ export const productOverview1PropsSchema = z.object({
|
|
|
112
130
|
quantityLabel: z.string().optional(),
|
|
113
131
|
deliveryEstimateLabel: z.string().optional(),
|
|
114
132
|
shippingLabel: z.string().optional(),
|
|
133
|
+
badgeLabel: z.string().optional(),
|
|
134
|
+
availabilityLabel: z.string().optional(),
|
|
135
|
+
skuLabel: z.string().optional(),
|
|
136
|
+
overviewLabel: z.string().optional(),
|
|
137
|
+
specsLabel: z.string().optional(),
|
|
138
|
+
featuresLabel: z.string().optional(),
|
|
139
|
+
specificationsLabel: z.string().optional(),
|
|
140
|
+
galleryLabel: z.string().optional(),
|
|
141
|
+
photosLabel: z.string().optional(),
|
|
142
|
+
saveLabel: z.string().optional(),
|
|
143
|
+
savedLabel: z.string().optional(),
|
|
144
|
+
shareLabel: z.string().optional(),
|
|
145
|
+
basedOnReviewsLabel: z.string().optional(),
|
|
146
|
+
foundHelpfulLabel: z.string().optional(),
|
|
147
|
+
ofCounterLabel: z.string().optional(),
|
|
148
|
+
freeDeliveryLabel: z.string().optional(),
|
|
115
149
|
})
|
|
116
|
-
export type
|
|
150
|
+
export type ProductOverviewSpecProps = z.infer<typeof productOverviewPropsSchema>
|
|
117
151
|
|
|
118
152
|
export const cardProductSchema = z.object({
|
|
119
153
|
id: z.union([z.string(), z.number()]),
|
|
@@ -203,7 +237,7 @@ export type NavCartItem = z.infer<typeof navCartItemSchema>
|
|
|
203
237
|
export const storeUserSchema = z.object({
|
|
204
238
|
name: z.string(),
|
|
205
239
|
email: z.string(),
|
|
206
|
-
avatar: z.string(),
|
|
240
|
+
avatar: z.string().optional(),
|
|
207
241
|
initials: z.string(),
|
|
208
242
|
})
|
|
209
243
|
export type StoreUser = z.infer<typeof storeUserSchema>
|
|
@@ -275,17 +309,129 @@ export const storeContainer1PropsSchema = z.object({
|
|
|
275
309
|
})
|
|
276
310
|
export type StoreContainer1SpecProps = z.infer<typeof storeContainer1PropsSchema>
|
|
277
311
|
|
|
278
|
-
|
|
279
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Canonical product presentation — the serializable projection a storefront host
|
|
314
|
+
* produces from a canonical `Product` for the merchandising sections. Unlike
|
|
315
|
+
* `cardProductSchema` (legacy, numeric `price`), `price`/`compareAtPrice` carry
|
|
316
|
+
* canonical `Money` (decimal major-unit string + currency code), so the block
|
|
317
|
+
* formats in the shopper's currency and locale and renders a truthful
|
|
318
|
+
* unavailable state instead of `$0`. Optional fields are omitted, never `""`.
|
|
319
|
+
*
|
|
320
|
+
* This contract is additive: `cardProductSchema` and its consumers are
|
|
321
|
+
* unchanged, and a legacy numeric `price` still renders the legacy output.
|
|
322
|
+
*/
|
|
323
|
+
export const productPresentationSchema = z.object({
|
|
324
|
+
id: z.union([z.string(), z.number()]),
|
|
325
|
+
title: z.string(),
|
|
326
|
+
/** Omitted (never `""`) when the backend has no image. */
|
|
327
|
+
image: z.string().optional(),
|
|
328
|
+
imageAlt: z.string().optional(),
|
|
329
|
+
/** Product destination; omitted when unknown. */
|
|
330
|
+
href: z.string().optional(),
|
|
331
|
+
price: moneySchema,
|
|
332
|
+
/** Comparison ("was") price; rendered only when valid and higher than `price`. */
|
|
333
|
+
compareAtPrice: moneySchema.optional(),
|
|
334
|
+
/** Only from real evidence; omitted otherwise (never fabricated). */
|
|
335
|
+
rating: z.number().optional(),
|
|
336
|
+
badge: z.string().optional(),
|
|
337
|
+
})
|
|
338
|
+
export type ProductPresentation = z.infer<typeof productPresentationSchema>
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Canonical product card contract (item cell of a product listing). The cell
|
|
342
|
+
* consumes one canonical `ProductPresentation` projection only — the legacy
|
|
343
|
+
* numeric-price card shape is not accepted. `presentation` is the bounded
|
|
344
|
+
* structure switch (`classic`/`minimal`/`rich`) and `wishlisted` is the
|
|
345
|
+
* host-resolved membership state; callbacks and label strings are runtime
|
|
346
|
+
* props and are excluded here. Collection concerns (heading, item cap, loading
|
|
347
|
+
* and empty states) belong to `ProductGrid`.
|
|
348
|
+
*/
|
|
349
|
+
export const productCardPropsSchema = z.object({
|
|
350
|
+
item: productPresentationSchema,
|
|
351
|
+
presentation: z.enum(['classic', 'minimal', 'rich']).optional(),
|
|
352
|
+
wishlisted: z.boolean().optional(),
|
|
353
|
+
})
|
|
354
|
+
export type ProductCardSpecProps = z.infer<typeof productCardPropsSchema>
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Canonical product grid contract (listing). Consumes a collection of
|
|
358
|
+
* canonical `ProductPresentation` items and owns the listing's heading, item
|
|
359
|
+
* cap, wishlist membership, and loading/empty/ready states. `wishlistedIds` is
|
|
360
|
+
* resolved per cell before rendering, so the cell receives only its own
|
|
361
|
+
* boolean. Callbacks and label strings are runtime props and are excluded
|
|
362
|
+
* here. Error state belongs to the host.
|
|
363
|
+
*/
|
|
364
|
+
export const productGridPropsSchema = z.object({
|
|
365
|
+
items: z.array(productPresentationSchema),
|
|
366
|
+
presentation: z.enum(['classic', 'minimal', 'rich']).optional(),
|
|
280
367
|
heading: z.string().optional(),
|
|
281
368
|
maxItems: z.number().int().positive().optional(),
|
|
369
|
+
wishlistedIds: z.array(z.union([z.string(), z.number()])).readonly().optional(),
|
|
370
|
+
loading: z.boolean().optional(),
|
|
371
|
+
skeletonCount: z.number().int().positive().optional(),
|
|
372
|
+
emptyLabel: z.string().optional(),
|
|
373
|
+
})
|
|
374
|
+
export type ProductGridSpecProps = z.infer<typeof productGridPropsSchema>
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Serializable contract for the combined grid-and-carousel product
|
|
378
|
+
* presentation (`ProductShowcase1`). The grid pages are fixed-size, navigated
|
|
379
|
+
* manually (prev/next) with a visible current-page indication; there is no
|
|
380
|
+
* autoplay and no infinite loop. React-only callbacks are excluded.
|
|
381
|
+
*/
|
|
382
|
+
export const productShowcase1PropsSchema = z.object({
|
|
383
|
+
products: z.array(productPresentationSchema),
|
|
384
|
+
heading: z.string().optional(),
|
|
385
|
+
description: z.string().optional(),
|
|
386
|
+
pageSize: z.number().int().positive().optional(),
|
|
387
|
+
prevLabel: z.string().optional(),
|
|
388
|
+
nextLabel: z.string().optional(),
|
|
389
|
+
pageLabel: z.string().optional(),
|
|
390
|
+
unavailablePriceLabel: z.string().optional(),
|
|
391
|
+
emptyLabel: z.string().optional(),
|
|
392
|
+
addToCartLabel: z.string().optional(),
|
|
282
393
|
})
|
|
283
|
-
export type
|
|
394
|
+
export type ProductShowcase1SpecProps = z.infer<typeof productShowcase1PropsSchema>
|
|
284
395
|
|
|
285
|
-
|
|
286
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Canonical shopping cart contract (consolidated shopping-cart family). Lines
|
|
398
|
+
* are keyed by the backend cart line-item id and carry canonical `Money`
|
|
399
|
+
* amounts only — the legacy numeric-price shape is not accepted: a line always
|
|
400
|
+
* carries the backend-authoritative `lineTotal`, and an optional `unitPrice` is
|
|
401
|
+
* carried verbatim (never used for a local `price * quantity` computation).
|
|
402
|
+
* Suggestions fold the numbered carts' recommendations: optional image (omitted
|
|
403
|
+
* when absent) and optional canonical `Money` price. `presentation` is the
|
|
404
|
+
* bounded structure switch (`compact`/`page`); callbacks and interaction state
|
|
405
|
+
* are runtime props and are excluded here.
|
|
406
|
+
*/
|
|
407
|
+
export const shoppingCartItemSchema = z.object({
|
|
408
|
+
id: z.string(),
|
|
409
|
+
name: z.string(),
|
|
410
|
+
quantity: z.number().int().min(0),
|
|
411
|
+
/** Optional; absent images are omitted, never serialized as `""`. */
|
|
412
|
+
image: z.string().optional(),
|
|
413
|
+
variant: z.string().optional(),
|
|
414
|
+
/** Canonical backend line total; a malformed amount renders as unavailable. */
|
|
415
|
+
lineTotal: moneySchema,
|
|
416
|
+
/** Canonical backend unit price; carried verbatim, never used locally. */
|
|
417
|
+
unitPrice: moneySchema.optional(),
|
|
418
|
+
})
|
|
419
|
+
export type ShoppingCartItem = z.infer<typeof shoppingCartItemSchema>
|
|
420
|
+
|
|
421
|
+
export const shoppingCartSuggestionSchema = z.object({
|
|
422
|
+
id: z.string(),
|
|
423
|
+
name: z.string(),
|
|
424
|
+
/** Optional; absent images are omitted, never serialized as `""`. */
|
|
425
|
+
image: z.string().optional(),
|
|
426
|
+
unitPrice: moneySchema.optional(),
|
|
427
|
+
})
|
|
428
|
+
export type ShoppingCartSuggestion = z.infer<typeof shoppingCartSuggestionSchema>
|
|
429
|
+
|
|
430
|
+
export const shoppingCartPropsSchema = z.object({
|
|
431
|
+
items: z.array(shoppingCartItemSchema),
|
|
287
432
|
promotions: z.array(promotionSchema).default([]),
|
|
288
|
-
recentlyViewed: z.array(
|
|
433
|
+
recentlyViewed: z.array(shoppingCartSuggestionSchema).optional(),
|
|
434
|
+
presentation: z.enum(['compact', 'page']).optional(),
|
|
289
435
|
/** Backend-authoritative totals; rendered verbatim, never computed locally. */
|
|
290
436
|
totals: cartTotalsSchema.optional(),
|
|
291
437
|
totalsState: z.enum(['idle', 'pending', 'error']).optional(),
|
|
@@ -293,7 +439,6 @@ export const shoppingCart4PropsSchema = z.object({
|
|
|
293
439
|
totalsUnavailableLabel: z.string().optional(),
|
|
294
440
|
amountUnavailableLabel: z.string().optional(),
|
|
295
441
|
retryLabel: z.string().optional(),
|
|
296
|
-
taxRate: z.number().optional(),
|
|
297
442
|
titleLabel: z.string().optional(),
|
|
298
443
|
subtotalLabel: z.string().optional(),
|
|
299
444
|
discountLabel: z.string().optional(),
|
|
@@ -311,6 +456,9 @@ export const shoppingCart4PropsSchema = z.object({
|
|
|
311
456
|
estimatedDeliveryLabel: z.string().optional(),
|
|
312
457
|
secureCheckoutLabel: z.string().optional(),
|
|
313
458
|
recentlyViewedLabel: z.string().optional(),
|
|
459
|
+
promoPromptLabel: z.string().optional(),
|
|
460
|
+
promoPlaceholderLabel: z.string().optional(),
|
|
461
|
+
promoApplyLabel: z.string().optional(),
|
|
314
462
|
collapseLabel: z.string().optional(),
|
|
315
463
|
expandLabel: z.string().optional(),
|
|
316
464
|
checkoutLabel: z.string().optional(),
|
|
@@ -321,7 +469,7 @@ export const shoppingCart4PropsSchema = z.object({
|
|
|
321
469
|
decreaseLabel: z.string().optional(),
|
|
322
470
|
increaseLabel: z.string().optional(),
|
|
323
471
|
})
|
|
324
|
-
export type
|
|
472
|
+
export type ShoppingCartSpecProps = z.infer<typeof shoppingCartPropsSchema>
|
|
325
473
|
|
|
326
474
|
/**
|
|
327
475
|
* Optional brand seams shared by the existing blocks. These mirror the
|
|
@@ -349,7 +497,7 @@ export type HeroImage = z.infer<typeof heroImageSchema>
|
|
|
349
497
|
|
|
350
498
|
export const navbar4PropsSchema = z.object({
|
|
351
499
|
storeCategories: z.array(storeCategorySchema),
|
|
352
|
-
shippingNote: z.string(),
|
|
500
|
+
shippingNote: z.string().optional(),
|
|
353
501
|
utilityLinks: z.array(utilityLinkSchema),
|
|
354
502
|
logoText: z.string(),
|
|
355
503
|
logoHref: z.string().optional(),
|
|
@@ -368,6 +516,8 @@ export const navbar4PropsSchema = z.object({
|
|
|
368
516
|
subtotalUnavailableLabel: z.string().optional(),
|
|
369
517
|
user: storeUserSchema.optional(),
|
|
370
518
|
accountMenuItems: z.array(accountMenuItemSchema).optional(),
|
|
519
|
+
accountPending: z.boolean().optional(),
|
|
520
|
+
accountUnavailable: z.boolean().optional(),
|
|
371
521
|
})
|
|
372
522
|
export type Navbar4SpecProps = z.infer<typeof navbar4PropsSchema>
|
|
373
523
|
|
|
@@ -383,6 +533,115 @@ export const ctaSchema = z.object({
|
|
|
383
533
|
})
|
|
384
534
|
export type CtaSpec = z.infer<typeof ctaSchema>
|
|
385
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Serializable subset of the canonical `StorefrontHero`'s props (callbacks
|
|
538
|
+
* excluded). Every popularity, review, count, or countdown field is optional
|
|
539
|
+
* host-supplied evidence: omitted fields render nothing (honest omission)
|
|
540
|
+
* instead of fabricated defaults, and prices are formatted from the supplied
|
|
541
|
+
* ISO 4217 `currency` via `Intl.NumberFormat` (never hardcoded).
|
|
542
|
+
*/
|
|
543
|
+
export const storefrontHeroProductSchema = z.object({
|
|
544
|
+
id: z.string(),
|
|
545
|
+
name: z.string(),
|
|
546
|
+
price: z.number(),
|
|
547
|
+
originalPrice: z.number().optional(),
|
|
548
|
+
image: z.string().optional(),
|
|
549
|
+
imageAlt: z.string().optional(),
|
|
550
|
+
brand: z.string().optional(),
|
|
551
|
+
colors: z.array(z.string()).optional(),
|
|
552
|
+
rating: z.number().optional(),
|
|
553
|
+
reviewCount: z.number().optional(),
|
|
554
|
+
inStock: z.boolean().optional(),
|
|
555
|
+
isNew: z.boolean().optional(),
|
|
556
|
+
tag: z.string().optional(),
|
|
557
|
+
})
|
|
558
|
+
export type StorefrontHeroProduct = z.infer<typeof storefrontHeroProductSchema>
|
|
559
|
+
|
|
560
|
+
export const storefrontHeroPropsSchema = z.object({
|
|
561
|
+
title: z.string(),
|
|
562
|
+
description: z.string(),
|
|
563
|
+
presentation: z.enum(['compact', 'showcase', 'editorial', 'promotional']).optional(),
|
|
564
|
+
currency: z.string().optional(),
|
|
565
|
+
products: z.array(storefrontHeroProductSchema),
|
|
566
|
+
badge: z.string().optional(),
|
|
567
|
+
titleAccent: z.string().optional(),
|
|
568
|
+
primaryCta: ctaSchema.optional(),
|
|
569
|
+
secondaryCta: ctaSchema.optional(),
|
|
570
|
+
heroImage: heroImageSchema.optional(),
|
|
571
|
+
departments: z
|
|
572
|
+
.array(
|
|
573
|
+
z.object({
|
|
574
|
+
id: z.string(),
|
|
575
|
+
name: z.string(),
|
|
576
|
+
image: z.string().optional(),
|
|
577
|
+
imageAlt: z.string().optional(),
|
|
578
|
+
count: z.string().optional(),
|
|
579
|
+
href: z.string().optional(),
|
|
580
|
+
}),
|
|
581
|
+
)
|
|
582
|
+
.optional(),
|
|
583
|
+
featuredDeals: z
|
|
584
|
+
.array(
|
|
585
|
+
z.object({
|
|
586
|
+
id: z.string(),
|
|
587
|
+
name: z.string(),
|
|
588
|
+
description: z.string().optional(),
|
|
589
|
+
image: z.string().optional(),
|
|
590
|
+
imageAlt: z.string().optional(),
|
|
591
|
+
price: z.number(),
|
|
592
|
+
originalPrice: z.number().optional(),
|
|
593
|
+
cta: ctaSchema.optional(),
|
|
594
|
+
}),
|
|
595
|
+
)
|
|
596
|
+
.optional(),
|
|
597
|
+
storeLocator: z
|
|
598
|
+
.object({
|
|
599
|
+
title: z.string(),
|
|
600
|
+
description: z.string(),
|
|
601
|
+
ctaLabel: z.string(),
|
|
602
|
+
ctaHref: z.string().optional(),
|
|
603
|
+
})
|
|
604
|
+
.optional(),
|
|
605
|
+
shippingInfo: z.string().optional(),
|
|
606
|
+
deals: z
|
|
607
|
+
.array(
|
|
608
|
+
z.object({
|
|
609
|
+
id: z.string(),
|
|
610
|
+
title: z.string(),
|
|
611
|
+
discount: z.string().optional(),
|
|
612
|
+
endTime: z.string().optional(),
|
|
613
|
+
}),
|
|
614
|
+
)
|
|
615
|
+
.optional(),
|
|
616
|
+
categories: z
|
|
617
|
+
.array(
|
|
618
|
+
z.object({
|
|
619
|
+
id: z.string(),
|
|
620
|
+
name: z.string(),
|
|
621
|
+
image: z.string().optional(),
|
|
622
|
+
imageAlt: z.string().optional(),
|
|
623
|
+
count: z.string().optional(),
|
|
624
|
+
href: z.string().optional(),
|
|
625
|
+
}),
|
|
626
|
+
)
|
|
627
|
+
.optional(),
|
|
628
|
+
rotationInterval: z.number().optional(),
|
|
629
|
+
addToCartLabel: z.string().optional(),
|
|
630
|
+
buyNowLabel: z.string().optional(),
|
|
631
|
+
newLabel: z.string().optional(),
|
|
632
|
+
outOfStockLabel: z.string().optional(),
|
|
633
|
+
colorsLabel: z.string().optional(),
|
|
634
|
+
discountSuffixLabel: z.string().optional(),
|
|
635
|
+
searchPlaceholder: z.string().optional(),
|
|
636
|
+
searchLabel: z.string().optional(),
|
|
637
|
+
featuredDescription: z.string().optional(),
|
|
638
|
+
shopNowLabel: z.string().optional(),
|
|
639
|
+
endsInLabel: z.string().optional(),
|
|
640
|
+
productsLabel: z.string().optional(),
|
|
641
|
+
viewLabel: z.string().optional(),
|
|
642
|
+
})
|
|
643
|
+
export type StorefrontHeroSpecProps = z.infer<typeof storefrontHeroPropsSchema>
|
|
644
|
+
|
|
386
645
|
export const faqItemSchema = z.object({
|
|
387
646
|
value: z.string(),
|
|
388
647
|
question: z.string(),
|
|
@@ -407,7 +666,130 @@ export const ctaSection1PropsSchema = z.object({
|
|
|
407
666
|
})
|
|
408
667
|
export type CtaSection1SpecProps = z.infer<typeof ctaSection1PropsSchema>
|
|
409
668
|
|
|
669
|
+
/**
|
|
670
|
+
* Serializable contract for the canonical `Feature` benefits section. The
|
|
671
|
+
* item `icon` is a Lucide icon NAME (e.g. `'Zap'`) that the registry resolves
|
|
672
|
+
* to a component at the seam; the React-only callbacks (`onCtaClick`,
|
|
673
|
+
* `onLearnMore`) are intentionally excluded — the catalog adapter binds them.
|
|
674
|
+
*/
|
|
675
|
+
export const featureItemSpecSchema = z.object({
|
|
676
|
+
id: z.string(),
|
|
677
|
+
title: z.string(),
|
|
678
|
+
description: z.string(),
|
|
679
|
+
icon: z.string(),
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* `cards` branch: the card grid never owns an image. The presentation is
|
|
684
|
+
* optional so an omitted value resolves to `cards`.
|
|
685
|
+
*/
|
|
686
|
+
export const featureCardsSpecSchema = z.object({
|
|
687
|
+
items: z.array(featureItemSpecSchema),
|
|
688
|
+
presentation: z.literal('cards').optional(),
|
|
689
|
+
badge: z.string().optional(),
|
|
690
|
+
title: z.string().optional(),
|
|
691
|
+
description: z.string().optional(),
|
|
692
|
+
ctaLabel: z.string().optional(),
|
|
693
|
+
learnMoreLabel: z.string().optional(),
|
|
694
|
+
className: z.string().optional(),
|
|
695
|
+
})
|
|
696
|
+
export type FeatureCardsSpecProps = z.infer<typeof featureCardsSpecSchema>
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* `split` branch: the editorial image is mandatory and must carry a non-empty
|
|
700
|
+
* `src`. `alt` stays required so the caller decides whether the image is
|
|
701
|
+
* informative or decorative. `cards`-only chrome is intentionally absent.
|
|
702
|
+
*/
|
|
703
|
+
export const featureSplitSpecSchema = z.object({
|
|
704
|
+
items: z.array(featureItemSpecSchema),
|
|
705
|
+
presentation: z.literal('split'),
|
|
706
|
+
title: z.string().optional(),
|
|
707
|
+
description: z.string().optional(),
|
|
708
|
+
image: z.object({ src: z.string().trim().min(1), alt: z.string() }),
|
|
709
|
+
className: z.string().optional(),
|
|
710
|
+
})
|
|
711
|
+
export type FeatureSplitSpecProps = z.infer<typeof featureSplitSpecSchema>
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Serialized `Feature` union. The split branch is listed first so
|
|
715
|
+
* `presentation: 'split'` resolves deterministically; an omitted presentation
|
|
716
|
+
* falls through to `cards`. A split input without a non-empty image matches
|
|
717
|
+
* neither branch and fails to parse.
|
|
718
|
+
*/
|
|
719
|
+
export const featurePropsSchema = z.union([featureSplitSpecSchema, featureCardsSpecSchema])
|
|
720
|
+
export type FeatureSpecProps = z.infer<typeof featurePropsSchema>
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Canonical category contract for the consolidated `ProductCategory` block.
|
|
724
|
+
*
|
|
725
|
+
* `categories` is the canonical category projection (a `@thorprovider/types`
|
|
726
|
+
* `ProductCategory` subset): ids are canonical strings, optional fields are
|
|
727
|
+
* omitted rather than serialized as `""`, and there are no demo-only required
|
|
728
|
+
* fields (no required subtitle, sector icon enum, or Lucide component). `icon`
|
|
729
|
+
* is a plain, serializable icon-key string resolved by the block against a
|
|
730
|
+
* bounded map with a neutral fallback. The `presentation` value is a bounded
|
|
731
|
+
* structure switch, never a per-store fork. Callbacks are runtime props and are
|
|
732
|
+
* excluded here (see the component).
|
|
733
|
+
*/
|
|
734
|
+
export const productCategoryItemSchema = z.object({
|
|
735
|
+
id: z.string(),
|
|
736
|
+
handle: z.string().optional(),
|
|
737
|
+
name: z.string(),
|
|
738
|
+
description: z.string().optional(),
|
|
739
|
+
/** Optional real count; an unknown count is omitted, never fabricated as `0`. */
|
|
740
|
+
itemCount: z.number().optional(),
|
|
741
|
+
/** Omitted (never `""`) when the backend has no image. */
|
|
742
|
+
image: z.string().optional(),
|
|
743
|
+
trending: z.boolean().optional(),
|
|
744
|
+
/** Serializable icon key (e.g. `'shirt'`); unknown keys render a neutral fallback. */
|
|
745
|
+
icon: z.string().optional(),
|
|
746
|
+
subcategories: z.array(z.string()).optional(),
|
|
747
|
+
featured: z.boolean().optional(),
|
|
748
|
+
})
|
|
749
|
+
export type ProductCategoryItem = z.infer<typeof productCategoryItemSchema>
|
|
750
|
+
|
|
751
|
+
export const productCategoryPropsSchema = z.object({
|
|
752
|
+
categories: z.array(productCategoryItemSchema),
|
|
753
|
+
presentation: z.enum(['grid', 'icons', 'list']).optional(),
|
|
754
|
+
title: z.string().optional(),
|
|
755
|
+
description: z.string().optional(),
|
|
756
|
+
badge: z.string().optional(),
|
|
757
|
+
cta: ctaSchema.optional(),
|
|
758
|
+
/** Controlled search value; filtering is owned by the host. */
|
|
759
|
+
query: z.string().optional(),
|
|
760
|
+
searchPlaceholder: z.string().optional(),
|
|
761
|
+
searchLabel: z.string().optional(),
|
|
762
|
+
emptyLabel: z.string().optional(),
|
|
763
|
+
noResultsLabel: z.string().optional(),
|
|
764
|
+
browseLabel: z.string().optional(),
|
|
765
|
+
viewAllLabel: z.string().optional(),
|
|
766
|
+
trendingLabel: z.string().optional(),
|
|
767
|
+
featuredLabel: z.string().optional(),
|
|
768
|
+
itemsLabel: z.string().optional(),
|
|
769
|
+
imageFallback: z.string().optional(),
|
|
770
|
+
className: z.string().optional(),
|
|
771
|
+
})
|
|
772
|
+
export type ProductCategorySpecProps = z.infer<typeof productCategoryPropsSchema>
|
|
773
|
+
|
|
774
|
+
export const categoryCardPropsSchema = z.object({
|
|
775
|
+
category: productCategoryItemSchema,
|
|
776
|
+
presentation: z.enum(['grid', 'icons', 'list']).optional(),
|
|
777
|
+
browseLabel: z.string().optional(),
|
|
778
|
+
itemsLabel: z.string().optional(),
|
|
779
|
+
trendingLabel: z.string().optional(),
|
|
780
|
+
featuredLabel: z.string().optional(),
|
|
781
|
+
imageFallback: z.string().optional(),
|
|
782
|
+
})
|
|
783
|
+
export type CategoryCardSpecProps = z.infer<typeof categoryCardPropsSchema>
|
|
784
|
+
|
|
785
|
+
export const categoryGridPropsSchema = productCategoryPropsSchema.extend({
|
|
786
|
+
maxItems: z.number().int().positive().optional(),
|
|
787
|
+
loading: z.boolean().optional(),
|
|
788
|
+
skeletonCount: z.number().int().positive().optional(),
|
|
789
|
+
})
|
|
790
|
+
export type CategoryGridSpecProps = z.infer<typeof categoryGridPropsSchema>
|
|
791
|
+
|
|
410
792
|
// Pure monetary helpers, re-exported from the server-safe contracts entry so a
|
|
411
793
|
// host boundary can validate/format `Money` without importing the React entry.
|
|
412
794
|
export { isCompleteFiniteDecimal, isAvailableMoney, formatMoney } from './money'
|
|
413
|
-
export { resolveAbsoluteQuantity } from './cart'
|
|
795
|
+
export { resolveAbsoluteQuantity } from './cart'
|
package/src/ui/button-group.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
2
2
|
import { cn } from "cn"
|
|
3
3
|
import { Slot } from "radix-ui"
|
|
4
4
|
|
|
5
|
-
import { Separator } from "
|
|
5
|
+
import { Separator } from "./separator"
|
|
6
6
|
|
|
7
7
|
const buttonGroupVariants = cva(
|
|
8
8
|
"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
package/src/ui/carousel.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import useEmblaCarousel, {
|
|
|
7
7
|
} from "embla-carousel-react"
|
|
8
8
|
import { ArrowLeft, ArrowRight } from "lucide-react"
|
|
9
9
|
|
|
10
|
-
import { Button } from "
|
|
10
|
+
import { Button } from "./button"
|
|
11
11
|
|
|
12
12
|
type CarouselApi = UseEmblaCarouselType[1]
|
|
13
13
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
package/src/ui/dialog.tsx
CHANGED
package/src/ui/field.tsx
CHANGED
|
@@ -4,8 +4,8 @@ import { useMemo } from "react"
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
5
|
import { cn } from "cn"
|
|
6
6
|
|
|
7
|
-
import { Label } from "
|
|
8
|
-
import { Separator } from "
|
|
7
|
+
import { Label } from "./label"
|
|
8
|
+
import { Separator } from "./separator"
|
|
9
9
|
|
|
10
10
|
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
11
11
|
return (
|
package/src/ui/input-group.tsx
CHANGED
|
@@ -4,9 +4,9 @@ import * as React from "react"
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
5
|
import { cn } from "cn"
|
|
6
6
|
|
|
7
|
-
import { Button } from "
|
|
8
|
-
import { Input } from "
|
|
9
|
-
import { Textarea } from "
|
|
7
|
+
import { Button } from "./button"
|
|
8
|
+
import { Input } from "./input"
|
|
9
|
+
import { Textarea } from "./textarea"
|
|
10
10
|
|
|
11
11
|
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
12
12
|
return (
|
package/src/ui/pagination.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
MoreHorizontalIcon,
|
|
7
7
|
} from "lucide-react"
|
|
8
8
|
|
|
9
|
-
import { buttonVariants, type Button } from "
|
|
9
|
+
import { buttonVariants, type Button } from "./button"
|
|
10
10
|
|
|
11
11
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
|
12
12
|
return (
|
package/src/ui/sidebar.tsx
CHANGED
|
@@ -6,24 +6,24 @@ import { cn } from "cn"
|
|
|
6
6
|
import { PanelLeftIcon } from "lucide-react"
|
|
7
7
|
import { Slot } from "radix-ui"
|
|
8
8
|
|
|
9
|
-
import { useIsMobile } from "
|
|
10
|
-
import { Button } from "
|
|
11
|
-
import { Input } from "
|
|
12
|
-
import { Separator } from "
|
|
9
|
+
import { useIsMobile } from "../hooks/use-mobile"
|
|
10
|
+
import { Button } from "./button"
|
|
11
|
+
import { Input } from "./input"
|
|
12
|
+
import { Separator } from "./separator"
|
|
13
13
|
import {
|
|
14
14
|
Sheet,
|
|
15
15
|
SheetContent,
|
|
16
16
|
SheetDescription,
|
|
17
17
|
SheetHeader,
|
|
18
18
|
SheetTitle,
|
|
19
|
-
} from "
|
|
20
|
-
import { Skeleton } from "
|
|
19
|
+
} from "./sheet"
|
|
20
|
+
import { Skeleton } from "./skeleton"
|
|
21
21
|
import {
|
|
22
22
|
Tooltip,
|
|
23
23
|
TooltipContent,
|
|
24
24
|
TooltipProvider,
|
|
25
25
|
TooltipTrigger,
|
|
26
|
-
} from "
|
|
26
|
+
} from "./tooltip"
|
|
27
27
|
|
|
28
28
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
|
29
29
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
package/src/ui/toggle-group.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { type VariantProps } from "class-variance-authority"
|
|
|
5
5
|
import { cn } from "cn"
|
|
6
6
|
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
|
|
7
7
|
|
|
8
|
-
import { toggleVariants } from "
|
|
8
|
+
import { toggleVariants } from "./toggle"
|
|
9
9
|
|
|
10
10
|
const ToggleGroupContext = React.createContext<
|
|
11
11
|
VariantProps<typeof toggleVariants> & {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": false,
|
|
5
|
+
"emitDeclarationOnly": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"incremental": false
|
|
9
|
+
},
|
|
10
|
+
"include": ["src/**/*"],
|
|
11
|
+
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/*.test.tsx"]
|
|
12
|
+
}
|
package/tsup.config.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { defineConfig } from 'tsup'
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// Emit one module per source file (no bundling) so consumers can import a
|
|
5
|
+
// single block and bundle only what they import. `outbase: 'src'` preserves
|
|
6
|
+
// the source layout under dist/ (e.g. dist/blocks/.../navbar-4.mjs).
|
|
7
|
+
entry: ['src/**/*.ts', 'src/**/*.tsx', '!src/**/*.test.ts', '!src/**/*.test.tsx'],
|
|
8
8
|
format: ['cjs', 'esm'],
|
|
9
|
-
|
|
9
|
+
// Declarations are emitted per-file by `tsc -p tsconfig.build.json` (see the
|
|
10
|
+
// build script); tsup's bundled dts step OOMs with 170+ entries.
|
|
11
|
+
dts: false,
|
|
10
12
|
splitting: false,
|
|
11
13
|
sourcemap: true,
|
|
12
14
|
clean: true,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
bundle: false,
|
|
16
|
+
esbuildOptions(options) {
|
|
17
|
+
options.outbase = 'src'
|
|
18
|
+
}
|
|
15
19
|
})
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
CLI Building entry: {"index":"src/index.ts","contracts":"src/lib/contracts.ts"}
|
|
2
|
-
CLI Using tsconfig: tsconfig.json
|
|
3
|
-
CLI tsup v8.5.1
|
|
4
|
-
CLI Using tsup config: /home/nelson/dev/active/thorprovider/repos/thorcommerce/packages/blocks/tsup.config.ts
|
|
5
|
-
CLI Target: esnext
|
|
6
|
-
CLI Cleaning output folder
|
|
7
|
-
CJS Build start
|
|
8
|
-
ESM Build start
|
|
9
|
-
DTS Build start
|
|
10
|
-
ESM dist/contracts.mjs 10.65 KB
|
|
11
|
-
ESM dist/index.mjs 751.64 KB
|
|
12
|
-
ESM dist/contracts.mjs.map 26.52 KB
|
|
13
|
-
ESM dist/index.mjs.map 1.24 MB
|
|
14
|
-
ESM ⚡️ Build success in 1060ms
|
|
15
|
-
CJS dist/contracts.js 15.38 KB
|
|
16
|
-
CJS dist/index.js 849.47 KB
|
|
17
|
-
CJS dist/contracts.js.map 26.72 KB
|
|
18
|
-
CJS dist/index.js.map 1.22 MB
|
|
19
|
-
CJS ⚡️ Build success in 1061ms
|
|
20
|
-
DTS ⚡️ Build success in 15393ms
|
|
21
|
-
DTS dist/index.d.ts 107.68 KB
|
|
22
|
-
DTS dist/contracts.d.ts 52.25 KB
|
|
23
|
-
DTS dist/index.d.mts 107.68 KB
|
|
24
|
-
DTS dist/contracts.d.mts 52.25 KB
|