@bearmenu/ui 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 (207) hide show
  1. package/dist/chunk-2CUFS5LF.js +80 -0
  2. package/dist/chunk-3GWWZKX7.js +33 -0
  3. package/dist/chunk-3VMDCCCX.js +10 -0
  4. package/dist/chunk-4AVS6CV4.js +89 -0
  5. package/dist/chunk-4JOV7FXW.js +17 -0
  6. package/dist/chunk-4KOMHDZD.js +89 -0
  7. package/dist/chunk-4UO73SKC.js +26 -0
  8. package/dist/chunk-7H54PTVH.js +116 -0
  9. package/dist/chunk-CW24PLDW.js +24 -0
  10. package/dist/chunk-EBAMCZDW.js +112 -0
  11. package/dist/chunk-ERMIA7NP.js +106 -0
  12. package/dist/chunk-ESO2EM3L.js +46 -0
  13. package/dist/chunk-EUHEIWHZ.js +32 -0
  14. package/dist/chunk-FEK5XGZW.js +8 -0
  15. package/dist/chunk-NKHH23YK.js +31 -0
  16. package/dist/chunk-R47NEOSI.js +78 -0
  17. package/dist/chunk-UXAYXRFN.js +69 -0
  18. package/dist/chunk-X6GATUI2.js +19 -0
  19. package/dist/components/accordion.d.ts +9 -0
  20. package/dist/components/accordion.js +53 -0
  21. package/dist/components/alert-dialog.d.ts +23 -0
  22. package/dist/components/alert-dialog.js +98 -0
  23. package/dist/components/alert.d.ts +11 -0
  24. package/dist/components/alert.js +52 -0
  25. package/dist/components/aspect-ratio.d.ts +6 -0
  26. package/dist/components/aspect-ratio.js +2 -0
  27. package/dist/components/avatar.d.ts +8 -0
  28. package/dist/components/avatar.js +47 -0
  29. package/dist/components/badge.d.ts +13 -0
  30. package/dist/components/badge.js +3 -0
  31. package/dist/components/breadcrumb.d.ts +14 -0
  32. package/dist/components/breadcrumb.js +96 -0
  33. package/dist/components/button.d.ts +16 -0
  34. package/dist/components/button.js +4 -0
  35. package/dist/components/calendar.d.ts +11 -0
  36. package/dist/components/calendar.js +56 -0
  37. package/dist/components/card.d.ts +13 -0
  38. package/dist/components/card.js +4 -0
  39. package/dist/components/carousel.d.ts +24 -0
  40. package/dist/components/carousel.js +198 -0
  41. package/dist/components/checkbox.d.ts +6 -0
  42. package/dist/components/checkbox.js +25 -0
  43. package/dist/components/collapsible.d.ts +8 -0
  44. package/dist/components/collapsible.js +26 -0
  45. package/dist/components/command.d.ts +84 -0
  46. package/dist/components/command.js +4 -0
  47. package/dist/components/currency-input.d.ts +17 -0
  48. package/dist/components/currency-input.js +121 -0
  49. package/dist/components/dialog.d.ts +22 -0
  50. package/dist/components/dialog.js +3 -0
  51. package/dist/components/drawer.d.ts +27 -0
  52. package/dist/components/drawer.js +85 -0
  53. package/dist/components/dropdown-menu.d.ts +28 -0
  54. package/dist/components/dropdown-menu.js +211 -0
  55. package/dist/components/empty-state.d.ts +22 -0
  56. package/dist/components/empty-state.js +81 -0
  57. package/dist/components/form.d.ts +28 -0
  58. package/dist/components/form.js +97 -0
  59. package/dist/components/haptic-button.d.ts +12 -0
  60. package/dist/components/haptic-button.js +17 -0
  61. package/dist/components/input-otp.d.ts +14 -0
  62. package/dist/components/input-otp.js +62 -0
  63. package/dist/components/input.d.ts +6 -0
  64. package/dist/components/input.js +3 -0
  65. package/dist/components/label.d.ts +8 -0
  66. package/dist/components/label.js +3 -0
  67. package/dist/components/lazy-markdown.d.ts +11 -0
  68. package/dist/components/lazy-markdown.js +13 -0
  69. package/dist/components/link-button.d.ts +30 -0
  70. package/dist/components/link-button.js +17 -0
  71. package/dist/components/markdown-renderer.d.ts +11 -0
  72. package/dist/components/markdown-renderer.js +10 -0
  73. package/dist/components/multi-select-combobox.d.ts +23 -0
  74. package/dist/components/multi-select-combobox.js +114 -0
  75. package/dist/components/pagination.d.ts +18 -0
  76. package/dist/components/pagination.js +101 -0
  77. package/dist/components/popover.d.ts +8 -0
  78. package/dist/components/popover.js +3 -0
  79. package/dist/components/progress.d.ts +6 -0
  80. package/dist/components/progress.js +30 -0
  81. package/dist/components/radio-group.d.ts +7 -0
  82. package/dist/components/radio-group.js +30 -0
  83. package/dist/components/rating.d.ts +15 -0
  84. package/dist/components/rating.js +120 -0
  85. package/dist/components/scroll-area.d.ts +7 -0
  86. package/dist/components/scroll-area.js +44 -0
  87. package/dist/components/search-bar.d.ts +17 -0
  88. package/dist/components/search-bar.js +92 -0
  89. package/dist/components/searchable-select.d.ts +22 -0
  90. package/dist/components/searchable-select.js +104 -0
  91. package/dist/components/select.d.ts +15 -0
  92. package/dist/components/select.js +129 -0
  93. package/dist/components/separator.d.ts +6 -0
  94. package/dist/components/separator.js +25 -0
  95. package/dist/components/sheet.d.ts +29 -0
  96. package/dist/components/sheet.js +96 -0
  97. package/dist/components/skeleton-card.d.ts +13 -0
  98. package/dist/components/skeleton-card.js +154 -0
  99. package/dist/components/skeleton.d.ts +6 -0
  100. package/dist/components/skeleton.js +3 -0
  101. package/dist/components/slider.d.ts +6 -0
  102. package/dist/components/slider.js +24 -0
  103. package/dist/components/sliding-panels.d.ts +19 -0
  104. package/dist/components/sliding-panels.js +75 -0
  105. package/dist/components/sonner.d.ts +6 -0
  106. package/dist/components/sonner.js +23 -0
  107. package/dist/components/sticky-container.d.ts +44 -0
  108. package/dist/components/sticky-container.js +39 -0
  109. package/dist/components/switch.d.ts +6 -0
  110. package/dist/components/switch.js +31 -0
  111. package/dist/components/table.d.ts +12 -0
  112. package/dist/components/table.js +81 -0
  113. package/dist/components/tabs.d.ts +12 -0
  114. package/dist/components/tabs.js +55 -0
  115. package/dist/components/text.d.ts +31 -0
  116. package/dist/components/text.js +3 -0
  117. package/dist/components/textarea.d.ts +6 -0
  118. package/dist/components/textarea.js +23 -0
  119. package/dist/components/toast.d.ts +18 -0
  120. package/dist/components/toast.js +3 -0
  121. package/dist/components/toaster.d.ts +5 -0
  122. package/dist/components/toaster.js +25 -0
  123. package/dist/components/toggle-group.d.ts +12 -0
  124. package/dist/components/toggle-group.js +72 -0
  125. package/dist/components/toggle.d.ts +13 -0
  126. package/dist/components/toggle.js +3 -0
  127. package/dist/components/tooltip.d.ts +9 -0
  128. package/dist/components/tooltip.js +26 -0
  129. package/dist/components/use-mobile.d.ts +3 -0
  130. package/dist/components/use-mobile.js +19 -0
  131. package/dist/components/use-toast.d.ts +42 -0
  132. package/dist/components/use-toast.js +2 -0
  133. package/dist/hooks/use-haptic.d.ts +22 -0
  134. package/dist/hooks/use-haptic.js +2 -0
  135. package/dist/hooks/use-mobile.d.ts +3 -0
  136. package/dist/hooks/use-mobile.js +19 -0
  137. package/dist/hooks/use-mounted-theme.d.ts +6 -0
  138. package/dist/hooks/use-mounted-theme.js +18 -0
  139. package/dist/hooks/use-sticky.d.ts +18 -0
  140. package/dist/hooks/use-sticky.js +2 -0
  141. package/dist/lib/utils.d.ts +5 -0
  142. package/dist/lib/utils.js +2 -0
  143. package/package.json +203 -0
  144. package/src/components/accordion.tsx +57 -0
  145. package/src/components/alert-dialog.tsx +116 -0
  146. package/src/components/alert.tsx +57 -0
  147. package/src/components/aspect-ratio.tsx +9 -0
  148. package/src/components/avatar.tsx +49 -0
  149. package/src/components/badge.tsx +38 -0
  150. package/src/components/breadcrumb.tsx +102 -0
  151. package/src/components/button.tsx +94 -0
  152. package/src/components/calendar.tsx +60 -0
  153. package/src/components/card.tsx +67 -0
  154. package/src/components/carousel.tsx +230 -0
  155. package/src/components/checkbox.tsx +27 -0
  156. package/src/components/collapsible.tsx +28 -0
  157. package/src/components/command.tsx +148 -0
  158. package/src/components/currency-input.tsx +158 -0
  159. package/src/components/dialog.tsx +103 -0
  160. package/src/components/drawer.tsx +99 -0
  161. package/src/components/dropdown-menu.tsx +228 -0
  162. package/src/components/empty-state.tsx +118 -0
  163. package/src/components/form.tsx +152 -0
  164. package/src/components/haptic-button.tsx +19 -0
  165. package/src/components/input-otp.tsx +70 -0
  166. package/src/components/input.tsx +23 -0
  167. package/src/components/label.tsx +20 -0
  168. package/src/components/lazy-markdown.tsx +20 -0
  169. package/src/components/link-button.tsx +40 -0
  170. package/src/components/markdown-renderer.tsx +20 -0
  171. package/src/components/multi-select-combobox.tsx +148 -0
  172. package/src/components/pagination.tsx +106 -0
  173. package/src/components/popover.tsx +30 -0
  174. package/src/components/progress.tsx +27 -0
  175. package/src/components/radio-group.tsx +37 -0
  176. package/src/components/rating.tsx +136 -0
  177. package/src/components/scroll-area.tsx +45 -0
  178. package/src/components/search-bar.tsx +113 -0
  179. package/src/components/searchable-select.tsx +134 -0
  180. package/src/components/select.tsx +152 -0
  181. package/src/components/separator.tsx +25 -0
  182. package/src/components/sheet.tsx +121 -0
  183. package/src/components/skeleton-card.tsx +282 -0
  184. package/src/components/skeleton.tsx +17 -0
  185. package/src/components/slider.tsx +24 -0
  186. package/src/components/sliding-panels.tsx +99 -0
  187. package/src/components/sonner.tsx +25 -0
  188. package/src/components/sticky-container.tsx +88 -0
  189. package/src/components/switch.tsx +28 -0
  190. package/src/components/table.tsx +90 -0
  191. package/src/components/tabs.tsx +72 -0
  192. package/src/components/text.test.tsx +192 -0
  193. package/src/components/text.tsx +114 -0
  194. package/src/components/textarea.tsx +22 -0
  195. package/src/components/toast.tsx +129 -0
  196. package/src/components/toaster.tsx +31 -0
  197. package/src/components/toggle-group.tsx +69 -0
  198. package/src/components/toggle.tsx +46 -0
  199. package/src/components/tooltip.tsx +29 -0
  200. package/src/components/use-mobile.tsx +19 -0
  201. package/src/components/use-toast.ts +180 -0
  202. package/src/globals.css +409 -0
  203. package/src/hooks/use-haptic.ts +118 -0
  204. package/src/hooks/use-mobile.ts +19 -0
  205. package/src/hooks/use-mounted-theme.ts +20 -0
  206. package/src/hooks/use-sticky.ts +47 -0
  207. package/src/lib/utils.ts +6 -0
@@ -0,0 +1,282 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+ import { Skeleton } from "./skeleton";
6
+ import { AspectRatio } from "./aspect-ratio";
7
+
8
+ interface SkeletonCardProps {
9
+ variant?: "experience" | "restaurant" | "generic" | "place" | "dish" | "drink" | "event";
10
+ showImage?: boolean;
11
+ showRating?: boolean;
12
+ showPrice?: boolean;
13
+ lines?: number; // for description lines
14
+ className?: string;
15
+ }
16
+
17
+ const SkeletonCard = React.forwardRef<HTMLDivElement, SkeletonCardProps>(
18
+ (
19
+ {
20
+ variant = "experience",
21
+ showImage = true,
22
+ showRating = true,
23
+ showPrice = true,
24
+ lines = 2,
25
+ className,
26
+ ...props
27
+ },
28
+ ref
29
+ ) => {
30
+ const renderPlaceCardSkeleton = () => (
31
+ <div className="glass transition-all duration-300 ease-in-out h-full flex flex-col w-full rounded-3xl border border-transparent">
32
+ <div className="p-4 flex-1 flex flex-col">
33
+ {/* SECTION 1 - PLACE HEADER */}
34
+ <div className="flex items-start gap-3 mb-3">
35
+ {/* Place Photo or Icon - 48x48px */}
36
+ <Skeleton className="w-12 h-12 rounded-lg flex-shrink-0" />
37
+
38
+ {/* Place Name & Rating */}
39
+ <div className="flex-1 min-w-0">
40
+ {/* Title - matches font-bold text-lg leading-tight (lh ~22.5px) */}
41
+ <Skeleton className="h-5 w-3/4 mb-2" />
42
+ {/* Rating row - matches text-sm */}
43
+ <div className="flex items-center gap-2">
44
+ <Skeleton className="h-4 w-16" />
45
+ <Skeleton className="h-3 w-1" />
46
+ <Skeleton className="h-4 w-12" />
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ {/* SECTION 2 - PRICE RANGES */}
52
+ <div className="mb-3">
53
+ <div className="flex items-center gap-4 text-xs">
54
+ <Skeleton className="h-3 w-20" />
55
+ <Skeleton className="h-3 w-20" />
56
+ </div>
57
+ </div>
58
+
59
+ {/* SECTION 3 - LOCATION & STATUS */}
60
+ <div className="mb-3">
61
+ <div className="flex items-center gap-2">
62
+ <Skeleton className="h-3.5 w-16" />
63
+ <Skeleton className="h-3.5 w-1" />
64
+ <Skeleton className="h-3.5 w-20" />
65
+ </div>
66
+ </div>
67
+
68
+ {/* SECTION 4 - DESCRIPTION PREVIEW */}
69
+ <div className="mb-4">
70
+ <Skeleton className="h-3 w-full mb-1" />
71
+ <Skeleton className="h-3 w-2/3" />
72
+ </div>
73
+ </div>
74
+ </div>
75
+ );
76
+
77
+ const renderDishCardSkeleton = () => (
78
+ <div className="glass transition-all duration-300 ease-in-out h-full flex flex-col w-full border rounded-lg">
79
+ <div className="p-3 h-full flex flex-col">
80
+ {/* SECTION 1 - DISH HEADER (icon + name + heart button) */}
81
+ <div className="flex items-start gap-3 mb-3">
82
+ {/* Dish Icon - 48x48px */}
83
+ <Skeleton className="w-12 h-12 rounded-lg flex-shrink-0" />
84
+
85
+ {/* Dish Name */}
86
+ <div className="flex-1 min-w-0">
87
+ {/* Title - matches font-bold text-lg leading-tight line-clamp-2 */}
88
+ <Skeleton className="h-5 w-3/4 mb-1" />
89
+ <Skeleton className="h-4 w-1/2" />
90
+ </div>
91
+
92
+ {/* Heart button placeholder - matches h-8 w-8 min-h-[44px] min-w-[44px] */}
93
+ <Skeleton className="h-8 w-8 rounded-md flex-shrink-0" />
94
+ </div>
95
+
96
+ {/* SECTION 2 - LOCATION BADGE */}
97
+ <div className="mb-3 flex items-center gap-2">
98
+ <Skeleton className="h-5 w-24 rounded-full" />
99
+ </div>
100
+
101
+ {/* SECTION 3 - BADGES ROW (For You / Rare / Qty) */}
102
+ <div className="flex items-center gap-2 mb-2">
103
+ <Skeleton className="h-5 w-16 rounded-full" />
104
+ <Skeleton className="h-5 w-12 rounded-full" />
105
+ </div>
106
+
107
+ {/* SECTION 4 - INGREDIENTS PREVIEW */}
108
+ <div className="mb-3">
109
+ <Skeleton className="h-3 w-full mb-1" />
110
+ <Skeleton className="h-3 w-2/3" />
111
+ </div>
112
+
113
+ {/* SECTION 5 - FOOTER (PRICE AND BUTTON) */}
114
+ <div className="flex items-center justify-between mt-auto">
115
+ <Skeleton className="h-6 w-16" />
116
+ <Skeleton className="h-8 w-24 rounded-md" />
117
+ </div>
118
+ </div>
119
+ </div>
120
+ );
121
+
122
+ const renderDrinkCardSkeleton = () => (
123
+ <div className="glass transition-all duration-300 ease-in-out w-full h-full border rounded-lg">
124
+ <div className="p-3 h-full flex flex-col">
125
+ {/* HEADER (icon + name + heart button) */}
126
+ <div className="flex items-start gap-3 mb-3">
127
+ {/* Drink Icon - 48x48px */}
128
+ <Skeleton className="w-12 h-12 rounded-lg flex-shrink-0" />
129
+
130
+ {/* Drink Name */}
131
+ <div className="flex-1 min-w-0">
132
+ {/* Title - matches font-bold text-lg leading-tight */}
133
+ <Skeleton className="h-5 w-3/4 mb-1" />
134
+ <Skeleton className="h-4 w-1/2" />
135
+ </div>
136
+
137
+ {/* Heart button placeholder - matches h-8 w-8 min-h-[44px] min-w-[44px] */}
138
+ <Skeleton className="h-8 w-8 rounded-md flex-shrink-0" />
139
+ </div>
140
+
141
+ {/* LOCATION BADGE */}
142
+ <div className="mb-3 flex items-center gap-2">
143
+ <Skeleton className="h-5 w-24 rounded-full" />
144
+ </div>
145
+
146
+ {/* BADGES ROW */}
147
+ <div className="flex items-center gap-2 mb-2">
148
+ <Skeleton className="h-5 w-16 rounded-full" />
149
+ <Skeleton className="h-5 w-12 rounded-full" />
150
+ </div>
151
+
152
+ {/* INGREDIENTS PREVIEW */}
153
+ <div className="mb-3">
154
+ <Skeleton className="h-3 w-full mb-1" />
155
+ <Skeleton className="h-3 w-2/3" />
156
+ </div>
157
+
158
+ {/* FOOTER WITH PRICE AND BUTTON */}
159
+ <div className="flex items-center justify-between mt-auto">
160
+ <Skeleton className="h-6 w-16" />
161
+ <Skeleton className="h-8 w-24 rounded-md" />
162
+ </div>
163
+ </div>
164
+ </div>
165
+ );
166
+
167
+ const renderEventCardSkeleton = () => (
168
+ <div className="glass transition-all duration-300 ease-in-out w-full overflow-hidden rounded-xl">
169
+ {/* Image area - matches aspect-[16/10] of EventCarouselCard */}
170
+ <div className="relative aspect-[16/10] overflow-hidden bg-white/[0.06]">
171
+ <Skeleton className="absolute inset-0 w-full h-full" />
172
+ </div>
173
+ {/* Content area - matches flex flex-col gap-1 p-3 */}
174
+ <div className="flex flex-col gap-1 p-3">
175
+ <Skeleton className="h-4 w-3/4" />
176
+ <Skeleton className="h-3 w-1/2" />
177
+ </div>
178
+ </div>
179
+ );
180
+
181
+ const renderExperienceSkeleton = () => (
182
+ <div className="glass rounded-xl overflow-hidden transition-all duration-300">
183
+ {/* Image skeleton - 16:9 aspect ratio */}
184
+ {showImage && (
185
+ <AspectRatio ratio={16 / 9}>
186
+ <Skeleton className="w-full h-full" />
187
+ </AspectRatio>
188
+ )}
189
+
190
+ {/* Content skeleton */}
191
+ <div className="p-6 space-y-4">
192
+ {/* Restaurant name */}
193
+ <Skeleton className="h-6 w-3/4" />
194
+
195
+ {/* Description lines */}
196
+ <div className="space-y-2">
197
+ {Array.from({ length: lines }).map((_, i) => (
198
+ <Skeleton key={i} className={cn("h-4", i === lines - 1 ? "w-1/2" : "w-full")} />
199
+ ))}
200
+ </div>
201
+
202
+ {/* Price and rating row */}
203
+ <div className="flex items-center justify-between">
204
+ {showPrice && <Skeleton className="h-5 w-20" />}
205
+ {showRating && <Skeleton className="h-4 w-16" />}
206
+ </div>
207
+
208
+ {/* Quick details */}
209
+ <div className="flex items-center justify-between text-sm">
210
+ <Skeleton className="h-4 w-24" />
211
+ <Skeleton className="h-4 w-16" />
212
+ </div>
213
+ </div>
214
+ </div>
215
+ );
216
+
217
+ const renderRestaurantSkeleton = () => (
218
+ <div className="glass rounded-xl overflow-hidden transition-all duration-300">
219
+ {/* Image skeleton */}
220
+ {showImage && (
221
+ <AspectRatio ratio={16 / 9}>
222
+ <Skeleton className="w-full h-full" />
223
+ </AspectRatio>
224
+ )}
225
+
226
+ {/* Content skeleton */}
227
+ <div className="p-6 space-y-4">
228
+ {/* Restaurant name */}
229
+ <Skeleton className="h-6 w-2/3" />
230
+
231
+ {/* Description */}
232
+ <div className="space-y-2">
233
+ <Skeleton className="h-4 w-full" />
234
+ <Skeleton className="h-4 w-3/4" />
235
+ </div>
236
+
237
+ {/* Rating and price */}
238
+ <div className="flex items-center justify-between">
239
+ {showRating && <Skeleton className="h-4 w-20" />}
240
+ {showPrice && <Skeleton className="h-5 w-16" />}
241
+ </div>
242
+ </div>
243
+ </div>
244
+ );
245
+
246
+ const renderGenericSkeleton = () => (
247
+ <div className="glass rounded-xl p-6 space-y-4">
248
+ {/* Title */}
249
+ <Skeleton className="h-6 w-1/2" />
250
+
251
+ {/* Content lines */}
252
+ <div className="space-y-2">
253
+ {Array.from({ length: lines }).map((_, i) => (
254
+ <Skeleton key={i} className={cn("h-4", i === lines - 1 ? "w-1/3" : "w-full")} />
255
+ ))}
256
+ </div>
257
+
258
+ {/* Footer content */}
259
+ <div className="flex items-center justify-between">
260
+ <Skeleton className="h-4 w-20" />
261
+ <Skeleton className="h-4 w-16" />
262
+ </div>
263
+ </div>
264
+ );
265
+
266
+ return (
267
+ <div ref={ref} className={cn("w-full", className)} {...props}>
268
+ {variant === "place" && renderPlaceCardSkeleton()}
269
+ {variant === "dish" && renderDishCardSkeleton()}
270
+ {variant === "drink" && renderDrinkCardSkeleton()}
271
+ {variant === "event" && renderEventCardSkeleton()}
272
+ {variant === "experience" && renderExperienceSkeleton()}
273
+ {variant === "restaurant" && renderRestaurantSkeleton()}
274
+ {variant === "generic" && renderGenericSkeleton()}
275
+ </div>
276
+ );
277
+ }
278
+ );
279
+
280
+ SkeletonCard.displayName = "SkeletonCard";
281
+
282
+ export { SkeletonCard };
@@ -0,0 +1,17 @@
1
+ import type React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
5
+ return (
6
+ <div
7
+ className={cn(
8
+ "animate-pulse rounded-xl glass border border-border/30 relative overflow-hidden",
9
+ "before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_2s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ );
15
+ }
16
+
17
+ export { Skeleton };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SliderPrimitive from "@radix-ui/react-slider";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Slider = React.forwardRef<
8
+ React.ElementRef<typeof SliderPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <SliderPrimitive.Root
12
+ ref={ref}
13
+ className={cn("relative flex w-full touch-none select-none items-center", className)}
14
+ {...props}
15
+ >
16
+ <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full glass border border-border/50">
17
+ <SliderPrimitive.Range className="absolute h-full bg-white shadow-lg" />
18
+ </SliderPrimitive.Track>
19
+ <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-primary ring-offset-background transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:shadow-lg hover:shadow-primary/20 hover:scale-110" />
20
+ </SliderPrimitive.Root>
21
+ ));
22
+ Slider.displayName = SliderPrimitive.Root.displayName;
23
+
24
+ export { Slider };
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useLayoutEffect, useRef, useState } from "react";
5
+ import { Button } from "./button";
6
+ import { ChevronLeft } from "lucide-react";
7
+ import { cn } from "../lib/utils";
8
+
9
+ export interface Panel {
10
+ id: string;
11
+ content: React.ReactNode;
12
+ showBackButton?: boolean;
13
+ backButtonLabel?: string;
14
+ onBack?: () => void;
15
+ }
16
+
17
+ export interface SlidingPanelsProps {
18
+ panels: Panel[];
19
+ currentPanel: string;
20
+ onPanelChange: (panelId: string) => void;
21
+ className?: string;
22
+ }
23
+
24
+ export const SlidingPanels = ({
25
+ panels,
26
+ currentPanel,
27
+ onPanelChange,
28
+ className,
29
+ }: SlidingPanelsProps) => {
30
+ const currentIndex = panels.findIndex((p) => p.id === currentPanel);
31
+ const currentPanelData = panels[currentIndex];
32
+ const panelRefs = useRef<(HTMLDivElement | null)[]>([]);
33
+ const [containerHeight, setContainerHeight] = useState<number | undefined>();
34
+
35
+ // Measure active panel height on panel change and whenever content resizes
36
+ useLayoutEffect(() => {
37
+ const activeEl = panelRefs.current[currentIndex];
38
+ if (!activeEl) return;
39
+
40
+ const updateHeight = () => setContainerHeight(activeEl.scrollHeight);
41
+ updateHeight();
42
+
43
+ const ro = new ResizeObserver(updateHeight);
44
+ ro.observe(activeEl);
45
+ return () => ro.disconnect();
46
+ }, [currentIndex]);
47
+
48
+ const handleBack = () => {
49
+ if (currentIndex > 0) {
50
+ const previousPanel = panels[currentIndex - 1];
51
+ if (currentPanelData?.onBack) {
52
+ currentPanelData.onBack();
53
+ } else {
54
+ onPanelChange(previousPanel.id);
55
+ }
56
+ }
57
+ };
58
+
59
+ return (
60
+ <div
61
+ className={cn("relative overflow-hidden transition-[height] duration-300", className)}
62
+ style={{ height: containerHeight }}
63
+ >
64
+ {panels.map((panel, index) => {
65
+ const isActive = panel.id === currentPanel;
66
+ const isLeft = index < currentIndex;
67
+ const isRight = index > currentIndex;
68
+
69
+ return (
70
+ <div
71
+ key={panel.id}
72
+ ref={(el) => {
73
+ panelRefs.current[index] = el;
74
+ }}
75
+ className={cn(
76
+ "absolute top-0 left-0 w-full transition-transform duration-300",
77
+ isActive && "translate-x-0",
78
+ isLeft && "-translate-x-full",
79
+ isRight && "translate-x-full"
80
+ )}
81
+ aria-hidden={!isActive}
82
+ >
83
+ {panel.showBackButton && (
84
+ <div className="flex items-center gap-2 mb-4">
85
+ <Button variant="ghost" size="icon" onClick={handleBack}>
86
+ <ChevronLeft className="h-4 w-4" />
87
+ </Button>
88
+ {panel.backButtonLabel && (
89
+ <h3 className="font-semibold">{panel.backButtonLabel}</h3>
90
+ )}
91
+ </div>
92
+ )}
93
+ {panel.content}
94
+ </div>
95
+ );
96
+ })}
97
+ </div>
98
+ );
99
+ };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { useTheme } from "next-themes";
4
+ import { Toaster as Sonner, ToasterProps } from "sonner";
5
+
6
+ const Toaster = ({ ...props }: ToasterProps) => {
7
+ const { theme = "system" } = useTheme();
8
+
9
+ return (
10
+ <Sonner
11
+ theme={theme as ToasterProps["theme"]}
12
+ className="toaster group"
13
+ style={
14
+ {
15
+ "--normal-bg": "var(--popover)",
16
+ "--normal-text": "var(--popover-foreground)",
17
+ "--normal-border": "var(--border)",
18
+ } as React.CSSProperties
19
+ }
20
+ {...props}
21
+ />
22
+ );
23
+ };
24
+
25
+ export { Toaster };
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import { useSticky } from "../hooks/use-sticky";
4
+ import { cn } from "../lib/utils";
5
+
6
+ interface StickyContainerProps {
7
+ /** Content to render inside the sticky container */
8
+ children: React.ReactNode;
9
+ /** Additional className for the sticky element */
10
+ className?: string;
11
+ /** Additional className for the inner container wrapper */
12
+ containerClassName?: string;
13
+ /**
14
+ * Top offset when sticky.
15
+ * Defaults to "sticky-header-safe" which uses the global CSS class
16
+ * to handle safe-area-inset-top correctly on all devices.
17
+ */
18
+ topOffset?: string;
19
+ /** z-index when sticky (default: z-40) */
20
+ zIndex?: string;
21
+ /** Whether to show the background/border/shadow when sticky (default: true) */
22
+ showStickyStyles?: boolean;
23
+ /** Custom className for when sticky */
24
+ stickyClassName?: string;
25
+ /** Hide children until sticky state is reached */
26
+ hideUntilSticky?: boolean;
27
+ /** Optional ID for the sticky container (useful for measuring height for nested sticky elements) */
28
+ id?: string;
29
+ }
30
+
31
+ /**
32
+ * Reusable sticky container component with automatic background/shadow when stuck
33
+ * Uses native CSS position: sticky with IntersectionObserver to detect sticky state
34
+ *
35
+ * Supports nested sticky elements - children can use `sticky bottom-0` to stick
36
+ * to the bottom of this container when scrolling.
37
+ *
38
+ * Automatically handles PWA safe area insets via CSS class.
39
+ *
40
+ * @example
41
+ * <StickyContainer>
42
+ * <SearchBar />
43
+ * <div className="sticky bottom-0">Categories</div>
44
+ * </StickyContainer>
45
+ */
46
+ export const StickyContainer = ({
47
+ children,
48
+ className,
49
+ containerClassName,
50
+ topOffset = "sticky-header-safe",
51
+ zIndex = "z-40",
52
+ showStickyStyles = true,
53
+ stickyClassName,
54
+ hideUntilSticky = false,
55
+ id,
56
+ }: StickyContainerProps) => {
57
+ const { isSticky, ref, sentinelRef } = useSticky();
58
+
59
+ return (
60
+ <>
61
+ {/* Sentinel element - placed 1px above sticky element to detect when it becomes stuck */}
62
+ <div ref={sentinelRef} className="h-px w-full" aria-hidden="true" />
63
+
64
+ {/* Sticky container */}
65
+ <div
66
+ ref={ref}
67
+ id={id}
68
+ className={cn(
69
+ "sticky px-4 transition-all duration-200",
70
+ hideUntilSticky
71
+ ? isSticky
72
+ ? "py-4 opacity-100 translate-y-0"
73
+ : "py-0 opacity-0 -translate-y-2 pointer-events-none"
74
+ : "py-4",
75
+ topOffset,
76
+ zIndex,
77
+ showStickyStyles && isSticky
78
+ ? stickyClassName || "bg-background border-b border-border"
79
+ : "bg-transparent border-b border-transparent",
80
+ className
81
+ )}
82
+ >
83
+ {/* Inner container wrapper - allows passing custom container classes */}
84
+ <div className={cn(containerClassName, "lg:px-4")}>{children}</div>
85
+ </div>
86
+ </>
87
+ );
88
+ };
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SwitchPrimitive from "@radix-ui/react-switch";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Switch = React.forwardRef<
8
+ React.ElementRef<typeof SwitchPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <SwitchPrimitive.Root
12
+ className={cn(
13
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input glass data-[state=checked]:shadow-lg data-[state=checked]:shadow-primary/20 hover:shadow-md",
14
+ className
15
+ )}
16
+ {...props}
17
+ ref={ref}
18
+ >
19
+ <SwitchPrimitive.Thumb
20
+ className={cn(
21
+ "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-all duration-300 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 data-[state=checked]:bg-primary-foreground backdrop-blur-md border border-border/50"
22
+ )}
23
+ />
24
+ </SwitchPrimitive.Root>
25
+ ));
26
+ Switch.displayName = SwitchPrimitive.Root.displayName;
27
+
28
+ export { Switch };
@@ -0,0 +1,90 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div className="relative w-full overflow-auto">
7
+ <table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
8
+ </div>
9
+ )
10
+ );
11
+ Table.displayName = "Table";
12
+
13
+ const TableHeader = React.forwardRef<
14
+ HTMLTableSectionElement,
15
+ React.HTMLAttributes<HTMLTableSectionElement>
16
+ >(({ className, ...props }, ref) => (
17
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
18
+ ));
19
+ TableHeader.displayName = "TableHeader";
20
+
21
+ const TableBody = React.forwardRef<
22
+ HTMLTableSectionElement,
23
+ React.HTMLAttributes<HTMLTableSectionElement>
24
+ >(({ className, ...props }, ref) => (
25
+ <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
26
+ ));
27
+ TableBody.displayName = "TableBody";
28
+
29
+ const TableFooter = React.forwardRef<
30
+ HTMLTableSectionElement,
31
+ React.HTMLAttributes<HTMLTableSectionElement>
32
+ >(({ className, ...props }, ref) => (
33
+ <tfoot
34
+ ref={ref}
35
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
36
+ {...props}
37
+ />
38
+ ));
39
+ TableFooter.displayName = "TableFooter";
40
+
41
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
42
+ ({ className, ...props }, ref) => (
43
+ <tr
44
+ ref={ref}
45
+ className={cn(
46
+ "border-b border-border/50 transition-all duration-300 hover:bg-muted/30 hover:backdrop-blur-sm data-[state=selected]:bg-muted/50",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ )
52
+ );
53
+ TableRow.displayName = "TableRow";
54
+
55
+ const TableHead = React.forwardRef<
56
+ HTMLTableCellElement,
57
+ React.ThHTMLAttributes<HTMLTableCellElement>
58
+ >(({ className, ...props }, ref) => (
59
+ <th
60
+ ref={ref}
61
+ className={cn(
62
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ ));
68
+ TableHead.displayName = "TableHead";
69
+
70
+ const TableCell = React.forwardRef<
71
+ HTMLTableCellElement,
72
+ React.TdHTMLAttributes<HTMLTableCellElement>
73
+ >(({ className, ...props }, ref) => (
74
+ <td
75
+ ref={ref}
76
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
77
+ {...props}
78
+ />
79
+ ));
80
+ TableCell.displayName = "TableCell";
81
+
82
+ const TableCaption = React.forwardRef<
83
+ HTMLTableCaptionElement,
84
+ React.HTMLAttributes<HTMLTableCaptionElement>
85
+ >(({ className, ...props }, ref) => (
86
+ <caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
87
+ ));
88
+ TableCaption.displayName = "TableCaption";
89
+
90
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };