reroute-js 0.46.0 → 1.0.0-alpha.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 (214) hide show
  1. package/README.md +2 -4
  2. package/_/store/package.json +1 -3
  3. package/_/store/src/client/components/Header.tsx +8 -20
  4. package/_/store/src/client/components/ProductCard.tsx +9 -16
  5. package/_/store/src/client/routes/[404].tsx +20 -31
  6. package/_/store/src/client/routes/categories/[category].tsx +23 -28
  7. package/_/store/src/client/routes/categories/index.tsx +19 -27
  8. package/_/store/src/client/routes/index.tsx +27 -37
  9. package/_/store/src/client/routes/products/[id].tsx +35 -48
  10. package/_/store/src/client/routes/products/index.tsx +20 -50
  11. package/_/store/src/client/theme.css +907 -95
  12. package/cli/bin.d.ts +1 -1
  13. package/cli/bin.js +78 -176
  14. package/cli/bin.js.map +11 -11
  15. package/cli/index.d.ts +1 -1
  16. package/cli/index.js +17 -112
  17. package/cli/index.js.map +5 -5
  18. package/cli/src/cli.d.ts +1 -1
  19. package/cli/src/commands/analyze.d.ts +1 -1
  20. package/cli/src/commands/build.d.ts +1 -1
  21. package/cli/src/commands/dev.d.ts +1 -1
  22. package/cli/src/commands/gen.d.ts +1 -1
  23. package/cli/src/commands/gen.d.ts.map +1 -1
  24. package/cli/src/commands/index.d.ts +1 -1
  25. package/cli/src/commands/init.d.ts +1 -1
  26. package/cli/src/commands/lib/assets.d.ts +1 -1
  27. package/cli/src/commands/lib/bundler.d.ts +1 -1
  28. package/cli/src/commands/lib/command.d.ts +1 -1
  29. package/cli/src/commands/lib/env.d.ts +1 -1
  30. package/cli/src/commands/lib/index.d.ts +2 -2
  31. package/cli/src/commands/lib/index.d.ts.map +1 -1
  32. package/cli/src/commands/lib/log.d.ts +1 -1
  33. package/cli/src/commands/lib/markdown/availability.d.ts +1 -1
  34. package/cli/src/commands/lib/markdown/index.d.ts +1 -1
  35. package/cli/src/commands/lib/markdown/processor.d.ts +1 -1
  36. package/cli/src/commands/lib/production.d.ts +1 -1
  37. package/cli/src/commands/lib/server.d.ts +1 -1
  38. package/cli/src/commands/lib/streaming/analyzer.d.ts +1 -1
  39. package/cli/src/commands/lib/streaming/suspense.d.ts +1 -1
  40. package/cli/src/commands/lib/terminal-ui.d.ts +1 -1
  41. package/cli/src/commands/lib/theme.d.ts +20 -0
  42. package/cli/src/commands/lib/theme.d.ts.map +1 -0
  43. package/cli/src/commands/lib/version.d.ts +1 -1
  44. package/cli/src/commands/og.d.ts +1 -1
  45. package/cli/src/commands/start.d.ts +1 -1
  46. package/cli/src/index.d.ts +1 -1
  47. package/core/index.d.ts +1 -1
  48. package/core/index.js +12 -12
  49. package/core/index.js.map +6 -6
  50. package/core/src/bundler/hash.d.ts +1 -1
  51. package/core/src/bundler/index.d.ts +1 -1
  52. package/core/src/config.d.ts +1 -1
  53. package/core/src/content/discovery.d.ts +1 -1
  54. package/core/src/content/index.d.ts +1 -1
  55. package/core/src/content/metadata.d.ts +1 -1
  56. package/core/src/i18n/catalog.d.ts +1 -1
  57. package/core/src/i18n/discover.d.ts +1 -1
  58. package/core/src/i18n/index.d.ts +1 -1
  59. package/core/src/i18n/messages.d.ts +1 -1
  60. package/core/src/i18n/negotiate.d.ts +1 -1
  61. package/core/src/i18n/pathname.d.ts +1 -1
  62. package/core/src/i18n/resolve.d.ts +1 -1
  63. package/core/src/i18n/types.d.ts +1 -1
  64. package/core/src/index.d.ts +1 -1
  65. package/core/src/llms/extractor.d.ts +1 -1
  66. package/core/src/llms/formatter.d.ts +1 -1
  67. package/core/src/llms/full-generator.d.ts +1 -1
  68. package/core/src/llms/index-generator.d.ts +1 -1
  69. package/core/src/llms/index.d.ts +1 -1
  70. package/core/src/og/discovery.d.ts +1 -1
  71. package/core/src/og/index.d.ts +1 -1
  72. package/core/src/og/meta.d.ts +1 -1
  73. package/core/src/og/render.d.ts +1 -1
  74. package/core/src/og/types.d.ts +1 -1
  75. package/core/src/robots/discovery.d.ts +1 -1
  76. package/core/src/robots/generator.d.ts +1 -1
  77. package/core/src/robots/index.d.ts +1 -1
  78. package/core/src/robots/policies.d.ts +1 -1
  79. package/core/src/rss/discovery.d.ts +1 -1
  80. package/core/src/rss/generator.d.ts +1 -1
  81. package/core/src/rss/index.d.ts +1 -1
  82. package/core/src/sitemap/discovery.d.ts +1 -1
  83. package/core/src/sitemap/generator.d.ts +1 -1
  84. package/core/src/sitemap/index.d.ts +1 -1
  85. package/core/src/ssr/index.d.ts +1 -1
  86. package/core/src/ssr/lib/cache.d.ts +1 -1
  87. package/core/src/ssr/lib/cleanup.d.ts +1 -1
  88. package/core/src/ssr/lib/collections.d.ts +1 -1
  89. package/core/src/ssr/lib/compression.d.ts +1 -1
  90. package/core/src/ssr/lib/compute/content.d.ts +1 -1
  91. package/core/src/ssr/lib/compute/index.d.ts +1 -1
  92. package/core/src/ssr/lib/compute/layouts.d.ts +1 -1
  93. package/core/src/ssr/lib/compute/routes.d.ts +1 -1
  94. package/core/src/ssr/lib/content-path.d.ts +1 -1
  95. package/core/src/ssr/lib/data.d.ts +1 -1
  96. package/core/src/ssr/lib/html.d.ts +1 -1
  97. package/core/src/ssr/lib/imports.d.ts +1 -1
  98. package/core/src/ssr/lib/index.d.ts +1 -1
  99. package/core/src/ssr/lib/layouts.d.ts +1 -1
  100. package/core/src/ssr/lib/metadata.d.ts +1 -1
  101. package/core/src/ssr/lib/mime.d.ts +1 -1
  102. package/core/src/ssr/lib/modules.d.ts +1 -1
  103. package/core/src/ssr/lib/path.d.ts +1 -1
  104. package/core/src/ssr/lib/preload.d.ts +1 -1
  105. package/core/src/ssr/lib/route-match.d.ts +1 -1
  106. package/core/src/ssr/lib/scripts/collections.d.ts +1 -1
  107. package/core/src/ssr/lib/scripts/data.d.ts +1 -1
  108. package/core/src/ssr/lib/scripts/escape.d.ts +1 -1
  109. package/core/src/ssr/lib/scripts/feeds.d.ts +1 -1
  110. package/core/src/ssr/lib/scripts/index.d.ts +1 -1
  111. package/core/src/ssr/lib/seed.d.ts +1 -1
  112. package/core/src/ssr/lib/serialize.d.ts +1 -1
  113. package/core/src/ssr/lib/setup.d.ts +2 -2
  114. package/core/src/ssr/lib/setup.d.ts.map +1 -1
  115. package/core/src/ssr/lib/styles.d.ts +3 -3
  116. package/core/src/ssr/lib/styles.d.ts.map +1 -1
  117. package/core/src/ssr/lib/template.d.ts +1 -1
  118. package/core/src/ssr/lib/types.d.ts +1 -1
  119. package/core/src/ssr/render.d.ts +2 -2
  120. package/core/src/ssr/render.d.ts.map +1 -1
  121. package/core/src/ssr/stream.d.ts +2 -2
  122. package/core/src/ssr/stream.d.ts.map +1 -1
  123. package/elysia/index.d.ts +1 -1
  124. package/elysia/index.js +21 -21
  125. package/elysia/index.js.map +9 -9
  126. package/elysia/src/index.d.ts +1 -1
  127. package/elysia/src/libs/assets.d.ts +1 -1
  128. package/elysia/src/libs/cache.d.ts +1 -1
  129. package/elysia/src/libs/caching.d.ts +1 -1
  130. package/elysia/src/libs/http.d.ts +1 -1
  131. package/elysia/src/libs/i18n.d.ts +1 -1
  132. package/elysia/src/libs/image.d.ts +1 -1
  133. package/elysia/src/libs/index.d.ts +1 -1
  134. package/elysia/src/libs/llms.d.ts +1 -1
  135. package/elysia/src/libs/response.d.ts +1 -1
  136. package/elysia/src/libs/serving.d.ts +1 -1
  137. package/elysia/src/plugin.d.ts +1 -1
  138. package/elysia/src/routes/artifacts.d.ts +1 -1
  139. package/elysia/src/routes/content.d.ts +1 -1
  140. package/elysia/src/routes/image.d.ts +1 -1
  141. package/elysia/src/routes/index.d.ts +1 -1
  142. package/elysia/src/routes/internal.d.ts +1 -1
  143. package/elysia/src/routes/llms.d.ts +1 -1
  144. package/elysia/src/routes/og.d.ts +1 -1
  145. package/elysia/src/routes/redirects.d.ts +1 -1
  146. package/elysia/src/routes/robots.d.ts +1 -1
  147. package/elysia/src/routes/rss.d.ts +1 -1
  148. package/elysia/src/routes/search.d.ts +1 -1
  149. package/elysia/src/routes/sitemap.d.ts +1 -1
  150. package/elysia/src/routes/ssr.d.ts +2 -2
  151. package/elysia/src/routes/ssr.d.ts.map +1 -1
  152. package/elysia/src/routes/static.d.ts +1 -1
  153. package/elysia/src/types.d.ts +1 -1
  154. package/package.json +2 -5
  155. package/react/index.d.ts +1 -1
  156. package/react/index.js +2 -2
  157. package/react/index.js.map +1 -1
  158. package/react/src/components/ClientOnly.d.ts +1 -1
  159. package/react/src/components/ContentRoute.d.ts +1 -1
  160. package/react/src/components/Image.d.ts +1 -1
  161. package/react/src/components/LazyRoute.d.ts +1 -1
  162. package/react/src/components/Link.d.ts +1 -1
  163. package/react/src/components/Markdown.d.ts +1 -1
  164. package/react/src/components/Outlet.d.ts +1 -1
  165. package/react/src/components/index.d.ts +1 -1
  166. package/react/src/hooks/index.d.ts +1 -1
  167. package/react/src/hooks/useContent.d.ts +1 -1
  168. package/react/src/hooks/useData.d.ts +1 -1
  169. package/react/src/hooks/useFeed.d.ts +1 -1
  170. package/react/src/hooks/useLayoutData.d.ts +1 -1
  171. package/react/src/hooks/useLlms.d.ts +1 -1
  172. package/react/src/hooks/useNavigate.d.ts +1 -1
  173. package/react/src/hooks/useParams.d.ts +1 -1
  174. package/react/src/hooks/useRouter.d.ts +1 -1
  175. package/react/src/hooks/useSearch.d.ts +1 -1
  176. package/react/src/hooks/useSearchParams.d.ts +1 -1
  177. package/react/src/hooks/useToc.d.ts +1 -1
  178. package/react/src/i18n/LocaleProvider.d.ts +1 -1
  179. package/react/src/i18n/helpers.d.ts +1 -1
  180. package/react/src/i18n/index.d.ts +1 -1
  181. package/react/src/index.d.ts +1 -1
  182. package/react/src/lib/collection.d.ts +1 -1
  183. package/react/src/lib/content.d.ts +1 -1
  184. package/react/src/lib/head.d.ts +1 -1
  185. package/react/src/lib/index.d.ts +1 -1
  186. package/react/src/lib/lazy-route.d.ts +1 -1
  187. package/react/src/lib/route-loader.d.ts +1 -1
  188. package/react/src/providers/ContentProvider.d.ts +1 -1
  189. package/react/src/providers/RerouteProvider.d.ts +1 -1
  190. package/react/src/providers/RouterProvider.d.ts +1 -1
  191. package/react/src/providers/index.d.ts +1 -1
  192. package/react/src/types/any.d.ts +1 -1
  193. package/react/src/types/index.d.ts +1 -1
  194. package/react/src/types/router.d.ts +1 -1
  195. package/telemetry/react.d.ts +1 -1
  196. package/telemetry/react.js +2 -2
  197. package/telemetry/react.js.map +1 -1
  198. package/telemetry/server.d.ts +1 -1
  199. package/telemetry/server.js +2 -2
  200. package/telemetry/server.js.map +4 -4
  201. package/telemetry/src/react/api.d.ts +1 -1
  202. package/telemetry/src/react/context.d.ts +1 -1
  203. package/telemetry/src/react/index.d.ts +1 -1
  204. package/telemetry/src/react/telemetry.d.ts +1 -1
  205. package/telemetry/src/server/context.d.ts +1 -1
  206. package/telemetry/src/server/headers/extractor.d.ts +1 -1
  207. package/telemetry/src/server/headers/index.d.ts +1 -1
  208. package/telemetry/src/server/headers/presets.d.ts +1 -1
  209. package/telemetry/src/server/index.d.ts +1 -1
  210. package/telemetry/src/server/instrumentation.d.ts +1 -1
  211. package/telemetry/src/server/plugin.d.ts +1 -1
  212. package/telemetry/src/server/sourcemap.d.ts +1 -1
  213. package/cli/src/commands/lib/tailwind.d.ts +0 -20
  214. package/cli/src/commands/lib/tailwind.d.ts.map +0 -1
@@ -4,7 +4,6 @@ import Header from '../../components/Header';
4
4
  import ProductCard from '../../components/ProductCard';
5
5
  import { getProduct, getProductsByCategory, type Product } from '../../lib/api';
6
6
 
7
- // Enable SSR data fetching for this route
8
7
  const ssr = {
9
8
  async data({ params }: { params: { id?: string } }) {
10
9
  try {
@@ -45,7 +44,6 @@ function ProductDetailPage() {
45
44
  const params = useParams<{ id: string }>();
46
45
  const productId = params.id ? Number.parseInt(params.id, 10) : null;
47
46
 
48
- // Read SSR-seeded or prefetched data for this route
49
47
  const routeData = useData<{
50
48
  product: Product | null;
51
49
  relatedProducts: Product[];
@@ -58,7 +56,6 @@ function ProductDetailPage() {
58
56
  const relatedProducts = data?.relatedProducts ?? [];
59
57
  const error = data?.error ?? (productId ? null : 'Invalid product ID');
60
58
 
61
- // Fallback on client nav if hover prefetch didn't run
62
59
  useEffect(() => {
63
60
  if (typeof window === 'undefined') return;
64
61
  if (routeData !== undefined) return;
@@ -80,7 +77,6 @@ function ProductDetailPage() {
80
77
 
81
78
  const handleAddToCart = () => {
82
79
  setAddingToCart(true);
83
- // Simulate adding to cart
84
80
  setTimeout(() => {
85
81
  setAddingToCart(false);
86
82
  alert(`Added ${product?.title} to cart!`);
@@ -95,10 +91,12 @@ function ProductDetailPage() {
95
91
  return (
96
92
  <>
97
93
  <Header />
98
- <main className='container-custom py-8'>
99
- <h1 className='text-4xl font-bold text-gray-900 mb-8'>Loading...</h1>
100
- <div className='text-center py-16 text-gray-600 text-lg'>
101
- <div className='animate-pulse'>Loading product details...</div>
94
+ <main className='page-main'>
95
+ <h1 className='page-title'>Loading...</h1>
96
+ <div className='state-loading'>
97
+ <div className='state-loading-pulse'>
98
+ Loading product details...
99
+ </div>
102
100
  </div>
103
101
  </main>
104
102
  </>
@@ -109,11 +107,11 @@ function ProductDetailPage() {
109
107
  return (
110
108
  <>
111
109
  <Header />
112
- <main className='container-custom py-8'>
113
- <div className='text-center py-16 bg-red-50 text-red-600 text-lg rounded-xl'>
110
+ <main className='page-main'>
111
+ <div className='state-error'>
114
112
  {error || 'Product not found'}
115
- <div className='mt-4'>
116
- <Link to='/products' className='link font-semibold'>
113
+ <div className='state-error-action'>
114
+ <Link to='/products' className='link link-semibold'>
117
115
  ← Back to Products
118
116
  </Link>
119
117
  </div>
@@ -127,9 +125,8 @@ function ProductDetailPage() {
127
125
  <>
128
126
  <Header />
129
127
 
130
- <main className='container-custom py-8'>
131
- {/* Breadcrumbs */}
132
- <nav className='flex items-center gap-2 text-sm text-gray-600 mb-8'>
128
+ <main className='page-main'>
129
+ <nav className='breadcrumb'>
133
130
  <Link to='/' className='link'>
134
131
  Home
135
132
  </Link>
@@ -142,13 +139,11 @@ function ProductDetailPage() {
142
139
  {product.category}
143
140
  </Link>
144
141
  <span>/</span>
145
- <span className='text-gray-900'>{product.title}</span>
142
+ <span className='breadcrumb-current'>{product.title}</span>
146
143
  </nav>
147
144
 
148
- {/* Product Details */}
149
- <div className='grid grid-cols-1 lg:grid-cols-2 gap-12 mb-16'>
150
- {/* Product Image */}
151
- <div className='card flex items-center justify-center p-8 min-h-[500px]'>
145
+ <div className='product-detail-grid'>
146
+ <div className='card product-detail-image'>
152
147
  <Image
153
148
  src={product.image}
154
149
  alt={product.title}
@@ -157,43 +152,36 @@ function ProductDetailPage() {
157
152
  quality={85}
158
153
  sizes='(max-width: 1024px) 90vw, 350px'
159
154
  priority
160
- className='max-w-full max-h-[500px] object-contain relative z-10'
155
+ className='product-detail-image-img'
161
156
  style={{ viewTransitionName: `product-image-${product.id}` }}
162
157
  />
163
158
  </div>
164
159
 
165
- {/* Product Info */}
166
- <div className='flex flex-col gap-6'>
160
+ <div className='product-detail-info'>
167
161
  <div className='badge-primary'>{product.category}</div>
168
162
 
169
- <h1 className='text-4xl font-bold text-gray-900 leading-tight'>
170
- {product.title}
171
- </h1>
163
+ <h1 className='product-detail-title'>{product.title}</h1>
172
164
 
173
165
  {product.rating && (
174
- <div className='flex items-center gap-4'>
175
- <div className='flex items-center gap-2 text-base text-gray-600'>
176
- <span className='text-2xl'>⭐</span>
177
- <span className='font-semibold text-xl'>
178
- {product.rating.rate}
179
- </span>
180
- <span>({product.rating.count} reviews)</span>
181
- </div>
166
+ <div className='product-rating-lg'>
167
+ <span className='product-rating-star'>⭐</span>
168
+ <span className='product-rating-value'>
169
+ {product.rating.rate}
170
+ </span>
171
+ <span>({product.rating.count} reviews)</span>
182
172
  </div>
183
173
  )}
184
174
 
185
- <div className='text-4xl font-bold text-primary-600'>
186
- ${product.price.toFixed(2)}
187
- </div>
175
+ <div className='price-lg'>${product.price.toFixed(2)}</div>
188
176
 
189
- <p className='text-lg text-gray-700 leading-relaxed'>
177
+ <p className='product-detail-description'>
190
178
  {product.description}
191
179
  </p>
192
180
 
193
- <div className='flex gap-4 mt-4'>
181
+ <div className='product-actions'>
194
182
  <button
195
183
  type='button'
196
- className='btn-primary flex-1 disabled:opacity-50'
184
+ className='btn-primary btn-grow'
197
185
  onClick={handleAddToCart}
198
186
  disabled={addingToCart}
199
187
  >
@@ -209,24 +197,23 @@ function ProductDetailPage() {
209
197
  </button>
210
198
  </div>
211
199
 
212
- <div className='p-4 bg-gray-50 rounded-xl text-sm text-gray-600 space-y-2'>
213
- <div className='flex items-center gap-2'>
214
- <span className='text-green-600 font-bold'>✓</span>
200
+ <div className='product-perks'>
201
+ <div className='product-perk'>
202
+ <span className='product-perk-check'>✓</span>
215
203
  <span>Free shipping on orders over $50</span>
216
204
  </div>
217
- <div className='flex items-center gap-2'>
218
- <span className='text-green-600 font-bold'>✓</span>
205
+ <div className='product-perk'>
206
+ <span className='product-perk-check'>✓</span>
219
207
  <span>30-day return policy</span>
220
208
  </div>
221
- <div className='flex items-center gap-2'>
222
- <span className='text-green-600 font-bold'>✓</span>
209
+ <div className='product-perk'>
210
+ <span className='product-perk-check'>✓</span>
223
211
  <span>Secure checkout</span>
224
212
  </div>
225
213
  </div>
226
214
  </div>
227
215
  </div>
228
216
 
229
- {/* Related Products */}
230
217
  {relatedProducts.length > 0 && (
231
218
  <section className='animate-in'>
232
219
  <h2 className='section-heading'>Related Products</h2>
@@ -54,7 +54,6 @@ function ProductsPage() {
54
54
  const [loading, setLoading] = useState(!routeData);
55
55
  const [error, setError] = useState<string | null>(routeData?.error || null);
56
56
 
57
- // Load categories on mount (if not provided by SSR)
58
57
  useEffect(() => {
59
58
  if (categories.length > 0) return;
60
59
  getCategories()
@@ -65,9 +64,7 @@ function ProductsPage() {
65
64
  // eslint-disable-next-line react-hooks/exhaustive-deps
66
65
  }, [categories.length]);
67
66
 
68
- // Load products based on category (skip initial when SSR provided for 'all')
69
67
  useEffect(() => {
70
- // When SSR provided initial products just stop loading
71
68
  if (products.length > 0) {
72
69
  setFilteredProducts(products);
73
70
  setLoading(false);
@@ -96,11 +93,9 @@ function ProductsPage() {
96
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
97
94
  }, [selectedCategory, products]);
98
95
 
99
- // Filter and sort products
100
96
  useEffect(() => {
101
97
  let result = [...products];
102
98
 
103
- // Apply search filter
104
99
  if (searchQuery.trim()) {
105
100
  const query = searchQuery.toLowerCase();
106
101
  result = result.filter(
@@ -111,7 +106,6 @@ function ProductsPage() {
111
106
  );
112
107
  }
113
108
 
114
- // Apply sorting
115
109
  switch (sortBy) {
116
110
  case 'price-asc':
117
111
  result.sort((a, b) => a.price - b.price);
@@ -123,7 +117,6 @@ function ProductsPage() {
123
117
  result.sort((a, b) => a.title.localeCompare(b.title));
124
118
  break;
125
119
  default:
126
- // Keep default order
127
120
  break;
128
121
  }
129
122
 
@@ -134,26 +127,22 @@ function ProductsPage() {
134
127
  <>
135
128
  <Header />
136
129
 
137
- <main className='container-custom py-8'>
138
- <div className='mb-8'>
130
+ <main className='page-main'>
131
+ <div className='page-intro'>
139
132
  <h1 className='section-heading'>All Products</h1>
140
133
  <p className='section-subheading'>
141
134
  Browse our complete collection of quality products
142
135
  </p>
143
136
  </div>
144
137
 
145
- {/* Filters */}
146
- <div className='flex flex-wrap items-center gap-4 mb-8 p-6 bg-white rounded-xl shadow-card'>
147
- <div className='flex items-center gap-2'>
148
- <label
149
- className='font-semibold text-gray-700'
150
- htmlFor='category-filter'
151
- >
138
+ <div className='filter-bar'>
139
+ <div className='filter-group'>
140
+ <label className='filter-label' htmlFor='category-filter'>
152
141
  Category:
153
142
  </label>
154
143
  <select
155
144
  id='category-filter'
156
- className='select min-w-[200px]'
145
+ className='select select-min'
157
146
  value={selectedCategory}
158
147
  onChange={(e) => setSelectedCategory(e.target.value)}
159
148
  >
@@ -169,54 +158,38 @@ function ProductsPage() {
169
158
  <input
170
159
  type='search'
171
160
  placeholder='Search products...'
172
- className='input flex-1 min-w-[250px]'
161
+ className='input input-grow'
173
162
  value={searchQuery}
174
163
  onChange={(e) => setSearchQuery(e.target.value)}
175
164
  />
176
165
 
177
- <div className='flex items-center gap-2'>
178
- <span className='font-semibold text-gray-700'>Sort:</span>
179
- <div className='flex gap-2'>
166
+ <div className='filter-group'>
167
+ <span className='filter-label'>Sort:</span>
168
+ <div className='filter-sort-group'>
180
169
  <button
181
170
  type='button'
182
- className={`btn-sm transition-all ${
183
- sortBy === 'default'
184
- ? 'bg-primary-600 text-white border-primary-600'
185
- : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
186
- }`}
171
+ className={`sort-btn${sortBy === 'default' ? ' is-active' : ''}`}
187
172
  onClick={() => setSortBy('default')}
188
173
  >
189
174
  Default
190
175
  </button>
191
176
  <button
192
177
  type='button'
193
- className={`btn-sm transition-all ${
194
- sortBy === 'price-asc'
195
- ? 'bg-primary-600 text-white border-primary-600'
196
- : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
197
- }`}
178
+ className={`sort-btn${sortBy === 'price-asc' ? ' is-active' : ''}`}
198
179
  onClick={() => setSortBy('price-asc')}
199
180
  >
200
181
  Price: Low to High
201
182
  </button>
202
183
  <button
203
184
  type='button'
204
- className={`btn-sm transition-all ${
205
- sortBy === 'price-desc'
206
- ? 'bg-primary-600 text-white border-primary-600'
207
- : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
208
- }`}
185
+ className={`sort-btn${sortBy === 'price-desc' ? ' is-active' : ''}`}
209
186
  onClick={() => setSortBy('price-desc')}
210
187
  >
211
188
  Price: High to Low
212
189
  </button>
213
190
  <button
214
191
  type='button'
215
- className={`btn-sm transition-all ${
216
- sortBy === 'name-asc'
217
- ? 'bg-primary-600 text-white border-primary-600'
218
- : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
219
- }`}
192
+ className={`sort-btn${sortBy === 'name-asc' ? ' is-active' : ''}`}
220
193
  onClick={() => setSortBy('name-asc')}
221
194
  >
222
195
  Name: A-Z
@@ -225,24 +198,21 @@ function ProductsPage() {
225
198
  </div>
226
199
 
227
200
  {!loading && (
228
- <div className='ml-auto text-sm text-gray-600'>
201
+ <div className='filter-count'>
229
202
  Showing {filteredProducts.length} of {products.length} products
230
203
  </div>
231
204
  )}
232
205
  </div>
233
206
 
234
- {/* Products Grid */}
235
207
  {loading ? (
236
- <div className='text-center py-16 text-gray-600 text-lg'>
237
- <div className='animate-pulse'>Loading products...</div>
208
+ <div className='state-loading'>
209
+ <div className='state-loading-pulse'>Loading products...</div>
238
210
  </div>
239
211
  ) : error ? (
240
- <div className='text-center py-16 bg-red-50 text-red-600 text-lg rounded-xl'>
241
- {error}
242
- </div>
212
+ <div className='state-error'>{error}</div>
243
213
  ) : filteredProducts.length === 0 ? (
244
- <div className='text-center py-16 text-gray-600 text-lg'>
245
- <div className='text-6xl mb-4'>🔍</div>
214
+ <div className='state-empty'>
215
+ <div className='state-empty-icon'>🔍</div>
246
216
  <div>No products found matching your criteria</div>
247
217
  </div>
248
218
  ) : (