@thorprovider/blocks 0.1.3 → 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 +73 -29
- 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/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-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/navbars/navbar-4-account-menu.test.tsx +27 -9
- package/src/blocks/marketing/navbars/navbar-4.test.tsx +33 -2
- package/src/blocks/marketing/navbars/navbar-4.tsx +29 -11
- package/src/index.ts +77 -48
- package/src/lib/contracts-curated.test.ts +42 -24
- package/src/lib/contracts.test.ts +143 -2
- package/src/lib/contracts.ts +346 -14
- 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/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/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()]),
|
|
@@ -275,13 +309,6 @@ export const storeContainer1PropsSchema = z.object({
|
|
|
275
309
|
})
|
|
276
310
|
export type StoreContainer1SpecProps = z.infer<typeof storeContainer1PropsSchema>
|
|
277
311
|
|
|
278
|
-
export const productCard1PropsSchema = z.object({
|
|
279
|
-
products: z.array(cardProductSchema),
|
|
280
|
-
heading: z.string().optional(),
|
|
281
|
-
maxItems: z.number().int().positive().optional(),
|
|
282
|
-
})
|
|
283
|
-
export type ProductCard1SpecProps = z.infer<typeof productCard1PropsSchema>
|
|
284
|
-
|
|
285
312
|
/**
|
|
286
313
|
* Canonical product presentation — the serializable projection a storefront host
|
|
287
314
|
* produces from a canonical `Product` for the merchandising sections. Unlike
|
|
@@ -310,6 +337,42 @@ export const productPresentationSchema = z.object({
|
|
|
310
337
|
})
|
|
311
338
|
export type ProductPresentation = z.infer<typeof productPresentationSchema>
|
|
312
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(),
|
|
367
|
+
heading: z.string().optional(),
|
|
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
|
+
|
|
313
376
|
/**
|
|
314
377
|
* Serializable contract for the combined grid-and-carousel product
|
|
315
378
|
* presentation (`ProductShowcase1`). The grid pages are fixed-size, navigated
|
|
@@ -330,10 +393,45 @@ export const productShowcase1PropsSchema = z.object({
|
|
|
330
393
|
})
|
|
331
394
|
export type ProductShowcase1SpecProps = z.infer<typeof productShowcase1PropsSchema>
|
|
332
395
|
|
|
333
|
-
|
|
334
|
-
|
|
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),
|
|
335
432
|
promotions: z.array(promotionSchema).default([]),
|
|
336
|
-
recentlyViewed: z.array(
|
|
433
|
+
recentlyViewed: z.array(shoppingCartSuggestionSchema).optional(),
|
|
434
|
+
presentation: z.enum(['compact', 'page']).optional(),
|
|
337
435
|
/** Backend-authoritative totals; rendered verbatim, never computed locally. */
|
|
338
436
|
totals: cartTotalsSchema.optional(),
|
|
339
437
|
totalsState: z.enum(['idle', 'pending', 'error']).optional(),
|
|
@@ -341,7 +439,6 @@ export const shoppingCart4PropsSchema = z.object({
|
|
|
341
439
|
totalsUnavailableLabel: z.string().optional(),
|
|
342
440
|
amountUnavailableLabel: z.string().optional(),
|
|
343
441
|
retryLabel: z.string().optional(),
|
|
344
|
-
taxRate: z.number().optional(),
|
|
345
442
|
titleLabel: z.string().optional(),
|
|
346
443
|
subtotalLabel: z.string().optional(),
|
|
347
444
|
discountLabel: z.string().optional(),
|
|
@@ -359,6 +456,9 @@ export const shoppingCart4PropsSchema = z.object({
|
|
|
359
456
|
estimatedDeliveryLabel: z.string().optional(),
|
|
360
457
|
secureCheckoutLabel: z.string().optional(),
|
|
361
458
|
recentlyViewedLabel: z.string().optional(),
|
|
459
|
+
promoPromptLabel: z.string().optional(),
|
|
460
|
+
promoPlaceholderLabel: z.string().optional(),
|
|
461
|
+
promoApplyLabel: z.string().optional(),
|
|
362
462
|
collapseLabel: z.string().optional(),
|
|
363
463
|
expandLabel: z.string().optional(),
|
|
364
464
|
checkoutLabel: z.string().optional(),
|
|
@@ -369,7 +469,7 @@ export const shoppingCart4PropsSchema = z.object({
|
|
|
369
469
|
decreaseLabel: z.string().optional(),
|
|
370
470
|
increaseLabel: z.string().optional(),
|
|
371
471
|
})
|
|
372
|
-
export type
|
|
472
|
+
export type ShoppingCartSpecProps = z.infer<typeof shoppingCartPropsSchema>
|
|
373
473
|
|
|
374
474
|
/**
|
|
375
475
|
* Optional brand seams shared by the existing blocks. These mirror the
|
|
@@ -433,6 +533,115 @@ export const ctaSchema = z.object({
|
|
|
433
533
|
})
|
|
434
534
|
export type CtaSpec = z.infer<typeof ctaSchema>
|
|
435
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
|
+
|
|
436
645
|
export const faqItemSchema = z.object({
|
|
437
646
|
value: z.string(),
|
|
438
647
|
question: z.string(),
|
|
@@ -457,6 +666,129 @@ export const ctaSection1PropsSchema = z.object({
|
|
|
457
666
|
})
|
|
458
667
|
export type CtaSection1SpecProps = z.infer<typeof ctaSection1PropsSchema>
|
|
459
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
|
+
|
|
460
792
|
// Pure monetary helpers, re-exported from the server-safe contracts entry so a
|
|
461
793
|
// host boundary can validate/format `Money` without importing the React entry.
|
|
462
794
|
export { isCompleteFiniteDecimal, isAvailableMoney, formatMoney } from './money'
|
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
|
-
ESM dist/contracts.mjs 11.75 KB
|
|
10
|
-
ESM dist/index.mjs 778.08 KB
|
|
11
|
-
ESM dist/contracts.mjs.map 29.49 KB
|
|
12
|
-
ESM dist/index.mjs.map 1.29 MB
|
|
13
|
-
ESM ⚡️ Build success in 228ms
|
|
14
|
-
CJS dist/contracts.js 16.83 KB
|
|
15
|
-
CJS dist/index.js 878.89 KB
|
|
16
|
-
CJS dist/contracts.js.map 29.69 KB
|
|
17
|
-
CJS dist/index.js.map 1.27 MB
|
|
18
|
-
CJS ⚡️ Build success in 229ms
|
|
19
|
-
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in 10197ms
|
|
21
|
-
DTS dist/index.d.ts 120.86 KB
|
|
22
|
-
DTS dist/contracts.d.ts 57.89 KB
|
|
23
|
-
DTS dist/index.d.mts 120.86 KB
|
|
24
|
-
DTS dist/contracts.d.mts 57.89 KB
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
RUN v3.2.4 /home/nelson/dev/active/thorprovider/repos/thorcommerce/packages/blocks
|
|
3
|
-
|
|
4
|
-
✓ src/blocks/e-commerce/navigation/mega-menu.test.tsx (9 tests) 1189ms
|
|
5
|
-
✓ MegaMenu > renders category columns from the categories prop 627ms
|
|
6
|
-
✓ src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx (12 tests) 1191ms
|
|
7
|
-
✓ MobileCategoryDrawer > keeps the drawer mounted off-screen with aria-hidden when closed 728ms
|
|
8
|
-
✓ src/blocks/e-commerce/product-categories/product-category-2.test.tsx (5 tests) 329ms
|
|
9
|
-
stderr | src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx > CheckoutForm1 payment step — launch flow collects no raw card data > renders card fields only in the explicit legacy mode
|
|
10
|
-
Received `true` for a non-boolean attribute `inline-end`.
|
|
11
|
-
|
|
12
|
-
If you want to write it to the DOM, pass a string instead: inline-end="true" or inline-end={value.toString()}.
|
|
13
|
-
|
|
14
|
-
✓ src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx (10 tests) 337ms
|
|
15
|
-
✓ src/blocks/e-commerce/checkout-forms/checkout-confirmation.test.tsx (7 tests) 567ms
|
|
16
|
-
✓ src/blocks/marketing/footers/footer-section-1.test.tsx (9 tests) 208ms
|
|
17
|
-
✓ src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx (17 tests) 704ms
|
|
18
|
-
✓ src/blocks/e-commerce/product-list/product-card-1.test.tsx (8 tests) 855ms
|
|
19
|
-
✓ src/blocks/marketing/navbars/navbar-4-cart-sheet.test.tsx (15 tests) 583ms
|
|
20
|
-
✓ Navbar4CartSheet — authoritative subtotal > never renders a fake zero for a supplied but absent/malformed subtotal 430ms
|
|
21
|
-
✓ src/blocks/e-commerce/navigation/skip-to-content.test.tsx (5 tests) 52ms
|
|
22
|
-
✓ src/blocks/marketing/navbars/navbar-4.test.tsx (19 tests) 851ms
|
|
23
|
-
✓ src/lib/money.test.ts (10 tests) 473ms
|
|
24
|
-
✓ formatMoney > uses the money currency rather than a hardcoded one 371ms
|
|
25
|
-
✓ src/lib/contracts-server-boundary.test.ts (3 tests) 92ms
|
|
26
|
-
✓ src/blocks/e-commerce/navigation/mobile-bottom-nav.test.tsx (7 tests) 446ms
|
|
27
|
-
✓ src/blocks/e-commerce/category-filters/category-filter-2.test.tsx (39 tests) 2736ms
|
|
28
|
-
✓ CategoryFilter2 — controlled canonical query > renders the query-selected search, category, sort and page values 428ms
|
|
29
|
-
✓ CategoryFilter2 — controlled canonical query > renders collection controls when collections are supplied 354ms
|
|
30
|
-
✓ CategoryFilter2 — no fabricated price or sort choices > omits the price control without a real backend range 301ms
|
|
31
|
-
✓ src/blocks/e-commerce/navigation/category-icon.test.tsx (7 tests) 584ms
|
|
32
|
-
✓ src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx (6 tests) 867ms
|
|
33
|
-
✓ StorefrontHero1 — optional editorial image > renders no editorial image when none is supplied 325ms
|
|
34
|
-
✓ src/lib/contracts.test.ts (3 tests) 33ms
|
|
35
|
-
✓ src/lib/cart.test.ts (2 tests) 23ms
|
|
36
|
-
✓ src/blocks/e-commerce/product-overview/product-overview-1.test.tsx (14 tests) 200ms
|
|
37
|
-
✓ src/blocks/e-commerce/navigation/breadcrumb.test.tsx (12 tests) 80ms
|
|
38
|
-
✓ src/blocks/e-commerce/navigation/back-to-top.test.tsx (5 tests) 23ms
|
|
39
|
-
✓ src/blocks/e-commerce/checkout-forms/card-formatting.test.ts (10 tests) 10ms
|
|
40
|
-
✓ src/lib/contracts-curated.test.ts (22 tests) 550ms
|
|
41
|
-
✓ src/blocks/e-commerce/product-list/product-showcase-1.test.tsx (7 tests) 498ms
|
|
42
|
-
|
|
43
|
-
Test Files 25 passed (25)
|
|
44
|
-
Tests 263 passed (263)
|
|
45
|
-
Start at 11:37:52
|
|
46
|
-
Duration 16.34s (transform 12.71s, setup 0ms, collect 81.98s, tests 13.48s, environment 33ms, prepare 18.67s)
|
|
47
|
-
|
|
File without changes
|