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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. package/DESIGN.md +608 -0
  2. package/README.md +61 -39
  3. package/blocks/sidebar-app/examples.mdx +33 -0
  4. package/blocks/sidebar-app/index.tsx +218 -0
  5. package/blocks/sidebar-app/inset-topbar.tsx +25 -0
  6. package/blocks/sidebar-app/menu-grid.ts +26 -0
  7. package/blocks/sidebar-app/nav-data.ts +36 -0
  8. package/blocks/sidebar-app/search-field.tsx +48 -0
  9. package/blocks/sidebar-app/user-footer.tsx +67 -0
  10. package/blocks/sidebar-app/workspace-header.tsx +129 -0
  11. package/components/AI/input-message/examples.mdx +124 -0
  12. package/components/AI/input-message/file-thumbnail.tsx +173 -0
  13. package/components/AI/input-message/index.tsx +1383 -0
  14. package/components/AI/thinking-indicator/examples.mdx +27 -0
  15. package/components/AI/thinking-indicator/index.tsx +210 -0
  16. package/components/accordion/examples.mdx +148 -0
  17. package/components/accordion/index.tsx +1034 -0
  18. package/components/avatar/examples.mdx +95 -0
  19. package/components/avatar/index.tsx +129 -0
  20. package/components/badge/change-demo.tsx +29 -0
  21. package/components/badge/examples.mdx +120 -0
  22. package/components/badge/index.tsx +225 -0
  23. package/components/badge/variants.ts +73 -0
  24. package/components/button/examples.mdx +164 -0
  25. package/components/button/index.tsx +416 -0
  26. package/components/button/loading-demo.tsx +25 -0
  27. package/components/button/variants.ts +166 -0
  28. package/components/card/examples.mdx +212 -0
  29. package/components/card/index.tsx +1037 -0
  30. package/components/copy/examples.mdx +87 -0
  31. package/components/copy/index.tsx +482 -0
  32. package/components/inputs/checkbox/examples.mdx +83 -0
  33. package/components/inputs/checkbox/index.tsx +611 -0
  34. package/components/inputs/checkbox/select-all-demo.tsx +48 -0
  35. package/components/inputs/color-picker/examples.mdx +72 -0
  36. package/components/inputs/color-picker/index.tsx +1955 -0
  37. package/components/inputs/color-picker/variants.ts +401 -0
  38. package/components/inputs/combobox/examples.mdx +273 -0
  39. package/components/inputs/combobox/index.tsx +1436 -0
  40. package/components/inputs/combobox/remote-demo.tsx +101 -0
  41. package/components/inputs/date-time/calendar/examples.mdx +34 -0
  42. package/components/inputs/date-time/calendar/index.tsx +351 -0
  43. package/components/inputs/date-time/calendar/use-month-window.ts +163 -0
  44. package/components/inputs/date-time/date-picker/examples.mdx +166 -0
  45. package/components/inputs/date-time/date-picker/index.tsx +385 -0
  46. package/components/inputs/date-time/date-picker/masked-date-input.tsx +336 -0
  47. package/components/inputs/date-time/date-range-picker/examples.mdx +29 -0
  48. package/components/inputs/date-time/date-range-picker/index.tsx +217 -0
  49. package/components/inputs/date-time/duration-picker/examples.mdx +16 -0
  50. package/components/inputs/date-time/duration-picker/index.tsx +189 -0
  51. package/components/inputs/date-time/month-picker/examples.mdx +16 -0
  52. package/components/inputs/date-time/month-picker/index.tsx +190 -0
  53. package/components/inputs/date-time/picker-shell.tsx +395 -0
  54. package/components/inputs/date-time/time-columns.tsx +91 -0
  55. package/components/inputs/date-time/time-picker/examples.mdx +26 -0
  56. package/components/inputs/date-time/time-picker/index.tsx +166 -0
  57. package/components/inputs/field/examples.mdx +113 -0
  58. package/components/inputs/field/index.tsx +267 -0
  59. package/components/inputs/file-dropzone/examples.mdx +73 -0
  60. package/components/inputs/file-dropzone/file-row.tsx +201 -0
  61. package/components/inputs/file-dropzone/index.tsx +296 -0
  62. package/components/inputs/file-dropzone/upload-demo.tsx +67 -0
  63. package/components/inputs/file-dropzone/variants.ts +94 -0
  64. package/components/inputs/input/examples.mdx +135 -0
  65. package/components/inputs/input/index.tsx +494 -0
  66. package/components/inputs/input/password-demo.tsx +30 -0
  67. package/components/inputs/label/examples.mdx +67 -0
  68. package/components/inputs/label/index.tsx +85 -0
  69. package/components/inputs/number-input/examples.mdx +56 -0
  70. package/components/inputs/number-input/index.tsx +214 -0
  71. package/components/inputs/radio/examples.mdx +77 -0
  72. package/components/inputs/radio/index.tsx +636 -0
  73. package/components/inputs/search/examples.mdx +74 -0
  74. package/components/inputs/search/index.tsx +333 -0
  75. package/components/inputs/select/examples.mdx +218 -0
  76. package/components/inputs/select/index.tsx +862 -0
  77. package/components/inputs/select/variants.ts +32 -0
  78. package/components/inputs/slider/examples.mdx +127 -0
  79. package/components/inputs/slider/index.tsx +1725 -0
  80. package/components/inputs/switch/examples.mdx +94 -0
  81. package/components/inputs/switch/index.tsx +504 -0
  82. package/components/inputs/textarea/examples.mdx +88 -0
  83. package/components/inputs/textarea/index.tsx +161 -0
  84. package/components/navigation/navigation-menu/examples.mdx +109 -0
  85. package/components/navigation/navigation-menu/index.tsx +613 -0
  86. package/components/navigation/pagination/examples.mdx +64 -0
  87. package/components/navigation/pagination/index.tsx +395 -0
  88. package/components/navigation/sidebar/examples.mdx +319 -0
  89. package/components/navigation/sidebar/index.tsx +453 -0
  90. package/components/navigation/sidebar/sidebar-core.tsx +1408 -0
  91. package/components/navigation/sidebar/sidebar-menu.tsx +1495 -0
  92. package/components/navigation/sidebar/sidebar.css +74 -0
  93. package/components/navigation/sidebar/variants.ts +55 -0
  94. package/components/navigation/tabs/examples.mdx +89 -0
  95. package/components/navigation/tabs/index.tsx +494 -0
  96. package/components/navigation/tabs-subtle/examples.mdx +49 -0
  97. package/components/navigation/tabs-subtle/index.tsx +512 -0
  98. package/components/overlays/alert-dialog/examples.mdx +123 -0
  99. package/components/overlays/alert-dialog/index.tsx +258 -0
  100. package/components/overlays/alert-dialog/pending-demo.tsx +36 -0
  101. package/components/overlays/command/examples.mdx +113 -0
  102. package/components/overlays/command/index.tsx +416 -0
  103. package/components/overlays/context-menu/examples.mdx +56 -0
  104. package/components/overlays/context-menu/index.tsx +279 -0
  105. package/components/overlays/dialog/examples.mdx +144 -0
  106. package/components/overlays/dialog/index.tsx +366 -0
  107. package/components/overlays/dropdown/dropdown-search.tsx +395 -0
  108. package/components/overlays/dropdown/examples.mdx +294 -0
  109. package/components/overlays/dropdown/index.tsx +1168 -0
  110. package/components/overlays/dropdown/menu-item.tsx +464 -0
  111. package/components/overlays/dropdown/menu-parts.tsx +660 -0
  112. package/components/overlays/popover/anchored-demo.tsx +59 -0
  113. package/components/overlays/popover/examples.mdx +180 -0
  114. package/components/overlays/popover/index.tsx +315 -0
  115. package/components/overlays/preview-card/examples.mdx +61 -0
  116. package/components/overlays/preview-card/index.tsx +204 -0
  117. package/components/overlays/sheet/examples.mdx +89 -0
  118. package/components/overlays/sheet/index.tsx +247 -0
  119. package/components/overlays/toast/examples.mdx +68 -0
  120. package/components/overlays/toast/global-demo.tsx +72 -0
  121. package/components/overlays/toast/index.tsx +474 -0
  122. package/components/overlays/toast/manager.ts +193 -0
  123. package/components/overlays/tooltip/examples.mdx +106 -0
  124. package/components/overlays/tooltip/index.tsx +519 -0
  125. package/components/progress/examples.mdx +59 -0
  126. package/components/progress/index.tsx +174 -0
  127. package/components/progress/progress.css +49 -0
  128. package/components/progress/step-demo.tsx +27 -0
  129. package/components/table/examples.mdx +184 -0
  130. package/components/table/index.tsx +510 -0
  131. package/components/table/sort-demo.tsx +61 -0
  132. package/dist/base.css +100 -0
  133. package/dist/blocks/sidebar-app/index.js +39 -26
  134. package/dist/components/AI/input-message/index.js +22 -16
  135. package/dist/components/AI/thinking-indicator/index.js +9 -6
  136. package/dist/components/accordion/index.js +16 -13
  137. package/dist/components/avatar/index.d.ts +46 -0
  138. package/dist/components/avatar/index.js +18 -0
  139. package/dist/components/badge/index.d.ts +18 -11
  140. package/dist/components/badge/index.js +11 -7
  141. package/dist/components/badge/variants.d.ts +33 -0
  142. package/dist/components/badge/variants.js +10 -0
  143. package/dist/components/button/index.d.ts +53 -17
  144. package/dist/components/button/index.js +10 -6
  145. package/dist/components/button/variants.d.ts +39 -0
  146. package/dist/components/button/variants.js +8 -0
  147. package/dist/components/card/index.d.ts +16 -3
  148. package/dist/components/card/index.js +16 -11
  149. package/dist/components/copy/index.js +13 -9
  150. package/dist/components/inputs/checkbox/index.d.ts +28 -9
  151. package/dist/components/inputs/checkbox/index.js +17 -13
  152. package/dist/components/inputs/color-picker/index.d.ts +13 -13
  153. package/dist/components/inputs/color-picker/index.js +25 -18
  154. package/dist/components/inputs/color-picker/variants.d.ts +61 -0
  155. package/dist/components/inputs/color-picker/variants.js +28 -0
  156. package/dist/components/inputs/combobox/index.d.ts +60 -17
  157. package/dist/components/inputs/combobox/index.js +26 -18
  158. package/dist/components/inputs/date-time/calendar/index.js +14 -11
  159. package/dist/components/inputs/date-time/date-picker/index.d.ts +53 -11
  160. package/dist/components/inputs/date-time/date-picker/index.js +33 -25
  161. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +3 -3
  162. package/dist/components/inputs/date-time/date-range-picker/index.js +32 -24
  163. package/dist/components/inputs/date-time/duration-picker/index.d.ts +3 -3
  164. package/dist/components/inputs/date-time/duration-picker/index.js +32 -24
  165. package/dist/components/inputs/date-time/month-picker/index.d.ts +3 -3
  166. package/dist/components/inputs/date-time/month-picker/index.js +31 -23
  167. package/dist/components/inputs/date-time/time-picker/index.d.ts +3 -3
  168. package/dist/components/inputs/date-time/time-picker/index.js +32 -24
  169. package/dist/components/inputs/field/index.d.ts +116 -0
  170. package/dist/components/inputs/field/index.js +28 -0
  171. package/dist/components/inputs/file-dropzone/index.d.ts +165 -0
  172. package/dist/components/inputs/file-dropzone/index.js +32 -0
  173. package/dist/components/inputs/file-dropzone/variants.d.ts +35 -0
  174. package/dist/components/inputs/file-dropzone/variants.js +12 -0
  175. package/dist/components/inputs/input/index.d.ts +95 -13
  176. package/dist/components/inputs/input/index.js +17 -9
  177. package/dist/components/inputs/label/index.d.ts +32 -0
  178. package/dist/components/inputs/label/index.js +11 -0
  179. package/dist/components/inputs/number-input/index.d.ts +84 -0
  180. package/dist/components/inputs/number-input/index.js +20 -0
  181. package/dist/components/inputs/radio/index.d.ts +20 -5
  182. package/dist/components/inputs/radio/index.js +15 -12
  183. package/dist/components/inputs/search/index.js +25 -18
  184. package/dist/components/inputs/select/index.d.ts +41 -11
  185. package/dist/components/inputs/select/index.js +27 -14
  186. package/dist/components/inputs/select/variants.d.ts +5 -0
  187. package/dist/components/inputs/select/variants.js +6 -0
  188. package/dist/components/inputs/slider/index.js +7 -5
  189. package/dist/components/inputs/switch/index.d.ts +8 -2
  190. package/dist/components/inputs/switch/index.js +14 -12
  191. package/dist/components/inputs/textarea/index.d.ts +4 -0
  192. package/dist/components/inputs/textarea/index.js +8 -6
  193. package/dist/components/navigation/navigation-menu/index.d.ts +114 -0
  194. package/dist/components/navigation/navigation-menu/index.js +40 -0
  195. package/dist/components/navigation/pagination/index.d.ts +73 -0
  196. package/dist/components/navigation/pagination/index.js +39 -0
  197. package/dist/components/navigation/sidebar/index.d.ts +44 -21
  198. package/dist/components/navigation/sidebar/index.js +41 -31
  199. package/dist/components/navigation/sidebar/variants.d.ts +25 -0
  200. package/dist/components/navigation/sidebar/variants.js +28 -0
  201. package/dist/components/navigation/tabs/index.d.ts +8 -2
  202. package/dist/components/navigation/tabs/index.js +14 -10
  203. package/dist/components/navigation/tabs-subtle/index.js +12 -10
  204. package/dist/components/overlays/alert-dialog/index.d.ts +121 -15
  205. package/dist/components/overlays/alert-dialog/index.js +22 -13
  206. package/dist/components/overlays/command/index.js +24 -17
  207. package/dist/components/overlays/context-menu/index.d.ts +33 -17
  208. package/dist/components/overlays/context-menu/index.js +27 -18
  209. package/dist/components/overlays/dialog/index.js +17 -12
  210. package/dist/components/overlays/dropdown/index.d.ts +257 -31
  211. package/dist/components/overlays/dropdown/index.js +42 -17
  212. package/dist/components/overlays/popover/index.d.ts +31 -9
  213. package/dist/components/overlays/popover/index.js +12 -9
  214. package/dist/components/overlays/preview-card/index.js +11 -8
  215. package/dist/components/overlays/sheet/index.js +18 -13
  216. package/dist/components/overlays/toast/index.d.ts +123 -47
  217. package/dist/components/overlays/toast/index.js +18 -10
  218. package/dist/components/overlays/tooltip/index.d.ts +113 -21
  219. package/dist/components/overlays/tooltip/index.js +15 -6
  220. package/dist/components/progress/index.d.ts +56 -0
  221. package/dist/components/progress/index.js +15 -0
  222. package/dist/components/table/index.d.ts +63 -8
  223. package/dist/components/table/index.js +19 -9
  224. package/dist/index.d.ts +2201 -736
  225. package/dist/index.js +223 -82
  226. package/dist/lib/date-locale.d.ts +14 -0
  227. package/dist/lib/date-locale.js +16 -0
  228. package/dist/lib/field-classes.d.ts +144 -0
  229. package/dist/lib/field-classes.js +40 -0
  230. package/dist/lib/field-variants.d.ts +47 -0
  231. package/dist/lib/field-variants.js +26 -0
  232. package/dist/lib/fluid-hover-highlight.d.ts +112 -0
  233. package/dist/lib/fluid-hover-highlight.js +17 -0
  234. package/dist/lib/focus-ring.d.ts +18 -0
  235. package/dist/lib/focus-ring.js +10 -0
  236. package/dist/lib/goo-indicator.d.ts +46 -0
  237. package/dist/lib/goo-indicator.js +14 -0
  238. package/dist/lib/icon-context.d.ts +30 -0
  239. package/dist/lib/icon-context.js +12 -0
  240. package/dist/lib/inert.d.ts +9 -0
  241. package/dist/lib/inert.js +8 -0
  242. package/dist/lib/morph-layers.d.ts +93 -0
  243. package/dist/lib/morph-layers.js +13 -0
  244. package/dist/lib/morph-part.d.ts +21 -0
  245. package/dist/lib/morph-part.js +12 -0
  246. package/dist/lib/popup.d.ts +20 -0
  247. package/dist/lib/popup.js +12 -0
  248. package/dist/lib/reduced-motion.d.ts +9 -0
  249. package/dist/lib/reduced-motion.js +6 -0
  250. package/dist/lib/row-index.d.ts +10 -0
  251. package/dist/lib/row-index.js +8 -0
  252. package/dist/lib/row-order.d.ts +28 -0
  253. package/dist/lib/row-order.js +10 -0
  254. package/dist/lib/scroll-column.d.ts +27 -0
  255. package/dist/lib/scroll-column.js +18 -0
  256. package/dist/lib/shape-context.d.ts +30 -0
  257. package/dist/lib/shape-context.js +12 -0
  258. package/dist/lib/size-context.d.ts +94 -0
  259. package/dist/lib/size-context.js +20 -0
  260. package/dist/lib/size-scale.d.ts +79 -0
  261. package/dist/lib/size-scale.js +8 -0
  262. package/dist/lib/slot.d.ts +12 -0
  263. package/dist/lib/slot.js +6 -0
  264. package/dist/lib/springs.d.ts +44 -0
  265. package/dist/lib/springs.js +8 -0
  266. package/dist/lib/surface-classes.d.ts +19 -0
  267. package/dist/lib/surface-classes.js +10 -0
  268. package/dist/lib/surface-context.d.ts +9 -0
  269. package/dist/lib/surface-context.js +8 -0
  270. package/dist/lib/use-controllable-state.d.ts +2 -0
  271. package/dist/lib/use-controllable-state.js +6 -0
  272. package/dist/lib/use-fluid-hover.d.ts +138 -0
  273. package/dist/lib/use-fluid-hover.js +14 -0
  274. package/dist/lib/use-is-mobile.d.ts +2 -0
  275. package/dist/lib/use-is-mobile.js +6 -0
  276. package/dist/lib/use-keyboard-nav-gate.d.ts +20 -0
  277. package/dist/lib/use-keyboard-nav-gate.js +7 -0
  278. package/dist/lib/use-merge-split.d.ts +47 -0
  279. package/dist/lib/use-merge-split.js +15 -0
  280. package/dist/lib/use-morph.d.ts +101 -0
  281. package/dist/lib/use-morph.js +20 -0
  282. package/dist/lib/use-touch-primary.d.ts +2 -0
  283. package/dist/lib/use-touch-primary.js +6 -0
  284. package/dist/lib/utils.d.ts +3 -0
  285. package/dist/lib/utils.js +6 -0
  286. package/dist/preset.d.ts +17 -0
  287. package/dist/preset.js +22 -2
  288. package/dist/primitives/code/index.js +7 -5
  289. package/dist/primitives/craft/index.js +9 -6
  290. package/dist/primitives/fluid-hover/index.js +9 -8
  291. package/dist/primitives/morph/index.js +15 -10
  292. package/dist/primitives/motion/index.js +10 -8
  293. package/dist/primitives/scroll-area/index.js +4 -3
  294. package/dist/primitives/separator/index.d.ts +25 -0
  295. package/dist/primitives/separator/index.js +9 -0
  296. package/dist/primitives/sizes/index.js +6 -4
  297. package/dist/primitives/skeleton/index.d.ts +35 -0
  298. package/dist/primitives/skeleton/index.js +12 -0
  299. package/dist/primitives/surface/index.js +6 -4
  300. package/dist/shared/{components-3mkepka5.js → components-0jmqhx2k.js} +28 -25
  301. package/dist/shared/components-124epb7f.js +53 -0
  302. package/dist/shared/{components-gz8zf3ec.js → components-1k0wv53d.js} +15 -43
  303. package/dist/shared/components-26snnbs9.js +322 -0
  304. package/dist/shared/{components-2dxkv6zm.js → components-2c0qcfx4.js} +8 -7
  305. package/dist/shared/{components-473g814w.js → components-2mt9te6d.js} +243 -137
  306. package/dist/shared/{components-hawvy2qz.js → components-2z48g4fp.js} +9 -30
  307. package/dist/shared/components-34txk9rj.js +35 -0
  308. package/dist/shared/components-3h92d94m.js +23 -0
  309. package/dist/shared/{components-zfnmn5d4.js → components-3sp623ae.js} +36 -31
  310. package/dist/shared/{components-jwst3vtj.js → components-3wxkjgqn.js} +186 -76
  311. package/dist/shared/{components-s2bz1xpk.js → components-49bsynbw.js} +8 -5
  312. package/dist/shared/components-4bwsd46w.js +71 -0
  313. package/dist/shared/components-4n4f5m3e.js +297 -0
  314. package/dist/shared/{components-0q0sqfh4.js → components-4y816hwp.js} +27 -49
  315. package/dist/shared/components-52ngsgca.js +399 -0
  316. package/dist/shared/{components-y9y0g8er.js → components-52r5763g.js} +22 -19
  317. package/dist/shared/components-559tf7yb.js +28 -0
  318. package/dist/shared/components-5mkga9hj.js +440 -0
  319. package/dist/shared/{components-kwa6mrf9.js → components-5s0nfqvw.js} +14 -9
  320. package/dist/shared/{components-dk0gjr5a.js → components-60atskhp.js} +19 -18
  321. package/dist/shared/{components-rr7a6ja9.js → components-658b26nb.js} +21 -18
  322. package/dist/shared/{components-xxvdw7k1.js → components-65vwfjs6.js} +37 -34
  323. package/dist/shared/{components-frw34b65.js → components-6j2fh74k.js} +114 -123
  324. package/dist/shared/components-6zg1yft7.js +167 -0
  325. package/dist/shared/components-6zng858q.js +53 -0
  326. package/dist/shared/components-72w4ge68.js +64 -0
  327. package/dist/shared/{components-cv16mryv.js → components-78x40gee.js} +13 -7
  328. package/dist/shared/{components-b2wyq2cq.js → components-7d4r7hh1.js} +39 -57
  329. package/dist/shared/components-7ntvvegr.js +316 -0
  330. package/dist/shared/components-8nwsqjn8.js +34 -0
  331. package/dist/shared/{components-5xc2j8jz.js → components-8tt3xbb5.js} +19 -18
  332. package/dist/shared/{components-vc7vq5nz.js → components-9as3rf68.js} +6 -3
  333. package/dist/shared/components-9mys9jna.js +255 -0
  334. package/dist/shared/components-adkarj8n.js +35 -0
  335. package/dist/shared/{components-b3c5et5b.js → components-ae9fncz4.js} +32 -31
  336. package/dist/shared/{components-hqeq3w9q.js → components-awra5sff.js} +24 -16
  337. package/dist/shared/components-b1e239g3.js +28 -0
  338. package/dist/shared/{components-q5xj5c16.js → components-b1f2bjf1.js} +9 -8
  339. package/dist/shared/{components-qmmjx1qw.js → components-bn42vq6d.js} +71 -72
  340. package/dist/shared/components-c96g82th.js +1444 -0
  341. package/dist/shared/components-c9yr4ed1.js +44 -0
  342. package/dist/shared/components-d3shbwqq.js +95 -0
  343. package/dist/shared/{components-jjw69c4c.js → components-dcc02hs2.js} +7 -4
  344. package/dist/shared/{components-men63rwj.js → components-de99ykt7.js} +3 -2
  345. package/dist/shared/components-dhgk6mbt.js +16 -0
  346. package/dist/shared/components-drxrtnp2.js +25 -0
  347. package/dist/shared/{components-6p8tfcd6.js → components-ds9fqat9.js} +21 -18
  348. package/dist/shared/{components-mjthm1kf.js → components-egy4tyjt.js} +11 -8
  349. package/dist/shared/components-f0xfpkhh.js +82 -0
  350. package/dist/shared/components-f7h57m6t.js +59 -0
  351. package/dist/shared/{components-d6xtjht3.js → components-fjkpbg6n.js} +33 -30
  352. package/dist/shared/{components-gc5vtq2f.js → components-gh0qeey3.js} +63 -29
  353. package/dist/shared/{components-39jq9va1.js → components-gvbapfen.js} +29 -24
  354. package/dist/shared/{components-5vgpnxgy.js → components-gzesg7hy.js} +19 -16
  355. package/dist/shared/components-h6p86be3.js +19 -0
  356. package/dist/shared/components-h83az0bz.js +33 -0
  357. package/dist/shared/components-jjrj6ymn.js +104 -0
  358. package/dist/shared/{components-wvp1m52g.js → components-jp5v0m0f.js} +15 -14
  359. package/dist/shared/{components-zrkzcayz.js → components-jqpefjsn.js} +54 -6
  360. package/dist/shared/{components-1z6tdadk.js → components-k74j4hwh.js} +20 -17
  361. package/dist/shared/components-kswxnm3h.js +24 -0
  362. package/dist/shared/{components-jbbmdr74.js → components-mkkbbgb7.js} +36 -17
  363. package/dist/shared/{components-pbmqctfa.js → components-mpw667rb.js} +169 -65
  364. package/dist/shared/{components-s5ngg32c.js → components-paa08427.js} +121 -47
  365. package/dist/shared/{components-vfbhxf5r.js → components-pd42emw3.js} +6 -5
  366. package/dist/shared/{components-hw9yk32z.js → components-pmfszv7a.js} +35 -23
  367. package/dist/shared/{components-chacnk61.js → components-pw06rbxq.js} +19 -19
  368. package/dist/shared/components-q03cktx8.js +246 -0
  369. package/dist/shared/{components-kvr7f9hy.js → components-qdm3yh26.js} +19 -10
  370. package/dist/shared/{components-27nnvrwe.js → components-qs4rkqsj.js} +15 -14
  371. package/dist/shared/{components-s083f38z.js → components-r6gbmkew.js} +22 -21
  372. package/dist/shared/{components-ta7shw28.js → components-r8eqs7gz.js} +4 -4
  373. package/dist/shared/{components-sn1rxx1d.js → components-rm02h9nt.js} +12 -11
  374. package/dist/shared/{components-c6s6b43g.js → components-rrbpmfx4.js} +3 -2
  375. package/dist/shared/{components-n7mjr357.js → components-tz1fv35x.js} +3 -2
  376. package/dist/shared/components-tz4gaf1v.js +23 -0
  377. package/dist/shared/{components-zx6b6yby.js → components-vf45j12c.js} +4 -18
  378. package/dist/shared/{components-3ky44908.js → components-vfjc9jzz.js} +2 -3
  379. package/dist/shared/{components-39vmfq9s.js → components-vsa625vk.js} +21 -20
  380. package/dist/shared/components-wawnmkph.js +500 -0
  381. package/dist/shared/components-wktrrb4m.js +38 -0
  382. package/dist/shared/{components-8snj490h.js → components-wwd363me.js} +78 -32
  383. package/dist/shared/{components-5pnw8183.js → components-xcwvgcrd.js} +23 -22
  384. package/dist/shared/components-xdqmt6ed.js +20 -0
  385. package/dist/shared/{components-h2ajmhwn.js → components-y1w3a6wx.js} +107 -35
  386. package/dist/shared/{components-ar66gnt0.js → components-y6578b8h.js} +37 -34
  387. package/dist/shared/{components-59efxjd9.js → components-yhgq0qxx.js} +13 -12
  388. package/dist/shared/{components-jthe1pwb.js → components-yz84gpkq.js} +101 -404
  389. package/dist/shared/components-z3v0n0de.js +11 -0
  390. package/dist/shared/{components-7r3etddy.js → components-z9n19fyk.js} +19 -54
  391. package/dist/shared/components-zbvjzh1p.js +141 -0
  392. package/dist/styles.css +241 -16
  393. package/dist/tailwind.css +13 -0
  394. package/dist/tokens.css +34 -0
  395. package/index.ts +60 -0
  396. package/lib/date-locale.ts +50 -0
  397. package/lib/field-classes.ts +115 -0
  398. package/lib/field-variants.ts +117 -0
  399. package/lib/fluid-hover-highlight.tsx +139 -0
  400. package/lib/focus-ring.tsx +46 -0
  401. package/lib/goo-indicator.tsx +261 -0
  402. package/lib/icon-context.tsx +310 -0
  403. package/lib/inert.ts +22 -0
  404. package/lib/morph-layers.tsx +102 -0
  405. package/lib/morph-part.tsx +49 -0
  406. package/lib/popup.ts +52 -0
  407. package/lib/reduced-motion.ts +19 -0
  408. package/lib/row-index.tsx +29 -0
  409. package/lib/row-order.tsx +115 -0
  410. package/lib/scroll-column.tsx +244 -0
  411. package/lib/shape-context.tsx +124 -0
  412. package/lib/size-context.tsx +66 -0
  413. package/lib/size-scale.ts +115 -0
  414. package/lib/slot.ts +25 -0
  415. package/lib/springs.ts +62 -0
  416. package/lib/surface-classes.ts +58 -0
  417. package/lib/surface-context.tsx +21 -0
  418. package/lib/use-controllable-state.ts +44 -0
  419. package/lib/use-fluid-hover.ts +541 -0
  420. package/lib/use-is-mobile.ts +25 -0
  421. package/lib/use-keyboard-nav-gate.ts +45 -0
  422. package/lib/use-merge-split.tsx +209 -0
  423. package/lib/use-morph.ts +557 -0
  424. package/lib/use-touch-primary.tsx +43 -0
  425. package/lib/utils.ts +37 -0
  426. package/package.json +177 -4
  427. package/preset.ts +196 -0
  428. package/primitives/code/examples.mdx +27 -0
  429. package/primitives/code/index.tsx +80 -0
  430. package/primitives/craft/demos.tsx +173 -0
  431. package/primitives/craft/examples.mdx +138 -0
  432. package/primitives/craft/index.tsx +76 -0
  433. package/primitives/fluid-hover/examples.mdx +84 -0
  434. package/primitives/fluid-hover/index.tsx +316 -0
  435. package/primitives/morph/examples.mdx +135 -0
  436. package/primitives/morph/index.tsx +192 -0
  437. package/primitives/motion/examples.mdx +100 -0
  438. package/primitives/motion/index.tsx +160 -0
  439. package/primitives/scroll-area/examples.mdx +58 -0
  440. package/primitives/scroll-area/index.tsx +200 -0
  441. package/primitives/separator/examples.mdx +30 -0
  442. package/primitives/separator/index.tsx +53 -0
  443. package/primitives/sizes/examples.mdx +167 -0
  444. package/primitives/sizes/index.tsx +89 -0
  445. package/primitives/skeleton/examples.mdx +51 -0
  446. package/primitives/skeleton/index.tsx +72 -0
  447. package/primitives/skeleton/skeleton.css +15 -0
  448. package/primitives/surface/examples.mdx +173 -0
  449. package/primitives/surface/index.tsx +109 -0
  450. package/primitives/surface/surface.css +176 -0
  451. package/dist/shared/components-0sn7ypjw.js +0 -61
  452. package/dist/shared/components-1434p1mh.js +0 -123
  453. package/dist/shared/components-a86dytt0.js +0 -986
  454. package/dist/shared/components-gf5s6ap9.js +0 -42
  455. package/dist/shared/components-hyq23phv.js +0 -310
  456. package/dist/shared/components-ng72k97e.js +0 -128
  457. package/dist/shared/components-raknhppb.js +0 -64
  458. package/dist/shared/components-vynjtrk6.js +0 -19
  459. package/dist/shared/components-wy9jpqny.js +0 -146
  460. /package/dist/shared/{components-1v7rxytx.js → components-3ey0hzpb.js} +0 -0
  461. /package/dist/shared/{components-ezxhrmer.js → components-jcnjay5g.js} +0 -0
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): shared parts for morphing
3
+ * overlays. A part inside the trigger and its twin inside the overlay share a
4
+ * motion `layoutId`, so the part flies from one to the other while the surface
5
+ * morphs, like motion-primitives' morphing dialog image and title
6
+ * (github.com/ibelick/motion-primitives `components/core/morphing-dialog.tsx`
7
+ * @ 120f64f6ca60348e251f929e9c81f11ccbe45eda — MIT © 2024 ibelick, notice:
8
+ * LICENSE.motion-primitives).
9
+ */
10
+
11
+ import { motion } from 'motion/react'
12
+ import { createContext, type ReactNode, useContext } from 'react'
13
+ import { useReduceMotion } from './reduced-motion'
14
+ import { spring } from './springs'
15
+
16
+ /** Set by an overlay root: the id its parts are scoped to and whether it is open. */
17
+ const MorphPartScope = createContext<{ id: string; open: boolean } | null>(null)
18
+
19
+ /** True inside an overlay's surface (`MorphSurface` provides it). */
20
+ const MorphPartInOverlay = createContext(false)
21
+
22
+ const { exit: _exit, ...enter } = spring.slow
23
+
24
+ interface MorphPartProps {
25
+ /** Pairs the part in the trigger with its twin in the overlay; unique within one overlay. */
26
+ id: string
27
+ /** Classes for the part's box. */
28
+ className?: string
29
+ /** The part's content: an image, a title. */
30
+ children?: ReactNode
31
+ }
32
+
33
+ function MorphPart({ id, className, children }: MorphPartProps) {
34
+ const scope = useContext(MorphPartScope)
35
+ const inOverlay = useContext(MorphPartInOverlay)
36
+ const reduced = useReduceMotion()
37
+ // The overlay's twin lets go of the id as soon as the close starts, so the
38
+ // trigger's part flies back while Base UI still shows the closing overlay.
39
+ const layoutId = scope && !reduced && (scope.open || !inOverlay) ? `${scope.id}-${id}` : undefined
40
+
41
+ return (
42
+ <motion.div layoutId={layoutId} transition={enter} className={className}>
43
+ {children}
44
+ </motion.div>
45
+ )
46
+ }
47
+
48
+ export type { MorphPartProps }
49
+ export { MorphPart, MorphPartInOverlay, MorphPartScope }
package/lib/popup.ts ADDED
@@ -0,0 +1,52 @@
1
+ /*
2
+ * Vendored from Fluid Functionalism — `registry/default/lib/popup.ts` at
3
+ * github.com/mickadesign/fluid-functionalism@b3587bdbd83fc66c2a6aae3817ffb856cb09260b
4
+ * (fluidfunctionalism.com). MIT License © 2026 Micka Touillaud — see
5
+ * LICENSE.fluid-functionalism in this package.
6
+ *
7
+ * Local modifications:
8
+ * - `popupMotionClass` (the side-aware transform origin and slide offset of
9
+ * the old `scaleY` enter/exit) dropped: the dropdown, select and combobox
10
+ * popups now morph out of their trigger through `lib/use-morph.ts`.
11
+ */
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Shared popup chrome for the dropdown, select, and combobox popups: the
15
+ // ScrollArea sizing the list scrolls inside. One definition, so the three
16
+ // popups never drift apart.
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Popup lists scroll inside ScrollArea: the hover-revealed thumb instead of
21
+ * the platform scrollbar, and a scroll-aware fade on the viewport. The popup
22
+ * is only max-height constrained, so the viewport's percentage height can't
23
+ * resolve — it inherits the max-height instead (the ScrollArea root inherits
24
+ * it from the popup) and sizes to its rows up to that. The scroll primitive's
25
+ * inline-styled sizer is forced back to a plain block so rows can shrink and
26
+ * truncate; the `[style]` qualifier keeps that off the rows' own wrapper on
27
+ * touch devices, where ScrollArea renders no sizer.
28
+ */
29
+ export const popupScrollAreaClass = 'min-h-0 flex-1 max-h-[inherit]'
30
+ export const popupViewportClass =
31
+ '!h-auto max-h-[inherit] [&>div[style]]:!block [&>div[style]]:!min-w-0 [--scroll-fade-size:32px]'
32
+
33
+ /** Keys that count as keyboard navigation inside a popup (earn the ring). */
34
+ export const POPUP_NAV_KEYS = [
35
+ 'ArrowDown',
36
+ 'ArrowUp',
37
+ 'ArrowLeft',
38
+ 'ArrowRight',
39
+ 'Home',
40
+ 'End',
41
+ 'PageUp',
42
+ 'PageDown',
43
+ 'Tab',
44
+ ]
45
+
46
+ /**
47
+ * Rows the fluid hover must skip: a disabled row is neither a target
48
+ * nor a hover stop, whichever attribute the primitive marks it with.
49
+ */
50
+ export function isDisabledRow(el: HTMLElement): boolean {
51
+ return el.getAttribute('aria-disabled') === 'true' || el.hasAttribute('data-disabled')
52
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): the one reduced-motion
3
+ * read for the morph layer and the liquid indicators.
4
+ */
5
+
6
+ import { useReducedMotion, useReducedMotionConfig } from 'motion/react'
7
+
8
+ /**
9
+ * Reduce motion when the OS asks for it or a surrounding `MotionConfig` does.
10
+ * motion's `useReducedMotionConfig` alone follows the OS only under
11
+ * `<MotionConfig reducedMotion="user">`; without that wrapper it defaults to
12
+ * "never", so a page that sets none would animate for a user who turned
13
+ * motion off.
14
+ */
15
+ export function useReduceMotion() {
16
+ const os = useReducedMotion() ?? false
17
+ const config = useReducedMotionConfig() ?? false
18
+ return os || config
19
+ }
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): numbers a group's rows in
3
+ * order (Checkbox.Group, Radio.Group, Switch.Group), so a row needs no
4
+ * `index` for the fluid hover and the group's index-keyed state.
5
+ */
6
+
7
+ import { Children, createContext, isValidElement, type ReactNode, useContext } from 'react'
8
+
9
+ const RowIndexContext = createContext(0)
10
+
11
+ /** Gives each element child its position among them. */
12
+ function IndexedRows({ children }: { children: ReactNode }) {
13
+ let index = 0
14
+ return (
15
+ <>
16
+ {Children.map(children, child =>
17
+ isValidElement(child) ? <RowIndexContext.Provider value={index++}>{child}</RowIndexContext.Provider> : child,
18
+ )}
19
+ </>
20
+ )
21
+ }
22
+
23
+ /** A row's index: its own `index`, else its position in the group. */
24
+ function useRowIndex(own?: number) {
25
+ const position = useContext(RowIndexContext)
26
+ return own ?? position
27
+ }
28
+
29
+ export { IndexedRows, useRowIndex }
@@ -0,0 +1,115 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): numbers a menu's rows in
3
+ * document order, so a `Dropdown.Item` needs no `index` for the fluid hover,
4
+ * the keyboard ring and the selection backgrounds. Unlike `lib/row-index.tsx`
5
+ * (direct children only) it reaches rows anywhere below the panel: inside a
6
+ * `Dropdown.Group`, behind a condition, out of a `map`. Labels, separators and
7
+ * groups take a number too without registering a rect, so a selection run
8
+ * never melts across them.
9
+ */
10
+
11
+ import {
12
+ createContext,
13
+ type RefObject,
14
+ useCallback,
15
+ useContext,
16
+ useId,
17
+ useLayoutEffect,
18
+ useMemo,
19
+ useReducer,
20
+ useRef,
21
+ useState,
22
+ } from 'react'
23
+
24
+ interface Slot {
25
+ element: HTMLElement
26
+ /** The row's own `index`; the numbering starts after the highest one. */
27
+ explicit?: number
28
+ /** A label, separator or group: numbered, never a row. */
29
+ gap?: boolean
30
+ }
31
+
32
+ interface RowOrder {
33
+ register: (id: string, slot: Slot) => () => void
34
+ order: ReadonlyMap<string, number>
35
+ /** The first row's number: the inline panel's roving tab stop. */
36
+ first?: number
37
+ }
38
+
39
+ const EMPTY: ReadonlyMap<string, number> = new Map()
40
+
41
+ const RowOrderContext = createContext<RowOrder | null>(null)
42
+
43
+ const before = (a: Slot, b: Slot) =>
44
+ a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
45
+
46
+ function number(slots: Map<string, Slot>) {
47
+ let next = 0
48
+ for (const slot of slots.values()) if (slot.explicit !== undefined) next = Math.max(next, slot.explicit + 1)
49
+ const order = new Map<string, number>()
50
+ let first: number | undefined
51
+ for (const [id, slot] of [...slots].sort(([, a], [, b]) => before(a, b))) {
52
+ if (slot.explicit !== undefined) continue
53
+ if (!slot.gap) first ??= next
54
+ order.set(id, next++)
55
+ }
56
+ return { order, first }
57
+ }
58
+
59
+ interface Numbering {
60
+ order: ReadonlyMap<string, number>
61
+ first?: number
62
+ }
63
+
64
+ function same({ order: a, first: firstA }: Numbering, { order: b, first: firstB }: Numbering) {
65
+ if (firstA !== firstB || a.size !== b.size) return false
66
+ for (const [id, index] of a) if (b.get(id) !== index) return false
67
+ return true
68
+ }
69
+
70
+ /** A panel's numbering. Provide the result through `RowOrderContext`. */
71
+ function useRowOrder(): RowOrder {
72
+ const slots = useRef(new Map<string, Slot>())
73
+ const [numbering, setNumbering] = useState<Numbering>({ order: EMPTY })
74
+ const [, changed] = useReducer((n: number) => n + 1, 0)
75
+
76
+ const register = useCallback((id: string, slot: Slot) => {
77
+ slots.current.set(id, slot)
78
+ changed()
79
+ return () => {
80
+ slots.current.delete(id)
81
+ changed()
82
+ }
83
+ }, [])
84
+
85
+ // Every render: a re-ordered list moves no row in or out, so only the DOM
86
+ // knows. Rows register in their own layout effects, which run first.
87
+ useLayoutEffect(() => {
88
+ const next = number(slots.current)
89
+ setNumbering(prev => (same(prev, next) ? prev : next))
90
+ })
91
+
92
+ return useMemo(() => ({ register, ...numbering }), [register, numbering])
93
+ }
94
+
95
+ /**
96
+ * Takes a place in the surrounding panel's order and returns the row's index
97
+ * (`explicit` when given, else its position; undefined until the first layout
98
+ * pass, and outside a panel) and the panel's first row.
99
+ */
100
+ function useRowSlot(ref: RefObject<HTMLElement | null>, explicit?: number, gap?: boolean) {
101
+ const ctx = useContext(RowOrderContext)
102
+ const id = useId()
103
+ const register = ctx?.register
104
+
105
+ useLayoutEffect(() => {
106
+ const element = ref.current
107
+ if (!register || !element) return
108
+ return register(id, { element, explicit, gap })
109
+ }, [register, id, explicit, gap, ref])
110
+
111
+ return { index: explicit ?? ctx?.order.get(id), firstRow: ctx?.first }
112
+ }
113
+
114
+ export type { RowOrder }
115
+ export { RowOrderContext, useRowOrder, useRowSlot }
@@ -0,0 +1,244 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): the scrolling option list
3
+ * the date and time pickers stack beside their calendar (months, years,
4
+ * hours, minutes), or lay out as a strip above it on a narrow panel.
5
+ */
6
+
7
+ import { type KeyboardEvent, type ReactNode, useEffect, useRef } from 'react'
8
+ import { FluidHoverHighlight } from './fluid-hover-highlight'
9
+ import { useReduceMotion } from './reduced-motion'
10
+ import { useShape } from './shape-context'
11
+ import { useSize } from './size-context'
12
+ import { useFluidHover, useRegisterFluidHoverItem } from './use-fluid-hover'
13
+ import { cn } from './utils'
14
+
15
+ interface ScrollColumnOption<T extends number | string> {
16
+ value: T
17
+ label: ReactNode
18
+ disabled?: boolean
19
+ }
20
+
21
+ interface ScrollColumnProps<T extends number | string> {
22
+ /** Accessible name of the list ("Month", "Hour"). */
23
+ label: string
24
+ options: ScrollColumnOption<T>[]
25
+ /** The picked option: a dot and a heavier weight. */
26
+ value: T | null
27
+ onValueChange: (value: T) => void
28
+ /** Options to band, as a scrollbar thumb would (the months the calendar shows). */
29
+ inView?: (value: T) => boolean
30
+ /** The option kept centred. Defaults to `value`. */
31
+ follow?: T | null
32
+ /** A column, or a strip on a narrow panel. Defaults to `'vertical'`. */
33
+ orientation?: 'vertical' | 'horizontal'
34
+ className?: string
35
+ }
36
+
37
+ /**
38
+ * Scrolls `list` so `item` sits in its middle, on whichever axis it scrolls.
39
+ * Measured in layout pixels: a panel still scaled by its opening morph
40
+ * reports shrunken client rects.
41
+ */
42
+ function centre(list: HTMLElement, item: HTMLElement, behavior: ScrollBehavior) {
43
+ const l = list.getBoundingClientRect()
44
+ const i = item.getBoundingClientRect()
45
+ const sx = l.width / list.offsetWidth || 1
46
+ const sy = l.height / list.offsetHeight || 1
47
+ list.scrollTo({
48
+ top: list.scrollTop + (i.top - l.top) / sy - (list.clientHeight - item.offsetHeight) / 2,
49
+ left: list.scrollLeft + (i.left - l.left) / sx - (list.clientWidth - item.offsetWidth) / 2,
50
+ behavior,
51
+ })
52
+ }
53
+
54
+ /** For a row of columns: a hairline between neighbours. */
55
+ const dividedColumns = 'flex [&>*+*]:border-s [&>*]:border-border'
56
+
57
+ const STEP: Record<string, number> = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }
58
+
59
+ function ScrollColumn<T extends number | string>({
60
+ label,
61
+ options,
62
+ value,
63
+ onValueChange,
64
+ inView,
65
+ follow = value,
66
+ orientation = 'vertical',
67
+ className,
68
+ }: ScrollColumnProps<T>) {
69
+ const listRef = useRef<HTMLDivElement>(null)
70
+ const containerRef = useRef<HTMLDivElement>(null)
71
+ const pointerInside = useRef(false)
72
+ const centred = useRef(false)
73
+ const reduceMotion = useReduceMotion()
74
+ const horizontal = orientation === 'horizontal'
75
+ const hover = useFluidHover(containerRef, { axis: horizontal ? 'x' : 'y' })
76
+ const shape = useShape()
77
+
78
+ // Recentres only when the followed option (or the axis) changes.
79
+ const axis = useRef(horizontal)
80
+ useEffect(() => {
81
+ const list = listRef.current
82
+ const item = list?.querySelector<HTMLElement>(`[data-value="${follow}"]`)
83
+ if (!list || !item || pointerInside.current) return
84
+ // A jump when the list opens or turns: it lands on its option.
85
+ const glide = centred.current && axis.current === horizontal && !reduceMotion
86
+ centre(list, item, glide ? 'smooth' : 'instant')
87
+ centred.current = true
88
+ axis.current = horizontal
89
+ }, [follow, horizontal])
90
+
91
+ // A resize (the panel settling its width, a strip turning into a column)
92
+ // moves the followed option off centre: put it back at once.
93
+ const followed = useRef(follow)
94
+ followed.current = follow
95
+ useEffect(() => {
96
+ const list = listRef.current
97
+ if (!list) return
98
+ const observer = new ResizeObserver(() => {
99
+ const item = list.querySelector<HTMLElement>(`[data-value="${followed.current}"]`)
100
+ if (item && !pointerInside.current) centre(list, item, 'instant')
101
+ })
102
+ observer.observe(list)
103
+ return () => observer.disconnect()
104
+ }, [])
105
+
106
+ // A run of banded options reads as one band: only its ends are rounded.
107
+ const banded = options.map(o => inView?.(o.value) ?? false)
108
+
109
+ const tabStop = options.some(o => o.value === value) ? value : (follow ?? options[0]?.value)
110
+
111
+ const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
112
+ const items = [
113
+ ...(containerRef.current?.querySelectorAll<HTMLButtonElement>('[role="option"]:not(:disabled)') ?? []),
114
+ ]
115
+ const at = items.indexOf(document.activeElement as HTMLButtonElement)
116
+ const next =
117
+ e.key === 'Home' ? 0 : e.key === 'End' ? items.length - 1 : STEP[e.key] ? at + (STEP[e.key] ?? 0) : undefined
118
+ if (next === undefined) return
119
+ e.preventDefault()
120
+ const item = items[Math.max(0, Math.min(items.length - 1, next))]
121
+ item?.focus({ preventScroll: true })
122
+ if (item && listRef.current) centre(listRef.current, item, reduceMotion ? 'instant' : 'smooth')
123
+ }
124
+
125
+ return (
126
+ <div
127
+ ref={listRef}
128
+ role="listbox"
129
+ aria-label={label}
130
+ aria-orientation={orientation}
131
+ onKeyDown={onKeyDown}
132
+ onPointerEnter={() => {
133
+ pointerInside.current = true
134
+ }}
135
+ onPointerLeave={() => {
136
+ pointerInside.current = false
137
+ }}
138
+ className={cn(
139
+ 'scrollbar-hide [--scroll-fade-size:24px]',
140
+ horizontal
141
+ ? 'overflow-x-auto overflow-y-hidden scroll-fade-x'
142
+ : 'overflow-y-auto overflow-x-hidden scroll-fade',
143
+ className,
144
+ )}
145
+ >
146
+ {/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: the fluid hover tracks the pointer here; the options inside are the buttons. */}
147
+ <div
148
+ ref={containerRef}
149
+ onMouseMove={hover.handlers.onMouseMove}
150
+ onMouseEnter={hover.handlers.onMouseEnter}
151
+ onMouseLeave={hover.handlers.onMouseLeave}
152
+ onClick={hover.handlers.onClick}
153
+ className={cn('relative flex p-1', horizontal ? 'w-max flex-row' : 'flex-col')}
154
+ >
155
+ <FluidHoverHighlight hover={hover} className={shape.bg} />
156
+ {options.map((option, index) => (
157
+ <ScrollColumnItem
158
+ key={option.value}
159
+ index={index}
160
+ registerItem={hover.registerItem}
161
+ option={option}
162
+ selected={option.value === value}
163
+ band={
164
+ !banded[index]
165
+ ? undefined
166
+ : banded[index - 1]
167
+ ? banded[index + 1]
168
+ ? 'middle'
169
+ : 'end'
170
+ : banded[index + 1]
171
+ ? 'start'
172
+ : 'single'
173
+ }
174
+ horizontal={horizontal}
175
+ tabStop={option.value === tabStop}
176
+ onSelect={() => onValueChange(option.value)}
177
+ />
178
+ ))}
179
+ </div>
180
+ </div>
181
+ )
182
+ }
183
+
184
+ function ScrollColumnItem<T extends number | string>({
185
+ index,
186
+ registerItem,
187
+ option,
188
+ selected,
189
+ band,
190
+ horizontal,
191
+ tabStop,
192
+ onSelect,
193
+ }: {
194
+ index: number
195
+ registerItem: (index: number, element: HTMLElement | null) => void
196
+ option: ScrollColumnOption<T>
197
+ selected: boolean
198
+ band?: 'start' | 'middle' | 'end' | 'single'
199
+ horizontal: boolean
200
+ tabStop: boolean
201
+ onSelect: () => void
202
+ }) {
203
+ const ref = useRef<HTMLButtonElement>(null)
204
+ useRegisterFluidHoverItem(registerItem, index, ref)
205
+ const shape = useShape()
206
+ const sizeClasses = useSize()
207
+
208
+ return (
209
+ <button
210
+ ref={ref}
211
+ type="button"
212
+ role="option"
213
+ aria-selected={selected}
214
+ tabIndex={tabStop ? 0 : -1}
215
+ disabled={option.disabled}
216
+ data-value={option.value}
217
+ data-fluid-hover-index={index}
218
+ onClick={onSelect}
219
+ className={cn(
220
+ 'relative z-[1] flex shrink-0 items-center justify-center px-2 tabular-nums whitespace-nowrap outline-none',
221
+ 'transition-[color,background-color] duration-fast focus-visible:ring-1 focus-visible:ring-focus-ring',
222
+ 'disabled:opacity-50 disabled:pointer-events-none',
223
+ sizeClasses.segmentItem,
224
+ sizeClasses.text,
225
+ shape.item,
226
+ // FF's tint for a passive region (Accordion's open row); the pick is Select's checked row.
227
+ band && !selected && 'bg-accent/20 dark:bg-accent/12',
228
+ selected && 'bg-active',
229
+ band && band !== 'single' && 'rounded-none',
230
+ band === 'start' && (horizontal ? 'rounded-s-lg' : 'rounded-t-lg'),
231
+ band === 'end' && (horizontal ? 'rounded-e-lg' : 'rounded-b-lg'),
232
+ selected ? 'text-foreground weight-semibold' : band ? 'text-foreground' : 'text-muted-foreground',
233
+ )}
234
+ >
235
+ {selected && (
236
+ <span aria-hidden className="absolute start-1 top-1/2 size-1 -translate-y-1/2 rounded-full bg-current" />
237
+ )}
238
+ {option.label}
239
+ </button>
240
+ )
241
+ }
242
+
243
+ export type { ScrollColumnOption, ScrollColumnProps }
244
+ export { dividedColumns, ScrollColumn }
@@ -0,0 +1,124 @@
1
+ // biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
2
+ /*
3
+ * Vendored from Fluid Functionalism — `registry/default/lib/shape-context.tsx` at
4
+ * github.com/mickadesign/fluid-functionalism@b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
+ * (fluidfunctionalism.com). MIT License © 2026 Micka Touillaud — see
6
+ * LICENSE.fluid-functionalism in this package.
7
+ *
8
+ * Local modifications:
9
+ * - `"use client"` directive dropped (no RSC here).
10
+ */
11
+
12
+ import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
13
+
14
+ type ShapeVariant = 'pill' | 'rounded'
15
+
16
+ interface ShapeClasses {
17
+ /** The variant these classes belong to — handy for conditionals. */
18
+ variant: ShapeVariant
19
+ item: string
20
+ bg: string
21
+ focusRing: string
22
+ mergedBg: string
23
+ container: string
24
+ button: string
25
+ input: string
26
+ // Numeric counterparts of `bg` / `mergedBg`, in px. Needed where individual
27
+ // corners are animated (e.g. the selected-background merge/split animation),
28
+ // which requires per-corner numeric border-radii rather than a class.
29
+ bgRadius: number
30
+ mergedRadius: number
31
+ }
32
+
33
+ const shapeMap: Record<ShapeVariant, ShapeClasses> = {
34
+ pill: {
35
+ variant: 'pill',
36
+ item: 'rounded-[20px]',
37
+ bg: 'rounded-[20px]',
38
+ // +2px over `item` because the focus ring sits 2px outside the element
39
+ // (top/left -2, width/height +4); this keeps the corners concentric so a
40
+ // pill element gets a pill ring (matches the rounded-mode 8px→10px bump).
41
+ focusRing: 'rounded-[22px]',
42
+ mergedBg: 'rounded-2xl',
43
+ container: 'rounded-3xl',
44
+ button: 'rounded-[20px]',
45
+ input: 'rounded-[20px]',
46
+ bgRadius: 20,
47
+ mergedRadius: 16,
48
+ },
49
+ rounded: {
50
+ variant: 'rounded',
51
+ item: 'rounded-lg',
52
+ bg: 'rounded-lg',
53
+ focusRing: 'rounded-[10px]',
54
+ mergedBg: 'rounded-lg',
55
+ container: 'rounded-xl',
56
+ button: 'rounded-lg',
57
+ input: 'rounded-lg',
58
+ bgRadius: 8,
59
+ mergedRadius: 8,
60
+ },
61
+ }
62
+
63
+ interface ShapeContextValue {
64
+ shape: ShapeVariant
65
+ setShape: (shape: ShapeVariant) => void
66
+ classes: ShapeClasses
67
+ }
68
+
69
+ const ShapeContext = createContext<ShapeContextValue | null>(null)
70
+
71
+ // Rounded is the default on every path: the site demos render under
72
+ // <ShapeProvider defaultShape="rounded">, the shipped :focus-visible fallback
73
+ // ring assumes its 8px radius, and the preset generators only emit a provider
74
+ // for pill. A consumer with no provider gets the corners the docs show.
75
+ function useShape(): ShapeClasses {
76
+ const ctx = useContext(ShapeContext)
77
+ if (!ctx) return shapeMap.rounded
78
+ return ctx.classes
79
+ }
80
+
81
+ function useShapeContext() {
82
+ const ctx = useContext(ShapeContext)
83
+ if (!ctx) throw new Error('useShapeContext must be used within a ShapeProvider')
84
+ return ctx
85
+ }
86
+
87
+ function ShapeProvider({ children, defaultShape = 'rounded' }: { children: ReactNode; defaultShape?: ShapeVariant }) {
88
+ const [shape, setShapeState] = useState<ShapeVariant>(defaultShape)
89
+ const transitionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
90
+
91
+ // Run a state change under the `.transitioning` guard (added + reflow-flushed
92
+ // first so the 180ms border-radius cross-fade applies). Clearing the previous
93
+ // timeout first keeps a double-press from removing the class mid-fade.
94
+ const transitionShape = useCallback((callback: () => void) => {
95
+ const root = document.documentElement
96
+ root.classList.add('transitioning')
97
+ void root.offsetHeight
98
+ callback()
99
+ if (transitionTimeoutRef.current) clearTimeout(transitionTimeoutRef.current)
100
+ transitionTimeoutRef.current = setTimeout(() => root.classList.remove('transitioning'), 200)
101
+ }, [])
102
+
103
+ const setShape = useCallback(
104
+ (next: ShapeVariant) => {
105
+ transitionShape(() => setShapeState(next))
106
+ },
107
+ [transitionShape],
108
+ )
109
+
110
+ // Publish the current element radius as a CSS custom property so plain-CSS
111
+ // consumers that can't read React context stay in sync with the shape
112
+ // system — e.g. the @layer base :focus-visible fallback ring in
113
+ // globals.css. Set on <html> so portalled content sees it too.
114
+ useEffect(() => {
115
+ document.documentElement.style.setProperty('--shape-input-radius', `${shapeMap[shape].bgRadius}px`)
116
+ }, [shape])
117
+
118
+ const value = useMemo(() => ({ shape, setShape, classes: shapeMap[shape] }), [shape, setShape])
119
+
120
+ return <ShapeContext.Provider value={value}>{children}</ShapeContext.Provider>
121
+ }
122
+
123
+ export type { ShapeClasses, ShapeVariant }
124
+ export { ShapeProvider, shapeMap, useShape, useShapeContext }