@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.
Files changed (178) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +228 -68
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
  7. package/src/blocks/application/login-forms/login-page.tsx +240 -0
  8. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
  9. package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
  10. package/src/blocks/application/verification/verify-1.tsx +2 -2
  11. package/src/blocks/application/viewport-ownership.test.tsx +41 -0
  12. package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
  13. package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
  14. package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
  15. package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
  16. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
  17. package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
  18. package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
  19. package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
  20. package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
  21. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
  22. package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
  23. package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
  24. package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
  25. package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
  26. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
  27. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
  28. package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
  29. package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
  30. package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
  31. package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
  32. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  33. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  34. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  35. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  36. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  37. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  38. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  39. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  40. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  41. package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
  42. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  43. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  44. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  45. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  46. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  47. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  48. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  49. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  50. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  51. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  52. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  53. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  54. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  55. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  56. package/src/blocks/marketing/features/feature.tsx +195 -0
  57. package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
  58. package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
  59. package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
  60. package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
  61. package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
  62. package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
  63. package/src/blocks/marketing/footers/footer-section.tsx +532 -0
  64. package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
  65. package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
  66. package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
  67. package/src/blocks/marketing/navbars/navbar.tsx +735 -0
  68. package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
  69. package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
  70. package/src/index.ts +156 -128
  71. package/src/lib/contracts-curated.test.ts +47 -29
  72. package/src/lib/contracts.test.ts +198 -2
  73. package/src/lib/contracts.ts +410 -16
  74. package/src/ui/button-group.tsx +1 -1
  75. package/src/ui/carousel.tsx +1 -1
  76. package/src/ui/dialog.tsx +1 -1
  77. package/src/ui/field.tsx +2 -2
  78. package/src/ui/input-group.tsx +3 -3
  79. package/src/ui/pagination.tsx +1 -1
  80. package/src/ui/sidebar.tsx +7 -7
  81. package/src/ui/toggle-group.tsx +1 -1
  82. package/tsconfig.build.json +12 -0
  83. package/tsup.config.ts +11 -7
  84. package/.turbo/turbo-build.log +0 -24
  85. package/.turbo/turbo-test.log +0 -47
  86. package/.turbo/turbo-type-check.log +0 -0
  87. package/dist/.tsbuildinfo +0 -1
  88. package/dist/contracts.d.mts +0 -1732
  89. package/dist/contracts.d.ts +0 -1732
  90. package/dist/contracts.js +0 -423
  91. package/dist/contracts.js.map +0 -1
  92. package/dist/contracts.mjs +0 -365
  93. package/dist/contracts.mjs.map +0 -1
  94. package/dist/index.d.mts +0 -3068
  95. package/dist/index.d.ts +0 -3068
  96. package/dist/index.js +0 -17834
  97. package/dist/index.js.map +0 -1
  98. package/dist/index.mjs +0 -17740
  99. package/dist/index.mjs.map +0 -1
  100. package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
  101. package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
  102. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
  103. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
  104. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
  105. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
  106. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
  107. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
  108. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
  109. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
  110. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
  111. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
  112. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
  113. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
  114. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
  115. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
  116. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
  117. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
  118. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
  119. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
  120. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
  121. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
  122. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
  123. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
  124. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
  125. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
  126. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
  127. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
  128. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
  129. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
  130. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
  131. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  132. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  133. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  134. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  135. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  136. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  137. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  138. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  139. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  140. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  141. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  142. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  143. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  144. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  145. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  146. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  147. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  148. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  149. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  150. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  151. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  152. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  153. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  154. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  155. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  156. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  157. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  158. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  159. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  160. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  161. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  162. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  163. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  164. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  165. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  166. package/src/blocks/marketing/features/feature-2.tsx +0 -82
  167. package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
  168. package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
  169. package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
  170. package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
  171. package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
  172. package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
  173. package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
  174. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
  175. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
  176. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
  177. package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
  178. package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
@@ -1,260 +0,0 @@
1
- 'use client'
2
-
3
- import { useState } from 'react'
4
- import { ChevronDown, Search, SlidersHorizontal, X } from 'lucide-react'
5
-
6
- import { Badge } from '../../../ui/badge'
7
- import { Button } from '../../../ui/button'
8
- import {
9
- DropdownMenu,
10
- DropdownMenuContent,
11
- DropdownMenuItem,
12
- DropdownMenuSeparator,
13
- DropdownMenuTrigger,
14
- } from '../../../ui/dropdown-menu'
15
- import { Input } from '../../../ui/input'
16
- import { cn } from '../../../lib/utils'
17
-
18
- export interface CategoryFilter1Category {
19
- id: string
20
- name: string
21
- count: number
22
- }
23
-
24
- export interface CategoryFilter1PriceRange {
25
- id: string
26
- label: string
27
- min: number
28
- max: number | null
29
- }
30
-
31
- export interface CategoryFilter1SortOption {
32
- id: string
33
- label: string
34
- }
35
-
36
- export interface CategoryFilter1Filters {
37
- category: string
38
- priceRange: string
39
- sort: string
40
- search: string
41
- }
42
-
43
- export interface CategoryFilter1Props {
44
- title?: string
45
- description?: string
46
- categories: CategoryFilter1Category[]
47
- priceRanges: CategoryFilter1PriceRange[]
48
- sortOptions: CategoryFilter1SortOption[]
49
- onFilterChange?: (filters: CategoryFilter1Filters) => void
50
- }
51
-
52
- export function CategoryFilter1({
53
- title,
54
- description,
55
- categories,
56
- priceRanges,
57
- sortOptions,
58
- onFilterChange,
59
- }: CategoryFilter1Props) {
60
- const [selectedCategory, setSelectedCategory] = useState('all')
61
- const [selectedPriceRange, setSelectedPriceRange] = useState('all')
62
- const [selectedSort, setSelectedSort] = useState('featured')
63
- const [searchQuery, setSearchQuery] = useState('')
64
-
65
- const activeFilters: { type: string; label: string; value: string }[] = []
66
- if (selectedCategory !== 'all') {
67
- const category = categories.find(c => c.id === selectedCategory)
68
- if (category) activeFilters.push({ type: 'category', label: category.name, value: selectedCategory })
69
- }
70
- if (selectedPriceRange !== 'all') {
71
- const priceRange = priceRanges.find(p => p.id === selectedPriceRange)
72
- if (priceRange) activeFilters.push({ type: 'price', label: priceRange.label, value: selectedPriceRange })
73
- }
74
- if (searchQuery) {
75
- activeFilters.push({ type: 'search', label: `"${searchQuery}"`, value: searchQuery })
76
- }
77
-
78
- const selectedCategoryCount = categories.find(c => c.id === selectedCategory)?.count ?? 0
79
-
80
- const clearFilter = (type: string) => {
81
- let nextCategory = selectedCategory
82
- let nextPrice = selectedPriceRange
83
- let nextSearch = searchQuery
84
- if (type === 'category') nextCategory = 'all'
85
- if (type === 'price') nextPrice = 'all'
86
- if (type === 'search') nextSearch = ''
87
- setSelectedCategory(nextCategory)
88
- setSelectedPriceRange(nextPrice)
89
- setSearchQuery(nextSearch)
90
- onFilterChange?.({ category: nextCategory, priceRange: nextPrice, sort: selectedSort, search: nextSearch })
91
- }
92
-
93
- const clearAllFilters = () => {
94
- setSelectedCategory('all')
95
- setSelectedPriceRange('all')
96
- setSearchQuery('')
97
- onFilterChange?.({ category: 'all', priceRange: 'all', sort: selectedSort, search: '' })
98
- }
99
-
100
- const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
101
- const next = e.target.value
102
- setSearchQuery(next)
103
- onFilterChange?.({ category: selectedCategory, priceRange: selectedPriceRange, sort: selectedSort, search: next })
104
- }
105
-
106
- const handleSortSelect = (id: string) => {
107
- setSelectedSort(id)
108
- onFilterChange?.({ category: selectedCategory, priceRange: selectedPriceRange, sort: id, search: searchQuery })
109
- }
110
-
111
- const handleCategorySelect = (id: string) => {
112
- setSelectedCategory(id)
113
- onFilterChange?.({ category: id, priceRange: selectedPriceRange, sort: selectedSort, search: searchQuery })
114
- }
115
-
116
- const handlePriceRangeSelect = (id: string) => {
117
- setSelectedPriceRange(id)
118
- onFilterChange?.({ category: selectedCategory, priceRange: id, sort: selectedSort, search: searchQuery })
119
- }
120
-
121
- return (
122
- <section className="py-8">
123
- <div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
124
- <div className="mb-8">
125
- {title ? <h2 className="text-3xl font-bold tracking-tight text-balance">{title}</h2> : null}
126
- {description ? <p className="text-muted-foreground mt-2">{description}</p> : null}
127
- {!description && title ? (
128
- <p className="text-muted-foreground mt-2">
129
- Browse our collection of {selectedCategoryCount} products
130
- </p>
131
- ) : null}
132
- </div>
133
-
134
- <div className="mb-6 flex flex-col gap-4">
135
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
136
- <div className="relative max-w-md flex-1">
137
- <Search className="text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2" />
138
- <Input
139
- placeholder="Search products..."
140
- value={searchQuery}
141
- onChange={handleSearchChange}
142
- className="h-9 pl-10"
143
- />
144
- </div>
145
-
146
- <DropdownMenu>
147
- <DropdownMenuTrigger asChild>
148
- <Button variant="outline" className="h-9 w-full cursor-pointer px-4 py-2 sm:w-auto">
149
- <SlidersHorizontal data-icon="inline-start" />
150
- Sort: {sortOptions.find(s => s.id === selectedSort)?.label}
151
- <ChevronDown data-icon="inline-end" />
152
- </Button>
153
- </DropdownMenuTrigger>
154
- <DropdownMenuContent align="end" className="w-56">
155
- {sortOptions.map(option => (
156
- <DropdownMenuItem
157
- key={option.id}
158
- onClick={() => handleSortSelect(option.id)}
159
- className={selectedSort === option.id ? 'bg-accent' : ''}
160
- >
161
- {option.label}
162
- </DropdownMenuItem>
163
- ))}
164
- </DropdownMenuContent>
165
- </DropdownMenu>
166
- </div>
167
-
168
- <div className="flex flex-wrap gap-3">
169
- <DropdownMenu>
170
- <DropdownMenuTrigger asChild>
171
- <Button variant="outline" size="sm" className="h-8 cursor-pointer px-3 text-xs">
172
- Category: {categories.find(c => c.id === selectedCategory)?.name}
173
- <ChevronDown data-icon="inline-end" />
174
- </Button>
175
- </DropdownMenuTrigger>
176
- <DropdownMenuContent className="w-56">
177
- {categories.map(category => (
178
- <DropdownMenuItem
179
- key={category.id}
180
- onClick={() => handleCategorySelect(category.id)}
181
- className={cn(selectedCategory === category.id && 'bg-accent')}
182
- >
183
- <div className="flex w-full items-center justify-between">
184
- <span>{category.name}</span>
185
- <Badge variant="secondary" className="px-2.5 py-0.5 text-xs font-semibold">
186
- {category.count}
187
- </Badge>
188
- </div>
189
- </DropdownMenuItem>
190
- ))}
191
- </DropdownMenuContent>
192
- </DropdownMenu>
193
-
194
- <DropdownMenu>
195
- <DropdownMenuTrigger asChild>
196
- <Button variant="outline" size="sm" className="h-8 cursor-pointer px-3 text-xs">
197
- Price: {priceRanges.find(p => p.id === selectedPriceRange)?.label}
198
- <ChevronDown data-icon="inline-end" />
199
- </Button>
200
- </DropdownMenuTrigger>
201
- <DropdownMenuContent className="w-48">
202
- {priceRanges.map(range => (
203
- <DropdownMenuItem
204
- key={range.id}
205
- onClick={() => handlePriceRangeSelect(range.id)}
206
- className={cn(selectedPriceRange === range.id && 'bg-accent')}
207
- >
208
- {range.label}
209
- </DropdownMenuItem>
210
- ))}
211
- </DropdownMenuContent>
212
- </DropdownMenu>
213
- </div>
214
-
215
- {activeFilters.length > 0 ? (
216
- <div className="flex flex-wrap items-center gap-2">
217
- <span className="text-muted-foreground text-sm font-medium">Active filters:</span>
218
- {activeFilters.map(filter => (
219
- <Badge key={filter.type} variant="secondary" className="px-2.5 py-0.5 font-semibold">
220
- {filter.label}
221
- <Button
222
- variant="ghost"
223
- size="sm"
224
- className="!p-1 h-auto cursor-pointer text-inherit"
225
- onClick={() => clearFilter(filter.type)}
226
- >
227
- <X className="size-3" />
228
- </Button>
229
- </Badge>
230
- ))}
231
- <DropdownMenuSeparator className="mx-2" />
232
- <Button
233
- variant="ghost"
234
- size="sm"
235
- onClick={clearAllFilters}
236
- className="h-auto cursor-pointer p-1.5 text-xs text-muted-foreground"
237
- >
238
- Clear all
239
- </Button>
240
- </div>
241
- ) : null}
242
- </div>
243
-
244
- <div className="bg-muted/50 rounded-lg border p-4">
245
- <div className="flex items-center justify-between">
246
- <div className="flex items-center gap-4">
247
- <span className="text-sm font-medium">Showing {selectedCategoryCount} results</span>
248
- {searchQuery && <span className="text-muted-foreground text-sm">for &quot;{searchQuery}&quot;</span>}
249
- </div>
250
- <div className="text-muted-foreground text-xs">
251
- Sorted by {sortOptions.find(s => s.id === selectedSort)?.label}
252
- </div>
253
- </div>
254
- </div>
255
- </div>
256
- </section>
257
- )
258
- }
259
-
260
- export default CategoryFilter1
@@ -1,222 +0,0 @@
1
- 'use client'
2
-
3
- import { useId } from 'react'
4
-
5
- import { Button } from '../../../ui/button'
6
- import { CardTitle } from '../../../ui/card'
7
- import { Input } from '../../../ui/input'
8
- import { CheckboxCheck } from '../checkout-forms/checkbox-check'
9
-
10
- import type {
11
- CategoryFilter2Category,
12
- CategoryFilter2Collection,
13
- CategoryFilter2Color,
14
- CategoryFilter2PriceRange,
15
- } from './category-filter-2'
16
-
17
- interface CategoryFilter2FilterPanelProps {
18
- selectedText?: string
19
- clearAllLabel?: string
20
- priceRangeTitle?: string
21
- priceRange: number[]
22
- /**
23
- * Real backend-provided range only. When absent the price section is not
24
- * rendered — the block never fabricates a `0..1000` range (E2 design
25
- * Decision 8 / spec "Price controls require real price metadata").
26
- */
27
- priceRangeConfig?: CategoryFilter2PriceRange
28
- minLabel?: string
29
- maxLabel?: string
30
- onPriceChange: (range: number[]) => void
31
- categoriesTitle?: string
32
- categories: CategoryFilter2Category[]
33
- selectedCategories: string[]
34
- onCategoryChange: (category: string, checked: boolean) => void
35
- collectionsTitle?: string
36
- collections: CategoryFilter2Collection[]
37
- selectedCollections: string[]
38
- onCollectionChange: (collection: string, checked: boolean) => void
39
- sizesTitle?: string
40
- sizes: string[]
41
- selectedSize: string | null
42
- onSizeChange: (size: string | null) => void
43
- colorsTitle?: string
44
- colors: CategoryFilter2Color[]
45
- selectedColor: string | null
46
- onColorChange: (color: string | null) => void
47
- onClearAll: () => void
48
- }
49
-
50
- export function CategoryFilter2FilterPanel({
51
- selectedText = 'Filtros',
52
- clearAllLabel = 'Restablecer',
53
- priceRangeTitle = 'Rango de precio',
54
- priceRange,
55
- priceRangeConfig,
56
- minLabel = 'Mínimo',
57
- maxLabel = 'Máximo',
58
- onPriceChange,
59
- categoriesTitle = 'Categoría',
60
- categories,
61
- selectedCategories,
62
- onCategoryChange,
63
- collectionsTitle = 'Colección',
64
- collections,
65
- selectedCollections,
66
- onCollectionChange,
67
- sizesTitle = 'Tamaño',
68
- sizes,
69
- selectedSize,
70
- onSizeChange,
71
- colorsTitle = 'Color',
72
- colors,
73
- selectedColor,
74
- onColorChange,
75
- onClearAll,
76
- }: CategoryFilter2FilterPanelProps) {
77
- const priceInputId = useId()
78
- const categoryInputId = useId()
79
- const collectionInputId = useId()
80
-
81
- return (
82
- <>
83
- <div className="flex flex-col gap-6">
84
- <div className="mb-3 flex items-center justify-between">
85
- <h1 className="font-semibold">{selectedText}</h1>
86
- <Button variant="ghost" size="sm" className="h-8 cursor-pointer px-2 text-xs" onClick={onClearAll}>
87
- {clearAllLabel}
88
- </Button>
89
- </div>
90
- </div>
91
-
92
- {priceRangeConfig ? (
93
- <div className="mt-4 flex flex-col gap-4">
94
- <CardTitle className="font-semibold">{priceRangeTitle}</CardTitle>
95
- <div className="flex items-center gap-3">
96
- <div className="flex flex-1 flex-col gap-1">
97
- <label htmlFor={`${priceInputId}-min`}>{minLabel}</label>
98
- <Input
99
- id={`${priceInputId}-min`}
100
- type="number"
101
- value={priceRange[0] ?? priceRangeConfig.min}
102
- onChange={e => {
103
- const value = parseInt(e.target.value)
104
- if (!isNaN(value) && value >= priceRangeConfig.min) {
105
- onPriceChange([value, priceRange[1] ?? priceRangeConfig.max])
106
- }
107
- }}
108
- min={priceRangeConfig.min}
109
- max={priceRange[1] ?? priceRangeConfig.max}
110
- step={priceRangeConfig.step}
111
- className="h-9"
112
- />
113
- </div>
114
- <div className="mt-5 flex-none">-</div>
115
- <div className="flex flex-1 flex-col gap-1">
116
- <label htmlFor={`${priceInputId}-max`}>{maxLabel}</label>
117
- <Input
118
- id={`${priceInputId}-max`}
119
- type="number"
120
- value={priceRange[1] ?? priceRangeConfig.max}
121
- onChange={e => {
122
- const value = parseInt(e.target.value)
123
- if (!isNaN(value) && value <= priceRangeConfig.max) {
124
- onPriceChange([priceRange[0] ?? priceRangeConfig.min, value])
125
- }
126
- }}
127
- min={priceRange[0] ?? priceRangeConfig.min}
128
- max={priceRangeConfig.max}
129
- step={priceRangeConfig.step}
130
- className="h-9"
131
- />
132
- </div>
133
- </div>
134
- </div>
135
- ) : null}
136
-
137
- {categories.length > 0 ? (
138
- <div className="mt-4 flex flex-col gap-4">
139
- <CardTitle className="font-semibold">{categoriesTitle}</CardTitle>
140
- <div className="flex flex-col gap-2">
141
- {categories.map(category => (
142
- <div key={category.id} className="flex items-center gap-2">
143
- <CheckboxCheck
144
- id={`${categoryInputId}-${category.id}`}
145
- checked={selectedCategories.includes(category.id)}
146
- onCheckedChange={checked => onCategoryChange(category.id, checked as boolean)}
147
- />
148
- <label htmlFor={`${categoryInputId}-${category.id}`} className="text-sm">
149
- {category.label}
150
- </label>
151
- </div>
152
- ))}
153
- </div>
154
- </div>
155
- ) : null}
156
-
157
- {collections.length > 0 ? (
158
- <div className="mt-4 flex flex-col gap-4">
159
- <CardTitle className="font-semibold">{collectionsTitle}</CardTitle>
160
- <div className="flex flex-col gap-2">
161
- {collections.map(collection => (
162
- <div key={collection.id} className="flex items-center gap-2">
163
- <CheckboxCheck
164
- id={`${collectionInputId}-${collection.id}`}
165
- checked={selectedCollections.includes(collection.id)}
166
- onCheckedChange={checked => onCollectionChange(collection.id, checked as boolean)}
167
- />
168
- <label htmlFor={`${collectionInputId}-${collection.id}`} className="text-sm">
169
- {collection.label}
170
- </label>
171
- </div>
172
- ))}
173
- </div>
174
- </div>
175
- ) : null}
176
-
177
- {sizes.length > 0 ? (
178
- <div className="mt-4 flex flex-col gap-4">
179
- <CardTitle className="font-semibold">{sizesTitle}</CardTitle>
180
- <div className="flex flex-wrap gap-2">
181
- {sizes.map(size => (
182
- <Button
183
- key={size}
184
- variant={selectedSize === size ? 'default' : 'outline'}
185
- size="sm"
186
- className="h-8 cursor-pointer px-3 text-xs"
187
- onClick={() => onSizeChange(size === selectedSize ? null : size)}
188
- aria-pressed={selectedSize === size}
189
- >
190
- {size}
191
- </Button>
192
- ))}
193
- </div>
194
- </div>
195
- ) : null}
196
-
197
- {colors.length > 0 ? (
198
- <div className="mt-4 flex flex-col gap-4">
199
- <CardTitle className="font-semibold">{colorsTitle}</CardTitle>
200
- <div className="flex flex-wrap gap-2">
201
- {colors.map(color => (
202
- <Button
203
- key={color.id}
204
- onClick={() => onColorChange(color.id === selectedColor ? null : color.id)}
205
- className={`relative size-8 cursor-pointer rounded-sm transition-transform duration-200 ${
206
- selectedColor === color.id ? 'ring-primary scale-105 ring-2' : 'hover:ring-foreground/20 hover:ring-1'
207
- } ${color.id === 'white' ? 'border-border border' : ''}`}
208
- style={{ backgroundColor: color.color }}
209
- aria-pressed={selectedColor === color.id}
210
- aria-label={color.id}
211
- >
212
- {selectedColor === color.id && <div className="border-background absolute inset-0 rounded-sm border-2" />}
213
- </Button>
214
- ))}
215
- </div>
216
- </div>
217
- ) : null}
218
- </>
219
- )
220
- }
221
-
222
- export default CategoryFilter2FilterPanel