@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,142 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef } from 'react'
|
|
4
|
-
|
|
5
|
-
import { cn } from '../../../lib/utils'
|
|
6
|
-
|
|
7
|
-
const CELL_SIZE = 20
|
|
8
|
-
const DURATION = 450
|
|
9
|
-
const STAGGER = 60
|
|
10
|
-
const SPINS = 1
|
|
11
|
-
const SPIN_BLUR = 2
|
|
12
|
-
|
|
13
|
-
const REEL_DIGITS = Array.from({ length: (SPINS + 1) * 10 }, (_, i) => i % 10)
|
|
14
|
-
|
|
15
|
-
type ShoppingCart1SpinningCounterProps = {
|
|
16
|
-
value: number
|
|
17
|
-
decimals?: number
|
|
18
|
-
prefix?: string
|
|
19
|
-
className?: string
|
|
20
|
-
digitClassName?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function ShoppingCart1SpinningCounter({
|
|
24
|
-
value,
|
|
25
|
-
decimals = 0,
|
|
26
|
-
prefix,
|
|
27
|
-
className,
|
|
28
|
-
digitClassName,
|
|
29
|
-
}: ShoppingCart1SpinningCounterProps) {
|
|
30
|
-
const safeValue = Math.max(0, value)
|
|
31
|
-
const formatted = safeValue.toFixed(decimals)
|
|
32
|
-
const chars = formatted.split('')
|
|
33
|
-
const containerRef = useRef<HTMLSpanElement>(null)
|
|
34
|
-
const isFirstRender = useRef(true)
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const container = containerRef.current
|
|
38
|
-
if (!container) return
|
|
39
|
-
|
|
40
|
-
const strips = Array.from(container.querySelectorAll<HTMLElement>('[data-reel-strip]'))
|
|
41
|
-
const timeouts: number[] = []
|
|
42
|
-
|
|
43
|
-
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
44
|
-
|
|
45
|
-
strips.forEach((strip, index) => {
|
|
46
|
-
const digit = Number(strip.dataset.digit)
|
|
47
|
-
const delay = (strips.length - 1 - index) * STAGGER
|
|
48
|
-
|
|
49
|
-
if (isFirstRender.current || !strip.dataset.initialized || prefersReducedMotion) {
|
|
50
|
-
strip.style.transition = 'none'
|
|
51
|
-
strip.style.transform = `translateY(-${digit * CELL_SIZE}px)`
|
|
52
|
-
strip.style.filter = 'blur(0px)'
|
|
53
|
-
strip.dataset.initialized = 'true'
|
|
54
|
-
void strip.offsetHeight
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const landingIndex = SPINS * 10 + digit
|
|
59
|
-
strip.style.transition = `transform ${DURATION}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms, filter ${DURATION}ms ease-out ${delay}ms`
|
|
60
|
-
strip.style.transform = `translateY(-${landingIndex * CELL_SIZE}px)`
|
|
61
|
-
strip.style.filter = `blur(${SPIN_BLUR}px)`
|
|
62
|
-
|
|
63
|
-
const settleTimeout = window.setTimeout(() => {
|
|
64
|
-
strip.style.filter = 'blur(0px)'
|
|
65
|
-
}, delay + 30)
|
|
66
|
-
|
|
67
|
-
const resetTimeout = window.setTimeout(() => {
|
|
68
|
-
strip.style.transition = 'none'
|
|
69
|
-
strip.style.transform = `translateY(-${digit * CELL_SIZE}px)`
|
|
70
|
-
void strip.offsetHeight
|
|
71
|
-
}, delay + DURATION + 30)
|
|
72
|
-
|
|
73
|
-
timeouts.push(settleTimeout, resetTimeout)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
isFirstRender.current = false
|
|
77
|
-
|
|
78
|
-
return () => {
|
|
79
|
-
timeouts.forEach(window.clearTimeout)
|
|
80
|
-
}
|
|
81
|
-
}, [formatted])
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<span className='relative inline-flex items-center'>
|
|
85
|
-
<span className='sr-only'>{prefix}{formatted}</span>
|
|
86
|
-
<span
|
|
87
|
-
ref={containerRef}
|
|
88
|
-
className={cn('inline-flex items-center font-[inherit] tabular-nums', className)}
|
|
89
|
-
style={{ height: CELL_SIZE }}
|
|
90
|
-
aria-hidden='true'
|
|
91
|
-
>
|
|
92
|
-
{prefix ? (
|
|
93
|
-
<span
|
|
94
|
-
className={digitClassName}
|
|
95
|
-
style={{ display: 'inline-flex', alignItems: 'center', height: CELL_SIZE }}
|
|
96
|
-
>
|
|
97
|
-
{prefix}
|
|
98
|
-
</span>
|
|
99
|
-
) : null}
|
|
100
|
-
{chars.map((char, index) => {
|
|
101
|
-
if (!/\d/.test(char)) {
|
|
102
|
-
return (
|
|
103
|
-
<span
|
|
104
|
-
key={index}
|
|
105
|
-
className={digitClassName}
|
|
106
|
-
style={{ display: 'inline-flex', alignItems: 'center', height: CELL_SIZE }}
|
|
107
|
-
>
|
|
108
|
-
{char}
|
|
109
|
-
</span>
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<span
|
|
115
|
-
key={index}
|
|
116
|
-
className='relative inline-block overflow-hidden'
|
|
117
|
-
style={{
|
|
118
|
-
height: CELL_SIZE,
|
|
119
|
-
maskImage: 'linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%)',
|
|
120
|
-
WebkitMaskImage: 'linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%)',
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
<span data-reel-strip data-digit={char} className='flex flex-col will-change-[transform,filter]'>
|
|
124
|
-
{REEL_DIGITS.map((cellDigit, cellIndex) => (
|
|
125
|
-
<span
|
|
126
|
-
key={cellIndex}
|
|
127
|
-
className={cn('flex items-center justify-center', digitClassName)}
|
|
128
|
-
style={{ height: CELL_SIZE }}
|
|
129
|
-
>
|
|
130
|
-
{cellDigit}
|
|
131
|
-
</span>
|
|
132
|
-
))}
|
|
133
|
-
</span>
|
|
134
|
-
</span>
|
|
135
|
-
)
|
|
136
|
-
})}
|
|
137
|
-
</span>
|
|
138
|
-
</span>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export default ShoppingCart1SpinningCounter
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { Minus, MoveRight, Package, Plus, Shield, ShoppingBag, Store, Trash2, CreditCard } from 'lucide-react'
|
|
2
|
-
import { Button } from '../../../ui/button'
|
|
3
|
-
import { Separator } from '../../../ui/separator'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '../../../ui/card'
|
|
5
|
-
import { ShoppingCart1SpinningCounter } from './shopping-cart-1-spinning-counter'
|
|
6
|
-
import { cn } from '../../../lib/utils'
|
|
7
|
-
|
|
8
|
-
export interface ShoppingCart1CartItem {
|
|
9
|
-
id: string
|
|
10
|
-
name: string
|
|
11
|
-
color: string
|
|
12
|
-
size: string
|
|
13
|
-
price: number
|
|
14
|
-
originalPrice: number
|
|
15
|
-
quantity: number
|
|
16
|
-
image: string
|
|
17
|
-
estimatedDelivery: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ShoppingCart1Shipping {
|
|
21
|
-
freeThreshold?: number
|
|
22
|
-
cost?: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ShoppingCart1Props {
|
|
26
|
-
items: ShoppingCart1CartItem[]
|
|
27
|
-
shipping?: ShoppingCart1Shipping
|
|
28
|
-
title?: string
|
|
29
|
-
itemCountLabel?: (count: number) => string
|
|
30
|
-
emptyTitle?: string
|
|
31
|
-
emptyDescription?: string
|
|
32
|
-
continueShoppingLabel?: string
|
|
33
|
-
removeItemLabel?: (name: string) => string
|
|
34
|
-
decreaseQuantityLabel?: (name: string) => string
|
|
35
|
-
increaseQuantityLabel?: (name: string) => string
|
|
36
|
-
estimatedDeliveryLabel?: string
|
|
37
|
-
orderSummaryLabel?: string
|
|
38
|
-
subtotalLabel?: string
|
|
39
|
-
shippingLabel?: string
|
|
40
|
-
freeShippingLabel?: string
|
|
41
|
-
youSaveLabel?: string
|
|
42
|
-
totalLabel?: string
|
|
43
|
-
taxNoteLabel?: string
|
|
44
|
-
checkoutLabel?: string
|
|
45
|
-
securePaymentLabel?: string
|
|
46
|
-
secureCheckoutLabel?: string
|
|
47
|
-
secureCheckoutDescription?: string
|
|
48
|
-
onUpdateQuantity?: (id: string, quantity: number) => void
|
|
49
|
-
onRemoveItem?: (id: string) => void
|
|
50
|
-
onCheckout?: () => void
|
|
51
|
-
onContinueShopping?: () => void
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function ShoppingCart1({
|
|
55
|
-
items,
|
|
56
|
-
shipping,
|
|
57
|
-
title = 'Tu carrito de compras',
|
|
58
|
-
itemCountLabel = (count) => `${count} ${count === 1 ? 'artículo' : 'artículos'} en tu carrito`,
|
|
59
|
-
emptyTitle = 'Tu carrito está vacío',
|
|
60
|
-
emptyDescription = 'Agrega productos para empezar',
|
|
61
|
-
continueShoppingLabel = 'Seguir comprando',
|
|
62
|
-
removeItemLabel = (name) => `Quitar ${name}`,
|
|
63
|
-
decreaseQuantityLabel = (name) => `Disminuir cantidad de ${name}`,
|
|
64
|
-
increaseQuantityLabel = (name) => `Aumentar cantidad de ${name}`,
|
|
65
|
-
estimatedDeliveryLabel = 'Entrega estimada',
|
|
66
|
-
orderSummaryLabel = 'Resumen del pedido',
|
|
67
|
-
subtotalLabel = 'Subtotal',
|
|
68
|
-
shippingLabel = 'Envío',
|
|
69
|
-
freeShippingLabel = 'Gratis',
|
|
70
|
-
youSaveLabel = 'Ahorras',
|
|
71
|
-
totalLabel = 'Total',
|
|
72
|
-
taxNoteLabel = 'incluye IVA si corresponde',
|
|
73
|
-
checkoutLabel = 'Finalizar compra',
|
|
74
|
-
securePaymentLabel = 'Pago seguro con cifrado SSL',
|
|
75
|
-
secureCheckoutLabel = 'Pago seguro',
|
|
76
|
-
secureCheckoutDescription = 'Tu información de pago está cifrada y protegida.',
|
|
77
|
-
onUpdateQuantity,
|
|
78
|
-
onRemoveItem,
|
|
79
|
-
onCheckout,
|
|
80
|
-
onContinueShopping,
|
|
81
|
-
}: ShoppingCart1Props) {
|
|
82
|
-
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
83
|
-
const savings = items.reduce((sum, item) => sum + (item.originalPrice - item.price) * item.quantity, 0)
|
|
84
|
-
const freeThreshold = shipping?.freeThreshold ?? Infinity
|
|
85
|
-
const shippingCost = shipping?.cost ?? 0
|
|
86
|
-
const shippingTotal = subtotal >= freeThreshold ? 0 : shippingCost
|
|
87
|
-
const total = subtotal + shippingTotal
|
|
88
|
-
|
|
89
|
-
const changeQuantity = (id: string, quantity: number, delta: number) => {
|
|
90
|
-
onUpdateQuantity?.(id, Math.max(1, quantity + delta))
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<div className='mx-auto w-full max-w-7xl px-4 py-8 sm:px-6 lg:px-8'>
|
|
95
|
-
<div className='mb-8 flex flex-col gap-2 text-center'>
|
|
96
|
-
<h1 className='text-3xl font-bold tracking-tight sm:text-4xl'>{title}</h1>
|
|
97
|
-
<p className='text-muted-foreground'>
|
|
98
|
-
{itemCountLabel(items.length)} •{' '}
|
|
99
|
-
<span className='text-foreground font-semibold'>${subtotal.toFixed(2)}</span>
|
|
100
|
-
</p>
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<div className='flex flex-col gap-8 lg:flex-row'>
|
|
104
|
-
<div className='flex flex-1 flex-col gap-6'>
|
|
105
|
-
{items.length === 0 ? (
|
|
106
|
-
<Card className='border-dashed'>
|
|
107
|
-
<CardContent className='flex flex-col items-center justify-center py-12 text-center'>
|
|
108
|
-
<ShoppingBag className='text-muted-foreground/50 mb-4 size-12' />
|
|
109
|
-
<h3 className='text-lg font-medium'>{emptyTitle}</h3>
|
|
110
|
-
<p className='text-muted-foreground mt-1 text-sm'>{emptyDescription}</p>
|
|
111
|
-
<Button
|
|
112
|
-
className='mt-4 h-9 cursor-pointer px-4 py-2'
|
|
113
|
-
variant='outline'
|
|
114
|
-
onClick={onContinueShopping}
|
|
115
|
-
>
|
|
116
|
-
{continueShoppingLabel}
|
|
117
|
-
</Button>
|
|
118
|
-
</CardContent>
|
|
119
|
-
</Card>
|
|
120
|
-
) : (
|
|
121
|
-
items.map(item => (
|
|
122
|
-
<Card key={item.id} className='gap-0 overflow-hidden py-0'>
|
|
123
|
-
<div className='flex flex-col sm:flex-row'>
|
|
124
|
-
<div className='relative h-auto w-full sm:w-40'>
|
|
125
|
-
<img
|
|
126
|
-
src={item.image || undefined}
|
|
127
|
-
alt={item.name}
|
|
128
|
-
className='h-36 w-full object-cover object-center'
|
|
129
|
-
/>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<div className='flex-1 p-4'>
|
|
133
|
-
<div className='flex items-start justify-between'>
|
|
134
|
-
<div>
|
|
135
|
-
<h3 className='text-foreground text-lg font-medium'>{item.name}</h3>
|
|
136
|
-
<p className='text-muted-foreground mt-1 text-sm'>
|
|
137
|
-
{item.color} {item.size ? `• ${item.size}` : ''}
|
|
138
|
-
</p>
|
|
139
|
-
</div>
|
|
140
|
-
<Button
|
|
141
|
-
variant='ghost'
|
|
142
|
-
size='icon'
|
|
143
|
-
className='size-8 cursor-pointer text-muted-foreground hover:bg-destructive/10 hover:text-destructive'
|
|
144
|
-
onClick={onRemoveItem ? () => onRemoveItem(item.id) : undefined}
|
|
145
|
-
aria-label={removeItemLabel(item.name)}
|
|
146
|
-
>
|
|
147
|
-
<Trash2 />
|
|
148
|
-
</Button>
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
<div className='mt-4 flex items-center justify-between'>
|
|
152
|
-
<div className='flex items-center gap-2'>
|
|
153
|
-
<Button
|
|
154
|
-
variant='outline'
|
|
155
|
-
size='icon'
|
|
156
|
-
className='size-8 cursor-pointer'
|
|
157
|
-
onClick={
|
|
158
|
-
onUpdateQuantity
|
|
159
|
-
? () => changeQuantity(item.id, item.quantity, -1)
|
|
160
|
-
: undefined
|
|
161
|
-
}
|
|
162
|
-
disabled={item.quantity <= 1}
|
|
163
|
-
aria-label={decreaseQuantityLabel(item.name)}
|
|
164
|
-
>
|
|
165
|
-
<Minus />
|
|
166
|
-
</Button>
|
|
167
|
-
<span className='w-8 text-center text-sm font-medium'>{item.quantity}</span>
|
|
168
|
-
<Button
|
|
169
|
-
variant='outline'
|
|
170
|
-
size='icon'
|
|
171
|
-
className='size-8 cursor-pointer'
|
|
172
|
-
onClick={
|
|
173
|
-
onUpdateQuantity
|
|
174
|
-
? () => changeQuantity(item.id, item.quantity, 1)
|
|
175
|
-
: undefined
|
|
176
|
-
}
|
|
177
|
-
aria-label={increaseQuantityLabel(item.name)}
|
|
178
|
-
>
|
|
179
|
-
<Plus />
|
|
180
|
-
</Button>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<div className='text-end'>
|
|
184
|
-
<p className='text-lg font-semibold'>${(item.price * item.quantity).toFixed(2)}</p>
|
|
185
|
-
{item.originalPrice > item.price && (
|
|
186
|
-
<p className='text-muted-foreground text-xs line-through'>
|
|
187
|
-
${item.originalPrice.toFixed(2)}
|
|
188
|
-
</p>
|
|
189
|
-
)}
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<CardFooter className='bg-muted/20 border-t px-4 !py-2'>
|
|
196
|
-
<div className='text-muted-foreground flex items-center text-sm'>
|
|
197
|
-
<Package className='me-2 size-4' />
|
|
198
|
-
<span>{estimatedDeliveryLabel}: {item.estimatedDelivery}</span>
|
|
199
|
-
</div>
|
|
200
|
-
</CardFooter>
|
|
201
|
-
</Card>
|
|
202
|
-
))
|
|
203
|
-
)}
|
|
204
|
-
</div>
|
|
205
|
-
|
|
206
|
-
<div className='flex w-full flex-col gap-4 lg:w-96'>
|
|
207
|
-
<Card className='sticky top-4 gap-0'>
|
|
208
|
-
<CardHeader className='pb-4'>
|
|
209
|
-
<CardTitle className='text-xl'>{orderSummaryLabel}</CardTitle>
|
|
210
|
-
</CardHeader>
|
|
211
|
-
<CardContent className='flex flex-col gap-4'>
|
|
212
|
-
<div className='flex flex-col gap-3'>
|
|
213
|
-
<div className='flex justify-between text-sm'>
|
|
214
|
-
<span className='text-muted-foreground'>{subtotalLabel}</span>
|
|
215
|
-
<span>${subtotal.toFixed(2)}</span>
|
|
216
|
-
</div>
|
|
217
|
-
<div className='flex justify-between text-sm'>
|
|
218
|
-
<span className='text-muted-foreground'>{shippingLabel}</span>
|
|
219
|
-
<span className={cn(shippingTotal === 0 && 'text-success')}>
|
|
220
|
-
{shippingTotal === 0 ? freeShippingLabel : `$${shippingTotal.toFixed(2)}`}
|
|
221
|
-
</span>
|
|
222
|
-
</div>
|
|
223
|
-
{savings > 0 && (
|
|
224
|
-
<div className='flex justify-between text-sm font-medium'>
|
|
225
|
-
<span>{youSaveLabel}</span>
|
|
226
|
-
<span>-${savings.toFixed(2)}</span>
|
|
227
|
-
</div>
|
|
228
|
-
)}
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
|
-
<Separator className='my-2' />
|
|
232
|
-
|
|
233
|
-
<div className='flex items-center justify-between text-base font-medium'>
|
|
234
|
-
<span>{totalLabel}</span>
|
|
235
|
-
<div className='text-end'>
|
|
236
|
-
<p className='text-xl font-bold'>
|
|
237
|
-
<ShoppingCart1SpinningCounter value={total} decimals={2} prefix='$' />
|
|
238
|
-
</p>
|
|
239
|
-
<p className='text-muted-foreground text-xs'>{taxNoteLabel}</p>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
|
|
243
|
-
<Button
|
|
244
|
-
size='lg'
|
|
245
|
-
className='mt-4 h-10 w-full cursor-pointer px-8 text-base font-medium'
|
|
246
|
-
disabled={items.length === 0}
|
|
247
|
-
onClick={onCheckout}
|
|
248
|
-
>
|
|
249
|
-
<ShoppingBag data-icon='inline-start' />
|
|
250
|
-
{checkoutLabel}
|
|
251
|
-
</Button>
|
|
252
|
-
|
|
253
|
-
<div className='text-muted-foreground flex items-center justify-center gap-2 text-xs'>
|
|
254
|
-
<CreditCard className='size-3.5' />
|
|
255
|
-
<span>{securePaymentLabel}</span>
|
|
256
|
-
</div>
|
|
257
|
-
</CardContent>
|
|
258
|
-
</Card>
|
|
259
|
-
|
|
260
|
-
<Card className='border-dashed py-4'>
|
|
261
|
-
<CardContent className='px-4'>
|
|
262
|
-
<div className='flex items-start gap-3'>
|
|
263
|
-
<div className='flex size-10 items-center justify-center rounded-full bg-warning/10 text-warning'>
|
|
264
|
-
<Shield className='size-5' />
|
|
265
|
-
</div>
|
|
266
|
-
<div>
|
|
267
|
-
<h4 className='font-medium'>{secureCheckoutLabel}</h4>
|
|
268
|
-
<p className='text-muted-foreground mt-1 text-xs'>
|
|
269
|
-
{secureCheckoutDescription}
|
|
270
|
-
</p>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
</CardContent>
|
|
274
|
-
</Card>
|
|
275
|
-
|
|
276
|
-
<Button
|
|
277
|
-
variant='outline'
|
|
278
|
-
className='h-9 w-full cursor-pointer px-4 py-2'
|
|
279
|
-
onClick={onContinueShopping}
|
|
280
|
-
>
|
|
281
|
-
<Store data-icon='inline-start' />
|
|
282
|
-
{continueShoppingLabel}
|
|
283
|
-
<MoveRight data-icon='inline-end' />
|
|
284
|
-
</Button>
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export default ShoppingCart1
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { Heart, Minus, Plus, Trash2 } from 'lucide-react'
|
|
2
|
-
import { Badge } from '../../../ui/badge'
|
|
3
|
-
import { Button } from '../../../ui/button'
|
|
4
|
-
import { Separator } from '../../../ui/separator'
|
|
5
|
-
import type { ShoppingCart2Item } from './shopping-cart-2'
|
|
6
|
-
|
|
7
|
-
export interface ShoppingCart2ItemRowProps {
|
|
8
|
-
item: ShoppingCart2Item
|
|
9
|
-
isLast: boolean
|
|
10
|
-
onRemove?: (id: string) => void
|
|
11
|
-
onQuantityChange?: (id: string, quantity: number) => void
|
|
12
|
-
onAddToWishlist?: (item: ShoppingCart2Item) => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function ShoppingCart2ItemRow({
|
|
16
|
-
item,
|
|
17
|
-
isLast,
|
|
18
|
-
onRemove,
|
|
19
|
-
onQuantityChange,
|
|
20
|
-
onAddToWishlist,
|
|
21
|
-
}: ShoppingCart2ItemRowProps) {
|
|
22
|
-
const formatPrice = (value: number) => `$${value.toFixed(2)}`
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div>
|
|
26
|
-
<div className='flex flex-col sm:flex-row sm:items-start gap-4'>
|
|
27
|
-
<img src={item.image || undefined} alt={item.name} className='w-20 h-20 rounded-lg object-cover shrink-0 sm:w-20 sm:h-20' />
|
|
28
|
-
|
|
29
|
-
<div className='flex-1 flex flex-col gap-3 min-w-0'>
|
|
30
|
-
<div className='flex items-start justify-between gap-2'>
|
|
31
|
-
<div className='flex-1 min-w-0'>
|
|
32
|
-
<h3 className='line-clamp-2 font-medium'>{item.name}</h3>
|
|
33
|
-
{item.brand ? <p className='text-muted-foreground text-sm'>{item.brand}</p> : null}
|
|
34
|
-
<div className='text-muted-foreground mt-1 flex gap-3 text-sm flex-wrap'>
|
|
35
|
-
{item.color ? <span className='whitespace-nowrap'>Color: {item.color}</span> : null}
|
|
36
|
-
{item.size ? <span className='whitespace-nowrap'>Talla: {item.size}</span> : null}
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
<Button
|
|
40
|
-
variant='ghost'
|
|
41
|
-
size='sm'
|
|
42
|
-
onClick={onRemove ? () => onRemove(item.id) : undefined}
|
|
43
|
-
className='h-8 shrink-0 px-3 text-xs cursor-pointer text-destructive'
|
|
44
|
-
aria-label={`Quitar ${item.name}`}
|
|
45
|
-
>
|
|
46
|
-
<Trash2 />
|
|
47
|
-
</Button>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2'>
|
|
51
|
-
<div className='flex items-center gap-2 flex-wrap'>
|
|
52
|
-
<span className='font-semibold text-base'>{formatPrice(item.price)}</span>
|
|
53
|
-
{item.originalPrice ? (
|
|
54
|
-
<span className='text-muted-foreground text-sm line-through'>
|
|
55
|
-
{formatPrice(item.originalPrice)}
|
|
56
|
-
</span>
|
|
57
|
-
) : null}
|
|
58
|
-
{item.originalPrice ? (
|
|
59
|
-
<Badge variant='destructive' className='rounded-sm px-2 py-0.5 font-semibold text-xs'>
|
|
60
|
-
Ahorra {formatPrice(item.originalPrice - item.price)}
|
|
61
|
-
</Badge>
|
|
62
|
-
) : null}
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<div className='flex items-center gap-2 justify-between sm:justify-end'>
|
|
66
|
-
<Button
|
|
67
|
-
variant='ghost'
|
|
68
|
-
size='sm'
|
|
69
|
-
className='h-8 cursor-pointer px-2 text-xs'
|
|
70
|
-
onClick={onAddToWishlist ? () => onAddToWishlist(item) : undefined}
|
|
71
|
-
aria-label={`Añadir ${item.name} a la lista de deseos`}
|
|
72
|
-
>
|
|
73
|
-
<Heart className='size-4' />
|
|
74
|
-
</Button>
|
|
75
|
-
<div className='flex items-center rounded-md border'>
|
|
76
|
-
<Button
|
|
77
|
-
variant='ghost'
|
|
78
|
-
size='sm'
|
|
79
|
-
className='h-8 cursor-pointer px-2 text-xs'
|
|
80
|
-
onClick={
|
|
81
|
-
onQuantityChange ? () => onQuantityChange(item.id, item.quantity - 1) : undefined
|
|
82
|
-
}
|
|
83
|
-
disabled={item.quantity <= 1}
|
|
84
|
-
aria-label={`Disminuir cantidad de ${item.name}`}
|
|
85
|
-
>
|
|
86
|
-
<Minus className='size-4' />
|
|
87
|
-
</Button>
|
|
88
|
-
<span className='min-w-[2rem] px-2 py-1 text-center text-sm'>{item.quantity}</span>
|
|
89
|
-
<Button
|
|
90
|
-
variant='ghost'
|
|
91
|
-
size='sm'
|
|
92
|
-
className='h-8 cursor-pointer px-2 text-xs'
|
|
93
|
-
onClick={
|
|
94
|
-
onQuantityChange ? () => onQuantityChange(item.id, item.quantity + 1) : undefined
|
|
95
|
-
}
|
|
96
|
-
aria-label={`Aumentar cantidad de ${item.name}`}
|
|
97
|
-
>
|
|
98
|
-
<Plus className='size-4' />
|
|
99
|
-
</Button>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
{!isLast ? <Separator className='mt-6' /> : null}
|
|
106
|
-
</div>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export default ShoppingCart2ItemRow
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
2
|
-
import { cn } from '../../../lib/utils'
|
|
3
|
-
import type { ShoppingCart2Recommendation } from './shopping-cart-2'
|
|
4
|
-
|
|
5
|
-
export interface ShoppingCart2RecommendationsProps {
|
|
6
|
-
recommendations: ShoppingCart2Recommendation[]
|
|
7
|
-
title?: string
|
|
8
|
-
onRecommendationClick?: (product: ShoppingCart2Recommendation) => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function ShoppingCart2Recommendations({
|
|
12
|
-
recommendations,
|
|
13
|
-
title = 'También te puede gustar',
|
|
14
|
-
onRecommendationClick,
|
|
15
|
-
}: ShoppingCart2RecommendationsProps) {
|
|
16
|
-
if (recommendations.length === 0) return null
|
|
17
|
-
|
|
18
|
-
const formatPrice = (value: number) => `$${value.toFixed(2)}`
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className='mt-12'>
|
|
22
|
-
<h3 className='mb-4 text-lg font-semibold'>{title}</h3>
|
|
23
|
-
<div className='grid gap-4 sm:grid-cols-2 lg:grid-cols-4'>
|
|
24
|
-
{recommendations.map(product => (
|
|
25
|
-
<Card
|
|
26
|
-
key={product.id}
|
|
27
|
-
className={cn(
|
|
28
|
-
'group py-0 transition-shadow',
|
|
29
|
-
onRecommendationClick && 'cursor-pointer hover:shadow-md'
|
|
30
|
-
)}
|
|
31
|
-
onClick={onRecommendationClick ? () => onRecommendationClick(product) : undefined}
|
|
32
|
-
>
|
|
33
|
-
<CardContent className='p-4'>
|
|
34
|
-
<div className='mb-3 aspect-square overflow-hidden'>
|
|
35
|
-
<img
|
|
36
|
-
src={product.image || undefined}
|
|
37
|
-
alt={product.name}
|
|
38
|
-
className='size-full rounded-md object-cover'
|
|
39
|
-
/>
|
|
40
|
-
</div>
|
|
41
|
-
<h4 className='mb-1 line-clamp-2 text-sm font-medium'>{product.name}</h4>
|
|
42
|
-
<p className='text-sm font-semibold'>{formatPrice(product.price)}</p>
|
|
43
|
-
</CardContent>
|
|
44
|
-
</Card>
|
|
45
|
-
))}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export default ShoppingCart2Recommendations
|