@thorprovider/blocks 0.1.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/.turbo/turbo-build.log +24 -0
- package/.turbo/turbo-test.log +10 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -0
- package/README.md +161 -0
- package/components.json +33 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/contracts.d.mts +563 -0
- package/dist/contracts.d.ts +563 -0
- package/dist/contracts.js +207 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contracts.mjs +168 -0
- package/dist/contracts.mjs.map +1 -0
- package/dist/index.d.mts +2408 -0
- package/dist/index.d.ts +2408 -0
- package/dist/index.js +16422 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +16352 -0
- package/dist/index.mjs.map +1 -0
- package/docs/standards/data-contracts.md +70 -0
- package/docs/standards/theming.md +85 -0
- package/package.json +62 -0
- package/src/blocks/application/app-shells/app-shell-1-sidebar.tsx +198 -0
- package/src/blocks/application/app-shells/app-shell-1.tsx +196 -0
- package/src/blocks/application/login-forms/login-page-1.tsx +246 -0
- package/src/blocks/application/login-forms/login-page-2.tsx +207 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1-brand-mark.tsx +38 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +320 -0
- package/src/blocks/application/signup-forms/signup-page-1.tsx +231 -0
- package/src/blocks/application/verification/verify-1.tsx +307 -0
- package/src/blocks/application/verification/verify-confetti.tsx +192 -0
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +260 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +179 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +341 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +57 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +124 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +170 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +52 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +57 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +68 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +298 -0
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +47 -0
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +9 -0
- package/src/blocks/e-commerce/checkout-forms/checkbox-check.tsx +74 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +189 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +283 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-field-feedback.tsx +29 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +96 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-country-field.tsx +51 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +139 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +184 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +36 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +107 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +235 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +130 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +175 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +190 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +312 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +91 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +110 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +137 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +68 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +79 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +172 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +392 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-types.ts +7 -0
- package/src/blocks/e-commerce/navigation/category-icon.test.tsx +54 -0
- package/src/blocks/e-commerce/navigation/category-icon.tsx +94 -0
- package/src/blocks/e-commerce/navigation/mega-menu.test.tsx +139 -0
- package/src/blocks/e-commerce/navigation/mega-menu.tsx +271 -0
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +152 -0
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +276 -0
- package/src/blocks/e-commerce/order-history/order-history-1.tsx +159 -0
- package/src/blocks/e-commerce/order-history/order-history-2.tsx +222 -0
- package/src/blocks/e-commerce/product-categories/avatar-group.tsx +86 -0
- package/src/blocks/e-commerce/product-categories/product-category-1.tsx +126 -0
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +100 -0
- package/src/blocks/e-commerce/product-categories/product-category-4.tsx +129 -0
- package/src/blocks/e-commerce/product-categories/product-category-5.tsx +218 -0
- package/src/blocks/e-commerce/product-list/product-card-1.tsx +131 -0
- package/src/blocks/e-commerce/product-list/product-card-2.tsx +71 -0
- package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +166 -0
- package/src/blocks/e-commerce/product-list/product-card-3.tsx +215 -0
- package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +98 -0
- package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +171 -0
- package/src/blocks/e-commerce/product-list/product-card-5.tsx +367 -0
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +201 -0
- package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +272 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +59 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-lightbox-tab.tsx +104 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-reviews-tab.tsx +82 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-tabs.tsx +69 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +274 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view-card.tsx +135 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view-dialog.tsx +393 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view.tsx +136 -0
- package/src/blocks/e-commerce/reviews/review-rating-1-review-form.tsx +133 -0
- package/src/blocks/e-commerce/reviews/review-rating-1.tsx +178 -0
- package/src/blocks/e-commerce/reviews/review-rating-2.tsx +246 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +142 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +291 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +110 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +51 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +146 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +295 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +142 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +342 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +146 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +391 -0
- package/src/blocks/e-commerce/store-containers/store-container-1.tsx +29 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +244 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +283 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +318 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +205 -0
- package/src/blocks/marketing/banners/avatar-group.tsx +69 -0
- package/src/blocks/marketing/banners/banner-1.tsx +188 -0
- package/src/blocks/marketing/cta/cta-section-1.tsx +112 -0
- package/src/blocks/marketing/faq/faq-block-1.tsx +45 -0
- package/src/blocks/marketing/features/feature-1.tsx +97 -0
- package/src/blocks/marketing/features/feature-2.tsx +82 -0
- package/src/blocks/marketing/footers/footer-section-1.tsx +141 -0
- package/src/blocks/marketing/footers/footer-section-3.tsx +192 -0
- package/src/blocks/marketing/footers/footer-section-4.tsx +355 -0
- package/src/blocks/marketing/hero-sections/avatar-group.tsx +86 -0
- package/src/blocks/marketing/hero-sections/hero-section-1.tsx +135 -0
- package/src/blocks/marketing/logo-clouds/logo-cloud-1-logo-icon.tsx +52 -0
- package/src/blocks/marketing/logo-clouds/logo-cloud-1.tsx +56 -0
- package/src/blocks/marketing/navbars/navbar-1.tsx +267 -0
- package/src/blocks/marketing/navbars/navbar-2.tsx +351 -0
- package/src/blocks/marketing/navbars/navbar-3.tsx +303 -0
- package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +118 -0
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +248 -0
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +177 -0
- package/src/blocks/marketing/navbars/navbar-4-mobile-nav.tsx +104 -0
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +219 -0
- package/src/blocks/marketing/navbars/navbar-4-search-bar.tsx +52 -0
- package/src/blocks/marketing/navbars/navbar-4.tsx +411 -0
- package/src/blocks/marketing/pricing/pricing-section-1.tsx +94 -0
- package/src/blocks/marketing/statistics/statistic-1.tsx +93 -0
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +141 -0
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +108 -0
- package/src/blocks/shared/reaction-toggle.tsx +145 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/index.ts +200 -0
- package/src/lib/contracts.test.ts +32 -0
- package/src/lib/contracts.ts +235 -0
- package/src/lib/types/menu.ts +43 -0
- package/src/lib/types.ts +4 -0
- package/src/lib/utils.ts +10 -0
- package/src/ui/accordion.tsx +65 -0
- package/src/ui/avatar.tsx +108 -0
- package/src/ui/badge.tsx +47 -0
- package/src/ui/breadcrumb.tsx +108 -0
- package/src/ui/button-group.tsx +83 -0
- package/src/ui/button.tsx +63 -0
- package/src/ui/card.tsx +91 -0
- package/src/ui/carousel.tsx +241 -0
- package/src/ui/dialog.tsx +158 -0
- package/src/ui/drawer.tsx +134 -0
- package/src/ui/dropdown-menu.tsx +256 -0
- package/src/ui/empty.tsx +103 -0
- package/src/ui/field.tsx +248 -0
- package/src/ui/input-group.tsx +170 -0
- package/src/ui/input-otp.tsx +76 -0
- package/src/ui/input.tsx +20 -0
- package/src/ui/label.tsx +23 -0
- package/src/ui/navigation-menu.tsx +167 -0
- package/src/ui/pagination.tsx +127 -0
- package/src/ui/progress.tsx +30 -0
- package/src/ui/radio-group.tsx +44 -0
- package/src/ui/select.tsx +189 -0
- package/src/ui/separator.tsx +27 -0
- package/src/ui/sheet.tsx +142 -0
- package/src/ui/sidebar.tsx +726 -0
- package/src/ui/skeleton.tsx +13 -0
- package/src/ui/table.tsx +115 -0
- package/src/ui/tabs.tsx +90 -0
- package/src/ui/textarea.tsx +17 -0
- package/src/ui/toggle-group.tsx +83 -0
- package/src/ui/toggle.tsx +46 -0
- package/src/ui/tooltip.tsx +56 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +16 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { Money, PaymentMethod, ProductPreview } from '@thorprovider/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Serialization contracts for spec-driven storefronts.
|
|
6
|
+
*
|
|
7
|
+
* These Zod schemas are the *serializable subset* of what a spec may write for
|
|
8
|
+
* a block. The component keeps its rich React interface; the catalog/registry
|
|
9
|
+
* is the adapter that maps these props to real component props and binds
|
|
10
|
+
* callbacks (see storefronts-on-blocks design D3). `z.infer` is the typed
|
|
11
|
+
* boundary between spec and catalog — a prop outside the schema is a compile
|
|
12
|
+
* error, not a runtime surprise.
|
|
13
|
+
*
|
|
14
|
+
* Data shapes are projections of the @thorprovider/types canon so that
|
|
15
|
+
* @thorprovider/adapters output validates without conflict:
|
|
16
|
+
* - Prices are plain numbers here while the canon `Money` is a decimal string
|
|
17
|
+
* ({ amount: "45.00", currencyCode }). The projection happens at the
|
|
18
|
+
* loader boundary.
|
|
19
|
+
* - Blocks use `title` while the canon `ProductPreview` uses `name`. Reconcile
|
|
20
|
+
* via the canonical aliases below or the catalog/loader adapter.
|
|
21
|
+
*/
|
|
22
|
+
export type CanonMoney = Money
|
|
23
|
+
export type CanonProduct = ProductPreview
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fallback data-URI for required image fields when the data source has no
|
|
27
|
+
* image. Image fields are non-optional `z.string()` in these contracts, so the
|
|
28
|
+
* boundary must never emit `""` (React warns about `src=""`); it must emit a
|
|
29
|
+
* real, renderable value. Hosts may override with their own branded fallback
|
|
30
|
+
* (e.g. electrostore's `CATEGORY_IMAGE_FALLBACK`), but blocks ship a neutral
|
|
31
|
+
* one so direct consumers are safe by default.
|
|
32
|
+
*/
|
|
33
|
+
export const PRODUCT_IMAGE_FALLBACK =
|
|
34
|
+
'data:image/svg+xml;charset=utf-8,' +
|
|
35
|
+
encodeURIComponent(
|
|
36
|
+
`<svg xmlns='http://www.w3.org/2000/svg' width='600' height='600'><rect width='100%' height='100%' fill='#e2e8f0'/><g fill='none' stroke='#94a3b8' stroke-width='12' stroke-linecap='round'><path d='M150 300h300M300 150v300'/><path d='M220 230h50M220 300h50M220 370h50'/><path d='M330 230h50M330 300h50M330 370h50'/></g></svg>`,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Data contracts
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export const cardProductSchema = z.object({
|
|
44
|
+
id: z.union([z.string(), z.number()]),
|
|
45
|
+
title: z.string(),
|
|
46
|
+
price: z.number(),
|
|
47
|
+
originalPrice: z.number().optional(),
|
|
48
|
+
image: z.string(),
|
|
49
|
+
badge: z.string().optional(),
|
|
50
|
+
})
|
|
51
|
+
export type CardProduct = z.infer<typeof cardProductSchema>
|
|
52
|
+
|
|
53
|
+
export const cartItemSchema = z.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
name: z.string(),
|
|
56
|
+
price: z.number(),
|
|
57
|
+
quantity: z.number().int().min(0),
|
|
58
|
+
image: z.string(),
|
|
59
|
+
variant: z.string().optional(),
|
|
60
|
+
})
|
|
61
|
+
export type CartItem = z.infer<typeof cartItemSchema>
|
|
62
|
+
|
|
63
|
+
export const recentlyViewedItemSchema = z.object({
|
|
64
|
+
id: z.string(),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
price: z.number(),
|
|
67
|
+
image: z.string(),
|
|
68
|
+
})
|
|
69
|
+
export type RecentlyViewedItem = z.infer<typeof recentlyViewedItemSchema>
|
|
70
|
+
|
|
71
|
+
export const promotionSchema = z.object({
|
|
72
|
+
threshold: z.number(),
|
|
73
|
+
reward: z.string(),
|
|
74
|
+
icon: z.string().default('gift'),
|
|
75
|
+
})
|
|
76
|
+
export type Promotion = z.infer<typeof promotionSchema>
|
|
77
|
+
|
|
78
|
+
const storeCategoryBase = {
|
|
79
|
+
label: z.string(),
|
|
80
|
+
href: z.string(),
|
|
81
|
+
emphasis: z.boolean().optional(),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const storeCategorySchema: z.ZodType<StoreCategory> = z.lazy(() =>
|
|
85
|
+
z.object({
|
|
86
|
+
...storeCategoryBase,
|
|
87
|
+
children: z.array(storeCategorySchema).optional(),
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
export type StoreCategory = {
|
|
91
|
+
label: string
|
|
92
|
+
href: string
|
|
93
|
+
children?: StoreCategory[]
|
|
94
|
+
emphasis?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const utilityLinkSchema = z.object({
|
|
98
|
+
label: z.string(),
|
|
99
|
+
href: z.string(),
|
|
100
|
+
})
|
|
101
|
+
export type UtilityLink = z.infer<typeof utilityLinkSchema>
|
|
102
|
+
|
|
103
|
+
export const navCartItemSchema = z.object({
|
|
104
|
+
id: z.string(),
|
|
105
|
+
name: z.string(),
|
|
106
|
+
variant: z.string(),
|
|
107
|
+
price: z.number(),
|
|
108
|
+
qty: z.number().int().min(0),
|
|
109
|
+
image: z.string(),
|
|
110
|
+
})
|
|
111
|
+
export type NavCartItem = z.infer<typeof navCartItemSchema>
|
|
112
|
+
|
|
113
|
+
export const storeUserSchema = z.object({
|
|
114
|
+
name: z.string(),
|
|
115
|
+
email: z.string(),
|
|
116
|
+
avatar: z.string(),
|
|
117
|
+
initials: z.string(),
|
|
118
|
+
})
|
|
119
|
+
export type StoreUser = z.infer<typeof storeUserSchema>
|
|
120
|
+
|
|
121
|
+
export const accountMenuItemSchema = z.object({
|
|
122
|
+
label: z.string(),
|
|
123
|
+
href: z.string(),
|
|
124
|
+
description: z.string().optional(),
|
|
125
|
+
icon: z.enum(['orders', 'wishlist', 'addresses', 'settings']).optional(),
|
|
126
|
+
})
|
|
127
|
+
export type AccountMenuItem = z.infer<typeof accountMenuItemSchema>
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Serialization contract for a payment method offered at checkout.
|
|
131
|
+
*
|
|
132
|
+
* A projection of `@thorprovider/types` `PaymentMethod` — the storefront-facing
|
|
133
|
+
* shape produced by the adapters transform. Checkout blocks render the method
|
|
134
|
+
* list from this data instead of hardcoding providers.
|
|
135
|
+
*/
|
|
136
|
+
export const paymentMethodSchema: z.ZodType<PaymentMethod> = z.object({
|
|
137
|
+
id: z.string(),
|
|
138
|
+
name: z.string(),
|
|
139
|
+
type: z.enum([
|
|
140
|
+
'card',
|
|
141
|
+
'wallet',
|
|
142
|
+
'bank_transfer',
|
|
143
|
+
'buy_now_pay_later',
|
|
144
|
+
'cash_on_delivery',
|
|
145
|
+
'crypto',
|
|
146
|
+
'other',
|
|
147
|
+
]),
|
|
148
|
+
isEnabled: z.boolean(),
|
|
149
|
+
requiresSetup: z.boolean(),
|
|
150
|
+
description: z.string().optional(),
|
|
151
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
152
|
+
features: z
|
|
153
|
+
.object({
|
|
154
|
+
saveForLater: z.boolean().optional(),
|
|
155
|
+
requires3DS: z.boolean().optional(),
|
|
156
|
+
supportsRefunds: z.boolean().optional(),
|
|
157
|
+
minAmount: z.number().optional(),
|
|
158
|
+
maxAmount: z.number().optional(),
|
|
159
|
+
supportedCurrencies: z.array(z.string()).optional(),
|
|
160
|
+
requiresVerification: z.boolean().optional(),
|
|
161
|
+
})
|
|
162
|
+
.optional(),
|
|
163
|
+
logo: z.string().optional(),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Block prop schemas
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
export const buttonPropsSchema = z.object({
|
|
171
|
+
variant: z
|
|
172
|
+
.enum(['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'])
|
|
173
|
+
.optional(),
|
|
174
|
+
size: z
|
|
175
|
+
.enum(['default', 'xs', 'sm', 'lg', 'icon', 'icon-xs', 'icon-sm', 'icon-lg'])
|
|
176
|
+
.optional(),
|
|
177
|
+
disabled: z.boolean().optional(),
|
|
178
|
+
className: z.string().optional(),
|
|
179
|
+
})
|
|
180
|
+
export type ButtonSpecProps = z.infer<typeof buttonPropsSchema>
|
|
181
|
+
|
|
182
|
+
export const storeContainer1PropsSchema = z.object({
|
|
183
|
+
size: z.enum(['default', 'narrow', 'wide', 'full']).optional(),
|
|
184
|
+
className: z.string().optional(),
|
|
185
|
+
})
|
|
186
|
+
export type StoreContainer1SpecProps = z.infer<typeof storeContainer1PropsSchema>
|
|
187
|
+
|
|
188
|
+
export const productCard1PropsSchema = z.object({
|
|
189
|
+
products: z.array(cardProductSchema),
|
|
190
|
+
heading: z.string().optional(),
|
|
191
|
+
maxItems: z.number().int().positive().optional(),
|
|
192
|
+
})
|
|
193
|
+
export type ProductCard1SpecProps = z.infer<typeof productCard1PropsSchema>
|
|
194
|
+
|
|
195
|
+
export const shoppingCart4PropsSchema = z.object({
|
|
196
|
+
items: z.array(cartItemSchema),
|
|
197
|
+
promotions: z.array(promotionSchema).default([]),
|
|
198
|
+
recentlyViewed: z.array(recentlyViewedItemSchema).optional(),
|
|
199
|
+
taxRate: z.number().optional(),
|
|
200
|
+
titleLabel: z.string().optional(),
|
|
201
|
+
subtotalLabel: z.string().optional(),
|
|
202
|
+
taxLabel: z.string().optional(),
|
|
203
|
+
shippingLabel: z.string().optional(),
|
|
204
|
+
shippingFreeLabel: z.string().optional(),
|
|
205
|
+
totalLabel: z.string().optional(),
|
|
206
|
+
awayFromLabel: z.string().optional(),
|
|
207
|
+
activatedLabel: z.string().optional(),
|
|
208
|
+
itemsLabel: z.string().optional(),
|
|
209
|
+
expressCheckoutLabel: z.string().optional(),
|
|
210
|
+
expressLabel: z.string().optional(),
|
|
211
|
+
standardLabel: z.string().optional(),
|
|
212
|
+
estimatedDeliveryLabel: z.string().optional(),
|
|
213
|
+
secureCheckoutLabel: z.string().optional(),
|
|
214
|
+
recentlyViewedLabel: z.string().optional(),
|
|
215
|
+
collapseLabel: z.string().optional(),
|
|
216
|
+
expandLabel: z.string().optional(),
|
|
217
|
+
checkoutLabel: z.string().optional(),
|
|
218
|
+
quickCheckoutLabel: z.string().optional(),
|
|
219
|
+
checkoutNowLabel: z.string().optional(),
|
|
220
|
+
})
|
|
221
|
+
export type ShoppingCart4SpecProps = z.infer<typeof shoppingCart4PropsSchema>
|
|
222
|
+
|
|
223
|
+
export const navbar4PropsSchema = z.object({
|
|
224
|
+
storeCategories: z.array(storeCategorySchema),
|
|
225
|
+
shippingNote: z.string(),
|
|
226
|
+
utilityLinks: z.array(utilityLinkSchema),
|
|
227
|
+
logoText: z.string(),
|
|
228
|
+
logoHref: z.string().optional(),
|
|
229
|
+
accountHref: z.string().optional(),
|
|
230
|
+
searchQuery: z.string().optional(),
|
|
231
|
+
items: z.array(navCartItemSchema).optional(),
|
|
232
|
+
user: storeUserSchema.optional(),
|
|
233
|
+
accountMenuItems: z.array(accountMenuItemSchema).optional(),
|
|
234
|
+
})
|
|
235
|
+
export type Navbar4SpecProps = z.infer<typeof navbar4PropsSchema>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical navigation types: recursive menu item and collection item.
|
|
5
|
+
*
|
|
6
|
+
* Ported from the legacy `@thorprovider/components` navigation library
|
|
7
|
+
* (source: branch `feat/storefronts`) into the blocks catalog so any
|
|
8
|
+
* consumer can feed a multi-level category tree to the navigation blocks.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const menuItemSchema: z.ZodType<{
|
|
12
|
+
id: string
|
|
13
|
+
label: string
|
|
14
|
+
icon?: string
|
|
15
|
+
image?: string
|
|
16
|
+
href?: string
|
|
17
|
+
badge?: string
|
|
18
|
+
featured?: boolean
|
|
19
|
+
children?: MenuItem[]
|
|
20
|
+
}> = z.lazy(() =>
|
|
21
|
+
z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
label: z.string(),
|
|
24
|
+
icon: z.string().optional(),
|
|
25
|
+
image: z.string().optional(),
|
|
26
|
+
href: z.string().optional(),
|
|
27
|
+
badge: z.string().optional(),
|
|
28
|
+
featured: z.boolean().optional(),
|
|
29
|
+
children: z.array(menuItemSchema).optional(),
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
export type MenuItem = z.infer<typeof menuItemSchema>
|
|
34
|
+
|
|
35
|
+
export const collectionItemSchema = z.object({
|
|
36
|
+
id: z.string(),
|
|
37
|
+
label: z.string(),
|
|
38
|
+
description: z.string().optional(),
|
|
39
|
+
image: z.string().optional(),
|
|
40
|
+
href: z.string().optional(),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export type CollectionItem = z.infer<typeof collectionItemSchema>
|
package/src/lib/types.ts
ADDED
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from 'clsx'
|
|
2
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merge Tailwind CSS class names safely.
|
|
6
|
+
* Deduplicates conflicting utilities (e.g. `p-4` vs `p-2` → keeps last).
|
|
7
|
+
*/
|
|
8
|
+
export function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs))
|
|
10
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
6
|
+
import { Accordion as AccordionPrimitive } from "radix-ui"
|
|
7
|
+
|
|
8
|
+
function Accordion({
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
11
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function AccordionItem({
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
18
|
+
return (
|
|
19
|
+
<AccordionPrimitive.Item
|
|
20
|
+
data-slot="accordion-item"
|
|
21
|
+
className={cn("border-b last:border-b-0", className)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AccordionTrigger({
|
|
28
|
+
className,
|
|
29
|
+
children,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
32
|
+
return (
|
|
33
|
+
<AccordionPrimitive.Header className="flex">
|
|
34
|
+
<AccordionPrimitive.Trigger
|
|
35
|
+
data-slot="accordion-trigger"
|
|
36
|
+
className={cn(
|
|
37
|
+
"flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" />
|
|
44
|
+
</AccordionPrimitive.Trigger>
|
|
45
|
+
</AccordionPrimitive.Header>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function AccordionContent({
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
54
|
+
return (
|
|
55
|
+
<AccordionPrimitive.Content
|
|
56
|
+
data-slot="accordion-content"
|
|
57
|
+
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
61
|
+
</AccordionPrimitive.Content>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
function Avatar({
|
|
8
|
+
className,
|
|
9
|
+
size = "default",
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
12
|
+
size?: "default" | "sm" | "lg"
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<AvatarPrimitive.Root
|
|
16
|
+
data-slot="avatar"
|
|
17
|
+
data-size={size}
|
|
18
|
+
className={cn(
|
|
19
|
+
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AvatarImage({
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
31
|
+
return (
|
|
32
|
+
<AvatarPrimitive.Image
|
|
33
|
+
data-slot="avatar-image"
|
|
34
|
+
className={cn("aspect-square size-full", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function AvatarFallback({
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
44
|
+
return (
|
|
45
|
+
<AvatarPrimitive.Fallback
|
|
46
|
+
data-slot="avatar-fallback"
|
|
47
|
+
className={cn(
|
|
48
|
+
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
57
|
+
return (
|
|
58
|
+
<span
|
|
59
|
+
data-slot="avatar-badge"
|
|
60
|
+
className={cn(
|
|
61
|
+
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none",
|
|
62
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
63
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
64
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="avatar-group"
|
|
76
|
+
className={cn(
|
|
77
|
+
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function AvatarGroupCount({
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
}: React.ComponentProps<"div">) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
data-slot="avatar-group-count"
|
|
92
|
+
className={cn(
|
|
93
|
+
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
Avatar,
|
|
103
|
+
AvatarImage,
|
|
104
|
+
AvatarFallback,
|
|
105
|
+
AvatarBadge,
|
|
106
|
+
AvatarGroup,
|
|
107
|
+
AvatarGroupCount,
|
|
108
|
+
}
|
package/src/ui/badge.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
import { Slot } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
12
|
+
secondary:
|
|
13
|
+
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-destructive-foreground focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
|
|
16
|
+
outline:
|
|
17
|
+
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
18
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
19
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
function Badge({
|
|
29
|
+
className,
|
|
30
|
+
variant = "default",
|
|
31
|
+
asChild = false,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<"span"> &
|
|
34
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
35
|
+
const Comp = asChild ? Slot.Root : "span"
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Comp
|
|
39
|
+
data-slot="badge"
|
|
40
|
+
data-variant={variant}
|
|
41
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "cn"
|
|
3
|
+
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
|
4
|
+
import { Slot } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
|
7
|
+
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
|
11
|
+
return (
|
|
12
|
+
<ol
|
|
13
|
+
data-slot="breadcrumb-list"
|
|
14
|
+
className={cn(
|
|
15
|
+
"flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
24
|
+
return (
|
|
25
|
+
<li
|
|
26
|
+
data-slot="breadcrumb-item"
|
|
27
|
+
className={cn("inline-flex items-center gap-1.5", className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function BreadcrumbLink({
|
|
34
|
+
asChild,
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<"a"> & {
|
|
38
|
+
asChild?: boolean
|
|
39
|
+
}) {
|
|
40
|
+
const Comp = asChild ? Slot.Root : "a"
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Comp
|
|
44
|
+
data-slot="breadcrumb-link"
|
|
45
|
+
className={cn("transition-colors hover:text-foreground", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
|
52
|
+
return (
|
|
53
|
+
<span
|
|
54
|
+
data-slot="breadcrumb-page"
|
|
55
|
+
role="link"
|
|
56
|
+
aria-disabled="true"
|
|
57
|
+
aria-current="page"
|
|
58
|
+
className={cn("font-normal text-foreground", className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function BreadcrumbSeparator({
|
|
65
|
+
children,
|
|
66
|
+
className,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<"li">) {
|
|
69
|
+
return (
|
|
70
|
+
<li
|
|
71
|
+
data-slot="breadcrumb-separator"
|
|
72
|
+
role="presentation"
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
className={cn("[&>svg]:size-3.5", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
77
|
+
{children ?? <ChevronRight />}
|
|
78
|
+
</li>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function BreadcrumbEllipsis({
|
|
83
|
+
className,
|
|
84
|
+
...props
|
|
85
|
+
}: React.ComponentProps<"span">) {
|
|
86
|
+
return (
|
|
87
|
+
<span
|
|
88
|
+
data-slot="breadcrumb-ellipsis"
|
|
89
|
+
role="presentation"
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
className={cn("flex size-9 items-center justify-center", className)}
|
|
92
|
+
{...props}
|
|
93
|
+
>
|
|
94
|
+
<MoreHorizontal className="size-4" />
|
|
95
|
+
<span className="sr-only">More</span>
|
|
96
|
+
</span>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
Breadcrumb,
|
|
102
|
+
BreadcrumbList,
|
|
103
|
+
BreadcrumbItem,
|
|
104
|
+
BreadcrumbLink,
|
|
105
|
+
BreadcrumbPage,
|
|
106
|
+
BreadcrumbSeparator,
|
|
107
|
+
BreadcrumbEllipsis,
|
|
108
|
+
}
|