@thorprovider/blocks 1.0.0 → 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 +48 -0
- package/README.md +156 -40
- package/package.json +1 -1
- 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/{marketing/navbars/navbar-4-account-menu.test.tsx → e-commerce/navigation/storefront-navbar-account-menu.test.tsx} +8 -8
- 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} +40 -40
- package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +17 -17
- package/src/blocks/e-commerce/product-list/product-card.tsx +63 -40
- package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +2 -2
- 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 +80 -81
- package/src/lib/contracts-curated.test.ts +5 -5
- package/src/lib/contracts.test.ts +55 -0
- package/src/lib/contracts.ts +64 -2
- package/tsup.config.ts +1 -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/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-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,192 +0,0 @@
|
|
|
1
|
-
import { Input } from '../../../ui/input'
|
|
2
|
-
import { Button } from '../../../ui/button'
|
|
3
|
-
import { ArrowRight, type LucideIcon } from 'lucide-react'
|
|
4
|
-
|
|
5
|
-
export interface FooterLink {
|
|
6
|
-
label: string
|
|
7
|
-
href?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface FooterLinkColumn {
|
|
11
|
-
title: string
|
|
12
|
-
links: FooterLink[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface SocialLink {
|
|
16
|
-
label: string
|
|
17
|
-
href?: string
|
|
18
|
-
icon: LucideIcon
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface FooterSection3Props {
|
|
22
|
-
/** Texto del logo (identidad del bloque). Requerido. */
|
|
23
|
-
logoText: string
|
|
24
|
-
/** Descripción corta de la marca. Requerida. */
|
|
25
|
-
description: string
|
|
26
|
-
/** Columnas de enlaces (máximo recomendado 4). Requerido. */
|
|
27
|
-
linkColumns: FooterLinkColumn[]
|
|
28
|
-
/** Enlaces legales de la barra inferior. Por defecto []. */
|
|
29
|
-
legalLinks?: FooterLink[]
|
|
30
|
-
/** Redes sociales (cada una con su icono lucide). Por defecto []. */
|
|
31
|
-
socialLinks?: SocialLink[]
|
|
32
|
-
/** Título del bloque newsletter. Por defecto 'Suscríbete a nuestro boletín'. */
|
|
33
|
-
newsletterTitle?: string
|
|
34
|
-
/** Placeholder del input de email. Por defecto 'Tu correo electrónico'. */
|
|
35
|
-
newsletterPlaceholder?: string
|
|
36
|
-
/** aria-label del botón de envío (icon-only). Por defecto 'Suscribirse'. */
|
|
37
|
-
newsletterLabel?: string
|
|
38
|
-
/** Callback al enviar el formulario newsletter. Recibe el email. */
|
|
39
|
-
onSubscribe?: (email: string) => void
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function FooterSection3({
|
|
43
|
-
logoText,
|
|
44
|
-
description,
|
|
45
|
-
linkColumns,
|
|
46
|
-
legalLinks = [],
|
|
47
|
-
socialLinks = [],
|
|
48
|
-
newsletterTitle = 'Suscríbete a nuestro boletín',
|
|
49
|
-
newsletterPlaceholder = 'Tu correo electrónico',
|
|
50
|
-
newsletterLabel = 'Suscribirse',
|
|
51
|
-
onSubscribe,
|
|
52
|
-
}: FooterSection3Props) {
|
|
53
|
-
return (
|
|
54
|
-
<footer className='w-full py-16' aria-label='Pie de página'>
|
|
55
|
-
<div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
56
|
-
{/* Logo y branding */}
|
|
57
|
-
<div className='mb-8 flex items-center gap-2'>
|
|
58
|
-
<svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
|
59
|
-
<path
|
|
60
|
-
d='M26 24.75C26.4142 24.75 26.75 24.4142 26.75 24C26.75 23.5858 26.4142 23.25 26 23.25V24.75ZM26 23.25H11V24.75H26V23.25ZM8.75 21V15H7.25V21H8.75ZM11 23.25C9.75736 23.25 8.75 22.2426 8.75 21H7.25C7.25 23.0711 8.92893 24.75 11 24.75V23.25Z'
|
|
61
|
-
fill='currentColor'
|
|
62
|
-
/>
|
|
63
|
-
<path
|
|
64
|
-
d='M1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4C0.75 4.41421 1.08579 4.75 1.5 4.75V3.25ZM1.5 4.75H6V3.25H1.5V4.75ZM7.25 6V21H8.75V6H7.25ZM6 4.75C6.69036 4.75 7.25 5.30964 7.25 6H8.75C8.75 4.48122 7.51878 3.25 6 3.25V4.75Z'
|
|
65
|
-
fill='currentColor'
|
|
66
|
-
/>
|
|
67
|
-
<path
|
|
68
|
-
d='M22 21.75C22.4142 21.75 22.75 21.4142 22.75 21C22.75 20.5858 22.4142 20.25 22 20.25V21.75ZM22 20.25H11V21.75H22V20.25ZM8.75 18V12H7.25V18H8.75ZM11 20.25C9.75736 20.25 8.75 19.2426 8.75 18H7.25C7.25 20.0711 8.92893 21.75 11 21.75V20.25Z'
|
|
69
|
-
fill='currentColor'
|
|
70
|
-
/>
|
|
71
|
-
<path
|
|
72
|
-
d='M27.2057 19.754C27.0654 20.1438 26.6357 20.346 26.246 20.2057C25.8562 20.0654 25.654 19.6357 25.7943 19.246L27.2057 19.754ZM30.0361 9.67744L29.3305 9.4234L29.3305 9.4234L30.0361 9.67744ZM25.7943 19.246L29.3305 9.4234L30.7418 9.93148L27.2057 19.754L25.7943 19.246ZM28.1543 7.75L8 7.75V6.25L28.1543 6.25V7.75ZM29.3305 9.4234C29.6237 8.60882 29.0201 7.75 28.1543 7.75V6.25C30.059 6.25 31.3869 8.13941 30.7418 9.93148L29.3305 9.4234Z'
|
|
73
|
-
fill='currentColor'
|
|
74
|
-
/>
|
|
75
|
-
<path
|
|
76
|
-
d='M13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21C12.75 20.5858 13.0858 20.25 13.5 20.25V21.75ZM26.7111 19.009L27.4174 19.2613L27.4174 19.2613L26.7111 19.009ZM13.5 20.25H23.8858V21.75H13.5V20.25ZM26.0048 18.7568L27.7937 13.7477L29.2063 14.2523L27.4174 19.2613L26.0048 18.7568ZM23.8858 20.25C24.8367 20.25 25.6849 19.6522 26.0048 18.7568L27.4174 19.2613C26.8843 20.7537 25.4706 21.75 23.8858 21.75V20.25Z'
|
|
77
|
-
fill='currentColor'
|
|
78
|
-
/>
|
|
79
|
-
<path d='M21.1694 10.5806L14.5651 17.1849' stroke='currentColor' />
|
|
80
|
-
<path d='M22.1694 14.5806L18.5632 18.1868' stroke='currentColor' />
|
|
81
|
-
<circle cx='13.1' cy='26.1' r='1.7' stroke='currentColor' />
|
|
82
|
-
<circle cx='22.1' cy='26.1' r='1.7' stroke='currentColor' />
|
|
83
|
-
</svg>
|
|
84
|
-
<span className='text-2xl font-semibold'>{logoText}</span>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
<p className='text-muted-foreground mb-8 max-w-lg text-sm'>{description}</p>
|
|
88
|
-
|
|
89
|
-
{/* Grid principal */}
|
|
90
|
-
<div className='grid grid-cols-2 gap-x-8 gap-y-12 md:grid-cols-4 lg:grid-cols-6'>
|
|
91
|
-
{/* Columna newsletter */}
|
|
92
|
-
<div className='col-span-2 me-8 flex flex-col gap-4 md:col-span-4 lg:col-span-2'>
|
|
93
|
-
<h4 className='text-foreground text-base font-medium'>{newsletterTitle}</h4>
|
|
94
|
-
<form
|
|
95
|
-
className='relative'
|
|
96
|
-
onSubmit={e => {
|
|
97
|
-
e.preventDefault()
|
|
98
|
-
if (onSubscribe) {
|
|
99
|
-
const email = new FormData(e.currentTarget).get('email')
|
|
100
|
-
if (typeof email === 'string') {
|
|
101
|
-
onSubscribe(email)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}}
|
|
105
|
-
>
|
|
106
|
-
<Input
|
|
107
|
-
type='email'
|
|
108
|
-
name='email'
|
|
109
|
-
placeholder={newsletterPlaceholder}
|
|
110
|
-
className='border-muted-foreground/20 h-10 bg-transparent pe-12'
|
|
111
|
-
aria-label={newsletterPlaceholder}
|
|
112
|
-
/>
|
|
113
|
-
<Button
|
|
114
|
-
type='submit'
|
|
115
|
-
variant='outline'
|
|
116
|
-
size='icon'
|
|
117
|
-
className='absolute end-1 top-1 size-8 cursor-pointer p-0'
|
|
118
|
-
aria-label={newsletterLabel}
|
|
119
|
-
>
|
|
120
|
-
<ArrowRight className='size-4' />
|
|
121
|
-
</Button>
|
|
122
|
-
</form>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
{/* Columnas de enlaces */}
|
|
126
|
-
{linkColumns.map(column => (
|
|
127
|
-
<div key={column.title} className='flex flex-col gap-4'>
|
|
128
|
-
<h4 className='text-foreground text-base font-medium'>{column.title}</h4>
|
|
129
|
-
<div className='flex flex-col gap-4'>
|
|
130
|
-
{column.links.map(link => (
|
|
131
|
-
<span
|
|
132
|
-
key={link.label}
|
|
133
|
-
className='text-muted-foreground hover:text-foreground w-fit text-sm transition-colors'
|
|
134
|
-
>
|
|
135
|
-
<span className='group relative inline-flex items-center'>
|
|
136
|
-
{link.label}
|
|
137
|
-
<span className='bg-primary absolute start-0 -bottom-0.5 h-px w-0 transition-all duration-300 ease-out group-hover:w-full' />
|
|
138
|
-
</span>
|
|
139
|
-
</span>
|
|
140
|
-
))}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
))}
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
{/* Barra inferior */}
|
|
147
|
-
<div className='border-muted-foreground/20 mt-16 flex flex-col items-center justify-between gap-6 border-t pt-8 md:flex-row'>
|
|
148
|
-
{/* Enlaces legales */}
|
|
149
|
-
<div className='flex gap-4'>
|
|
150
|
-
{legalLinks.map(link => (
|
|
151
|
-
<span
|
|
152
|
-
key={link.label}
|
|
153
|
-
className='text-muted-foreground hover:text-foreground w-fit text-sm transition-colors'
|
|
154
|
-
>
|
|
155
|
-
<span className='group relative inline-flex items-center'>
|
|
156
|
-
{link.label}
|
|
157
|
-
<span className='bg-primary absolute start-0 -bottom-0.5 h-px w-0 transition-all duration-300 ease-out group-hover:w-full' />
|
|
158
|
-
</span>
|
|
159
|
-
</span>
|
|
160
|
-
))}
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Iconos de redes sociales */}
|
|
164
|
-
<div className='flex gap-4'>
|
|
165
|
-
{socialLinks.map(({ label, href, icon: Icon }) =>
|
|
166
|
-
href ? (
|
|
167
|
-
<a
|
|
168
|
-
key={label}
|
|
169
|
-
href={href}
|
|
170
|
-
className='bg-muted/50 text-muted-foreground hover:text-primary rounded-full p-2 transition-colors'
|
|
171
|
-
aria-label={label}
|
|
172
|
-
>
|
|
173
|
-
<Icon className='size-4' />
|
|
174
|
-
</a>
|
|
175
|
-
) : (
|
|
176
|
-
<span
|
|
177
|
-
key={label}
|
|
178
|
-
className='bg-muted/50 text-muted-foreground rounded-full p-2'
|
|
179
|
-
aria-hidden='true'
|
|
180
|
-
>
|
|
181
|
-
<Icon className='size-4' />
|
|
182
|
-
</span>
|
|
183
|
-
)
|
|
184
|
-
)}
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
</footer>
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export default FooterSection3
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import type { ReactElement, ReactNode, SVGProps } from 'react'
|
|
2
|
-
import { Input } from '../../../ui/input'
|
|
3
|
-
import { Button } from '../../../ui/button'
|
|
4
|
-
import { ArrowRight, Mail, MapPin, Phone } from 'lucide-react'
|
|
5
|
-
|
|
6
|
-
export type FooterSection4SocialIcon = 'facebook' | 'twitter' | 'instagram'
|
|
7
|
-
|
|
8
|
-
export interface FooterSection4Link {
|
|
9
|
-
label: string
|
|
10
|
-
href?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface FooterSection4LinkColumn {
|
|
14
|
-
title: string
|
|
15
|
-
links: FooterSection4Link[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface FooterSection4Contact {
|
|
19
|
-
/** Título de la columna de contacto. Requerido. */
|
|
20
|
-
heading: string
|
|
21
|
-
/** Dirección del negocio. Por defecto undefined. */
|
|
22
|
-
address?: string
|
|
23
|
-
/** href del enlace de dirección. Por defecto undefined. */
|
|
24
|
-
addressHref?: string
|
|
25
|
-
/** Teléfono de contacto. Por defecto undefined. */
|
|
26
|
-
phone?: string
|
|
27
|
-
/** href del enlace de teléfono (p. ej. tel:). Por defecto undefined. */
|
|
28
|
-
phoneHref?: string
|
|
29
|
-
/** Email de contacto. Por defecto undefined. */
|
|
30
|
-
email?: string
|
|
31
|
-
/** href del enlace de email (p. ej. mailto:). Por defecto undefined. */
|
|
32
|
-
emailHref?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface FooterSection4SocialLink {
|
|
36
|
-
label: string
|
|
37
|
-
href?: string
|
|
38
|
-
icon: FooterSection4SocialIcon
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface FooterSection4Props {
|
|
42
|
-
/** Nombre de la marca mostrado junto al logo. Requerido. */
|
|
43
|
-
brandName: string
|
|
44
|
-
/** Descripción corta de la marca bajo el logo. Requerida. */
|
|
45
|
-
tagline: string
|
|
46
|
-
/** Placeholder del input de email del newsletter. Por defecto 'Tu correo electrónico'. */
|
|
47
|
-
newsletterPlaceholder?: string
|
|
48
|
-
/** Texto del botón de suscripción. Por defecto 'Suscribirse'. */
|
|
49
|
-
newsletterSubmitLabel?: string
|
|
50
|
-
/** Datos de la columna de contacto (dirección, teléfono, email). Requerido. */
|
|
51
|
-
contact: FooterSection4Contact
|
|
52
|
-
/** Columnas de enlaces (Quick Links, Resources, Legal...). Requerido. */
|
|
53
|
-
linkColumns: FooterSection4LinkColumn[]
|
|
54
|
-
/** Texto de copyright de la barra inferior (incluye año). Requerido. */
|
|
55
|
-
copyright: string
|
|
56
|
-
/** Redes sociales con su icono serializable por nombre. Por defecto []. */
|
|
57
|
-
socialLinks?: FooterSection4SocialLink[]
|
|
58
|
-
/** Logo personalizado; si se omite se usa el SVG interno del bloque. Por defecto undefined. */
|
|
59
|
-
logo?: ReactNode
|
|
60
|
-
/** Callback al enviar el formulario newsletter. Recibe el email. */
|
|
61
|
-
onNewsletterSubmit?: (email: string) => void
|
|
62
|
-
/** Callback al hacer clic en un enlace de columna o contacto. Recibe el enlace. */
|
|
63
|
-
onLinkClick?: (link: FooterSection4Link) => void
|
|
64
|
-
/** Callback al hacer clic en una red social. Recibe el label. */
|
|
65
|
-
onSocialClick?: (label: string) => void
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const socialIconPaths: Record<
|
|
69
|
-
FooterSection4SocialIcon,
|
|
70
|
-
(props: SVGProps<SVGSVGElement>) => ReactElement
|
|
71
|
-
> = {
|
|
72
|
-
facebook: props => (
|
|
73
|
-
<svg
|
|
74
|
-
xmlns='http://www.w3.org/2000/svg'
|
|
75
|
-
width='24'
|
|
76
|
-
height='24'
|
|
77
|
-
viewBox='0 0 24 24'
|
|
78
|
-
fill='none'
|
|
79
|
-
stroke='currentColor'
|
|
80
|
-
strokeWidth='2'
|
|
81
|
-
strokeLinecap='round'
|
|
82
|
-
strokeLinejoin='round'
|
|
83
|
-
{...props}
|
|
84
|
-
>
|
|
85
|
-
<path d='M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z' />
|
|
86
|
-
</svg>
|
|
87
|
-
),
|
|
88
|
-
twitter: props => (
|
|
89
|
-
<svg
|
|
90
|
-
xmlns='http://www.w3.org/2000/svg'
|
|
91
|
-
width='24'
|
|
92
|
-
height='24'
|
|
93
|
-
viewBox='0 0 24 24'
|
|
94
|
-
fill='none'
|
|
95
|
-
stroke='currentColor'
|
|
96
|
-
strokeWidth='2'
|
|
97
|
-
strokeLinecap='round'
|
|
98
|
-
strokeLinejoin='round'
|
|
99
|
-
{...props}
|
|
100
|
-
>
|
|
101
|
-
<path d='M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z' />
|
|
102
|
-
</svg>
|
|
103
|
-
),
|
|
104
|
-
instagram: props => (
|
|
105
|
-
<svg
|
|
106
|
-
xmlns='http://www.w3.org/2000/svg'
|
|
107
|
-
width='24'
|
|
108
|
-
height='24'
|
|
109
|
-
viewBox='0 0 24 24'
|
|
110
|
-
fill='none'
|
|
111
|
-
stroke='currentColor'
|
|
112
|
-
strokeWidth='2'
|
|
113
|
-
strokeLinecap='round'
|
|
114
|
-
strokeLinejoin='round'
|
|
115
|
-
{...props}
|
|
116
|
-
>
|
|
117
|
-
<rect width='20' height='20' x='2' y='2' rx='5' ry='5' />
|
|
118
|
-
<path d='M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z' />
|
|
119
|
-
<line x1='17.5' y1='6.5' x2='17.51' y2='6.5' />
|
|
120
|
-
</svg>
|
|
121
|
-
),
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const DefaultLogo = (
|
|
125
|
-
<svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
|
126
|
-
<path
|
|
127
|
-
d='M26 24.75C26.4142 24.75 26.75 24.4142 26.75 24C26.75 23.5858 26.4142 23.25 26 23.25V24.75ZM26 23.25H11V24.75H26V23.25ZM8.75 21V15H7.25V21H8.75ZM11 23.25C9.75736 23.25 8.75 22.2426 8.75 21H7.25C7.25 23.0711 8.92893 24.75 11 24.75V23.25Z'
|
|
128
|
-
fill='currentColor'
|
|
129
|
-
/>
|
|
130
|
-
<path
|
|
131
|
-
d='M1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4C0.75 4.41421 1.08579 4.75 1.5 4.75V3.25ZM1.5 4.75H6V3.25H1.5V4.75ZM7.25 6V21H8.75V6H7.25ZM6 4.75C6.69036 4.75 7.25 5.30964 7.25 6H8.75C8.75 4.48122 7.51878 3.25 6 3.25V4.75Z'
|
|
132
|
-
fill='currentColor'
|
|
133
|
-
/>
|
|
134
|
-
<path
|
|
135
|
-
d='M22 21.75C22.4142 21.75 22.75 21.4142 22.75 21C22.75 20.5858 22.4142 20.25 22 20.25V21.75ZM22 20.25H11V21.75H22V20.25ZM8.75 18V12H7.25V18H8.75ZM11 20.25C9.75736 20.25 8.75 19.2426 8.75 18H7.25C7.25 20.0711 8.92893 21.75 11 21.75V20.25Z'
|
|
136
|
-
fill='currentColor'
|
|
137
|
-
/>
|
|
138
|
-
<path
|
|
139
|
-
d='M27.2057 19.754C27.0654 20.1438 26.6357 20.346 26.246 20.2057C25.8562 20.0654 25.654 19.6357 25.7943 19.246L27.2057 19.754ZM30.0361 9.67744L29.3305 9.4234L29.3305 9.4234L30.0361 9.67744ZM25.7943 19.246L29.3305 9.4234L30.7418 9.93148L27.2057 19.754L25.7943 19.246ZM28.1543 7.75L8 7.75V6.25L28.1543 6.25V7.75ZM29.3305 9.4234C29.6237 8.60882 29.0201 7.75 28.1543 7.75V6.25C30.059 6.25 31.3869 8.13941 30.7418 9.93148L29.3305 9.4234Z'
|
|
140
|
-
fill='currentColor'
|
|
141
|
-
/>
|
|
142
|
-
<path
|
|
143
|
-
d='M13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21C12.75 20.5858 13.0858 20.25 13.5 20.25V21.75ZM26.7111 19.009L27.4174 19.2613L27.4174 19.2613L26.7111 19.009ZM13.5 20.25H23.8858V21.75H13.5V20.25ZM26.0048 18.7568L27.7937 13.7477L29.2063 14.2523L27.4174 19.2613L26.0048 18.7568ZM23.8858 20.25C24.8367 20.25 25.6849 19.6522 26.0048 18.7568L27.4174 19.2613C26.8843 20.7537 25.4706 21.75 23.8858 21.75V20.25Z'
|
|
144
|
-
fill='currentColor'
|
|
145
|
-
/>
|
|
146
|
-
<path d='M21.1694 10.5806L14.5651 17.1849' stroke='currentColor' />
|
|
147
|
-
<path d='M22.1694 14.5806L18.5632 18.1868' stroke='currentColor' />
|
|
148
|
-
<circle cx='13.1' cy='26.1' r='1.7' stroke='currentColor' />
|
|
149
|
-
<circle cx='22.1' cy='26.1' r='1.7' stroke='currentColor' />
|
|
150
|
-
</svg>
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
const contactItems = (contact: FooterSection4Contact, onLinkClick?: FooterSection4Props['onLinkClick']) => {
|
|
154
|
-
const items: { key: string; icon: ReactElement; href?: string; text?: string }[] = []
|
|
155
|
-
if (contact.address) {
|
|
156
|
-
items.push({
|
|
157
|
-
key: 'address',
|
|
158
|
-
icon: <MapPin className='mt-0.5 size-4 shrink-0' aria-hidden />,
|
|
159
|
-
href: contact.addressHref,
|
|
160
|
-
text: contact.address,
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
if (contact.phone) {
|
|
164
|
-
items.push({
|
|
165
|
-
key: 'phone',
|
|
166
|
-
icon: <Phone className='size-4 shrink-0' aria-hidden />,
|
|
167
|
-
href: contact.phoneHref,
|
|
168
|
-
text: contact.phone,
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
if (contact.email) {
|
|
172
|
-
items.push({
|
|
173
|
-
key: 'email',
|
|
174
|
-
icon: <Mail className='size-4 shrink-0' aria-hidden />,
|
|
175
|
-
href: contact.emailHref,
|
|
176
|
-
text: contact.email,
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
return items
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function FooterSection4({
|
|
183
|
-
brandName,
|
|
184
|
-
tagline,
|
|
185
|
-
newsletterPlaceholder = 'Tu correo electrónico',
|
|
186
|
-
newsletterSubmitLabel = 'Suscribirse',
|
|
187
|
-
contact,
|
|
188
|
-
linkColumns,
|
|
189
|
-
copyright,
|
|
190
|
-
socialLinks = [],
|
|
191
|
-
logo,
|
|
192
|
-
onNewsletterSubmit,
|
|
193
|
-
onLinkClick,
|
|
194
|
-
onSocialClick,
|
|
195
|
-
}: FooterSection4Props) {
|
|
196
|
-
const logoMark = logo ?? DefaultLogo
|
|
197
|
-
return (
|
|
198
|
-
<footer className='from-background to-muted/50 w-full bg-linear-to-b py-16'>
|
|
199
|
-
<div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
200
|
-
{/* Top Section with Logo and Newsletter */}
|
|
201
|
-
<section className='mb-16 flex flex-col items-start justify-between gap-8 md:flex-row md:items-center'>
|
|
202
|
-
<div className='max-w-sm flex flex-col gap-4'>
|
|
203
|
-
<div className='flex items-center gap-2'>
|
|
204
|
-
{logoMark}
|
|
205
|
-
<h2 className='text-2xl font-bold text-balance'>{brandName}</h2>
|
|
206
|
-
</div>
|
|
207
|
-
<p className='text-muted-foreground text-sm leading-relaxed text-balance'>{tagline}</p>
|
|
208
|
-
</div>
|
|
209
|
-
|
|
210
|
-
<div className='w-full md:w-auto'>
|
|
211
|
-
<form
|
|
212
|
-
className='relative'
|
|
213
|
-
onSubmit={e => {
|
|
214
|
-
e.preventDefault()
|
|
215
|
-
if (onNewsletterSubmit) {
|
|
216
|
-
const email = new FormData(e.currentTarget).get('email')
|
|
217
|
-
if (typeof email === 'string') {
|
|
218
|
-
onNewsletterSubmit(email)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}}
|
|
222
|
-
>
|
|
223
|
-
<Input
|
|
224
|
-
type='email'
|
|
225
|
-
name='email'
|
|
226
|
-
placeholder={newsletterPlaceholder}
|
|
227
|
-
className='border-muted bg-background/50 h-12 w-full ps-4 pe-32 backdrop-blur-xs md:w-[400px]'
|
|
228
|
-
aria-label={newsletterPlaceholder}
|
|
229
|
-
/>
|
|
230
|
-
<Button
|
|
231
|
-
type='submit'
|
|
232
|
-
className='bg-primary hover:bg-primary/90 absolute end-1 top-1 h-10 cursor-pointer px-4'
|
|
233
|
-
aria-label={newsletterSubmitLabel}
|
|
234
|
-
>
|
|
235
|
-
{newsletterSubmitLabel}
|
|
236
|
-
<ArrowRight />
|
|
237
|
-
</Button>
|
|
238
|
-
</form>
|
|
239
|
-
</div>
|
|
240
|
-
</section>
|
|
241
|
-
|
|
242
|
-
{/* Middle Section with Contact Info and Links */}
|
|
243
|
-
<div className='mb-16 grid grid-cols-1 gap-12 md:grid-cols-2 lg:grid-cols-5'>
|
|
244
|
-
{/* Contact Information */}
|
|
245
|
-
<address className='not-italic lg:col-span-2'>
|
|
246
|
-
<h3 className='mb-6 text-lg font-semibold text-balance'>{contact.heading}</h3>
|
|
247
|
-
<ul className='flex flex-col gap-4'>
|
|
248
|
-
{contactItems(contact, onLinkClick).map(item => (
|
|
249
|
-
<li key={item.key} className='text-muted-foreground flex items-start gap-3 text-sm'>
|
|
250
|
-
{item.icon}
|
|
251
|
-
{item.href ? (
|
|
252
|
-
<a
|
|
253
|
-
href={item.href}
|
|
254
|
-
onClick={e => {
|
|
255
|
-
if (!onLinkClick) {
|
|
256
|
-
e.preventDefault()
|
|
257
|
-
} else {
|
|
258
|
-
onLinkClick({ label: item.text ?? '' })
|
|
259
|
-
}
|
|
260
|
-
}}
|
|
261
|
-
className='hover:text-foreground transition-colors'
|
|
262
|
-
>
|
|
263
|
-
{item.text}
|
|
264
|
-
</a>
|
|
265
|
-
) : (
|
|
266
|
-
<span>{item.text}</span>
|
|
267
|
-
)}
|
|
268
|
-
</li>
|
|
269
|
-
))}
|
|
270
|
-
</ul>
|
|
271
|
-
</address>
|
|
272
|
-
|
|
273
|
-
{/* Link columns */}
|
|
274
|
-
{linkColumns.map(column => (
|
|
275
|
-
<nav key={column.title} aria-label={column.title}>
|
|
276
|
-
<h3 className='mb-6 text-lg font-semibold text-balance'>{column.title}</h3>
|
|
277
|
-
<ul className='grid grid-cols-1 gap-3'>
|
|
278
|
-
{column.links.map(link => (
|
|
279
|
-
<li key={link.label}>
|
|
280
|
-
{link.href ? (
|
|
281
|
-
<a
|
|
282
|
-
href={link.href}
|
|
283
|
-
onClick={e => {
|
|
284
|
-
if (!onLinkClick) {
|
|
285
|
-
e.preventDefault()
|
|
286
|
-
} else {
|
|
287
|
-
onLinkClick(link)
|
|
288
|
-
}
|
|
289
|
-
}}
|
|
290
|
-
className='group text-muted-foreground hover:text-foreground focus-visible:ring-ring relative inline-block rounded-sm text-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden'
|
|
291
|
-
>
|
|
292
|
-
{link.label}
|
|
293
|
-
</a>
|
|
294
|
-
) : (
|
|
295
|
-
<span className='text-muted-foreground relative inline-block text-sm'>
|
|
296
|
-
{link.label}
|
|
297
|
-
</span>
|
|
298
|
-
)}
|
|
299
|
-
</li>
|
|
300
|
-
))}
|
|
301
|
-
</ul>
|
|
302
|
-
</nav>
|
|
303
|
-
))}
|
|
304
|
-
</div>
|
|
305
|
-
|
|
306
|
-
{/* Bottom Section */}
|
|
307
|
-
<div className='border-muted-foreground/20 flex flex-col items-center justify-between border-t pt-8 md:flex-row'>
|
|
308
|
-
<p className='text-muted-foreground mb-4 text-sm md:mb-0'>{copyright}</p>
|
|
309
|
-
|
|
310
|
-
<nav aria-label='Social media'>
|
|
311
|
-
<ul className='flex gap-4'>
|
|
312
|
-
{socialLinks.map(({ label, href, icon }) => {
|
|
313
|
-
const Icon = socialIconPaths[icon]
|
|
314
|
-
const element = (
|
|
315
|
-
<>
|
|
316
|
-
<Icon className='size-5' aria-hidden />
|
|
317
|
-
<span className='sr-only'>{label}</span>
|
|
318
|
-
</>
|
|
319
|
-
)
|
|
320
|
-
const classes =
|
|
321
|
-
'bg-accent text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex size-10 items-center justify-center rounded-full p-2 transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden'
|
|
322
|
-
return (
|
|
323
|
-
<li key={label}>
|
|
324
|
-
{href ? (
|
|
325
|
-
<a
|
|
326
|
-
href={href}
|
|
327
|
-
onClick={e => {
|
|
328
|
-
if (!onSocialClick) {
|
|
329
|
-
e.preventDefault()
|
|
330
|
-
} else {
|
|
331
|
-
onSocialClick(label)
|
|
332
|
-
}
|
|
333
|
-
}}
|
|
334
|
-
className={classes}
|
|
335
|
-
aria-label={`Seguir en ${label}`}
|
|
336
|
-
>
|
|
337
|
-
{element}
|
|
338
|
-
</a>
|
|
339
|
-
) : (
|
|
340
|
-
<span className={classes} aria-hidden='true'>
|
|
341
|
-
{element}
|
|
342
|
-
</span>
|
|
343
|
-
)}
|
|
344
|
-
</li>
|
|
345
|
-
)
|
|
346
|
-
})}
|
|
347
|
-
</ul>
|
|
348
|
-
</nav>
|
|
349
|
-
</div>
|
|
350
|
-
</div>
|
|
351
|
-
</footer>
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export default FooterSection4
|