@justanarthur/modo-ui 0.5.0 → 0.6.0-canary.1

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 (467) hide show
  1. package/DESIGN.md +608 -0
  2. package/README.md +66 -44
  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 +1734 -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 +68 -0
  87. package/components/navigation/pagination/index.tsx +401 -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 +276 -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 +425 -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 +165 -0
  106. package/components/overlays/dialog/index.tsx +451 -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 +1169 -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 +197 -0
  114. package/components/overlays/popover/index.tsx +317 -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 +101 -0
  118. package/components/overlays/sheet/index.tsx +307 -0
  119. package/components/overlays/toast/examples.mdx +85 -0
  120. package/components/overlays/toast/global-demo.tsx +72 -0
  121. package/components/overlays/toast/index.tsx +544 -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 +539 -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 +525 -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.d.ts +3 -3
  184. package/dist/components/inputs/search/index.js +25 -18
  185. package/dist/components/inputs/select/index.d.ts +41 -11
  186. package/dist/components/inputs/select/index.js +27 -14
  187. package/dist/components/inputs/select/variants.d.ts +5 -0
  188. package/dist/components/inputs/select/variants.js +6 -0
  189. package/dist/components/inputs/slider/index.d.ts +2 -0
  190. package/dist/components/inputs/slider/index.js +7 -5
  191. package/dist/components/inputs/switch/index.d.ts +8 -2
  192. package/dist/components/inputs/switch/index.js +14 -12
  193. package/dist/components/inputs/textarea/index.d.ts +4 -0
  194. package/dist/components/inputs/textarea/index.js +8 -6
  195. package/dist/components/navigation/navigation-menu/index.d.ts +114 -0
  196. package/dist/components/navigation/navigation-menu/index.js +40 -0
  197. package/dist/components/navigation/pagination/index.d.ts +75 -0
  198. package/dist/components/navigation/pagination/index.js +39 -0
  199. package/dist/components/navigation/sidebar/index.d.ts +44 -21
  200. package/dist/components/navigation/sidebar/index.js +41 -31
  201. package/dist/components/navigation/sidebar/variants.d.ts +25 -0
  202. package/dist/components/navigation/sidebar/variants.js +28 -0
  203. package/dist/components/navigation/tabs/index.d.ts +8 -2
  204. package/dist/components/navigation/tabs/index.js +14 -10
  205. package/dist/components/navigation/tabs-subtle/index.js +12 -10
  206. package/dist/components/overlays/alert-dialog/index.d.ts +135 -16
  207. package/dist/components/overlays/alert-dialog/index.js +22 -13
  208. package/dist/components/overlays/command/index.d.ts +25 -20
  209. package/dist/components/overlays/command/index.js +24 -17
  210. package/dist/components/overlays/context-menu/index.d.ts +33 -17
  211. package/dist/components/overlays/context-menu/index.js +27 -18
  212. package/dist/components/overlays/dialog/index.d.ts +42 -16
  213. package/dist/components/overlays/dialog/index.js +19 -12
  214. package/dist/components/overlays/dropdown/index.d.ts +256 -30
  215. package/dist/components/overlays/dropdown/index.js +42 -17
  216. package/dist/components/overlays/popover/index.d.ts +38 -12
  217. package/dist/components/overlays/popover/index.js +12 -9
  218. package/dist/components/overlays/preview-card/index.js +11 -8
  219. package/dist/components/overlays/sheet/index.d.ts +25 -8
  220. package/dist/components/overlays/sheet/index.js +18 -13
  221. package/dist/components/overlays/toast/index.d.ts +209 -50
  222. package/dist/components/overlays/toast/index.js +22 -10
  223. package/dist/components/overlays/tooltip/index.d.ts +123 -21
  224. package/dist/components/overlays/tooltip/index.js +15 -6
  225. package/dist/components/progress/index.d.ts +56 -0
  226. package/dist/components/progress/index.js +15 -0
  227. package/dist/components/table/index.d.ts +63 -8
  228. package/dist/components/table/index.js +20 -9
  229. package/dist/index.d.ts +2380 -831
  230. package/dist/index.js +229 -82
  231. package/dist/lib/date-locale.d.ts +14 -0
  232. package/dist/lib/date-locale.js +16 -0
  233. package/dist/lib/field-classes.d.ts +144 -0
  234. package/dist/lib/field-classes.js +40 -0
  235. package/dist/lib/field-variants.d.ts +47 -0
  236. package/dist/lib/field-variants.js +26 -0
  237. package/dist/lib/fluid-hover-highlight.d.ts +112 -0
  238. package/dist/lib/fluid-hover-highlight.js +17 -0
  239. package/dist/lib/focus-ring.d.ts +18 -0
  240. package/dist/lib/focus-ring.js +10 -0
  241. package/dist/lib/goo-indicator.d.ts +46 -0
  242. package/dist/lib/goo-indicator.js +14 -0
  243. package/dist/lib/icon-context.d.ts +30 -0
  244. package/dist/lib/icon-context.js +12 -0
  245. package/dist/lib/inert.d.ts +9 -0
  246. package/dist/lib/inert.js +8 -0
  247. package/dist/lib/morph-layers.d.ts +93 -0
  248. package/dist/lib/morph-layers.js +13 -0
  249. package/dist/lib/morph-part.d.ts +21 -0
  250. package/dist/lib/morph-part.js +12 -0
  251. package/dist/lib/popup.d.ts +20 -0
  252. package/dist/lib/popup.js +12 -0
  253. package/dist/lib/reduced-motion.d.ts +9 -0
  254. package/dist/lib/reduced-motion.js +6 -0
  255. package/dist/lib/row-index.d.ts +10 -0
  256. package/dist/lib/row-index.js +8 -0
  257. package/dist/lib/row-order.d.ts +28 -0
  258. package/dist/lib/row-order.js +10 -0
  259. package/dist/lib/scroll-column.d.ts +27 -0
  260. package/dist/lib/scroll-column.js +18 -0
  261. package/dist/lib/shape-context.d.ts +30 -0
  262. package/dist/lib/shape-context.js +12 -0
  263. package/dist/lib/size-context.d.ts +94 -0
  264. package/dist/lib/size-context.js +20 -0
  265. package/dist/lib/size-scale.d.ts +79 -0
  266. package/dist/lib/size-scale.js +8 -0
  267. package/dist/lib/slot.d.ts +19 -0
  268. package/dist/lib/slot.js +6 -0
  269. package/dist/lib/springs.d.ts +44 -0
  270. package/dist/lib/springs.js +8 -0
  271. package/dist/lib/surface-classes.d.ts +19 -0
  272. package/dist/lib/surface-classes.js +10 -0
  273. package/dist/lib/surface-context.d.ts +9 -0
  274. package/dist/lib/surface-context.js +8 -0
  275. package/dist/lib/use-controllable-state.d.ts +2 -0
  276. package/dist/lib/use-controllable-state.js +6 -0
  277. package/dist/lib/use-fluid-hover.d.ts +138 -0
  278. package/dist/lib/use-fluid-hover.js +14 -0
  279. package/dist/lib/use-is-mobile.d.ts +2 -0
  280. package/dist/lib/use-is-mobile.js +6 -0
  281. package/dist/lib/use-keyboard-nav-gate.d.ts +20 -0
  282. package/dist/lib/use-keyboard-nav-gate.js +7 -0
  283. package/dist/lib/use-merge-split.d.ts +47 -0
  284. package/dist/lib/use-merge-split.js +15 -0
  285. package/dist/lib/use-morph.d.ts +101 -0
  286. package/dist/lib/use-morph.js +20 -0
  287. package/dist/lib/use-touch-primary.d.ts +2 -0
  288. package/dist/lib/use-touch-primary.js +6 -0
  289. package/dist/lib/utils.d.ts +3 -0
  290. package/dist/lib/utils.js +6 -0
  291. package/dist/preset.d.ts +17 -0
  292. package/dist/preset.js +22 -2
  293. package/dist/primitives/code/index.js +7 -5
  294. package/dist/primitives/craft/index.js +9 -6
  295. package/dist/primitives/fluid-hover/index.js +9 -8
  296. package/dist/primitives/morph/index.js +15 -10
  297. package/dist/primitives/motion/index.js +10 -8
  298. package/dist/primitives/scroll-area/index.js +4 -3
  299. package/dist/primitives/separator/index.d.ts +25 -0
  300. package/dist/primitives/separator/index.js +9 -0
  301. package/dist/primitives/sizes/index.js +6 -4
  302. package/dist/primitives/skeleton/index.d.ts +35 -0
  303. package/dist/primitives/skeleton/index.js +12 -0
  304. package/dist/primitives/surface/index.js +6 -4
  305. package/dist/shared/{components-3mkepka5.js → components-0jmqhx2k.js} +28 -25
  306. package/dist/shared/{components-s5ngg32c.js → components-0khyhjn5.js} +121 -47
  307. package/dist/shared/{components-sn1rxx1d.js → components-0n95jaa2.js} +12 -11
  308. package/dist/shared/components-0x6mx1p5.js +252 -0
  309. package/dist/shared/components-124epb7f.js +53 -0
  310. package/dist/shared/{components-gz8zf3ec.js → components-1k0wv53d.js} +15 -43
  311. package/dist/shared/components-26snnbs9.js +322 -0
  312. package/dist/shared/{components-2dxkv6zm.js → components-2c0qcfx4.js} +8 -7
  313. package/dist/shared/{components-y9y0g8er.js → components-2gk4yywm.js} +23 -20
  314. package/dist/shared/{components-473g814w.js → components-2mt9te6d.js} +243 -137
  315. package/dist/shared/{components-hawvy2qz.js → components-2z48g4fp.js} +9 -30
  316. package/dist/shared/components-34txk9rj.js +35 -0
  317. package/dist/shared/components-3h92d94m.js +23 -0
  318. package/dist/shared/{components-s2bz1xpk.js → components-49bsynbw.js} +8 -5
  319. package/dist/shared/components-4bwsd46w.js +71 -0
  320. package/dist/shared/{components-0q0sqfh4.js → components-4y816hwp.js} +27 -49
  321. package/dist/shared/components-52ngsgca.js +399 -0
  322. package/dist/shared/components-559tf7yb.js +28 -0
  323. package/dist/shared/components-591m0a57.js +500 -0
  324. package/dist/shared/components-5mkga9hj.js +440 -0
  325. package/dist/shared/{components-zfnmn5d4.js → components-5nsn4w8c.js} +79 -35
  326. package/dist/shared/{components-kwa6mrf9.js → components-5s0nfqvw.js} +14 -9
  327. package/dist/shared/{components-39vmfq9s.js → components-5t2f1kq7.js} +24 -22
  328. package/dist/shared/{components-dk0gjr5a.js → components-60atskhp.js} +19 -18
  329. package/dist/shared/components-6zng858q.js +53 -0
  330. package/dist/shared/components-72w4ge68.js +64 -0
  331. package/dist/shared/{components-cv16mryv.js → components-78x40gee.js} +13 -7
  332. package/dist/shared/components-79q3xxwr.js +179 -0
  333. package/dist/shared/{components-b2wyq2cq.js → components-7d4r7hh1.js} +39 -57
  334. package/dist/shared/components-7ntvvegr.js +316 -0
  335. package/dist/shared/{components-5vgpnxgy.js → components-7xrqvk6g.js} +20 -17
  336. package/dist/shared/components-8nwsqjn8.js +34 -0
  337. package/dist/shared/{components-5xc2j8jz.js → components-8tt3xbb5.js} +19 -18
  338. package/dist/shared/{components-vc7vq5nz.js → components-9as3rf68.js} +6 -3
  339. package/dist/shared/components-adkarj8n.js +35 -0
  340. package/dist/shared/{components-b3c5et5b.js → components-ae9fncz4.js} +32 -31
  341. package/dist/shared/{components-jthe1pwb.js → components-ajhdkhjz.js} +101 -404
  342. package/dist/shared/{components-hqeq3w9q.js → components-awra5sff.js} +24 -16
  343. package/dist/shared/components-b1e239g3.js +28 -0
  344. package/dist/shared/{components-q5xj5c16.js → components-b1f2bjf1.js} +9 -8
  345. package/dist/shared/components-c9yr4ed1.js +44 -0
  346. package/dist/shared/components-d3shbwqq.js +95 -0
  347. package/dist/shared/{components-wvp1m52g.js → components-d6vf38m5.js} +22 -18
  348. package/dist/shared/{components-jjw69c4c.js → components-dcc02hs2.js} +7 -4
  349. package/dist/shared/{components-men63rwj.js → components-de99ykt7.js} +3 -2
  350. package/dist/shared/components-dhgk6mbt.js +16 -0
  351. package/dist/shared/components-drxrtnp2.js +25 -0
  352. package/dist/shared/{components-mjthm1kf.js → components-egy4tyjt.js} +11 -8
  353. package/dist/shared/components-f0xfpkhh.js +82 -0
  354. package/dist/shared/components-f7h57m6t.js +59 -0
  355. package/dist/shared/{components-frw34b65.js → components-faww1hy7.js} +114 -123
  356. package/dist/shared/components-gjd5v54y.js +271 -0
  357. package/dist/shared/{components-39jq9va1.js → components-gvbapfen.js} +29 -24
  358. package/dist/shared/components-h6p86be3.js +19 -0
  359. package/dist/shared/components-h83az0bz.js +33 -0
  360. package/dist/shared/{components-ar66gnt0.js → components-h9vrr1j2.js} +37 -34
  361. package/dist/shared/components-hbk69y68.js +298 -0
  362. package/dist/shared/{components-rr7a6ja9.js → components-jamcmdsd.js} +59 -27
  363. package/dist/shared/{components-zrkzcayz.js → components-jqpefjsn.js} +54 -6
  364. package/dist/shared/{components-xxvdw7k1.js → components-k1b0vhcq.js} +37 -34
  365. package/dist/shared/{components-1z6tdadk.js → components-k74j4hwh.js} +20 -17
  366. package/dist/shared/components-kswxnm3h.js +24 -0
  367. package/dist/shared/components-m1m46jdv.js +1444 -0
  368. package/dist/shared/{components-5pnw8183.js → components-mjnzv5vj.js} +23 -22
  369. package/dist/shared/components-mkezgkgk.js +10 -0
  370. package/dist/shared/{components-jbbmdr74.js → components-mkkbbgb7.js} +36 -17
  371. package/dist/shared/{components-pbmqctfa.js → components-mpw667rb.js} +169 -65
  372. package/dist/shared/{components-6p8tfcd6.js → components-my6hpnn6.js} +22 -19
  373. package/dist/shared/{components-s083f38z.js → components-nbvtmgme.js} +22 -21
  374. package/dist/shared/{components-jwst3vtj.js → components-ncdgtg6b.js} +224 -76
  375. package/dist/shared/{components-vfbhxf5r.js → components-pd42emw3.js} +6 -5
  376. package/dist/shared/{components-hw9yk32z.js → components-pmfszv7a.js} +35 -23
  377. package/dist/shared/{components-chacnk61.js → components-pw06rbxq.js} +19 -19
  378. package/dist/shared/{components-qmmjx1qw.js → components-q3pbax3p.js} +71 -72
  379. package/dist/shared/components-q5h7v6rr.js +104 -0
  380. package/dist/shared/{components-kvr7f9hy.js → components-qdm3yh26.js} +19 -10
  381. package/dist/shared/{components-27nnvrwe.js → components-qs4rkqsj.js} +15 -14
  382. package/dist/shared/{components-ta7shw28.js → components-r8eqs7gz.js} +4 -4
  383. package/dist/shared/{components-c6s6b43g.js → components-rrbpmfx4.js} +3 -2
  384. package/dist/shared/{components-d6xtjht3.js → components-tarz14gk.js} +33 -30
  385. package/dist/shared/{components-n7mjr357.js → components-tz1fv35x.js} +3 -2
  386. package/dist/shared/components-tz4gaf1v.js +23 -0
  387. package/dist/shared/{components-zx6b6yby.js → components-vf45j12c.js} +4 -18
  388. package/dist/shared/{components-gc5vtq2f.js → components-vqpzqwcg.js} +63 -29
  389. package/dist/shared/components-wktrrb4m.js +38 -0
  390. package/dist/shared/{components-8snj490h.js → components-wwd363me.js} +78 -32
  391. package/dist/shared/components-xdqmt6ed.js +20 -0
  392. package/dist/shared/{components-h2ajmhwn.js → components-y1w3a6wx.js} +107 -35
  393. package/dist/shared/{components-59efxjd9.js → components-yhgq0qxx.js} +13 -12
  394. package/dist/shared/components-z3v0n0de.js +11 -0
  395. package/dist/shared/{components-7r3etddy.js → components-z9n19fyk.js} +19 -54
  396. package/dist/shared/components-zbvjzh1p.js +141 -0
  397. package/dist/styles.css +241 -16
  398. package/dist/tailwind.css +13 -0
  399. package/dist/tokens.css +34 -0
  400. package/index.ts +60 -0
  401. package/lib/date-locale.ts +50 -0
  402. package/lib/field-classes.ts +115 -0
  403. package/lib/field-variants.ts +117 -0
  404. package/lib/fluid-hover-highlight.tsx +139 -0
  405. package/lib/focus-ring.tsx +46 -0
  406. package/lib/goo-indicator.tsx +261 -0
  407. package/lib/icon-context.tsx +310 -0
  408. package/lib/inert.ts +22 -0
  409. package/lib/morph-layers.tsx +102 -0
  410. package/lib/morph-part.tsx +49 -0
  411. package/lib/popup.ts +52 -0
  412. package/lib/reduced-motion.ts +19 -0
  413. package/lib/row-index.tsx +29 -0
  414. package/lib/row-order.tsx +115 -0
  415. package/lib/scroll-column.tsx +244 -0
  416. package/lib/shape-context.tsx +124 -0
  417. package/lib/size-context.tsx +66 -0
  418. package/lib/size-scale.ts +115 -0
  419. package/lib/slot.ts +35 -0
  420. package/lib/springs.ts +62 -0
  421. package/lib/surface-classes.ts +58 -0
  422. package/lib/surface-context.tsx +21 -0
  423. package/lib/use-controllable-state.ts +44 -0
  424. package/lib/use-fluid-hover.ts +541 -0
  425. package/lib/use-is-mobile.ts +25 -0
  426. package/lib/use-keyboard-nav-gate.ts +45 -0
  427. package/lib/use-merge-split.tsx +209 -0
  428. package/lib/use-morph.ts +557 -0
  429. package/lib/use-touch-primary.tsx +43 -0
  430. package/lib/utils.ts +37 -0
  431. package/package.json +177 -4
  432. package/preset.ts +196 -0
  433. package/primitives/code/examples.mdx +27 -0
  434. package/primitives/code/index.tsx +80 -0
  435. package/primitives/craft/demos.tsx +173 -0
  436. package/primitives/craft/examples.mdx +138 -0
  437. package/primitives/craft/index.tsx +76 -0
  438. package/primitives/fluid-hover/examples.mdx +84 -0
  439. package/primitives/fluid-hover/index.tsx +316 -0
  440. package/primitives/morph/examples.mdx +135 -0
  441. package/primitives/morph/index.tsx +192 -0
  442. package/primitives/motion/examples.mdx +100 -0
  443. package/primitives/motion/index.tsx +160 -0
  444. package/primitives/scroll-area/examples.mdx +58 -0
  445. package/primitives/scroll-area/index.tsx +200 -0
  446. package/primitives/separator/examples.mdx +30 -0
  447. package/primitives/separator/index.tsx +53 -0
  448. package/primitives/sizes/examples.mdx +167 -0
  449. package/primitives/sizes/index.tsx +89 -0
  450. package/primitives/skeleton/examples.mdx +51 -0
  451. package/primitives/skeleton/index.tsx +72 -0
  452. package/primitives/skeleton/skeleton.css +15 -0
  453. package/primitives/surface/examples.mdx +173 -0
  454. package/primitives/surface/index.tsx +109 -0
  455. package/primitives/surface/surface.css +176 -0
  456. package/dist/shared/components-0sn7ypjw.js +0 -61
  457. package/dist/shared/components-1434p1mh.js +0 -123
  458. package/dist/shared/components-3ky44908.js +0 -9
  459. package/dist/shared/components-a86dytt0.js +0 -986
  460. package/dist/shared/components-gf5s6ap9.js +0 -42
  461. package/dist/shared/components-hyq23phv.js +0 -310
  462. package/dist/shared/components-ng72k97e.js +0 -128
  463. package/dist/shared/components-raknhppb.js +0 -64
  464. package/dist/shared/components-vynjtrk6.js +0 -19
  465. package/dist/shared/components-wy9jpqny.js +0 -146
  466. /package/dist/shared/{components-1v7rxytx.js → components-3ey0hzpb.js} +0 -0
  467. /package/dist/shared/{components-ezxhrmer.js → components-jcnjay5g.js} +0 -0
package/README.md CHANGED
@@ -139,6 +139,7 @@ positioning) are ported.
139
139
  | `preset.ts` | The UnoCSS preset (theme and token utilities), shipped as `@justanarthur/modo-ui/unocss`. |
140
140
  | `uno.config.ts`, `vite.ts` | The UnoCSS setup of the docs site and the prebuilt CSS (see below). |
141
141
  | `index.ts`, `bunup.config.ts`, `scripts/build-css.ts` | The package build: `bun run build` writes the components, their types and the stylesheets to `dist/`. The docs site builds with `bun run build:docs` into `docs-dist/`. |
142
+ | `scripts/exports.ts`, `scripts/use-client.ts`, `scripts/check-singletons.ts` | Packaging around that build. `exports.ts` (first step of `bun run build`) writes one `./lib/<name>` export per `lib/` module into `package.json`, plus `./components/*/variants` and `./blocks/*/variants` for the items' server-callable `variants.ts` entries; commit the `package.json` it leaves. `use-client.ts` (after bunup) puts `"use client"` on the emitted modules that render, call a hook or create a context, or use a binding from one; pure helpers (`cn`, class tables, springs, `variants.ts`) and re-export entries stay directive-free so a React Server Component can call them (`--list` prints the split). `check-singletons.ts` (`bun run check:dist`) checks that every React context and the `toast()` manager exist once in `dist/`, and that every entry has its `.d.ts`. |
142
143
  | `lib/` | FF's shared system files (upstream `lib/`, `hooks/` and the fluid-hover highlight, flat, upstream names), imported relatively by items. modo only scans the tier folders, so it never mistakes them for items. |
143
144
  | `modo.components.tsx` | Components only the docs site uses: the `Icon`, `Select` and `DocsNav` shell-slot adapters and the `ThemeSwitcher` panel item, picked in `modo.config.ts` by export name (`./modo.components.tsx#Select`). |
144
145
  | `primitives/<id>/` | FF's "System" pages: `index.tsx` (component + TSDoc prose) and `examples.mdx`, pulled in by `@example {@include ./examples.mdx}`. |
@@ -156,7 +157,8 @@ positioning) are ported.
156
157
  | `lib/font-weight.ts` | — | not ported: its `fontWeights` became the `--weight-*` tokens in `tokens/typography.css` (the `weight-*` utilities) |
157
158
  | `lib/popup.ts` | `lib/popup.ts` | `popupMotionClass` dropped (unused since the popups morph) |
158
159
  | `lib/shape-context.tsx` | `lib/shape-context.tsx` | `"use client"` dropped |
159
- | `lib/size-context.tsx` | `lib/size-context.tsx` | `"use client"` dropped; `SizeProvider` moved to `primitives/sizes` (the documented item), with `SizeContext` exported for it; `sizeMap` text classes → `text-body` / `text-body-compact` |
160
+ | `lib/size-context.tsx` | `lib/size-context.tsx` | `"use client"` dropped; `SizeProvider` moved to `primitives/sizes` (the documented item), with `SizeContext` exported for it; `sizeMap`, `typeScale` and their types moved to `size-scale.ts` and re-exported |
161
+ | `lib/size-context.tsx` (`sizeMap`, `typeScale`) | `lib/size-scale.ts` | The ladder's plain data, split out so server code can read it (no JSX, hooks or contexts); `sizeMap` text classes → `text-body` / `text-body-compact` |
160
162
  | `lib/icon-context.tsx` | `lib/icon-context.tsx` | `"use client"` dropped; `size`/`strokeWidth` widened to `number \| string` for lucide v1 + React 18 types |
161
163
  | `lib/surface-context.tsx` | `lib/surface-context.tsx` | `"use client"` dropped |
162
164
  | `lib/surface-classes.ts` | `lib/surface-classes.ts` | `SURFACE_HOVER_*` and `surfaceHoverClasses` dropped (only Sidebar used them) |
@@ -169,10 +171,14 @@ positioning) are ported.
169
171
  | — (new) | `lib/use-controllable-state.ts` | Local addition: controlled + uncontrolled state, Base UI style |
170
172
  | — (new) | `lib/use-morph.ts` | Local addition: the morph engine (source rect → popup box on a spring tier, exit hold for Base UI's unmount), geometry after beUI's popover; goo runs on its own `spring.goo` with a blur of at least 8px that eases to nothing as the surface lands, so the neck pinches off; a close-only `exit` override, an `instant` predicate that ends a run at once, press points from clicks, context menus and long presses (a droplet source for `from="pointer"`), cleanup when the popup unmounts mid-run; focus goes back to the trigger when a closed popup that held it is gone and Base UI skipped the return (the exit hold makes a menu read the pointer leaving the inert popup as a hover close) |
171
173
  | — (new) | `lib/reduced-motion.ts` | Local addition: `useReduceMotion`, the one reduced-motion read for the morph layer and the liquid indicators — the OS setting, or a `MotionConfig` asking for it (motion's `useReducedMotionConfig` alone ignores the OS without `<MotionConfig reducedMotion="user">`) |
172
- | — (new) | `lib/field-classes.ts` | Local addition: the field ladder FF repeats per text field (rest → muted fill on hover or a group's `data-active` → card fill on focus; `data-invalid` keeps a destructive ring), as `fieldVariants` + `fieldIconClasses`, shared by Input, Textarea, Combobox and Sidebar's input; `useFieldBox` (variant, state attributes, radius, inset) + `focusControlOnPress`, the box Input, Textarea, Combobox's fields and the date/time pickers' field build on, so each takes `variant` |
174
+ | — (new) | `lib/field-classes.ts` | Local addition: the field ladder FF repeats per text field (rest → muted fill on hover or a group's `data-active` → card fill on focus; `data-invalid` keeps a destructive ring), as `fieldVariants` + `fieldIconClasses`, shared by Input, Textarea, Combobox and Sidebar's input; `useFieldBox` (variant, state attributes incl. `readOnly` / `required`, radius, inset) + `focusControlOnPress`, the box Input, Textarea, NumberInput, Combobox's fields and the date/time pickers' field build on, so each takes `variant`; `FieldStateContext`, which hands a `Field`'s state to its control; the pure class tables live in `field-variants.ts` and are re-exported here |
175
+ | — (new) | `lib/field-variants.ts` | Local addition: the field ladder's pure class tables (box, icon, adornment, action, label/description insets), split from `field-classes.ts` so a React Server Component can call them |
173
176
  | — (new) | `lib/date-locale.ts` | Local addition: what the date and time pickers read off a react-day-picker (date-fns) locale — the hour cycle (Intl), typed dates, times and months in the locale's formats (date-fns `parse` with `P`…`PPPP`), field names for column labels (`Intl.DisplayNames`) |
177
+ | — (new) | `lib/row-order.tsx` | Local addition: document-order row numbering for menus and lists (`useRowOrder`, `useRowSlot`, `RowOrderContext`), so a row's `index` is optional; unlike `row-index.tsx` (direct children) it reaches rows anywhere below the panel; labels, separators and groups are numbered without a rect, so a selection run never melts across them. Lifted out of Dropdown |
178
+ | — (new) | `lib/inert.ts` | Local addition: `inert` that holds on React 18 and 19 (18 passes it through as an unknown attribute and needs `''`, 19 reads a boolean and drops `''`): `inertProps(on)` to spread, `markInert` as a ref for an element that is always inert; Sidebar's folded and collapsed parts, Tabs' ghost label |
179
+ | — (new) | `lib/focus-ring.tsx` | Local addition: `FocusRing`, the one keyboard ring a list panel draws, 2px outside the focused row and springing between rows (a snap under reduced motion); radius from the surrounding shape, or pinned with `shape` (popups pin `rounded`); Dropdown and its submenus, NavigationMenu, Select |
174
180
  | — (new) | `lib/scroll-column.tsx` | Local addition: `ScrollColumn`, the scrolling option list beside a calendar (months, years, hours, minutes) or a strip above it: the pick gets a dot, a run of `inView` options one band, the followed option stays centred; fluid hover, listbox semantics, arrow keys; `dividedColumns` puts a hairline between neighbours |
175
- | — (new) | `lib/slot.ts` | Local addition: Dialog's `render` / `asChild` slot shape (`SlotProps`, `slotRender`), shared by every overlay part that wraps a control |
181
+ | — (new) | `lib/slot.ts` | Local addition: Dialog's `render` / `asChild` slot shape (`SlotProps`, `slotRender`), shared by every overlay part that wraps a control; `children` fill a `render` element that has none of its own (Base UI semantics), and `nativeButton` reaches the Base UI triggers and closes (`false` for a `<div>` or link) |
176
182
  | — (new) | `lib/morph-part.tsx` | Local addition: `MorphPart` (shared parts over a motion `layoutId`, after motion-primitives' morphing dialog) and the scope contexts overlays provide |
177
183
  | — (new) | `lib/goo-indicator.tsx` | Local addition: `GooIndicator` (melts between items, a lingering blob follows it) and `GooLayer` (goo over the shapes' coverage, filled with their own token, on only while moving), on the engine's `GOO_MATRIX` / `GOO_BLUR_RATIO` |
178
184
  | — (new) | `lib/morph-layers.tsx` | Local addition: `MorphSurface`, the renderer (shapes layer with beUI's goo filter plus a `--shadow-color` hairline around the melted shape, surface background and shadow, clipped content) |
@@ -201,10 +207,11 @@ only what is specific to it:
201
207
  `@/components/ui/fluid-hover-highlight` → `../../lib/fluid-hover-highlight`,
202
208
  `@/registry/radix/*` → the Base-flavor sibling, `framer-motion` →
203
209
  `motion/react`, `next/link` → a plain anchor.
204
- - A local `interface <Item>Props` whose members are each declared on one line
205
- with the FF docs API-table text (modo's parser lists only members declared
206
- in the interface body), modo TSDoc written from the FF docs page, and
207
- `export default <Item>`. Upstream's named exports are kept.
210
+ - A local `interface <Item>Props` whose members carry the FF docs API-table
211
+ text, modo TSDoc written from the FF docs page, and `export default <Item>`.
212
+ Upstream's named exports are kept. (Many members are re-declared in the
213
+ interface body from when modo listed only those; it now reads props through
214
+ the TypeScript compiler, inherited members included.)
208
215
 
209
216
  #### primitives
210
217
 
@@ -218,47 +225,59 @@ only what is specific to it:
218
225
  | `Morph` | — (local; see [Morph sources](#morph-sources)) | `primitives/morph/index.tsx` | a docs stage like `Motion`: a trigger and a panel playing one `from` × `effect` pair over `lib/use-morph.ts`; static `.Part` (= `MorphPart`); also documents the liquid indicators (`lib/goo-indicator.tsx`) |
219
226
  | `Code` | — (local; FF has no code item) | `primitives/code/index.tsx` | sugar-high tokens colored by the `--syntax-*` tokens, one surface step above its substrate; also the docs chrome's Code slot |
220
227
  | `Craft` | — (local; after the before/after pairs on fluidfunctionalism.com/docs/skill) | `primitives/craft/index.tsx` (+ `demos.tsx`) | a docs stage: a bad and a good version side by side with a one-line verdict each; the bad sides live in `demos.tsx` and break one rule each on purpose, the good sides are this package's items |
228
+ | `Separator` | — (local) | `primitives/separator/index.tsx` | Base UI Separator (role and orientation for assistive tech) on the `border` token; `orientation` |
229
+ | `Skeleton` | — (local) | `primitives/skeleton/index.tsx` (+ `skeleton.css`) | a `--skeleton` fill pulsing in opacity on `--duration-loop` (also under reduced motion); one block sized by its caller, or `rows` on the size ladder (`bordered` frames them); hidden from assistive tech |
221
230
 
222
231
  #### components
223
232
 
224
233
  | Item | Upstream | Local | Specific modifications |
225
234
  |---|---|---|---|
226
235
  | `Accordion` | `registry/base/accordion.tsx` | `components/accordion/index.tsx` | open state runs through `useControllableState` (see [Behavior additions](#behavior-additions)); standalone `Accordion` accepts `highlight` and passes it to its items as their default; the panel morphs out of its row (a shape grows from the press point into the item, content clipped by it, layout height on its bottom edge; the `"item"` tint is that shape melted into the row through `GooFilter`), `effect` / `tier` on `Accordion.Content`; the group's hover highlight and focus ring snap with reflowed rows; statics `.Group/.Item/.Trigger/.Content` |
227
- | `AlertDialog` | — (local) | `components/overlays/alert-dialog/index.tsx` | Base UI AlertDialog root (always modal, no outside-press dismissal) over Dialog's own parts and `DialogState`, so it morphs like Dialog; `AlertDialog.Content` drops the ✕ by default; statics as Dialog's |
228
- | `Badge` | `registry/default/badge.tsx` | `components/badge/index.tsx` | `variant` re-declared (it came from cva's `VariantProps` upstream); changes animate: a new label or variant springs the width (inner content span, `spring.moderate`) while the label blur-crossfades, a new color fades, the dot scales in and out; "Changes" example over a local `change-demo.tsx` stage |
229
- | `Button` | `registry/base/button.tsx` | `components/button/index.tsx` | `variant`/`disabled`/`children` re-declared; extra Sizes / Active / As-child / Loading morph examples; `loading` folds the surface into a circle around the spinner and back (`spring.moderate`, box unchanged), label and spinner crossfade, `aria-busy`, the disabled dim fades; "Loading morph" example over a local `loading-demo.tsx` stage; also serves modo's chrome Button (`shell.Button`) |
236
+ | `AlertDialog` | — (local) | `components/overlays/alert-dialog/index.tsx` (+ `pending-demo.tsx`) | Base UI AlertDialog root (always modal, no outside-press dismissal) over Dialog's own parts and `DialogState`, so it morphs like Dialog; `AlertDialog.Content` drops the ✕ by default; statics as Dialog's; `AlertDialog.Action` (promise-aware confirm: Button `loading`, every close blocked via `details.cancel()` while pending, closes on resolve, `onError` on reject; a controlled `pending` makes a plain `onClick` close once `pending` goes true and back to false) and `AlertDialog.Cancel` (Close as a ghost Button, disabled while pending); `pending-demo.tsx` is the docs stage; `onOpenChange(open, details)` with Base UI's change details (an action's own close reports `'imperative-action'`) |
237
+ | `Avatar` | — (local) | `components/avatar/index.tsx` | Base UI Avatar (image loading status, the fallback's `delay`); one control tall (36 / 28px compact), `shape` circle or square; a 1px inset image edge after FF's media outline (`outline-image-edge`); the image fades in on the fast tier; statics `.Image/.Fallback` |
238
+ | `Badge` | `registry/default/badge.tsx` | `components/badge/index.tsx` | `variant` re-declared (it came from cva's `VariantProps` upstream); changes animate: a new label or variant springs the width (inner content span, `spring.moderate`) while the label blur-crossfades, a new color fades, the dot scales in and out; "Changes" example over a local `change-demo.tsx` stage; `badgeColors`, `badgeVariants` and the size aliases moved to `variants.ts` (server-callable) and re-exported; local variant `outline` (the dot badge's neutral hairline without the dot; `color` has no effect) |
239
+ | `Button` | `registry/base/button.tsx` | `components/button/index.tsx` | `variant`/`disabled`/`children` re-declared; extra Sizes / Active / As-child / Loading morph examples; `loading` folds the surface into a circle around the spinner and back (`spring.moderate`, box unchanged), label and spinner crossfade, `aria-busy`, the disabled dim fades; "Loading morph" example over a local `loading-demo.tsx` stage; also serves modo's chrome Button (`shell.Button`); `buttonVariants` and the size aliases moved to `variants.ts` (server-callable) and re-exported; local variants for shadcn-style hosts: `outline` (renders as `tertiary`), `destructive` / `success` (their `-strong` hue as text on a translucent wash that deepens on hover and press, never a solid fill), `link` (underlined foreground text on the control height); size `xs` → `compact`; `buttonVariants` resolves the size aliases and takes `surface` (fill/ring/wash/hover/press/`aria-disabled` as root classes, for an anchor host); `loading` keeps focus (`focusableWhenDisabled`; `asChild` sets `aria-disabled` and blocks clicks) |
230
240
  | `Calendar` | — (local, on react-day-picker) | `components/inputs/date-time/calendar/index.tsx` (+ `use-month-window.ts`) | DayPicker props pass through (as shadcn's Calendar); styled through `classNames` and custom `Month` / `Day` / `DayButton` (no react-day-picker stylesheet); one scrolling list of months rendered as a 25-month window that moves as you scroll; a pinned weekday row, muted weekends, today's dot; FF's fills (the pick `bg-foreground text-background` like the primary button, a range `bg-active`, its preview `bg-hover`); month and year columns that mark the month being read and jump on click, before or after the days (`columnsSide`), turning into strips when narrow; `extraColumns` / `extraColumnsSide` for the date picker's time |
231
- | `Card` | `registry/default/card.tsx` | `components/card/index.tsx` | `next/link` → plain anchors (stretched card link, `CardButton`'s `href` form); statics `.Group/.Header/.Title/.Description/.Action/.Content/.Footer/.Media/.Image/.Eyebrow/.Feature/.Button` |
232
- | `Checkbox` (upstream `CheckboxItem`), `Checkbox.Group` (upstream `CheckboxGroup`) | `registry/base/checkbox-group.tsx` | `components/inputs/checkbox/index.tsx` | renamed; the row stands alone too (own state from `defaultChecked`, `onCheckedChange`, own hover and focus ring, `size`); rows numbered by the group (`lib/row-index.tsx`), so `index` is optional; uncontrolled mode + group-level callback; item `checked`/`onToggle` optional, falling back to group context; arrow-key focus guarded for `noUncheckedIndexedAccess`; shared `useSelectionRuns`, picks melt and split through the goo |
233
- | `ColorPicker` | `registry/default/color-picker.tsx` | `components/inputs/color-picker/index.tsx` | `@/registry/radix/{slider,tooltip}` → the Base siblings `../slider` / `../../overlays/tooltip`; `noUncheckedIndexedAccess` guards on regex captures, `rgb()`/`hsl()`/`oklch()` parts, `itemRects[i]` and the Slider's array value; static `.Popover`; uncontrolled support was already upstream (verified, kept); `ColorPicker.Popover` (goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier`) and the format menu morph out of their triggers, replacing the motion wrappers and the ignored `actionsRef` |
234
- | `Combobox` | `registry/base/combobox.tsx` | `components/inputs/combobox/index.tsx` | root de-generified (the parser needs a plain function plus a same-file `interface ComboboxProps`): items gained an index signature, `value`/`defaultValue` are `string \| string[]` and `onValueChange` is a union of the two handler shapes; `ComboboxValue<Multiple>` still exported; `creatable` added; `React.*` type refs replaced by named type imports; statics `.Input/.Chips/.Content/.List/.Item/.Empty`; the popup morphs out of the field (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Combobox.Content`), replacing the `scaleY` wrapper and the `actionsRef` deferred unmount |
235
- | `Command` | — (local) | `components/overlays/command/index.tsx` | Base UI Autocomplete rendered inline in Dialog's panel (`position="top"`, no ✕), morphing from its trigger with goo; grouped/flat command data filtered by label + keywords; one fluid highlight follows Base UI's highlight; ⌘K/Ctrl+K `hotkey` (one palette answers a press); fresh palette per open; statics `.Trigger/.Content/.Palette/.Shortcut`; `CommandList`/`CommandListRoot` exported for Search |
241
+ | `Card` | `registry/default/card.tsx` | `components/card/index.tsx` | `next/link` → plain anchors (stretched card link, `CardButton`'s `href` form); statics `.Group/.Header/.Title/.Description/.Action/.Content/.Footer/.Media/.Image/.Eyebrow/.Feature/.Button/.Divider`; local `border="outlined"` on a standalone card (the outlined group's hairline frame; inside a group the group decides); local `Card.Divider` (a full-bleed hairline between the card's parts, vertical in an inline card; the Separator primitive) |
242
+ | `Checkbox` (upstream `CheckboxItem`), `Checkbox.Group` (upstream `CheckboxGroup`) | `registry/base/checkbox-group.tsx` | `components/inputs/checkbox/index.tsx` | renamed; the row stands alone too (own state from `defaultChecked`, `onCheckedChange`, own hover and focus ring, `size`); rows numbered by the group (`lib/row-index.tsx`), so `index` is optional; uncontrolled mode + group-level callback; item `checked`/`onToggle` optional, falling back to group context; arrow-key focus guarded for `noUncheckedIndexedAccess`; shared `useSelectionRuns`, picks melt and split through the goo; `label` optional, any node (named via `aria-labelledby`); without a label and outside a group the checkbox is a bare box, Base UI's Checkbox.Root itself (`id` / `<label htmlFor>`, `aria-label`, `name` / `value` / `required` for forms), keeping its border when checked; `indeterminate` / `checked="indeterminate"` shows the icon set's `minus`, swapped with the self-drawing check in one cell; `disabled` dims a row and takes it out of the group's hover and arrow keys |
243
+ | `ColorPicker` | `registry/default/color-picker.tsx` | `components/inputs/color-picker/index.tsx` | `@/registry/radix/{slider,tooltip}` → the Base siblings `../slider` / `../../overlays/tooltip`; `noUncheckedIndexedAccess` guards on regex captures, `rgb()`/`hsl()`/`oklch()` parts, `itemRects[i]` and the Slider's array value; static `.Popover`; uncontrolled support was already upstream (verified, kept); `ColorPicker.Popover` (goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier`) and the format menu morph out of their triggers, replacing the motion wrappers and the ignored `actionsRef`; the color types and math (`parseColor`, `buildParsed`, conversions, formatting) in `variants.ts` (server-callable) and re-exported |
244
+ | `Combobox` | `registry/base/combobox.tsx` | `components/inputs/combobox/index.tsx` | root de-generified (the parser needs a plain function plus a same-file `interface ComboboxProps`): items gained an index signature, `value`/`defaultValue` are `string \| string[]` and `onValueChange` is a union of the two handler shapes; `ComboboxValue<Multiple>` still exported; `creatable` added; `React.*` type refs replaced by named type imports; statics `.Input/.Chips/.Content/.List/.Item/.Empty`; the popup morphs out of the field (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Combobox.Content`), replacing the `scaleY` wrapper and the `actionsRef` deferred unmount; `loading` (the chevron blurs into a `loader-circle` spinner, a status row under the list, the empty message held back) and `loadingLabel`; controlled query (`inputValue` / `defaultInputValue` / `onInputValueChange`); `filter={null}` shows the items as given; `readOnly` (Base UI's, plus the field box's read-only state; no clear or chip ✕); controlled popup (`open` / `defaultOpen` / `onOpenChange`); `footer` on `Combobox.Content` (a node or `({ close, query }) => node`); object items' `label` may be any node (unclipped in rows and chips) with `textValue` to filter, fill the field and name the chip |
245
+ | `Command` | — (local) | `components/overlays/command/index.tsx` | Base UI Autocomplete rendered inline in Dialog's panel (`position="top"`, no ✕), morphing from its trigger with goo; grouped/flat command data filtered by label + keywords; one fluid highlight follows Base UI's highlight; ⌘K/Ctrl+K `hotkey` (one palette answers a press); fresh palette per open; statics `.Trigger/.Content/.Palette/.Shortcut`; `CommandList`/`CommandListRoot` exported for Search; `onOpenChange(open, details)` with Base UI's change details (`reason`, `event`, `cancel()`) |
236
246
  | `ContextMenu` | — (local; see [Morph sources](#morph-sources)) | `components/overlays/context-menu/index.tsx` | Base UI ContextMenu (right click, long press) with the morph layer growing from the press point (`from="pointer"` default, goo on `spring.goo`); rows are Dropdown's `MenuItem`, `Dropdown.Label`, `Dropdown.Separator` with fluid hover; `ContextMenu.Item` adds `shortcut`; `size` on the root; statics `.Trigger/.Content/.Item/.Label/.Separator` |
237
- | `Dialog` | `registry/base/dialog.tsx` | `components/overlays/dialog/index.tsx` | `DialogProps` and `DialogContentProps` fully re-declared (the slot props now come from `lib/slot.ts`); statics attached as expando properties (`Dialog.Trigger = …`), so `<Dialog.Content>` types without a cast; FF's "With a sidebar" example replaced by "Surfaces inside a dialog" (written before the Sidebar was ported); the panel morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dialog.Content`), the backdrop fades with the morph's progress, the root holds the open state and scopes `Morph.Part` pairs |
238
- | `Dropdown` | `registry/base/dropdown.tsx` | `components/overlays/dropdown/index.tsx` | uncontrolled selection and uncontrolled search filtering (below); `items[next].focus()` guarded; statics `.Menu/.Trigger/.Content/.Item/.Label/.Separator/.Search/.Empty`; FF's "Create from the query" section skipped — adding a row to the list is real consumer state; the popup menu morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dropdown.Content`), replacing the `scaleY` wrapper and the ignored `actionsRef`; popup rows hand activation to the Menu item's `onClick` so Enter / Space pick |
239
- | `DatePicker`, `DateRangePicker`, `TimePicker`, `DurationPicker`, `MonthPicker` | — (local; props after shadcn's date picker and utegsk/ui's pickers) | `components/inputs/date-time/<id>/index.tsx` (+ the group's `picker-shell.tsx`, `time-columns.tsx`) | a field on the field ladder whose panel morphs out of it with Combobox's options (goo by default; `from` / `effect` / `hideSource` / `tier`, in place with `effect="morph"` + `hideSource`), or a bottom `Sheet` under 640px; one hour column with AM/PM on a 12-hour clock; `columnsSide` / `timeColumnsSide`; the panel header types the value in the locale's formats; `value` / `defaultValue` / `onValueChange` with `null` for empty — a `Date`, react-day-picker's `DateRange`, `"HH:mm"`, a date-fns `Duration`, a month's first day; `locale` is a react-day-picker locale throughout |
240
- | — | `registry/default/menu-item.tsx` | `components/overlays/dropdown/menu-item.tsx` | a row with no `checked` derives it from a self-managed panel and toggles the panel's state; `sourceIndex` carries the authored index so selection survives filter re-indexing |
247
+ | `Dialog` | `registry/base/dialog.tsx` | `components/overlays/dialog/index.tsx` | `DialogProps` and `DialogContentProps` fully re-declared (the slot props now come from `lib/slot.ts`); statics attached as expando properties (`Dialog.Trigger = …`), so `<Dialog.Content>` types without a cast; FF's "With a sidebar" example replaced by "Surfaces inside a dialog" (written before the Sidebar was ported); the panel morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dialog.Content`), the backdrop fades with the morph's progress, the root holds the open state and scopes `Morph.Part` pairs; `onOpenChange(open, details)` hears Base UI's change details and can `cancel()` (one-argument handlers still work), and `createDialogChangeDetails` builds them for the library's own changes; `disablePointerDismissal` on the root; `Dialog.Content` types and forwards `initialFocus` / `finalFocus` and takes `popupClassName` (merged after the popup's width) |
248
+ | `Dropdown` | `registry/base/dropdown.tsx` | `components/overlays/dropdown/index.tsx` | uncontrolled selection and uncontrolled search filtering (below); `items[next].focus()` guarded; statics `.Menu/.Trigger/.Content/.Item/.Label/.Separator/.Search/.Empty`; FF's "Create from the query" section skipped — adding a row to the list is real consumer state; the popup menu morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dropdown.Content`), replacing the `scaleY` wrapper and the ignored `actionsRef`; popup rows hand activation to the Menu item's `onClick` so Enter / Space pick; shadcn DropdownMenu parity: children rows with an optional index (`lib/row-order.tsx`), `inset` / `shortcut` rows, a cancelable `onSelect(event)`, `asChild` on the trigger, a keyboard focus ring, `keepMounted`, and the parts in `menu-parts.tsx` (see the headers) |
249
+ | `DatePicker`, `DateRangePicker`, `TimePicker`, `DurationPicker`, `MonthPicker` | — (local; props after shadcn's date picker and utegsk/ui's pickers) | `components/inputs/date-time/<id>/index.tsx` (+ the group's `picker-shell.tsx`, `time-columns.tsx`) | a field on the field ladder whose panel morphs out of it with Combobox's options (goo by default; `from` / `effect` / `hideSource` / `tier`, in place with `effect="morph"` + `hideSource`), or a bottom `Sheet` under 640px; one hour column with AM/PM on a 12-hour clock; `columnsSide` / `timeColumnsSide`; the panel header types the value in the locale's formats; `value` / `defaultValue` / `onValueChange` with `null` for empty — a `Date`, react-day-picker's `DateRange`, `"HH:mm"`, a date-fns `Duration`, a month's first day; `locale` is a react-day-picker locale throughout; `picker-shell.tsx` takes an `editor`, `triggerLabel` and `readOnly` |
250
+ | `DatePicker` | — (local; the masked input after utegsk/ui's `MaskedDateInput`) | `components/inputs/date-time/date-picker/index.tsx` (+ `masked-date-input.tsx`) | `editable` masked input in the field (Escape restores the last real date), `labels`, `formatDate`, `actions`, `readOnly`, string values (date-only strings read as local days) with an invalid state |
251
+ | — | `registry/default/menu-item.tsx` | `components/overlays/dropdown/menu-item.tsx` | a row with no `checked` derives it from a self-managed panel and toggles the panel's state; `sourceIndex` carries the authored index so selection survives filter re-indexing; `label` and `index` optional (`children` rows numbered in document order), `inset`, `shortcut`, cancelable `onSelect(event)`; checkbox and radio rows report to the panel for the merged selection background |
252
+ | — | — (local) | `components/overlays/dropdown/menu-parts.tsx` | `Dropdown.Group`, the `Dropdown.Shortcut` re-export, value-keyed `Dropdown.CheckboxItem`, `Dropdown.RadioGroup` / `Dropdown.RadioItem`, `Dropdown.Sub` / `.SubTrigger` / `.SubContent` (a submenu morphs out of its row), and `useMenuPanel`, the state both popups share (keyboard focus ring, the submenu trigger's pinned highlight, value-checked backgrounds) |
241
253
  | — | `registry/default/dropdown-search.tsx` | `components/overlays/dropdown/dropdown-search.tsx` | `filter` mode: `value`/`onValueChange` optional, the field reads and writes the query held by the panel (`DropdownFilterContext`) |
242
254
  | `Copy` (upstream `InputCopy`) | `registry/default/input-copy.tsx` | `components/copy/index.tsx` | renamed, out of the inputs group; `@/registry/radix/tooltip` → `../overlays/tooltip`; `useRef<ReturnType<typeof setTimeout> \| null>(null)` (React 18 overloads); defaults spelled into the prop prose; the tooltip comment's "Radix" reads "Base UI"; the copied state morphs: the glyph's two sheets melt through the goo filter into a status-tinted disc behind the check (✕ on failure) and split back |
243
- | `Input`, `Label`, `InputGroup` | `registry/default/input-group.tsx` | `components/inputs/input/index.tsx` | `InputField` split into `Input` (the ringed box, standalone: own hover outside a group, `invalid`, `size`) and `Label` (a plain `<label>`, rendered through `Field.Label` in a field), composed back by `InputGroup.Field`; item renamed `input`, `Input` the default export; uncontrolled mode (`InputHTMLAttributes`' own `defaultValue` omitted in favour of the local one); static `.Field` |
255
+ | `Field` | — (local) | `components/inputs/field/index.tsx` | Base UI Field with no form library: `.Label` / `.Description` / `.Error` / `.Control`, inset to the text in the field box (`lib/field-variants.ts`); the field's state (invalid, also from native validity; disabled, read-only, required) reaches any control on `useFieldBox` through `FieldStateContext` |
256
+ | `FileDropzone` | — (local) | `components/inputs/file-dropzone/index.tsx` (+ `file-row.tsx`, `variants.ts`, `upload-demo.tsx`) | a label around a visually hidden file input, joined to Base UI's Field; hands picked files over and keeps none (no upload logic); drag verdicts accept / partial (some will be skipped) / reject, refusing only drags where nothing can pass; `required` announced, not native; `.Row` (`FileRow`: status and progress); the pure accept / size / count helpers (`acceptsFile`, `dragVerdict`, `sortFiles`, `formatFileSize`) in `variants.ts`; `upload-demo.tsx` is the docs stage |
257
+ | `Input`, `Label`, `InputGroup` | `registry/default/input-group.tsx` | `components/inputs/input/index.tsx` | `InputField` split into `Input` (the ringed box, standalone: own hover outside a group, `invalid`, `size`) and `Label` (now its own item `components/inputs/label/index.tsx`, re-exported from `inputs/input`; rendered through `Field.Label` in a field), composed back by `InputGroup.Field`; item renamed `input`, `Input` the default export; uncontrolled mode (`InputHTMLAttributes`' own `defaultValue` omitted in favour of the local one); static `.Field`; `startAdornment` / `endAdornment`, `readOnly`, `required` (both also from a surrounding `Field`), `InputAction` (an icon button inside the box) and `InputAdornment`; a `type` swap keeps the caret and selection |
258
+ | `Label` | `registry/default/input-group.tsx` | `components/inputs/label/index.tsx` | split out of InputField; invalid, size; data-disabled dims it; re-exported from inputs/input |
244
259
  | `InputMessage` | `registry/default/input-message.tsx` | `components/AI/input-message/index.tsx` | uncontrolled twins for value, files, queue and status (below); `FilePreviewTile` turned into a `forwardRef` (it exits inside `<AnimatePresence mode="popLayout">`, which measures through a ref); guards on `queue[0]`, `history[i]`, `suggestions[i]` and the `moveQueued` swap; FF's Playground section skipped and the surrounding transcript dropped (`ChatMessage` is not ported) |
260
+ | `NavigationMenu` | — (local) | `components/navigation/navigation-menu/index.tsx` | Base UI NavigationMenu; one shared flyout morphing from its trigger (anchored to the List, frame springs to each content's size, content crossfades by activation direction); fluid hover on strip and panels; one keyboard ring per panel; `Indicator` is a GooIndicator under the open/current item |
261
+ | `NumberInput` | — (local) | `components/inputs/number-input/index.tsx` | Base UI NumberField on the field box (`useFieldBox`), steppers as field actions inside the ring, Intl `format`, scrub |
262
+ | `Pagination` | — (local, from the skill's authoring.md template) | `components/navigation/pagination/index.tsx` | pages strip (GooIndicator + fluid hover) or jump-to-page field; edges; page-size Select; labels as props |
245
263
  | — | `registry/default/file-thumbnail.tsx` | `components/AI/input-message/file-thumbnail.tsx` | the lazy `import("pdfjs-dist")` is kept as-is — verified against modo's esbuild settings, it lands in a ~790 kB chunk fetched only when a PDF is attached; the worker still comes from jsDelivr, so no bundler-side worker config is needed; the `@next/next/no-img-element` eslint-disable dropped |
246
- | `Popover` | — (local; after beUI's gooey popover and motion-primitives' morphing popover, see [Morph sources](#morph-sources)) | `components/overlays/popover/index.tsx` | Base UI Popover with the morph layer (goo by default); `openOnHover` on the trigger; statics `.Trigger/.Content/.Title/.Description/.Close` |
264
+ | `Popover` | — (local; after beUI's gooey popover and motion-primitives' morphing popover, see [Morph sources](#morph-sources)) | `components/overlays/popover/index.tsx` | Base UI Popover with the morph layer (goo by default); `openOnHover` on the trigger; statics `.Trigger/.Content/.Title/.Description/.Close`; `Popover.Content` forwards `anchor` (the morph's source when it is an element), `alignOffset`, `collisionPadding`, `collisionBoundary`, `initialFocus`, `finalFocus`; `onOpenChange(open, details)` with Base UI's change details (`cancel()`) |
247
265
  | `PreviewCard` | — (local; after beUI's gooey popover, see [Morph sources](#morph-sources)) | `components/overlays/preview-card/index.tsx` | Base UI PreviewCard with the morph layer (goo from the link on `spring.goo`); `delay` / `closeDelay` / `render` on the trigger, a token-styled link by default; `from` without `'pointer'`; statics `.Trigger/.Content` |
248
- | `Radio` (upstream `RadioItem`), `Radio.Group` (upstream `RadioGroup`) | `registry/base/radio-group.tsx` | `components/inputs/radio/index.tsx` | renamed; rows numbered by the group (`lib/row-index.tsx`), so `index` is optional; uncontrolled index and value modes; group context carries a `selectIndex` setter every item calls, so items need no `selected`/`onSelect`; arrow-key focus guarded; selected background is a `GooIndicator` |
266
+ | `Progress` | — (local) | `components/progress/index.tsx` (+ `progress.css`, `step-demo.tsx`) | Base UI Progress; the determinate fill is a full-width bar slid with `translateX` on `spring.moderate` (after FF's CarouselDots fill, never scaled); with no `value` a segment sweeps the track; `.Spinner` draws Button's figure-eight loading glyph; both loops on `--duration-loop` (`progress.css`); `step-demo.tsx` is the docs stage |
267
+ | `Radio` (upstream `RadioItem`), `Radio.Group` (upstream `RadioGroup`) | `registry/base/radio-group.tsx` | `components/inputs/radio/index.tsx` | renamed; rows numbered by the group (`lib/row-index.tsx`), so `index` is optional; uncontrolled index and value modes; group context carries a `selectIndex` setter every item calls, so items need no `selected`/`onSelect`; arrow-key focus guarded; selected background is a `GooIndicator`; `label` optional, any node; a `Radio` without one is a bare circle, Base UI's Radio.Root (picked by `value`, `id` / `<label htmlFor>`, keeping its border when picked) laid out freely inside a value-mode group, which then draws no rows and leaves the arrow keys to Base UI; `disabled` on a row, or on the group (which dims as a whole); the group's `name` / `required` reach Base UI's RadioGroup for forms |
249
268
  | `Search` | — (local; after beUI's morphing search, see [Morph sources](#morph-sources)) | `components/inputs/search/index.tsx` | pill or icon button springing its own width in place (`spring.slow`), growing over its neighbors (`align`); with `items`, Autocomplete suggestions ooze out of the field via the morph layer (goo on `spring.goo`, `effect`/`tier`) with Command's rows; Escape closes → clears → collapses; blur collapses when empty; `shortcut`; `<search>` landmark + form, `onSubmit` |
250
- | `Select` | `registry/base/select.tsx` | `components/inputs/select/index.tsx` | members re-declared one per line (the parser drops members whose type spans lines); upstream's uncontrolled `defaultValue` verified and kept rather than rerouted through `useControllableState`; expando statics `.Trigger/.Content/.Item/.Group/.Label/.Separator`; the popup morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Select.Content`), replacing the `scaleY` wrapper and the `actionsRef` deferred unmount |
251
- | `Sheet` | — (local; replaces beUI's bottom sheet and drawer, see [Morph sources](#morph-sources)) | `components/overlays/sheet/index.tsx` | Base UI Drawer (swipe to dismiss, snap points, `Drawer.Viewport` for the swipe) with the morph growing from the sheet's own edge; a swipe-dismissed sheet slides on off its edge from the release point (the morph's `exit`); `side`, `snapPoints`, `defaultSnapPoint`; Dialog's parts and `DialogState` (Base UI's Drawer parts are Dialog's); statics as Dialog's plus `.Provider` / `.Indent` |
252
- | `Sidebar` | `registry/base/sidebar.tsx` (+ `registry/default/sidebar-core.tsx`, `sidebar-menu.tsx`; `app/globals.css` `.scroll-divider`) | `components/navigation/sidebar/index.tsx` (+ `sidebar-core.tsx`, `sidebar-menu.tsx`, `sidebar.css`) | the mobile sheet is the DS's `Sheet` (goo from its edge, swipe to dismiss) instead of a Dialog with a framer slide; no collapsed peek: `openOnHover` on the provider opens the sidebar in flow while the pointer is between its edge and the far side of a trigger beside it, the trigger switching between open and on hover (`panel-*-dashed` glyphs added to the icon context); the active row backgrounds are `GooIndicator`s and the hover highlight the liquid `FluidHoverHighlight`; uncontrolled selection (`value` on the buttons, `value` / `defaultValue` / `onValueChange` on `Sidebar`) and collapsible rows (`Sidebar.MenuItem collapsible`, below); statics `.Provider/.Trigger/.Rail/.Inset/.Input/.Header/.Content/.Footer/.Separator/.Group/.GroupLabel/.GroupAction/.GroupActions/.GroupContent/.Menu/.MenuItem/.MenuButton/.MenuAction/.MenuActions/.MenuBadge/.MenuSkeleton/.MenuSub/.MenuSubItem/.MenuSubButton` through a `SidebarComponent` cast; `fontVariationSettings` → `weight-*`, `text-[Npx]` → type roles, hex focus ring → `ring-focus-ring`, `duration-80\|160\|240` → tiers |
253
- | `Slider` | `registry/base/slider.tsx` | `components/inputs/slider/index.tsx` | `var(--color-accent)` → `var(--accent)` (no `--color-*` aliases here); uncontrolled support on the public wrapper, with both engines still fully controlled; `SliderProps extends Omit<SliderEngineProps, "value" \| "onChange">`; React 18 ref types (`MutableRefObject`); indexed reads of the value/step/pip arrays asserted |
254
- | `Switch` | `registry/base/switch.tsx` | `components/inputs/switch/index.tsx` | uncontrolled support; every in-body read goes through the resolved `isChecked` and a `toggle()` that also calls `onToggle`; the thumb is a `GooIndicator` in Base UI's Thumb (stretches as it travels), dragged x held in state; local `description` (muted line, `aria-describedby`) and `trackSide`; local `Switch.Group`: switches as settings rows (track at the end) in one outlined fieldset, after an outlined `Card.Group` (shared hairlines dropped around the fluid hover highlight), rows numbered by the group |
255
- | `Table` | `registry/default/table.tsx` | `components/table/index.tsx` | statics `.Header/.Body/.Row/.Head/.Cell`; `TableProps` / `TableRowProps` exported |
256
- | `Tabs` | `registry/base/tabs.tsx` | `components/navigation/tabs/index.tsx` | `defaultSelectedIndex` added as the index-mode twin of the existing `defaultValue`; indicator and hover pill are `GooIndicator`s (the hover pill returns to the active tab as it fades); statics `.List/.Item/.Panel` |
269
+ | `Select` | `registry/base/select.tsx` | `components/inputs/select/index.tsx` | members re-declared one per line (the parser drops members whose type spans lines); upstream's uncontrolled `defaultValue` verified and kept rather than rerouted through `useControllableState`; expando statics `.Trigger/.Content/.Item/.Group/.Label/.Separator`; the popup morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Select.Content`), replacing the `scaleY` wrapper and the `actionsRef` deferred unmount; `Select.Item`'s `index` is optional: rows take their place in document order (`lib/row-order.tsx`), inside groups, conditionals and maps, and the checked row is found by its own index; `textValue` for typeahead; a node label is shown as is (unclipped) in the rows and, through Base UI's `items`, in the trigger; `Select.Value` part, with `Select.Trigger` children replacing its value slot; `side` / `align` / `sideOffset` on `Select.Content`; `triggerVariants` in `variants.ts` (server-callable) and re-exported; the keyboard ring is the shared `lib/focus-ring.tsx` |
270
+ | `Sheet` | — (local; replaces beUI's bottom sheet and drawer, see [Morph sources](#morph-sources)) | `components/overlays/sheet/index.tsx` | Base UI Drawer (swipe to dismiss, snap points, `Drawer.Viewport` for the swipe) with the morph growing from the sheet's own edge; a swipe-dismissed sheet slides on off its edge from the release point (the morph's `exit`); `side`, `snapPoints`, `defaultSnapPoint`; Dialog's parts and `DialogState` (Base UI's Drawer parts are Dialog's); statics as Dialog's plus `.Provider` / `.Indent`; `onOpenChange(open, details)` with Base UI's change details (`reason`, `event`, `cancel()`); `disablePointerDismissal` on the root; `Sheet.Content` takes `side` (overrides the root's, the swipe follows), `initialFocus` / `finalFocus` and `popupClassName` (merged after `PLACEMENT`) |
271
+ | `Sidebar` | `registry/base/sidebar.tsx` (+ `registry/default/sidebar-core.tsx`, `sidebar-menu.tsx`; `app/globals.css` `.scroll-divider`) | `components/navigation/sidebar/index.tsx` (+ `sidebar-core.tsx`, `sidebar-menu.tsx`, `sidebar.css`) | the mobile sheet is the DS's `Sheet` (goo from its edge, swipe to dismiss) instead of a Dialog with a framer slide; no collapsed peek: `openOnHover` on the provider opens the sidebar in flow while the pointer is between its edge and the far side of a trigger beside it, the trigger switching between open and on hover (`panel-*-dashed` glyphs added to the icon context); the active row backgrounds are `GooIndicator`s and the hover highlight the liquid `FluidHoverHighlight`; uncontrolled selection (`value` on the buttons, `value` / `defaultValue` / `onValueChange` on `Sidebar`) and collapsible rows (`Sidebar.MenuItem collapsible`, below); statics `.Provider/.Trigger/.Rail/.Inset/.Input/.Header/.Content/.Footer/.Separator/.Group/.GroupLabel/.GroupAction/.GroupActions/.GroupContent/.Menu/.MenuItem/.MenuButton/.MenuAction/.MenuActions/.MenuBadge/.MenuSkeleton/.MenuSub/.MenuSubItem/.MenuSubButton` through a `SidebarComponent` cast; `fontVariationSettings` → `weight-*`, `text-[Npx]` → type roles, hex focus ring → `ring-focus-ring`, `duration-80\|160\|240` → tiers; `sidebarMenuButtonVariants` and the `SIDEBAR_*` constants in `variants.ts` (server-callable; `variants.ts` files are bunup entries); `inert` through `lib/inert.ts` |
272
+ | `Slider` | `registry/base/slider.tsx` | `components/inputs/slider/index.tsx` | `var(--color-accent)` → `var(--accent)` (no `--color-*` aliases here); uncontrolled support on the public wrapper, with both engines still fully controlled; `SliderProps extends Omit<SliderEngineProps, "value" \| "onChange">`; React 18 ref types (`MutableRefObject`); indexed reads of the value/step/pip arrays asserted; `aria-label` names the thumbs ahead of the visible `label` |
273
+ | `Switch` | `registry/base/switch.tsx` | `components/inputs/switch/index.tsx` | uncontrolled support; every in-body read goes through the resolved `isChecked` and a `toggle()` that also calls `onToggle`; the thumb is a `GooIndicator` in Base UI's Thumb (stretches as it travels), dragged x held in state; local `description` (muted line, `aria-describedby`) and `trackSide`; local `Switch.Group`: switches as settings rows (track at the end) in one outlined fieldset, after an outlined `Card.Group` (shared hairlines dropped around the fluid hover highlight), rows numbered by the group; `label` optional, any node; without one (outside a group) the switch is the bare track, Base UI's Switch.Root, which toggles itself (click, keyboard, a `<label htmlFor>`) through `onCheckedChange`, with the thumb drag on the track and the toggle that follows a drag swallowed; `name` / `value` / `required` reach Base UI's hidden input in both modes |
274
+ | `Table` | `registry/default/table.tsx` | `components/table/index.tsx` (+ `sort-demo.tsx`) | statics `.Header/.Body/.Row/.Head/.Cell`; `TableProps` / `TableRowProps` exported; local additions: Footer, Caption, Empty, sortable Head (`sortDirection`/`defaultSortDirection`/`onSort`), `stacked` + Cell `label` (stacked cells `items-start`), `containerClassName`; unindexed body rows numbered in document order (`lib/row-order`) |
275
+ | `Tabs` | `registry/base/tabs.tsx` | `components/navigation/tabs/index.tsx` | `defaultSelectedIndex` added as the index-mode twin of the existing `defaultValue`; indicator and hover pill are `GooIndicator`s (the hover pill returns to the active tab as it fades); statics `.List/.Item/.Panel`; `Tabs.Item` node `label` (inert ghost copy), `href` link tabs |
257
276
  | `TabsSubtle` | `registry/base/tabs-subtle.tsx` | `components/navigation/tabs-subtle/index.tsx` | uncontrolled mode; panels may be authored inside the root (below); the tab list's ref write goes through `MutableRefObject`; selected and hover pills are `GooIndicator`s; statics `.Item/.Panel` |
258
- | `Textarea` | — (local; Input's multi-line twin) | `components/inputs/textarea/index.tsx` | Base UI `Field.Control` rendered as a `<textarea>`; the field ladder from `lib/field-classes.ts`; padding sized so one row matches the control height; grows from `minRows` to `maxRows` (InputMessage's measure), then scrolls; `value` / `defaultValue` / `onChange` |
277
+ | `Textarea` | — (local; Input's multi-line twin) | `components/inputs/textarea/index.tsx` | Base UI `Field.Control` rendered as a `<textarea>`; the field ladder from `lib/field-classes.ts`; padding sized so one row matches the control height; grows from `minRows` to `maxRows` (InputMessage's measure), then scrolls; `value` / `defaultValue` / `onChange`; `readOnly` / `required` (also from a surrounding `Field`) |
259
278
  | `ThinkingIndicator` | `registry/default/thinking-indicator.tsx` | `components/AI/thinking-indicator/index.tsx` | upstream prop JSDoc kept (it already matches the FF API table); the `.shimmer-text` rule it rides lives in `base.css`; local `variant="goo"` (three dots merging and splitting through `GooFilter`, plain label; default stays `shimmer`); reduced motion also follows MotionConfig |
260
- | `Toast` | — (local; after Sileo, see [Morph sources](#morph-sources)) | `components/overlays/toast/index.tsx` | Base UI Toast (queue, timers, swipe, announcements) with Sileo's pill melting into a body through the shared `GooFilter`; autopilot expand/collapse, six states on the `--status-*` tokens, promise toasts; `Toast` is the provider + viewport, `Toast.Trigger` shows one, `useToast()` the imperative API |
261
- | `Tooltip` | `registry/base/tooltip.tsx` | `components/overlays/tooltip/index.tsx` | `className`/`children` docs filled in; statics `.Provider` / `.PortalContainer`; the slide + fade replaced by the morph layer (goo from the trigger on `spring.goo`, `from` / `effect` / `tier` props; `followCursor` tooltips fade); a hand-off between grouped tooltips skips the morph (Base UI's `data-instant`) |
279
+ | `Toast` | — (local; after Sileo, see [Morph sources](#morph-sources)) | `components/overlays/toast/index.tsx` (+ `manager.ts`, `global-demo.tsx`) | Base UI Toast (queue, timers, swipe, announcements) with Sileo's pill melting into a body through the shared `GooFilter`; autopilot expand/collapse, six states on the `--status-*` tokens, promise toasts; `Toast` is the provider + viewport, `Toast.Trigger` shows one, `useToast()` the in-tree API (plus `toasts`, `update`, `dismiss`); `manager.ts` holds one module-level Base UI `createToastManager()` exported as the global `toast()` (`.success/.error/.warning/.info/.loading/.promise/.update/.dismiss`, each returning `{ id, update, dismiss }`), queuing calls made before a provider subscribes (dropped on the server); `<Toast manager={toast}>` renders them (`manager` takes any source with a `subscribe`); ReactNode title/description, `icon` as a component or node, `action` as `{ label, onClick }` or a node, `duration` as an alias of `timeout`; a loading toast stays until its type changes, then leaves on the timeout; status glyphs via `useIcons()` instead of direct lucide imports; `global-demo.tsx` is the docs stage; `Toast.Action` / `Toast.Close` (Base UI's parts as compact Buttons, `render` / `asChild`) for custom action nodes |
280
+ | `Tooltip` | `registry/base/tooltip.tsx` | `components/overlays/tooltip/index.tsx` | `className`/`children` docs filled in; statics `.Provider` / `.PortalContainer`; the slide + fade replaced by the morph layer (goo from the trigger on `spring.goo`, `from` / `effect` / `tier` props; `followCursor` tooltips fade); a hand-off between grouped tooltips skips the morph (Base UI's `data-instant`); local compound parts `TooltipRoot` (`open` / `defaultOpen` / `onOpenChange`, `delay` or `delayDuration`, `closeDelay`, `followCursor`), `TooltipTrigger` (`render` or Radix-style `asChild`; drops the slot's `nativeButton`) and `TooltipContent` (`side`, `align`, `sideOffset`, `alignOffset`, `collisionPadding`, `positionerClassName`, morph options, `render` / `asChild` for the label, rest props on the popup), also as statics `.Root .Trigger .Content`; `Tooltip` without `content` is the root, and the flat form gains `open` / `defaultOpen` / `align` / `alignOffset` / `collisionPadding` / `delay` / `closeDelay`; `TooltipProvider` takes `delay` / `closeDelay` / `timeout` and Radix's `delayDuration` / `skipDelayDuration` (Base UI name wins; a negative skip clamps to 0) |
262
281
 
263
282
  #### blocks
264
283
 
@@ -318,6 +337,7 @@ becomes the one block.
318
337
  | primitives | `code` (Code) | — (local, sugar-high) | — |
319
338
  | primitives | `morph` (Morph) | — (local, `lib/use-morph.ts`) | — |
320
339
  | primitives | `craft` (Craft) | — (local, bad/good pairs) | skill (before/after) |
340
+ | primitives | `separator` (Separator), `skeleton` (Skeleton) | — (local) | — |
321
341
  | components | accordion, button, combobox, dialog, dropdown, select, slider, switch, tabs, tabs-subtle, tooltip | `registry/base/<id>.tsx` | same slug |
322
342
  | components | `checkbox` (Checkbox, `.Group`), `radio` (Radio, `.Group`) | `registry/base/checkbox-group.tsx`, `radio-group.tsx` | checkbox-group, radio-group |
323
343
  | components | badge, card, color-picker, input-message, table, thinking-indicator | `registry/default/<id>.tsx` | same slug |
@@ -325,6 +345,7 @@ becomes the one block.
325
345
  | components | `input` (Input, Label, InputGroup) | `registry/default/input-group.tsx` | input-group |
326
346
  | components | alert-dialog, command, context-menu, popover, preview-card, search, sheet, toast | — (local, see [Morph sources](#morph-sources)) | — |
327
347
  | components | calendar, date-picker, date-range-picker, time-picker, duration-picker, month-picker (group `inputs/date-time`) | — (local, on react-day-picker) | — |
348
+ | components | avatar, progress, field, number-input, file-dropzone, navigation-menu, pagination | — (local) | — |
328
349
  | components | sidebar | `base/sidebar.tsx` + `default/sidebar-core.tsx` + `default/sidebar-menu.tsx` | sidebar |
329
350
  | blocks | `sidebar-app` (SidebarApp) | the preset generator's output + `blocks/sidebar-*` | sidebar, preset `sa1FQfCxH6` |
330
351
 
@@ -522,19 +543,20 @@ declared packages.
522
543
 
523
544
  ## Docs and examples
524
545
 
525
- What modo's parser (`lib/src/lib/tsdoc.ts`) and example compiler
546
+ What modo's item reader (`lib/src/lib/docs.ts`, `tsdoc.ts`) and example compiler
526
547
  (`lib/src/lib/example.ts`) need, and how this package answers it:
527
548
 
528
- - **The item is the file's `export default`** — either `function X(…)` or a
529
- `const X = forwardRef<HTMLXElement, XProps>(…)`. The forwardRef form's first
530
- type argument must contain no `<` or `,`, which is why ScrollArea spells out
531
- `HTMLDivElement` instead of `ComponentRef<typeof …>`, and why Combobox's
532
- root lost its generics.
533
- - **Props come from a same-file `interface XProps`**, and only from members
534
- declared in its body — inherited members are invisible, and a member whose
535
- type spans several lines is dropped. Every member needs a description, and
536
- defaults are read from that prose (`Defaults to \`false\`.`), not from the
537
- destructuring. Hence the FF API-table text re-declared on every prop.
549
+ - **The item is the file's `export default`**, followed through re-exports
550
+ to its declaration — `function X(…)`, a `forwardRef<El, XProps>(…)`, or any
551
+ component-typed value. (The workarounds for the old text parser stay: ScrollArea's
552
+ spelled-out `HTMLDivElement`, Combobox's de-generified root.)
553
+ - **Props are read with the TypeScript compiler** from the render function's
554
+ parameter, the `forwardRef` props type or the component's type, inherited
555
+ members included (`extends`, `Omit`, `Pick`, intersections, across files).
556
+ React's DOM attributes and other libraries' typings (Base UI,
557
+ react-day-picker) stay out unless re-declared, so a prop meant for the table
558
+ is declared with a description. The Default column takes the destructured
559
+ default, else `@default`, else a closing ``Defaults to `x`.`` sentence.
538
560
  - **Examples live in `examples.mdx`** next to the item, included by the
539
561
  TSDoc's `@example {@include ./examples.mdx}`: a `# <FF section title>`,
540
562
  an optional caption, then one JSX block (or a fenced code block for code to
@@ -600,7 +622,7 @@ The runner kills stray `modo dev` processes, splices a temporary
600
622
  upward. To run the package on its own, without the switcher:
601
623
 
602
624
  ```sh
603
- cd ui && MODO_PORT=5180 bunx modo dev
625
+ cd ui && MODO_PORT=5180 bun ../lib/dist/cli.js dev # after a lib build; ui has no modo bin
604
626
  ```
605
627
 
606
628
  ## How to verify
@@ -0,0 +1,33 @@
1
+ import SidebarApp from './index'
2
+
3
+ # Preset
4
+
5
+ The preset as it ships: collapsed. Press the trigger (or `[`) and the sidebar slides in; pick a thread and the active row melts over to it.
6
+
7
+ <div className="h-[560px] w-full overflow-hidden rounded-xl border border-border bg-background">
8
+ <SidebarApp contained />
9
+ </div>
10
+
11
+ # Open
12
+
13
+ `defaultOpen` starts it expanded. Open the workspace switcher, a thread's "More options" or the user row: each menu grows out of its row.
14
+
15
+ <div className="h-[560px] w-full overflow-hidden rounded-xl border border-border bg-background">
16
+ <SidebarApp contained defaultOpen />
17
+ </div>
18
+
19
+ # Open on hover
20
+
21
+ `openOnHover`: move to the left edge or onto the topbar's trigger and the collapsed sidebar opens; leave it and it closes. Press the trigger to keep it open, again to go back to on hover.
22
+
23
+ <div className="h-[560px] w-full overflow-hidden rounded-xl border border-border bg-background">
24
+ <SidebarApp contained openOnHover />
25
+ </div>
26
+
27
+ # Floating on the right
28
+
29
+ The same shell with `variant="floating"` and `side="right"`; the shortcut becomes `]`.
30
+
31
+ <div className="h-[560px] w-full overflow-hidden rounded-xl border border-border bg-background">
32
+ <SidebarApp contained defaultOpen variant="floating" side="right" />
33
+ </div>
@@ -0,0 +1,218 @@
1
+ /*
2
+ * Fluid Functionalism's sidebar app shell as the preset `sa1FQfCxH6`
3
+ * (fluidfunctionalism.com/docs/sidebar?preset=sa1FQfCxH6) configures it: the
4
+ * inset layout starting collapsed, a workspace switcher, search and a "New"
5
+ * row in the header, two collapsible thread sections, a row menu on every
6
+ * thread and a user row with two buttons in the footer. The composition is
7
+ * the app-sidebar.tsx and page.tsx that FF's preset generator emits for it
8
+ * (github.com/mickadesign/fluid-functionalism `lib/preset/sidebar-install.ts`
9
+ * @ c367a0d066bc10e663b3d269bc40539ea8417b25), over the sidebar-app,
10
+ * sidebar-workspace-header and sidebar-user-footer blocks ported beside it —
11
+ * MIT License © 2026 Micka Touillaud (notice: LICENSE.fluid-functionalism).
12
+ *
13
+ * Local changes: the selected thread rides the Sidebar's `value` (the rows
14
+ * carry `value`s) and the topbar shows it; the switcher keeps its own pick
15
+ * (`defaultCheckedIndex`); the footer's icon buttons are the DS's ghost
16
+ * `Button`; `variant`, `side`, `openOnHover` and `contained` are props.
17
+ */
18
+
19
+ import { useState } from 'react'
20
+ import Button from '../../components/button'
21
+ import Sidebar from '../../components/navigation/sidebar'
22
+ import { DropdownContent, DropdownMenu, DropdownTrigger, MenuItem } from '../../components/overlays/dropdown'
23
+ import Tooltip from '../../components/overlays/tooltip'
24
+ import { useIcon } from '../../lib/icon-context'
25
+ import { cn } from '../../lib/utils'
26
+ import { SidebarInsetTopbar } from './inset-topbar'
27
+ import { NAV_SECTIONS } from './nav-data'
28
+ import { SidebarSearchField } from './search-field'
29
+ import { SidebarUserFooter } from './user-footer'
30
+ import { SidebarWorkspaceHeader, WorkspaceTile } from './workspace-header'
31
+
32
+ interface SidebarAppProps {
33
+ /** Start with the sidebar open; the preset starts it collapsed. Defaults to `false`. */
34
+ defaultOpen?: boolean
35
+ /** The sidebar's layout (see Sidebar). Defaults to `'inset'`. */
36
+ variant?: 'sidebar' | 'floating' | 'inset'
37
+ /** The edge it sits on. Defaults to `'left'`. */
38
+ side?: 'left' | 'right'
39
+ /** Open the collapsed sidebar while the pointer is at its edge or on the topbar's trigger (see Sidebar). Defaults to `false`. */
40
+ openOnHover?: boolean
41
+ /** Fill the parent box instead of the viewport, as the examples do. Defaults to `false`. */
42
+ contained?: boolean
43
+ }
44
+
45
+ interface AppSidebarProps {
46
+ variant: 'sidebar' | 'floating' | 'inset'
47
+ side: 'left' | 'right'
48
+ className?: string
49
+ value: string
50
+ onValueChange: (value: string) => void
51
+ }
52
+
53
+ function AppSidebar({ variant, side, className, value, onValueChange }: AppSidebarProps) {
54
+ const PlusIcon = useIcon('plus')
55
+ const PencilIcon = useIcon('pencil')
56
+ const MoreVerticalIcon = useIcon('more-vertical')
57
+ const LinkIcon = useIcon('link')
58
+ const UserIcon = useIcon('user')
59
+ const SettingsIcon = useIcon('settings')
60
+ const ArrowLeftIcon = useIcon('arrow-left')
61
+ const MoonIcon = useIcon('moon')
62
+
63
+ return (
64
+ <Sidebar variant={variant} side={side} className={className} value={value} onValueChange={onValueChange}>
65
+ <Sidebar.Header>
66
+ <SidebarWorkspaceHeader
67
+ name="Acme Inc"
68
+ tile={<WorkspaceTile>A</WorkspaceTile>}
69
+ defaultCheckedIndex={0}
70
+ menu={
71
+ <>
72
+ <MenuItem index={0} label="Acme Inc" />
73
+ <MenuItem index={1} label="Personal" />
74
+ <MenuItem index={2} icon={PlusIcon} label="New workspace" />
75
+ </>
76
+ }
77
+ />
78
+ {/* Search and the action rows are one block on the menu rows' rhythm. */}
79
+ <div className="flex flex-col gap-0.5">
80
+ <SidebarSearchField />
81
+ <Sidebar.Menu>
82
+ <Sidebar.MenuItem>
83
+ <Sidebar.MenuButton icon={PlusIcon}>
84
+ New
85
+ <span className="ml-auto inline-flex opacity-0 transition-opacity duration-fast group-hover/menu-item:opacity-100 group-focus-within/menu-item:opacity-100">
86
+ <kbd className="font-sans text-micro text-muted-foreground">⇧⌘O</kbd>
87
+ </span>
88
+ </Sidebar.MenuButton>
89
+ </Sidebar.MenuItem>
90
+ </Sidebar.Menu>
91
+ </div>
92
+ </Sidebar.Header>
93
+
94
+ <Sidebar.Content>
95
+ {NAV_SECTIONS.map(section => (
96
+ <Sidebar.Group key={section.label} collapsible>
97
+ <Sidebar.GroupLabel>{section.label}</Sidebar.GroupLabel>
98
+ <Sidebar.GroupActions>
99
+ <Tooltip content="Add item" side="top">
100
+ <Sidebar.GroupAction aria-label="Add item">
101
+ <PlusIcon />
102
+ </Sidebar.GroupAction>
103
+ </Tooltip>
104
+ </Sidebar.GroupActions>
105
+ <Sidebar.Menu>
106
+ {section.items.map(item => (
107
+ <Sidebar.MenuItem key={item.label}>
108
+ {/* The status draws the dot and the screen-reader "unread"; the value selects. */}
109
+ <Sidebar.MenuButton status={item.status} value={item.label}>
110
+ {item.label}
111
+ </Sidebar.MenuButton>
112
+ <DropdownMenu>
113
+ <DropdownTrigger
114
+ render={
115
+ <Sidebar.MenuAction showOnHover aria-label="More options">
116
+ <MoreVerticalIcon />
117
+ </Sidebar.MenuAction>
118
+ }
119
+ />
120
+ {/* 240px, the header and footer triggers' width. */}
121
+ <DropdownContent className="min-w-0 w-[240px]" align="start" sideOffset={4}>
122
+ <MenuItem index={0} icon={PencilIcon} label="Rename" />
123
+ <MenuItem index={1} icon={LinkIcon} label="Share" />
124
+ </DropdownContent>
125
+ </DropdownMenu>
126
+ </Sidebar.MenuItem>
127
+ ))}
128
+ </Sidebar.Menu>
129
+ </Sidebar.Group>
130
+ ))}
131
+ </Sidebar.Content>
132
+
133
+ <Sidebar.Footer>
134
+ <div className="flex items-center gap-1 pr-1.5">
135
+ <SidebarUserFooter
136
+ name="Jane Doe"
137
+ avatar={
138
+ <span className="flex size-5 items-center justify-center rounded-full bg-muted-foreground text-micro-compact text-background">
139
+ J
140
+ </span>
141
+ }
142
+ className="min-w-0 flex-1"
143
+ menu={
144
+ <>
145
+ <MenuItem index={0} icon={UserIcon} label="Profile" />
146
+ <MenuItem index={1} icon={SettingsIcon} label="Settings" />
147
+ <MenuItem index={2} icon={ArrowLeftIcon} label="Log out" />
148
+ </>
149
+ }
150
+ />
151
+ <Tooltip content="Settings" side="top">
152
+ <Button variant="ghost" size="icon-compact" aria-label="Settings" className="size-6 shrink-0">
153
+ <SettingsIcon />
154
+ </Button>
155
+ </Tooltip>
156
+ <Tooltip content="Theme" side="top">
157
+ <Button variant="ghost" size="icon-compact" aria-label="Theme" className="size-6 shrink-0">
158
+ <MoonIcon />
159
+ </Button>
160
+ </Tooltip>
161
+ </div>
162
+ </Sidebar.Footer>
163
+ </Sidebar>
164
+ )
165
+ }
166
+
167
+ /**
168
+ * An app shell around the Sidebar, as Fluid Functionalism's playground preset
169
+ * `sa1FQfCxH6` builds it: the inset layout, starting collapsed.
170
+ *
171
+ * Press the trigger in the topbar (or `[`) to bring the sidebar in. The
172
+ * header holds a workspace switcher, a search field and a "New" row; two
173
+ * thread sections fold from their labels and carry an "Add item" action;
174
+ * every thread shows its status dot and, on hover, a "More options" menu;
175
+ * the footer anchors the user row and two buttons. Pick a thread and the
176
+ * active row melts over to it while the topbar follows.
177
+ *
178
+ * Every overlay grows out of what opened it: the switcher, the user menu
179
+ * and the row menus are Dropdowns, the labels on the buttons are Tooltips,
180
+ * a collapsed sidebar with `openOnHover` slides open from its edge, and below
181
+ * the `md` breakpoint the sidebar is a Sheet growing out of the left edge.
182
+ *
183
+ * @example {@include ./examples.mdx}
184
+ */
185
+ export default function SidebarApp({
186
+ defaultOpen = false,
187
+ variant = 'inset',
188
+ side = 'left',
189
+ openOnHover = false,
190
+ contained = false,
191
+ }: SidebarAppProps) {
192
+ const [thread, setThread] = useState(NAV_SECTIONS[0]?.items[0]?.label ?? '')
193
+
194
+ return (
195
+ <Sidebar.Provider
196
+ defaultOpen={defaultOpen}
197
+ openOnHover={openOnHover}
198
+ className={contained ? 'h-full min-h-0' : undefined}
199
+ >
200
+ <AppSidebar
201
+ variant={variant}
202
+ side={side}
203
+ className={contained ? 'h-full' : undefined}
204
+ value={thread}
205
+ onValueChange={setThread}
206
+ />
207
+ {/* The floating card sits in a p-2 gutter: the topbar drops to line up with it. */}
208
+ <Sidebar.Inset className={cn(contained && 'min-h-0', variant === 'floating' && 'pt-2')}>
209
+ <SidebarInsetTopbar>
210
+ <span className="truncate text-body text-muted-foreground">{thread}</span>
211
+ </SidebarInsetTopbar>
212
+ <main className="flex flex-1 flex-col gap-4 p-4" />
213
+ </Sidebar.Inset>
214
+ </Sidebar.Provider>
215
+ )
216
+ }
217
+
218
+ export type { SidebarAppProps }
@@ -0,0 +1,25 @@
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/blocks/sidebar-app/inset-topbar.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
6
+ * Local modifications:
7
+ * - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
8
+ * `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
9
+ * - No peek to hide behind: the sidebar opens on hover in flow (the
10
+ * provider's `openOnHover`), so the trigger stays, riding the inset's edge.
11
+ */
12
+
13
+ import { type ReactNode } from 'react'
14
+ import { SidebarTrigger } from '../../components/navigation/sidebar'
15
+ import { cn } from '../../lib/utils'
16
+
17
+ // The main region's topbar, its trigger first.
18
+ export function SidebarInsetTopbar({ children, className }: { children?: ReactNode; className?: string }) {
19
+ return (
20
+ <header className={cn('flex h-12 shrink-0 items-center gap-2 px-1.5', className)}>
21
+ <SidebarTrigger />
22
+ {children}
23
+ </header>
24
+ )
25
+ }
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
3
+ * `registry/default/lib/sidebar-menu-grid.ts` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
4
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
5
+ * Local modifications:
6
+ * - The width reads Base UI's `--anchor-width` only (the Radix fallback goes).
7
+ * - The `-ml-1` shift is dropped: the class lands on the morph surface's
8
+ * content, which would slide off its own background.
9
+ */
10
+
11
+ /**
12
+ * Shared geometry for sidebar-anchored dropdown menus (the workspace switcher
13
+ * in the header, the user menu in the footer).
14
+ *
15
+ * The popup is trigger-width plus 10px, so that:
16
+ * - the leading icon slot's centre lands on the sidebar rows' 16px leading
17
+ * axis (pl-2, with a 20px letter-tile overhanging the 16px slot by 2px a
18
+ * side),
19
+ * - gap-2 lands the label on the rows' 32px text axis, and
20
+ * - pr-1.5 puts a trailing glyph (the check) on the trigger chevron's
21
+ * vertical axis.
22
+ */
23
+ export const SIDEBAR_MENU_GRID =
24
+ '[&_[role=menuitem]]:pl-2 [&_[role=menuitem]]:pr-1.5 [&_[role=menuitem]]:gap-2 [&_[role=menuitemradio]]:pl-2 [&_[role=menuitemradio]]:pr-1.5 [&_[role=menuitemradio]]:gap-2'
25
+
26
+ export const SIDEBAR_MENU_POPUP = `min-w-[240px] w-[calc(var(--anchor-width)_+_10px)] ${SIDEBAR_MENU_GRID}`