@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,219 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import type { JSX } from 'react'
|
|
4
|
-
import { AnimatePresence, motion, useReducedMotion } from 'motion/react'
|
|
5
|
-
import { Flame, ShoppingBag, Sparkles, Star, Trophy } from 'lucide-react'
|
|
6
|
-
|
|
7
|
-
import { Badge } from '../../../ui/badge'
|
|
8
|
-
import { Button } from '../../../ui/button'
|
|
9
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
10
|
-
import { cn } from '../../../lib/utils'
|
|
11
|
-
|
|
12
|
-
import { Navbar4LikeButton } from './navbar-4-like-button'
|
|
13
|
-
|
|
14
|
-
export interface CatalogProduct {
|
|
15
|
-
id: string
|
|
16
|
-
title: string
|
|
17
|
-
subtitle: string
|
|
18
|
-
image: string
|
|
19
|
-
price: number
|
|
20
|
-
originalPrice: number
|
|
21
|
-
rating: number
|
|
22
|
-
reviewCount: number
|
|
23
|
-
discountPercentage: number
|
|
24
|
-
isLowStock?: boolean
|
|
25
|
-
badge?: {
|
|
26
|
-
text: string
|
|
27
|
-
variant: 'trending' | 'bestseller' | 'new'
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface Navbar4ProductGridProps {
|
|
32
|
-
products: CatalogProduct[]
|
|
33
|
-
searchQuery: string
|
|
34
|
-
/** When omitted, the section heading is hidden. */
|
|
35
|
-
heading?: string
|
|
36
|
-
currencySymbol?: string
|
|
37
|
-
onAddToCart?: (product: CatalogProduct) => void
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const filterEase = [0.22, 1, 0.36, 1] as const
|
|
41
|
-
|
|
42
|
-
interface BadgeStyle {
|
|
43
|
-
bg: string
|
|
44
|
-
icon: JSX.Element | null
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const badgeStyles: Record<string, BadgeStyle> = {
|
|
48
|
-
trending: {
|
|
49
|
-
bg: 'bg-black',
|
|
50
|
-
icon: <Flame className='size-3' />,
|
|
51
|
-
},
|
|
52
|
-
bestseller: {
|
|
53
|
-
bg: 'bg-black',
|
|
54
|
-
icon: <Trophy className='size-3' />,
|
|
55
|
-
},
|
|
56
|
-
new: {
|
|
57
|
-
bg: 'bg-black',
|
|
58
|
-
icon: <Sparkles className='size-3' />,
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface Navbar4ProductCardProps {
|
|
63
|
-
product: CatalogProduct
|
|
64
|
-
currencySymbol?: string
|
|
65
|
-
onAddToCart?: (product: CatalogProduct) => void
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function Navbar4ProductCard({
|
|
69
|
-
product,
|
|
70
|
-
currencySymbol = '$',
|
|
71
|
-
onAddToCart,
|
|
72
|
-
}: Navbar4ProductCardProps) {
|
|
73
|
-
const badgeStyle = product.badge ? badgeStyles[product.badge.variant] || badgeStyles.new : null
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Card className='relative overflow-hidden rounded-md py-0 shadow-none transition-shadow hover:shadow-md'>
|
|
77
|
-
<CardContent className='px-0'>
|
|
78
|
-
<div className='relative aspect-square w-full overflow-hidden'>
|
|
79
|
-
<img src={product.image || undefined} alt={product.title} className='size-full object-cover' loading='lazy' />
|
|
80
|
-
|
|
81
|
-
{product.badge && badgeStyle ? (
|
|
82
|
-
<div className='absolute start-2 top-2 z-20'>
|
|
83
|
-
<Badge
|
|
84
|
-
className={cn(
|
|
85
|
-
'flex h-auto items-center gap-1 px-2 py-1 font-semibold text-white backdrop-blur-xs',
|
|
86
|
-
badgeStyle.bg,
|
|
87
|
-
)}
|
|
88
|
-
>
|
|
89
|
-
{badgeStyle.icon}
|
|
90
|
-
<span className='text-xs font-medium'>{product.badge.text}</span>
|
|
91
|
-
</Badge>
|
|
92
|
-
</div>
|
|
93
|
-
) : null}
|
|
94
|
-
|
|
95
|
-
{product.rating > 0 ? (
|
|
96
|
-
<div className='bg-background/80 absolute start-2 bottom-2 z-10 flex items-center gap-1 rounded-md border px-2 py-1 text-sm backdrop-blur-xs'>
|
|
97
|
-
<Star className='fill-foreground text-foreground size-4' />
|
|
98
|
-
<span>{product.rating}</span>
|
|
99
|
-
{product.reviewCount > 0 ? (
|
|
100
|
-
<span className='text-muted-foreground'>
|
|
101
|
-
|{' '}
|
|
102
|
-
{product.reviewCount >= 1000
|
|
103
|
-
? `${(product.reviewCount / 1000).toFixed(1)}k`
|
|
104
|
-
: product.reviewCount}
|
|
105
|
-
</span>
|
|
106
|
-
) : null}
|
|
107
|
-
</div>
|
|
108
|
-
) : null}
|
|
109
|
-
|
|
110
|
-
<Navbar4LikeButton className='absolute end-2 top-2 z-20' />
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<div className='flex flex-col gap-3 p-4'>
|
|
114
|
-
<div className='flex flex-col gap-1.5'>
|
|
115
|
-
<h3 className='font-medium text-balance'>{product.title}</h3>
|
|
116
|
-
<p className='text-muted-foreground text-sm text-balance'>{product.subtitle}</p>
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<div className='flex items-center gap-2'>
|
|
120
|
-
<span className='font-semibold'>
|
|
121
|
-
{currencySymbol}
|
|
122
|
-
{product.price}
|
|
123
|
-
</span>
|
|
124
|
-
<span className='text-muted-foreground text-sm line-through'>
|
|
125
|
-
{currencySymbol}
|
|
126
|
-
{product.originalPrice}
|
|
127
|
-
</span>
|
|
128
|
-
<span className='text-sm'>({product.discountPercentage}% OFF)</span>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
{product.isLowStock ? <p className='text-destructive text-sm'>Only few items left!</p> : null}
|
|
132
|
-
|
|
133
|
-
<Button className='w-full' onClick={onAddToCart ? () => onAddToCart(product) : undefined}>
|
|
134
|
-
<ShoppingBag data-icon='inline-start' />
|
|
135
|
-
Add to cart
|
|
136
|
-
</Button>
|
|
137
|
-
</div>
|
|
138
|
-
</CardContent>
|
|
139
|
-
</Card>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function Navbar4ProductGrid({
|
|
144
|
-
products,
|
|
145
|
-
searchQuery,
|
|
146
|
-
heading,
|
|
147
|
-
currencySymbol,
|
|
148
|
-
onAddToCart,
|
|
149
|
-
}: Navbar4ProductGridProps) {
|
|
150
|
-
const reduceMotion = useReducedMotion()
|
|
151
|
-
const query = searchQuery.trim().toLowerCase()
|
|
152
|
-
const filtered = query
|
|
153
|
-
? products.filter(
|
|
154
|
-
product =>
|
|
155
|
-
product.title.toLowerCase().includes(query) || product.subtitle.toLowerCase().includes(query),
|
|
156
|
-
)
|
|
157
|
-
: products
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<section aria-label='Featured products' className='mx-auto w-full max-w-7xl px-4 py-10 sm:px-6 lg:px-8'>
|
|
161
|
-
<div className='mb-6 flex items-end justify-between gap-4'>
|
|
162
|
-
<div>
|
|
163
|
-
{heading ? (
|
|
164
|
-
<h2 className='text-lg font-semibold tracking-tight'>{heading}</h2>
|
|
165
|
-
) : null}
|
|
166
|
-
<p className='text-muted-foreground text-sm'>
|
|
167
|
-
{query
|
|
168
|
-
? `${filtered.length} ${filtered.length === 1 ? 'result' : 'results'} for “${searchQuery.trim()}”`
|
|
169
|
-
: 'Shop the latest pieces — add anything to your bag.'}
|
|
170
|
-
</p>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
{filtered.length === 0 ? (
|
|
175
|
-
<div className='border-border bg-muted/30 flex min-h-64 items-center justify-center rounded-xl border border-dashed px-6 text-center'>
|
|
176
|
-
<div>
|
|
177
|
-
<p className='font-medium'>No products match your search</p>
|
|
178
|
-
<p className='text-muted-foreground mt-1 text-sm'>Try a different keyword or clear the search.</p>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
) : (
|
|
182
|
-
<motion.ul
|
|
183
|
-
layout
|
|
184
|
-
className='grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3'
|
|
185
|
-
transition={{ duration: 0.28, ease: filterEase }}
|
|
186
|
-
>
|
|
187
|
-
<AnimatePresence mode='popLayout'>
|
|
188
|
-
{filtered.map(product => (
|
|
189
|
-
<motion.li
|
|
190
|
-
key={product.id}
|
|
191
|
-
layout={!reduceMotion}
|
|
192
|
-
initial={reduceMotion ? false : { opacity: 0, y: 12, scale: 0.97 }}
|
|
193
|
-
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
194
|
-
exit={
|
|
195
|
-
reduceMotion
|
|
196
|
-
? undefined
|
|
197
|
-
: {
|
|
198
|
-
opacity: 0,
|
|
199
|
-
y: -8,
|
|
200
|
-
scale: 0.98,
|
|
201
|
-
filter: 'blur(4px)',
|
|
202
|
-
transition: { duration: 0.2, ease: 'easeIn' },
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
transition={{ duration: 0.28, ease: filterEase }}
|
|
206
|
-
>
|
|
207
|
-
<Navbar4ProductCard
|
|
208
|
-
product={product}
|
|
209
|
-
currencySymbol={currencySymbol}
|
|
210
|
-
onAddToCart={onAddToCart}
|
|
211
|
-
/>
|
|
212
|
-
</motion.li>
|
|
213
|
-
))}
|
|
214
|
-
</AnimatePresence>
|
|
215
|
-
</motion.ul>
|
|
216
|
-
)}
|
|
217
|
-
</section>
|
|
218
|
-
)
|
|
219
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import { Star } from 'lucide-react'
|
|
5
|
-
import { cn } from '../../../lib/utils'
|
|
6
|
-
import { Card, CardContent, CardHeader, CardFooter, CardTitle } from '../../../ui/card'
|
|
7
|
-
import {
|
|
8
|
-
Carousel,
|
|
9
|
-
CarouselContent,
|
|
10
|
-
CarouselItem,
|
|
11
|
-
CarouselNext,
|
|
12
|
-
CarouselPrevious,
|
|
13
|
-
type CarouselApi,
|
|
14
|
-
} from '../../../ui/carousel'
|
|
15
|
-
import { Avatar, AvatarFallback, AvatarImage } from '../../../ui/avatar'
|
|
16
|
-
import { Button } from '../../../ui/button'
|
|
17
|
-
|
|
18
|
-
export interface Testimonial1Item {
|
|
19
|
-
id: string
|
|
20
|
-
name: string
|
|
21
|
-
role: string
|
|
22
|
-
avatar: string
|
|
23
|
-
content: string
|
|
24
|
-
rating: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface Testimonial1Props {
|
|
28
|
-
items: Testimonial1Item[]
|
|
29
|
-
title?: string
|
|
30
|
-
prevLabel?: string
|
|
31
|
-
nextLabel?: string
|
|
32
|
-
slideAriaLabel?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function RatingStars({ rating }: { rating: number }) {
|
|
36
|
-
return (
|
|
37
|
-
<div className='flex items-center gap-1'>
|
|
38
|
-
{[...Array(5)].map((_, index) => (
|
|
39
|
-
<Star
|
|
40
|
-
key={index}
|
|
41
|
-
className={cn('size-4', index < Math.floor(rating) ? 'fill-foreground' : 'fill-none')}
|
|
42
|
-
/>
|
|
43
|
-
))}
|
|
44
|
-
<span className='text-muted-foreground ms-2 text-sm'>({rating})</span>
|
|
45
|
-
</div>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function Testimonial1({
|
|
50
|
-
items,
|
|
51
|
-
title = 'Lo que dicen nuestros clientes',
|
|
52
|
-
prevLabel = 'Anterior',
|
|
53
|
-
nextLabel = 'Siguiente',
|
|
54
|
-
slideAriaLabel = 'Ir a la diapositiva {n}',
|
|
55
|
-
}: Testimonial1Props) {
|
|
56
|
-
const [api, setApi] = React.useState<CarouselApi | null>(null)
|
|
57
|
-
const [current, setCurrent] = React.useState(0)
|
|
58
|
-
|
|
59
|
-
React.useEffect(() => {
|
|
60
|
-
if (!api) return
|
|
61
|
-
|
|
62
|
-
const onSelect = () => {
|
|
63
|
-
setCurrent(api.selectedScrollSnap())
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
api.on('select', onSelect)
|
|
67
|
-
return () => {
|
|
68
|
-
api.off('select', onSelect)
|
|
69
|
-
}
|
|
70
|
-
}, [api])
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<section className='py-12 lg:py-20'>
|
|
74
|
-
<div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
75
|
-
<header className='mb-16 text-center'>
|
|
76
|
-
<h2 className='text-3xl font-bold text-balance md:text-4xl'>{title}</h2>
|
|
77
|
-
</header>
|
|
78
|
-
|
|
79
|
-
<Carousel className='w-full' setApi={setApi} opts={{ align: 'start', loop: true }}>
|
|
80
|
-
<CarouselContent className='-ml-1'>
|
|
81
|
-
{items.map(testimonial => (
|
|
82
|
-
<CarouselItem
|
|
83
|
-
key={testimonial.id}
|
|
84
|
-
className='basis-full px-4 last:pe-0 sm:basis-1/2 lg:basis-1/3'
|
|
85
|
-
>
|
|
86
|
-
<Card className='h-full overflow-hidden border border-border py-6'>
|
|
87
|
-
<CardHeader className='gap-0 px-6'>
|
|
88
|
-
<div className='flex items-center gap-4'>
|
|
89
|
-
<Avatar className='bg-muted size-12'>
|
|
90
|
-
<AvatarImage src={testimonial.avatar || undefined} alt={testimonial.name} className='size-12' />
|
|
91
|
-
<AvatarFallback className='bg-card'>{testimonial.name[0]}</AvatarFallback>
|
|
92
|
-
</Avatar>
|
|
93
|
-
<div>
|
|
94
|
-
<CardTitle className='text-foreground font-semibold'>{testimonial.name}</CardTitle>
|
|
95
|
-
<p className='text-muted-foreground text-sm'>{testimonial.role}</p>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</CardHeader>
|
|
99
|
-
<CardContent className='px-6'>
|
|
100
|
-
<p className='text-muted-foreground text-base'>{testimonial.content}</p>
|
|
101
|
-
</CardContent>
|
|
102
|
-
<CardFooter className='bg-transparent border-t-0 px-6 pb-6'>
|
|
103
|
-
<RatingStars rating={testimonial.rating} />
|
|
104
|
-
</CardFooter>
|
|
105
|
-
</Card>
|
|
106
|
-
</CarouselItem>
|
|
107
|
-
))}
|
|
108
|
-
</CarouselContent>
|
|
109
|
-
<CarouselPrevious
|
|
110
|
-
variant='outline'
|
|
111
|
-
className='hidden cursor-pointer lg:flex'
|
|
112
|
-
aria-label={prevLabel}
|
|
113
|
-
/>
|
|
114
|
-
<CarouselNext
|
|
115
|
-
variant='outline'
|
|
116
|
-
className='hidden cursor-pointer lg:flex'
|
|
117
|
-
aria-label={nextLabel}
|
|
118
|
-
/>
|
|
119
|
-
<div className='mt-8 flex items-center justify-center gap-2'>
|
|
120
|
-
{items.map((_, index) => (
|
|
121
|
-
<Button
|
|
122
|
-
variant='ghost'
|
|
123
|
-
key={index}
|
|
124
|
-
onClick={() => api?.scrollTo(index)}
|
|
125
|
-
className={cn(
|
|
126
|
-
'h-9 px-4 py-2',
|
|
127
|
-
'size-2 cursor-pointer rounded-full !p-0 transition-all',
|
|
128
|
-
current === index ? 'bg-foreground w-6' : 'bg-muted',
|
|
129
|
-
)}
|
|
130
|
-
aria-label={slideAriaLabel.replace('{n}', String(index + 1))}
|
|
131
|
-
aria-current={current === index ? 'true' : 'false'}
|
|
132
|
-
/>
|
|
133
|
-
))}
|
|
134
|
-
</div>
|
|
135
|
-
</Carousel>
|
|
136
|
-
</div>
|
|
137
|
-
</section>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default Testimonial1
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { Quote } from 'lucide-react'
|
|
2
|
-
import { Card, CardContent, CardHeader } from '../../../ui/card'
|
|
3
|
-
import { Avatar, AvatarFallback, AvatarImage } from '../../../ui/avatar'
|
|
4
|
-
|
|
5
|
-
export interface Testimonial2Item {
|
|
6
|
-
id: string
|
|
7
|
-
name: string
|
|
8
|
-
role: string
|
|
9
|
-
avatar: string
|
|
10
|
-
content: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface Testimonial2Props {
|
|
14
|
-
items: Testimonial2Item[]
|
|
15
|
-
title?: string
|
|
16
|
-
description?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const bentoGridClasses = [
|
|
20
|
-
'grid grid-rows-[auto_1fr] gap-8 ring-1 ring-foreground/10 sm:p-6 md:col-span-2 lg:col-span-2 lg:row-span-2 shadow-sm',
|
|
21
|
-
'ring-1 ring-foreground/10 md:col-span-2 lg:col-span-2 py-6 shadow-sm',
|
|
22
|
-
'pt-0 ring-1 ring-foreground/10 lg:col-span-1 shadow-sm',
|
|
23
|
-
'pt-0 ring-1 ring-foreground/10 lg:col-span-1 shadow-sm',
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
const contentClasses = [
|
|
27
|
-
'',
|
|
28
|
-
'h-full px-6',
|
|
29
|
-
'h-full pt-6 px-6',
|
|
30
|
-
'h-full pt-6 px-6',
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
const textClasses = [
|
|
34
|
-
'text-xl text-balance',
|
|
35
|
-
'text-base text-balance',
|
|
36
|
-
'text-base text-balance',
|
|
37
|
-
'text-base text-balance',
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
function initials(name: string) {
|
|
41
|
-
return name
|
|
42
|
-
.split(' ')
|
|
43
|
-
.map(n => n[0])
|
|
44
|
-
.join('')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function Testimonial2({
|
|
48
|
-
items,
|
|
49
|
-
title = 'Con la confianza de quienes construyen',
|
|
50
|
-
description,
|
|
51
|
-
}: Testimonial2Props) {
|
|
52
|
-
/** El diseño bento consume exactamente 4 tarjetas: la primera ocupa la celda
|
|
53
|
-
* grande (lg:row-span-2) y las tres restantes completan la cuadrícula. */
|
|
54
|
-
const bentoItems = items.slice(0, 4)
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<section className='py-12 md:py-16'>
|
|
58
|
-
<div className='mx-auto w-full max-w-7xl flex flex-col gap-8 px-4 sm:px-6 lg:px-8 md:gap-16'>
|
|
59
|
-
<div className='relative z-10 mx-auto max-w-4xl flex flex-col gap-6 text-center md:gap-12'>
|
|
60
|
-
<h2 className='text-4xl font-medium max-lg:text-balance lg:text-5xl'>{title}</h2>
|
|
61
|
-
{description && (
|
|
62
|
-
<p className='text-muted-foreground mx-auto max-w-2xl max-lg:text-balance'>
|
|
63
|
-
{description}
|
|
64
|
-
</p>
|
|
65
|
-
)}
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<div className='grid gap-4 md:grid-cols-2 lg:grid-cols-4 lg:grid-rows-2'>
|
|
69
|
-
{bentoItems.map((testimonial, index) => (
|
|
70
|
-
<Card key={testimonial.id} className={bentoGridClasses[index]}>
|
|
71
|
-
{index === 0 && (
|
|
72
|
-
<CardHeader className='pb-6'>
|
|
73
|
-
<div className='flex items-center justify-start'>
|
|
74
|
-
<Quote className='text-muted-foreground/30 size-16 rotate-180' />
|
|
75
|
-
</div>
|
|
76
|
-
</CardHeader>
|
|
77
|
-
)}
|
|
78
|
-
<CardContent className={contentClasses[index]}>
|
|
79
|
-
<blockquote className='grid h-full grid-rows-[1fr_auto] gap-6'>
|
|
80
|
-
<p className={textClasses[index]}>{testimonial.content}</p>
|
|
81
|
-
|
|
82
|
-
<div className='grid grid-cols-[auto_1fr] items-center gap-3'>
|
|
83
|
-
<Avatar className='bg-muted size-12'>
|
|
84
|
-
<AvatarImage
|
|
85
|
-
src={testimonial.avatar || undefined}
|
|
86
|
-
alt={testimonial.name}
|
|
87
|
-
height='400'
|
|
88
|
-
width='400'
|
|
89
|
-
loading='lazy'
|
|
90
|
-
/>
|
|
91
|
-
<AvatarFallback>{initials(testimonial.name)}</AvatarFallback>
|
|
92
|
-
</Avatar>
|
|
93
|
-
<div>
|
|
94
|
-
<cite className='text-sm font-medium not-italic'>{testimonial.name}</cite>
|
|
95
|
-
<span className='text-muted-foreground block text-sm'>{testimonial.role}</span>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</blockquote>
|
|
99
|
-
</CardContent>
|
|
100
|
-
</Card>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
</section>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export default Testimonial2
|