@thorprovider/blocks 0.1.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +73 -29
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  7. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  8. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  9. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  10. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  11. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  12. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  13. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  14. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  15. package/src/blocks/e-commerce/product-list/product-card.tsx +424 -0
  16. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  17. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  18. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  19. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  20. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  21. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  22. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  23. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  24. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  25. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  26. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  27. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  28. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  29. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  30. package/src/blocks/marketing/features/feature.tsx +195 -0
  31. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +27 -9
  32. package/src/blocks/marketing/navbars/navbar-4.test.tsx +33 -2
  33. package/src/blocks/marketing/navbars/navbar-4.tsx +29 -11
  34. package/src/index.ts +77 -48
  35. package/src/lib/contracts-curated.test.ts +42 -24
  36. package/src/lib/contracts.test.ts +143 -2
  37. package/src/lib/contracts.ts +346 -14
  38. package/src/ui/button-group.tsx +1 -1
  39. package/src/ui/carousel.tsx +1 -1
  40. package/src/ui/dialog.tsx +1 -1
  41. package/src/ui/field.tsx +2 -2
  42. package/src/ui/input-group.tsx +3 -3
  43. package/src/ui/pagination.tsx +1 -1
  44. package/src/ui/sidebar.tsx +7 -7
  45. package/src/ui/toggle-group.tsx +1 -1
  46. package/tsconfig.build.json +12 -0
  47. package/tsup.config.ts +11 -7
  48. package/.turbo/turbo-build.log +0 -24
  49. package/.turbo/turbo-test.log +0 -47
  50. package/.turbo/turbo-type-check.log +0 -0
  51. package/dist/.tsbuildinfo +0 -1
  52. package/dist/contracts.d.mts +0 -1732
  53. package/dist/contracts.d.ts +0 -1732
  54. package/dist/contracts.js +0 -423
  55. package/dist/contracts.js.map +0 -1
  56. package/dist/contracts.mjs +0 -365
  57. package/dist/contracts.mjs.map +0 -1
  58. package/dist/index.d.mts +0 -3068
  59. package/dist/index.d.ts +0 -3068
  60. package/dist/index.js +0 -17834
  61. package/dist/index.js.map +0 -1
  62. package/dist/index.mjs +0 -17740
  63. package/dist/index.mjs.map +0 -1
  64. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  65. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  66. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  67. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  68. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  69. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  70. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  71. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  72. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  73. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  74. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  75. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  76. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  77. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  78. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  79. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  80. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  81. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  82. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  83. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  84. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  85. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  86. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  87. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  88. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  89. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  90. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  91. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  92. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  93. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  94. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  95. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  96. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  97. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  98. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  99. package/src/blocks/marketing/features/feature-2.tsx +0 -82
@@ -1,59 +0,0 @@
1
- import { Camera } from 'lucide-react'
2
- import { Badge } from '../../../ui/badge'
3
- import { cn } from '../../../lib/utils'
4
-
5
- import type { ProductOverview5Product } from './product-overview-5'
6
-
7
- export interface ProductOverview5GalleryProps {
8
- product: ProductOverview5Product
9
- badgeLabel?: string
10
- selectedImage: number
11
- onSelectImage: (index: number) => void
12
- photosLabel?: string
13
- }
14
-
15
- export function ProductOverview5Gallery({
16
- product,
17
- badgeLabel,
18
- selectedImage,
19
- onSelectImage,
20
- photosLabel = 'fotos',
21
- }: ProductOverview5GalleryProps) {
22
- return (
23
- <div>
24
- <div className='bg-muted relative aspect-square overflow-hidden rounded-xl'>
25
- <img src={product.mainImages[selectedImage] || undefined} alt={product.name} className='size-full object-cover' />
26
- {badgeLabel && (
27
- <Badge className='px-2.5 py-0.5 font-semibold absolute top-4 left-4'>
28
- {badgeLabel}
29
- </Badge>
30
- )}
31
- <div className='absolute right-4 bottom-4 rounded-full bg-black/60 px-3 py-1.5 text-sm text-white backdrop-blur-sm'>
32
- <Camera className='mr-1 inline size-4' />
33
- {product.mainImages.length} {photosLabel}
34
- </div>
35
- </div>
36
-
37
- {product.mainImages.length > 1 && (
38
- <div className='mt-4 flex gap-3 overflow-x-auto pb-2'>
39
- {product.mainImages.map((image, index) => (
40
- <button
41
- type='button'
42
- key={index}
43
- onClick={() => onSelectImage(index)}
44
- className={cn(
45
- 'relative aspect-square size-20 flex-shrink-0 cursor-pointer overflow-hidden rounded-lg border-2 transition-all',
46
- selectedImage === index ? 'border-primary ring-primary/20 shadow-md ring-2' : 'border-muted hover:border-muted-foreground/50',
47
- )}
48
- aria-label={`Mostrar imagen ${index + 1} de ${product.name}`}
49
- >
50
- <img src={image || undefined} alt={`${product.name} ${index + 1}`} className='size-full object-cover' />
51
- </button>
52
- ))}
53
- </div>
54
- )}
55
- </div>
56
- )
57
- }
58
-
59
- export default ProductOverview5Gallery
@@ -1,274 +0,0 @@
1
- 'use client'
2
-
3
- import { useState } from 'react'
4
- import { Camera, Heart, Info, MessageSquare, Package, Share2, ShoppingCart, Star } from 'lucide-react'
5
- import { Card, CardContent } from '../../../ui/card'
6
- import { Button } from '../../../ui/button'
7
- import { Separator } from '../../../ui/separator'
8
- import { ReactionToggle } from '../../shared/reaction-toggle'
9
- import { ProductOverview5Gallery } from './product-overview-5-gallery'
10
- import { ProductOverview5LightboxTab } from './product-overview-5-lightbox-tab'
11
- import { ProductOverview5ReviewsTab } from './product-overview-5-reviews-tab'
12
- import {
13
- ProductOverview5Tabs,
14
- ProductOverview5TabsContentFade,
15
- ProductOverview5TabsIndicator,
16
- ProductOverview5TabsList,
17
- ProductOverview5TabsTrigger,
18
- } from './product-overview-5-tabs'
19
-
20
- export interface ProductOverview5Review {
21
- id: number
22
- author: string
23
- rating: number
24
- comment: string
25
- date: string
26
- helpful: number
27
- }
28
-
29
- export interface ProductOverview5Product {
30
- name: string
31
- price: number
32
- originalPrice: null | number
33
- rating: number
34
- reviewCount: number
35
- sku: string
36
- mainImages: string[]
37
- galleryImages: string[]
38
- description: string
39
- features: string[]
40
- specifications: Record<string, string>
41
- userReviews: ProductOverview5Review[]
42
- }
43
-
44
- export interface ProductOverview5Props {
45
- product: ProductOverview5Product
46
- badgeLabel?: string
47
- availabilityLabel?: string
48
- currencySymbol?: string
49
- skuLabel?: string
50
- overviewLabel?: string
51
- specsLabel?: string
52
- featuresLabel?: string
53
- specificationsLabel?: string
54
- reviewsLabel?: string
55
- galleryLabel?: string
56
- saveLabel?: string
57
- savedLabel?: string
58
- shareLabel?: string
59
- addToCartLabel?: string
60
- basedOnReviewsLabel?: string
61
- foundHelpfulLabel?: string
62
- ofCounterLabel?: string
63
- photosLabel?: string
64
- onAddToCart?: (product: ProductOverview5Product) => void
65
- onToggleWishlist?: (product: ProductOverview5Product, isWishlisted: boolean) => void
66
- onShare?: (product: ProductOverview5Product) => void
67
- }
68
-
69
- function renderStars(rating: number) {
70
- return Array.from({ length: 5 }, (_, i) => (
71
- <Star key={i} className={`size-4 ${i < Math.floor(rating) ? 'fill-foreground text-foreground' : i < rating ? 'text-foreground' : ''}`} />
72
- ))
73
- }
74
-
75
- export default function ProductOverview5({
76
- product,
77
- badgeLabel = 'Destacado',
78
- availabilityLabel = 'Disponible',
79
- currencySymbol = '$',
80
- skuLabel = 'SKU',
81
- overviewLabel = 'Resumen',
82
- specsLabel = 'Especificaciones',
83
- featuresLabel = 'Características clave',
84
- specificationsLabel = 'Especificaciones técnicas',
85
- reviewsLabel = 'Reseñas',
86
- galleryLabel = 'Galería',
87
- saveLabel = 'Guardar',
88
- savedLabel = 'Guardado',
89
- shareLabel = 'Compartir',
90
- addToCartLabel = 'Agregar al carrito',
91
- basedOnReviewsLabel = 'Basado en {count} reseñas',
92
- foundHelpfulLabel = '{count} personas encontraron esto útil',
93
- ofCounterLabel = '{current} de {total}',
94
- photosLabel = 'fotos',
95
- onAddToCart,
96
- onToggleWishlist,
97
- onShare,
98
- }: ProductOverview5Props) {
99
- const [selectedImage, setSelectedImage] = useState(0)
100
- const [isWishlisted, setIsWishlisted] = useState(false)
101
-
102
- return (
103
- <section className='py-12'>
104
- <div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
105
- <div className='grid gap-12 lg:grid-cols-2'>
106
- <ProductOverview5Gallery
107
- product={product}
108
- badgeLabel={badgeLabel}
109
- selectedImage={selectedImage}
110
- onSelectImage={setSelectedImage}
111
- photosLabel={photosLabel}
112
- />
113
-
114
- <div className='flex flex-col gap-8'>
115
- <div>
116
- <h1 className='text-3xl leading-tight font-bold text-balance'>{product.name}</h1>
117
- <div className='mt-4 flex flex-wrap items-center gap-4'>
118
- <div className='flex items-center gap-2'>
119
- <div className='flex'>{renderStars(product.rating)}</div>
120
- <span className='text-sm font-medium'>{product.rating}</span>
121
- </div>
122
- <span className='text-muted-foreground text-sm'>
123
- {product.userReviews.length} {reviewsLabel.toLowerCase()}
124
- </span>
125
- <Separator orientation='vertical' className='h-4' />
126
- <span className='text-muted-foreground text-sm'>
127
- {skuLabel}: {product.sku}
128
- </span>
129
- </div>
130
- </div>
131
-
132
- <div className='flex items-baseline gap-3'>
133
- <span className='text-4xl font-bold'>
134
- {currencySymbol}
135
- {product.price}
136
- </span>
137
- {product.originalPrice && (
138
- <span className='text-muted-foreground text-xl line-through'>
139
- {currencySymbol}
140
- {product.originalPrice}
141
- </span>
142
- )}
143
- </div>
144
-
145
- <div className='inline-flex items-center w-fit gap-2 rounded-full bg-success/10 px-3 py-1.5'>
146
- <div className='size-2 rounded-full bg-success' />
147
- <span className='text-sm font-medium text-success'>{availabilityLabel}</span>
148
- </div>
149
-
150
- <p className='text-muted-foreground leading-relaxed'>{product.description}</p>
151
-
152
- <div className='flex flex-col gap-4'>
153
- <Button
154
- size='lg'
155
- className='h-12 w-full cursor-pointer gap-2'
156
- onClick={onAddToCart ? () => onAddToCart(product) : undefined}
157
- >
158
- <ShoppingCart />
159
- {addToCartLabel}
160
- </Button>
161
- <div className='flex gap-3'>
162
- <Button
163
- variant='outline'
164
- size='lg'
165
- className='h-12 flex-1 cursor-pointer gap-2'
166
- aria-pressed={isWishlisted}
167
- onClick={() => {
168
- const next = !isWishlisted
169
- setIsWishlisted(next)
170
- onToggleWishlist?.(product, next)
171
- }}
172
- >
173
- <ReactionToggle active={isWishlisted} icon={Heart} activeColorClassName='text-destructive' />
174
- {isWishlisted ? savedLabel : saveLabel}
175
- </Button>
176
- <Button
177
- variant='outline'
178
- size='lg'
179
- className='h-12 flex-1 cursor-pointer gap-2'
180
- onClick={onShare ? () => onShare(product) : undefined}
181
- >
182
- <Share2 />
183
- {shareLabel}
184
- </Button>
185
- </div>
186
- </div>
187
- </div>
188
- </div>
189
-
190
- <div className='mt-16'>
191
- <ProductOverview5Tabs defaultValue='overview' className='w-full'>
192
- <ProductOverview5TabsList className='bg-muted/50 relative grid !h-12 w-full grid-cols-4 rounded-xl p-1'>
193
- <ProductOverview5TabsIndicator />
194
- <ProductOverview5TabsTrigger
195
- value='overview'
196
- className='z-10 h-10 gap-2 text-sm font-medium data-active:bg-transparent data-active:shadow-none dark:data-active:bg-transparent'
197
- >
198
- <Info className='size-4 shrink-0' />
199
- <span className='hidden sm:inline'>{overviewLabel}</span>
200
- </ProductOverview5TabsTrigger>
201
- <ProductOverview5TabsTrigger
202
- value='specs'
203
- className='z-10 h-10 gap-2 text-sm font-medium data-active:bg-transparent data-active:shadow-none dark:data-active:bg-transparent'
204
- >
205
- <Package className='size-4 shrink-0' />
206
- <span className='hidden sm:inline'>{specsLabel}</span>
207
- </ProductOverview5TabsTrigger>
208
- <ProductOverview5TabsTrigger
209
- value='reviews'
210
- className='z-10 h-10 gap-2 text-sm font-medium data-active:bg-transparent data-active:shadow-none dark:data-active:bg-transparent'
211
- >
212
- <MessageSquare className='size-4 shrink-0' />
213
- <span className='hidden sm:inline'>
214
- {reviewsLabel} ({product.userReviews.length})
215
- </span>
216
- </ProductOverview5TabsTrigger>
217
- <ProductOverview5TabsTrigger
218
- value='gallery'
219
- className='z-10 h-10 gap-2 text-sm font-medium data-active:bg-transparent data-active:shadow-none dark:data-active:bg-transparent'
220
- >
221
- <Camera className='size-4 shrink-0' />
222
- <span className='hidden sm:inline'>{galleryLabel}</span>
223
- </ProductOverview5TabsTrigger>
224
- </ProductOverview5TabsList>
225
- <ProductOverview5TabsContentFade value='overview' className='mt-8'>
226
- <Card className='border shadow-none'>
227
- <CardContent className='p-8 py-0'>
228
- <h3 className='mb-6 text-xl font-semibold text-balance'>{featuresLabel}</h3>
229
- <div className='grid gap-4 sm:grid-cols-2'>
230
- {product.features.map(feature => (
231
- <div key={feature} className='group flex items-center gap-3'>
232
- <div className='bg-primary/20 size-2 rounded-full transition-colors' />
233
- <span className='text-sm'>{feature}</span>
234
- </div>
235
- ))}
236
- </div>
237
- </CardContent>
238
- </Card>
239
- </ProductOverview5TabsContentFade>
240
- <ProductOverview5TabsContentFade value='specs' className='mt-8'>
241
- <Card className='border shadow-none'>
242
- <CardContent className='p-8 py-0'>
243
- <h3 className='mb-6 text-xl font-semibold text-balance'>{specificationsLabel}</h3>
244
- <div className='flex flex-col gap-4'>
245
- {Object.entries(product.specifications).map(([key, value]) => (
246
- <div key={key} className='border-muted grid grid-cols-1 gap-2 border-b py-3 last:border-b-0 md:grid-cols-3 md:gap-4'>
247
- <span className='text-muted-foreground text-sm font-medium'>{key}</span>
248
- <span className='col-span-1 text-sm md:col-span-2'>{value}</span>
249
- </div>
250
- ))}
251
- </div>
252
- </CardContent>
253
- </Card>
254
- </ProductOverview5TabsContentFade>
255
- <ProductOverview5TabsContentFade value='reviews' className='mt-8'>
256
- <ProductOverview5ReviewsTab
257
- product={product}
258
- basedOnReviewsLabel={basedOnReviewsLabel}
259
- foundHelpfulLabel={foundHelpfulLabel}
260
- />
261
- </ProductOverview5TabsContentFade>
262
- <ProductOverview5TabsContentFade value='gallery' className='mt-8'>
263
- <ProductOverview5LightboxTab
264
- product={product}
265
- galleryLabel={galleryLabel}
266
- ofCounterLabel={ofCounterLabel}
267
- />
268
- </ProductOverview5TabsContentFade>
269
- </ProductOverview5Tabs>
270
- </div>
271
- </div>
272
- </section>
273
- )
274
- }
@@ -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