@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,392 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react'
|
|
4
|
-
import { ChevronLeft, ChevronRight, Lock } from 'lucide-react'
|
|
5
|
-
|
|
6
|
-
import type { PaymentMethod } from '@thorprovider/types'
|
|
7
|
-
|
|
8
|
-
import { Button } from '../../../ui/button'
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
|
|
10
|
-
|
|
11
|
-
import { CheckoutForm3InformationStep } from './checkout-form-3-information-step'
|
|
12
|
-
import { CheckoutForm3OrderSummary } from './checkout-form-3-order-summary'
|
|
13
|
-
import { CheckoutForm3PaymentStep } from './checkout-form-3-payment-step'
|
|
14
|
-
import { CheckoutForm3Progress, CHECKOUT_FORM_3_STEP_ICONS } from './checkout-form-3-progress'
|
|
15
|
-
import { CheckoutForm3ReviewStep } from './checkout-form-3-review-step'
|
|
16
|
-
import { CheckoutForm3ShippingStep } from './checkout-form-3-shipping-step'
|
|
17
|
-
|
|
18
|
-
export interface CheckoutForm3CartItem {
|
|
19
|
-
id: number
|
|
20
|
-
name: string
|
|
21
|
-
price: number
|
|
22
|
-
quantity: number
|
|
23
|
-
image: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface CheckoutForm3ShippingMethod {
|
|
27
|
-
id: string
|
|
28
|
-
name: string
|
|
29
|
-
description: string
|
|
30
|
-
cost: number
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface CheckoutForm3CountryOption {
|
|
34
|
-
value: string
|
|
35
|
-
label: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface CheckoutForm3Step {
|
|
39
|
-
id: number
|
|
40
|
-
name: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface CheckoutForm3FormData {
|
|
44
|
-
email: string
|
|
45
|
-
firstName: string
|
|
46
|
-
lastName: string
|
|
47
|
-
phone: string
|
|
48
|
-
address: string
|
|
49
|
-
apartment: string
|
|
50
|
-
city: string
|
|
51
|
-
state: string
|
|
52
|
-
zipCode: string
|
|
53
|
-
country: string
|
|
54
|
-
shippingMethod: string
|
|
55
|
-
paymentMethod: string
|
|
56
|
-
cardNumber: string
|
|
57
|
-
expiryDate: string
|
|
58
|
-
cvv: string
|
|
59
|
-
cardName: string
|
|
60
|
-
billingAddress: string
|
|
61
|
-
newsletter: boolean
|
|
62
|
-
saveInfo: boolean
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type CheckoutForm3ChangeHandler = (
|
|
66
|
-
field: keyof CheckoutForm3FormData,
|
|
67
|
-
value: string | boolean
|
|
68
|
-
) => void
|
|
69
|
-
|
|
70
|
-
export interface CheckoutForm3Props {
|
|
71
|
-
cartItems: CheckoutForm3CartItem[]
|
|
72
|
-
shippingMethods: CheckoutForm3ShippingMethod[]
|
|
73
|
-
paymentMethods?: PaymentMethod[]
|
|
74
|
-
taxRate?: number
|
|
75
|
-
countryOptions?: CheckoutForm3CountryOption[]
|
|
76
|
-
steps?: CheckoutForm3Step[]
|
|
77
|
-
currencySymbol?: string
|
|
78
|
-
titleLabel?: string
|
|
79
|
-
subtitleLabel?: string
|
|
80
|
-
backLabel?: string
|
|
81
|
-
continueLabel?: string
|
|
82
|
-
placeOrderLabel?: string
|
|
83
|
-
shippingAddressLabel?: string
|
|
84
|
-
shippingMethodLabel?: string
|
|
85
|
-
paymentLabel?: string
|
|
86
|
-
noMethodsLabel?: string
|
|
87
|
-
securePaymentLabel?: string
|
|
88
|
-
emailLabel?: string
|
|
89
|
-
firstNameLabel?: string
|
|
90
|
-
lastNameLabel?: string
|
|
91
|
-
phoneLabel?: string
|
|
92
|
-
addressLabel?: string
|
|
93
|
-
apartmentLabel?: string
|
|
94
|
-
cityLabel?: string
|
|
95
|
-
stateLabel?: string
|
|
96
|
-
zipCodeLabel?: string
|
|
97
|
-
countryLabel?: string
|
|
98
|
-
cardNumberLabel?: string
|
|
99
|
-
expiryLabel?: string
|
|
100
|
-
cvvLabel?: string
|
|
101
|
-
cardNameLabel?: string
|
|
102
|
-
newsletterLabel?: string
|
|
103
|
-
saveInfoLabel?: string
|
|
104
|
-
orderSummaryLabel?: string
|
|
105
|
-
subtotalLabel?: string
|
|
106
|
-
shippingLabel?: string
|
|
107
|
-
taxLabel?: string
|
|
108
|
-
totalLabel?: string
|
|
109
|
-
freeLabel?: string
|
|
110
|
-
orderReviewLabel?: string
|
|
111
|
-
contactReviewLabel?: string
|
|
112
|
-
shipToReviewLabel?: string
|
|
113
|
-
paymentReviewLabel?: string
|
|
114
|
-
shippingReviewLabel?: string
|
|
115
|
-
onComplete?: (data: CheckoutForm3FormData) => void
|
|
116
|
-
onNextStep?: (step: number) => void
|
|
117
|
-
onPreviousStep?: (step: number) => void
|
|
118
|
-
onFieldChange?: CheckoutForm3ChangeHandler
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const DEFAULT_COUNTRY_OPTIONS: CheckoutForm3CountryOption[] = [
|
|
122
|
-
{ value: 'US', label: 'Estados Unidos' },
|
|
123
|
-
{ value: 'CA', label: 'Canadá' },
|
|
124
|
-
{ value: 'UK', label: 'Reino Unido' },
|
|
125
|
-
{ value: 'AU', label: 'Australia' },
|
|
126
|
-
]
|
|
127
|
-
|
|
128
|
-
const DEFAULT_STEPS: CheckoutForm3Step[] = [
|
|
129
|
-
{ id: 1, name: 'Información' },
|
|
130
|
-
{ id: 2, name: 'Envío' },
|
|
131
|
-
{ id: 3, name: 'Pago' },
|
|
132
|
-
{ id: 4, name: 'Revisión' },
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
export default function CheckoutForm3({
|
|
136
|
-
cartItems,
|
|
137
|
-
shippingMethods,
|
|
138
|
-
paymentMethods = [],
|
|
139
|
-
taxRate = 0,
|
|
140
|
-
countryOptions = DEFAULT_COUNTRY_OPTIONS,
|
|
141
|
-
steps = DEFAULT_STEPS,
|
|
142
|
-
currencySymbol = '$',
|
|
143
|
-
titleLabel = 'Checkout seguro',
|
|
144
|
-
subtitleLabel,
|
|
145
|
-
backLabel = 'Anterior',
|
|
146
|
-
continueLabel = 'Continuar',
|
|
147
|
-
placeOrderLabel = 'Realizar pedido',
|
|
148
|
-
shippingAddressLabel = 'Dirección de envío',
|
|
149
|
-
shippingMethodLabel = 'Método de envío',
|
|
150
|
-
paymentLabel = 'Método de pago',
|
|
151
|
-
noMethodsLabel = 'No hay métodos de pago disponibles para esta región.',
|
|
152
|
-
securePaymentLabel = 'Tu información de pago está cifrada y es segura',
|
|
153
|
-
emailLabel = 'Correo electrónico',
|
|
154
|
-
firstNameLabel = 'Nombre',
|
|
155
|
-
lastNameLabel = 'Apellidos',
|
|
156
|
-
phoneLabel = 'Teléfono',
|
|
157
|
-
addressLabel = 'Dirección',
|
|
158
|
-
apartmentLabel = 'Apartamento, suite, etc.',
|
|
159
|
-
cityLabel = 'Ciudad',
|
|
160
|
-
stateLabel = 'Estado/Provincia',
|
|
161
|
-
zipCodeLabel = 'Código postal',
|
|
162
|
-
countryLabel = 'País',
|
|
163
|
-
cardNumberLabel = 'Número de tarjeta',
|
|
164
|
-
expiryLabel = 'Fecha de vencimiento',
|
|
165
|
-
cvvLabel = 'CVV',
|
|
166
|
-
cardNameLabel = 'Nombre en la tarjeta',
|
|
167
|
-
newsletterLabel = 'Quiero recibir novedades y ofertas',
|
|
168
|
-
saveInfoLabel = 'Guardar esta información para la próxima vez',
|
|
169
|
-
orderSummaryLabel = 'Resumen del pedido',
|
|
170
|
-
subtotalLabel = 'Subtotal',
|
|
171
|
-
shippingLabel = 'Envío',
|
|
172
|
-
taxLabel = 'Impuestos',
|
|
173
|
-
totalLabel = 'Total',
|
|
174
|
-
freeLabel = 'Gratis',
|
|
175
|
-
orderReviewLabel = 'Revisión del pedido',
|
|
176
|
-
contactReviewLabel = 'Contacto',
|
|
177
|
-
shipToReviewLabel = 'Enviar a',
|
|
178
|
-
paymentReviewLabel = 'Pago',
|
|
179
|
-
shippingReviewLabel = 'Envío',
|
|
180
|
-
onComplete,
|
|
181
|
-
onNextStep,
|
|
182
|
-
onPreviousStep,
|
|
183
|
-
onFieldChange,
|
|
184
|
-
}: CheckoutForm3Props) {
|
|
185
|
-
const [currentStep, setCurrentStep] = useState(1)
|
|
186
|
-
const [formData, setFormData] = useState<CheckoutForm3FormData>(() => ({
|
|
187
|
-
email: '',
|
|
188
|
-
firstName: '',
|
|
189
|
-
lastName: '',
|
|
190
|
-
phone: '',
|
|
191
|
-
address: '',
|
|
192
|
-
apartment: '',
|
|
193
|
-
city: '',
|
|
194
|
-
state: '',
|
|
195
|
-
zipCode: '',
|
|
196
|
-
country: countryOptions[0]?.value ?? '',
|
|
197
|
-
shippingMethod: shippingMethods[0]?.id ?? '',
|
|
198
|
-
paymentMethod: paymentMethods[0]?.id ?? '',
|
|
199
|
-
cardNumber: '',
|
|
200
|
-
expiryDate: '',
|
|
201
|
-
cvv: '',
|
|
202
|
-
cardName: '',
|
|
203
|
-
billingAddress: 'same',
|
|
204
|
-
newsletter: false,
|
|
205
|
-
saveInfo: false,
|
|
206
|
-
}))
|
|
207
|
-
|
|
208
|
-
const subtotal = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
209
|
-
const shippingCosts = Object.fromEntries(
|
|
210
|
-
shippingMethods.map(method => [method.id, method.cost]),
|
|
211
|
-
) as Record<string, number>
|
|
212
|
-
const shipping = shippingCosts[formData.shippingMethod] ?? 0
|
|
213
|
-
const tax = subtotal * taxRate
|
|
214
|
-
const total = subtotal + shipping + tax
|
|
215
|
-
|
|
216
|
-
const handleInputChange: CheckoutForm3ChangeHandler = (field, value) => {
|
|
217
|
-
setFormData(prev => ({ ...prev, [field]: value }))
|
|
218
|
-
onFieldChange?.(field, value)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const handleNextStep = () => {
|
|
222
|
-
const next = Math.min(currentStep + 1, steps.length)
|
|
223
|
-
setCurrentStep(next)
|
|
224
|
-
onNextStep?.(next)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const handlePreviousStep = () => {
|
|
228
|
-
const next = Math.max(currentStep - 1, 1)
|
|
229
|
-
setCurrentStep(next)
|
|
230
|
-
onPreviousStep?.(next)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** Continue stays disabled until the fields this step is responsible for are filled in. */
|
|
234
|
-
const isStepComplete = (stepNumber: number) => {
|
|
235
|
-
if (stepNumber < currentStep) return true
|
|
236
|
-
if (stepNumber === 1) return Boolean(formData.email && formData.firstName && formData.lastName)
|
|
237
|
-
if (stepNumber === 2) return Boolean(formData.address && formData.city && formData.zipCode)
|
|
238
|
-
if (stepNumber === 3) {
|
|
239
|
-
const selectedMethod = paymentMethods.find(method => method.id === formData.paymentMethod)
|
|
240
|
-
if (!formData.paymentMethod) return false
|
|
241
|
-
return selectedMethod?.type === 'card' ? Boolean(formData.cardNumber) : true
|
|
242
|
-
}
|
|
243
|
-
return false
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const CurrentStepIcon = CHECKOUT_FORM_3_STEP_ICONS[currentStep - 1]
|
|
247
|
-
|
|
248
|
-
return (
|
|
249
|
-
<section className='py-8'>
|
|
250
|
-
<div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
251
|
-
<div className='mb-8'>
|
|
252
|
-
<h1 className='text-3xl font-bold tracking-tight text-balance'>{titleLabel}</h1>
|
|
253
|
-
<p className='text-muted-foreground mt-2'>
|
|
254
|
-
{subtitleLabel ?? `Completa tu compra en ${steps.length} sencillos pasos`}
|
|
255
|
-
</p>
|
|
256
|
-
</div>
|
|
257
|
-
|
|
258
|
-
<div className='grid gap-8 lg:grid-cols-3'>
|
|
259
|
-
<div className='lg:col-span-2'>
|
|
260
|
-
<CheckoutForm3Progress
|
|
261
|
-
steps={steps}
|
|
262
|
-
currentStep={currentStep}
|
|
263
|
-
isStepComplete={isStepComplete}
|
|
264
|
-
/>
|
|
265
|
-
|
|
266
|
-
<Card>
|
|
267
|
-
<CardHeader>
|
|
268
|
-
<CardTitle className='flex items-center gap-2'>
|
|
269
|
-
{CurrentStepIcon ? <CurrentStepIcon className='size-5' aria-hidden='true' /> : null}
|
|
270
|
-
{steps[currentStep - 1].name}
|
|
271
|
-
</CardTitle>
|
|
272
|
-
</CardHeader>
|
|
273
|
-
<CardContent className='flex flex-col gap-6'>
|
|
274
|
-
{currentStep === 1 ? (
|
|
275
|
-
<CheckoutForm3InformationStep
|
|
276
|
-
formData={formData}
|
|
277
|
-
onChange={handleInputChange}
|
|
278
|
-
emailLabel={emailLabel}
|
|
279
|
-
firstNameLabel={firstNameLabel}
|
|
280
|
-
lastNameLabel={lastNameLabel}
|
|
281
|
-
phoneLabel={phoneLabel}
|
|
282
|
-
newsletterLabel={newsletterLabel}
|
|
283
|
-
/>
|
|
284
|
-
) : null}
|
|
285
|
-
|
|
286
|
-
{currentStep === 2 ? (
|
|
287
|
-
<CheckoutForm3ShippingStep
|
|
288
|
-
formData={formData}
|
|
289
|
-
onChange={handleInputChange}
|
|
290
|
-
countryOptions={countryOptions}
|
|
291
|
-
shippingMethods={shippingMethods}
|
|
292
|
-
shippingAddressLabel={shippingAddressLabel}
|
|
293
|
-
shippingMethodLabel={shippingMethodLabel}
|
|
294
|
-
addressLabel={addressLabel}
|
|
295
|
-
apartmentLabel={apartmentLabel}
|
|
296
|
-
cityLabel={cityLabel}
|
|
297
|
-
stateLabel={stateLabel}
|
|
298
|
-
zipCodeLabel={zipCodeLabel}
|
|
299
|
-
countryLabel={countryLabel}
|
|
300
|
-
freeLabel={freeLabel}
|
|
301
|
-
currencySymbol={currencySymbol}
|
|
302
|
-
/>
|
|
303
|
-
) : null}
|
|
304
|
-
|
|
305
|
-
{currentStep === 3 ? (
|
|
306
|
-
<CheckoutForm3PaymentStep
|
|
307
|
-
formData={formData}
|
|
308
|
-
onChange={handleInputChange}
|
|
309
|
-
paymentMethods={paymentMethods}
|
|
310
|
-
paymentLabel={paymentLabel}
|
|
311
|
-
noMethodsLabel={noMethodsLabel}
|
|
312
|
-
cardNumberLabel={cardNumberLabel}
|
|
313
|
-
expiryLabel={expiryLabel}
|
|
314
|
-
cvvLabel={cvvLabel}
|
|
315
|
-
cardNameLabel={cardNameLabel}
|
|
316
|
-
securePaymentLabel={securePaymentLabel}
|
|
317
|
-
/>
|
|
318
|
-
) : null}
|
|
319
|
-
|
|
320
|
-
{currentStep === 4 ? (
|
|
321
|
-
<CheckoutForm3ReviewStep
|
|
322
|
-
formData={formData}
|
|
323
|
-
onChange={handleInputChange}
|
|
324
|
-
shippingMethods={shippingMethods}
|
|
325
|
-
paymentMethods={paymentMethods}
|
|
326
|
-
orderReviewLabel={orderReviewLabel}
|
|
327
|
-
contactReviewLabel={contactReviewLabel}
|
|
328
|
-
shipToReviewLabel={shipToReviewLabel}
|
|
329
|
-
paymentReviewLabel={paymentReviewLabel}
|
|
330
|
-
shippingReviewLabel={shippingReviewLabel}
|
|
331
|
-
saveInfoLabel={saveInfoLabel}
|
|
332
|
-
/>
|
|
333
|
-
) : null}
|
|
334
|
-
|
|
335
|
-
<div className='flex justify-between pt-6'>
|
|
336
|
-
<Button
|
|
337
|
-
type='button'
|
|
338
|
-
variant='outline'
|
|
339
|
-
className='h-9 px-4 py-2 cursor-pointer'
|
|
340
|
-
onClick={handlePreviousStep}
|
|
341
|
-
disabled={currentStep === 1}
|
|
342
|
-
>
|
|
343
|
-
<ChevronLeft data-icon='inline-start' />
|
|
344
|
-
{backLabel}
|
|
345
|
-
</Button>
|
|
346
|
-
|
|
347
|
-
{currentStep < steps.length ? (
|
|
348
|
-
<Button
|
|
349
|
-
type='button'
|
|
350
|
-
onClick={handleNextStep}
|
|
351
|
-
className='h-9 px-4 py-2 cursor-pointer'
|
|
352
|
-
disabled={!isStepComplete(currentStep)}
|
|
353
|
-
>
|
|
354
|
-
{continueLabel}
|
|
355
|
-
<ChevronRight data-icon='inline-end' />
|
|
356
|
-
</Button>
|
|
357
|
-
) : (
|
|
358
|
-
<Button
|
|
359
|
-
type='button'
|
|
360
|
-
className='h-9 px-4 py-2 min-w-32 cursor-pointer'
|
|
361
|
-
onClick={() => onComplete?.(formData)}
|
|
362
|
-
>
|
|
363
|
-
<Lock data-icon='inline-start' />
|
|
364
|
-
{placeOrderLabel}
|
|
365
|
-
</Button>
|
|
366
|
-
)}
|
|
367
|
-
</div>
|
|
368
|
-
</CardContent>
|
|
369
|
-
</Card>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
<div className='lg:col-span-1'>
|
|
373
|
-
<CheckoutForm3OrderSummary
|
|
374
|
-
items={cartItems}
|
|
375
|
-
subtotal={subtotal}
|
|
376
|
-
shipping={shipping}
|
|
377
|
-
tax={tax}
|
|
378
|
-
total={total}
|
|
379
|
-
currencySymbol={currencySymbol}
|
|
380
|
-
orderSummaryLabel={orderSummaryLabel}
|
|
381
|
-
subtotalLabel={subtotalLabel}
|
|
382
|
-
shippingLabel={shippingLabel}
|
|
383
|
-
taxLabel={taxLabel}
|
|
384
|
-
totalLabel={totalLabel}
|
|
385
|
-
freeLabel={freeLabel}
|
|
386
|
-
/>
|
|
387
|
-
</div>
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
</section>
|
|
391
|
-
)
|
|
392
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import { useEffect, useState } from 'react'
|
|
5
|
-
|
|
6
|
-
import { cn } from '../../../lib/utils'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @title Avatar group
|
|
10
|
-
* @description Overlapping avatar stack whose neighbours lift with a distance falloff on hover.
|
|
11
|
-
*
|
|
12
|
-
* Adapted from Transitions.dev "Avatar group hover".
|
|
13
|
-
*
|
|
14
|
-
* A dock, not a switch. Lifting only the hovered item makes the row twitch — one element jumps
|
|
15
|
-
* while the rest sit rigid — so the response falls off with distance: the neighbour rises about
|
|
16
|
-
* half as far, the next barely at all. The falloff is what makes the row read as one soft object.
|
|
17
|
-
*
|
|
18
|
-
* The return is deliberately springier than the approach: a strong overshoot curve on the way back
|
|
19
|
-
* reads as elastic rather than mechanical.
|
|
20
|
-
*/
|
|
21
|
-
const LIFT = -4
|
|
22
|
-
const FALLOFF = 0.45
|
|
23
|
-
const ACTIVE_SCALE = 1.05
|
|
24
|
-
const DURATION = 320
|
|
25
|
-
const EASE_IN = 'cubic-bezier(0.22, 1, 0.36, 1)'
|
|
26
|
-
const EASE_OUT = 'cubic-bezier(0.34, 3.85, 0.64, 1)'
|
|
27
|
-
|
|
28
|
-
export function AvatarGroup({
|
|
29
|
-
children,
|
|
30
|
-
className,
|
|
31
|
-
overlap = '-ml-2',
|
|
32
|
-
}: {
|
|
33
|
-
children: React.ReactNode
|
|
34
|
-
className?: string
|
|
35
|
-
/** Negative margin applied to every item but the first. Set to `''` for a non-overlapping row. */
|
|
36
|
-
overlap?: string
|
|
37
|
-
}) {
|
|
38
|
-
const items = React.Children.toArray(children)
|
|
39
|
-
const [activeIndex, setActiveIndex] = useState<number | null>(null)
|
|
40
|
-
const [reducedMotion, setReducedMotion] = useState(false)
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
const query = window.matchMedia('(prefers-reduced-motion: reduce)')
|
|
44
|
-
const sync = () => setReducedMotion(query.matches)
|
|
45
|
-
|
|
46
|
-
sync()
|
|
47
|
-
query.addEventListener('change', sync)
|
|
48
|
-
return () => query.removeEventListener('change', sync)
|
|
49
|
-
}, [])
|
|
50
|
-
|
|
51
|
-
const itemStyle = (index: number): React.CSSProperties | undefined => {
|
|
52
|
-
if (reducedMotion) {
|
|
53
|
-
return undefined
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const isActive = index === activeIndex
|
|
57
|
-
const shift = activeIndex === null ? 0 : LIFT * FALLOFF ** Math.abs(index - activeIndex)
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
// translateY before scale, so scaling never amplifies the lift distance.
|
|
61
|
-
transform: `translateY(${shift.toFixed(3)}px) scale(${isActive ? ACTIVE_SCALE : 1})`,
|
|
62
|
-
transition: `transform ${DURATION}ms ${activeIndex === null ? EASE_OUT : EASE_IN}`,
|
|
63
|
-
willChange: 'transform',
|
|
64
|
-
// A lifted item has to sit above the neighbour overlapping it, or the lift happens underneath.
|
|
65
|
-
zIndex: isActive ? 1 : undefined,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div className={cn('flex', className)} onMouseLeave={() => setActiveIndex(null)}>
|
|
71
|
-
{items.map((child, index) => (
|
|
72
|
-
<div
|
|
73
|
-
// eslint-disable-next-line react/no-array-index-key -- position is the identity here
|
|
74
|
-
key={index}
|
|
75
|
-
className={cn('relative', index > 0 && overlap)}
|
|
76
|
-
style={itemStyle(index)}
|
|
77
|
-
onMouseEnter={() => setActiveIndex(index)}
|
|
78
|
-
>
|
|
79
|
-
{child}
|
|
80
|
-
</div>
|
|
81
|
-
))}
|
|
82
|
-
</div>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default AvatarGroup
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { Button } from '../../../ui/button'
|
|
2
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
3
|
-
import { cn } from '../../../lib/utils'
|
|
4
|
-
import {
|
|
5
|
-
ArrowRight,
|
|
6
|
-
BatteryCharging,
|
|
7
|
-
Cable,
|
|
8
|
-
Camera,
|
|
9
|
-
Lightbulb,
|
|
10
|
-
ShieldCheck,
|
|
11
|
-
Smartphone,
|
|
12
|
-
Sparkles,
|
|
13
|
-
Wrench,
|
|
14
|
-
X,
|
|
15
|
-
} from 'lucide-react'
|
|
16
|
-
import type { CTA } from '../../../lib/types'
|
|
17
|
-
|
|
18
|
-
const categoryIcons = {
|
|
19
|
-
Smartphone,
|
|
20
|
-
BatteryCharging,
|
|
21
|
-
Wrench,
|
|
22
|
-
Camera,
|
|
23
|
-
Cable,
|
|
24
|
-
ShieldCheck,
|
|
25
|
-
Sparkles,
|
|
26
|
-
Lightbulb,
|
|
27
|
-
} as const
|
|
28
|
-
|
|
29
|
-
export type ProductCategory1CategoryIcon =
|
|
30
|
-
| 'Smartphone'
|
|
31
|
-
| 'BatteryCharging'
|
|
32
|
-
| 'Wrench'
|
|
33
|
-
| 'Camera'
|
|
34
|
-
| 'Cable'
|
|
35
|
-
| 'ShieldCheck'
|
|
36
|
-
| 'Sparkles'
|
|
37
|
-
| 'Lightbulb'
|
|
38
|
-
|
|
39
|
-
export interface ProductCategory1Category {
|
|
40
|
-
id: string
|
|
41
|
-
name: string
|
|
42
|
-
subtitle: string
|
|
43
|
-
icon: ProductCategory1CategoryIcon
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface ProductCategory1Props {
|
|
47
|
-
badge?: string
|
|
48
|
-
title: string
|
|
49
|
-
description?: string
|
|
50
|
-
cta?: CTA
|
|
51
|
-
categories: ProductCategory1Category[]
|
|
52
|
-
className?: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function ProductCategory1({
|
|
56
|
-
badge,
|
|
57
|
-
title,
|
|
58
|
-
description,
|
|
59
|
-
cta,
|
|
60
|
-
categories,
|
|
61
|
-
className,
|
|
62
|
-
}: ProductCategory1Props) {
|
|
63
|
-
return (
|
|
64
|
-
<section className={cn('relative w-full py-12 md:py-16 lg:py-20', className)}>
|
|
65
|
-
<div className='relative z-10 mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
66
|
-
<header className='mx-auto mb-12 max-w-5xl'>
|
|
67
|
-
<div className='flex flex-col items-center justify-between gap-6 md:flex-row md:items-start md:text-start'>
|
|
68
|
-
<div className='flex flex-col items-center gap-4 text-center md:items-start md:text-start'>
|
|
69
|
-
{badge ? (
|
|
70
|
-
<div className='inline-flex'>
|
|
71
|
-
<span className='bg-secondary/80 text-secondary-foreground rounded-full px-4 py-1.5 text-sm font-medium'>
|
|
72
|
-
{badge}
|
|
73
|
-
</span>
|
|
74
|
-
</div>
|
|
75
|
-
) : null}
|
|
76
|
-
<div className='flex flex-col gap-2'>
|
|
77
|
-
<h2 className='text-foreground text-3xl font-bold tracking-tight md:text-4xl'>{title}</h2>
|
|
78
|
-
{description ? (
|
|
79
|
-
<p className='text-muted-foreground max-w-2xl'>{description}</p>
|
|
80
|
-
) : null}
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
{cta ? (
|
|
84
|
-
<Button
|
|
85
|
-
className='h-10 cursor-pointer whitespace-nowrap px-4'
|
|
86
|
-
size='lg'
|
|
87
|
-
asChild
|
|
88
|
-
>
|
|
89
|
-
<a href={cta.href}>
|
|
90
|
-
{cta.label}
|
|
91
|
-
<ArrowRight />
|
|
92
|
-
</a>
|
|
93
|
-
</Button>
|
|
94
|
-
) : null}
|
|
95
|
-
</div>
|
|
96
|
-
</header>
|
|
97
|
-
|
|
98
|
-
<div className='mx-auto grid max-w-5xl grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4'>
|
|
99
|
-
{categories.map(category => {
|
|
100
|
-
const Icon = categoryIcons[category.icon] ?? X
|
|
101
|
-
return (
|
|
102
|
-
<Card
|
|
103
|
-
key={category.id}
|
|
104
|
-
className='group relative overflow-hidden py-6 px-2 shadow-xs transition-all hover:shadow-md'
|
|
105
|
-
>
|
|
106
|
-
<CardContent>
|
|
107
|
-
<div className='flex flex-col gap-4'>
|
|
108
|
-
<div className='bg-secondary/80 text-secondary-foreground flex size-12 items-center justify-center rounded-lg'>
|
|
109
|
-
<Icon className='size-5' strokeWidth={1.5} />
|
|
110
|
-
</div>
|
|
111
|
-
<div>
|
|
112
|
-
<h3 className='text-foreground text-lg font-semibold'>{category.name}</h3>
|
|
113
|
-
<p className='text-muted-foreground text-sm'>{category.subtitle}</p>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
</CardContent>
|
|
117
|
-
</Card>
|
|
118
|
-
)
|
|
119
|
-
})}
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
</section>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export default ProductCategory1
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import {
|
|
4
|
-
ProductCategory2,
|
|
5
|
-
type ProductCategory2Category,
|
|
6
|
-
} from './product-category-2'
|
|
7
|
-
|
|
8
|
-
const base: ProductCategory2Category = {
|
|
9
|
-
id: 'cat-1',
|
|
10
|
-
name: 'Herramientas',
|
|
11
|
-
description: 'Taladros y accesorios',
|
|
12
|
-
image: '/products/drill.png',
|
|
13
|
-
trending: false,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const render = (category: ProductCategory2Category) =>
|
|
17
|
-
renderToStaticMarkup(<ProductCategory2 categories={[category]} />)
|
|
18
|
-
|
|
19
|
-
// The count is the only text node that ends in "items" inside a span (the
|
|
20
|
-
// `items-center` utility lives in a class attribute and is never followed by
|
|
21
|
-
// `</span>`).
|
|
22
|
-
const hasItemCount = (html: string) => html.includes('items</span>')
|
|
23
|
-
|
|
24
|
-
describe('ProductCategory2 — optional itemCount', () => {
|
|
25
|
-
it('renders no count when itemCount is omitted (unknown projection)', () => {
|
|
26
|
-
const html = render(base)
|
|
27
|
-
expect(hasItemCount(html)).toBe(false)
|
|
28
|
-
// The tile itself still renders.
|
|
29
|
-
expect(html).toContain('Herramientas')
|
|
30
|
-
expect(html).toContain('Browse')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('preserves a real zero count', () => {
|
|
34
|
-
const html = render({ ...base, itemCount: 0 })
|
|
35
|
-
expect(html).toContain('0 items</span>')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('renders a positive count', () => {
|
|
39
|
-
const html = render({ ...base, itemCount: 7 })
|
|
40
|
-
expect(html).toContain('7 items</span>')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('omits a negative count', () => {
|
|
44
|
-
expect(hasItemCount(render({ ...base, itemCount: -1 }))).toBe(false)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('omits a non-finite count', () => {
|
|
48
|
-
expect(hasItemCount(render({ ...base, itemCount: Number.NaN }))).toBe(false)
|
|
49
|
-
expect(hasItemCount(render({ ...base, itemCount: Number.POSITIVE_INFINITY }))).toBe(false)
|
|
50
|
-
})
|
|
51
|
-
})
|