@arolariu/components 0.1.2 → 0.3.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 (360) hide show
  1. package/DEBUGGING.md +396 -396
  2. package/LICENSE.md +21 -21
  3. package/changelog.md +120 -118
  4. package/dist/components/ui/accordion.d.ts +6 -6
  5. package/dist/components/ui/accordion.d.ts.map +1 -1
  6. package/dist/components/ui/accordion.js +20 -28
  7. package/dist/components/ui/accordion.js.map +1 -1
  8. package/dist/components/ui/alert-dialog.d.ts +19 -13
  9. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  10. package/dist/components/ui/alert-dialog.js +40 -63
  11. package/dist/components/ui/alert-dialog.js.map +1 -1
  12. package/dist/components/ui/alert.d.ts +7 -8
  13. package/dist/components/ui/alert.d.ts.map +1 -1
  14. package/dist/components/ui/alert.js +18 -21
  15. package/dist/components/ui/alert.js.map +1 -1
  16. package/dist/components/ui/aspect-ratio.d.ts +1 -2
  17. package/dist/components/ui/aspect-ratio.d.ts.map +1 -1
  18. package/dist/components/ui/aspect-ratio.js +1 -8
  19. package/dist/components/ui/aspect-ratio.js.map +1 -1
  20. package/dist/components/ui/avatar.d.ts +5 -5
  21. package/dist/components/ui/avatar.d.ts.map +1 -1
  22. package/dist/components/ui/avatar.js +17 -20
  23. package/dist/components/ui/avatar.js.map +1 -1
  24. package/dist/components/ui/background-beams.d.ts.map +1 -1
  25. package/dist/components/ui/background-beams.js +6 -6
  26. package/dist/components/ui/background-beams.js.map +1 -1
  27. package/dist/components/ui/badge.d.ts +7 -7
  28. package/dist/components/ui/badge.d.ts.map +1 -1
  29. package/dist/components/ui/badge.js +9 -12
  30. package/dist/components/ui/badge.js.map +1 -1
  31. package/dist/components/ui/breadcrumb.d.ts +17 -9
  32. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  33. package/dist/components/ui/breadcrumb.js +32 -39
  34. package/dist/components/ui/breadcrumb.js.map +1 -1
  35. package/dist/components/ui/bubble-background.d.ts +1 -1
  36. package/dist/components/ui/bubble-background.d.ts.map +1 -1
  37. package/dist/components/ui/bubble-background.js +13 -13
  38. package/dist/components/ui/bubble-background.js.map +1 -1
  39. package/dist/components/ui/button-group.d.ts +13 -0
  40. package/dist/components/ui/button-group.d.ts.map +1 -0
  41. package/dist/components/ui/button-group.js +47 -0
  42. package/dist/components/ui/button-group.js.map +1 -0
  43. package/dist/components/ui/button.d.ts +8 -7
  44. package/dist/components/ui/button.d.ts.map +1 -1
  45. package/dist/components/ui/button.js +16 -15
  46. package/dist/components/ui/button.js.map +1 -1
  47. package/dist/components/ui/calendar.d.ts.map +1 -1
  48. package/dist/components/ui/calendar.js +22 -22
  49. package/dist/components/ui/calendar.js.map +1 -1
  50. package/dist/components/ui/card.d.ts +7 -8
  51. package/dist/components/ui/card.d.ts.map +1 -1
  52. package/dist/components/ui/card.js +33 -46
  53. package/dist/components/ui/card.js.map +1 -1
  54. package/dist/components/ui/carousel.d.ts +7 -8
  55. package/dist/components/ui/carousel.d.ts.map +1 -1
  56. package/dist/components/ui/carousel.js +30 -21
  57. package/dist/components/ui/carousel.js.map +1 -1
  58. package/dist/components/ui/chart.d.ts +37 -29
  59. package/dist/components/ui/chart.d.ts.map +1 -1
  60. package/dist/components/ui/chart.js +29 -27
  61. package/dist/components/ui/chart.js.map +1 -1
  62. package/dist/components/ui/checkbox.d.ts +2 -2
  63. package/dist/components/ui/checkbox.d.ts.map +1 -1
  64. package/dist/components/ui/checkbox.js +11 -13
  65. package/dist/components/ui/checkbox.js.map +1 -1
  66. package/dist/components/ui/collapsible.d.ts +4 -5
  67. package/dist/components/ui/collapsible.d.ts.map +1 -1
  68. package/dist/components/ui/collapsible.js +3 -20
  69. package/dist/components/ui/collapsible.js.map +1 -1
  70. package/dist/components/ui/command.d.ts +79 -17
  71. package/dist/components/ui/command.d.ts.map +1 -1
  72. package/dist/components/ui/command.js +52 -77
  73. package/dist/components/ui/command.js.map +1 -1
  74. package/dist/components/ui/context-menu.d.ts +23 -21
  75. package/dist/components/ui/context-menu.d.ts.map +1 -1
  76. package/dist/components/ui/context-menu.js +60 -104
  77. package/dist/components/ui/context-menu.js.map +1 -1
  78. package/dist/components/ui/counting-number.d.ts +1 -1
  79. package/dist/components/ui/counting-number.d.ts.map +1 -1
  80. package/dist/components/ui/counting-number.js +4 -3
  81. package/dist/components/ui/counting-number.js.map +1 -1
  82. package/dist/components/ui/dialog.d.ts +17 -13
  83. package/dist/components/ui/dialog.d.ts.map +1 -1
  84. package/dist/components/ui/dialog.js +38 -66
  85. package/dist/components/ui/dialog.js.map +1 -1
  86. package/dist/components/ui/dot-background.d.ts +10 -17
  87. package/dist/components/ui/dot-background.d.ts.map +1 -1
  88. package/dist/components/ui/dot-background.js +2 -2
  89. package/dist/components/ui/dot-background.js.map +1 -1
  90. package/dist/components/ui/drawer.d.ts +20 -11
  91. package/dist/components/ui/drawer.d.ts.map +1 -1
  92. package/dist/components/ui/drawer.js +37 -62
  93. package/dist/components/ui/drawer.js.map +1 -1
  94. package/dist/components/ui/dropdown-menu.d.ts +23 -21
  95. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  96. package/dist/components/ui/dropdown-menu.js +65 -109
  97. package/dist/components/ui/dropdown-menu.js.map +1 -1
  98. package/dist/components/ui/dropdrawer.d.ts +3 -3
  99. package/dist/components/ui/dropdrawer.d.ts.map +1 -1
  100. package/dist/components/ui/dropdrawer.js +13 -16
  101. package/dist/components/ui/dropdrawer.js.map +1 -1
  102. package/dist/components/ui/empty.d.ts +13 -0
  103. package/dist/components/ui/empty.d.ts.map +1 -0
  104. package/dist/components/ui/empty.js +65 -0
  105. package/dist/components/ui/empty.js.map +1 -0
  106. package/dist/components/ui/field.d.ts +25 -0
  107. package/dist/components/ui/field.d.ts.map +1 -0
  108. package/dist/components/ui/field.js +135 -0
  109. package/dist/components/ui/field.js.map +1 -0
  110. package/dist/components/ui/fireworks-background.d.ts.map +1 -1
  111. package/dist/components/ui/fireworks-background.js +1 -1
  112. package/dist/components/ui/fireworks-background.js.map +1 -1
  113. package/dist/components/ui/flip-button.d.ts +1 -1
  114. package/dist/components/ui/flip-button.d.ts.map +1 -1
  115. package/dist/components/ui/flip-button.js +3 -3
  116. package/dist/components/ui/flip-button.js.map +1 -1
  117. package/dist/components/ui/form.d.ts +7 -8
  118. package/dist/components/ui/form.d.ts.map +1 -1
  119. package/dist/components/ui/form.js +29 -28
  120. package/dist/components/ui/form.js.map +1 -1
  121. package/dist/components/ui/gradient-background.d.ts +1 -1
  122. package/dist/components/ui/gradient-background.d.ts.map +1 -1
  123. package/dist/components/ui/gradient-background.js +2 -2
  124. package/dist/components/ui/gradient-background.js.map +1 -1
  125. package/dist/components/ui/gradient-text.d.ts +1 -1
  126. package/dist/components/ui/gradient-text.d.ts.map +1 -1
  127. package/dist/components/ui/gradient-text.js +5 -5
  128. package/dist/components/ui/gradient-text.js.map +1 -1
  129. package/dist/components/ui/highlight-text.d.ts +1 -1
  130. package/dist/components/ui/highlight-text.d.ts.map +1 -1
  131. package/dist/components/ui/highlight-text.js +3 -3
  132. package/dist/components/ui/highlight-text.js.map +1 -1
  133. package/dist/components/ui/hole-background.d.ts.map +1 -1
  134. package/dist/components/ui/hole-background.js +11 -10
  135. package/dist/components/ui/hole-background.js.map +1 -1
  136. package/dist/components/ui/hover-card.d.ts +5 -5
  137. package/dist/components/ui/hover-card.d.ts.map +1 -1
  138. package/dist/components/ui/hover-card.js +12 -26
  139. package/dist/components/ui/hover-card.js.map +1 -1
  140. package/dist/components/ui/input-group.d.ts +17 -0
  141. package/dist/components/ui/input-group.d.ts.map +1 -0
  142. package/dist/components/ui/input-group.js +91 -0
  143. package/dist/components/ui/input-group.js.map +1 -0
  144. package/dist/components/ui/input-otp.d.ts +31 -8
  145. package/dist/components/ui/input-otp.d.ts.map +1 -1
  146. package/dist/components/ui/input-otp.js +24 -27
  147. package/dist/components/ui/input-otp.js.map +1 -1
  148. package/dist/components/ui/input.d.ts +1 -1
  149. package/dist/components/ui/input.d.ts.map +1 -1
  150. package/dist/components/ui/input.js +7 -8
  151. package/dist/components/ui/input.js.map +1 -1
  152. package/dist/components/ui/item.d.ts +24 -0
  153. package/dist/components/ui/item.d.ts.map +1 -0
  154. package/dist/components/ui/item.js +122 -0
  155. package/dist/components/ui/item.js.map +1 -0
  156. package/dist/components/ui/kbd.d.ts +5 -0
  157. package/dist/components/ui/kbd.d.ts.map +1 -0
  158. package/dist/components/ui/kbd.js +21 -0
  159. package/dist/components/ui/kbd.js.map +1 -0
  160. package/dist/components/ui/label.d.ts +3 -2
  161. package/dist/components/ui/label.d.ts.map +1 -1
  162. package/dist/components/ui/label.js +9 -8
  163. package/dist/components/ui/label.js.map +1 -1
  164. package/dist/components/ui/menubar.d.ts +20 -18
  165. package/dist/components/ui/menubar.d.ts.map +1 -1
  166. package/dist/components/ui/menubar.js +75 -95
  167. package/dist/components/ui/menubar.js.map +1 -1
  168. package/dist/components/ui/navigation-menu.d.ts +11 -13
  169. package/dist/components/ui/navigation-menu.d.ts.map +1 -1
  170. package/dist/components/ui/navigation-menu.js +39 -58
  171. package/dist/components/ui/navigation-menu.js.map +1 -1
  172. package/dist/components/ui/pagination.d.ts +25 -10
  173. package/dist/components/ui/pagination.d.ts.map +1 -1
  174. package/dist/components/ui/pagination.js +33 -41
  175. package/dist/components/ui/pagination.js.map +1 -1
  176. package/dist/components/ui/popover.d.ts +6 -6
  177. package/dist/components/ui/popover.d.ts.map +1 -1
  178. package/dist/components/ui/popover.js +10 -26
  179. package/dist/components/ui/popover.js.map +1 -1
  180. package/dist/components/ui/progress.d.ts +2 -2
  181. package/dist/components/ui/progress.d.ts.map +1 -1
  182. package/dist/components/ui/progress.js +8 -10
  183. package/dist/components/ui/progress.js.map +1 -1
  184. package/dist/components/ui/radio-group.d.ts +3 -3
  185. package/dist/components/ui/radio-group.d.ts.map +1 -1
  186. package/dist/components/ui/radio-group.js +17 -20
  187. package/dist/components/ui/radio-group.js.map +1 -1
  188. package/dist/components/ui/resizable.d.ts +21 -6
  189. package/dist/components/ui/resizable.d.ts.map +1 -1
  190. package/dist/components/ui/resizable.js +9 -21
  191. package/dist/components/ui/resizable.js.map +1 -1
  192. package/dist/components/ui/ripple-button.d.ts +1 -1
  193. package/dist/components/ui/ripple-button.d.ts.map +1 -1
  194. package/dist/components/ui/ripple-button.js +4 -4
  195. package/dist/components/ui/ripple-button.js.map +1 -1
  196. package/dist/components/ui/scratcher.d.ts.map +1 -1
  197. package/dist/components/ui/scratcher.js +7 -5
  198. package/dist/components/ui/scratcher.js.map +1 -1
  199. package/dist/components/ui/scroll-area.d.ts +3 -3
  200. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  201. package/dist/components/ui/scroll-area.js +14 -18
  202. package/dist/components/ui/scroll-area.js.map +1 -1
  203. package/dist/components/ui/select.d.ts +11 -13
  204. package/dist/components/ui/select.d.ts.map +1 -1
  205. package/dist/components/ui/select.js +55 -78
  206. package/dist/components/ui/select.js.map +1 -1
  207. package/dist/components/ui/separator.d.ts +2 -2
  208. package/dist/components/ui/separator.d.ts.map +1 -1
  209. package/dist/components/ui/separator.js +7 -8
  210. package/dist/components/ui/separator.js.map +1 -1
  211. package/dist/components/ui/sheet.d.ts +24 -12
  212. package/dist/components/ui/sheet.d.ts.map +1 -1
  213. package/dist/components/ui/sheet.js +56 -68
  214. package/dist/components/ui/sheet.js.map +1 -1
  215. package/dist/components/ui/sidebar.d.ts +35 -39
  216. package/dist/components/ui/sidebar.d.ts.map +1 -1
  217. package/dist/components/ui/sidebar.js +115 -124
  218. package/dist/components/ui/sidebar.js.map +1 -1
  219. package/dist/components/ui/skeleton.d.ts +1 -1
  220. package/dist/components/ui/skeleton.d.ts.map +1 -1
  221. package/dist/components/ui/skeleton.js +2 -3
  222. package/dist/components/ui/skeleton.js.map +1 -1
  223. package/dist/components/ui/slider.d.ts +2 -2
  224. package/dist/components/ui/slider.d.ts.map +1 -1
  225. package/dist/components/ui/slider.js +12 -31
  226. package/dist/components/ui/slider.js.map +1 -1
  227. package/dist/components/ui/sonner.d.ts +4 -2
  228. package/dist/components/ui/sonner.d.ts.map +1 -1
  229. package/dist/components/ui/sonner.js +8 -5
  230. package/dist/components/ui/sonner.js.map +1 -1
  231. package/dist/components/ui/spinner.d.ts +4 -0
  232. package/dist/components/ui/spinner.d.ts.map +1 -0
  233. package/dist/components/ui/spinner.js +16 -0
  234. package/dist/components/ui/spinner.js.map +1 -0
  235. package/dist/components/ui/switch.d.ts +2 -2
  236. package/dist/components/ui/switch.d.ts.map +1 -1
  237. package/dist/components/ui/switch.js +8 -10
  238. package/dist/components/ui/switch.js.map +1 -1
  239. package/dist/components/ui/table.d.ts +9 -9
  240. package/dist/components/ui/table.d.ts.map +1 -1
  241. package/dist/components/ui/table.js +40 -49
  242. package/dist/components/ui/table.js.map +1 -1
  243. package/dist/components/ui/tabs.d.ts +6 -6
  244. package/dist/components/ui/tabs.d.ts.map +1 -1
  245. package/dist/components/ui/tabs.js +18 -27
  246. package/dist/components/ui/tabs.js.map +1 -1
  247. package/dist/components/ui/textarea.d.ts +1 -1
  248. package/dist/components/ui/textarea.d.ts.map +1 -1
  249. package/dist/components/ui/textarea.js +7 -8
  250. package/dist/components/ui/textarea.js.map +1 -1
  251. package/dist/components/ui/toggle-group.d.ts +9 -4
  252. package/dist/components/ui/toggle-group.d.ts.map +1 -1
  253. package/dist/components/ui/toggle-group.js +12 -16
  254. package/dist/components/ui/toggle-group.js.map +1 -1
  255. package/dist/components/ui/toggle.d.ts +9 -6
  256. package/dist/components/ui/toggle.d.ts.map +1 -1
  257. package/dist/components/ui/toggle.js +8 -9
  258. package/dist/components/ui/toggle.js.map +1 -1
  259. package/dist/components/ui/tooltip.d.ts +6 -6
  260. package/dist/components/ui/tooltip.d.ts.map +1 -1
  261. package/dist/components/ui/tooltip.js +14 -39
  262. package/dist/components/ui/tooltip.js.map +1 -1
  263. package/dist/components/ui/typewriter.d.ts.map +1 -1
  264. package/dist/components/ui/typewriter.js +9 -9
  265. package/dist/components/ui/typewriter.js.map +1 -1
  266. package/dist/hooks/useIsMobile.d.ts +2 -5
  267. package/dist/hooks/useIsMobile.d.ts.map +1 -1
  268. package/dist/hooks/useIsMobile.js +1 -1
  269. package/dist/hooks/useIsMobile.js.map +1 -1
  270. package/dist/hooks/useWindowSize.d.ts +0 -1
  271. package/dist/hooks/useWindowSize.d.ts.map +1 -1
  272. package/dist/hooks/useWindowSize.js +1 -1
  273. package/dist/hooks/useWindowSize.js.map +1 -1
  274. package/dist/index.css +841 -1128
  275. package/dist/index.css.map +1 -1
  276. package/dist/index.d.ts +43 -37
  277. package/dist/index.d.ts.map +1 -1
  278. package/dist/index.js +11 -5
  279. package/dist/lib/utilities.d.ts +9 -0
  280. package/dist/lib/utilities.d.ts.map +1 -0
  281. package/dist/lib/{utils.js → utilities.js} +1 -1
  282. package/dist/lib/utilities.js.map +1 -0
  283. package/package.json +121 -90
  284. package/{README.md → readme.md} +627 -627
  285. package/src/components/ui/accordion.tsx +55 -66
  286. package/src/components/ui/alert-dialog.tsx +124 -160
  287. package/src/components/ui/alert.tsx +56 -69
  288. package/src/components/ui/aspect-ratio.tsx +7 -12
  289. package/src/components/ui/avatar.tsx +43 -53
  290. package/src/components/ui/background-beams.tsx +145 -142
  291. package/src/components/ui/badge.tsx +39 -48
  292. package/src/components/ui/breadcrumb.tsx +94 -117
  293. package/src/components/ui/bubble-background.tsx +170 -189
  294. package/src/components/ui/button-group.tsx +69 -0
  295. package/src/components/ui/button.tsx +55 -61
  296. package/src/components/ui/calendar.tsx +175 -216
  297. package/src/components/ui/card.tsx +64 -97
  298. package/src/components/ui/carousel.tsx +216 -241
  299. package/src/components/ui/chart.tsx +293 -385
  300. package/src/components/ui/checkbox.tsx +27 -32
  301. package/src/components/ui/collapsible.tsx +11 -34
  302. package/src/components/ui/command.tsx +138 -184
  303. package/src/components/ui/context-menu.tsx +186 -255
  304. package/src/components/ui/counting-number.tsx +92 -108
  305. package/src/components/ui/dialog.tsx +106 -146
  306. package/src/components/ui/dot-background.tsx +153 -158
  307. package/src/components/ui/drawer.tsx +105 -141
  308. package/src/components/ui/dropdown-menu.tsx +188 -260
  309. package/src/components/ui/dropdrawer.tsx +865 -973
  310. package/src/components/ui/empty.tsx +86 -0
  311. package/src/components/ui/field.tsx +198 -0
  312. package/src/components/ui/fireworks-background.tsx +325 -378
  313. package/src/components/ui/flip-button.tsx +89 -110
  314. package/src/components/ui/form.tsx +144 -174
  315. package/src/components/ui/gradient-background.tsx +30 -43
  316. package/src/components/ui/gradient-text.tsx +62 -65
  317. package/src/components/ui/highlight-text.tsx +54 -71
  318. package/src/components/ui/hole-background.tsx +326 -361
  319. package/src/components/ui/hover-card.tsx +29 -44
  320. package/src/components/ui/input-group.tsx +145 -0
  321. package/src/components/ui/input-otp.tsx +66 -77
  322. package/src/components/ui/input.tsx +21 -22
  323. package/src/components/ui/item.tsx +163 -0
  324. package/src/components/ui/kbd.tsx +31 -0
  325. package/src/components/ui/label.tsx +23 -24
  326. package/src/components/ui/menubar.tsx +233 -279
  327. package/src/components/ui/navigation-menu.tsx +120 -171
  328. package/src/components/ui/pagination.tsx +92 -129
  329. package/src/components/ui/popover.tsx +33 -48
  330. package/src/components/ui/progress.tsx +24 -31
  331. package/src/components/ui/radio-group.tsx +43 -45
  332. package/src/components/ui/resizable.tsx +38 -56
  333. package/src/components/ui/ripple-button.tsx +90 -111
  334. package/src/components/ui/scratcher.tsx +167 -171
  335. package/src/components/ui/scroll-area.tsx +42 -58
  336. package/src/components/ui/select.tsx +145 -191
  337. package/src/components/ui/separator.tsx +26 -28
  338. package/src/components/ui/sheet.tsx +112 -145
  339. package/src/components/ui/sidebar.tsx +664 -729
  340. package/src/components/ui/skeleton.tsx +15 -19
  341. package/src/components/ui/slider.tsx +23 -63
  342. package/src/components/ui/sonner.tsx +36 -26
  343. package/src/components/ui/spinner.tsx +18 -0
  344. package/src/components/ui/switch.tsx +28 -31
  345. package/src/components/ui/table.tsx +93 -119
  346. package/src/components/ui/tabs.tsx +54 -66
  347. package/src/components/ui/textarea.tsx +21 -20
  348. package/src/components/ui/toggle-group.tsx +53 -73
  349. package/src/components/ui/toggle.tsx +44 -47
  350. package/src/components/ui/tooltip.tsx +32 -61
  351. package/src/components/ui/typewriter.tsx +173 -188
  352. package/src/hooks/useIsMobile.tsx +42 -45
  353. package/src/hooks/useWindowSize.tsx +66 -72
  354. package/src/index.css +67 -67
  355. package/src/index.ts +342 -408
  356. package/src/lib/utilities.ts +12 -0
  357. package/dist/lib/utils.d.ts +0 -7
  358. package/dist/lib/utils.d.ts.map +0 -1
  359. package/dist/lib/utils.js.map +0 -1
  360. package/src/lib/utils.ts +0 -10
@@ -1,973 +1,865 @@
1
- "use client";
2
-
3
- import { AnimatePresence, motion, Transition } from "motion/react";
4
- import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
5
- import * as React from "react";
6
-
7
- import {
8
- Drawer,
9
- DrawerClose,
10
- DrawerContent,
11
- DrawerFooter,
12
- DrawerHeader,
13
- DrawerTitle,
14
- DrawerTrigger,
15
- } from "@/components/ui/drawer";
16
- import {
17
- DropdownMenu,
18
- DropdownMenuContent,
19
- DropdownMenuItem,
20
- DropdownMenuLabel,
21
- DropdownMenuSeparator,
22
- DropdownMenuSub,
23
- DropdownMenuSubContent,
24
- DropdownMenuSubTrigger,
25
- DropdownMenuTrigger,
26
- } from "@/components/ui/dropdown-menu";
27
- import { useIsMobile } from "@/hooks/useIsMobile";
28
- import { cn } from "@/lib/utils";
29
-
30
- const DropDrawerContext = React.createContext<{ isMobile: boolean }>({
31
- isMobile: false,
32
- });
33
-
34
- const useDropDrawerContext = () => {
35
- const context = React.useContext(DropDrawerContext);
36
- if (!context) {
37
- throw new Error(
38
- "DropDrawer components cannot be rendered outside the Context",
39
- );
40
- }
41
- return context;
42
- };
43
-
44
- function DropDrawer({
45
- children,
46
- ...props
47
- }:
48
- | React.ComponentProps<typeof Drawer>
49
- | React.ComponentProps<typeof DropdownMenu>) {
50
- const isMobile = useIsMobile();
51
- const DropdownComponent = isMobile ? Drawer : DropdownMenu;
52
-
53
- return (
54
- <DropDrawerContext.Provider value={{ isMobile }}>
55
- <DropdownComponent
56
- data-slot="drop-drawer"
57
- {...(isMobile && { autoFocus: true })}
58
- {...props}
59
- >
60
- {children}
61
- </DropdownComponent>
62
- </DropDrawerContext.Provider>
63
- );
64
- }
65
-
66
- function DropDrawerTrigger({
67
- className,
68
- children,
69
- ...props
70
- }:
71
- | React.ComponentProps<typeof DrawerTrigger>
72
- | React.ComponentProps<typeof DropdownMenuTrigger>) {
73
- const { isMobile } = useDropDrawerContext();
74
- const TriggerComponent = isMobile ? DrawerTrigger : DropdownMenuTrigger;
75
-
76
- return (
77
- <TriggerComponent
78
- data-slot="drop-drawer-trigger"
79
- className={className}
80
- {...props}
81
- >
82
- {children}
83
- </TriggerComponent>
84
- );
85
- }
86
-
87
- function DropDrawerContent({
88
- className,
89
- children,
90
- ...props
91
- }:
92
- | React.ComponentProps<typeof DrawerContent>
93
- | React.ComponentProps<typeof DropdownMenuContent>) {
94
- const { isMobile } = useDropDrawerContext();
95
- const [activeSubmenu, setActiveSubmenu] = React.useState<string | null>(null);
96
- const [submenuTitle, setSubmenuTitle] = React.useState<string | null>(null);
97
- const [submenuStack, setSubmenuStack] = React.useState<
98
- { id: string; title: string }[]
99
- >([]);
100
- // Add animation direction state
101
- const [animationDirection, setAnimationDirection] = React.useState<
102
- "forward" | "backward"
103
- >("forward");
104
-
105
- // Create a ref to store submenu content by ID
106
- const submenuContentRef = React.useRef<Map<string, React.ReactNode[]>>(
107
- new Map(),
108
- );
109
-
110
- // Function to navigate to a submenu
111
- const navigateToSubmenu = React.useCallback((id: string, title: string) => {
112
- // Set animation direction to forward when navigating to a submenu
113
- setAnimationDirection("forward");
114
- setActiveSubmenu(id);
115
- setSubmenuTitle(title);
116
- setSubmenuStack((prev) => [...prev, { id, title }]);
117
- }, []);
118
-
119
- // Function to go back to previous menu
120
- const goBack = React.useCallback(() => {
121
- // Set animation direction to backward when going back
122
- setAnimationDirection("backward");
123
-
124
- if (submenuStack.length <= 1) {
125
- // If we're at the first level, go back to main menu
126
- setActiveSubmenu(null);
127
- setSubmenuTitle(null);
128
- setSubmenuStack([]);
129
- } else {
130
- // Go back to previous submenu
131
- const newStack = [...submenuStack];
132
- newStack.pop(); // Remove current
133
- const previous = newStack[newStack.length - 1];
134
- setActiveSubmenu(previous.id);
135
- setSubmenuTitle(previous.title);
136
- setSubmenuStack(newStack);
137
- }
138
- }, [submenuStack]);
139
-
140
- // Function to register submenu content
141
- const registerSubmenuContent = React.useCallback(
142
- (id: string, content: React.ReactNode[]) => {
143
- submenuContentRef.current.set(id, content);
144
- },
145
- [],
146
- );
147
-
148
- // Function to extract submenu content
149
- const extractSubmenuContent = React.useCallback(
150
- (elements: React.ReactNode, targetId: string): React.ReactNode[] => {
151
- const result: React.ReactNode[] = [];
152
-
153
- // Recursive function to search through all children
154
- const findSubmenuContent = (node: React.ReactNode) => {
155
- // Skip if not a valid element
156
- if (!React.isValidElement(node)) return;
157
-
158
- const element = node as React.ReactElement;
159
- // Use a more specific type to avoid 'any'
160
- const props = element.props as {
161
- id?: string;
162
- "data-submenu-id"?: string;
163
- children?: React.ReactNode;
164
- };
165
-
166
- // Check if this is a DropDrawerSub
167
- if (element.type === DropDrawerSub) {
168
- // Get all possible ID values
169
- const elementId = props.id;
170
- const dataSubmenuId = props["data-submenu-id"];
171
-
172
- // If this is the submenu we're looking for
173
- if (elementId === targetId || dataSubmenuId === targetId) {
174
- // Find the SubContent within this Sub
175
- if (props.children) {
176
- React.Children.forEach(props.children, (child) => {
177
- if (
178
- React.isValidElement(child) &&
179
- child.type === DropDrawerSubContent
180
- ) {
181
- // Add all children of the SubContent to the result
182
- const subContentProps = child.props as {
183
- children?: React.ReactNode;
184
- };
185
- if (subContentProps.children) {
186
- React.Children.forEach(
187
- subContentProps.children,
188
- (contentChild) => {
189
- result.push(contentChild);
190
- },
191
- );
192
- }
193
- }
194
- });
195
- }
196
- return; // Found what we needed, no need to search deeper
197
- }
198
- }
199
-
200
- // If this element has children, search through them
201
- if (props.children) {
202
- if (Array.isArray(props.children)) {
203
- props.children.forEach((child: React.ReactNode) =>
204
- findSubmenuContent(child),
205
- );
206
- } else {
207
- findSubmenuContent(props.children);
208
- }
209
- }
210
- };
211
-
212
- // Start the search from the root elements
213
- if (Array.isArray(elements)) {
214
- elements.forEach((child) => findSubmenuContent(child));
215
- } else {
216
- findSubmenuContent(elements);
217
- }
218
-
219
- return result;
220
- },
221
- [],
222
- );
223
-
224
- // Get submenu content (either from cache or extract it)
225
- const getSubmenuContent = React.useCallback(
226
- (id: string) => {
227
- // Check if we have the content in our ref
228
- const cachedContent = submenuContentRef.current.get(id || "");
229
- if (cachedContent && cachedContent.length > 0) {
230
- return cachedContent;
231
- }
232
-
233
- // If not in cache, extract it
234
- const submenuContent = extractSubmenuContent(children, id);
235
-
236
- if (submenuContent.length === 0) {
237
- return [];
238
- }
239
-
240
- // Store in cache for future use
241
- if (id) {
242
- submenuContentRef.current.set(id, submenuContent);
243
- }
244
-
245
- return submenuContent;
246
- },
247
- [children, extractSubmenuContent],
248
- );
249
-
250
- // Animation variants for Framer Motion
251
- const variants = {
252
- enter: (direction: "forward" | "backward") => ({
253
- x: direction === "forward" ? "100%" : "-100%",
254
- opacity: 0,
255
- }),
256
- center: {
257
- x: 0,
258
- opacity: 1,
259
- },
260
- exit: (direction: "forward" | "backward") => ({
261
- x: direction === "forward" ? "-100%" : "100%",
262
- opacity: 0,
263
- }),
264
- };
265
-
266
- // Animation transition
267
- const transition = {
268
- duration: 0.3,
269
- ease: [0.25, 0.1, 0.25, 1.0], // cubic-bezier easing
270
- } satisfies Transition;
271
-
272
- if (isMobile) {
273
- return (
274
- <SubmenuContext.Provider
275
- value={{
276
- activeSubmenu,
277
- setActiveSubmenu: (id) => {
278
- if (id === null) {
279
- setActiveSubmenu(null);
280
- setSubmenuTitle(null);
281
- setSubmenuStack([]);
282
- }
283
- },
284
- submenuTitle,
285
- setSubmenuTitle,
286
- navigateToSubmenu,
287
- registerSubmenuContent,
288
- }}
289
- >
290
- <DrawerContent
291
- data-slot="drop-drawer-content"
292
- className={cn("max-h-[90vh]", className)}
293
- {...props}
294
- >
295
- {activeSubmenu ? (
296
- <>
297
- <DrawerHeader>
298
- <div className="flex items-center gap-2">
299
- <button
300
- onClick={goBack}
301
- className="hover:bg-neutral-100/50 rounded-full p-1 dark:hover:bg-neutral-800/50"
302
- >
303
- <ChevronLeftIcon className="h-5 w-5" />
304
- </button>
305
- <DrawerTitle>{submenuTitle || "Submenu"}</DrawerTitle>
306
- </div>
307
- </DrawerHeader>
308
- <div className="flex-1 relative overflow-y-auto max-h-[70vh]">
309
- {/* Use AnimatePresence to handle exit animations */}
310
- <AnimatePresence
311
- initial={false}
312
- mode="wait"
313
- custom={animationDirection}
314
- >
315
- <motion.div
316
- key={activeSubmenu || "main"}
317
- custom={animationDirection}
318
- variants={variants}
319
- initial="enter"
320
- animate="center"
321
- exit="exit"
322
- transition={transition}
323
- className="pb-6 space-y-1.5 w-full h-full"
324
- >
325
- {activeSubmenu
326
- ? getSubmenuContent(activeSubmenu)
327
- : children}
328
- </motion.div>
329
- </AnimatePresence>
330
- </div>
331
- </>
332
- ) : (
333
- <>
334
- <DrawerHeader className="sr-only">
335
- <DrawerTitle>Menu</DrawerTitle>
336
- </DrawerHeader>
337
- <div className="overflow-y-auto max-h-[70vh]">
338
- <AnimatePresence
339
- initial={false}
340
- mode="wait"
341
- custom={animationDirection}
342
- >
343
- <motion.div
344
- key="main-menu"
345
- custom={animationDirection}
346
- variants={variants}
347
- initial="enter"
348
- animate="center"
349
- exit="exit"
350
- transition={transition}
351
- className="pb-6 space-y-1.5 w-full"
352
- >
353
- {children}
354
- </motion.div>
355
- </AnimatePresence>
356
- </div>
357
- </>
358
- )}
359
- </DrawerContent>
360
- </SubmenuContext.Provider>
361
- );
362
- }
363
-
364
- return (
365
- <SubmenuContext.Provider
366
- value={{
367
- activeSubmenu,
368
- setActiveSubmenu,
369
- submenuTitle,
370
- setSubmenuTitle,
371
- registerSubmenuContent,
372
- }}
373
- >
374
- <DropdownMenuContent
375
- data-slot="drop-drawer-content"
376
- align="end"
377
- sideOffset={4}
378
- className={cn(
379
- "max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[220px] overflow-y-auto",
380
- className,
381
- )}
382
- {...props}
383
- >
384
- {children}
385
- </DropdownMenuContent>
386
- </SubmenuContext.Provider>
387
- );
388
- }
389
-
390
- function DropDrawerItem({
391
- className,
392
- children,
393
- onSelect,
394
- onClick,
395
- icon,
396
- variant = "default",
397
- inset,
398
- disabled,
399
- ...props
400
- }: React.ComponentProps<typeof DropdownMenuItem> & {
401
- icon?: React.ReactNode;
402
- }) {
403
- const { isMobile } = useDropDrawerContext();
404
-
405
- // Define hooks outside of conditionals to follow React rules
406
- // Check if this item is inside a group by looking at parent elements
407
- const isInGroup = React.useCallback(
408
- (element: HTMLElement | null): boolean => {
409
- if (!element) return false;
410
-
411
- // Check if any parent has a data-drop-drawer-group attribute
412
- let parent = element.parentElement;
413
- while (parent) {
414
- if (parent.hasAttribute("data-drop-drawer-group")) {
415
- return true;
416
- }
417
- parent = parent.parentElement;
418
- }
419
- return false;
420
- },
421
- [],
422
- );
423
-
424
- // Create a ref to check if the item is in a group
425
- const itemRef = React.useRef<HTMLDivElement>(null);
426
- const [isInsideGroup, setIsInsideGroup] = React.useState(false);
427
-
428
- React.useEffect(() => {
429
- // Only run this effect in mobile mode
430
- if (!isMobile) return;
431
-
432
- // Use a short timeout to ensure the DOM is fully rendered
433
- const timer = setTimeout(() => {
434
- if (itemRef.current) {
435
- setIsInsideGroup(isInGroup(itemRef.current));
436
- }
437
- }, 0);
438
-
439
- return () => clearTimeout(timer);
440
- }, [isInGroup, isMobile]);
441
-
442
- if (isMobile) {
443
- const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
444
- if (disabled) return;
445
- if (onClick) onClick(e);
446
- if (onSelect) onSelect(e as unknown as Event);
447
- };
448
-
449
- // Only wrap in DrawerClose if it's not a submenu item
450
- const content = (
451
- <div
452
- ref={itemRef}
453
- data-slot="drop-drawer-item"
454
- data-variant={variant}
455
- data-inset={inset}
456
- data-disabled={disabled}
457
- className={cn(
458
- "flex cursor-pointer items-center justify-between px-4 py-4",
459
- // Only apply margin, background and rounded corners if not in a group
460
- !isInsideGroup &&
461
- "bg-neutral-100 dark:bg-neutral-100 mx-2 my-1.5 rounded-md dark:bg-neutral-800 dark:dark:bg-neutral-800",
462
- // For items in a group, don't add background but add more padding
463
- isInsideGroup && "bg-transparent py-4",
464
- inset && "pl-8",
465
- variant === "destructive" &&
466
- "text-red-500 dark:text-red-500 dark:text-red-900 dark:dark:text-red-900",
467
- disabled && "pointer-events-none opacity-50",
468
- className,
469
- )}
470
- onClick={handleClick}
471
- aria-disabled={disabled}
472
- {...props}
473
- >
474
- <div className="flex items-center gap-2">{children}</div>
475
- {icon && <div className="flex-shrink-0">{icon}</div>}
476
- </div>
477
- );
478
-
479
- // Check if this is inside a submenu
480
- const isInSubmenu =
481
- (props as Record<string, unknown>)["data-parent-submenu-id"] ||
482
- (props as Record<string, unknown>)["data-parent-submenu"];
483
-
484
- if (isInSubmenu) {
485
- return content;
486
- }
487
-
488
- return <DrawerClose asChild>{content}</DrawerClose>;
489
- }
490
-
491
- return (
492
- <DropdownMenuItem
493
- data-slot="drop-drawer-item"
494
- data-variant={variant}
495
- data-inset={inset}
496
- className={className}
497
- onSelect={onSelect}
498
- onClick={onClick as React.MouseEventHandler<HTMLDivElement>}
499
- variant={variant}
500
- inset={inset}
501
- disabled={disabled}
502
- {...props}
503
- >
504
- <div className="flex w-full items-center justify-between">
505
- <div>{children}</div>
506
- {icon && <div>{icon}</div>}
507
- </div>
508
- </DropdownMenuItem>
509
- );
510
- }
511
-
512
- function DropDrawerSeparator({
513
- className,
514
- ...props
515
- }: React.ComponentProps<typeof DropdownMenuSeparator>) {
516
- const { isMobile } = useDropDrawerContext();
517
-
518
- // For mobile, render a simple divider
519
- if (isMobile) {
520
- return null;
521
- }
522
-
523
- // For desktop, use the standard dropdown separator
524
- return (
525
- <DropdownMenuSeparator
526
- data-slot="drop-drawer-separator"
527
- className={className}
528
- {...props}
529
- />
530
- );
531
- }
532
-
533
- function DropDrawerLabel({
534
- className,
535
- children,
536
- ...props
537
- }:
538
- | React.ComponentProps<typeof DropdownMenuLabel>
539
- | React.ComponentProps<typeof DrawerTitle>) {
540
- const { isMobile } = useDropDrawerContext();
541
-
542
- if (isMobile) {
543
- return (
544
- <DrawerHeader className="p-0">
545
- <DrawerTitle
546
- data-slot="drop-drawer-label"
547
- className={cn(
548
- "text-neutral-500 px-4 py-2 text-sm font-medium dark:text-neutral-400",
549
- className,
550
- )}
551
- {...props}
552
- >
553
- {children}
554
- </DrawerTitle>
555
- </DrawerHeader>
556
- );
557
- }
558
-
559
- return (
560
- <DropdownMenuLabel
561
- data-slot="drop-drawer-label"
562
- className={className}
563
- {...props}
564
- >
565
- {children}
566
- </DropdownMenuLabel>
567
- );
568
- }
569
-
570
- function DropDrawerFooter({
571
- className,
572
- children,
573
- ...props
574
- }: React.ComponentProps<typeof DrawerFooter> | React.ComponentProps<"div">) {
575
- const { isMobile } = useDropDrawerContext();
576
-
577
- if (isMobile) {
578
- return (
579
- <DrawerFooter
580
- data-slot="drop-drawer-footer"
581
- className={cn("p-4", className)}
582
- {...props}
583
- >
584
- {children}
585
- </DrawerFooter>
586
- );
587
- }
588
-
589
- // No direct equivalent in DropdownMenu, so we'll just render a div
590
- return (
591
- <div
592
- data-slot="drop-drawer-footer"
593
- className={cn("p-2", className)}
594
- {...props}
595
- >
596
- {children}
597
- </div>
598
- );
599
- }
600
-
601
- function DropDrawerGroup({
602
- className,
603
- children,
604
- ...props
605
- }: React.ComponentProps<"div"> & {
606
- children: React.ReactNode;
607
- }) {
608
- const { isMobile } = useDropDrawerContext();
609
-
610
- // Add separators between children on mobile
611
- const childrenWithSeparators = React.useMemo(() => {
612
- if (!isMobile) return children;
613
-
614
- const childArray = React.Children.toArray(children);
615
-
616
- // Filter out any existing separators
617
- const filteredChildren = childArray.filter(
618
- (child) =>
619
- React.isValidElement(child) && child.type !== DropDrawerSeparator,
620
- );
621
-
622
- // Add separators between items
623
- return filteredChildren.flatMap((child, index) => {
624
- if (index === filteredChildren.length - 1) return [child];
625
- return [
626
- child,
627
- <div
628
- key={`separator-${index}`}
629
- className="bg-neutral-200 h-px dark:bg-neutral-800"
630
- aria-hidden="true"
631
- />,
632
- ];
633
- });
634
- }, [children, isMobile]);
635
-
636
- if (isMobile) {
637
- return (
638
- <div
639
- data-drop-drawer-group
640
- data-slot="drop-drawer-group"
641
- role="group"
642
- className={cn(
643
- "bg-neutral-100 dark:bg-neutral-100 mx-2 my-3 overflow-hidden rounded-xl dark:bg-neutral-800 dark:dark:bg-neutral-800",
644
- className,
645
- )}
646
- {...props}
647
- >
648
- {childrenWithSeparators}
649
- </div>
650
- );
651
- }
652
-
653
- // On desktop, use a div with proper role and attributes
654
- return (
655
- <div
656
- data-drop-drawer-group
657
- data-slot="drop-drawer-group"
658
- role="group"
659
- className={className}
660
- {...props}
661
- >
662
- {children}
663
- </div>
664
- );
665
- }
666
-
667
- // Context for managing submenu state on mobile
668
- interface SubmenuContextType {
669
- activeSubmenu: string | null;
670
- setActiveSubmenu: (id: string | null) => void;
671
- submenuTitle: string | null;
672
- setSubmenuTitle: (title: string | null) => void;
673
- navigateToSubmenu?: (id: string, title: string) => void;
674
- registerSubmenuContent?: (id: string, content: React.ReactNode[]) => void;
675
- }
676
-
677
- const SubmenuContext = React.createContext<SubmenuContextType>({
678
- activeSubmenu: null,
679
- setActiveSubmenu: () => {},
680
- submenuTitle: null,
681
- setSubmenuTitle: () => {},
682
- navigateToSubmenu: undefined,
683
- registerSubmenuContent: undefined,
684
- });
685
-
686
- // Submenu components
687
- // Counter for generating simple numeric IDs
688
- let submenuIdCounter = 0;
689
-
690
- function DropDrawerSub({
691
- children,
692
- id,
693
- ...props
694
- }: React.ComponentProps<typeof DropdownMenuSub> & {
695
- id?: string;
696
- }) {
697
- const { isMobile } = useDropDrawerContext();
698
- const { registerSubmenuContent } = React.useContext(SubmenuContext);
699
-
700
- // Generate a simple numeric ID instead of using React.useId()
701
- const [generatedId] = React.useState(() => `submenu-${submenuIdCounter++}`);
702
- const submenuId = id || generatedId;
703
-
704
- // Extract submenu content to register with parent
705
- React.useEffect(() => {
706
- if (!registerSubmenuContent) return;
707
-
708
- // Find the SubContent within this Sub
709
- const contentItems: React.ReactNode[] = [];
710
- React.Children.forEach(children, (child) => {
711
- if (React.isValidElement(child) && child.type === DropDrawerSubContent) {
712
- // Add all children of the SubContent to the result
713
- React.Children.forEach(
714
- (child.props as { children?: React.ReactNode }).children,
715
- (contentChild) => {
716
- contentItems.push(contentChild);
717
- },
718
- );
719
- }
720
- });
721
-
722
- // Register the content with the parent
723
- if (contentItems.length > 0) {
724
- registerSubmenuContent(submenuId, contentItems);
725
- }
726
- }, [children, registerSubmenuContent, submenuId]);
727
-
728
- if (isMobile) {
729
- // For mobile, we'll use the context to manage submenu state
730
- // Process children to pass the submenu ID to the trigger and content
731
- const processedChildren = React.Children.map(children, (child) => {
732
- if (!React.isValidElement(child)) return child;
733
-
734
- if (child.type === DropDrawerSubTrigger) {
735
- return React.cloneElement(
736
- child as React.ReactElement,
737
- {
738
- ...(child.props as object),
739
- "data-parent-submenu-id": submenuId,
740
- "data-submenu-id": submenuId,
741
- // Use only data attributes, not custom props
742
- "data-parent-submenu": submenuId,
743
- } as React.HTMLAttributes<HTMLElement>,
744
- );
745
- }
746
-
747
- if (child.type === DropDrawerSubContent) {
748
- return React.cloneElement(
749
- child as React.ReactElement,
750
- {
751
- ...(child.props as object),
752
- "data-parent-submenu-id": submenuId,
753
- "data-submenu-id": submenuId,
754
- // Use only data attributes, not custom props
755
- "data-parent-submenu": submenuId,
756
- } as React.HTMLAttributes<HTMLElement>,
757
- );
758
- }
759
-
760
- return child;
761
- });
762
-
763
- return (
764
- <div
765
- data-slot="drop-drawer-sub"
766
- data-submenu-id={submenuId}
767
- id={submenuId}
768
- >
769
- {processedChildren}
770
- </div>
771
- );
772
- }
773
-
774
- // For desktop, pass the generated ID to the DropdownMenuSub
775
- return (
776
- <DropdownMenuSub
777
- data-slot="drop-drawer-sub"
778
- data-submenu-id={submenuId}
779
- // Don't pass id to DropdownMenuSub as it doesn't accept this prop
780
- {...props}
781
- >
782
- {children}
783
- </DropdownMenuSub>
784
- );
785
- }
786
-
787
- function DropDrawerSubTrigger({
788
- className,
789
- inset,
790
- children,
791
- ...props
792
- }: React.ComponentProps<typeof DropdownMenuSubTrigger> & {
793
- icon?: React.ReactNode;
794
- }) {
795
- const { isMobile } = useDropDrawerContext();
796
- const { navigateToSubmenu } = React.useContext(SubmenuContext);
797
-
798
- // Define hooks outside of conditionals to follow React rules
799
- // Check if this item is inside a group by looking at parent elements
800
- const isInGroup = React.useCallback(
801
- (element: HTMLElement | null): boolean => {
802
- if (!element) return false;
803
-
804
- // Check if any parent has a data-drop-drawer-group attribute
805
- let parent = element.parentElement;
806
- while (parent) {
807
- if (parent.hasAttribute("data-drop-drawer-group")) {
808
- return true;
809
- }
810
- parent = parent.parentElement;
811
- }
812
- return false;
813
- },
814
- [],
815
- );
816
-
817
- // Create a ref to check if the item is in a group
818
- const itemRef = React.useRef<HTMLDivElement>(null);
819
- const [isInsideGroup, setIsInsideGroup] = React.useState(false);
820
-
821
- React.useEffect(() => {
822
- // Only run this effect in mobile mode
823
- if (!isMobile) return;
824
-
825
- // Use a short timeout to ensure the DOM is fully rendered
826
- const timer = setTimeout(() => {
827
- if (itemRef.current) {
828
- setIsInsideGroup(isInGroup(itemRef.current));
829
- }
830
- }, 0);
831
-
832
- return () => clearTimeout(timer);
833
- }, [isInGroup, isMobile]);
834
-
835
- if (isMobile) {
836
- // Find the parent submenu ID
837
- const handleClick = (e: React.MouseEvent) => {
838
- e.preventDefault();
839
- e.stopPropagation();
840
-
841
- // Get the closest parent with data-submenu-id attribute
842
- const element = e.currentTarget as HTMLElement;
843
- let submenuId: string | null = null;
844
-
845
- // First check if the element itself has the data attribute
846
- if (element.closest("[data-submenu-id]")) {
847
- const closestElement = element.closest("[data-submenu-id]");
848
- const id = closestElement?.getAttribute("data-submenu-id");
849
- if (id) {
850
- submenuId = id;
851
- }
852
- }
853
-
854
- // If not found, try props
855
- if (!submenuId) {
856
- submenuId =
857
- ((props as Record<string, unknown>)[
858
- "data-parent-submenu-id"
859
- ] as string) ||
860
- ((props as Record<string, unknown>)["data-parent-submenu"] as string);
861
- }
862
-
863
- if (!submenuId) {
864
- return;
865
- }
866
-
867
- // Get the title
868
- const title = typeof children === "string" ? children : "Submenu";
869
-
870
- // Navigate to the submenu
871
- if (navigateToSubmenu) {
872
- navigateToSubmenu(submenuId, title);
873
- }
874
- };
875
-
876
- // Combine onClick handlers
877
- const combinedOnClick = (e: React.MouseEvent) => {
878
- // Call the original onClick if provided
879
- const typedProps = props as Record<string, unknown>;
880
- if (typedProps["onClick"]) {
881
- const originalOnClick = typedProps[
882
- "onClick"
883
- ] as React.MouseEventHandler<HTMLDivElement>;
884
- originalOnClick(e as React.MouseEvent<HTMLDivElement>);
885
- }
886
-
887
- // Call our navigation handler
888
- handleClick(e);
889
- };
890
-
891
- // Remove onClick from props to avoid duplicate handlers
892
- const { ...restProps } = props as Record<string, unknown>;
893
-
894
- // Don't wrap in DrawerClose for submenu triggers
895
- return (
896
- <div
897
- ref={itemRef}
898
- data-slot="drop-drawer-sub-trigger"
899
- data-inset={inset}
900
- className={cn(
901
- "flex cursor-pointer items-center justify-between px-4 py-4",
902
- // Only apply margin, background and rounded corners if not in a group
903
- !isInsideGroup &&
904
- "bg-neutral-100 dark:bg-neutral-100 mx-2 my-1.5 rounded-md dark:bg-neutral-800 dark:dark:bg-neutral-800",
905
- // For items in a group, don't add background but add more padding
906
- isInsideGroup && "bg-transparent py-4",
907
- inset && "pl-8",
908
- className,
909
- )}
910
- onClick={combinedOnClick}
911
- {...restProps}
912
- >
913
- <div className="flex items-center gap-2">{children}</div>
914
- <ChevronRightIcon className="h-5 w-5" />
915
- </div>
916
- );
917
- }
918
-
919
- return (
920
- <DropdownMenuSubTrigger
921
- data-slot="drop-drawer-sub-trigger"
922
- data-inset={inset}
923
- className={className}
924
- inset={inset}
925
- {...props}
926
- >
927
- {children}
928
- </DropdownMenuSubTrigger>
929
- );
930
- }
931
-
932
- function DropDrawerSubContent({
933
- className,
934
- sideOffset = 4,
935
- children,
936
- ...props
937
- }: React.ComponentProps<typeof DropdownMenuSubContent>) {
938
- const { isMobile } = useDropDrawerContext();
939
-
940
- if (isMobile) {
941
- // For mobile, we don't render the content directly
942
- // It will be rendered by the DropDrawerContent component when active
943
- return null;
944
- }
945
-
946
- return (
947
- <DropdownMenuSubContent
948
- data-slot="drop-drawer-sub-content"
949
- sideOffset={sideOffset}
950
- className={cn(
951
- "z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 p-1 shadow-lg dark:border-neutral-800",
952
- className,
953
- )}
954
- {...props}
955
- >
956
- {children}
957
- </DropdownMenuSubContent>
958
- );
959
- }
960
-
961
- export {
962
- DropDrawer,
963
- DropDrawerContent,
964
- DropDrawerFooter,
965
- DropDrawerGroup,
966
- DropDrawerItem,
967
- DropDrawerLabel,
968
- DropDrawerSeparator,
969
- DropDrawerSub,
970
- DropDrawerSubContent,
971
- DropDrawerSubTrigger,
972
- DropDrawerTrigger,
973
- };
1
+ "use client";
2
+
3
+ /* eslint-disable */
4
+
5
+ import {ChevronLeftIcon, ChevronRightIcon} from "lucide-react";
6
+ import {AnimatePresence, motion, Transition} from "motion/react";
7
+ import * as React from "react";
8
+
9
+ import {Drawer, DrawerClose, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger} from "@/components/ui/drawer";
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuLabel,
15
+ DropdownMenuSeparator,
16
+ DropdownMenuSub,
17
+ DropdownMenuSubContent,
18
+ DropdownMenuSubTrigger,
19
+ DropdownMenuTrigger,
20
+ } from "@/components/ui/dropdown-menu";
21
+ import {useIsMobile} from "@/hooks/useIsMobile";
22
+ import {cn} from "@/lib/utilities";
23
+
24
+ const DropDrawerContext = React.createContext<{isMobile: boolean}>({
25
+ isMobile: false,
26
+ });
27
+
28
+ const useDropDrawerContext = () => {
29
+ const context = React.useContext(DropDrawerContext);
30
+ if (!context) {
31
+ throw new Error("DropDrawer components cannot be rendered outside the Context");
32
+ }
33
+ return context;
34
+ };
35
+
36
+ function DropDrawer({children, ...props}: React.ComponentProps<typeof Drawer> | React.ComponentProps<typeof DropdownMenu>) {
37
+ const isMobile = useIsMobile();
38
+ const DropdownComponent = isMobile ? Drawer : DropdownMenu;
39
+
40
+ return (
41
+ <DropDrawerContext.Provider value={{isMobile}}>
42
+ <DropdownComponent
43
+ data-slot='drop-drawer'
44
+ {...(isMobile && {autoFocus: true})}
45
+ {...props}>
46
+ {children}
47
+ </DropdownComponent>
48
+ </DropDrawerContext.Provider>
49
+ );
50
+ }
51
+
52
+ function DropDrawerTrigger({
53
+ className,
54
+ children,
55
+ ...props
56
+ }: React.ComponentProps<typeof DrawerTrigger> | React.ComponentProps<typeof DropdownMenuTrigger>) {
57
+ const {isMobile} = useDropDrawerContext();
58
+ const TriggerComponent = isMobile ? DrawerTrigger : DropdownMenuTrigger;
59
+
60
+ return (
61
+ <TriggerComponent
62
+ data-slot='drop-drawer-trigger'
63
+ className={className}
64
+ {...props}>
65
+ {children}
66
+ </TriggerComponent>
67
+ );
68
+ }
69
+
70
+ function DropDrawerContent({
71
+ className,
72
+ children,
73
+ ...props
74
+ }: React.ComponentProps<typeof DrawerContent> | React.ComponentProps<typeof DropdownMenuContent>) {
75
+ const {isMobile} = useDropDrawerContext();
76
+ const [activeSubmenu, setActiveSubmenu] = React.useState<string | null>(null);
77
+ const [submenuTitle, setSubmenuTitle] = React.useState<string | null>(null);
78
+ const [submenuStack, setSubmenuStack] = React.useState<{id: string; title: string}[]>([]);
79
+ // Add animation direction state
80
+ const [animationDirection, setAnimationDirection] = React.useState<"forward" | "backward">("forward");
81
+
82
+ // Create a ref to store submenu content by ID
83
+ const submenuContentRef = React.useRef<Map<string, React.ReactNode[]>>(new Map());
84
+
85
+ // Function to navigate to a submenu
86
+ const navigateToSubmenu = React.useCallback((id: string, title: string) => {
87
+ // Set animation direction to forward when navigating to a submenu
88
+ setAnimationDirection("forward");
89
+ setActiveSubmenu(id);
90
+ setSubmenuTitle(title);
91
+ setSubmenuStack((prev) => [...prev, {id, title}]);
92
+ }, []);
93
+
94
+ // Function to go back to previous menu
95
+ const goBack = React.useCallback(() => {
96
+ // Set animation direction to backward when going back
97
+ setAnimationDirection("backward");
98
+
99
+ if (submenuStack.length <= 1) {
100
+ // If we're at the first level, go back to main menu
101
+ setActiveSubmenu(null);
102
+ setSubmenuTitle(null);
103
+ setSubmenuStack([]);
104
+ } else {
105
+ // Go back to previous submenu
106
+ const newStack = [...submenuStack];
107
+ newStack.pop(); // Remove current
108
+ const previous = newStack[newStack.length - 1];
109
+ setActiveSubmenu(previous.id);
110
+ setSubmenuTitle(previous.title);
111
+ setSubmenuStack(newStack);
112
+ }
113
+ }, [submenuStack]);
114
+
115
+ // Function to register submenu content
116
+ const registerSubmenuContent = React.useCallback((id: string, content: React.ReactNode[]) => {
117
+ submenuContentRef.current.set(id, content);
118
+ }, []);
119
+
120
+ // Function to extract submenu content
121
+ const extractSubmenuContent = React.useCallback((elements: React.ReactNode, targetId: string): React.ReactNode[] => {
122
+ const result: React.ReactNode[] = [];
123
+
124
+ // Recursive function to search through all children
125
+ const findSubmenuContent = (node: React.ReactNode) => {
126
+ // Skip if not a valid element
127
+ if (!React.isValidElement(node)) return;
128
+
129
+ const element = node as React.ReactElement;
130
+ // Use a more specific type to avoid 'any'
131
+ const props = element.props as {
132
+ id?: string;
133
+ "data-submenu-id"?: string;
134
+ children?: React.ReactNode;
135
+ };
136
+
137
+ // Check if this is a DropDrawerSub
138
+ if (element.type === DropDrawerSub) {
139
+ // Get all possible ID values
140
+ const elementId = props.id;
141
+ const dataSubmenuId = props["data-submenu-id"];
142
+
143
+ // If this is the submenu we're looking for
144
+ if (elementId === targetId || dataSubmenuId === targetId) {
145
+ // Find the SubContent within this Sub
146
+ if (props.children) {
147
+ React.Children.forEach(props.children, (child) => {
148
+ if (React.isValidElement(child) && child.type === DropDrawerSubContent) {
149
+ // Add all children of the SubContent to the result
150
+ const subContentProps = child.props as {
151
+ children?: React.ReactNode;
152
+ };
153
+ if (subContentProps.children) {
154
+ React.Children.forEach(subContentProps.children, (contentChild) => {
155
+ result.push(contentChild);
156
+ });
157
+ }
158
+ }
159
+ });
160
+ }
161
+ return; // Found what we needed, no need to search deeper
162
+ }
163
+ }
164
+
165
+ // If this element has children, search through them
166
+ if (props.children) {
167
+ if (Array.isArray(props.children)) {
168
+ props.children.forEach((child: React.ReactNode) => findSubmenuContent(child));
169
+ } else {
170
+ findSubmenuContent(props.children);
171
+ }
172
+ }
173
+ };
174
+
175
+ // Start the search from the root elements
176
+ if (Array.isArray(elements)) {
177
+ elements.forEach((child) => findSubmenuContent(child));
178
+ } else {
179
+ findSubmenuContent(elements);
180
+ }
181
+
182
+ return result;
183
+ }, []);
184
+
185
+ // Get submenu content (either from cache or extract it)
186
+ const getSubmenuContent = React.useCallback(
187
+ (id: string) => {
188
+ // Check if we have the content in our ref
189
+ const cachedContent = submenuContentRef.current.get(id || "");
190
+ if (cachedContent && cachedContent.length > 0) {
191
+ return cachedContent;
192
+ }
193
+
194
+ // If not in cache, extract it
195
+ const submenuContent = extractSubmenuContent(children, id);
196
+
197
+ if (submenuContent.length === 0) {
198
+ return [];
199
+ }
200
+
201
+ // Store in cache for future use
202
+ if (id) {
203
+ submenuContentRef.current.set(id, submenuContent);
204
+ }
205
+
206
+ return submenuContent;
207
+ },
208
+ [children, extractSubmenuContent],
209
+ );
210
+
211
+ // Animation variants for Framer Motion
212
+ const variants = {
213
+ enter: (direction: "forward" | "backward") => ({
214
+ x: direction === "forward" ? "100%" : "-100%",
215
+ opacity: 0,
216
+ }),
217
+ center: {
218
+ x: 0,
219
+ opacity: 1,
220
+ },
221
+ exit: (direction: "forward" | "backward") => ({
222
+ x: direction === "forward" ? "-100%" : "100%",
223
+ opacity: 0,
224
+ }),
225
+ };
226
+
227
+ // Animation transition
228
+ const transition = {
229
+ duration: 0.3,
230
+ ease: [0.25, 0.1, 0.25, 1.0], // cubic-bezier easing
231
+ } satisfies Transition;
232
+
233
+ if (isMobile) {
234
+ return (
235
+ <SubmenuContext.Provider
236
+ value={{
237
+ activeSubmenu,
238
+ setActiveSubmenu: (id) => {
239
+ if (id === null) {
240
+ setActiveSubmenu(null);
241
+ setSubmenuTitle(null);
242
+ setSubmenuStack([]);
243
+ }
244
+ },
245
+ submenuTitle,
246
+ setSubmenuTitle,
247
+ navigateToSubmenu,
248
+ registerSubmenuContent,
249
+ }}>
250
+ <DrawerContent
251
+ data-slot='drop-drawer-content'
252
+ className={cn("max-h-[90vh]", className)}
253
+ {...props}>
254
+ {activeSubmenu ? (
255
+ <>
256
+ <DrawerHeader>
257
+ <div className='flex items-center gap-2'>
258
+ <button
259
+ onClick={goBack}
260
+ className='rounded-full p-1 hover:bg-neutral-100/50 dark:hover:bg-neutral-800/50'>
261
+ <ChevronLeftIcon className='h-5 w-5' />
262
+ </button>
263
+ <DrawerTitle>{submenuTitle || "Submenu"}</DrawerTitle>
264
+ </div>
265
+ </DrawerHeader>
266
+ <div className='relative max-h-[70vh] flex-1 overflow-y-auto'>
267
+ {/* Use AnimatePresence to handle exit animations */}
268
+ <AnimatePresence
269
+ initial={false}
270
+ mode='wait'
271
+ custom={animationDirection}>
272
+ <motion.div
273
+ key={activeSubmenu || "main"}
274
+ custom={animationDirection}
275
+ variants={variants}
276
+ initial='enter'
277
+ animate='center'
278
+ exit='exit'
279
+ transition={transition}
280
+ className='h-full w-full space-y-1.5 pb-6'>
281
+ {activeSubmenu ? getSubmenuContent(activeSubmenu) : children}
282
+ </motion.div>
283
+ </AnimatePresence>
284
+ </div>
285
+ </>
286
+ ) : (
287
+ <>
288
+ <DrawerHeader className='sr-only'>
289
+ <DrawerTitle>Menu</DrawerTitle>
290
+ </DrawerHeader>
291
+ <div className='max-h-[70vh] overflow-y-auto'>
292
+ <AnimatePresence
293
+ initial={false}
294
+ mode='wait'
295
+ custom={animationDirection}>
296
+ <motion.div
297
+ key='main-menu'
298
+ custom={animationDirection}
299
+ variants={variants}
300
+ initial='enter'
301
+ animate='center'
302
+ exit='exit'
303
+ transition={transition}
304
+ className='w-full space-y-1.5 pb-6'>
305
+ {children}
306
+ </motion.div>
307
+ </AnimatePresence>
308
+ </div>
309
+ </>
310
+ )}
311
+ </DrawerContent>
312
+ </SubmenuContext.Provider>
313
+ );
314
+ }
315
+
316
+ return (
317
+ <SubmenuContext.Provider
318
+ value={{
319
+ activeSubmenu,
320
+ setActiveSubmenu,
321
+ submenuTitle,
322
+ setSubmenuTitle,
323
+ registerSubmenuContent,
324
+ }}>
325
+ <DropdownMenuContent
326
+ data-slot='drop-drawer-content'
327
+ align='end'
328
+ sideOffset={4}
329
+ className={cn("max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[220px] overflow-y-auto", className)}
330
+ {...props}>
331
+ {children}
332
+ </DropdownMenuContent>
333
+ </SubmenuContext.Provider>
334
+ );
335
+ }
336
+
337
+ function DropDrawerItem({
338
+ className,
339
+ children,
340
+ onSelect,
341
+ onClick,
342
+ icon,
343
+ inset,
344
+ disabled,
345
+ ...props
346
+ }: React.ComponentProps<typeof DropdownMenuItem> & {
347
+ icon?: React.ReactNode;
348
+ }) {
349
+ const {isMobile} = useDropDrawerContext();
350
+
351
+ // Define hooks outside of conditionals to follow React rules
352
+ // Check if this item is inside a group by looking at parent elements
353
+ const isInGroup = React.useCallback((element: HTMLElement | null): boolean => {
354
+ if (!element) return false;
355
+
356
+ // Check if any parent has a data-drop-drawer-group attribute
357
+ let parent = element.parentElement;
358
+ while (parent) {
359
+ if (parent.hasAttribute("data-drop-drawer-group")) {
360
+ return true;
361
+ }
362
+ parent = parent.parentElement;
363
+ }
364
+ return false;
365
+ }, []);
366
+
367
+ // Create a ref to check if the item is in a group
368
+ const itemRef = React.useRef<HTMLDivElement>(null);
369
+ const [isInsideGroup, setIsInsideGroup] = React.useState(false);
370
+
371
+ React.useEffect(() => {
372
+ // Only run this effect in mobile mode
373
+ if (!isMobile) return;
374
+
375
+ // Use a short timeout to ensure the DOM is fully rendered
376
+ const timer = setTimeout(() => {
377
+ if (itemRef.current) {
378
+ setIsInsideGroup(isInGroup(itemRef.current));
379
+ }
380
+ }, 0);
381
+
382
+ return () => clearTimeout(timer);
383
+ }, [isInGroup, isMobile]);
384
+
385
+ if (isMobile) {
386
+ const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
387
+ if (disabled) return;
388
+ if (onClick) onClick(e);
389
+ if (onSelect) onSelect(e as unknown as Event);
390
+ };
391
+
392
+ // Only wrap in DrawerClose if it's not a submenu item
393
+ const content = (
394
+ <div
395
+ ref={itemRef}
396
+ data-slot='drop-drawer-item'
397
+ data-inset={inset}
398
+ data-disabled={disabled}
399
+ className={cn(
400
+ "flex cursor-pointer items-center justify-between px-4 py-4",
401
+ // Only apply margin, background and rounded corners if not in a group
402
+ !isInsideGroup && "mx-2 my-1.5 rounded-md bg-neutral-100 dark:bg-neutral-100 dark:bg-neutral-800 dark:dark:bg-neutral-800",
403
+ // For items in a group, don't add background but add more padding
404
+ isInsideGroup && "bg-transparent py-4",
405
+ inset && "pl-8",
406
+ disabled && "pointer-events-none opacity-50",
407
+ className,
408
+ )}
409
+ onClick={handleClick}
410
+ aria-disabled={disabled}
411
+ {...props}>
412
+ <div className='flex items-center gap-2'>{children}</div>
413
+ {icon && <div className='flex-shrink-0'>{icon}</div>}
414
+ </div>
415
+ );
416
+
417
+ // Check if this is inside a submenu
418
+ const isInSubmenu =
419
+ (props as Record<string, unknown>)["data-parent-submenu-id"] || (props as Record<string, unknown>)["data-parent-submenu"];
420
+
421
+ if (isInSubmenu) {
422
+ return content;
423
+ }
424
+
425
+ return <DrawerClose asChild>{content}</DrawerClose>;
426
+ }
427
+
428
+ return (
429
+ <DropdownMenuItem
430
+ data-slot='drop-drawer-item'
431
+ data-inset={inset}
432
+ className={className}
433
+ onSelect={onSelect}
434
+ onClick={onClick as React.MouseEventHandler<HTMLDivElement>}
435
+ inset={inset}
436
+ disabled={disabled}
437
+ {...props}>
438
+ <div className='flex w-full items-center justify-between'>
439
+ <div>{children}</div>
440
+ {icon && <div>{icon}</div>}
441
+ </div>
442
+ </DropdownMenuItem>
443
+ );
444
+ }
445
+
446
+ function DropDrawerSeparator({className, ...props}: React.ComponentProps<typeof DropdownMenuSeparator>) {
447
+ const {isMobile} = useDropDrawerContext();
448
+
449
+ // For mobile, render a simple divider
450
+ if (isMobile) {
451
+ return null;
452
+ }
453
+
454
+ // For desktop, use the standard dropdown separator
455
+ return (
456
+ <DropdownMenuSeparator
457
+ data-slot='drop-drawer-separator'
458
+ className={className}
459
+ {...props}
460
+ />
461
+ );
462
+ }
463
+
464
+ function DropDrawerLabel({
465
+ className,
466
+ children,
467
+ ...props
468
+ }: React.ComponentProps<typeof DropdownMenuLabel> | React.ComponentProps<typeof DrawerTitle>) {
469
+ const {isMobile} = useDropDrawerContext();
470
+
471
+ if (isMobile) {
472
+ return (
473
+ <DrawerHeader className='p-0'>
474
+ <DrawerTitle
475
+ data-slot='drop-drawer-label'
476
+ className={cn("px-4 py-2 text-sm font-medium text-neutral-500 dark:text-neutral-400", className)}
477
+ {...props}>
478
+ {children}
479
+ </DrawerTitle>
480
+ </DrawerHeader>
481
+ );
482
+ }
483
+
484
+ return (
485
+ <DropdownMenuLabel
486
+ data-slot='drop-drawer-label'
487
+ className={className}
488
+ {...props}>
489
+ {children}
490
+ </DropdownMenuLabel>
491
+ );
492
+ }
493
+
494
+ function DropDrawerFooter({className, children, ...props}: React.ComponentProps<typeof DrawerFooter> | React.ComponentProps<"div">) {
495
+ const {isMobile} = useDropDrawerContext();
496
+
497
+ if (isMobile) {
498
+ return (
499
+ <DrawerFooter
500
+ data-slot='drop-drawer-footer'
501
+ className={cn("p-4", className)}
502
+ {...props}>
503
+ {children}
504
+ </DrawerFooter>
505
+ );
506
+ }
507
+
508
+ // No direct equivalent in DropdownMenu, so we'll just render a div
509
+ return (
510
+ <div
511
+ data-slot='drop-drawer-footer'
512
+ className={cn("p-2", className)}
513
+ {...props}>
514
+ {children}
515
+ </div>
516
+ );
517
+ }
518
+
519
+ function DropDrawerGroup({
520
+ className,
521
+ children,
522
+ ...props
523
+ }: React.ComponentProps<"div"> & {
524
+ children: React.ReactNode;
525
+ }) {
526
+ const {isMobile} = useDropDrawerContext();
527
+
528
+ // Add separators between children on mobile
529
+ const childrenWithSeparators = React.useMemo(() => {
530
+ if (!isMobile) return children;
531
+
532
+ const childArray = React.Children.toArray(children);
533
+
534
+ // Filter out any existing separators
535
+ const filteredChildren = childArray.filter((child) => React.isValidElement(child) && child.type !== DropDrawerSeparator);
536
+
537
+ // Add separators between items
538
+ return filteredChildren.flatMap((child, index) => {
539
+ if (index === filteredChildren.length - 1) return [child];
540
+ return [
541
+ child,
542
+ <div
543
+ key={`separator-${index}`}
544
+ className='h-px bg-neutral-200 dark:bg-neutral-800'
545
+ aria-hidden='true'
546
+ />,
547
+ ];
548
+ });
549
+ }, [children, isMobile]);
550
+
551
+ if (isMobile) {
552
+ return (
553
+ <div
554
+ data-drop-drawer-group
555
+ data-slot='drop-drawer-group'
556
+ role='group'
557
+ className={cn(
558
+ "mx-2 my-3 overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-100 dark:bg-neutral-800 dark:dark:bg-neutral-800",
559
+ className,
560
+ )}
561
+ {...props}>
562
+ {childrenWithSeparators}
563
+ </div>
564
+ );
565
+ }
566
+
567
+ // On desktop, use a div with proper role and attributes
568
+ return (
569
+ <div
570
+ data-drop-drawer-group
571
+ data-slot='drop-drawer-group'
572
+ role='group'
573
+ className={className}
574
+ {...props}>
575
+ {children}
576
+ </div>
577
+ );
578
+ }
579
+
580
+ // Context for managing submenu state on mobile
581
+ interface SubmenuContextType {
582
+ activeSubmenu: string | null;
583
+ setActiveSubmenu: (id: string | null) => void;
584
+ submenuTitle: string | null;
585
+ setSubmenuTitle: (title: string | null) => void;
586
+ navigateToSubmenu?: (id: string, title: string) => void;
587
+ registerSubmenuContent?: (id: string, content: React.ReactNode[]) => void;
588
+ }
589
+
590
+ const SubmenuContext = React.createContext<SubmenuContextType>({
591
+ activeSubmenu: null,
592
+ setActiveSubmenu: () => {},
593
+ submenuTitle: null,
594
+ setSubmenuTitle: () => {},
595
+ navigateToSubmenu: undefined,
596
+ registerSubmenuContent: undefined,
597
+ });
598
+
599
+ // Submenu components
600
+ // Counter for generating simple numeric IDs
601
+ let submenuIdCounter = 0;
602
+
603
+ function DropDrawerSub({
604
+ children,
605
+ id,
606
+ ...props
607
+ }: React.ComponentProps<typeof DropdownMenuSub> & {
608
+ id?: string;
609
+ }) {
610
+ const {isMobile} = useDropDrawerContext();
611
+ const {registerSubmenuContent} = React.useContext(SubmenuContext);
612
+
613
+ // Generate a simple numeric ID instead of using React.useId()
614
+ const [generatedId] = React.useState(() => `submenu-${submenuIdCounter++}`);
615
+ const submenuId = id || generatedId;
616
+
617
+ // Extract submenu content to register with parent
618
+ React.useEffect(() => {
619
+ if (!registerSubmenuContent) return;
620
+
621
+ // Find the SubContent within this Sub
622
+ const contentItems: React.ReactNode[] = [];
623
+ React.Children.forEach(children, (child) => {
624
+ if (React.isValidElement(child) && child.type === DropDrawerSubContent) {
625
+ // Add all children of the SubContent to the result
626
+ React.Children.forEach((child.props as {children?: React.ReactNode}).children, (contentChild) => {
627
+ contentItems.push(contentChild);
628
+ });
629
+ }
630
+ });
631
+
632
+ // Register the content with the parent
633
+ if (contentItems.length > 0) {
634
+ registerSubmenuContent(submenuId, contentItems);
635
+ }
636
+ }, [children, registerSubmenuContent, submenuId]);
637
+
638
+ if (isMobile) {
639
+ // For mobile, we'll use the context to manage submenu state
640
+ // Process children to pass the submenu ID to the trigger and content
641
+ const processedChildren = React.Children.map(children, (child) => {
642
+ if (!React.isValidElement(child)) return child;
643
+
644
+ if (child.type === DropDrawerSubTrigger) {
645
+ return React.cloneElement(
646
+ child as React.ReactElement,
647
+ {
648
+ ...(child.props as object),
649
+ "data-parent-submenu-id": submenuId,
650
+ "data-submenu-id": submenuId,
651
+ // Use only data attributes, not custom props
652
+ "data-parent-submenu": submenuId,
653
+ } as React.HTMLAttributes<HTMLElement>,
654
+ );
655
+ }
656
+
657
+ if (child.type === DropDrawerSubContent) {
658
+ return React.cloneElement(
659
+ child as React.ReactElement,
660
+ {
661
+ ...(child.props as object),
662
+ "data-parent-submenu-id": submenuId,
663
+ "data-submenu-id": submenuId,
664
+ // Use only data attributes, not custom props
665
+ "data-parent-submenu": submenuId,
666
+ } as React.HTMLAttributes<HTMLElement>,
667
+ );
668
+ }
669
+
670
+ return child;
671
+ });
672
+
673
+ return (
674
+ <div
675
+ data-slot='drop-drawer-sub'
676
+ data-submenu-id={submenuId}
677
+ id={submenuId}>
678
+ {processedChildren}
679
+ </div>
680
+ );
681
+ }
682
+
683
+ // For desktop, pass the generated ID to the DropdownMenuSub
684
+ return (
685
+ <DropdownMenuSub
686
+ data-slot='drop-drawer-sub'
687
+ data-submenu-id={submenuId}
688
+ // Don't pass id to DropdownMenuSub as it doesn't accept this prop
689
+ {...props}>
690
+ {children}
691
+ </DropdownMenuSub>
692
+ );
693
+ }
694
+
695
+ function DropDrawerSubTrigger({
696
+ className,
697
+ inset,
698
+ children,
699
+ ...props
700
+ }: React.ComponentProps<typeof DropdownMenuSubTrigger> & {
701
+ icon?: React.ReactNode;
702
+ }) {
703
+ const {isMobile} = useDropDrawerContext();
704
+ const {navigateToSubmenu} = React.useContext(SubmenuContext);
705
+
706
+ // Define hooks outside of conditionals to follow React rules
707
+ // Check if this item is inside a group by looking at parent elements
708
+ const isInGroup = React.useCallback((element: HTMLElement | null): boolean => {
709
+ if (!element) return false;
710
+
711
+ // Check if any parent has a data-drop-drawer-group attribute
712
+ let parent = element.parentElement;
713
+ while (parent) {
714
+ if (parent.hasAttribute("data-drop-drawer-group")) {
715
+ return true;
716
+ }
717
+ parent = parent.parentElement;
718
+ }
719
+ return false;
720
+ }, []);
721
+
722
+ // Create a ref to check if the item is in a group
723
+ const itemRef = React.useRef<HTMLDivElement>(null);
724
+ const [isInsideGroup, setIsInsideGroup] = React.useState(false);
725
+
726
+ React.useEffect(() => {
727
+ // Only run this effect in mobile mode
728
+ if (!isMobile) return;
729
+
730
+ // Use a short timeout to ensure the DOM is fully rendered
731
+ const timer = setTimeout(() => {
732
+ if (itemRef.current) {
733
+ setIsInsideGroup(isInGroup(itemRef.current));
734
+ }
735
+ }, 0);
736
+
737
+ return () => clearTimeout(timer);
738
+ }, [isInGroup, isMobile]);
739
+
740
+ if (isMobile) {
741
+ // Find the parent submenu ID
742
+ const handleClick = (e: React.MouseEvent) => {
743
+ e.preventDefault();
744
+ e.stopPropagation();
745
+
746
+ // Get the closest parent with data-submenu-id attribute
747
+ const element = e.currentTarget as HTMLElement;
748
+ let submenuId: string | null = null;
749
+
750
+ // First check if the element itself has the data attribute
751
+ if (element.closest("[data-submenu-id]")) {
752
+ const closestElement = element.closest("[data-submenu-id]");
753
+ const id = closestElement?.getAttribute("data-submenu-id");
754
+ if (id) {
755
+ submenuId = id;
756
+ }
757
+ }
758
+
759
+ // If not found, try props
760
+ if (!submenuId) {
761
+ submenuId =
762
+ ((props as Record<string, unknown>)["data-parent-submenu-id"] as string)
763
+ || ((props as Record<string, unknown>)["data-parent-submenu"] as string);
764
+ }
765
+
766
+ if (!submenuId) {
767
+ return;
768
+ }
769
+
770
+ // Get the title
771
+ const title = typeof children === "string" ? children : "Submenu";
772
+
773
+ // Navigate to the submenu
774
+ if (navigateToSubmenu) {
775
+ navigateToSubmenu(submenuId, title);
776
+ }
777
+ };
778
+
779
+ // Combine onClick handlers
780
+ const combinedOnClick = (e: React.MouseEvent) => {
781
+ // Call the original onClick if provided
782
+ const typedProps = props as Record<string, unknown>;
783
+ if (typedProps["onClick"]) {
784
+ const originalOnClick = typedProps["onClick"] as React.MouseEventHandler<HTMLDivElement>;
785
+ originalOnClick(e as React.MouseEvent<HTMLDivElement>);
786
+ }
787
+
788
+ // Call our navigation handler
789
+ handleClick(e);
790
+ };
791
+
792
+ // Remove onClick from props to avoid duplicate handlers
793
+ const {...restProps} = props as Record<string, unknown>;
794
+
795
+ // Don't wrap in DrawerClose for submenu triggers
796
+ return (
797
+ <div
798
+ ref={itemRef}
799
+ data-slot='drop-drawer-sub-trigger'
800
+ data-inset={inset}
801
+ className={cn(
802
+ "flex cursor-pointer items-center justify-between px-4 py-4",
803
+ // Only apply margin, background and rounded corners if not in a group
804
+ !isInsideGroup && "mx-2 my-1.5 rounded-md bg-neutral-100 dark:bg-neutral-100 dark:bg-neutral-800 dark:dark:bg-neutral-800",
805
+ // For items in a group, don't add background but add more padding
806
+ isInsideGroup && "bg-transparent py-4",
807
+ inset && "pl-8",
808
+ className,
809
+ )}
810
+ onClick={combinedOnClick}
811
+ {...restProps}>
812
+ <div className='flex items-center gap-2'>{children}</div>
813
+ <ChevronRightIcon className='h-5 w-5' />
814
+ </div>
815
+ );
816
+ }
817
+
818
+ return (
819
+ <DropdownMenuSubTrigger
820
+ data-slot='drop-drawer-sub-trigger'
821
+ data-inset={inset}
822
+ className={className}
823
+ inset={inset}
824
+ {...props}>
825
+ {children}
826
+ </DropdownMenuSubTrigger>
827
+ );
828
+ }
829
+
830
+ function DropDrawerSubContent({className, sideOffset = 4, children, ...props}: React.ComponentProps<typeof DropdownMenuSubContent>) {
831
+ const {isMobile} = useDropDrawerContext();
832
+
833
+ if (isMobile) {
834
+ // For mobile, we don't render the content directly
835
+ // It will be rendered by the DropDrawerContent component when active
836
+ return null;
837
+ }
838
+
839
+ return (
840
+ <DropdownMenuSubContent
841
+ data-slot='drop-drawer-sub-content'
842
+ sideOffset={sideOffset}
843
+ className={cn(
844
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 p-1 shadow-lg dark:border-neutral-800",
845
+ className,
846
+ )}
847
+ {...props}>
848
+ {children}
849
+ </DropdownMenuSubContent>
850
+ );
851
+ }
852
+
853
+ export {
854
+ DropDrawer,
855
+ DropDrawerContent,
856
+ DropDrawerFooter,
857
+ DropDrawerGroup,
858
+ DropDrawerItem,
859
+ DropDrawerLabel,
860
+ DropDrawerSeparator,
861
+ DropDrawerSub,
862
+ DropDrawerSubContent,
863
+ DropDrawerSubTrigger,
864
+ DropDrawerTrigger,
865
+ };