@justanarthur/modo-ui 0.5.0 → 0.6.0-canary.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 (461) hide show
  1. package/DESIGN.md +608 -0
  2. package/README.md +61 -39
  3. package/blocks/sidebar-app/examples.mdx +33 -0
  4. package/blocks/sidebar-app/index.tsx +218 -0
  5. package/blocks/sidebar-app/inset-topbar.tsx +25 -0
  6. package/blocks/sidebar-app/menu-grid.ts +26 -0
  7. package/blocks/sidebar-app/nav-data.ts +36 -0
  8. package/blocks/sidebar-app/search-field.tsx +48 -0
  9. package/blocks/sidebar-app/user-footer.tsx +67 -0
  10. package/blocks/sidebar-app/workspace-header.tsx +129 -0
  11. package/components/AI/input-message/examples.mdx +124 -0
  12. package/components/AI/input-message/file-thumbnail.tsx +173 -0
  13. package/components/AI/input-message/index.tsx +1383 -0
  14. package/components/AI/thinking-indicator/examples.mdx +27 -0
  15. package/components/AI/thinking-indicator/index.tsx +210 -0
  16. package/components/accordion/examples.mdx +148 -0
  17. package/components/accordion/index.tsx +1034 -0
  18. package/components/avatar/examples.mdx +95 -0
  19. package/components/avatar/index.tsx +129 -0
  20. package/components/badge/change-demo.tsx +29 -0
  21. package/components/badge/examples.mdx +120 -0
  22. package/components/badge/index.tsx +225 -0
  23. package/components/badge/variants.ts +73 -0
  24. package/components/button/examples.mdx +164 -0
  25. package/components/button/index.tsx +416 -0
  26. package/components/button/loading-demo.tsx +25 -0
  27. package/components/button/variants.ts +166 -0
  28. package/components/card/examples.mdx +212 -0
  29. package/components/card/index.tsx +1037 -0
  30. package/components/copy/examples.mdx +87 -0
  31. package/components/copy/index.tsx +482 -0
  32. package/components/inputs/checkbox/examples.mdx +83 -0
  33. package/components/inputs/checkbox/index.tsx +611 -0
  34. package/components/inputs/checkbox/select-all-demo.tsx +48 -0
  35. package/components/inputs/color-picker/examples.mdx +72 -0
  36. package/components/inputs/color-picker/index.tsx +1955 -0
  37. package/components/inputs/color-picker/variants.ts +401 -0
  38. package/components/inputs/combobox/examples.mdx +273 -0
  39. package/components/inputs/combobox/index.tsx +1436 -0
  40. package/components/inputs/combobox/remote-demo.tsx +101 -0
  41. package/components/inputs/date-time/calendar/examples.mdx +34 -0
  42. package/components/inputs/date-time/calendar/index.tsx +351 -0
  43. package/components/inputs/date-time/calendar/use-month-window.ts +163 -0
  44. package/components/inputs/date-time/date-picker/examples.mdx +166 -0
  45. package/components/inputs/date-time/date-picker/index.tsx +385 -0
  46. package/components/inputs/date-time/date-picker/masked-date-input.tsx +336 -0
  47. package/components/inputs/date-time/date-range-picker/examples.mdx +29 -0
  48. package/components/inputs/date-time/date-range-picker/index.tsx +217 -0
  49. package/components/inputs/date-time/duration-picker/examples.mdx +16 -0
  50. package/components/inputs/date-time/duration-picker/index.tsx +189 -0
  51. package/components/inputs/date-time/month-picker/examples.mdx +16 -0
  52. package/components/inputs/date-time/month-picker/index.tsx +190 -0
  53. package/components/inputs/date-time/picker-shell.tsx +395 -0
  54. package/components/inputs/date-time/time-columns.tsx +91 -0
  55. package/components/inputs/date-time/time-picker/examples.mdx +26 -0
  56. package/components/inputs/date-time/time-picker/index.tsx +166 -0
  57. package/components/inputs/field/examples.mdx +113 -0
  58. package/components/inputs/field/index.tsx +267 -0
  59. package/components/inputs/file-dropzone/examples.mdx +73 -0
  60. package/components/inputs/file-dropzone/file-row.tsx +201 -0
  61. package/components/inputs/file-dropzone/index.tsx +296 -0
  62. package/components/inputs/file-dropzone/upload-demo.tsx +67 -0
  63. package/components/inputs/file-dropzone/variants.ts +94 -0
  64. package/components/inputs/input/examples.mdx +135 -0
  65. package/components/inputs/input/index.tsx +494 -0
  66. package/components/inputs/input/password-demo.tsx +30 -0
  67. package/components/inputs/label/examples.mdx +67 -0
  68. package/components/inputs/label/index.tsx +85 -0
  69. package/components/inputs/number-input/examples.mdx +56 -0
  70. package/components/inputs/number-input/index.tsx +214 -0
  71. package/components/inputs/radio/examples.mdx +77 -0
  72. package/components/inputs/radio/index.tsx +636 -0
  73. package/components/inputs/search/examples.mdx +74 -0
  74. package/components/inputs/search/index.tsx +333 -0
  75. package/components/inputs/select/examples.mdx +218 -0
  76. package/components/inputs/select/index.tsx +862 -0
  77. package/components/inputs/select/variants.ts +32 -0
  78. package/components/inputs/slider/examples.mdx +127 -0
  79. package/components/inputs/slider/index.tsx +1725 -0
  80. package/components/inputs/switch/examples.mdx +94 -0
  81. package/components/inputs/switch/index.tsx +504 -0
  82. package/components/inputs/textarea/examples.mdx +88 -0
  83. package/components/inputs/textarea/index.tsx +161 -0
  84. package/components/navigation/navigation-menu/examples.mdx +109 -0
  85. package/components/navigation/navigation-menu/index.tsx +613 -0
  86. package/components/navigation/pagination/examples.mdx +64 -0
  87. package/components/navigation/pagination/index.tsx +395 -0
  88. package/components/navigation/sidebar/examples.mdx +319 -0
  89. package/components/navigation/sidebar/index.tsx +453 -0
  90. package/components/navigation/sidebar/sidebar-core.tsx +1408 -0
  91. package/components/navigation/sidebar/sidebar-menu.tsx +1495 -0
  92. package/components/navigation/sidebar/sidebar.css +74 -0
  93. package/components/navigation/sidebar/variants.ts +55 -0
  94. package/components/navigation/tabs/examples.mdx +89 -0
  95. package/components/navigation/tabs/index.tsx +494 -0
  96. package/components/navigation/tabs-subtle/examples.mdx +49 -0
  97. package/components/navigation/tabs-subtle/index.tsx +512 -0
  98. package/components/overlays/alert-dialog/examples.mdx +123 -0
  99. package/components/overlays/alert-dialog/index.tsx +258 -0
  100. package/components/overlays/alert-dialog/pending-demo.tsx +36 -0
  101. package/components/overlays/command/examples.mdx +113 -0
  102. package/components/overlays/command/index.tsx +416 -0
  103. package/components/overlays/context-menu/examples.mdx +56 -0
  104. package/components/overlays/context-menu/index.tsx +279 -0
  105. package/components/overlays/dialog/examples.mdx +144 -0
  106. package/components/overlays/dialog/index.tsx +366 -0
  107. package/components/overlays/dropdown/dropdown-search.tsx +395 -0
  108. package/components/overlays/dropdown/examples.mdx +294 -0
  109. package/components/overlays/dropdown/index.tsx +1168 -0
  110. package/components/overlays/dropdown/menu-item.tsx +464 -0
  111. package/components/overlays/dropdown/menu-parts.tsx +660 -0
  112. package/components/overlays/popover/anchored-demo.tsx +59 -0
  113. package/components/overlays/popover/examples.mdx +180 -0
  114. package/components/overlays/popover/index.tsx +315 -0
  115. package/components/overlays/preview-card/examples.mdx +61 -0
  116. package/components/overlays/preview-card/index.tsx +204 -0
  117. package/components/overlays/sheet/examples.mdx +89 -0
  118. package/components/overlays/sheet/index.tsx +247 -0
  119. package/components/overlays/toast/examples.mdx +68 -0
  120. package/components/overlays/toast/global-demo.tsx +72 -0
  121. package/components/overlays/toast/index.tsx +474 -0
  122. package/components/overlays/toast/manager.ts +193 -0
  123. package/components/overlays/tooltip/examples.mdx +106 -0
  124. package/components/overlays/tooltip/index.tsx +519 -0
  125. package/components/progress/examples.mdx +59 -0
  126. package/components/progress/index.tsx +174 -0
  127. package/components/progress/progress.css +49 -0
  128. package/components/progress/step-demo.tsx +27 -0
  129. package/components/table/examples.mdx +184 -0
  130. package/components/table/index.tsx +510 -0
  131. package/components/table/sort-demo.tsx +61 -0
  132. package/dist/base.css +100 -0
  133. package/dist/blocks/sidebar-app/index.js +39 -26
  134. package/dist/components/AI/input-message/index.js +22 -16
  135. package/dist/components/AI/thinking-indicator/index.js +9 -6
  136. package/dist/components/accordion/index.js +16 -13
  137. package/dist/components/avatar/index.d.ts +46 -0
  138. package/dist/components/avatar/index.js +18 -0
  139. package/dist/components/badge/index.d.ts +18 -11
  140. package/dist/components/badge/index.js +11 -7
  141. package/dist/components/badge/variants.d.ts +33 -0
  142. package/dist/components/badge/variants.js +10 -0
  143. package/dist/components/button/index.d.ts +53 -17
  144. package/dist/components/button/index.js +10 -6
  145. package/dist/components/button/variants.d.ts +39 -0
  146. package/dist/components/button/variants.js +8 -0
  147. package/dist/components/card/index.d.ts +16 -3
  148. package/dist/components/card/index.js +16 -11
  149. package/dist/components/copy/index.js +13 -9
  150. package/dist/components/inputs/checkbox/index.d.ts +28 -9
  151. package/dist/components/inputs/checkbox/index.js +17 -13
  152. package/dist/components/inputs/color-picker/index.d.ts +13 -13
  153. package/dist/components/inputs/color-picker/index.js +25 -18
  154. package/dist/components/inputs/color-picker/variants.d.ts +61 -0
  155. package/dist/components/inputs/color-picker/variants.js +28 -0
  156. package/dist/components/inputs/combobox/index.d.ts +60 -17
  157. package/dist/components/inputs/combobox/index.js +26 -18
  158. package/dist/components/inputs/date-time/calendar/index.js +14 -11
  159. package/dist/components/inputs/date-time/date-picker/index.d.ts +53 -11
  160. package/dist/components/inputs/date-time/date-picker/index.js +33 -25
  161. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +3 -3
  162. package/dist/components/inputs/date-time/date-range-picker/index.js +32 -24
  163. package/dist/components/inputs/date-time/duration-picker/index.d.ts +3 -3
  164. package/dist/components/inputs/date-time/duration-picker/index.js +32 -24
  165. package/dist/components/inputs/date-time/month-picker/index.d.ts +3 -3
  166. package/dist/components/inputs/date-time/month-picker/index.js +31 -23
  167. package/dist/components/inputs/date-time/time-picker/index.d.ts +3 -3
  168. package/dist/components/inputs/date-time/time-picker/index.js +32 -24
  169. package/dist/components/inputs/field/index.d.ts +116 -0
  170. package/dist/components/inputs/field/index.js +28 -0
  171. package/dist/components/inputs/file-dropzone/index.d.ts +165 -0
  172. package/dist/components/inputs/file-dropzone/index.js +32 -0
  173. package/dist/components/inputs/file-dropzone/variants.d.ts +35 -0
  174. package/dist/components/inputs/file-dropzone/variants.js +12 -0
  175. package/dist/components/inputs/input/index.d.ts +95 -13
  176. package/dist/components/inputs/input/index.js +17 -9
  177. package/dist/components/inputs/label/index.d.ts +32 -0
  178. package/dist/components/inputs/label/index.js +11 -0
  179. package/dist/components/inputs/number-input/index.d.ts +84 -0
  180. package/dist/components/inputs/number-input/index.js +20 -0
  181. package/dist/components/inputs/radio/index.d.ts +20 -5
  182. package/dist/components/inputs/radio/index.js +15 -12
  183. package/dist/components/inputs/search/index.js +25 -18
  184. package/dist/components/inputs/select/index.d.ts +41 -11
  185. package/dist/components/inputs/select/index.js +27 -14
  186. package/dist/components/inputs/select/variants.d.ts +5 -0
  187. package/dist/components/inputs/select/variants.js +6 -0
  188. package/dist/components/inputs/slider/index.js +7 -5
  189. package/dist/components/inputs/switch/index.d.ts +8 -2
  190. package/dist/components/inputs/switch/index.js +14 -12
  191. package/dist/components/inputs/textarea/index.d.ts +4 -0
  192. package/dist/components/inputs/textarea/index.js +8 -6
  193. package/dist/components/navigation/navigation-menu/index.d.ts +114 -0
  194. package/dist/components/navigation/navigation-menu/index.js +40 -0
  195. package/dist/components/navigation/pagination/index.d.ts +73 -0
  196. package/dist/components/navigation/pagination/index.js +39 -0
  197. package/dist/components/navigation/sidebar/index.d.ts +44 -21
  198. package/dist/components/navigation/sidebar/index.js +41 -31
  199. package/dist/components/navigation/sidebar/variants.d.ts +25 -0
  200. package/dist/components/navigation/sidebar/variants.js +28 -0
  201. package/dist/components/navigation/tabs/index.d.ts +8 -2
  202. package/dist/components/navigation/tabs/index.js +14 -10
  203. package/dist/components/navigation/tabs-subtle/index.js +12 -10
  204. package/dist/components/overlays/alert-dialog/index.d.ts +121 -15
  205. package/dist/components/overlays/alert-dialog/index.js +22 -13
  206. package/dist/components/overlays/command/index.js +24 -17
  207. package/dist/components/overlays/context-menu/index.d.ts +33 -17
  208. package/dist/components/overlays/context-menu/index.js +27 -18
  209. package/dist/components/overlays/dialog/index.js +17 -12
  210. package/dist/components/overlays/dropdown/index.d.ts +257 -31
  211. package/dist/components/overlays/dropdown/index.js +42 -17
  212. package/dist/components/overlays/popover/index.d.ts +31 -9
  213. package/dist/components/overlays/popover/index.js +12 -9
  214. package/dist/components/overlays/preview-card/index.js +11 -8
  215. package/dist/components/overlays/sheet/index.js +18 -13
  216. package/dist/components/overlays/toast/index.d.ts +123 -47
  217. package/dist/components/overlays/toast/index.js +18 -10
  218. package/dist/components/overlays/tooltip/index.d.ts +113 -21
  219. package/dist/components/overlays/tooltip/index.js +15 -6
  220. package/dist/components/progress/index.d.ts +56 -0
  221. package/dist/components/progress/index.js +15 -0
  222. package/dist/components/table/index.d.ts +63 -8
  223. package/dist/components/table/index.js +19 -9
  224. package/dist/index.d.ts +2201 -736
  225. package/dist/index.js +223 -82
  226. package/dist/lib/date-locale.d.ts +14 -0
  227. package/dist/lib/date-locale.js +16 -0
  228. package/dist/lib/field-classes.d.ts +144 -0
  229. package/dist/lib/field-classes.js +40 -0
  230. package/dist/lib/field-variants.d.ts +47 -0
  231. package/dist/lib/field-variants.js +26 -0
  232. package/dist/lib/fluid-hover-highlight.d.ts +112 -0
  233. package/dist/lib/fluid-hover-highlight.js +17 -0
  234. package/dist/lib/focus-ring.d.ts +18 -0
  235. package/dist/lib/focus-ring.js +10 -0
  236. package/dist/lib/goo-indicator.d.ts +46 -0
  237. package/dist/lib/goo-indicator.js +14 -0
  238. package/dist/lib/icon-context.d.ts +30 -0
  239. package/dist/lib/icon-context.js +12 -0
  240. package/dist/lib/inert.d.ts +9 -0
  241. package/dist/lib/inert.js +8 -0
  242. package/dist/lib/morph-layers.d.ts +93 -0
  243. package/dist/lib/morph-layers.js +13 -0
  244. package/dist/lib/morph-part.d.ts +21 -0
  245. package/dist/lib/morph-part.js +12 -0
  246. package/dist/lib/popup.d.ts +20 -0
  247. package/dist/lib/popup.js +12 -0
  248. package/dist/lib/reduced-motion.d.ts +9 -0
  249. package/dist/lib/reduced-motion.js +6 -0
  250. package/dist/lib/row-index.d.ts +10 -0
  251. package/dist/lib/row-index.js +8 -0
  252. package/dist/lib/row-order.d.ts +28 -0
  253. package/dist/lib/row-order.js +10 -0
  254. package/dist/lib/scroll-column.d.ts +27 -0
  255. package/dist/lib/scroll-column.js +18 -0
  256. package/dist/lib/shape-context.d.ts +30 -0
  257. package/dist/lib/shape-context.js +12 -0
  258. package/dist/lib/size-context.d.ts +94 -0
  259. package/dist/lib/size-context.js +20 -0
  260. package/dist/lib/size-scale.d.ts +79 -0
  261. package/dist/lib/size-scale.js +8 -0
  262. package/dist/lib/slot.d.ts +12 -0
  263. package/dist/lib/slot.js +6 -0
  264. package/dist/lib/springs.d.ts +44 -0
  265. package/dist/lib/springs.js +8 -0
  266. package/dist/lib/surface-classes.d.ts +19 -0
  267. package/dist/lib/surface-classes.js +10 -0
  268. package/dist/lib/surface-context.d.ts +9 -0
  269. package/dist/lib/surface-context.js +8 -0
  270. package/dist/lib/use-controllable-state.d.ts +2 -0
  271. package/dist/lib/use-controllable-state.js +6 -0
  272. package/dist/lib/use-fluid-hover.d.ts +138 -0
  273. package/dist/lib/use-fluid-hover.js +14 -0
  274. package/dist/lib/use-is-mobile.d.ts +2 -0
  275. package/dist/lib/use-is-mobile.js +6 -0
  276. package/dist/lib/use-keyboard-nav-gate.d.ts +20 -0
  277. package/dist/lib/use-keyboard-nav-gate.js +7 -0
  278. package/dist/lib/use-merge-split.d.ts +47 -0
  279. package/dist/lib/use-merge-split.js +15 -0
  280. package/dist/lib/use-morph.d.ts +101 -0
  281. package/dist/lib/use-morph.js +20 -0
  282. package/dist/lib/use-touch-primary.d.ts +2 -0
  283. package/dist/lib/use-touch-primary.js +6 -0
  284. package/dist/lib/utils.d.ts +3 -0
  285. package/dist/lib/utils.js +6 -0
  286. package/dist/preset.d.ts +17 -0
  287. package/dist/preset.js +22 -2
  288. package/dist/primitives/code/index.js +7 -5
  289. package/dist/primitives/craft/index.js +9 -6
  290. package/dist/primitives/fluid-hover/index.js +9 -8
  291. package/dist/primitives/morph/index.js +15 -10
  292. package/dist/primitives/motion/index.js +10 -8
  293. package/dist/primitives/scroll-area/index.js +4 -3
  294. package/dist/primitives/separator/index.d.ts +25 -0
  295. package/dist/primitives/separator/index.js +9 -0
  296. package/dist/primitives/sizes/index.js +6 -4
  297. package/dist/primitives/skeleton/index.d.ts +35 -0
  298. package/dist/primitives/skeleton/index.js +12 -0
  299. package/dist/primitives/surface/index.js +6 -4
  300. package/dist/shared/{components-3mkepka5.js → components-0jmqhx2k.js} +28 -25
  301. package/dist/shared/components-124epb7f.js +53 -0
  302. package/dist/shared/{components-gz8zf3ec.js → components-1k0wv53d.js} +15 -43
  303. package/dist/shared/components-26snnbs9.js +322 -0
  304. package/dist/shared/{components-2dxkv6zm.js → components-2c0qcfx4.js} +8 -7
  305. package/dist/shared/{components-473g814w.js → components-2mt9te6d.js} +243 -137
  306. package/dist/shared/{components-hawvy2qz.js → components-2z48g4fp.js} +9 -30
  307. package/dist/shared/components-34txk9rj.js +35 -0
  308. package/dist/shared/components-3h92d94m.js +23 -0
  309. package/dist/shared/{components-zfnmn5d4.js → components-3sp623ae.js} +36 -31
  310. package/dist/shared/{components-jwst3vtj.js → components-3wxkjgqn.js} +186 -76
  311. package/dist/shared/{components-s2bz1xpk.js → components-49bsynbw.js} +8 -5
  312. package/dist/shared/components-4bwsd46w.js +71 -0
  313. package/dist/shared/components-4n4f5m3e.js +297 -0
  314. package/dist/shared/{components-0q0sqfh4.js → components-4y816hwp.js} +27 -49
  315. package/dist/shared/components-52ngsgca.js +399 -0
  316. package/dist/shared/{components-y9y0g8er.js → components-52r5763g.js} +22 -19
  317. package/dist/shared/components-559tf7yb.js +28 -0
  318. package/dist/shared/components-5mkga9hj.js +440 -0
  319. package/dist/shared/{components-kwa6mrf9.js → components-5s0nfqvw.js} +14 -9
  320. package/dist/shared/{components-dk0gjr5a.js → components-60atskhp.js} +19 -18
  321. package/dist/shared/{components-rr7a6ja9.js → components-658b26nb.js} +21 -18
  322. package/dist/shared/{components-xxvdw7k1.js → components-65vwfjs6.js} +37 -34
  323. package/dist/shared/{components-frw34b65.js → components-6j2fh74k.js} +114 -123
  324. package/dist/shared/components-6zg1yft7.js +167 -0
  325. package/dist/shared/components-6zng858q.js +53 -0
  326. package/dist/shared/components-72w4ge68.js +64 -0
  327. package/dist/shared/{components-cv16mryv.js → components-78x40gee.js} +13 -7
  328. package/dist/shared/{components-b2wyq2cq.js → components-7d4r7hh1.js} +39 -57
  329. package/dist/shared/components-7ntvvegr.js +316 -0
  330. package/dist/shared/components-8nwsqjn8.js +34 -0
  331. package/dist/shared/{components-5xc2j8jz.js → components-8tt3xbb5.js} +19 -18
  332. package/dist/shared/{components-vc7vq5nz.js → components-9as3rf68.js} +6 -3
  333. package/dist/shared/components-9mys9jna.js +255 -0
  334. package/dist/shared/components-adkarj8n.js +35 -0
  335. package/dist/shared/{components-b3c5et5b.js → components-ae9fncz4.js} +32 -31
  336. package/dist/shared/{components-hqeq3w9q.js → components-awra5sff.js} +24 -16
  337. package/dist/shared/components-b1e239g3.js +28 -0
  338. package/dist/shared/{components-q5xj5c16.js → components-b1f2bjf1.js} +9 -8
  339. package/dist/shared/{components-qmmjx1qw.js → components-bn42vq6d.js} +71 -72
  340. package/dist/shared/components-c96g82th.js +1444 -0
  341. package/dist/shared/components-c9yr4ed1.js +44 -0
  342. package/dist/shared/components-d3shbwqq.js +95 -0
  343. package/dist/shared/{components-jjw69c4c.js → components-dcc02hs2.js} +7 -4
  344. package/dist/shared/{components-men63rwj.js → components-de99ykt7.js} +3 -2
  345. package/dist/shared/components-dhgk6mbt.js +16 -0
  346. package/dist/shared/components-drxrtnp2.js +25 -0
  347. package/dist/shared/{components-6p8tfcd6.js → components-ds9fqat9.js} +21 -18
  348. package/dist/shared/{components-mjthm1kf.js → components-egy4tyjt.js} +11 -8
  349. package/dist/shared/components-f0xfpkhh.js +82 -0
  350. package/dist/shared/components-f7h57m6t.js +59 -0
  351. package/dist/shared/{components-d6xtjht3.js → components-fjkpbg6n.js} +33 -30
  352. package/dist/shared/{components-gc5vtq2f.js → components-gh0qeey3.js} +63 -29
  353. package/dist/shared/{components-39jq9va1.js → components-gvbapfen.js} +29 -24
  354. package/dist/shared/{components-5vgpnxgy.js → components-gzesg7hy.js} +19 -16
  355. package/dist/shared/components-h6p86be3.js +19 -0
  356. package/dist/shared/components-h83az0bz.js +33 -0
  357. package/dist/shared/components-jjrj6ymn.js +104 -0
  358. package/dist/shared/{components-wvp1m52g.js → components-jp5v0m0f.js} +15 -14
  359. package/dist/shared/{components-zrkzcayz.js → components-jqpefjsn.js} +54 -6
  360. package/dist/shared/{components-1z6tdadk.js → components-k74j4hwh.js} +20 -17
  361. package/dist/shared/components-kswxnm3h.js +24 -0
  362. package/dist/shared/{components-jbbmdr74.js → components-mkkbbgb7.js} +36 -17
  363. package/dist/shared/{components-pbmqctfa.js → components-mpw667rb.js} +169 -65
  364. package/dist/shared/{components-s5ngg32c.js → components-paa08427.js} +121 -47
  365. package/dist/shared/{components-vfbhxf5r.js → components-pd42emw3.js} +6 -5
  366. package/dist/shared/{components-hw9yk32z.js → components-pmfszv7a.js} +35 -23
  367. package/dist/shared/{components-chacnk61.js → components-pw06rbxq.js} +19 -19
  368. package/dist/shared/components-q03cktx8.js +246 -0
  369. package/dist/shared/{components-kvr7f9hy.js → components-qdm3yh26.js} +19 -10
  370. package/dist/shared/{components-27nnvrwe.js → components-qs4rkqsj.js} +15 -14
  371. package/dist/shared/{components-s083f38z.js → components-r6gbmkew.js} +22 -21
  372. package/dist/shared/{components-ta7shw28.js → components-r8eqs7gz.js} +4 -4
  373. package/dist/shared/{components-sn1rxx1d.js → components-rm02h9nt.js} +12 -11
  374. package/dist/shared/{components-c6s6b43g.js → components-rrbpmfx4.js} +3 -2
  375. package/dist/shared/{components-n7mjr357.js → components-tz1fv35x.js} +3 -2
  376. package/dist/shared/components-tz4gaf1v.js +23 -0
  377. package/dist/shared/{components-zx6b6yby.js → components-vf45j12c.js} +4 -18
  378. package/dist/shared/{components-3ky44908.js → components-vfjc9jzz.js} +2 -3
  379. package/dist/shared/{components-39vmfq9s.js → components-vsa625vk.js} +21 -20
  380. package/dist/shared/components-wawnmkph.js +500 -0
  381. package/dist/shared/components-wktrrb4m.js +38 -0
  382. package/dist/shared/{components-8snj490h.js → components-wwd363me.js} +78 -32
  383. package/dist/shared/{components-5pnw8183.js → components-xcwvgcrd.js} +23 -22
  384. package/dist/shared/components-xdqmt6ed.js +20 -0
  385. package/dist/shared/{components-h2ajmhwn.js → components-y1w3a6wx.js} +107 -35
  386. package/dist/shared/{components-ar66gnt0.js → components-y6578b8h.js} +37 -34
  387. package/dist/shared/{components-59efxjd9.js → components-yhgq0qxx.js} +13 -12
  388. package/dist/shared/{components-jthe1pwb.js → components-yz84gpkq.js} +101 -404
  389. package/dist/shared/components-z3v0n0de.js +11 -0
  390. package/dist/shared/{components-7r3etddy.js → components-z9n19fyk.js} +19 -54
  391. package/dist/shared/components-zbvjzh1p.js +141 -0
  392. package/dist/styles.css +241 -16
  393. package/dist/tailwind.css +13 -0
  394. package/dist/tokens.css +34 -0
  395. package/index.ts +60 -0
  396. package/lib/date-locale.ts +50 -0
  397. package/lib/field-classes.ts +115 -0
  398. package/lib/field-variants.ts +117 -0
  399. package/lib/fluid-hover-highlight.tsx +139 -0
  400. package/lib/focus-ring.tsx +46 -0
  401. package/lib/goo-indicator.tsx +261 -0
  402. package/lib/icon-context.tsx +310 -0
  403. package/lib/inert.ts +22 -0
  404. package/lib/morph-layers.tsx +102 -0
  405. package/lib/morph-part.tsx +49 -0
  406. package/lib/popup.ts +52 -0
  407. package/lib/reduced-motion.ts +19 -0
  408. package/lib/row-index.tsx +29 -0
  409. package/lib/row-order.tsx +115 -0
  410. package/lib/scroll-column.tsx +244 -0
  411. package/lib/shape-context.tsx +124 -0
  412. package/lib/size-context.tsx +66 -0
  413. package/lib/size-scale.ts +115 -0
  414. package/lib/slot.ts +25 -0
  415. package/lib/springs.ts +62 -0
  416. package/lib/surface-classes.ts +58 -0
  417. package/lib/surface-context.tsx +21 -0
  418. package/lib/use-controllable-state.ts +44 -0
  419. package/lib/use-fluid-hover.ts +541 -0
  420. package/lib/use-is-mobile.ts +25 -0
  421. package/lib/use-keyboard-nav-gate.ts +45 -0
  422. package/lib/use-merge-split.tsx +209 -0
  423. package/lib/use-morph.ts +557 -0
  424. package/lib/use-touch-primary.tsx +43 -0
  425. package/lib/utils.ts +37 -0
  426. package/package.json +177 -4
  427. package/preset.ts +196 -0
  428. package/primitives/code/examples.mdx +27 -0
  429. package/primitives/code/index.tsx +80 -0
  430. package/primitives/craft/demos.tsx +173 -0
  431. package/primitives/craft/examples.mdx +138 -0
  432. package/primitives/craft/index.tsx +76 -0
  433. package/primitives/fluid-hover/examples.mdx +84 -0
  434. package/primitives/fluid-hover/index.tsx +316 -0
  435. package/primitives/morph/examples.mdx +135 -0
  436. package/primitives/morph/index.tsx +192 -0
  437. package/primitives/motion/examples.mdx +100 -0
  438. package/primitives/motion/index.tsx +160 -0
  439. package/primitives/scroll-area/examples.mdx +58 -0
  440. package/primitives/scroll-area/index.tsx +200 -0
  441. package/primitives/separator/examples.mdx +30 -0
  442. package/primitives/separator/index.tsx +53 -0
  443. package/primitives/sizes/examples.mdx +167 -0
  444. package/primitives/sizes/index.tsx +89 -0
  445. package/primitives/skeleton/examples.mdx +51 -0
  446. package/primitives/skeleton/index.tsx +72 -0
  447. package/primitives/skeleton/skeleton.css +15 -0
  448. package/primitives/surface/examples.mdx +173 -0
  449. package/primitives/surface/index.tsx +109 -0
  450. package/primitives/surface/surface.css +176 -0
  451. package/dist/shared/components-0sn7ypjw.js +0 -61
  452. package/dist/shared/components-1434p1mh.js +0 -123
  453. package/dist/shared/components-a86dytt0.js +0 -986
  454. package/dist/shared/components-gf5s6ap9.js +0 -42
  455. package/dist/shared/components-hyq23phv.js +0 -310
  456. package/dist/shared/components-ng72k97e.js +0 -128
  457. package/dist/shared/components-raknhppb.js +0 -64
  458. package/dist/shared/components-vynjtrk6.js +0 -19
  459. package/dist/shared/components-wy9jpqny.js +0 -146
  460. /package/dist/shared/{components-1v7rxytx.js → components-3ey0hzpb.js} +0 -0
  461. /package/dist/shared/{components-ezxhrmer.js → components-jcnjay5g.js} +0 -0
@@ -0,0 +1,660 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): the children-based menu
3
+ * parts shadcn's DropdownMenu has and the FF port lacked. Groups, value-keyed
4
+ * checkbox and radio rows, and submenus, all on Base UI's Menu
5
+ * (`Menu.Group`, `Menu.CheckboxItem`, `Menu.RadioGroup` / `Menu.RadioItem`,
6
+ * `Menu.SubmenuRoot` / `Menu.SubmenuTrigger`). The rows are `MenuItem`; a
7
+ * submenu is a panel of its own (fluid hover, keyboard ring, selection
8
+ * backgrounds) that grows out of its trigger row through the morph engine.
9
+ * `useMenuPanel` is the state both popups share; both draw the shared
10
+ * keyboard ring (`lib/focus-ring.tsx`, pinned to the rounded shape).
11
+ */
12
+
13
+ import { Menu } from '@base-ui/react/menu'
14
+ import {
15
+ type ComponentProps,
16
+ createContext,
17
+ type FocusEvent,
18
+ forwardRef,
19
+ type HTMLAttributes,
20
+ type MutableRefObject,
21
+ type ReactNode,
22
+ type RefObject,
23
+ type SyntheticEvent,
24
+ useCallback,
25
+ useContext,
26
+ useEffect,
27
+ useImperativeHandle,
28
+ useMemo,
29
+ useRef,
30
+ useState,
31
+ } from 'react'
32
+ import { FluidHoverHighlight } from '../../../lib/fluid-hover-highlight'
33
+ import { FocusRing } from '../../../lib/focus-ring'
34
+ import { type IconComponent, useIcon } from '../../../lib/icon-context'
35
+ import { MorphSurface } from '../../../lib/morph-layers'
36
+ import { isDisabledRow, popupScrollAreaClass, popupViewportClass } from '../../../lib/popup'
37
+ import { RowOrderContext, useRowOrder, useRowSlot } from '../../../lib/row-order'
38
+ import { shapeMap } from '../../../lib/shape-context'
39
+ import { useSize } from '../../../lib/size-context'
40
+ import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
41
+ import { SurfaceProvider, useSurface } from '../../../lib/surface-context'
42
+ import { useControllableState } from '../../../lib/use-controllable-state'
43
+ import { type ItemRect, type UseFluidHoverReturn, useFluidHover } from '../../../lib/use-fluid-hover'
44
+ import { useKeyboardNavGate } from '../../../lib/use-keyboard-nav-gate'
45
+ import { SelectionBackgrounds, useMergeSplitBlocks, useSelectionRuns } from '../../../lib/use-merge-split'
46
+ import { type MorphOrigin, useMorph, useMorphOrigin } from '../../../lib/use-morph'
47
+ import { cn } from '../../../lib/utils'
48
+ import { ScrollArea } from '../../../primitives/scroll-area'
49
+ import {
50
+ DropdownContext,
51
+ type DropdownContextValue,
52
+ MenuItem,
53
+ type MenuItemRenderOptions,
54
+ useDropdown,
55
+ } from './menu-item'
56
+
57
+ // Menus keep the "rounded" shape whatever the rest of the UI is (index.tsx).
58
+ const shape = shapeMap.rounded
59
+
60
+ const NO_INDICES: number[] = []
61
+
62
+ type MenuPositionerProps = ComponentProps<typeof Menu.Positioner>
63
+
64
+ function mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {
65
+ return (node: T | null) => {
66
+ for (const ref of refs) {
67
+ if (typeof ref === 'function') ref(node)
68
+ else if (ref) (ref as MutableRefObject<T | null>).current = node
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * A submenu is a React child of the popup that holds its trigger, so its
75
+ * events bubble there through the portal. A panel acts only on events that
76
+ * happened in its own DOM.
77
+ */
78
+ const ownEvent = (e: SyntheticEvent) => (e.currentTarget as Node).contains(e.target as Node)
79
+
80
+ /** Wraps a MenuItem's styled row in the Base UI menu-item primitive. */
81
+ function renderPopupItem({
82
+ radio,
83
+ checkbox,
84
+ checked,
85
+ value,
86
+ disabled,
87
+ label,
88
+ closeOnClick,
89
+ onClick,
90
+ element,
91
+ children,
92
+ }: MenuItemRenderOptions) {
93
+ if (checkbox)
94
+ return (
95
+ <Menu.CheckboxItem
96
+ checked={!!checked}
97
+ disabled={disabled}
98
+ label={label}
99
+ closeOnClick={closeOnClick}
100
+ onClick={onClick}
101
+ render={element}
102
+ >
103
+ {children}
104
+ </Menu.CheckboxItem>
105
+ )
106
+ if (radio)
107
+ return (
108
+ <Menu.RadioItem
109
+ value={value}
110
+ disabled={disabled}
111
+ label={label}
112
+ closeOnClick={closeOnClick}
113
+ onClick={onClick}
114
+ render={element}
115
+ >
116
+ {children}
117
+ </Menu.RadioItem>
118
+ )
119
+ return (
120
+ <Menu.Item disabled={disabled} label={label} closeOnClick={closeOnClick} onClick={onClick} render={element}>
121
+ {children}
122
+ </Menu.Item>
123
+ )
124
+ }
125
+
126
+ /** The rows a `Dropdown.CheckboxItem` / `Dropdown.RadioItem` reports checked, for the merged selection background. */
127
+ function useValueChecked() {
128
+ const [valueChecked, setValueChecked] = useState(NO_INDICES)
129
+ const reportChecked = useCallback(
130
+ (index: number, on: boolean) =>
131
+ setValueChecked(prev =>
132
+ on === prev.includes(index) ? prev : on ? [...prev, index] : prev.filter(i => i !== index),
133
+ ),
134
+ [],
135
+ )
136
+ return { valueChecked, reportChecked }
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // The state a popup panel keeps: fluid hover, the row order, the keyboard
141
+ // ring, the highlight an open submenu pins, and the value-checked rows.
142
+ // ---------------------------------------------------------------------------
143
+
144
+ function useMenuPanel(containerRef: RefObject<HTMLDivElement | null>, open: boolean) {
145
+ // Annotated: left to inference, the declaration emit names this type through the
146
+ // root barrel (`import("../../..")`), a cycle that makes bunup's d.ts bundling
147
+ // silently drop `index.d.ts` and every entry importing the dropdown.
148
+ const hover: UseFluidHoverReturn = useFluidHover(containerRef, { isItemDisabled: isDisabledRow })
149
+ const { activeIndex, setActiveIndex, itemRects, isMeasured } = hover
150
+ const rowOrder = useRowOrder()
151
+ const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
152
+ const { keyboardNavRef, trackKeyboardNav } = useKeyboardNavGate(open)
153
+
154
+ const [pinned, setPinned] = useState<number | null>(null)
155
+ const pin = useCallback((index: number, on: boolean) => setPinned(p => (on ? index : p === index ? null : p)), [])
156
+
157
+ const { valueChecked, reportChecked } = useValueChecked()
158
+
159
+ // A close drops the ring and the highlight, so the next open doesn't
160
+ // spring them over from the last row.
161
+ useEffect(() => {
162
+ if (open) return
163
+ setFocusedIndex(null)
164
+ setActiveIndex(null)
165
+ }, [open, setActiveIndex])
166
+
167
+ const lit = activeIndex ?? pinned
168
+ const shownHover = useMemo(() => ({ ...hover, activeIndex: lit }), [hover, lit])
169
+ const focusRect: ItemRect | null = isMeasured && focusedIndex !== null ? (itemRects[focusedIndex] ?? null) : null
170
+
171
+ /** A row took focus: light it, and ring it when the keyboard moved it. */
172
+ const focusRow = useCallback(
173
+ (e: FocusEvent) => {
174
+ const row = (e.target as HTMLElement).closest<HTMLElement>('[data-fluid-hover-index]')
175
+ if (!row) return false
176
+ const index = Number(row.getAttribute('data-fluid-hover-index'))
177
+ // Base UI keeps disabled rows focusable; they get the ring, never the light.
178
+ setActiveIndex(isDisabledRow(row) ? null : index)
179
+ setFocusedIndex(keyboardNavRef.current && (e.target as HTMLElement).matches(':focus-visible') ? index : null)
180
+ return true
181
+ },
182
+ [keyboardNavRef, setActiveIndex],
183
+ )
184
+
185
+ return {
186
+ hover,
187
+ shownHover,
188
+ lit,
189
+ rowOrder,
190
+ focusRect,
191
+ setFocusedIndex,
192
+ focusRow,
193
+ trackKeyboardNav,
194
+ pin,
195
+ valueChecked,
196
+ reportChecked,
197
+ }
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Dropdown.Group
202
+ // ---------------------------------------------------------------------------
203
+
204
+ /** True inside a `Dropdown.Group`, where `Dropdown.Label` names the group. */
205
+ const DropdownGroupContext = createContext(false)
206
+
207
+ interface DropdownGroupProps extends HTMLAttributes<HTMLDivElement> {
208
+ /** Rows, with an optional `Dropdown.Label` first: it becomes the group's accessible name. */
209
+ children?: ReactNode
210
+ /** Extra classes. */
211
+ className?: string
212
+ }
213
+
214
+ /** A run of related rows (role="group"), named by the `Dropdown.Label` inside it. */
215
+ const DropdownGroup = forwardRef<HTMLDivElement, DropdownGroupProps>(({ className, ...props }, ref) => {
216
+ const own = useRef<HTMLDivElement>(null)
217
+ useRowSlot(own, undefined, true)
218
+ return (
219
+ <DropdownGroupContext.Provider value={true}>
220
+ <Menu.Group ref={mergeRefs(own, ref)} className={cn('flex flex-col', className)} {...props} />
221
+ </DropdownGroupContext.Provider>
222
+ )
223
+ })
224
+
225
+ DropdownGroup.displayName = 'DropdownGroup'
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Dropdown.CheckboxItem
229
+ // ---------------------------------------------------------------------------
230
+
231
+ interface DropdownCheckboxItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect' | 'defaultChecked'> {
232
+ /** Whether the row is checked. Pair with `onCheckedChange`. */
233
+ checked?: boolean
234
+ /** Initial checked state, for an uncontrolled row. Defaults to `false`. */
235
+ defaultChecked?: boolean
236
+ /** Called with the new state when the row is toggled. */
237
+ onCheckedChange?: (checked: boolean) => void
238
+ /** Optional leading icon. */
239
+ icon?: IconComponent
240
+ /** The row text. Omit it to pass the row's content as `children`. */
241
+ label?: string
242
+ /** The row's content when there is no `label`. */
243
+ children?: ReactNode
244
+ /** Position among the panel's rows. Omitted, the row takes its place in document order. */
245
+ index?: number
246
+ /** Called when the row is picked, before it toggles. With `closeOnClick`, `event.preventDefault()` keeps the menu open. */
247
+ onSelect?: (event: Event) => void
248
+ /** Dims the row; the pointer and the keyboard skip it. Defaults to `false`. */
249
+ disabled?: boolean
250
+ /** Whether toggling closes the menu. Defaults to `false`. */
251
+ closeOnClick?: boolean
252
+ /** Indents the row to the label column of rows with an icon. Defaults to `false`. */
253
+ inset?: boolean
254
+ /** A keyboard shortcut shown muted at the row's end. */
255
+ shortcut?: ReactNode
256
+ /** Extra classes for the row. */
257
+ className?: string
258
+ }
259
+
260
+ /** A row that toggles on and off by itself (role="menuitemcheckbox"), keyed on its own state rather than an index. */
261
+ const DropdownCheckboxItem = forwardRef<HTMLDivElement, DropdownCheckboxItemProps>(
262
+ ({ checked, defaultChecked = false, onCheckedChange, onSelect, closeOnClick = false, ...props }, ref) => {
263
+ const [on, setOn] = useControllableState(checked, defaultChecked, onCheckedChange)
264
+ return (
265
+ <MenuItem
266
+ ref={ref}
267
+ kind="checkbox"
268
+ checked={on}
269
+ closeOnClick={closeOnClick}
270
+ onSelect={event => {
271
+ onSelect?.(event)
272
+ setOn(prev => !prev)
273
+ }}
274
+ {...props}
275
+ />
276
+ )
277
+ },
278
+ )
279
+
280
+ DropdownCheckboxItem.displayName = 'DropdownCheckboxItem'
281
+
282
+ // ---------------------------------------------------------------------------
283
+ // Dropdown.RadioGroup + Dropdown.RadioItem
284
+ // ---------------------------------------------------------------------------
285
+
286
+ const RadioValueContext = createContext<{ value?: string; select: (value: string) => void } | null>(null)
287
+
288
+ interface DropdownRadioGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, 'defaultValue' | 'onChange'> {
289
+ /** The picked row's value. Pair with `onValueChange`. */
290
+ value?: string
291
+ /** Initial value, for an uncontrolled group. */
292
+ defaultValue?: string
293
+ /** Called with the picked row's value. */
294
+ onValueChange?: (value: string) => void
295
+ /** `Dropdown.RadioItem` rows, plus an optional `Dropdown.Label`. */
296
+ children?: ReactNode
297
+ /** Extra classes. */
298
+ className?: string
299
+ }
300
+
301
+ /** One choice among its `Dropdown.RadioItem` rows, keyed on their `value`. */
302
+ const DropdownRadioGroup = forwardRef<HTMLDivElement, DropdownRadioGroupProps>(
303
+ ({ value, defaultValue, onValueChange, className, ...props }, ref) => {
304
+ const [current, setCurrent] = useControllableState<string | undefined>(value, defaultValue, next => {
305
+ if (next !== undefined) onValueChange?.(next)
306
+ })
307
+ const own = useRef<HTMLDivElement>(null)
308
+ useRowSlot(own, undefined, true)
309
+ const ctx = useMemo(() => ({ value: current, select: (next: string) => setCurrent(next) }), [current, setCurrent])
310
+ return (
311
+ <RadioValueContext.Provider value={ctx}>
312
+ <DropdownGroupContext.Provider value={false}>
313
+ <Menu.RadioGroup
314
+ ref={mergeRefs(own, ref)}
315
+ value={current ?? null}
316
+ className={cn('flex flex-col', className)}
317
+ {...props}
318
+ />
319
+ </DropdownGroupContext.Provider>
320
+ </RadioValueContext.Provider>
321
+ )
322
+ },
323
+ )
324
+
325
+ DropdownRadioGroup.displayName = 'DropdownRadioGroup'
326
+
327
+ interface DropdownRadioItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
328
+ /** The value this row stands for in its `Dropdown.RadioGroup`. */
329
+ value: string
330
+ /** Optional leading icon. */
331
+ icon?: IconComponent
332
+ /** The row text. Omit it to pass the row's content as `children`. */
333
+ label?: string
334
+ /** The row's content when there is no `label`. */
335
+ children?: ReactNode
336
+ /** Position among the panel's rows. Omitted, the row takes its place in document order. */
337
+ index?: number
338
+ /** Called when the row is picked, before the group's value changes. Call `event.preventDefault()` to keep the menu open. */
339
+ onSelect?: (event: Event) => void
340
+ /** Dims the row; the pointer and the keyboard skip it. Defaults to `false`. */
341
+ disabled?: boolean
342
+ /** Whether picking the row closes the menu. Defaults to `true`. */
343
+ closeOnClick?: boolean
344
+ /** Indents the row to the label column of rows with an icon. Defaults to `false`. */
345
+ inset?: boolean
346
+ /** A keyboard shortcut shown muted at the row's end. */
347
+ shortcut?: ReactNode
348
+ /** Extra classes for the row. */
349
+ className?: string
350
+ }
351
+
352
+ /** A row of a `Dropdown.RadioGroup` (role="menuitemradio"), checked while the group's value is its `value`. */
353
+ const DropdownRadioItem = forwardRef<HTMLDivElement, DropdownRadioItemProps>(({ value, onSelect, ...props }, ref) => {
354
+ const group = useContext(RadioValueContext)
355
+ return (
356
+ <MenuItem
357
+ ref={ref}
358
+ kind="radio"
359
+ value={value}
360
+ checked={group?.value === value}
361
+ onSelect={event => {
362
+ onSelect?.(event)
363
+ group?.select(value)
364
+ }}
365
+ {...props}
366
+ />
367
+ )
368
+ })
369
+
370
+ DropdownRadioItem.displayName = 'DropdownRadioItem'
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // Dropdown.Sub + Dropdown.SubTrigger + Dropdown.SubContent
374
+ // ---------------------------------------------------------------------------
375
+
376
+ interface SubContextValue {
377
+ open: boolean
378
+ origin: RefObject<MorphOrigin>
379
+ }
380
+
381
+ const SubContext = createContext<SubContextValue | null>(null)
382
+
383
+ function useSubContext() {
384
+ const ctx = useContext(SubContext)
385
+ if (!ctx) throw new Error('Dropdown.SubTrigger and Dropdown.SubContent must be inside <Dropdown.Sub>')
386
+ return ctx
387
+ }
388
+
389
+ interface DropdownSubProps {
390
+ /** `Dropdown.SubTrigger` and `Dropdown.SubContent`. */
391
+ children: ReactNode
392
+ /** Controlled open state. Pair with `onOpenChange`. */
393
+ open?: boolean
394
+ /** Initial open state, for an uncontrolled submenu. Defaults to `false`. */
395
+ defaultOpen?: boolean
396
+ /** Called when the submenu opens or closes. */
397
+ onOpenChange?: (open: boolean) => void
398
+ /** Disables the submenu. Defaults to `false`. */
399
+ disabled?: boolean
400
+ }
401
+
402
+ /** A submenu: its trigger row and its panel. Lives inside `Dropdown.Content` (or another submenu). */
403
+ function DropdownSub({ children, open, defaultOpen = false, onOpenChange, disabled = false }: DropdownSubProps) {
404
+ const [current, setCurrent] = useControllableState(open, defaultOpen, onOpenChange)
405
+ const { origin, capture } = useMorphOrigin()
406
+ const ctx = useMemo(() => ({ open: current, origin }), [current, origin])
407
+ return (
408
+ <SubContext.Provider value={ctx}>
409
+ <Menu.SubmenuRoot
410
+ open={current}
411
+ disabled={disabled}
412
+ onOpenChange={(next, details) => {
413
+ if (next) capture(details)
414
+ setCurrent(next)
415
+ }}
416
+ >
417
+ {children}
418
+ </Menu.SubmenuRoot>
419
+ </SubContext.Provider>
420
+ )
421
+ }
422
+
423
+ DropdownSub.displayName = 'DropdownSub'
424
+
425
+ interface DropdownSubTriggerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
426
+ /** Optional leading icon. */
427
+ icon?: IconComponent
428
+ /** The row text. Omit it to pass the row's content as `children`. */
429
+ label?: string
430
+ /** The row's content when there is no `label`. */
431
+ children?: ReactNode
432
+ /** Position among the panel's rows. Omitted, the row takes its place in document order. */
433
+ index?: number
434
+ /** Dims the row; the pointer and the keyboard skip it. Defaults to `false`. */
435
+ disabled?: boolean
436
+ /** Indents the row to the label column of rows with an icon. Defaults to `false`. */
437
+ inset?: boolean
438
+ /** Open the submenu when the pointer rests on the row. Defaults to `true`. */
439
+ openOnHover?: boolean
440
+ /** How long the pointer rests before it opens, in ms. Defaults to `100`. */
441
+ delay?: number
442
+ /** How long before a hover-opened submenu closes once the pointer leaves, in ms. Defaults to `0`. */
443
+ closeDelay?: number
444
+ /** Extra classes for the row. */
445
+ className?: string
446
+ }
447
+
448
+ /**
449
+ * The row that opens a submenu, with a trailing chevron: ArrowRight, a press
450
+ * or a resting pointer opens it, ArrowLeft closes it. Its row stays lit while
451
+ * the submenu is open.
452
+ */
453
+ const DropdownSubTrigger = forwardRef<HTMLDivElement, DropdownSubTriggerProps>(
454
+ ({ openOnHover, delay, closeDelay, ...props }, ref) => {
455
+ const { open, origin } = useSubContext()
456
+ const parent = useDropdown()
457
+ const Chevron = useIcon('chevron-right')
458
+ const size = useSize()
459
+
460
+ const ctx = useMemo<DropdownContextValue>(
461
+ () => ({
462
+ ...parent,
463
+ renderMenuItem: ({ disabled, label, onClick, element, children }) => (
464
+ <Menu.SubmenuTrigger
465
+ disabled={disabled}
466
+ label={label}
467
+ onClick={onClick}
468
+ openOnHover={openOnHover}
469
+ delay={delay}
470
+ closeDelay={closeDelay}
471
+ render={element}
472
+ >
473
+ {children}
474
+ <Chevron
475
+ size={size.icon}
476
+ strokeWidth={open ? 2 : 1.5}
477
+ className={cn(
478
+ 'ml-auto shrink-0 transition-[color,stroke-width] duration-fast',
479
+ open ? 'text-foreground' : 'text-muted-foreground',
480
+ )}
481
+ />
482
+ </Menu.SubmenuTrigger>
483
+ ),
484
+ }),
485
+ [parent, openOnHover, delay, closeDelay, Chevron, size.icon, open],
486
+ )
487
+
488
+ // The submenu grows out of this row, whatever Base UI reports as the trigger.
489
+ const setTrigger = useCallback(
490
+ (node: HTMLDivElement | null) => {
491
+ if (node) (origin as MutableRefObject<MorphOrigin>).current = { ...origin.current, trigger: node }
492
+ },
493
+ [origin],
494
+ )
495
+
496
+ return (
497
+ <DropdownContext.Provider value={ctx}>
498
+ <MenuItem ref={mergeRefs(setTrigger, ref)} pinned={open} {...props} />
499
+ </DropdownContext.Provider>
500
+ )
501
+ },
502
+ )
503
+
504
+ DropdownSubTrigger.displayName = 'DropdownSubTrigger'
505
+
506
+ interface DropdownSubContentProps {
507
+ /** Rows: `Dropdown.Item`, `Dropdown.CheckboxItem`, `Dropdown.RadioGroup`, `Dropdown.Group`, `Dropdown.Label`, `Dropdown.Separator`, another `Dropdown.Sub`. */
508
+ children: ReactNode
509
+ /** Extra classes for the submenu surface — it is 14rem wide by default. */
510
+ className?: string
511
+ /** Side of the trigger row the submenu prefers. Defaults to `"inline-end"`. */
512
+ side?: MenuPositionerProps['side']
513
+ /** Alignment against the trigger row. Defaults to `"start"`. */
514
+ align?: MenuPositionerProps['align']
515
+ /** Gap to the parent menu, in px: the goo neck's length. Defaults to `8`. */
516
+ sideOffset?: number
517
+ /** Shift along the trigger row, in px; `-4` lines the first row up with the trigger. Defaults to `-4`. */
518
+ alignOffset?: number
519
+ /** Where the submenu grows from (see Morph): its trigger row, its own center, a viewport edge, or a ref to any element. Defaults to `'trigger'`. */
520
+ from?: 'trigger' | 'center' | 'top' | 'right' | 'bottom' | 'left' | RefObject<HTMLElement | null>
521
+ /** How it grows (see Morph): with the liquid goo neck, a plain morph, a slide or a fade. Defaults to `'goo'`. */
522
+ effect?: 'goo' | 'morph' | 'slide' | 'fade'
523
+ /** Spring tier of a plain morph, slide or fade; goo runs on its own `spring.goo`. Defaults to `'moderate'`. */
524
+ tier?: 'moderate' | 'slow'
525
+ /** Keep the rows mounted (hidden) while the submenu is closed, so rows that run their own state keep it across opens. Defaults to `false`. */
526
+ keepMounted?: boolean
527
+ }
528
+
529
+ /** The submenu's panel: the same rows and systems as `Dropdown.Content`, lifted 2 levels above the menu it came from. */
530
+ const DropdownSubContent = forwardRef<HTMLDivElement, DropdownSubContentProps>(
531
+ (
532
+ {
533
+ children,
534
+ className,
535
+ side = 'inline-end',
536
+ align = 'start',
537
+ sideOffset = 8,
538
+ alignOffset = -4,
539
+ from,
540
+ effect,
541
+ tier = 'moderate',
542
+ keepMounted = false,
543
+ },
544
+ ref,
545
+ ) => {
546
+ const { open, origin } = useSubContext()
547
+ const morph = useMorph(open, origin, { from, effect, tier })
548
+ useImperativeHandle(ref, () => morph.popup as HTMLDivElement, [morph.popup])
549
+ const level = Math.min(useSurface() + 2, 8)
550
+ const containerRef = useRef<HTMLDivElement>(null)
551
+ const panel = useMenuPanel(containerRef, open)
552
+ const { hover, focusRow, setFocusedIndex } = panel
553
+ const { handlers, setActiveIndex, registerItem, itemRects, remeasure } = hover
554
+
555
+ useEffect(() => {
556
+ if (open) remeasure()
557
+ }, [open, remeasure])
558
+
559
+ const runs = useSelectionRuns(panel.valueChecked)
560
+ const blocks = useMergeSplitBlocks(runs, itemRects, shape.bgRadius)
561
+
562
+ const ctx = useMemo<DropdownContextValue>(
563
+ () => ({
564
+ registerItem,
565
+ activeIndex: panel.lit,
566
+ inMenu: true,
567
+ renderMenuItem: renderPopupItem,
568
+ pin: panel.pin,
569
+ reportChecked: panel.reportChecked,
570
+ }),
571
+ [registerItem, panel.lit, panel.pin, panel.reportChecked],
572
+ )
573
+
574
+ return (
575
+ <Menu.Portal keepMounted={keepMounted}>
576
+ <Menu.Positioner
577
+ side={side}
578
+ align={align}
579
+ sideOffset={sideOffset}
580
+ alignOffset={alignOffset}
581
+ className="z-50 outline-none"
582
+ >
583
+ <Menu.Popup
584
+ ref={morph.popupRef}
585
+ onKeyDownCapture={e => ownEvent(e) && panel.trackKeyboardNav(e)}
586
+ onMouseEnter={() => {
587
+ handlers.onMouseEnter()
588
+ setFocusedIndex(null)
589
+ }}
590
+ onMouseMove={e => ownEvent(e) && handlers.onMouseMove(e)}
591
+ onMouseLeave={handlers.onMouseLeave}
592
+ onClick={e => ownEvent(e) && handlers.onClick(e)}
593
+ onFocus={e => {
594
+ if (!ownEvent(e) || focusRow(e)) return
595
+ if (e.target !== e.currentTarget) setActiveIndex(null)
596
+ }}
597
+ onBlur={e => {
598
+ if (!ownEvent(e) || e.currentTarget.contains(e.relatedTarget as Node)) return
599
+ setFocusedIndex(null)
600
+ setActiveIndex(null)
601
+ }}
602
+ className="relative select-none outline-none"
603
+ >
604
+ <SurfaceProvider value={level}>
605
+ <MorphSurface
606
+ morph={morph}
607
+ bg={SURFACE_BG[level]}
608
+ shadow={SURFACE_SHADOW[3]}
609
+ radius={shape.container}
610
+ className={cn(
611
+ 'flex w-56 max-w-[var(--available-width)] max-h-[min(480px,var(--available-height))] flex-col overflow-hidden',
612
+ shape.container,
613
+ className,
614
+ )}
615
+ >
616
+ <ScrollArea className={popupScrollAreaClass} viewportClassName={cn(popupViewportClass, 'scroll-fade')}>
617
+ <div ref={containerRef} className="relative flex flex-col p-1">
618
+ <SelectionBackgrounds blocks={blocks} />
619
+ <FluidHoverHighlight hover={panel.shownHover} hidden={!open} className={shape.bg} />
620
+ <FocusRing rect={open ? panel.focusRect : null} shape="rounded" />
621
+ <RowOrderContext.Provider value={panel.rowOrder}>
622
+ <DropdownContext.Provider value={ctx}>{children}</DropdownContext.Provider>
623
+ </RowOrderContext.Provider>
624
+ </div>
625
+ </ScrollArea>
626
+ </MorphSurface>
627
+ </SurfaceProvider>
628
+ </Menu.Popup>
629
+ </Menu.Positioner>
630
+ </Menu.Portal>
631
+ )
632
+ },
633
+ )
634
+
635
+ DropdownSubContent.displayName = 'DropdownSubContent'
636
+
637
+ export type {
638
+ DropdownCheckboxItemProps,
639
+ DropdownGroupProps,
640
+ DropdownRadioGroupProps,
641
+ DropdownRadioItemProps,
642
+ DropdownSubContentProps,
643
+ DropdownSubProps,
644
+ DropdownSubTriggerProps,
645
+ }
646
+ export {
647
+ DropdownCheckboxItem,
648
+ DropdownGroup,
649
+ DropdownGroupContext,
650
+ DropdownRadioGroup,
651
+ DropdownRadioItem,
652
+ DropdownSub,
653
+ DropdownSubContent,
654
+ DropdownSubTrigger,
655
+ mergeRefs,
656
+ ownEvent,
657
+ renderPopupItem,
658
+ useMenuPanel,
659
+ useValueChecked,
660
+ }