@thorprovider/blocks 0.1.3 → 2.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 +155 -0
- package/README.md +228 -68
- package/docs/standards/data-contracts.md +13 -2
- package/package.json +26 -10
- package/scripts/fix-esm-extensions.mjs +40 -0
- package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
- package/src/blocks/application/login-forms/login-page.tsx +240 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
- package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
- package/src/blocks/application/verification/verify-1.tsx +2 -2
- package/src/blocks/application/viewport-ownership.test.tsx +41 -0
- package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
- package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
- package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
- package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
- package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
- package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
- package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
- package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
- package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
- package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
- 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 +447 -0
- package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
- package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
- package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
- package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
- package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
- package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
- package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
- package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
- package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
- package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
- package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
- package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
- package/src/blocks/marketing/features/feature.test.tsx +153 -0
- package/src/blocks/marketing/features/feature.tsx +195 -0
- package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
- package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
- package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
- package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
- package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
- package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
- package/src/blocks/marketing/footers/footer-section.tsx +532 -0
- package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
- package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
- package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
- package/src/blocks/marketing/navbars/navbar.tsx +735 -0
- package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
- package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
- package/src/index.ts +156 -128
- package/src/lib/contracts-curated.test.ts +47 -29
- package/src/lib/contracts.test.ts +198 -2
- package/src/lib/contracts.ts +410 -16
- package/src/ui/button-group.tsx +1 -1
- package/src/ui/carousel.tsx +1 -1
- package/src/ui/dialog.tsx +1 -1
- package/src/ui/field.tsx +2 -2
- package/src/ui/input-group.tsx +3 -3
- package/src/ui/pagination.tsx +1 -1
- package/src/ui/sidebar.tsx +7 -7
- package/src/ui/toggle-group.tsx +1 -1
- package/tsconfig.build.json +12 -0
- package/tsup.config.ts +11 -7
- package/.turbo/turbo-build.log +0 -24
- package/.turbo/turbo-test.log +0 -47
- package/.turbo/turbo-type-check.log +0 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/contracts.d.mts +0 -1732
- package/dist/contracts.d.ts +0 -1732
- package/dist/contracts.js +0 -423
- package/dist/contracts.js.map +0 -1
- package/dist/contracts.mjs +0 -365
- package/dist/contracts.mjs.map +0 -1
- package/dist/index.d.mts +0 -3068
- package/dist/index.d.ts +0 -3068
- package/dist/index.js +0 -17834
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -17740
- package/dist/index.mjs.map +0 -1
- package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
- package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
- package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
- package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
- package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
- package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
- package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
- package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
- package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
- package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
- package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
- package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
- package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
- package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
- package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
- package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
- package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
- package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
- package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
- package/src/blocks/marketing/features/feature-1.tsx +0 -97
- package/src/blocks/marketing/features/feature-2.tsx +0 -82
- package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
- package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
- package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
- package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
- package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
- package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
- package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
- package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { ArrowRight, Flame, Search, ShoppingBag, Star, TrendingUp } from 'lucide-react'
|
|
5
|
-
import type { ComponentProps, ReactNode } from 'react'
|
|
6
|
-
|
|
7
|
-
import { Badge } from '../../../ui/badge'
|
|
8
|
-
import { Button } from '../../../ui/button'
|
|
9
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
10
|
-
import { Carousel, CarouselContent, CarouselItem } from '../../../ui/carousel'
|
|
11
|
-
import type { CarouselApi } from '../../../ui/carousel'
|
|
12
|
-
import { Input } from '../../../ui/input'
|
|
13
|
-
import type { CTA } from '../../../lib/types'
|
|
14
|
-
import { cn } from '../../../lib/utils'
|
|
15
|
-
|
|
16
|
-
interface FeaturedProduct {
|
|
17
|
-
/** Unique identifier for the product. */
|
|
18
|
-
id: string
|
|
19
|
-
/** Product display name. */
|
|
20
|
-
name: string
|
|
21
|
-
/** Image URL or path for the product. */
|
|
22
|
-
image: string
|
|
23
|
-
/** Current selling price in the store currency. */
|
|
24
|
-
price: number
|
|
25
|
-
/** Average rating from 0 to 5. */
|
|
26
|
-
rating: number
|
|
27
|
-
/** Number of reviews the product has received. */
|
|
28
|
-
reviews: number
|
|
29
|
-
/** Whether the product is flagged as trending. */
|
|
30
|
-
trending: boolean
|
|
31
|
-
/** Badge label shown on the slide, e.g. "Best Seller". */
|
|
32
|
-
tag: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface StorefrontHero2Props {
|
|
36
|
-
/** Main heading displayed in the hero. */
|
|
37
|
-
title: string
|
|
38
|
-
/** Supporting description under the heading. */
|
|
39
|
-
description: string
|
|
40
|
-
/** Products rendered in the auto-rotating carousel. */
|
|
41
|
-
products: FeaturedProduct[]
|
|
42
|
-
/** Small pill badge above the heading. */
|
|
43
|
-
badge?: string
|
|
44
|
-
/** Placeholder text for the search input. */
|
|
45
|
-
searchPlaceholder?: string
|
|
46
|
-
/** Label of the search submit button. */
|
|
47
|
-
searchLabel?: string
|
|
48
|
-
/** Primary call to action; rendered as a link when it has an href. */
|
|
49
|
-
primaryCta?: CTA
|
|
50
|
-
/** Secondary call to action; rendered as a link when it has an href. */
|
|
51
|
-
secondaryCta?: CTA
|
|
52
|
-
/** Copy shown inside each product slide below the product name. */
|
|
53
|
-
featuredDescription?: string
|
|
54
|
-
/** Label of the call to action inside each product slide. */
|
|
55
|
-
productCtaLabel?: string
|
|
56
|
-
/** Label shown next to the review count. */
|
|
57
|
-
reviewsLabel?: string
|
|
58
|
-
/** Label of the trending badge on product slides. */
|
|
59
|
-
trendingLabel?: string
|
|
60
|
-
/** Called with the current query when the search is submitted. */
|
|
61
|
-
onSearch?: (query: string) => void
|
|
62
|
-
/** Called when a product slide call to action is clicked. */
|
|
63
|
-
onProductClick?: (product: FeaturedProduct) => void
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function CtaButton({
|
|
67
|
-
cta,
|
|
68
|
-
variant,
|
|
69
|
-
children,
|
|
70
|
-
}: {
|
|
71
|
-
cta: CTA
|
|
72
|
-
variant?: ComponentProps<typeof Button>['variant']
|
|
73
|
-
children: ReactNode
|
|
74
|
-
}) {
|
|
75
|
-
const className = 'h-10 cursor-pointer rounded-full px-4'
|
|
76
|
-
|
|
77
|
-
if (cta.href) {
|
|
78
|
-
return (
|
|
79
|
-
<Button variant={variant} size='lg' className={className} asChild>
|
|
80
|
-
<a href={cta.href}>{children}</a>
|
|
81
|
-
</Button>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<Button variant={variant} size='lg' className={className}>
|
|
87
|
-
{children}
|
|
88
|
-
</Button>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function StorefrontHero2({
|
|
93
|
-
title,
|
|
94
|
-
description,
|
|
95
|
-
products,
|
|
96
|
-
badge,
|
|
97
|
-
searchPlaceholder = 'Buscar productos...',
|
|
98
|
-
searchLabel = 'Buscar',
|
|
99
|
-
primaryCta,
|
|
100
|
-
secondaryCta,
|
|
101
|
-
featuredDescription,
|
|
102
|
-
productCtaLabel = 'Comprar ahora',
|
|
103
|
-
reviewsLabel = 'reseñas',
|
|
104
|
-
trendingLabel = 'Tendencia',
|
|
105
|
-
onSearch,
|
|
106
|
-
onProductClick,
|
|
107
|
-
}: StorefrontHero2Props) {
|
|
108
|
-
const [searchQuery, setSearchQuery] = useState('')
|
|
109
|
-
const [api, setApi] = useState<CarouselApi>()
|
|
110
|
-
const [currentSlide, setCurrentSlide] = useState(0)
|
|
111
|
-
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
if (!api || products.length <= 1) return
|
|
114
|
-
|
|
115
|
-
const interval = setInterval(() => {
|
|
116
|
-
const nextSlide = (currentSlide + 1) % products.length
|
|
117
|
-
api.scrollTo(nextSlide)
|
|
118
|
-
setCurrentSlide(nextSlide)
|
|
119
|
-
}, 5000)
|
|
120
|
-
|
|
121
|
-
return () => clearInterval(interval)
|
|
122
|
-
}, [api, currentSlide, products.length])
|
|
123
|
-
|
|
124
|
-
const handleSearch = () => {
|
|
125
|
-
if (onSearch) onSearch(searchQuery)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<section className='from-background to-accent/20 relative bg-linear-to-b'>
|
|
130
|
-
<div className='relative mx-auto w-full max-w-7xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20'>
|
|
131
|
-
<div className='grid grid-cols-1 items-center gap-12 lg:grid-cols-2'>
|
|
132
|
-
<header className='flex flex-col gap-8'>
|
|
133
|
-
{badge && (
|
|
134
|
-
<Badge variant='outline' className='flex h-auto w-fit items-center gap-2 rounded-full px-4 py-2 font-semibold'>
|
|
135
|
-
<TrendingUp className='size-4' />
|
|
136
|
-
{badge}
|
|
137
|
-
</Badge>
|
|
138
|
-
)}
|
|
139
|
-
|
|
140
|
-
<h1 className='text-5xl leading-tight font-bold text-balance md:text-6xl lg:text-7xl'>{title}</h1>
|
|
141
|
-
|
|
142
|
-
<p className='text-muted-foreground max-w-lg text-xl text-balance'>{description}</p>
|
|
143
|
-
|
|
144
|
-
<div className='relative max-w-md'>
|
|
145
|
-
<Input
|
|
146
|
-
type='search'
|
|
147
|
-
placeholder={searchPlaceholder}
|
|
148
|
-
value={searchQuery}
|
|
149
|
-
onChange={e => setSearchQuery(e.target.value)}
|
|
150
|
-
onKeyDown={e => {
|
|
151
|
-
if (e.key === 'Enter') handleSearch()
|
|
152
|
-
}}
|
|
153
|
-
className='h-14 rounded-full pe-4 pl-12 text-lg'
|
|
154
|
-
aria-label={searchPlaceholder}
|
|
155
|
-
/>
|
|
156
|
-
<Search className='text-muted-foreground absolute top-1/2 start-4 size-5 -translate-y-1/2' />
|
|
157
|
-
<Button
|
|
158
|
-
size='lg'
|
|
159
|
-
className='absolute top-1/2 end-2 h-10 -translate-y-1/2 cursor-pointer rounded-full px-6'
|
|
160
|
-
onClick={handleSearch}
|
|
161
|
-
>
|
|
162
|
-
{searchLabel}
|
|
163
|
-
</Button>
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
{(primaryCta || secondaryCta) && (
|
|
167
|
-
<div className='flex items-center gap-4'>
|
|
168
|
-
{primaryCta && (
|
|
169
|
-
<CtaButton cta={primaryCta}>
|
|
170
|
-
<span className='flex items-center gap-2'>
|
|
171
|
-
{primaryCta.label}
|
|
172
|
-
<ArrowRight />
|
|
173
|
-
</span>
|
|
174
|
-
</CtaButton>
|
|
175
|
-
)}
|
|
176
|
-
{secondaryCta && (
|
|
177
|
-
<CtaButton cta={secondaryCta} variant='outline'>
|
|
178
|
-
<span className='flex items-center gap-2'>
|
|
179
|
-
<ShoppingBag />
|
|
180
|
-
{secondaryCta.label}
|
|
181
|
-
</span>
|
|
182
|
-
</CtaButton>
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
</header>
|
|
187
|
-
|
|
188
|
-
{products.length > 0 && (
|
|
189
|
-
<div className='flex flex-col gap-4'>
|
|
190
|
-
<div className='relative h-[500px] w-full border-0'>
|
|
191
|
-
<Carousel
|
|
192
|
-
className='group size-full'
|
|
193
|
-
setApi={setApi}
|
|
194
|
-
opts={{
|
|
195
|
-
align: 'start',
|
|
196
|
-
loop: true,
|
|
197
|
-
duration: 20,
|
|
198
|
-
skipSnaps: true,
|
|
199
|
-
}}
|
|
200
|
-
>
|
|
201
|
-
<CarouselContent className='h-full'>
|
|
202
|
-
{products.map(product => (
|
|
203
|
-
<CarouselItem key={product.id} className='h-full'>
|
|
204
|
-
<Card className='relative size-full overflow-hidden border-1 py-4'>
|
|
205
|
-
<CardContent className='px-4'>
|
|
206
|
-
<div className='relative size-full overflow-hidden rounded-md'>
|
|
207
|
-
<img
|
|
208
|
-
src={product.image || undefined}
|
|
209
|
-
alt={product.name}
|
|
210
|
-
className='h-[500px] w-full object-cover'
|
|
211
|
-
loading='lazy'
|
|
212
|
-
/>
|
|
213
|
-
</div>
|
|
214
|
-
<div className='from-background/90 via-background/30 absolute inset-0 bg-linear-to-t to-transparent' />
|
|
215
|
-
|
|
216
|
-
<div className='text-background-foreground absolute inset-0 flex flex-col justify-end p-8'>
|
|
217
|
-
<div className='relative z-10 flex max-w-md flex-col gap-4'>
|
|
218
|
-
<Badge className='w-fit rounded-full px-2.5 py-0.5 font-semibold'>{product.tag}</Badge>
|
|
219
|
-
<h2 className='text-4xl font-bold'>{product.name}</h2>
|
|
220
|
-
{featuredDescription && (
|
|
221
|
-
<p className='text-background-foreground/80 text-lg'>{featuredDescription}</p>
|
|
222
|
-
)}
|
|
223
|
-
<span className='text-3xl font-bold'>${product.price.toFixed(2)}</span>
|
|
224
|
-
<div className='flex items-center gap-4 pt-2'>
|
|
225
|
-
<Button
|
|
226
|
-
size='lg'
|
|
227
|
-
className='h-10 cursor-pointer rounded-full px-8'
|
|
228
|
-
onClick={onProductClick ? () => onProductClick(product) : undefined}
|
|
229
|
-
>
|
|
230
|
-
{productCtaLabel}
|
|
231
|
-
</Button>
|
|
232
|
-
<div className='text-foreground flex items-center gap-1'>
|
|
233
|
-
<Star className='fill-foreground size-5' />
|
|
234
|
-
<span className='font-medium'>{product.rating}</span>
|
|
235
|
-
<span className='text-foreground/80'>
|
|
236
|
-
({product.reviews} {reviewsLabel})
|
|
237
|
-
</span>
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
|
|
243
|
-
{product.trending && (
|
|
244
|
-
<div className='text-background-foreground bg-foreground/10 dark:bg-background/20 absolute top-8 end-8 flex items-center gap-1 rounded-full px-3 py-1 text-sm font-medium backdrop-blur-xs'>
|
|
245
|
-
<Flame className='size-4' /> {trendingLabel}
|
|
246
|
-
</div>
|
|
247
|
-
)}
|
|
248
|
-
</CardContent>
|
|
249
|
-
</Card>
|
|
250
|
-
</CarouselItem>
|
|
251
|
-
))}
|
|
252
|
-
</CarouselContent>
|
|
253
|
-
</Carousel>
|
|
254
|
-
</div>
|
|
255
|
-
|
|
256
|
-
<div className='relative mt-8 flex justify-center gap-3'>
|
|
257
|
-
{products.map((_, index) => (
|
|
258
|
-
<button
|
|
259
|
-
key={index}
|
|
260
|
-
onClick={() => {
|
|
261
|
-
api?.scrollTo(index)
|
|
262
|
-
setCurrentSlide(index)
|
|
263
|
-
}}
|
|
264
|
-
className={cn(
|
|
265
|
-
'relative size-3 rounded-full transition-all',
|
|
266
|
-
currentSlide === index ? 'bg-primary' : 'bg-foreground/20 hover:bg-foreground/40'
|
|
267
|
-
)}
|
|
268
|
-
aria-label={`Ir a la diapositiva ${index + 1}`}
|
|
269
|
-
aria-current={currentSlide === index ? 'step' : undefined}
|
|
270
|
-
/>
|
|
271
|
-
))}
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
)}
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
</section>
|
|
278
|
-
)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export { StorefrontHero2 }
|
|
282
|
-
export type { FeaturedProduct, StorefrontHero2Props }
|
|
283
|
-
export default StorefrontHero2
|
|
@@ -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
|