@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
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import { ArrowRight, MapPin } from 'lucide-react'
|
|
2
|
-
import type { ComponentProps, ReactNode } from 'react'
|
|
3
|
-
|
|
4
|
-
import { Button } from '../../../ui/button'
|
|
5
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
6
|
-
import { cn } from '../../../lib/utils'
|
|
7
|
-
import type { CTA } from '../../../lib/types'
|
|
8
|
-
|
|
9
|
-
export interface Department {
|
|
10
|
-
/** Unique identifier */
|
|
11
|
-
id: string
|
|
12
|
-
/** Department name */
|
|
13
|
-
name: string
|
|
14
|
-
/** Image URL for the department chip */
|
|
15
|
-
image: string
|
|
16
|
-
/** Accessible image description (falls back to `name`) */
|
|
17
|
-
imageAlt?: string
|
|
18
|
-
/** Product count label shown under the name */
|
|
19
|
-
count?: string
|
|
20
|
-
/** Link destination when the department is a link */
|
|
21
|
-
href?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface FeaturedDeal {
|
|
25
|
-
/** Unique identifier */
|
|
26
|
-
id: string
|
|
27
|
-
/** Deal title */
|
|
28
|
-
name: string
|
|
29
|
-
/** Short supporting description */
|
|
30
|
-
description: string
|
|
31
|
-
/** Image URL */
|
|
32
|
-
image: string
|
|
33
|
-
/** Accessible image description (falls back to `name`) */
|
|
34
|
-
imageAlt?: string
|
|
35
|
-
/** Current price */
|
|
36
|
-
price: number
|
|
37
|
-
/** Original price, rendered struck-through when present */
|
|
38
|
-
originalPrice?: number
|
|
39
|
-
/** Optional call to action shown on the card */
|
|
40
|
-
cta?: CTA
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface StoreLocator {
|
|
44
|
-
/** Locator section title */
|
|
45
|
-
title: string
|
|
46
|
-
/** Locator section description */
|
|
47
|
-
description: string
|
|
48
|
-
/** Locator button label */
|
|
49
|
-
ctaLabel: string
|
|
50
|
-
/** Optional link destination for the locator button */
|
|
51
|
-
ctaHref?: string
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface StorefrontHero5Props {
|
|
55
|
-
/** Small pill shown above the title */
|
|
56
|
-
badge?: string
|
|
57
|
-
/** Main headline */
|
|
58
|
-
title: string
|
|
59
|
-
/** Accent text appended to the headline */
|
|
60
|
-
titleAccent?: string
|
|
61
|
-
/** Supporting paragraph under the headline */
|
|
62
|
-
description: string
|
|
63
|
-
/** Primary call to action */
|
|
64
|
-
primaryCta?: CTA
|
|
65
|
-
/** Secondary call to action */
|
|
66
|
-
secondaryCta?: CTA
|
|
67
|
-
/** Shipping banner text shown under the CTAs */
|
|
68
|
-
shippingInfo?: string
|
|
69
|
-
/** Department categories rendered as a grid */
|
|
70
|
-
departments: Department[]
|
|
71
|
-
/** Featured deals rendered as cards */
|
|
72
|
-
featuredDeals: FeaturedDeal[]
|
|
73
|
-
/** Store locator strip rendered at the bottom */
|
|
74
|
-
storeLocator?: StoreLocator
|
|
75
|
-
/** Invoked when a department is clicked (departments without `href`) */
|
|
76
|
-
onDepartmentClick?: (department: Department) => void
|
|
77
|
-
/** Invoked when a deal CTA is clicked (deals whose `cta` has no `href`) */
|
|
78
|
-
onShopNow?: (deal: FeaturedDeal) => void
|
|
79
|
-
/** Invoked when the locator button is clicked (when `ctaHref` is absent) */
|
|
80
|
-
onLocateStores?: () => void
|
|
81
|
-
/** Label for the deal call-to-action button */
|
|
82
|
-
shopNowLabel?: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function CtaButton({
|
|
86
|
-
cta,
|
|
87
|
-
variant,
|
|
88
|
-
children,
|
|
89
|
-
}: {
|
|
90
|
-
cta: CTA
|
|
91
|
-
variant?: ComponentProps<typeof Button>['variant']
|
|
92
|
-
children: ReactNode
|
|
93
|
-
}) {
|
|
94
|
-
if (cta.href) {
|
|
95
|
-
return (
|
|
96
|
-
<Button
|
|
97
|
-
variant={variant}
|
|
98
|
-
size='lg'
|
|
99
|
-
className='group h-10 px-4 cursor-pointer text-base'
|
|
100
|
-
asChild
|
|
101
|
-
>
|
|
102
|
-
<a href={cta.href}>{children}</a>
|
|
103
|
-
</Button>
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<Button
|
|
109
|
-
variant={variant}
|
|
110
|
-
size='lg'
|
|
111
|
-
className='group h-10 px-4 cursor-pointer text-base'
|
|
112
|
-
>
|
|
113
|
-
{children}
|
|
114
|
-
</Button>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function StorefrontHero5({
|
|
119
|
-
badge,
|
|
120
|
-
title,
|
|
121
|
-
titleAccent,
|
|
122
|
-
description,
|
|
123
|
-
primaryCta,
|
|
124
|
-
secondaryCta,
|
|
125
|
-
shippingInfo,
|
|
126
|
-
departments,
|
|
127
|
-
featuredDeals,
|
|
128
|
-
storeLocator,
|
|
129
|
-
onDepartmentClick,
|
|
130
|
-
onShopNow,
|
|
131
|
-
onLocateStores,
|
|
132
|
-
shopNowLabel = 'Comprar ahora',
|
|
133
|
-
}: StorefrontHero5Props) {
|
|
134
|
-
const departmentChipClasses = cn(
|
|
135
|
-
'flex flex-col items-center gap-4 rounded-lg p-2 text-center outline-none',
|
|
136
|
-
'transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-ring',
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<div className='bg-background relative overflow-hidden'>
|
|
141
|
-
<div className='relative z-10'>
|
|
142
|
-
<div className='mx-auto w-full max-w-7xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20'>
|
|
143
|
-
<div className='mx-auto max-w-4xl text-center'>
|
|
144
|
-
{badge && (
|
|
145
|
-
<div className='border-primary/20 bg-primary/5 text-primary inline-flex items-center justify-center rounded-md border px-4 py-1.5 text-sm'>
|
|
146
|
-
{badge}
|
|
147
|
-
</div>
|
|
148
|
-
)}
|
|
149
|
-
|
|
150
|
-
<h1 className='mt-6 text-4xl font-bold tracking-tight text-balance sm:text-5xl md:text-6xl'>
|
|
151
|
-
{title}{' '}
|
|
152
|
-
{titleAccent && <span className='text-primary'>{titleAccent}</span>}
|
|
153
|
-
</h1>
|
|
154
|
-
|
|
155
|
-
<p className='text-muted-foreground mx-auto mt-6 max-w-2xl text-lg'>
|
|
156
|
-
{description}
|
|
157
|
-
</p>
|
|
158
|
-
|
|
159
|
-
{(primaryCta || secondaryCta) && (
|
|
160
|
-
<div className='mt-8 flex flex-wrap justify-center gap-4'>
|
|
161
|
-
{primaryCta && (
|
|
162
|
-
<CtaButton cta={primaryCta}>
|
|
163
|
-
{primaryCta.label}
|
|
164
|
-
<ArrowRight className='transition-transform group-hover:translate-x-1' />
|
|
165
|
-
</CtaButton>
|
|
166
|
-
)}
|
|
167
|
-
{secondaryCta && <CtaButton cta={secondaryCta} variant='outline'>{secondaryCta.label}</CtaButton>}
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
170
|
-
|
|
171
|
-
{shippingInfo && (
|
|
172
|
-
<div className='mt-6'>
|
|
173
|
-
<div className='bg-background/80 text-muted-foreground inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-sm backdrop-blur-sm'>
|
|
174
|
-
<MapPin className='text-primary size-4' aria-hidden='true' />
|
|
175
|
-
<span>{shippingInfo}</span>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
|
|
180
|
-
<div className='mt-12 grid grid-cols-3 gap-8'>
|
|
181
|
-
{departments.map(department => {
|
|
182
|
-
const chip = (
|
|
183
|
-
<>
|
|
184
|
-
<img
|
|
185
|
-
src={department.image || undefined}
|
|
186
|
-
alt={department.imageAlt ?? department.name}
|
|
187
|
-
className='bg-card border-border size-16 rounded-full border object-cover'
|
|
188
|
-
loading='lazy'
|
|
189
|
-
/>
|
|
190
|
-
<div>
|
|
191
|
-
<div className='text-lg font-medium'>{department.name}</div>
|
|
192
|
-
{department.count && (
|
|
193
|
-
<div className='text-muted-foreground mt-1 text-sm'>{department.count}</div>
|
|
194
|
-
)}
|
|
195
|
-
</div>
|
|
196
|
-
</>
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
if (department.href) {
|
|
200
|
-
return (
|
|
201
|
-
<a
|
|
202
|
-
key={department.id}
|
|
203
|
-
href={department.href}
|
|
204
|
-
className={cn(departmentChipClasses, 'cursor-pointer')}
|
|
205
|
-
>
|
|
206
|
-
{chip}
|
|
207
|
-
</a>
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return (
|
|
212
|
-
<button
|
|
213
|
-
key={department.id}
|
|
214
|
-
type='button'
|
|
215
|
-
onClick={onDepartmentClick ? () => onDepartmentClick(department) : undefined}
|
|
216
|
-
className={cn(departmentChipClasses, 'cursor-pointer')}
|
|
217
|
-
>
|
|
218
|
-
{chip}
|
|
219
|
-
</button>
|
|
220
|
-
)
|
|
221
|
-
})}
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
{featuredDeals.length > 0 && (
|
|
225
|
-
<div className='mt-16 grid gap-6 sm:grid-cols-2'>
|
|
226
|
-
{featuredDeals.map(deal => (
|
|
227
|
-
<Card key={deal.id} className='mx-auto h-full w-full overflow-hidden border-0 py-0 shadow-lg'>
|
|
228
|
-
<CardContent className='p-0'>
|
|
229
|
-
<div className='relative aspect-[3/2] overflow-hidden'>
|
|
230
|
-
<img
|
|
231
|
-
src={deal.image || undefined}
|
|
232
|
-
alt={deal.imageAlt ?? deal.name}
|
|
233
|
-
className='size-full object-cover object-center transition-transform duration-700 hover:scale-105'
|
|
234
|
-
loading='lazy'
|
|
235
|
-
/>
|
|
236
|
-
<div className='from-background/90 via-background/30 absolute inset-0 bg-gradient-to-t to-transparent' />
|
|
237
|
-
<div className='absolute inset-0 flex items-end p-8'>
|
|
238
|
-
<div className='w-full'>
|
|
239
|
-
<div className='bg-background/80 flex flex-col items-center justify-between gap-6 rounded-lg p-6 backdrop-blur-sm sm:flex-row sm:gap-8'>
|
|
240
|
-
<div className='text-center sm:text-left'>
|
|
241
|
-
<h3 className='text-2xl font-bold tracking-tight sm:text-3xl'>{deal.name}</h3>
|
|
242
|
-
<p className='text-muted-foreground mt-1'>{deal.description}</p>
|
|
243
|
-
<div className='mt-2 flex items-baseline justify-center gap-2 sm:justify-start'>
|
|
244
|
-
<span className='text-lg font-bold'>${deal.price.toFixed(2)}</span>
|
|
245
|
-
{deal.originalPrice && (
|
|
246
|
-
<span className='text-muted-foreground text-sm line-through'>
|
|
247
|
-
${deal.originalPrice.toFixed(2)}
|
|
248
|
-
</span>
|
|
249
|
-
)}
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
{(deal.cta || onShopNow) && (
|
|
253
|
-
<div className='flex-shrink-0'>
|
|
254
|
-
{deal.cta?.href ? (
|
|
255
|
-
<Button size='lg' className='group h-10 px-4 cursor-pointer' asChild>
|
|
256
|
-
<a href={deal.cta.href}>
|
|
257
|
-
{deal.cta.label}
|
|
258
|
-
<ArrowRight className='transition-transform group-hover:translate-x-1' />
|
|
259
|
-
</a>
|
|
260
|
-
</Button>
|
|
261
|
-
) : (
|
|
262
|
-
<Button
|
|
263
|
-
size='lg'
|
|
264
|
-
className='group h-10 px-4 cursor-pointer'
|
|
265
|
-
onClick={onShopNow ? () => onShopNow(deal) : undefined}
|
|
266
|
-
>
|
|
267
|
-
{deal.cta?.label ?? shopNowLabel}
|
|
268
|
-
<ArrowRight className='transition-transform group-hover:translate-x-1' />
|
|
269
|
-
</Button>
|
|
270
|
-
)}
|
|
271
|
-
</div>
|
|
272
|
-
)}
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
</CardContent>
|
|
278
|
-
</Card>
|
|
279
|
-
))}
|
|
280
|
-
</div>
|
|
281
|
-
)}
|
|
282
|
-
|
|
283
|
-
{storeLocator && (
|
|
284
|
-
<div className='border-border/50 bg-background/50 mt-8 rounded-lg border p-6 backdrop-blur-sm'>
|
|
285
|
-
<div className='flex flex-col items-center gap-6 sm:flex-row sm:justify-between'>
|
|
286
|
-
<div className='flex items-center gap-4'>
|
|
287
|
-
<div className='bg-primary/10 text-primary flex size-12 items-center justify-center rounded-full'>
|
|
288
|
-
<MapPin className='size-6' aria-hidden='true' />
|
|
289
|
-
</div>
|
|
290
|
-
<div className='text-start'>
|
|
291
|
-
<h4 className='text-lg font-medium'>{storeLocator.title}</h4>
|
|
292
|
-
<p className='text-muted-foreground'>{storeLocator.description}</p>
|
|
293
|
-
</div>
|
|
294
|
-
</div>
|
|
295
|
-
{storeLocator.ctaHref ? (
|
|
296
|
-
<Button variant='outline' className='h-9 cursor-pointer px-4 py-2 whitespace-nowrap' asChild>
|
|
297
|
-
<a href={storeLocator.ctaHref}>{storeLocator.ctaLabel}</a>
|
|
298
|
-
</Button>
|
|
299
|
-
) : (
|
|
300
|
-
<Button
|
|
301
|
-
variant='outline'
|
|
302
|
-
className='h-9 cursor-pointer px-4 py-2 whitespace-nowrap'
|
|
303
|
-
onClick={onLocateStores ? onLocateStores : undefined}
|
|
304
|
-
>
|
|
305
|
-
{storeLocator.ctaLabel}
|
|
306
|
-
</Button>
|
|
307
|
-
)}
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
)}
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export default StorefrontHero5
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { ArrowRight, Sparkles } from 'lucide-react'
|
|
5
|
-
|
|
6
|
-
import { Badge } from '../../../ui/badge'
|
|
7
|
-
import { Button } from '../../../ui/button'
|
|
8
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
9
|
-
|
|
10
|
-
export interface HeroDeal {
|
|
11
|
-
/** Unique identifier for the deal. */
|
|
12
|
-
id: string
|
|
13
|
-
/** Deal name shown in the badge, e.g. "Summer Sale". */
|
|
14
|
-
title: string
|
|
15
|
-
/** Discount label, e.g. "50% OFF". */
|
|
16
|
-
discount: string
|
|
17
|
-
/** Countdown text, e.g. "2d : 15h : 30m". */
|
|
18
|
-
endTime: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface HeroCategory {
|
|
22
|
-
/** Unique identifier for the category. */
|
|
23
|
-
id: string
|
|
24
|
-
/** Category name. */
|
|
25
|
-
name: string
|
|
26
|
-
/** Product count label, e.g. "2.5k+". */
|
|
27
|
-
count: string
|
|
28
|
-
/** Image URL for the category card. */
|
|
29
|
-
image: string
|
|
30
|
-
/** Accessible image description (falls back to `name`). */
|
|
31
|
-
imageAlt?: string
|
|
32
|
-
/** Optional link destination; renders the card arrow as a link when present. */
|
|
33
|
-
href?: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface StorefrontHero6Props {
|
|
37
|
-
/** Small pill shown above the headline. */
|
|
38
|
-
badge?: string
|
|
39
|
-
/** Main headline. */
|
|
40
|
-
title: string
|
|
41
|
-
/** Accent line rendered on a new line after the headline. */
|
|
42
|
-
subtitle?: string
|
|
43
|
-
/** Supporting paragraph under the headline. */
|
|
44
|
-
description: string
|
|
45
|
-
/** Deals rendered in the sticky auto-rotating top bar. */
|
|
46
|
-
deals: HeroDeal[]
|
|
47
|
-
/** Product categories rendered as a card grid. */
|
|
48
|
-
categories: HeroCategory[]
|
|
49
|
-
/** Milliseconds between auto-rotating deals (default 3000). */
|
|
50
|
-
rotationInterval?: number
|
|
51
|
-
/** Label shown before the deal countdown, e.g. "Ends in". */
|
|
52
|
-
endsInLabel?: string
|
|
53
|
-
/** Label of the shop-now button in the deals bar. */
|
|
54
|
-
shopNowLabel?: string
|
|
55
|
-
/** Unit appended to each category product count. */
|
|
56
|
-
productsLabel?: string
|
|
57
|
-
/** Prefix for the category arrow accessible label. */
|
|
58
|
-
viewLabel?: string
|
|
59
|
-
/** Called when the shop-now button in the deals bar is clicked. */
|
|
60
|
-
onShopNow?: (deal: HeroDeal) => void
|
|
61
|
-
/** Called when a category arrow is clicked (categories without `href`). */
|
|
62
|
-
onCategoryClick?: (category: HeroCategory) => void
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function StorefrontHero6({
|
|
66
|
-
badge,
|
|
67
|
-
title,
|
|
68
|
-
subtitle,
|
|
69
|
-
description,
|
|
70
|
-
deals,
|
|
71
|
-
categories,
|
|
72
|
-
rotationInterval = 3000,
|
|
73
|
-
endsInLabel = 'Termina en',
|
|
74
|
-
shopNowLabel = 'Comprar ahora',
|
|
75
|
-
productsLabel = 'productos',
|
|
76
|
-
viewLabel = 'Ver',
|
|
77
|
-
onShopNow,
|
|
78
|
-
onCategoryClick,
|
|
79
|
-
}: StorefrontHero6Props) {
|
|
80
|
-
const [currentDeal, setCurrentDeal] = useState(0)
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (deals.length <= 1) return
|
|
84
|
-
|
|
85
|
-
const interval = setInterval(() => {
|
|
86
|
-
setCurrentDeal(prev => (prev + 1) % deals.length)
|
|
87
|
-
}, rotationInterval)
|
|
88
|
-
|
|
89
|
-
return () => clearInterval(interval)
|
|
90
|
-
}, [deals.length, rotationInterval])
|
|
91
|
-
|
|
92
|
-
const deal = deals[currentDeal]
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<div className='relative'>
|
|
96
|
-
{deal && (
|
|
97
|
-
<header className='bg-primary text-primary-foreground sticky top-0 z-50 shadow-md'>
|
|
98
|
-
<div className='mx-auto w-full max-w-7xl px-4 py-2 sm:px-6 lg:px-8'>
|
|
99
|
-
<div className='flex items-center justify-center'>
|
|
100
|
-
<div className='flex w-full max-w-3xl items-center justify-center gap-4'>
|
|
101
|
-
<div className='flex items-center gap-2'>
|
|
102
|
-
<Badge
|
|
103
|
-
variant='secondary'
|
|
104
|
-
className='bg-primary-foreground text-primary px-2.5 py-0.5 font-semibold'
|
|
105
|
-
>
|
|
106
|
-
{deal.title}
|
|
107
|
-
</Badge>
|
|
108
|
-
<span className='font-semibold'>{deal.discount}</span>
|
|
109
|
-
</div>
|
|
110
|
-
<span className='hidden sm:inline'>
|
|
111
|
-
{endsInLabel} {deal.endTime}
|
|
112
|
-
</span>
|
|
113
|
-
<Button
|
|
114
|
-
variant='link'
|
|
115
|
-
size='sm'
|
|
116
|
-
className='text-primary-foreground hover:text-primary-foreground/80 ml-auto h-8 cursor-pointer px-3 text-xs'
|
|
117
|
-
onClick={onShopNow ? () => onShopNow(deal) : undefined}
|
|
118
|
-
>
|
|
119
|
-
{shopNowLabel} <ArrowRight />
|
|
120
|
-
</Button>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
</div>
|
|
124
|
-
</header>
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
<main className='mx-auto w-full max-w-7xl px-4 py-8 sm:px-6 lg:px-8'>
|
|
128
|
-
<div className='mb-12 text-center'>
|
|
129
|
-
{badge && (
|
|
130
|
-
<div className='relative inline-block'>
|
|
131
|
-
<Badge
|
|
132
|
-
variant='outline'
|
|
133
|
-
className='mb-4 flex h-auto items-center gap-2 rounded-full px-3.5 py-1.5 text-sm font-normal'
|
|
134
|
-
>
|
|
135
|
-
<Sparkles className='size-4' aria-hidden='true' />
|
|
136
|
-
{badge}
|
|
137
|
-
</Badge>
|
|
138
|
-
</div>
|
|
139
|
-
)}
|
|
140
|
-
<h1 className='text-4xl font-bold tracking-tight text-balance sm:text-5xl md:text-6xl'>
|
|
141
|
-
{title}
|
|
142
|
-
{subtitle && (
|
|
143
|
-
<>
|
|
144
|
-
<br />
|
|
145
|
-
{subtitle}
|
|
146
|
-
</>
|
|
147
|
-
)}
|
|
148
|
-
</h1>
|
|
149
|
-
<p className='text-muted-foreground mx-auto mt-4 max-w-2xl text-lg'>{description}</p>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<div className='grid gap-4 sm:grid-cols-2 lg:grid-cols-4'>
|
|
153
|
-
{categories.map(category => (
|
|
154
|
-
<Card key={category.id} className='group relative overflow-hidden py-0 transition-all duration-500'>
|
|
155
|
-
<img
|
|
156
|
-
src={category.image || undefined}
|
|
157
|
-
alt={category.imageAlt ?? category.name}
|
|
158
|
-
className='aspect-4/5 size-full object-cover grayscale transition-all duration-500 hover:grayscale-0'
|
|
159
|
-
width={400}
|
|
160
|
-
height={500}
|
|
161
|
-
loading='lazy'
|
|
162
|
-
/>
|
|
163
|
-
<CardContent className='absolute inset-x-0 bottom-0 p-4'>
|
|
164
|
-
<div className='mt-1 flex items-center justify-between'>
|
|
165
|
-
<div>
|
|
166
|
-
<h2 className='text-background dark:text-foreground text-lg font-medium'>{category.name}</h2>
|
|
167
|
-
<span className='text-background/80 dark:text-foreground/80 text-sm'>
|
|
168
|
-
{category.count} {productsLabel}
|
|
169
|
-
</span>
|
|
170
|
-
</div>
|
|
171
|
-
{category.href ? (
|
|
172
|
-
<Button
|
|
173
|
-
variant='secondary'
|
|
174
|
-
size='icon'
|
|
175
|
-
className='h-9 w-9 cursor-pointer rounded-full backdrop-blur-xs'
|
|
176
|
-
aria-label={`${viewLabel} ${category.name}`}
|
|
177
|
-
asChild
|
|
178
|
-
>
|
|
179
|
-
<a href={category.href}>
|
|
180
|
-
<ArrowRight />
|
|
181
|
-
</a>
|
|
182
|
-
</Button>
|
|
183
|
-
) : (
|
|
184
|
-
<Button
|
|
185
|
-
variant='secondary'
|
|
186
|
-
size='icon'
|
|
187
|
-
className='h-9 w-9 cursor-pointer rounded-full backdrop-blur-xs'
|
|
188
|
-
aria-label={`${viewLabel} ${category.name}`}
|
|
189
|
-
onClick={onCategoryClick ? () => onCategoryClick(category) : undefined}
|
|
190
|
-
>
|
|
191
|
-
<ArrowRight />
|
|
192
|
-
</Button>
|
|
193
|
-
)}
|
|
194
|
-
</div>
|
|
195
|
-
</CardContent>
|
|
196
|
-
</Card>
|
|
197
|
-
))}
|
|
198
|
-
</div>
|
|
199
|
-
</main>
|
|
200
|
-
</div>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export { StorefrontHero6 }
|
|
205
|
-
export default StorefrontHero6
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { Badge } from '../../../ui/badge'
|
|
2
|
-
import { Button } from '../../../ui/button'
|
|
3
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
4
|
-
import { ArrowRight } from 'lucide-react'
|
|
5
|
-
import type { LucideIcon } from 'lucide-react'
|
|
6
|
-
|
|
7
|
-
export interface Feature1Item {
|
|
8
|
-
id: string
|
|
9
|
-
title: string
|
|
10
|
-
description: string
|
|
11
|
-
icon: LucideIcon
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Feature1Props {
|
|
15
|
-
items: Feature1Item[]
|
|
16
|
-
badge?: string
|
|
17
|
-
title?: string
|
|
18
|
-
description?: string
|
|
19
|
-
learnMoreLabel?: string
|
|
20
|
-
ctaLabel?: string
|
|
21
|
-
onCtaClick?: () => void
|
|
22
|
-
onLearnMore?: (item: Feature1Item) => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function Feature1({
|
|
26
|
-
items,
|
|
27
|
-
badge,
|
|
28
|
-
title,
|
|
29
|
-
description,
|
|
30
|
-
learnMoreLabel,
|
|
31
|
-
ctaLabel,
|
|
32
|
-
onCtaClick,
|
|
33
|
-
onLearnMore,
|
|
34
|
-
}: Feature1Props) {
|
|
35
|
-
return (
|
|
36
|
-
<section className='mx-auto w-full max-w-7xl px-4 py-12 sm:px-6 md:py-16 lg:px-8'>
|
|
37
|
-
{(badge || title || description) && (
|
|
38
|
-
<header className='mx-auto mb-12 max-w-2xl text-center'>
|
|
39
|
-
{badge && (
|
|
40
|
-
<Badge variant='secondary' className='h-auto mb-4 rounded-full px-4 py-1 font-medium'>
|
|
41
|
-
{badge}
|
|
42
|
-
</Badge>
|
|
43
|
-
)}
|
|
44
|
-
{title && (
|
|
45
|
-
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>{title}</h2>
|
|
46
|
-
)}
|
|
47
|
-
{description && (
|
|
48
|
-
<p className='text-muted-foreground text-pretty'>{description}</p>
|
|
49
|
-
)}
|
|
50
|
-
</header>
|
|
51
|
-
)}
|
|
52
|
-
|
|
53
|
-
<div className='grid gap-4 sm:grid-cols-2 sm:gap-6 md:grid-cols-3'>
|
|
54
|
-
{items.map(feature => (
|
|
55
|
-
<article key={feature.id} className='group'>
|
|
56
|
-
<Card className='relative h-full overflow-hidden transition-all hover:shadow-md'>
|
|
57
|
-
<CardContent className='px-6'>
|
|
58
|
-
<div className='bg-secondary text-secondary-foreground mb-4 inline-flex size-12 items-center justify-center rounded-xl'>
|
|
59
|
-
<feature.icon className='size-5' aria-hidden='true' />
|
|
60
|
-
</div>
|
|
61
|
-
<h3 className='mb-2 text-lg font-semibold'>{feature.title}</h3>
|
|
62
|
-
<p className='text-muted-foreground mb-4 text-sm'>{feature.description}</p>
|
|
63
|
-
|
|
64
|
-
{learnMoreLabel && (
|
|
65
|
-
<Button
|
|
66
|
-
variant='link'
|
|
67
|
-
size='sm'
|
|
68
|
-
className='h-8 px-3 text-xs text-muted-foreground hover:text-foreground h-auto cursor-pointer !p-0 text-sm'
|
|
69
|
-
onClick={onLearnMore ? () => onLearnMore(feature) : undefined}
|
|
70
|
-
>
|
|
71
|
-
{learnMoreLabel}
|
|
72
|
-
<ArrowRight />
|
|
73
|
-
</Button>
|
|
74
|
-
)}
|
|
75
|
-
</CardContent>
|
|
76
|
-
</Card>
|
|
77
|
-
</article>
|
|
78
|
-
))}
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
{ctaLabel && (
|
|
82
|
-
<div className='mt-12 text-center'>
|
|
83
|
-
<Button
|
|
84
|
-
size='lg'
|
|
85
|
-
className='h-10 cursor-pointer rounded-lg !px-8'
|
|
86
|
-
onClick={onCtaClick}
|
|
87
|
-
>
|
|
88
|
-
{ctaLabel}
|
|
89
|
-
<ArrowRight />
|
|
90
|
-
</Button>
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
|
-
</section>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default Feature1
|