@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,395 @@
1
+ // biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
2
+ /*
3
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
4
+ * `registry/default/dropdown-search.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
6
+ * Local modifications:
7
+ * - `"use client"` dropped; `@/lib/*` imports rewritten to `../../../lib/*`.
8
+ * - `filter` mode (local addition): `value` / `onValueChange` become optional
9
+ * and, with `filter`, the field reads and writes the query the surrounding
10
+ * panel holds (`DropdownFilterContext`), which filters the rows itself. The
11
+ * controlled `value` + `onValueChange` API is untouched.
12
+ * - Styling reads DS tokens (AGENTS.md styling): `duration-80|120|160` and
13
+ * tier-length JS durations → `duration-<tier>` / `spring.*`.
14
+ */
15
+
16
+ import {
17
+ createContext,
18
+ forwardRef,
19
+ type HTMLAttributes,
20
+ type InputHTMLAttributes,
21
+ type KeyboardEvent as ReactKeyboardEvent,
22
+ type RefObject,
23
+ useCallback,
24
+ useContext,
25
+ useEffect,
26
+ useMemo,
27
+ useRef,
28
+ useState,
29
+ } from 'react'
30
+ import { useIcon } from '../../../lib/icon-context'
31
+ import { useSize } from '../../../lib/size-context'
32
+ import { SURFACE_BG } from '../../../lib/surface-classes'
33
+ import { useSurface } from '../../../lib/surface-context'
34
+ import { cn } from '../../../lib/utils'
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Search inside a dropdown menu.
38
+ //
39
+ // DropdownSearch is a text field pinned to the top of a DropdownContent popup.
40
+ // It is controlled: the consumer filters the MenuItems it renders against
41
+ // `value`, so the list can be re-indexed freely (fluid hover keys on the
42
+ // indices of whatever is currently mounted). Two things make a field inside a
43
+ // menu behave:
44
+ //
45
+ // 1. The menu primitive's typeahead must not steal keystrokes from the
46
+ // field — every typing key stops propagating at the input.
47
+ // 2. Typing while a ROW is focused must land in the field: the popup
48
+ // hosts a capture-phase keydown handler (useDropdownSearchHost) that
49
+ // refocuses the input and appends the character.
50
+ //
51
+ // Arrow keys leave the field for the list (first / last row), Enter picks
52
+ // the first row, Escape closes the menu as usual. While the field has focus
53
+ // the first row carries the hover background, so what Enter will pick is
54
+ // always in view; it follows the query as the rows re-filter.
55
+ //
56
+ // Local addition — `filter`: the same field, with the query held by the
57
+ // surrounding panel instead of the consumer. The panel then drops the rows
58
+ // whose `label` doesn't match and re-indexes the survivors, which is exactly
59
+ // the work the controlled form asks the consumer to do.
60
+ // ---------------------------------------------------------------------------
61
+
62
+ interface SearchHandle {
63
+ input: HTMLInputElement | null
64
+ append: (text: string) => void
65
+ deleteBackward: () => void
66
+ }
67
+
68
+ interface DropdownSearchHostValue {
69
+ register: (handle: SearchHandle) => () => void
70
+ /** Whether the popup is open. Popups stay mounted through their exit
71
+ * animation, so the field watches this rather than its own mount. */
72
+ open: boolean
73
+ /** Move the hover background to the first enabled row (what Enter picks). */
74
+ highlightFirst: () => void
75
+ }
76
+
77
+ export const DropdownSearchHostContext = createContext<DropdownSearchHostValue | null>(null)
78
+
79
+ /** Local addition. The query a filtering panel holds for its `filter` field. */
80
+ export interface DropdownFilterContextValue {
81
+ query: string
82
+ setQuery: (query: string) => void
83
+ }
84
+
85
+ export const DropdownFilterContext = createContext<DropdownFilterContextValue | null>(null)
86
+
87
+ const ROW_SELECTOR = [
88
+ '[role="menuitem"]:not([aria-disabled="true"])',
89
+ '[role="menuitemradio"]:not([aria-disabled="true"])',
90
+ '[role="menuitemcheckbox"]:not([aria-disabled="true"])',
91
+ ].join(', ')
92
+
93
+ function menuRows(from: HTMLElement | null): HTMLElement[] {
94
+ const menu = from?.closest<HTMLElement>('[role="menu"]')
95
+ return menu ? Array.from(menu.querySelectorAll<HTMLElement>(ROW_SELECTOR)) : []
96
+ }
97
+
98
+ interface DropdownSearchHostOptions {
99
+ /** The rows' container: where the first enabled row is looked up. */
100
+ containerRef: RefObject<HTMLElement | null>
101
+ /** The popup's fluid-hover setter, for the first-row highlight. */
102
+ setActiveIndex: (index: number | null) => void
103
+ }
104
+
105
+ /**
106
+ * Hosts a DropdownSearch inside a popup. Returns the context value to
107
+ * provide, a capture-phase keydown handler for the popup element,
108
+ * `hasSearch()` / `searchMounted` for decisions that depend on a field
109
+ * being present, and the first-row highlight the field keeps while it has
110
+ * focus (`isSearchField` / `highlightFirst`, for the popup's focus and
111
+ * mouse-leave handlers).
112
+ */
113
+ export function useDropdownSearchHost(open: boolean, { containerRef, setActiveIndex }: DropdownSearchHostOptions) {
114
+ const handleRef = useRef<SearchHandle | null>(null)
115
+ // Reactive twin of the ref, for render-time decisions (the popup drops its
116
+ // scroll fade while a field is pinned at the top).
117
+ const [searchMounted, setSearchMounted] = useState(false)
118
+
119
+ const register = useCallback((handle: SearchHandle) => {
120
+ handleRef.current = handle
121
+ setSearchMounted(true)
122
+ return () => {
123
+ if (handleRef.current === handle) {
124
+ handleRef.current = null
125
+ setSearchMounted(false)
126
+ }
127
+ }
128
+ }, [])
129
+
130
+ // The row Enter will pick carries the hover background while the field
131
+ // has focus. Rows re-index from 0 on every filter, so this is looked up
132
+ // in the DOM each time rather than remembered.
133
+ const highlightFirst = useCallback(() => {
134
+ const first = containerRef.current?.querySelector<HTMLElement>(ROW_SELECTOR)
135
+ const index = first?.getAttribute('data-fluid-hover-index')
136
+ setActiveIndex(index != null ? Number(index) : null)
137
+ }, [containerRef, setActiveIndex])
138
+
139
+ /** Whether `target` is the mounted search field. */
140
+ const isSearchField = useCallback(
141
+ (target: EventTarget | null) => target !== null && target === handleRef.current?.input,
142
+ [],
143
+ )
144
+
145
+ const host = useMemo(() => ({ register, open, highlightFirst }), [register, open, highlightFirst])
146
+
147
+ // Typing on a focused row: redirect the character (or Backspace) into the
148
+ // field. Space is left alone — on a row it activates the item, which is
149
+ // what a menu user expects once they have arrowed down.
150
+ const onKeyDownCapture = useCallback((e: ReactKeyboardEvent<HTMLElement>) => {
151
+ const handle = handleRef.current
152
+ if (!handle?.input) return
153
+ if (e.target === handle.input) return
154
+ if (e.metaKey || e.ctrlKey || e.altKey) return
155
+ if (e.key.length === 1 && e.key !== ' ') {
156
+ e.preventDefault()
157
+ e.stopPropagation()
158
+ handle.input.focus()
159
+ handle.append(e.key)
160
+ } else if (e.key === 'Backspace') {
161
+ e.preventDefault()
162
+ e.stopPropagation()
163
+ handle.input.focus()
164
+ handle.deleteBackward()
165
+ }
166
+ }, [])
167
+
168
+ /** Whether a search field is mounted in the popup right now. */
169
+ const hasSearch = useCallback(() => handleRef.current !== null, [])
170
+
171
+ return {
172
+ host,
173
+ hasSearch,
174
+ searchMounted,
175
+ onKeyDownCapture,
176
+ isSearchField,
177
+ highlightFirst,
178
+ }
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // DropdownSearch
183
+ // ---------------------------------------------------------------------------
184
+
185
+ export interface DropdownSearchProps
186
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'size' | 'defaultValue'> {
187
+ /** The query. Filter the MenuItems you render against it. Optional under `filter`, where the panel holds the query. */
188
+ value?: string
189
+ /** Called on every keystroke, and with `""` on close (see `clearOnClose`). Optional under `filter`. */
190
+ onValueChange?: (value: string) => void
191
+ /** Local addition. Let the surrounding panel hold the query and filter its own `Dropdown.Item` rows by `label`, re-indexing the survivors and showing `Dropdown.Empty` when none match. Defaults to `false`. */
192
+ filter?: boolean
193
+ /** Placeholder text. Defaults to `"Search…"`. */
194
+ placeholder?: string
195
+ /** Reset the query to "" when the popup closes (the field unmounts with
196
+ * it), so the menu reopens unfiltered. @default true */
197
+ clearOnClose?: boolean
198
+ /** Take focus when the popup opens. @default true */
199
+ autoFocus?: boolean
200
+ }
201
+
202
+ const DropdownSearch = forwardRef<HTMLInputElement, DropdownSearchProps>(
203
+ (
204
+ {
205
+ value,
206
+ onValueChange,
207
+ filter = false,
208
+ placeholder = 'Search…',
209
+ clearOnClose = true,
210
+ autoFocus = true,
211
+ className,
212
+ onKeyDown,
213
+ ...props
214
+ },
215
+ ref,
216
+ ) => {
217
+ const SearchIcon = useIcon('search')
218
+ const sizeClasses = useSize()
219
+ const compact = sizeClasses.variant === 'compact'
220
+ const host = useContext(DropdownSearchHostContext)
221
+ // Outside a popup host (an inline panel) the field is simply open.
222
+ const open = host?.open ?? true
223
+ // `filter`: the panel is the source of truth, and the consumer's own
224
+ // `onValueChange` (if any) still hears every keystroke.
225
+ const panel = useContext(DropdownFilterContext)
226
+ const managed = filter ? panel : null
227
+ const query = managed ? managed.query : (value ?? '')
228
+ const setQuery = useCallback(
229
+ (next: string) => {
230
+ managed?.setQuery(next)
231
+ onValueChange?.(next)
232
+ },
233
+ [managed, onValueChange],
234
+ )
235
+ // The popup surface, painted explicitly: rows scroll underneath the sticky
236
+ // field, and the field's parent is often a display:contents group (the
237
+ // menu's radio group) that `bg-inherit` would resolve to transparent.
238
+ const surface = useSurface()
239
+ const inputRef = useRef<HTMLInputElement | null>(null)
240
+
241
+ // Latest value / callback for the imperative handle and the unmount
242
+ // cleanup, without re-registering on every keystroke.
243
+ const valueRef = useRef(query)
244
+ valueRef.current = query
245
+ const onValueChangeRef = useRef(setQuery)
246
+ onValueChangeRef.current = setQuery
247
+ const clearOnCloseRef = useRef(clearOnClose)
248
+ clearOnCloseRef.current = clearOnClose
249
+
250
+ useEffect(() => {
251
+ if (!host) return
252
+ return host.register({
253
+ get input() {
254
+ return inputRef.current
255
+ },
256
+ append: text => onValueChangeRef.current(valueRef.current + text),
257
+ deleteBackward: () => onValueChangeRef.current(valueRef.current.slice(0, -1)),
258
+ })
259
+ }, [host])
260
+
261
+ // On every open (mount, or a reopen while the popup was still playing
262
+ // its exit): reset the query so the list starts unfiltered, then take
263
+ // focus. The popup's own initial focus (first row on a keyboard open,
264
+ // the panel on a pointer open) is queued a frame after mount; the field
265
+ // takes over one frame later so a menu with a search opens ready to type.
266
+ useEffect(() => {
267
+ if (!open) return
268
+ if (clearOnCloseRef.current && valueRef.current !== '') {
269
+ onValueChangeRef.current('')
270
+ }
271
+ if (!autoFocus) return
272
+ let inner: number | undefined
273
+ const outer = requestAnimationFrame(() => {
274
+ inner = requestAnimationFrame(() => inputRef.current?.focus())
275
+ })
276
+ return () => {
277
+ cancelAnimationFrame(outer)
278
+ if (inner !== undefined) cancelAnimationFrame(inner)
279
+ }
280
+ }, [open, autoFocus])
281
+
282
+ // The rows re-filter in the same commit as the query, so after it the
283
+ // first enabled row is whatever Enter would pick now: highlight it while
284
+ // the field has focus. (Focus arriving at the field is the popup's
285
+ // onFocus; this covers the query moving under a focused field.)
286
+ useEffect(() => {
287
+ if (!host || document.activeElement !== inputRef.current) return
288
+ host.highlightFirst()
289
+ }, [host, query])
290
+
291
+ // Unmount (the popup finished closing): reset the query so the next
292
+ // open shows the full list.
293
+ useEffect(
294
+ () => () => {
295
+ if (clearOnCloseRef.current && valueRef.current !== '') {
296
+ onValueChangeRef.current('')
297
+ }
298
+ },
299
+ [],
300
+ )
301
+
302
+ const handleKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {
303
+ onKeyDown?.(e)
304
+ if (e.defaultPrevented) return
305
+ // Escape and Tab belong to the menu (close / leave). Everything else is
306
+ // either typing, which the menu's typeahead must not see, or list
307
+ // navigation handled right here.
308
+ if (e.key === 'Escape' || e.key === 'Tab') return
309
+ e.stopPropagation()
310
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
311
+ const rows = menuRows(e.currentTarget)
312
+ if (rows.length === 0) return
313
+ e.preventDefault()
314
+ ;(e.key === 'ArrowDown' ? rows[0] : rows[rows.length - 1])?.focus()
315
+ } else if (e.key === 'Enter') {
316
+ e.preventDefault()
317
+ menuRows(e.currentTarget)[0]?.click()
318
+ }
319
+ }
320
+
321
+ return (
322
+ <div
323
+ // Sticky at the top of the scrolling popup, bleeding into its 4px
324
+ // padding so the divider runs edge to edge and rows scroll underneath
325
+ // it; margin + the list gap put the first row 4px below the divider —
326
+ // the same inset as the popup's side padding.
327
+ className={cn(
328
+ 'group/search sticky top-0 z-20 -mx-1 -mt-1 mb-0.5 flex shrink-0 items-center border-b border-border/60',
329
+ SURFACE_BG[surface],
330
+ sizeClasses.control,
331
+ sizeClasses.gap,
332
+ compact ? 'px-2.5' : 'px-3',
333
+ className,
334
+ )}
335
+ >
336
+ <SearchIcon
337
+ size={sizeClasses.icon}
338
+ strokeWidth={1.5}
339
+ className="shrink-0 text-muted-foreground transition-[color,stroke-width] duration-fast group-focus-within/search:text-foreground group-focus-within/search:stroke-[2]"
340
+ />
341
+ <input
342
+ ref={node => {
343
+ inputRef.current = node
344
+ if (typeof ref === 'function') ref(node)
345
+ else if (ref) ref.current = node
346
+ }}
347
+ type="text"
348
+ role="searchbox"
349
+ autoComplete="off"
350
+ autoCorrect="off"
351
+ spellCheck={false}
352
+ value={query}
353
+ onChange={e => setQuery(e.target.value)}
354
+ onKeyDown={handleKeyDown}
355
+ placeholder={placeholder}
356
+ // rounded-none: the site's base :focus-visible rule hands focused
357
+ // elements the shape radius, and a text input clips its caret to
358
+ // its own corners (pill mode nicks the caret at the left edge).
359
+ className={cn(
360
+ 'min-w-0 flex-1 rounded-none bg-transparent text-foreground placeholder:text-muted-foreground outline-none font-[inherit]',
361
+ sizeClasses.text,
362
+ // The line box: Safari runs the caret its full height, so the
363
+ // ladder's leading keeps it in proportion to the row. (Chrome
364
+ // sizes the caret to the font itself, whatever the leading.)
365
+ compact ? 'leading-5' : 'leading-6',
366
+ )}
367
+ {...props}
368
+ />
369
+ </div>
370
+ )
371
+ },
372
+ )
373
+
374
+ DropdownSearch.displayName = 'DropdownSearch'
375
+
376
+ // ---------------------------------------------------------------------------
377
+ // DropdownEmpty — the "no results" row a filtered menu shows instead of items.
378
+ // ---------------------------------------------------------------------------
379
+
380
+ const DropdownEmpty = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
381
+ const sizeClasses = useSize()
382
+ return (
383
+ <div
384
+ ref={ref}
385
+ role="status"
386
+ aria-live="polite"
387
+ className={cn('px-2 py-6 text-center text-muted-foreground', sizeClasses.text, className)}
388
+ {...props}
389
+ />
390
+ )
391
+ })
392
+
393
+ DropdownEmpty.displayName = 'DropdownEmpty'
394
+
395
+ export { DropdownEmpty, DropdownSearch }
@@ -0,0 +1,294 @@
1
+ import Dropdown from './index'
2
+ import Button from '../../button'
3
+ import Dialog from '../dialog'
4
+ import { Bell, ChevronDown, Clock, Copy, Ellipsis, Lock, LogOut, Mail, Monitor, Palette, Pencil, Settings, Shield, SquareLibrary, Star, Trash2, User, Users } from 'lucide-react'
5
+
6
+ # Basic
7
+
8
+ The inline panel, running its own selection: `defaultCheckedIndex` makes
9
+ the rows radio options and the panel moves the marker itself.
10
+
11
+ <Dropdown defaultCheckedIndex={0}>
12
+ <Dropdown.Item index={0} icon={SquareLibrary} label="Teamspaces" />
13
+ <Dropdown.Item index={1} icon={Clock} label="Recents" />
14
+ <Dropdown.Item index={2} icon={Star} label="Favorites" />
15
+ <Dropdown.Item index={3} icon={Users} label="Shared" />
16
+ <Dropdown.Item index={4} icon={Lock} label="Private" />
17
+ </Dropdown>
18
+
19
+ # Groups
20
+
21
+ Without any checked state the rows are plain actions. Dropdown.Label
22
+ captions a run of them, Dropdown.Separator rules between the runs, and the
23
+ indices keep counting across both.
24
+
25
+ <Dropdown>
26
+ <Dropdown.Label>Account</Dropdown.Label>
27
+ <Dropdown.Item index={0} icon={Mail} label="Email" />
28
+ <Dropdown.Item index={1} icon={Bell} label="Notifications" />
29
+ <Dropdown.Item index={2} icon={Shield} label="Privacy" />
30
+ <Dropdown.Separator />
31
+ <Dropdown.Label>Appearance</Dropdown.Label>
32
+ <Dropdown.Item index={3} icon={Settings} label="General" />
33
+ <Dropdown.Item index={4} icon={Palette} label="Theme" />
34
+ <Dropdown.Item index={5} icon={Monitor} label="Display" />
35
+ </Dropdown>
36
+
37
+ # Triggered menu
38
+
39
+ 3 parts: Dropdown.Menu, Dropdown.Trigger, Dropdown.Content. Any element
40
+ goes in `render`, and picking a row closes the menu.
41
+
42
+ <Dropdown.Menu>
43
+ <Dropdown.Trigger render={<Button variant="ghost">Open menu</Button>} />
44
+ <Dropdown.Content defaultCheckedIndex={0}>
45
+ <Dropdown.Item index={0} icon={SquareLibrary} label="Teamspaces" />
46
+ <Dropdown.Item index={1} icon={Clock} label="Recents" />
47
+ <Dropdown.Item index={2} icon={Star} label="Favorites" />
48
+ <Dropdown.Item index={3} icon={Users} label="Shared" />
49
+ <Dropdown.Item index={4} icon={Lock} label="Private" />
50
+ </Dropdown.Content>
51
+ </Dropdown.Menu>
52
+
53
+ # Morph
54
+
55
+ The menu oozes out of its trigger by default. `effect="morph"` with `hideSource` turns the trigger itself into the menu and shrinks it back, one continuous surface.
56
+
57
+ <div className="flex flex-wrap items-start justify-center gap-3">
58
+ <Dropdown.Menu>
59
+ <Dropdown.Trigger render={<Button variant="secondary">Goo</Button>} />
60
+ <Dropdown.Content defaultCheckedIndex={0}>
61
+ <Dropdown.Item index={0} icon={Mail} label="Email" />
62
+ <Dropdown.Item index={1} icon={Bell} label="Notifications" />
63
+ <Dropdown.Item index={2} icon={Shield} label="Privacy" />
64
+ </Dropdown.Content>
65
+ </Dropdown.Menu>
66
+ <Dropdown.Menu>
67
+ <Dropdown.Trigger render={<Button variant="secondary">Morph</Button>} />
68
+ <Dropdown.Content effect="morph" hideSource defaultCheckedIndex={0}>
69
+ <Dropdown.Item index={0} icon={Settings} label="General" />
70
+ <Dropdown.Item index={1} icon={Palette} label="Theme" />
71
+ <Dropdown.Item index={2} icon={Monitor} label="Display" />
72
+ </Dropdown.Content>
73
+ </Dropdown.Menu>
74
+ </div>
75
+
76
+ # Multiple selection
77
+
78
+ `defaultCheckedIndices` (or `checkedIndices`): rows become checkboxes, the
79
+ menu stays open as you toggle, and touching picks share one background.
80
+
81
+ <Dropdown.Menu>
82
+ <Dropdown.Trigger render={<Button variant="ghost">Status</Button>} />
83
+ <Dropdown.Content defaultCheckedIndices={[0, 1, 2]}>
84
+ {['Open', 'In progress', 'In review', 'Blocked', 'Done', 'Archived'].map((status, i) => (
85
+ <Dropdown.Item key={status} index={i} label={status} />
86
+ ))}
87
+ </Dropdown.Content>
88
+ </Dropdown.Menu>
89
+
90
+ # Searchable menu
91
+
92
+ Put a Dropdown.Search marked `filter` first and the panel filters its own
93
+ rows by `label`. Type to filter, press Enter to pick the first match.
94
+
95
+ <Dropdown.Menu>
96
+ <Dropdown.Trigger render={<Button variant="ghost" trailingIcon={ChevronDown}>Language</Button>} />
97
+ <Dropdown.Content defaultCheckedIndex={0}>
98
+ <Dropdown.Search filter placeholder="Search languages" />
99
+ {['Auto detect', 'Arabic', 'Bengali', 'Chinese (Simplified)', 'Czech', 'Danish', 'Dutch', 'English (UK)', 'English (US)', 'Finnish', 'French', 'German', 'Greek', 'Hebrew', 'Hindi', 'Hungarian', 'Indonesian', 'Italian', 'Japanese', 'Korean', 'Norwegian', 'Polish', 'Portuguese (Brazil)', 'Romanian', 'Russian', 'Spanish', 'Swedish', 'Thai', 'Turkish', 'Ukrainian', 'Vietnamese'].map((language, i) => (
100
+ <Dropdown.Item key={language} index={i} label={language} />
101
+ ))}
102
+ <Dropdown.Empty>No languages found</Dropdown.Empty>
103
+ </Dropdown.Content>
104
+ </Dropdown.Menu>
105
+
106
+ # Searchable multiple selection
107
+
108
+ Both at once. The panel filters the rows and keeps the picks on the rows
109
+ themselves, so a filtered-away pick is still checked when it comes back.
110
+
111
+ <Dropdown.Menu>
112
+ <Dropdown.Trigger render={<Button variant="ghost">Labels</Button>} />
113
+ <Dropdown.Content defaultCheckedIndices={[0, 3]}>
114
+ <Dropdown.Search filter placeholder="Search labels" />
115
+ {['Bug', 'Feature', 'Docs', 'Design', 'Performance', 'Accessibility', 'Refactor', 'Testing', 'Infrastructure', 'Security', 'Good first issue', 'Help wanted'].map((label, i) => (
116
+ <Dropdown.Item key={label} index={i} label={label} />
117
+ ))}
118
+ <Dropdown.Empty>No labels found</Dropdown.Empty>
119
+ </Dropdown.Content>
120
+ </Dropdown.Menu>
121
+
122
+ # Children items
123
+
124
+ Rows written shadcn-style: the content as children and no `index` — each row
125
+ takes its place in document order, conditional rows included. `shortcut`
126
+ adds a muted caption at the end, `inset` lines a text-only row up with the
127
+ rows that have an icon, and `onSelect` runs on a pick by pointer or keyboard.
128
+
129
+ <Dropdown.Menu>
130
+ <Dropdown.Trigger asChild>
131
+ <Button variant="ghost" size="icon" aria-label="Row actions"><Ellipsis /></Button>
132
+ </Dropdown.Trigger>
133
+ <Dropdown.Content align="end">
134
+ <Dropdown.Item icon={Pencil} shortcut="⌘E">Edit</Dropdown.Item>
135
+ <Dropdown.Item icon={Copy} shortcut="⌘D">Duplicate</Dropdown.Item>
136
+ <Dropdown.Item inset>Move to…</Dropdown.Item>
137
+ <Dropdown.Item disabled inset>Archive</Dropdown.Item>
138
+ <Dropdown.Separator />
139
+ <Dropdown.Item icon={Trash2} shortcut="⌫">Delete</Dropdown.Item>
140
+ </Dropdown.Content>
141
+ </Dropdown.Menu>
142
+
143
+ # Groups and labels
144
+
145
+ `Dropdown.Group` holds a run of related rows; a `Dropdown.Label` inside it
146
+ names the group for assistive tech. Labels and separators break the row
147
+ order, so nothing selected melts across them.
148
+
149
+ <Dropdown.Menu>
150
+ <Dropdown.Trigger render={<Button variant="secondary" leadingIcon={User}>Account</Button>} />
151
+ <Dropdown.Content>
152
+ <Dropdown.Group>
153
+ <Dropdown.Label>My account</Dropdown.Label>
154
+ <Dropdown.Item icon={User}>Profile</Dropdown.Item>
155
+ <Dropdown.Item icon={Settings}>Settings</Dropdown.Item>
156
+ </Dropdown.Group>
157
+ <Dropdown.Separator />
158
+ <Dropdown.Group>
159
+ <Dropdown.Label inset>Team</Dropdown.Label>
160
+ <Dropdown.Item inset>Invite members</Dropdown.Item>
161
+ <Dropdown.Item inset>New team</Dropdown.Item>
162
+ </Dropdown.Group>
163
+ <Dropdown.Separator />
164
+ <Dropdown.Item icon={LogOut}>Log out</Dropdown.Item>
165
+ </Dropdown.Content>
166
+ </Dropdown.Menu>
167
+
168
+ # Shortcut
169
+
170
+ `Dropdown.Shortcut` is the trailing caption on its own, for rows that lay
171
+ out their own content.
172
+
173
+ <Dropdown className="w-64">
174
+ <Dropdown.Item>
175
+ <Mail />
176
+ New message
177
+ <Dropdown.Shortcut>⌘N</Dropdown.Shortcut>
178
+ </Dropdown.Item>
179
+ <Dropdown.Item>
180
+ <Bell />
181
+ Mute thread
182
+ <Dropdown.Shortcut>⇧⌘M</Dropdown.Shortcut>
183
+ </Dropdown.Item>
184
+ </Dropdown>
185
+
186
+ # Submenu
187
+
188
+ A `Dropdown.Sub` holds a trigger row and its panel. ArrowRight, a press or a
189
+ resting pointer opens it, ArrowLeft or Escape closes it; it grows out of its
190
+ row, which stays lit while it is open, and nests as deep as you like.
191
+
192
+ <Dropdown.Menu>
193
+ <Dropdown.Trigger render={<Button variant="secondary">Options</Button>} />
194
+ <Dropdown.Content keepMounted>
195
+ <Dropdown.Item icon={User}>Profile</Dropdown.Item>
196
+ <Dropdown.Sub>
197
+ <Dropdown.SubTrigger icon={Palette}>Theme</Dropdown.SubTrigger>
198
+ <Dropdown.SubContent keepMounted>
199
+ <Dropdown.RadioGroup defaultValue="system">
200
+ <Dropdown.RadioItem value="light">Light</Dropdown.RadioItem>
201
+ <Dropdown.RadioItem value="dark">Dark</Dropdown.RadioItem>
202
+ <Dropdown.RadioItem value="system">System</Dropdown.RadioItem>
203
+ </Dropdown.RadioGroup>
204
+ </Dropdown.SubContent>
205
+ </Dropdown.Sub>
206
+ <Dropdown.Sub>
207
+ <Dropdown.SubTrigger icon={Users}>Invite users</Dropdown.SubTrigger>
208
+ <Dropdown.SubContent>
209
+ <Dropdown.Item icon={Mail}>Email</Dropdown.Item>
210
+ <Dropdown.Item icon={Copy}>Copy link</Dropdown.Item>
211
+ <Dropdown.Separator />
212
+ <Dropdown.Sub>
213
+ <Dropdown.SubTrigger inset>More</Dropdown.SubTrigger>
214
+ <Dropdown.SubContent>
215
+ <Dropdown.Item>Slack</Dropdown.Item>
216
+ <Dropdown.Item>Teams</Dropdown.Item>
217
+ </Dropdown.SubContent>
218
+ </Dropdown.Sub>
219
+ </Dropdown.SubContent>
220
+ </Dropdown.Sub>
221
+ <Dropdown.Separator />
222
+ <Dropdown.Item icon={LogOut}>Log out</Dropdown.Item>
223
+ </Dropdown.Content>
224
+ </Dropdown.Menu>
225
+
226
+ # Checkbox items
227
+
228
+ `Dropdown.CheckboxItem` toggles itself — `defaultChecked`, or `checked` with
229
+ `onCheckedChange` — and keeps the menu open. Touching checked rows share one
230
+ background, as in a multiple selection. Uncontrolled rows live inside the
231
+ popup, so `keepMounted` keeps their state across opens.
232
+
233
+ <Dropdown.Menu>
234
+ <Dropdown.Trigger render={<Button variant="secondary" trailingIcon={ChevronDown}>Columns</Button>} />
235
+ <Dropdown.Content align="end" keepMounted>
236
+ <Dropdown.Label>Visible columns</Dropdown.Label>
237
+ <Dropdown.CheckboxItem defaultChecked>Name</Dropdown.CheckboxItem>
238
+ <Dropdown.CheckboxItem defaultChecked>Email</Dropdown.CheckboxItem>
239
+ <Dropdown.CheckboxItem>Role</Dropdown.CheckboxItem>
240
+ <Dropdown.CheckboxItem defaultChecked>Status</Dropdown.CheckboxItem>
241
+ <Dropdown.CheckboxItem disabled>Created</Dropdown.CheckboxItem>
242
+ </Dropdown.Content>
243
+ </Dropdown.Menu>
244
+
245
+ # Radio group
246
+
247
+ `Dropdown.RadioGroup` picks one of its `Dropdown.RadioItem` rows by `value`
248
+ (`defaultValue`, or `value` with `onValueChange`). Picking one closes the
249
+ menu.
250
+
251
+ <Dropdown.Menu>
252
+ <Dropdown.Trigger render={<Button variant="secondary" trailingIcon={ChevronDown}>Language</Button>} />
253
+ <Dropdown.Content keepMounted>
254
+ <Dropdown.RadioGroup defaultValue="sk">
255
+ <Dropdown.Label>Language</Dropdown.Label>
256
+ <Dropdown.RadioItem value="sk">Slovenčina</Dropdown.RadioItem>
257
+ <Dropdown.RadioItem value="cs">Čeština</Dropdown.RadioItem>
258
+ <Dropdown.RadioItem value="en">English</Dropdown.RadioItem>
259
+ <Dropdown.RadioItem value="de" disabled>Deutsch</Dropdown.RadioItem>
260
+ </Dropdown.RadioGroup>
261
+ </Dropdown.Content>
262
+ </Dropdown.Menu>
263
+
264
+ # Inside a dialog
265
+
266
+ A menu lifts 2 surface levels above whatever it opens over — a dialog
267
+ included — and so does its submenu above the menu.
268
+
269
+ <Dialog>
270
+ <Dialog.Trigger render={<Button variant="tertiary">Open dialog</Button>} />
271
+ <Dialog.Content>
272
+ <Dialog.Header>
273
+ <Dialog.Title>Share document</Dialog.Title>
274
+ <Dialog.Description>Pick who can see it.</Dialog.Description>
275
+ </Dialog.Header>
276
+ <Dropdown.Menu>
277
+ <Dropdown.Trigger render={<Button variant="secondary" trailingIcon={ChevronDown}>Access</Button>} />
278
+ <Dropdown.Content keepMounted>
279
+ <Dropdown.RadioGroup defaultValue="team">
280
+ <Dropdown.RadioItem value="private" icon={Lock}>Only me</Dropdown.RadioItem>
281
+ <Dropdown.RadioItem value="team" icon={Users}>My team</Dropdown.RadioItem>
282
+ </Dropdown.RadioGroup>
283
+ <Dropdown.Separator />
284
+ <Dropdown.Sub>
285
+ <Dropdown.SubTrigger icon={Shield}>Advanced</Dropdown.SubTrigger>
286
+ <Dropdown.SubContent keepMounted>
287
+ <Dropdown.CheckboxItem defaultChecked>Allow comments</Dropdown.CheckboxItem>
288
+ <Dropdown.CheckboxItem>Allow downloads</Dropdown.CheckboxItem>
289
+ </Dropdown.SubContent>
290
+ </Dropdown.Sub>
291
+ </Dropdown.Content>
292
+ </Dropdown.Menu>
293
+ </Dialog.Content>
294
+ </Dialog>