@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,342 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { ArrowRight, Clock, Heart, Minus, Plus, ShoppingBag, Trash2 } from 'lucide-react'
|
|
4
|
-
import type { LucideIcon } from 'lucide-react'
|
|
5
|
-
import { Button } from '../../../ui/button'
|
|
6
|
-
import { CheckboxCheck } from '../checkout-forms/checkbox-check'
|
|
7
|
-
import { Separator } from '../../../ui/separator'
|
|
8
|
-
import { Badge } from '../../../ui/badge'
|
|
9
|
-
import { Progress } from '../../../ui/progress'
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
|
|
11
|
-
import { ShoppingCart3SpinningCounter } from './shopping-cart-3-spinning-counter'
|
|
12
|
-
import { cn } from '../../../lib/utils'
|
|
13
|
-
|
|
14
|
-
export type ShoppingCart3Tag = 'Limited Edition' | 'Best Seller' | 'New Arrival'
|
|
15
|
-
|
|
16
|
-
export interface ShoppingCart3CartItem {
|
|
17
|
-
id: string
|
|
18
|
-
name: string
|
|
19
|
-
color: string
|
|
20
|
-
size: string
|
|
21
|
-
price: number
|
|
22
|
-
originalPrice: number
|
|
23
|
-
quantity: number
|
|
24
|
-
image: string
|
|
25
|
-
selected: boolean
|
|
26
|
-
tag: ShoppingCart3Tag
|
|
27
|
-
estimatedDelivery: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ShoppingCart3Feature {
|
|
31
|
-
icon: LucideIcon
|
|
32
|
-
title: string
|
|
33
|
-
description: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface ShoppingCart3Props {
|
|
37
|
-
items: ShoppingCart3CartItem[]
|
|
38
|
-
freeShippingThreshold?: number
|
|
39
|
-
features?: ShoppingCart3Feature[]
|
|
40
|
-
title?: string
|
|
41
|
-
itemCountLabel?: (count: number) => string
|
|
42
|
-
selectAllLabel?: string
|
|
43
|
-
saveForLaterLabel?: string
|
|
44
|
-
removeItemLabel?: (name: string) => string
|
|
45
|
-
decreaseQuantityLabel?: (name: string) => string
|
|
46
|
-
increaseQuantityLabel?: (name: string) => string
|
|
47
|
-
estimatedDeliveryLabel?: string
|
|
48
|
-
orderSummaryLabel?: string
|
|
49
|
-
subtotalLabel?: string
|
|
50
|
-
shippingLabel?: string
|
|
51
|
-
freeShippingLabel?: string
|
|
52
|
-
youSaveLabel?: string
|
|
53
|
-
totalLabel?: string
|
|
54
|
-
taxNoteLabel?: string
|
|
55
|
-
checkoutLabel?: string
|
|
56
|
-
securePaymentLabel?: string
|
|
57
|
-
freeShippingProgressLabel?: (remaining: number) => string
|
|
58
|
-
freeShippingReachedLabel?: string
|
|
59
|
-
tagLabels?: Partial<Record<ShoppingCart3Tag, string>>
|
|
60
|
-
onUpdateQuantity?: (id: string, quantity: number) => void
|
|
61
|
-
onRemoveItem?: (id: string) => void
|
|
62
|
-
onCheckout?: () => void
|
|
63
|
-
onSaveForLater?: (ids: string[]) => void
|
|
64
|
-
onToggleSelect?: (id: string, selected: boolean) => void
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const DEFAULT_TAG_LABELS: Record<ShoppingCart3Tag, string> = {
|
|
68
|
-
'Limited Edition': 'Edición limitada',
|
|
69
|
-
'Best Seller': 'Más vendido',
|
|
70
|
-
'New Arrival': 'Nuevo ingreso',
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function ShoppingCart3({
|
|
74
|
-
items,
|
|
75
|
-
freeShippingThreshold,
|
|
76
|
-
features,
|
|
77
|
-
title = 'Tu carrito de compras',
|
|
78
|
-
itemCountLabel = (count) => `${count} ${count === 1 ? 'artículo' : 'artículos'} en tu carrito`,
|
|
79
|
-
selectAllLabel = 'Seleccionar todo',
|
|
80
|
-
saveForLaterLabel = 'Guardar para después',
|
|
81
|
-
removeItemLabel = (name) => `Quitar ${name}`,
|
|
82
|
-
decreaseQuantityLabel = (name) => `Disminuir cantidad de ${name}`,
|
|
83
|
-
increaseQuantityLabel = (name) => `Aumentar cantidad de ${name}`,
|
|
84
|
-
estimatedDeliveryLabel = 'Entrega estimada',
|
|
85
|
-
orderSummaryLabel = 'Resumen del pedido',
|
|
86
|
-
subtotalLabel = 'Subtotal',
|
|
87
|
-
shippingLabel = 'Envío',
|
|
88
|
-
freeShippingLabel = 'Gratis',
|
|
89
|
-
youSaveLabel = 'Ahorras',
|
|
90
|
-
totalLabel = 'Total',
|
|
91
|
-
taxNoteLabel = 'incluye IVA si corresponde',
|
|
92
|
-
checkoutLabel = 'Finalizar compra',
|
|
93
|
-
securePaymentLabel = 'Pago seguro con cifrado SSL',
|
|
94
|
-
freeShippingProgressLabel = (remaining) =>
|
|
95
|
-
`Agrega $${remaining.toFixed(2)} más para obtener envío gratis`,
|
|
96
|
-
freeShippingReachedLabel = '¡Ahorra con envío gratis!',
|
|
97
|
-
tagLabels,
|
|
98
|
-
onUpdateQuantity,
|
|
99
|
-
onRemoveItem,
|
|
100
|
-
onCheckout,
|
|
101
|
-
onSaveForLater,
|
|
102
|
-
onToggleSelect,
|
|
103
|
-
}: ShoppingCart3Props) {
|
|
104
|
-
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
105
|
-
const savings = items.reduce((sum, item) => sum + (item.originalPrice - item.price) * item.quantity, 0)
|
|
106
|
-
const shippingTotal: number = 0
|
|
107
|
-
const total = subtotal + shippingTotal
|
|
108
|
-
|
|
109
|
-
const allSelected = items.length > 0 && items.every((item) => item.selected)
|
|
110
|
-
const selectedIds = items.filter((item) => item.selected).map((item) => item.id)
|
|
111
|
-
const resolveTag = (tag: ShoppingCart3Tag) => (tagLabels ? (tagLabels[tag] ?? tag) : tag)
|
|
112
|
-
const freeShippingProgress =
|
|
113
|
-
freeShippingThreshold != null ? Math.min(100, (subtotal / freeShippingThreshold) * 100) : 0
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<section className='bg-background py-8'>
|
|
117
|
-
<div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
118
|
-
<header className='mb-6 text-center'>
|
|
119
|
-
<h1 className='text-2xl font-semibold text-balance md:text-3xl'>{title}</h1>
|
|
120
|
-
<p className='text-muted-foreground mt-1 text-sm'>{itemCountLabel(items.length)}</p>
|
|
121
|
-
</header>
|
|
122
|
-
|
|
123
|
-
<div className='flex flex-col gap-6 lg:flex-row lg:gap-8'>
|
|
124
|
-
{/* Cart Items Section */}
|
|
125
|
-
<section className='flex-1 flex flex-col gap-4'>
|
|
126
|
-
<div className='flex min-h-8 items-center justify-between px-4'>
|
|
127
|
-
<div className='flex items-center gap-2'>
|
|
128
|
-
<CheckboxCheck
|
|
129
|
-
checked={allSelected}
|
|
130
|
-
onCheckedChange={() => items.forEach((item) => onToggleSelect?.(item.id, !allSelected))}
|
|
131
|
-
className='size-4'
|
|
132
|
-
disabled={!onToggleSelect}
|
|
133
|
-
id='select-all'
|
|
134
|
-
/>
|
|
135
|
-
<label htmlFor='select-all' className='text-sm font-medium'>
|
|
136
|
-
{selectAllLabel}
|
|
137
|
-
</label>
|
|
138
|
-
</div>
|
|
139
|
-
<Button
|
|
140
|
-
variant='ghost'
|
|
141
|
-
size='sm'
|
|
142
|
-
className='h-8 cursor-pointer text-sm'
|
|
143
|
-
disabled={selectedIds.length === 0}
|
|
144
|
-
onClick={onSaveForLater ? () => onSaveForLater(selectedIds) : undefined}
|
|
145
|
-
>
|
|
146
|
-
<Heart data-icon='inline-start' />
|
|
147
|
-
{saveForLaterLabel}
|
|
148
|
-
</Button>
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
<div className='flex flex-col gap-4'>
|
|
152
|
-
{items.map((item) => (
|
|
153
|
-
<div
|
|
154
|
-
key={item.id}
|
|
155
|
-
className='rounded-lg transition-all'
|
|
156
|
-
onClick={onToggleSelect ? () => onToggleSelect(item.id, !item.selected) : undefined}
|
|
157
|
-
>
|
|
158
|
-
<Card className={cn('overflow-hidden transition-all', item.selected ? 'shadow-md' : 'shadow-none')}>
|
|
159
|
-
<CardContent className='flex items-start gap-4' onClick={(e) => e.stopPropagation()}>
|
|
160
|
-
<div onClick={(e) => e.stopPropagation()}>
|
|
161
|
-
<CheckboxCheck
|
|
162
|
-
checked={item.selected}
|
|
163
|
-
onCheckedChange={() => onToggleSelect?.(item.id, !item.selected)}
|
|
164
|
-
className='mt-1 size-4'
|
|
165
|
-
disabled={!onToggleSelect}
|
|
166
|
-
/>
|
|
167
|
-
</div>
|
|
168
|
-
|
|
169
|
-
<div className='flex flex-1 gap-4'>
|
|
170
|
-
<div className='relative aspect-square size-20 overflow-hidden rounded-md border sm:size-24'>
|
|
171
|
-
<img src={item.image || undefined} alt={item.name} className='size-full object-cover' />
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
<div className='flex flex-1 flex-col'>
|
|
175
|
-
<div className='flex items-start justify-between'>
|
|
176
|
-
<div>
|
|
177
|
-
<CardTitle className='text-base text-balance'>{item.name}</CardTitle>
|
|
178
|
-
<div className='text-muted-foreground mt-1 flex gap-2 text-xs'>
|
|
179
|
-
<span>{item.color}</span>
|
|
180
|
-
{item.size ? (
|
|
181
|
-
<>
|
|
182
|
-
<span>•</span>
|
|
183
|
-
<span>{item.size}</span>
|
|
184
|
-
</>
|
|
185
|
-
) : null}
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
<Badge variant='secondary' className='h-5.5 rounded-sm px-2.5 text-xs font-semibold'>
|
|
189
|
-
{resolveTag(item.tag)}
|
|
190
|
-
</Badge>
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
<div className='mt-2 flex items-center gap-2'>
|
|
194
|
-
<span className='text-base font-medium'>${item.price.toFixed(2)}</span>
|
|
195
|
-
{item.originalPrice > item.price ? (
|
|
196
|
-
<span className='text-muted-foreground text-sm line-through'>
|
|
197
|
-
${item.originalPrice.toFixed(2)}
|
|
198
|
-
</span>
|
|
199
|
-
) : null}
|
|
200
|
-
</div>
|
|
201
|
-
<div className='mt-2 flex flex-wrap items-end justify-between gap-2'>
|
|
202
|
-
<p className='text-muted-foreground text-xs'>
|
|
203
|
-
<Clock className='me-1 inline-block size-3' />
|
|
204
|
-
{estimatedDeliveryLabel}: {item.estimatedDelivery}
|
|
205
|
-
</p>
|
|
206
|
-
|
|
207
|
-
<div className='ms-auto flex items-center justify-end gap-4 max-md:ms-0'>
|
|
208
|
-
<div className='flex items-center gap-2'>
|
|
209
|
-
<Button
|
|
210
|
-
variant='outline'
|
|
211
|
-
size='icon'
|
|
212
|
-
className='size-8 cursor-pointer rounded'
|
|
213
|
-
onClick={
|
|
214
|
-
onUpdateQuantity
|
|
215
|
-
? () => onUpdateQuantity(item.id, Math.max(1, item.quantity - 1))
|
|
216
|
-
: undefined
|
|
217
|
-
}
|
|
218
|
-
disabled={item.quantity <= 1}
|
|
219
|
-
aria-label={decreaseQuantityLabel(item.name)}
|
|
220
|
-
>
|
|
221
|
-
<Minus />
|
|
222
|
-
</Button>
|
|
223
|
-
<span className='w-6 text-center text-sm'>{item.quantity}</span>
|
|
224
|
-
<Button
|
|
225
|
-
variant='outline'
|
|
226
|
-
size='icon'
|
|
227
|
-
className='size-8 cursor-pointer rounded'
|
|
228
|
-
onClick={
|
|
229
|
-
onUpdateQuantity
|
|
230
|
-
? () => onUpdateQuantity(item.id, item.quantity + 1)
|
|
231
|
-
: undefined
|
|
232
|
-
}
|
|
233
|
-
aria-label={increaseQuantityLabel(item.name)}
|
|
234
|
-
>
|
|
235
|
-
<Plus />
|
|
236
|
-
</Button>
|
|
237
|
-
</div>
|
|
238
|
-
<Button
|
|
239
|
-
variant='ghost'
|
|
240
|
-
size='icon'
|
|
241
|
-
className='size-8 cursor-pointer rounded-sm text-muted-foreground hover:bg-destructive/10 hover:text-destructive'
|
|
242
|
-
onClick={onRemoveItem ? () => onRemoveItem(item.id) : undefined}
|
|
243
|
-
aria-label={removeItemLabel(item.name)}
|
|
244
|
-
>
|
|
245
|
-
<Trash2 />
|
|
246
|
-
</Button>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
</CardContent>
|
|
252
|
-
</Card>
|
|
253
|
-
</div>
|
|
254
|
-
))}
|
|
255
|
-
</div>
|
|
256
|
-
</section>
|
|
257
|
-
|
|
258
|
-
{/* Summary Section */}
|
|
259
|
-
<aside className='w-full lg:w-[380px]'>
|
|
260
|
-
<div className='sticky top-8 flex flex-col gap-4'>
|
|
261
|
-
{freeShippingThreshold != null ? (
|
|
262
|
-
<Card className='py-4'>
|
|
263
|
-
<CardContent className='px-4'>
|
|
264
|
-
{subtotal < freeShippingThreshold ? (
|
|
265
|
-
<p className='text-xs'>{freeShippingProgressLabel(freeShippingThreshold - subtotal)}</p>
|
|
266
|
-
) : (
|
|
267
|
-
<p className='text-xs font-medium'>{freeShippingReachedLabel}</p>
|
|
268
|
-
)}
|
|
269
|
-
<Progress value={subtotal < freeShippingThreshold ? freeShippingProgress : 100} className='mt-2 h-1.5' />
|
|
270
|
-
</CardContent>
|
|
271
|
-
</Card>
|
|
272
|
-
) : null}
|
|
273
|
-
|
|
274
|
-
<Card className='gap-4 py-4'>
|
|
275
|
-
<CardHeader className='px-4'>
|
|
276
|
-
<CardTitle className='text-lg'>{orderSummaryLabel}</CardTitle>
|
|
277
|
-
</CardHeader>
|
|
278
|
-
<CardContent className='flex flex-col gap-3 px-4'>
|
|
279
|
-
<div className='flex justify-between text-sm'>
|
|
280
|
-
<span className='text-muted-foreground'>{subtotalLabel}</span>
|
|
281
|
-
<span className='font-medium'>${subtotal.toFixed(2)}</span>
|
|
282
|
-
</div>
|
|
283
|
-
<div className='flex justify-between text-sm'>
|
|
284
|
-
<span className='text-muted-foreground'>{shippingLabel}</span>
|
|
285
|
-
<span className='font-medium'>
|
|
286
|
-
{shippingTotal === 0 ? freeShippingLabel : `$${shippingTotal.toFixed(2)}`}
|
|
287
|
-
</span>
|
|
288
|
-
</div>
|
|
289
|
-
{savings > 0 && (
|
|
290
|
-
<div className='flex justify-between text-sm'>
|
|
291
|
-
<span>{youSaveLabel}</span>
|
|
292
|
-
<span className='font-medium'>-${savings.toFixed(2)}</span>
|
|
293
|
-
</div>
|
|
294
|
-
)}
|
|
295
|
-
<Separator />
|
|
296
|
-
<div className='flex justify-between text-base font-medium'>
|
|
297
|
-
<span>{totalLabel}</span>
|
|
298
|
-
<span>
|
|
299
|
-
<ShoppingCart3SpinningCounter value={total} decimals={2} prefix='$' />
|
|
300
|
-
</span>
|
|
301
|
-
</div>
|
|
302
|
-
|
|
303
|
-
<Button
|
|
304
|
-
className='mt-4 h-8 w-full cursor-pointer px-3 text-xs'
|
|
305
|
-
size='sm'
|
|
306
|
-
onClick={onCheckout}
|
|
307
|
-
>
|
|
308
|
-
<ShoppingBag data-icon='inline-start' />
|
|
309
|
-
{checkoutLabel}
|
|
310
|
-
<ArrowRight data-icon='inline-end' />
|
|
311
|
-
</Button>
|
|
312
|
-
|
|
313
|
-
<p className='text-muted-foreground text-center text-xs'>{securePaymentLabel}</p>
|
|
314
|
-
</CardContent>
|
|
315
|
-
</Card>
|
|
316
|
-
|
|
317
|
-
{features && features.length > 0 ? (
|
|
318
|
-
<div className='grid grid-cols-1 gap-3'>
|
|
319
|
-
{features.map((feature, index) => (
|
|
320
|
-
<Card key={index} className='py-3'>
|
|
321
|
-
<CardContent className='flex items-center gap-3 px-3'>
|
|
322
|
-
<div className='bg-secondary rounded-full p-1.5'>
|
|
323
|
-
<feature.icon className='size-4' />
|
|
324
|
-
</div>
|
|
325
|
-
<div>
|
|
326
|
-
<h3 className='text-sm font-medium'>{feature.title}</h3>
|
|
327
|
-
<p className='text-muted-foreground text-xs'>{feature.description}</p>
|
|
328
|
-
</div>
|
|
329
|
-
</CardContent>
|
|
330
|
-
</Card>
|
|
331
|
-
))}
|
|
332
|
-
</div>
|
|
333
|
-
) : null}
|
|
334
|
-
</div>
|
|
335
|
-
</aside>
|
|
336
|
-
</div>
|
|
337
|
-
</div>
|
|
338
|
-
</section>
|
|
339
|
-
)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
export default ShoppingCart3
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef } from 'react'
|
|
4
|
-
|
|
5
|
-
import { cn } from '../../../lib/utils'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Spinning counter: each digit is a clipped vertical reel of 0-9 cells that
|
|
9
|
-
* spins forward and lands on the target digit. Adapted from Transitions.dev
|
|
10
|
-
* "Spinning counter" (26-spinning-counter.md), re-tuned for cart totals:
|
|
11
|
-
* one loop instead of several, and a snappier duration so rapid +/- clicks
|
|
12
|
-
* don't queue up sluggish animations. The spec's directional SVG blur is
|
|
13
|
-
* approximated here with a CSS `filter: blur()` transition for simplicity.
|
|
14
|
-
*
|
|
15
|
-
* Non-digit characters (currency symbol, decimal point, thousands separator)
|
|
16
|
-
* render as static text so only the digits spin.
|
|
17
|
-
*/
|
|
18
|
-
const CELL_SIZE = 20 // px
|
|
19
|
-
const DURATION = 450 // ms
|
|
20
|
-
const STAGGER = 60 // ms per column
|
|
21
|
-
const SPINS = 1 // extra full loops before landing
|
|
22
|
-
const SPIN_BLUR = 2 // px
|
|
23
|
-
|
|
24
|
-
const REEL_DIGITS = Array.from({ length: (SPINS + 1) * 10 }, (_, i) => i % 10)
|
|
25
|
-
|
|
26
|
-
type ShoppingCart4SpinningCounterProps = {
|
|
27
|
-
value: number
|
|
28
|
-
/** Fixed decimal places to format the value with, e.g. 2 for currency. */
|
|
29
|
-
decimals?: number
|
|
30
|
-
/** Static text rendered before the formatted value, e.g. '$'. */
|
|
31
|
-
prefix?: string
|
|
32
|
-
className?: string
|
|
33
|
-
digitClassName?: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function ShoppingCart4SpinningCounter({ value, decimals = 0, prefix, className, digitClassName }: ShoppingCart4SpinningCounterProps) {
|
|
37
|
-
const safeValue = Math.max(0, value)
|
|
38
|
-
const formatted = safeValue.toFixed(decimals)
|
|
39
|
-
const chars = formatted.split('')
|
|
40
|
-
const containerRef = useRef<HTMLSpanElement>(null)
|
|
41
|
-
const isFirstRender = useRef(true)
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const container = containerRef.current
|
|
45
|
-
if (!container) return
|
|
46
|
-
|
|
47
|
-
const strips = Array.from(container.querySelectorAll<HTMLElement>('[data-reel-strip]'))
|
|
48
|
-
const timeouts: number[] = []
|
|
49
|
-
|
|
50
|
-
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
51
|
-
|
|
52
|
-
strips.forEach((strip, index) => {
|
|
53
|
-
const digit = Number(strip.dataset.digit)
|
|
54
|
-
const delay = (strips.length - 1 - index) * STAGGER
|
|
55
|
-
|
|
56
|
-
if (isFirstRender.current || !strip.dataset.initialized || prefersReducedMotion) {
|
|
57
|
-
strip.style.transition = 'none'
|
|
58
|
-
strip.style.transform = `translateY(-${digit * CELL_SIZE}px)`
|
|
59
|
-
strip.style.filter = 'blur(0px)'
|
|
60
|
-
strip.dataset.initialized = 'true'
|
|
61
|
-
void strip.offsetHeight
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const landingIndex = SPINS * 10 + digit
|
|
66
|
-
strip.style.transition = `transform ${DURATION}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms, filter ${DURATION}ms ease-out ${delay}ms`
|
|
67
|
-
strip.style.transform = `translateY(-${landingIndex * CELL_SIZE}px)`
|
|
68
|
-
strip.style.filter = `blur(${SPIN_BLUR}px)`
|
|
69
|
-
|
|
70
|
-
const settleTimeout = window.setTimeout(() => {
|
|
71
|
-
strip.style.filter = 'blur(0px)'
|
|
72
|
-
}, delay + 30)
|
|
73
|
-
|
|
74
|
-
const resetTimeout = window.setTimeout(() => {
|
|
75
|
-
strip.style.transition = 'none'
|
|
76
|
-
strip.style.transform = `translateY(-${digit * CELL_SIZE}px)`
|
|
77
|
-
void strip.offsetHeight
|
|
78
|
-
}, delay + DURATION + 30)
|
|
79
|
-
|
|
80
|
-
timeouts.push(settleTimeout, resetTimeout)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
isFirstRender.current = false
|
|
84
|
-
|
|
85
|
-
return () => {
|
|
86
|
-
timeouts.forEach(window.clearTimeout)
|
|
87
|
-
}
|
|
88
|
-
}, [formatted])
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<span className='relative inline-flex items-center'>
|
|
92
|
-
<span className='sr-only'>{prefix}{formatted}</span>
|
|
93
|
-
<span
|
|
94
|
-
ref={containerRef}
|
|
95
|
-
className={cn('inline-flex items-center font-[inherit] tabular-nums', className)}
|
|
96
|
-
style={{ height: CELL_SIZE }}
|
|
97
|
-
aria-hidden='true'
|
|
98
|
-
>
|
|
99
|
-
{prefix ? (
|
|
100
|
-
<span className={digitClassName} style={{ display: 'inline-flex', alignItems: 'center', height: CELL_SIZE }}>
|
|
101
|
-
{prefix}
|
|
102
|
-
</span>
|
|
103
|
-
) : null}
|
|
104
|
-
{chars.map((char, index) => {
|
|
105
|
-
if (!/\d/.test(char)) {
|
|
106
|
-
return (
|
|
107
|
-
<span
|
|
108
|
-
key={index}
|
|
109
|
-
className={digitClassName}
|
|
110
|
-
style={{ display: 'inline-flex', alignItems: 'center', height: CELL_SIZE }}
|
|
111
|
-
>
|
|
112
|
-
{char}
|
|
113
|
-
</span>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<span
|
|
119
|
-
key={index}
|
|
120
|
-
className='relative inline-block overflow-hidden'
|
|
121
|
-
style={{
|
|
122
|
-
height: CELL_SIZE,
|
|
123
|
-
maskImage: 'linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%)',
|
|
124
|
-
WebkitMaskImage: 'linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%)',
|
|
125
|
-
}}
|
|
126
|
-
>
|
|
127
|
-
<span data-reel-strip data-digit={char} className='flex flex-col will-change-[transform,filter]'>
|
|
128
|
-
{REEL_DIGITS.map((cellDigit, cellIndex) => (
|
|
129
|
-
<span
|
|
130
|
-
key={cellIndex}
|
|
131
|
-
className={cn('flex items-center justify-center', digitClassName)}
|
|
132
|
-
style={{ height: CELL_SIZE }}
|
|
133
|
-
>
|
|
134
|
-
{cellDigit}
|
|
135
|
-
</span>
|
|
136
|
-
))}
|
|
137
|
-
</span>
|
|
138
|
-
</span>
|
|
139
|
-
)
|
|
140
|
-
})}
|
|
141
|
-
</span>
|
|
142
|
-
</span>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export default ShoppingCart4SpinningCounter
|