@thorprovider/blocks 0.1.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 (184) hide show
  1. package/.turbo/turbo-build.log +24 -0
  2. package/.turbo/turbo-test.log +10 -0
  3. package/.turbo/turbo-type-check.log +0 -0
  4. package/AGENTS.md +1 -0
  5. package/README.md +161 -0
  6. package/components.json +33 -0
  7. package/dist/.tsbuildinfo +1 -0
  8. package/dist/contracts.d.mts +563 -0
  9. package/dist/contracts.d.ts +563 -0
  10. package/dist/contracts.js +207 -0
  11. package/dist/contracts.js.map +1 -0
  12. package/dist/contracts.mjs +168 -0
  13. package/dist/contracts.mjs.map +1 -0
  14. package/dist/index.d.mts +2408 -0
  15. package/dist/index.d.ts +2408 -0
  16. package/dist/index.js +16422 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/index.mjs +16352 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/docs/standards/data-contracts.md +70 -0
  21. package/docs/standards/theming.md +85 -0
  22. package/package.json +62 -0
  23. package/src/blocks/application/app-shells/app-shell-1-sidebar.tsx +198 -0
  24. package/src/blocks/application/app-shells/app-shell-1.tsx +196 -0
  25. package/src/blocks/application/login-forms/login-page-1.tsx +246 -0
  26. package/src/blocks/application/login-forms/login-page-2.tsx +207 -0
  27. package/src/blocks/application/password-recovery/forgot-password-page-1-brand-mark.tsx +38 -0
  28. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +320 -0
  29. package/src/blocks/application/signup-forms/signup-page-1.tsx +231 -0
  30. package/src/blocks/application/verification/verify-1.tsx +307 -0
  31. package/src/blocks/application/verification/verify-confetti.tsx +192 -0
  32. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +260 -0
  33. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +179 -0
  34. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +341 -0
  35. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +57 -0
  36. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +124 -0
  37. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +170 -0
  38. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +52 -0
  39. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +57 -0
  40. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +68 -0
  41. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +298 -0
  42. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +47 -0
  43. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +9 -0
  44. package/src/blocks/e-commerce/checkout-forms/checkbox-check.tsx +74 -0
  45. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +189 -0
  46. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +283 -0
  47. package/src/blocks/e-commerce/checkout-forms/checkout-field-feedback.tsx +29 -0
  48. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +96 -0
  49. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-country-field.tsx +51 -0
  50. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +139 -0
  51. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +184 -0
  52. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +36 -0
  53. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +107 -0
  54. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +235 -0
  55. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +130 -0
  56. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +175 -0
  57. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +190 -0
  58. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +312 -0
  59. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +91 -0
  60. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +110 -0
  61. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +137 -0
  62. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +68 -0
  63. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +79 -0
  64. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +172 -0
  65. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +392 -0
  66. package/src/blocks/e-commerce/checkout-forms/checkout-form-types.ts +7 -0
  67. package/src/blocks/e-commerce/navigation/category-icon.test.tsx +54 -0
  68. package/src/blocks/e-commerce/navigation/category-icon.tsx +94 -0
  69. package/src/blocks/e-commerce/navigation/mega-menu.test.tsx +139 -0
  70. package/src/blocks/e-commerce/navigation/mega-menu.tsx +271 -0
  71. package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +152 -0
  72. package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +276 -0
  73. package/src/blocks/e-commerce/order-history/order-history-1.tsx +159 -0
  74. package/src/blocks/e-commerce/order-history/order-history-2.tsx +222 -0
  75. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +86 -0
  76. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +126 -0
  77. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +100 -0
  78. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +129 -0
  79. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +218 -0
  80. package/src/blocks/e-commerce/product-list/product-card-1.tsx +131 -0
  81. package/src/blocks/e-commerce/product-list/product-card-2.tsx +71 -0
  82. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +166 -0
  83. package/src/blocks/e-commerce/product-list/product-card-3.tsx +215 -0
  84. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +98 -0
  85. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +171 -0
  86. package/src/blocks/e-commerce/product-list/product-card-5.tsx +367 -0
  87. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +201 -0
  88. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +272 -0
  89. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +59 -0
  90. package/src/blocks/e-commerce/product-overview/product-overview-5-lightbox-tab.tsx +104 -0
  91. package/src/blocks/e-commerce/product-overview/product-overview-5-reviews-tab.tsx +82 -0
  92. package/src/blocks/e-commerce/product-overview/product-overview-5-tabs.tsx +69 -0
  93. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +274 -0
  94. package/src/blocks/e-commerce/product-overview/product-quick-view-card.tsx +135 -0
  95. package/src/blocks/e-commerce/product-overview/product-quick-view-dialog.tsx +393 -0
  96. package/src/blocks/e-commerce/product-overview/product-quick-view.tsx +136 -0
  97. package/src/blocks/e-commerce/reviews/review-rating-1-review-form.tsx +133 -0
  98. package/src/blocks/e-commerce/reviews/review-rating-1.tsx +178 -0
  99. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +246 -0
  100. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +142 -0
  101. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +291 -0
  102. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +110 -0
  103. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +51 -0
  104. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +146 -0
  105. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +295 -0
  106. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +142 -0
  107. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +342 -0
  108. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +146 -0
  109. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +391 -0
  110. package/src/blocks/e-commerce/store-containers/store-container-1.tsx +29 -0
  111. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +244 -0
  112. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +283 -0
  113. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +318 -0
  114. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +205 -0
  115. package/src/blocks/marketing/banners/avatar-group.tsx +69 -0
  116. package/src/blocks/marketing/banners/banner-1.tsx +188 -0
  117. package/src/blocks/marketing/cta/cta-section-1.tsx +112 -0
  118. package/src/blocks/marketing/faq/faq-block-1.tsx +45 -0
  119. package/src/blocks/marketing/features/feature-1.tsx +97 -0
  120. package/src/blocks/marketing/features/feature-2.tsx +82 -0
  121. package/src/blocks/marketing/footers/footer-section-1.tsx +141 -0
  122. package/src/blocks/marketing/footers/footer-section-3.tsx +192 -0
  123. package/src/blocks/marketing/footers/footer-section-4.tsx +355 -0
  124. package/src/blocks/marketing/hero-sections/avatar-group.tsx +86 -0
  125. package/src/blocks/marketing/hero-sections/hero-section-1.tsx +135 -0
  126. package/src/blocks/marketing/logo-clouds/logo-cloud-1-logo-icon.tsx +52 -0
  127. package/src/blocks/marketing/logo-clouds/logo-cloud-1.tsx +56 -0
  128. package/src/blocks/marketing/navbars/navbar-1.tsx +267 -0
  129. package/src/blocks/marketing/navbars/navbar-2.tsx +351 -0
  130. package/src/blocks/marketing/navbars/navbar-3.tsx +303 -0
  131. package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +118 -0
  132. package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +248 -0
  133. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +177 -0
  134. package/src/blocks/marketing/navbars/navbar-4-mobile-nav.tsx +104 -0
  135. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +219 -0
  136. package/src/blocks/marketing/navbars/navbar-4-search-bar.tsx +52 -0
  137. package/src/blocks/marketing/navbars/navbar-4.tsx +411 -0
  138. package/src/blocks/marketing/pricing/pricing-section-1.tsx +94 -0
  139. package/src/blocks/marketing/statistics/statistic-1.tsx +93 -0
  140. package/src/blocks/marketing/testimonials/testimonial-1.tsx +141 -0
  141. package/src/blocks/marketing/testimonials/testimonial-2.tsx +108 -0
  142. package/src/blocks/shared/reaction-toggle.tsx +145 -0
  143. package/src/hooks/use-mobile.ts +19 -0
  144. package/src/index.ts +200 -0
  145. package/src/lib/contracts.test.ts +32 -0
  146. package/src/lib/contracts.ts +235 -0
  147. package/src/lib/types/menu.ts +43 -0
  148. package/src/lib/types.ts +4 -0
  149. package/src/lib/utils.ts +10 -0
  150. package/src/ui/accordion.tsx +65 -0
  151. package/src/ui/avatar.tsx +108 -0
  152. package/src/ui/badge.tsx +47 -0
  153. package/src/ui/breadcrumb.tsx +108 -0
  154. package/src/ui/button-group.tsx +83 -0
  155. package/src/ui/button.tsx +63 -0
  156. package/src/ui/card.tsx +91 -0
  157. package/src/ui/carousel.tsx +241 -0
  158. package/src/ui/dialog.tsx +158 -0
  159. package/src/ui/drawer.tsx +134 -0
  160. package/src/ui/dropdown-menu.tsx +256 -0
  161. package/src/ui/empty.tsx +103 -0
  162. package/src/ui/field.tsx +248 -0
  163. package/src/ui/input-group.tsx +170 -0
  164. package/src/ui/input-otp.tsx +76 -0
  165. package/src/ui/input.tsx +20 -0
  166. package/src/ui/label.tsx +23 -0
  167. package/src/ui/navigation-menu.tsx +167 -0
  168. package/src/ui/pagination.tsx +127 -0
  169. package/src/ui/progress.tsx +30 -0
  170. package/src/ui/radio-group.tsx +44 -0
  171. package/src/ui/select.tsx +189 -0
  172. package/src/ui/separator.tsx +27 -0
  173. package/src/ui/sheet.tsx +142 -0
  174. package/src/ui/sidebar.tsx +726 -0
  175. package/src/ui/skeleton.tsx +13 -0
  176. package/src/ui/table.tsx +115 -0
  177. package/src/ui/tabs.tsx +90 -0
  178. package/src/ui/textarea.tsx +17 -0
  179. package/src/ui/toggle-group.tsx +83 -0
  180. package/src/ui/toggle.tsx +46 -0
  181. package/src/ui/tooltip.tsx +56 -0
  182. package/tsconfig.json +20 -0
  183. package/tsup.config.ts +16 -0
  184. package/vitest.config.ts +9 -0
@@ -0,0 +1,97 @@
1
+ import { Badge } from '../../../ui/badge'
2
+ import { Button } from '../../../ui/button'
3
+ import { Card, CardContent } from '../../../ui/card'
4
+ import { ArrowRight } from 'lucide-react'
5
+ import type { LucideIcon } from 'lucide-react'
6
+
7
+ export interface Feature1Item {
8
+ id: string
9
+ title: string
10
+ description: string
11
+ icon: LucideIcon
12
+ }
13
+
14
+ export interface Feature1Props {
15
+ items: Feature1Item[]
16
+ badge?: string
17
+ title?: string
18
+ description?: string
19
+ learnMoreLabel?: string
20
+ ctaLabel?: string
21
+ onCtaClick?: () => void
22
+ onLearnMore?: (item: Feature1Item) => void
23
+ }
24
+
25
+ export function Feature1({
26
+ items,
27
+ badge,
28
+ title,
29
+ description,
30
+ learnMoreLabel,
31
+ ctaLabel,
32
+ onCtaClick,
33
+ onLearnMore,
34
+ }: Feature1Props) {
35
+ return (
36
+ <section className='mx-auto w-full max-w-7xl px-4 py-12 sm:px-6 md:py-16 lg:px-8'>
37
+ {(badge || title || description) && (
38
+ <header className='mx-auto mb-12 max-w-2xl text-center'>
39
+ {badge && (
40
+ <Badge variant='secondary' className='h-auto mb-4 rounded-full px-4 py-1 font-medium'>
41
+ {badge}
42
+ </Badge>
43
+ )}
44
+ {title && (
45
+ <h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>{title}</h2>
46
+ )}
47
+ {description && (
48
+ <p className='text-muted-foreground text-pretty'>{description}</p>
49
+ )}
50
+ </header>
51
+ )}
52
+
53
+ <div className='grid gap-4 sm:grid-cols-2 sm:gap-6 md:grid-cols-3'>
54
+ {items.map(feature => (
55
+ <article key={feature.id} className='group'>
56
+ <Card className='relative h-full overflow-hidden transition-all hover:shadow-md'>
57
+ <CardContent className='px-6'>
58
+ <div className='bg-secondary text-secondary-foreground mb-4 inline-flex size-12 items-center justify-center rounded-xl'>
59
+ <feature.icon className='size-5' aria-hidden='true' />
60
+ </div>
61
+ <h3 className='mb-2 text-lg font-semibold'>{feature.title}</h3>
62
+ <p className='text-muted-foreground mb-4 text-sm'>{feature.description}</p>
63
+
64
+ {learnMoreLabel && (
65
+ <Button
66
+ variant='link'
67
+ size='sm'
68
+ className='h-8 px-3 text-xs text-muted-foreground hover:text-foreground h-auto cursor-pointer !p-0 text-sm'
69
+ onClick={onLearnMore ? () => onLearnMore(feature) : undefined}
70
+ >
71
+ {learnMoreLabel}
72
+ <ArrowRight />
73
+ </Button>
74
+ )}
75
+ </CardContent>
76
+ </Card>
77
+ </article>
78
+ ))}
79
+ </div>
80
+
81
+ {ctaLabel && (
82
+ <div className='mt-12 text-center'>
83
+ <Button
84
+ size='lg'
85
+ className='h-10 cursor-pointer rounded-lg !px-8'
86
+ onClick={onCtaClick}
87
+ >
88
+ {ctaLabel}
89
+ <ArrowRight />
90
+ </Button>
91
+ </div>
92
+ )}
93
+ </section>
94
+ )
95
+ }
96
+
97
+ export default Feature1
@@ -0,0 +1,82 @@
1
+ import { cn } from '../../../lib/utils'
2
+ import type { LucideIcon } from 'lucide-react'
3
+
4
+ export interface Feature2Item {
5
+ id: string
6
+ title: string
7
+ description: string
8
+ icon: LucideIcon
9
+ }
10
+
11
+ export interface Feature2Props {
12
+ items: Feature2Item[]
13
+ title?: string
14
+ description?: string
15
+ image?: { src: string; alt: string }
16
+ className?: string
17
+ }
18
+
19
+ export function Feature2({
20
+ items,
21
+ title,
22
+ description,
23
+ image,
24
+ className,
25
+ }: Feature2Props) {
26
+ return (
27
+ <section
28
+ className={cn('mx-auto w-full max-w-7xl px-4 py-12 sm:px-6 md:py-16 lg:px-8', className)}
29
+ >
30
+ <div className='grid gap-12 lg:grid-cols-2 lg:gap-16'>
31
+ <div className='flex flex-col gap-10'>
32
+ {(title || description) && (
33
+ <header className='flex flex-col gap-4'>
34
+ {title && (
35
+ <h2 className='text-3xl font-bold tracking-tight text-balance sm:text-4xl lg:text-5xl'>
36
+ {title}
37
+ </h2>
38
+ )}
39
+ {description && (
40
+ <p className='text-muted-foreground text-base text-pretty md:text-lg'>
41
+ {description}
42
+ </p>
43
+ )}
44
+ </header>
45
+ )}
46
+
47
+ <div className='grid gap-6 sm:grid-cols-2 sm:gap-8'>
48
+ {items.map(item => (
49
+ <div key={item.id} className='group flex flex-col items-start gap-3 rounded-lg'>
50
+ <div className='bg-secondary text-secondary-foreground inline-flex size-10 items-center justify-center rounded-full'>
51
+ <item.icon className='size-5' aria-hidden='true' />
52
+ </div>
53
+ <div className='flex flex-col gap-1.5'>
54
+ <h3 className='text-foreground text-base font-semibold md:text-lg'>{item.title}</h3>
55
+ <p className='text-muted-foreground text-sm text-balance'>{item.description}</p>
56
+ </div>
57
+ </div>
58
+ ))}
59
+ </div>
60
+ </div>
61
+
62
+ {image && (
63
+ <div className='relative'>
64
+ <div className='bg-muted relative z-10 overflow-hidden rounded-lg shadow-sm transition-all duration-300 hover:shadow-md'>
65
+ <div className='aspect-[4/3] w-full'>
66
+ <img
67
+ src={image.src || undefined}
68
+ alt={image.alt}
69
+ className='size-full object-cover grayscale'
70
+ loading='lazy'
71
+ decoding='async'
72
+ />
73
+ </div>
74
+ </div>
75
+ </div>
76
+ )}
77
+ </div>
78
+ </section>
79
+ )
80
+ }
81
+
82
+ export default Feature2
@@ -0,0 +1,141 @@
1
+ 'use client'
2
+
3
+ import { Input } from '../../../ui/input'
4
+ import { Button } from '../../../ui/button'
5
+ import { ArrowRight } from 'lucide-react'
6
+
7
+ export interface FooterLink {
8
+ label: string
9
+ href?: string
10
+ }
11
+
12
+ export interface FooterLinkColumn {
13
+ title: string
14
+ links: FooterLink[]
15
+ }
16
+
17
+ export interface FooterNewsletter {
18
+ placeholder?: string
19
+ buttonLabel: string
20
+ onSubmit?: (email: string) => void
21
+ }
22
+
23
+ export interface FooterSection1Props {
24
+ brandName: string
25
+ brandDescription?: string
26
+ linkColumns: FooterLinkColumn[]
27
+ newsletter?: FooterNewsletter
28
+ }
29
+
30
+ export function FooterSection1({
31
+ brandName,
32
+ brandDescription,
33
+ linkColumns,
34
+ newsletter,
35
+ }: FooterSection1Props) {
36
+ return (
37
+ <footer className='bg-secondary w-full py-16' aria-label='Pie de página'>
38
+ <div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
39
+ <div className='grid grid-cols-1 gap-8 lg:grid-cols-12'>
40
+ {/* Left Section */}
41
+ <div className='me-8 md:col-span-5'>
42
+ <div className='mb-6 flex items-center gap-2'>
43
+ <svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
44
+ <path
45
+ 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'
46
+ fill='currentColor'
47
+ />
48
+ <path
49
+ 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'
50
+ fill='currentColor'
51
+ />
52
+ <path
53
+ 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'
54
+ fill='currentColor'
55
+ />
56
+ <path
57
+ 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'
58
+ fill='currentColor'
59
+ />
60
+ <path
61
+ 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'
62
+ fill='currentColor'
63
+ />
64
+ <path d='M21.1694 10.5806L14.5651 17.1849' stroke='currentColor' />
65
+ <path d='M22.1694 14.5806L18.5632 18.1868' stroke='currentColor' />
66
+ <circle cx='13.1' cy='26.1' r='1.7' stroke='currentColor' />
67
+ <circle cx='22.1' cy='26.1' r='1.7' stroke='currentColor' />
68
+ </svg>
69
+ <span className='text-xl font-semibold'>{brandName}</span>
70
+ </div>
71
+ {brandDescription && (
72
+ <p className='text-muted-foreground mb-8 max-w-sm text-sm leading-relaxed text-balance'>
73
+ {brandDescription}
74
+ </p>
75
+ )}
76
+ {newsletter && (
77
+ <form
78
+ className='relative'
79
+ onSubmit={e => {
80
+ e.preventDefault()
81
+ if (newsletter.onSubmit) {
82
+ const email = new FormData(e.currentTarget).get('email')
83
+ if (typeof email === 'string') {
84
+ newsletter.onSubmit(email)
85
+ }
86
+ }
87
+ }}
88
+ >
89
+ <Input
90
+ type='email'
91
+ name='email'
92
+ placeholder={newsletter.placeholder}
93
+ className='border-muted-foreground/20 bg-background/80 h-12 pe-32'
94
+ aria-label='Correo electrónico'
95
+ />
96
+ <Button
97
+ type='submit'
98
+ variant='default'
99
+ className='absolute end-1 top-1 h-10 cursor-pointer px-4'
100
+ aria-label='Suscribirse a la newsletter'
101
+ >
102
+ {newsletter.buttonLabel}
103
+ <ArrowRight />
104
+ </Button>
105
+ </form>
106
+ )}
107
+ </div>
108
+
109
+ {/* Right Section */}
110
+ <div className='md:col-span-7'>
111
+ <div className='grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3'>
112
+ {linkColumns.map(column => (
113
+ <div key={column.title}>
114
+ <h3 className='mb-4 text-lg font-medium'>{column.title}</h3>
115
+ <ul className='flex flex-col gap-3'>
116
+ {column.links.map(link => (
117
+ <li key={link.label}>
118
+ {link.href ? (
119
+ <a
120
+ href={link.href}
121
+ className='text-muted-foreground hover:text-primary text-sm transition-colors'
122
+ >
123
+ {link.label}
124
+ </a>
125
+ ) : (
126
+ <span className='text-muted-foreground text-sm'>{link.label}</span>
127
+ )}
128
+ </li>
129
+ ))}
130
+ </ul>
131
+ </div>
132
+ ))}
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </footer>
138
+ )
139
+ }
140
+
141
+ export default FooterSection1
@@ -0,0 +1,192 @@
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