@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.
Files changed (111) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +108 -23
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/e-commerce/navigation/back-to-top.test.tsx +27 -0
  7. package/src/blocks/e-commerce/navigation/back-to-top.tsx +71 -0
  8. package/src/blocks/e-commerce/navigation/breadcrumb.test.tsx +174 -0
  9. package/src/blocks/e-commerce/navigation/breadcrumb.tsx +130 -0
  10. package/src/blocks/e-commerce/navigation/mobile-bottom-nav.test.tsx +74 -0
  11. package/src/blocks/e-commerce/navigation/mobile-bottom-nav.tsx +78 -0
  12. package/src/blocks/e-commerce/navigation/skip-to-content.test.tsx +35 -0
  13. package/src/blocks/e-commerce/navigation/skip-to-content.tsx +37 -0
  14. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  15. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  16. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  17. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  18. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  19. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  20. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  21. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  22. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  23. package/src/blocks/e-commerce/product-list/product-card.tsx +424 -0
  24. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  25. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  26. package/src/blocks/e-commerce/product-list/product-showcase-1.test.tsx +78 -0
  27. package/src/blocks/e-commerce/product-list/product-showcase-1.tsx +277 -0
  28. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  29. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  30. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  31. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  32. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  33. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  34. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  35. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  36. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +204 -109
  37. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  38. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  39. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  40. package/src/blocks/marketing/features/feature.tsx +195 -0
  41. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +56 -0
  42. package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +30 -14
  43. package/src/blocks/marketing/navbars/navbar-4-cart-sheet.test.tsx +150 -0
  44. package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +102 -9
  45. package/src/blocks/marketing/navbars/navbar-4.test.tsx +154 -2
  46. package/src/blocks/marketing/navbars/navbar-4.tsx +152 -32
  47. package/src/index.ts +93 -46
  48. package/src/lib/contracts-curated.test.ts +42 -24
  49. package/src/lib/contracts.test.ts +143 -2
  50. package/src/lib/contracts.ts +395 -13
  51. package/src/ui/button-group.tsx +1 -1
  52. package/src/ui/carousel.tsx +1 -1
  53. package/src/ui/dialog.tsx +1 -1
  54. package/src/ui/field.tsx +2 -2
  55. package/src/ui/input-group.tsx +3 -3
  56. package/src/ui/pagination.tsx +1 -1
  57. package/src/ui/sidebar.tsx +7 -7
  58. package/src/ui/toggle-group.tsx +1 -1
  59. package/tsconfig.build.json +12 -0
  60. package/tsup.config.ts +11 -7
  61. package/.turbo/turbo-build.log +0 -24
  62. package/.turbo/turbo-test.log +0 -37
  63. package/.turbo/turbo-type-check.log +0 -0
  64. package/dist/.tsbuildinfo +0 -1
  65. package/dist/contracts.d.mts +0 -1584
  66. package/dist/contracts.d.ts +0 -1584
  67. package/dist/contracts.js +0 -390
  68. package/dist/contracts.js.map +0 -1
  69. package/dist/contracts.mjs +0 -334
  70. package/dist/contracts.mjs.map +0 -1
  71. package/dist/index.d.mts +0 -2786
  72. package/dist/index.d.ts +0 -2786
  73. package/dist/index.js +0 -17139
  74. package/dist/index.js.map +0 -1
  75. package/dist/index.mjs +0 -17059
  76. package/dist/index.mjs.map +0 -1
  77. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  78. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  79. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  80. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  81. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  82. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  83. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -131
  84. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  85. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  86. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  87. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  88. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  89. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  90. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  91. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  92. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  93. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  94. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  95. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  96. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  97. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  98. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  99. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  100. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  101. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  102. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  103. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  104. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -137
  105. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -65
  106. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  107. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  108. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  109. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  110. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  111. 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