@justanarthur/modo-ui 0.6.0-canary.8 → 0.6.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 (490) hide show
  1. package/README.md +66 -164
  2. package/dist/base.css +4 -141
  3. package/dist/blocks/sidebar-app/index.js +26 -40
  4. package/dist/components/AI/input-message/index.js +16 -23
  5. package/dist/components/AI/thinking-indicator/index.js +6 -9
  6. package/dist/components/accordion/index.d.ts +11 -39
  7. package/dist/components/accordion/index.js +13 -18
  8. package/dist/components/badge/index.d.ts +11 -18
  9. package/dist/components/badge/index.js +7 -12
  10. package/dist/components/button/index.d.ts +17 -53
  11. package/dist/components/button/index.js +6 -11
  12. package/dist/components/card/index.d.ts +9 -32
  13. package/dist/components/card/index.js +11 -17
  14. package/dist/components/copy/index.d.ts +8 -41
  15. package/dist/components/copy/index.js +9 -15
  16. package/dist/components/inputs/checkbox/index.d.ts +9 -28
  17. package/dist/components/inputs/checkbox/index.js +13 -18
  18. package/dist/components/inputs/color-picker/index.d.ts +14 -22
  19. package/dist/components/inputs/color-picker/index.js +18 -26
  20. package/dist/components/inputs/combobox/index.d.ts +20 -85
  21. package/dist/components/inputs/combobox/index.js +18 -27
  22. package/dist/components/inputs/date-time/calendar/index.d.ts +18 -24
  23. package/dist/components/inputs/date-time/calendar/index.js +11 -15
  24. package/dist/components/inputs/date-time/date-picker/index.d.ts +16 -105
  25. package/dist/components/inputs/date-time/date-picker/index.js +25 -35
  26. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +12 -106
  27. package/dist/components/inputs/date-time/date-range-picker/index.js +24 -35
  28. package/dist/components/inputs/date-time/duration-picker/index.d.ts +13 -66
  29. package/dist/components/inputs/date-time/duration-picker/index.js +24 -33
  30. package/dist/components/inputs/date-time/month-picker/index.d.ts +10 -60
  31. package/dist/components/inputs/date-time/month-picker/index.js +23 -32
  32. package/dist/components/inputs/date-time/time-picker/index.d.ts +10 -58
  33. package/dist/components/inputs/date-time/time-picker/index.js +24 -33
  34. package/dist/components/inputs/input/index.d.ts +13 -95
  35. package/dist/components/inputs/input/index.js +9 -18
  36. package/dist/components/inputs/radio/index.d.ts +5 -20
  37. package/dist/components/inputs/radio/index.js +12 -16
  38. package/dist/components/inputs/search/index.d.ts +3 -3
  39. package/dist/components/inputs/search/index.js +18 -26
  40. package/dist/components/inputs/select/index.d.ts +23 -99
  41. package/dist/components/inputs/select/index.js +14 -29
  42. package/dist/components/inputs/slider/index.d.ts +0 -9
  43. package/dist/components/inputs/slider/index.js +5 -8
  44. package/dist/components/inputs/switch/index.d.ts +2 -8
  45. package/dist/components/inputs/switch/index.js +12 -15
  46. package/dist/components/inputs/textarea/index.d.ts +1 -20
  47. package/dist/components/inputs/textarea/index.js +6 -9
  48. package/dist/components/navigation/sidebar/index.d.ts +21 -44
  49. package/dist/components/navigation/sidebar/index.js +31 -42
  50. package/dist/components/navigation/tabs/index.d.ts +2 -8
  51. package/dist/components/navigation/tabs/index.js +10 -15
  52. package/dist/components/navigation/tabs-subtle/index.js +10 -13
  53. package/dist/components/overlays/alert-dialog/index.d.ts +16 -135
  54. package/dist/components/overlays/alert-dialog/index.js +13 -23
  55. package/dist/components/overlays/command/index.d.ts +20 -25
  56. package/dist/components/overlays/command/index.js +17 -25
  57. package/dist/components/overlays/context-menu/index.d.ts +17 -39
  58. package/dist/components/overlays/context-menu/index.js +18 -29
  59. package/dist/components/overlays/dialog/index.d.ts +16 -42
  60. package/dist/components/overlays/dialog/index.js +12 -20
  61. package/dist/components/overlays/dropdown/index.d.ts +34 -304
  62. package/dist/components/overlays/dropdown/index.js +17 -44
  63. package/dist/components/overlays/popover/index.d.ts +12 -38
  64. package/dist/components/overlays/popover/index.js +9 -13
  65. package/dist/components/overlays/preview-card/index.js +8 -12
  66. package/dist/components/overlays/sheet/index.d.ts +8 -25
  67. package/dist/components/overlays/sheet/index.js +13 -19
  68. package/dist/components/overlays/toast/index.d.ts +50 -209
  69. package/dist/components/overlays/toast/index.js +10 -23
  70. package/dist/components/overlays/tooltip/index.d.ts +21 -123
  71. package/dist/components/overlays/tooltip/index.js +6 -16
  72. package/dist/components/table/index.d.ts +8 -63
  73. package/dist/components/table/index.js +9 -20
  74. package/dist/index.d.ts +1048 -2999
  75. package/dist/index.js +82 -231
  76. package/dist/preset.d.ts +0 -22
  77. package/dist/preset.js +2 -24
  78. package/dist/primitives/code/index.js +5 -8
  79. package/dist/primitives/craft/index.js +6 -10
  80. package/dist/primitives/fluid-hover/index.js +8 -10
  81. package/dist/primitives/morph/index.js +10 -16
  82. package/dist/primitives/motion/index.js +8 -11
  83. package/dist/primitives/scroll-area/index.js +3 -5
  84. package/dist/primitives/sizes/index.js +4 -6
  85. package/dist/primitives/surface/index.js +4 -6
  86. package/dist/shared/{components-4y816hwp.js → components-0q0sqfh4.js} +49 -27
  87. package/dist/shared/components-0sn7ypjw.js +61 -0
  88. package/dist/shared/{components-vc864c51.js → components-11da9ajg.js} +5 -8
  89. package/dist/shared/components-1434p1mh.js +123 -0
  90. package/dist/shared/{components-t2a3qmy9.js → components-1z6tdadk.js} +24 -40
  91. package/dist/shared/{components-qs4rkqsj.js → components-27nnvrwe.js} +14 -15
  92. package/dist/shared/{components-av5t26rc.js → components-2dxkv6zm.js} +7 -8
  93. package/dist/shared/{components-wqrvqq5p.js → components-2ecg8svx.js} +152 -234
  94. package/dist/shared/{components-3ydr2fqe.js → components-2ph6t61s.js} +34 -37
  95. package/dist/shared/{components-6gk118w3.js → components-39jq9va1.js} +24 -29
  96. package/dist/shared/components-3ky44908.js +9 -0
  97. package/dist/shared/{components-y1qvbawa.js → components-3mkepka5.js} +25 -28
  98. package/dist/shared/{components-xqszvkj4.js → components-4700p9kz.js} +22 -24
  99. package/dist/shared/{components-yhgq0qxx.js → components-59efxjd9.js} +12 -13
  100. package/dist/shared/{components-9j9abkct.js → components-5vgpnxgy.js} +27 -51
  101. package/dist/shared/{components-8tt3xbb5.js → components-5xc2j8jz.js} +18 -19
  102. package/dist/shared/{components-jymmaevv.js → components-6p8tfcd6.js} +29 -57
  103. package/dist/shared/{components-4nck8acq.js → components-7r3etddy.js} +54 -19
  104. package/dist/shared/{components-skfd7kff.js → components-8snj490h.js} +32 -78
  105. package/dist/shared/{components-ctqmge8t.js → components-b0eac83j.js} +30 -9
  106. package/dist/shared/{components-9sttq78t.js → components-b2wyq2cq.js} +59 -41
  107. package/dist/shared/{components-vkwq2k96.js → components-b3c5et5b.js} +38 -49
  108. package/dist/shared/{components-rrbpmfx4.js → components-c6s6b43g.js} +2 -3
  109. package/dist/shared/{components-pw06rbxq.js → components-chacnk61.js} +19 -19
  110. package/dist/shared/{components-71nbt03m.js → components-cv16mryv.js} +7 -26
  111. package/dist/shared/{components-eqze3y30.js → components-d6xtjht3.js} +30 -35
  112. package/dist/shared/{components-5y5shqfd.js → components-dk0gjr5a.js} +18 -19
  113. package/dist/shared/{components-khspp26b.js → components-fzrq6tr3.js} +34 -37
  114. package/dist/shared/{components-km7fhgbk.js → components-gc5vtq2f.js} +30 -66
  115. package/dist/shared/components-gf5s6ap9.js +42 -0
  116. package/dist/shared/{components-qqcmtrbv.js → components-gz8zf3ec.js} +55 -56
  117. package/dist/shared/{components-mattt5jp.js → components-h2ajmhwn.js} +35 -107
  118. package/dist/shared/{components-awra5sff.js → components-hqeq3w9q.js} +16 -24
  119. package/dist/shared/{components-5tzhqbcw.js → components-hw9yk32z.js} +24 -36
  120. package/dist/shared/components-hyq23phv.js +310 -0
  121. package/dist/shared/{components-8mt5bsv6.js → components-jbbmdr74.js} +22 -41
  122. package/dist/shared/{components-dcc02hs2.js → components-jjw69c4c.js} +4 -7
  123. package/dist/shared/{components-2syq4mwc.js → components-jthe1pwb.js} +420 -139
  124. package/dist/shared/{components-r7q1zm7r.js → components-jwst3vtj.js} +76 -224
  125. package/dist/shared/{components-qdm3yh26.js → components-kvr7f9hy.js} +10 -19
  126. package/dist/shared/{components-s8yqk2dc.js → components-kwa6mrf9.js} +11 -46
  127. package/dist/shared/{components-de99ykt7.js → components-men63rwj.js} +2 -3
  128. package/dist/shared/{components-6b0zb8z3.js → components-mjthm1kf.js} +9 -13
  129. package/dist/shared/{components-2c64r8c9.js → components-n42c79bz.js} +22 -23
  130. package/dist/shared/{components-tz1fv35x.js → components-n7mjr357.js} +2 -3
  131. package/dist/shared/components-ng72k97e.js +128 -0
  132. package/dist/shared/{components-dyae0y7h.js → components-pbmqctfa.js} +65 -169
  133. package/dist/shared/{components-px2zm7dc.js → components-q5xj5c16.js} +8 -9
  134. package/dist/shared/components-raknhppb.js +64 -0
  135. package/dist/shared/{components-8rb25gbe.js → components-rr7a6ja9.js} +27 -59
  136. package/dist/shared/{components-5012r6gr.js → components-s083f38z.js} +34 -164
  137. package/dist/shared/components-s5ngg32c.js +267 -0
  138. package/dist/shared/components-sn1rxx1d.js +143 -0
  139. package/dist/shared/{components-r8eqs7gz.js → components-ta7shw28.js} +4 -4
  140. package/dist/shared/{components-9as3rf68.js → components-vc7vq5nz.js} +3 -6
  141. package/dist/shared/{components-ft3zdc79.js → components-vfbhxf5r.js} +5 -6
  142. package/dist/shared/components-vynjtrk6.js +19 -0
  143. package/dist/shared/{components-w0xq6q9k.js → components-w1r67ag9.js} +149 -294
  144. package/dist/shared/{components-z0bqtvcp.js → components-w90endve.js} +72 -71
  145. package/dist/shared/{components-t5pk5nmc.js → components-wvp1m52g.js} +102 -149
  146. package/dist/shared/components-wy9jpqny.js +146 -0
  147. package/dist/shared/{components-5sxjeh6h.js → components-y9y0g8er.js} +28 -52
  148. package/dist/shared/components-ypp01w3p.js +986 -0
  149. package/dist/shared/{components-t50hrswb.js → components-zfnmn5d4.js} +36 -82
  150. package/dist/shared/{components-jqpefjsn.js → components-zrkzcayz.js} +6 -54
  151. package/dist/shared/{components-vf45j12c.js → components-zx6b6yby.js} +18 -4
  152. package/dist/styles.css +32 -320
  153. package/dist/tailwind.css +0 -16
  154. package/dist/tokens.css +0 -67
  155. package/package.json +4 -227
  156. package/DESIGN.md +0 -623
  157. package/blocks/sidebar-app/examples.mdx +0 -33
  158. package/blocks/sidebar-app/index.tsx +0 -218
  159. package/blocks/sidebar-app/inset-topbar.tsx +0 -25
  160. package/blocks/sidebar-app/menu-grid.ts +0 -26
  161. package/blocks/sidebar-app/nav-data.ts +0 -36
  162. package/blocks/sidebar-app/search-field.tsx +0 -48
  163. package/blocks/sidebar-app/user-footer.tsx +0 -67
  164. package/blocks/sidebar-app/workspace-header.tsx +0 -129
  165. package/components/AI/input-message/examples.mdx +0 -130
  166. package/components/AI/input-message/file-thumbnail.tsx +0 -173
  167. package/components/AI/input-message/index.tsx +0 -1385
  168. package/components/AI/thinking-indicator/examples.mdx +0 -27
  169. package/components/AI/thinking-indicator/index.tsx +0 -210
  170. package/components/accordion/examples.mdx +0 -233
  171. package/components/accordion/index.tsx +0 -1071
  172. package/components/avatar/examples.mdx +0 -95
  173. package/components/avatar/index.tsx +0 -129
  174. package/components/badge/change-demo.tsx +0 -29
  175. package/components/badge/examples.mdx +0 -124
  176. package/components/badge/index.tsx +0 -225
  177. package/components/badge/variants.ts +0 -73
  178. package/components/button/examples.mdx +0 -168
  179. package/components/button/index.tsx +0 -424
  180. package/components/button/loading-demo.tsx +0 -25
  181. package/components/button/variants.ts +0 -168
  182. package/components/card/examples.mdx +0 -251
  183. package/components/card/index.tsx +0 -1070
  184. package/components/copy/examples.mdx +0 -125
  185. package/components/copy/index.tsx +0 -662
  186. package/components/inputs/checkbox/examples.mdx +0 -87
  187. package/components/inputs/checkbox/index.tsx +0 -611
  188. package/components/inputs/checkbox/select-all-demo.tsx +0 -48
  189. package/components/inputs/color-picker/examples.mdx +0 -111
  190. package/components/inputs/color-picker/index.tsx +0 -1981
  191. package/components/inputs/color-picker/variants.ts +0 -401
  192. package/components/inputs/combobox/examples.mdx +0 -366
  193. package/components/inputs/combobox/index.tsx +0 -1527
  194. package/components/inputs/combobox/remote-demo.tsx +0 -105
  195. package/components/inputs/date-time/calendar/examples.mdx +0 -45
  196. package/components/inputs/date-time/calendar/index.tsx +0 -370
  197. package/components/inputs/date-time/calendar/use-month-window.ts +0 -173
  198. package/components/inputs/date-time/date-picker/examples.mdx +0 -243
  199. package/components/inputs/date-time/date-picker/index.tsx +0 -476
  200. package/components/inputs/date-time/date-picker/masked-date-input.tsx +0 -348
  201. package/components/inputs/date-time/date-range-picker/examples.mdx +0 -93
  202. package/components/inputs/date-time/date-range-picker/index.tsx +0 -525
  203. package/components/inputs/date-time/duration-picker/examples.mdx +0 -40
  204. package/components/inputs/date-time/duration-picker/index.tsx +0 -253
  205. package/components/inputs/date-time/month-picker/examples.mdx +0 -45
  206. package/components/inputs/date-time/month-picker/index.tsx +0 -254
  207. package/components/inputs/date-time/picker-shell.tsx +0 -581
  208. package/components/inputs/date-time/time-columns.tsx +0 -94
  209. package/components/inputs/date-time/time-picker/examples.mdx +0 -57
  210. package/components/inputs/date-time/time-picker/index.tsx +0 -227
  211. package/components/inputs/field/examples.mdx +0 -117
  212. package/components/inputs/field/index.tsx +0 -268
  213. package/components/inputs/file-dropzone/examples.mdx +0 -83
  214. package/components/inputs/file-dropzone/file-row.tsx +0 -201
  215. package/components/inputs/file-dropzone/index.tsx +0 -316
  216. package/components/inputs/file-dropzone/upload-demo.tsx +0 -67
  217. package/components/inputs/file-dropzone/variants.ts +0 -94
  218. package/components/inputs/input/examples.mdx +0 -166
  219. package/components/inputs/input/index.tsx +0 -494
  220. package/components/inputs/input/password-demo.tsx +0 -30
  221. package/components/inputs/label/examples.mdx +0 -67
  222. package/components/inputs/label/index.tsx +0 -85
  223. package/components/inputs/number-input/examples.mdx +0 -69
  224. package/components/inputs/number-input/index.tsx +0 -221
  225. package/components/inputs/radio/examples.mdx +0 -77
  226. package/components/inputs/radio/index.tsx +0 -636
  227. package/components/inputs/search/examples.mdx +0 -74
  228. package/components/inputs/search/index.tsx +0 -333
  229. package/components/inputs/select/examples.mdx +0 -296
  230. package/components/inputs/select/index.tsx +0 -1044
  231. package/components/inputs/select/variants.ts +0 -32
  232. package/components/inputs/slider/examples.mdx +0 -153
  233. package/components/inputs/slider/index.tsx +0 -1867
  234. package/components/inputs/switch/examples.mdx +0 -96
  235. package/components/inputs/switch/index.tsx +0 -504
  236. package/components/inputs/textarea/examples.mdx +0 -126
  237. package/components/inputs/textarea/index.tsx +0 -207
  238. package/components/navigation/navigation-menu/examples.mdx +0 -136
  239. package/components/navigation/navigation-menu/index.tsx +0 -704
  240. package/components/navigation/pagination/examples.mdx +0 -74
  241. package/components/navigation/pagination/index.tsx +0 -401
  242. package/components/navigation/sidebar/examples.mdx +0 -325
  243. package/components/navigation/sidebar/index.tsx +0 -453
  244. package/components/navigation/sidebar/sidebar-core.tsx +0 -1408
  245. package/components/navigation/sidebar/sidebar-menu.tsx +0 -1495
  246. package/components/navigation/sidebar/sidebar.css +0 -74
  247. package/components/navigation/sidebar/variants.ts +0 -55
  248. package/components/navigation/tabs/examples.mdx +0 -89
  249. package/components/navigation/tabs/index.tsx +0 -494
  250. package/components/navigation/tabs-subtle/examples.mdx +0 -49
  251. package/components/navigation/tabs-subtle/index.tsx +0 -512
  252. package/components/overlays/alert-dialog/examples.mdx +0 -123
  253. package/components/overlays/alert-dialog/index.tsx +0 -276
  254. package/components/overlays/alert-dialog/pending-demo.tsx +0 -36
  255. package/components/overlays/command/examples.mdx +0 -113
  256. package/components/overlays/command/index.tsx +0 -425
  257. package/components/overlays/context-menu/examples.mdx +0 -56
  258. package/components/overlays/context-menu/index.tsx +0 -279
  259. package/components/overlays/dialog/examples.mdx +0 -165
  260. package/components/overlays/dialog/index.tsx +0 -451
  261. package/components/overlays/dropdown/dropdown-search.tsx +0 -395
  262. package/components/overlays/dropdown/examples.mdx +0 -444
  263. package/components/overlays/dropdown/index.tsx +0 -1241
  264. package/components/overlays/dropdown/menu-item.tsx +0 -510
  265. package/components/overlays/dropdown/menu-parts.tsx +0 -725
  266. package/components/overlays/popover/anchored-demo.tsx +0 -59
  267. package/components/overlays/popover/examples.mdx +0 -203
  268. package/components/overlays/popover/index.tsx +0 -317
  269. package/components/overlays/preview-card/examples.mdx +0 -61
  270. package/components/overlays/preview-card/index.tsx +0 -204
  271. package/components/overlays/sheet/examples.mdx +0 -101
  272. package/components/overlays/sheet/index.tsx +0 -307
  273. package/components/overlays/toast/examples.mdx +0 -85
  274. package/components/overlays/toast/global-demo.tsx +0 -72
  275. package/components/overlays/toast/index.tsx +0 -544
  276. package/components/overlays/toast/manager.ts +0 -193
  277. package/components/overlays/tooltip/examples.mdx +0 -106
  278. package/components/overlays/tooltip/index.tsx +0 -539
  279. package/components/progress/examples.mdx +0 -59
  280. package/components/progress/index.tsx +0 -174
  281. package/components/progress/progress.css +0 -49
  282. package/components/progress/step-demo.tsx +0 -27
  283. package/components/table/examples.mdx +0 -188
  284. package/components/table/index.tsx +0 -535
  285. package/components/table/sort-demo.tsx +0 -61
  286. package/dist/components/avatar/index.d.ts +0 -46
  287. package/dist/components/avatar/index.js +0 -19
  288. package/dist/components/badge/variants.d.ts +0 -33
  289. package/dist/components/badge/variants.js +0 -10
  290. package/dist/components/button/variants.d.ts +0 -39
  291. package/dist/components/button/variants.js +0 -8
  292. package/dist/components/inputs/color-picker/variants.d.ts +0 -61
  293. package/dist/components/inputs/color-picker/variants.js +0 -28
  294. package/dist/components/inputs/field/index.d.ts +0 -116
  295. package/dist/components/inputs/field/index.js +0 -29
  296. package/dist/components/inputs/file-dropzone/index.d.ts +0 -167
  297. package/dist/components/inputs/file-dropzone/index.js +0 -33
  298. package/dist/components/inputs/file-dropzone/variants.d.ts +0 -35
  299. package/dist/components/inputs/file-dropzone/variants.js +0 -12
  300. package/dist/components/inputs/label/index.d.ts +0 -32
  301. package/dist/components/inputs/label/index.js +0 -11
  302. package/dist/components/inputs/number-input/index.d.ts +0 -87
  303. package/dist/components/inputs/number-input/index.js +0 -21
  304. package/dist/components/inputs/select/variants.d.ts +0 -5
  305. package/dist/components/inputs/select/variants.js +0 -6
  306. package/dist/components/navigation/navigation-menu/index.d.ts +0 -126
  307. package/dist/components/navigation/navigation-menu/index.js +0 -41
  308. package/dist/components/navigation/pagination/index.d.ts +0 -75
  309. package/dist/components/navigation/pagination/index.js +0 -41
  310. package/dist/components/navigation/sidebar/variants.d.ts +0 -25
  311. package/dist/components/navigation/sidebar/variants.js +0 -28
  312. package/dist/components/progress/index.d.ts +0 -56
  313. package/dist/components/progress/index.js +0 -15
  314. package/dist/docs/index.d.ts +0 -83
  315. package/dist/docs/index.js +0 -288
  316. package/dist/docs/vite.d.ts +0 -31
  317. package/dist/docs/vite.js +0 -45
  318. package/dist/lib/date-locale.d.ts +0 -26
  319. package/dist/lib/date-locale.js +0 -24
  320. package/dist/lib/field-classes.d.ts +0 -149
  321. package/dist/lib/field-classes.js +0 -41
  322. package/dist/lib/field-variants.d.ts +0 -47
  323. package/dist/lib/field-variants.js +0 -26
  324. package/dist/lib/fluid-hover-highlight.d.ts +0 -112
  325. package/dist/lib/fluid-hover-highlight.js +0 -17
  326. package/dist/lib/focus-ring.d.ts +0 -18
  327. package/dist/lib/focus-ring.js +0 -11
  328. package/dist/lib/goo-indicator.d.ts +0 -46
  329. package/dist/lib/goo-indicator.js +0 -14
  330. package/dist/lib/icon-context.d.ts +0 -30
  331. package/dist/lib/icon-context.js +0 -12
  332. package/dist/lib/inert.d.ts +0 -9
  333. package/dist/lib/inert.js +0 -8
  334. package/dist/lib/morph-layers.d.ts +0 -93
  335. package/dist/lib/morph-layers.js +0 -13
  336. package/dist/lib/morph-part.d.ts +0 -21
  337. package/dist/lib/morph-part.js +0 -12
  338. package/dist/lib/popup.d.ts +0 -20
  339. package/dist/lib/popup.js +0 -12
  340. package/dist/lib/reduced-motion.d.ts +0 -9
  341. package/dist/lib/reduced-motion.js +0 -6
  342. package/dist/lib/row-index.d.ts +0 -10
  343. package/dist/lib/row-index.js +0 -8
  344. package/dist/lib/row-order.d.ts +0 -28
  345. package/dist/lib/row-order.js +0 -10
  346. package/dist/lib/scroll-column.d.ts +0 -31
  347. package/dist/lib/scroll-column.js +0 -19
  348. package/dist/lib/shape-context.d.ts +0 -30
  349. package/dist/lib/shape-context.js +0 -13
  350. package/dist/lib/shape-radius.d.ts +0 -19
  351. package/dist/lib/shape-radius.js +0 -6
  352. package/dist/lib/size-context.d.ts +0 -94
  353. package/dist/lib/size-context.js +0 -20
  354. package/dist/lib/size-scale.d.ts +0 -79
  355. package/dist/lib/size-scale.js +0 -8
  356. package/dist/lib/slot.d.ts +0 -39
  357. package/dist/lib/slot.js +0 -14
  358. package/dist/lib/springs.d.ts +0 -44
  359. package/dist/lib/springs.js +0 -8
  360. package/dist/lib/surface-classes.d.ts +0 -19
  361. package/dist/lib/surface-classes.js +0 -10
  362. package/dist/lib/surface-context.d.ts +0 -9
  363. package/dist/lib/surface-context.js +0 -8
  364. package/dist/lib/use-controllable-state.d.ts +0 -2
  365. package/dist/lib/use-controllable-state.js +0 -6
  366. package/dist/lib/use-fluid-hover.d.ts +0 -138
  367. package/dist/lib/use-fluid-hover.js +0 -14
  368. package/dist/lib/use-is-mobile.d.ts +0 -2
  369. package/dist/lib/use-is-mobile.js +0 -6
  370. package/dist/lib/use-keyboard-nav-gate.d.ts +0 -20
  371. package/dist/lib/use-keyboard-nav-gate.js +0 -7
  372. package/dist/lib/use-merge-split.d.ts +0 -47
  373. package/dist/lib/use-merge-split.js +0 -15
  374. package/dist/lib/use-morph.d.ts +0 -101
  375. package/dist/lib/use-morph.js +0 -20
  376. package/dist/lib/use-touch-primary.d.ts +0 -2
  377. package/dist/lib/use-touch-primary.js +0 -6
  378. package/dist/lib/utils.d.ts +0 -3
  379. package/dist/lib/utils.js +0 -6
  380. package/dist/primitives/separator/index.d.ts +0 -25
  381. package/dist/primitives/separator/index.js +0 -9
  382. package/dist/primitives/skeleton/index.d.ts +0 -35
  383. package/dist/primitives/skeleton/index.js +0 -13
  384. package/dist/shared/components-124epb7f.js +0 -53
  385. package/dist/shared/components-1kab3j44.js +0 -337
  386. package/dist/shared/components-26snnbs9.js +0 -322
  387. package/dist/shared/components-34txk9rj.js +0 -35
  388. package/dist/shared/components-3h92d94m.js +0 -23
  389. package/dist/shared/components-4vfm04wq.js +0 -82
  390. package/dist/shared/components-54nq988h.js +0 -123
  391. package/dist/shared/components-7s7pykk5.js +0 -281
  392. package/dist/shared/components-7zkhz3r8.js +0 -399
  393. package/dist/shared/components-8v644a0s.js +0 -298
  394. package/dist/shared/components-a4evvq8h.js +0 -271
  395. package/dist/shared/components-adkarj8n.js +0 -35
  396. package/dist/shared/components-b1e239g3.js +0 -28
  397. package/dist/shared/components-b94t6qtj.js +0 -143
  398. package/dist/shared/components-c9yr4ed1.js +0 -44
  399. package/dist/shared/components-cwg1d0w6.js +0 -254
  400. package/dist/shared/components-d3shbwqq.js +0 -95
  401. package/dist/shared/components-dhgk6mbt.js +0 -16
  402. package/dist/shared/components-drxrtnp2.js +0 -25
  403. package/dist/shared/components-ej4r7wmj.js +0 -482
  404. package/dist/shared/components-f7h57m6t.js +0 -59
  405. package/dist/shared/components-fejdzvqc.js +0 -448
  406. package/dist/shared/components-gx4h5rbb.js +0 -40
  407. package/dist/shared/components-gyt7my41.js +0 -328
  408. package/dist/shared/components-h6p86be3.js +0 -19
  409. package/dist/shared/components-h83az0bz.js +0 -33
  410. package/dist/shared/components-hdzrqqpx.js +0 -55
  411. package/dist/shared/components-krtx3m5b.js +0 -84
  412. package/dist/shared/components-kswxnm3h.js +0 -24
  413. package/dist/shared/components-mpa2y6x8.js +0 -277
  414. package/dist/shared/components-mrbaxt8q.js +0 -62
  415. package/dist/shared/components-pt5ftm4p.js +0 -38
  416. package/dist/shared/components-rpw4md2b.js +0 -179
  417. package/dist/shared/components-rtadhd9j.js +0 -64
  418. package/dist/shared/components-tz4gaf1v.js +0 -23
  419. package/dist/shared/components-wbbckbw8.js +0 -34
  420. package/dist/shared/components-xdqmt6ed.js +0 -20
  421. package/dist/shared/components-xjkdvnbg.js +0 -107
  422. package/dist/shared/components-z3v0n0de.js +0 -11
  423. package/dist/shared/components-z6dfqtyt.js +0 -1545
  424. package/dist/tokens/colors.css +0 -230
  425. package/dist/tokens/motion.css +0 -40
  426. package/dist/tokens/radius.css +0 -10
  427. package/dist/tokens/spacing.css +0 -9
  428. package/dist/tokens/typography.css +0 -119
  429. package/docs/chrome.css +0 -120
  430. package/docs/chrome.tsx +0 -283
  431. package/docs/index.ts +0 -9
  432. package/docs/vite.ts +0 -85
  433. package/index.ts +0 -60
  434. package/lib/date-locale.ts +0 -195
  435. package/lib/field-classes.ts +0 -121
  436. package/lib/field-variants.ts +0 -117
  437. package/lib/fluid-hover-highlight.tsx +0 -139
  438. package/lib/focus-ring.tsx +0 -46
  439. package/lib/goo-indicator.tsx +0 -261
  440. package/lib/icon-context.tsx +0 -310
  441. package/lib/inert.ts +0 -22
  442. package/lib/morph-layers.tsx +0 -102
  443. package/lib/morph-part.tsx +0 -49
  444. package/lib/popup.ts +0 -52
  445. package/lib/reduced-motion.ts +0 -19
  446. package/lib/row-index.tsx +0 -29
  447. package/lib/row-order.tsx +0 -126
  448. package/lib/scroll-column.tsx +0 -273
  449. package/lib/shape-context.tsx +0 -138
  450. package/lib/shape-radius.ts +0 -67
  451. package/lib/size-context.tsx +0 -66
  452. package/lib/size-scale.ts +0 -115
  453. package/lib/slot.ts +0 -115
  454. package/lib/springs.ts +0 -62
  455. package/lib/surface-classes.ts +0 -58
  456. package/lib/surface-context.tsx +0 -21
  457. package/lib/use-controllable-state.ts +0 -44
  458. package/lib/use-fluid-hover.ts +0 -541
  459. package/lib/use-is-mobile.ts +0 -25
  460. package/lib/use-keyboard-nav-gate.ts +0 -45
  461. package/lib/use-merge-split.tsx +0 -209
  462. package/lib/use-morph.ts +0 -557
  463. package/lib/use-touch-primary.tsx +0 -43
  464. package/lib/utils.ts +0 -37
  465. package/preset.ts +0 -199
  466. package/primitives/code/examples.mdx +0 -27
  467. package/primitives/code/index.tsx +0 -80
  468. package/primitives/craft/demos.tsx +0 -173
  469. package/primitives/craft/examples.mdx +0 -142
  470. package/primitives/craft/index.tsx +0 -76
  471. package/primitives/fluid-hover/examples.mdx +0 -84
  472. package/primitives/fluid-hover/index.tsx +0 -316
  473. package/primitives/morph/examples.mdx +0 -139
  474. package/primitives/morph/index.tsx +0 -192
  475. package/primitives/motion/examples.mdx +0 -100
  476. package/primitives/motion/index.tsx +0 -160
  477. package/primitives/scroll-area/examples.mdx +0 -58
  478. package/primitives/scroll-area/index.tsx +0 -204
  479. package/primitives/separator/examples.mdx +0 -30
  480. package/primitives/separator/index.tsx +0 -53
  481. package/primitives/sizes/examples.mdx +0 -167
  482. package/primitives/sizes/index.tsx +0 -89
  483. package/primitives/skeleton/examples.mdx +0 -51
  484. package/primitives/skeleton/index.tsx +0 -72
  485. package/primitives/skeleton/skeleton.css +0 -15
  486. package/primitives/surface/examples.mdx +0 -173
  487. package/primitives/surface/index.tsx +0 -109
  488. package/primitives/surface/surface.css +0 -176
  489. /package/dist/shared/{components-3ey0hzpb.js → components-1v7rxytx.js} +0 -0
  490. /package/dist/shared/{components-jcnjay5g.js → components-ezxhrmer.js} +0 -0
@@ -1,1071 +0,0 @@
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/base/accordion.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
- * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
6
- * Local modifications:
7
- * - `"use client"` dropped; `framer-motion` → `motion/react`; `@/lib/*`,
8
- * `@/hooks/*` and `@/components/ui/fluid-hover-highlight` → `../../lib/*`,
9
- * except `SizeProvider` → `../../primitives/sizes`.
10
- * - Open state (Accordion and AccordionGroup) runs through
11
- * `useControllableState`, the Base UI contract: `value` controls, otherwise
12
- * the accordion keeps its own state from `defaultValue` and still reports
13
- * every change to `onValueChange`. Upstream treated `onValueChange` alone
14
- * as controlled, so an uncontrolled accordion with a listener never opened.
15
- * - Standalone `Accordion` accepts `highlight` (the FF API table lists it on
16
- * the root) and hands it to its items through context as their default;
17
- * `AccordionItem`'s own `highlight` still wins.
18
- * - `AccordionItem` hands its ref to Base UI's `Item` instead of overriding the
19
- * render function's ref, which carries the list-item registration the arrow
20
- * keys, Home and End read (without it they threw a TypeError).
21
- * - Prop JSDoc rewritten from the FF docs API tables; modo TSDoc and
22
- * examples on `Accordion`.
23
- * - Compound statics `Accordion.Group/.Item/.Trigger/.Content` attached with
24
- * `Object.assign`, typed through an `AccordionComponent` cast on the
25
- * forwardRef. Upstream's named exports are kept.
26
- * - Styling reads DS tokens (AGENTS.md styling): inline
27
- * `fontVariationSettings` → `weight-*`; the hex focus-ring fallback →
28
- * `ring-focus-ring` / `border-focus-ring`; `duration-80|120|160` and
29
- * tier-length JS durations → `duration-<tier>` / `spring.*`.
30
- * - The panel morphs out of its row instead of springing its height (local
31
- * morph language, see `primitives/morph`): one progress value grows a shape
32
- * from the press point in the row (its middle for a keyboard open) to the
33
- * whole item, the content is revealed by a clip from the same rect, and the
34
- * panel's layout height follows the shape's bottom edge, so the rows below
35
- * flow with it. Under `highlight="item"` the open tint is that shape, melted
36
- * into the row through the shared `GooFilter`; it lives in
37
- * `AccordionContent` now (the group draws only the `"trigger"` tint), and
38
- * the hover fills sit one layer above it. The group's hover highlight and
39
- * focus ring snap with rows a morph pushes around (instead of springing
40
- * after them over the content) until the pointer or focus moves. `effect`
41
- * and `tier` on `AccordionContent`; reduced motion snaps, as before.
42
- * - `AccordionTrigger` takes `render` / `asChild` (`lib/slot.ts`, local): the element is
43
- * Base UI's Trigger as it is, inside the same row and header, with no row look, label
44
- * copy or chevron. Its own `id` is the one Base UI registers (`slotId`).
45
- */
46
-
47
- import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion'
48
- import { AnimatePresence, animate, motion, useMotionValue, useTransform } from 'motion/react'
49
- import {
50
- createContext,
51
- type ForwardRefExoticComponent,
52
- forwardRef,
53
- type HTMLAttributes,
54
- type ReactNode,
55
- type RefAttributes,
56
- useCallback,
57
- useContext,
58
- useEffect,
59
- useId,
60
- useImperativeHandle,
61
- useLayoutEffect,
62
- useMemo,
63
- useRef,
64
- useState,
65
- } from 'react'
66
-
67
- // SSR-safe layout effect (client components still server-render in Next).
68
- const useIsoLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
69
-
70
- import { FluidHoverHighlight } from '../../lib/fluid-hover-highlight'
71
- import { type IconComponent, useIcon } from '../../lib/icon-context'
72
- import { GooFilter } from '../../lib/morph-layers'
73
- import { useReduceMotion } from '../../lib/reduced-motion'
74
- import { useShape } from '../../lib/shape-context'
75
- import { type SizeVariant, useSize } from '../../lib/size-context'
76
- import { type SlotProps, slotId, slotRender } from '../../lib/slot'
77
- import { spring } from '../../lib/springs'
78
- import { useControllableState } from '../../lib/use-controllable-state'
79
- import { useFluidHover, useRegisterFluidHoverItem } from '../../lib/use-fluid-hover'
80
- import { GOO_BLUR_RATIO } from '../../lib/use-morph'
81
- import { cn } from '../../lib/utils'
82
- import { SizeProvider } from '../../primitives/sizes'
83
-
84
- // ─── Contexts ────────────────────────────────────────────────────────────────
85
-
86
- interface ItemRect {
87
- top: number
88
- left: number
89
- width: number
90
- height: number
91
- }
92
-
93
- interface AccordionGroupContextValue {
94
- registerItem: (index: number, element: HTMLElement | null) => void
95
- registerFullItem: (index: number, element: HTMLElement | null) => void
96
- activeIndex: number | null
97
- grouped: true
98
- remeasure: () => void
99
- openValues: Set<string>
100
- openItemRects: Map<number, ItemRect>
101
- /** The group's choice; its items draw the `"item"` tint themselves. */
102
- highlight: 'trigger' | 'item'
103
- /** A closed row is hovered: open tints step back. */
104
- dimOpen: boolean
105
- }
106
-
107
- const AccordionGroupContext = createContext<AccordionGroupContextValue | null>(null)
108
-
109
- function useAccordionGroup() {
110
- return useContext(AccordionGroupContext)
111
- }
112
-
113
- interface AccordionItemContextValue {
114
- index?: number
115
- value: string
116
- isOpen: boolean
117
- triggerRef: React.MutableRefObject<HTMLDivElement | null>
118
- /** Standalone items carry the group's choice themselves. */
119
- highlight: 'trigger' | 'item'
120
- /** Where the row was last pressed, in px from its left edge; the panel grows from there. */
121
- press: React.MutableRefObject<number | null>
122
- }
123
-
124
- const AccordionItemContext = createContext<AccordionItemContextValue | null>(null)
125
-
126
- function useAccordionItemContext() {
127
- const ctx = useContext(AccordionItemContext)
128
- if (!ctx) throw new Error('AccordionTrigger/AccordionContent must be used within an AccordionItem')
129
- return ctx
130
- }
131
-
132
- // ─── AccordionGroup ──────────────────────────────────────────────────────────
133
-
134
- type AccordionGroupSingleProps = {
135
- /** Whether one or multiple items can be expanded. Defaults to `"single"`. */
136
- type?: 'single'
137
- /** Controlled expanded value. */
138
- value?: string
139
- /** Initially expanded item value — uncontrolled. */
140
- defaultValue?: string
141
- /** Callback when expanded state changes. */
142
- onValueChange?: (value: string) => void
143
- /** Allow collapsing all items when type is single. Defaults to `true`. */
144
- collapsible?: boolean
145
- }
146
-
147
- type AccordionGroupMultipleProps = {
148
- /** Whether one or multiple items can be expanded. */
149
- type: 'multiple'
150
- /** Controlled expanded values. */
151
- value?: string[]
152
- /** Initially expanded item values — uncontrolled. */
153
- defaultValue?: string[]
154
- /** Callback when expanded state changes. */
155
- onValueChange?: (value: string[]) => void
156
- }
157
-
158
- type AccordionGroupProps = HTMLAttributes<HTMLDivElement> & {
159
- children: ReactNode
160
- /** Pins the group's rows to one step of the size ladder (default 36px,
161
- * compact 28px — see /docs/sizes). Omitted, they follow the surrounding
162
- * SizeProvider. */
163
- size?: SizeVariant
164
- /** What an open item tints. "item" paints the row and its panel as one
165
- * block, and holds while it stays open. "trigger" scopes the fill to the
166
- * row and shows it on hover only, leaving the panel on the page's own
167
- * surface — the way a sidebar row highlights without colouring its
168
- * sub-tree. @default "item" */
169
- highlight?: 'trigger' | 'item'
170
- } & (AccordionGroupSingleProps | AccordionGroupMultipleProps)
171
-
172
- const AccordionGroup = forwardRef<HTMLDivElement, AccordionGroupProps>((props, ref) => {
173
- const { children, highlight = 'item', type = 'single', size, className, ...rest } = props
174
-
175
- const containerRef = useRef<HTMLDivElement>(null)
176
- const fullItemElementsRef = useRef<Map<number, HTMLElement>>(new Map())
177
- const [openItemRects, setOpenItemRects] = useState<Map<number, ItemRect>>(new Map())
178
- const openItemRectsRef = useRef(openItemRects)
179
-
180
- const hover = useFluidHover(containerRef)
181
- const { activeIndex, setActiveIndex, itemRects, handlers, registerItem, measureItems } = hover
182
-
183
- const registerFullItem = useCallback((index: number, element: HTMLElement | null) => {
184
- if (element) {
185
- fullItemElementsRef.current.set(index, element)
186
- } else {
187
- fullItemElementsRef.current.delete(index)
188
- }
189
- }, [])
190
-
191
- const measureFullItems = useCallback(() => {
192
- if (!containerRef.current) return
193
- const next = new Map<number, ItemRect>()
194
- fullItemElementsRef.current.forEach((el, idx) => {
195
- next.set(idx, {
196
- top: el.offsetTop,
197
- left: el.offsetLeft,
198
- width: el.offsetWidth,
199
- height: el.offsetHeight,
200
- })
201
- })
202
- // Skip the state update when nothing moved (mirrors the fluid hover
203
- // hook's measureItems guard) — this runs per animation frame via
204
- // onUpdate, and an unconditional set would invalidate the group
205
- // context and re-render every item even on no-op remeasures.
206
- const prev = openItemRectsRef.current
207
- let changed = prev.size !== next.size
208
- if (!changed) {
209
- for (const [idx, r] of next) {
210
- const p = prev.get(idx)
211
- if (!p || p.top !== r.top || p.left !== r.left || p.width !== r.width || p.height !== r.height) {
212
- changed = true
213
- break
214
- }
215
- }
216
- }
217
- if (!changed) return
218
- openItemRectsRef.current = next
219
- setOpenItemRects(next)
220
- }, [])
221
-
222
- const sp = props as AccordionGroupSingleProps
223
- const mp = props as AccordionGroupMultipleProps
224
- const [singleValue, setSingleValue] = useControllableState<string>(
225
- type === 'single' ? sp.value : undefined,
226
- type === 'single' ? (sp.defaultValue ?? '') : '',
227
- type === 'single' ? sp.onValueChange : undefined,
228
- )
229
- const [multipleValue, setMultipleValue] = useControllableState<string[]>(
230
- type === 'multiple' ? mp.value : undefined,
231
- type === 'multiple' ? (mp.defaultValue ?? []) : [],
232
- type === 'multiple' ? mp.onValueChange : undefined,
233
- )
234
-
235
- const openValuesList: string[] = type === 'multiple' ? multipleValue : singleValue ? [singleValue] : []
236
-
237
- // Keyed on the joined values so the Set (and the group context value
238
- // below) keeps a stable identity across re-renders where the open values
239
- // haven't actually changed.
240
- const openValuesKey = openValuesList.join(',')
241
-
242
- const openValues = useMemo(
243
- () => new Set(openValuesList),
244
- // Deliberately keyed on the joined string, not the (fresh) array.
245
- [openValuesKey],
246
- )
247
-
248
- useEffect(() => {
249
- measureItems()
250
- measureFullItems()
251
- }, [measureItems, measureFullItems, children])
252
-
253
- useEffect(() => {
254
- measureItems()
255
- measureFullItems()
256
- }, [measureItems, measureFullItems, openValuesKey])
257
-
258
- const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
259
-
260
- const focusRect = focusedIndex !== null ? itemRects[focusedIndex] : null
261
- // "trigger" tints the open row only while you're on it: the panel below
262
- // already says the item is open, so the fill goes back to being a hover
263
- // affordance rather than a persistent state. The trigger rects are the
264
- // ones fluid hover already tracks. Local: the "item" block is drawn by each
265
- // item's content, as the shape its panel morphs out of.
266
- const expandedRects =
267
- highlight === 'trigger'
268
- ? new Map(
269
- [...openItemRects.keys()].flatMap(idx => {
270
- const rect = idx === activeIndex ? itemRects[idx] : null
271
- return rect ? ([[idx, rect]] as [number, ItemRect][]) : []
272
- }),
273
- )
274
- : new Map<number, ItemRect>()
275
-
276
- const isHoveringNonOpen = activeIndex !== null && !openItemRects.has(activeIndex)
277
- const shape = useShape()
278
-
279
- const {
280
- value: _value,
281
- defaultValue: _defaultValue,
282
- onValueChange: _onValueChange,
283
- collapsible: _collapsible,
284
- type: _type,
285
- ...htmlProps
286
- } = rest as Record<string, unknown>
287
-
288
- // Translate FF API → Base UI Accordion API.
289
- // Base UI always uses `value: string[]` and a `multiple: boolean`. In
290
- // single mode we wrap the active value in a single-element array.
291
- const baseValue: string[] = openValuesList
292
-
293
- const baseOnValueChange = (next: string[]) => {
294
- if (type === 'multiple') setMultipleValue(next)
295
- else setSingleValue(next[0] ?? '')
296
- }
297
-
298
- // Local: rows a morphing panel pushes around carry the hover highlight and
299
- // the focus ring with them (a snap, not a spring chasing them), until the
300
- // pointer or the focus moves again.
301
- const [reflowing, setReflowing] = useState(false)
302
- const remeasure = useCallback(() => {
303
- setReflowing(true)
304
- measureItems()
305
- measureFullItems()
306
- }, [measureItems, measureFullItems])
307
-
308
- // Memoized: the group re-renders on every fluid-hover mousemove; a
309
- // fresh context object each time would re-render every item with it.
310
- const groupContextValue = useMemo<AccordionGroupContextValue>(
311
- () => ({
312
- registerItem,
313
- registerFullItem,
314
- activeIndex,
315
- grouped: true,
316
- remeasure,
317
- openValues,
318
- openItemRects,
319
- highlight,
320
- dimOpen: isHoveringNonOpen,
321
- }),
322
- [registerItem, registerFullItem, activeIndex, remeasure, openValues, openItemRects, highlight, isHoveringNonOpen],
323
- )
324
-
325
- const group = (
326
- <AccordionGroupContext.Provider value={groupContextValue}>
327
- <AccordionPrimitive.Root
328
- value={baseValue}
329
- onValueChange={baseOnValueChange}
330
- multiple={type === 'multiple'}
331
- render={rootProps => {
332
- const {
333
- style: _baseStyle,
334
- onDrag: _onDrag,
335
- onDragStart: _onDragStart,
336
- onDragEnd: _onDragEnd,
337
- onAnimationStart: _onAnimationStart,
338
- onAnimationEnd: _onAnimationEnd,
339
- onAnimationIteration: _onAnimationIteration,
340
- ...restRoot
341
- } = rootProps as React.HTMLAttributes<HTMLDivElement>
342
- return (
343
- <div
344
- {...restRoot}
345
- ref={node => {
346
- ;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
347
- if (typeof ref === 'function') ref(node)
348
- else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
349
- }}
350
- onMouseEnter={handlers.onMouseEnter}
351
- onMouseMove={e => {
352
- setReflowing(false)
353
- const container = containerRef.current
354
- if (container) {
355
- const cRect = container.getBoundingClientRect()
356
- const layoutH = container.offsetHeight
357
- const visualH = cRect.height
358
- const scale = layoutH > 0 ? visualH / layoutH : 1
359
- const localY = (e.clientY - cRect.top) / scale + container.scrollTop
360
- for (const [idx, full] of openItemRects) {
361
- const trigger = itemRects[idx]
362
- if (!trigger) continue
363
- const contentTop = trigger.top + trigger.height
364
- const contentBottom = full.top + full.height
365
- if (localY >= contentTop && localY <= contentBottom) {
366
- setActiveIndex(null)
367
- return
368
- }
369
- }
370
- }
371
- handlers.onMouseMove(e)
372
- }}
373
- onMouseLeave={handlers.onMouseLeave}
374
- onFocus={e => {
375
- setReflowing(false)
376
- const indexAttr = (e.target as HTMLElement)
377
- .closest('[data-fluid-hover-index]')
378
- ?.getAttribute('data-fluid-hover-index')
379
- if (indexAttr != null) {
380
- const idx = Number(indexAttr)
381
- setActiveIndex(idx)
382
- setFocusedIndex((e.target as HTMLElement).matches(':focus-visible') ? idx : null)
383
- }
384
- }}
385
- onBlur={e => {
386
- if (containerRef.current?.contains(e.relatedTarget as Node)) return
387
- setFocusedIndex(null)
388
- setActiveIndex(null)
389
- }}
390
- className={cn('relative flex flex-col w-72 max-w-full', className)}
391
- {...(htmlProps as HTMLAttributes<HTMLDivElement>)}
392
- >
393
- {/* Expanded item backgrounds */}
394
- <AnimatePresence>
395
- {[...expandedRects.entries()].map(([idx, rect]) => (
396
- <motion.div
397
- key={`expanded-${idx}`}
398
- className={`absolute ${shape.bg} bg-accent/20 dark:bg-accent/12 pointer-events-none`}
399
- // Fade in from the item's current rect: with initial={false}
400
- // a newly-opened item's background would pop in at full
401
- // opacity mid-layout-shift while the previous item's bg is
402
- // still fading out — reads as a glitch when switching items
403
- // (especially under /demo's scaled card). Geometry still
404
- // snaps (duration 0) so the bg hugs the animating item.
405
- initial={{
406
- top: rect.top,
407
- left: rect.left,
408
- width: rect.width,
409
- height: rect.height,
410
- opacity: 0,
411
- }}
412
- animate={{
413
- top: rect.top,
414
- left: rect.left,
415
- width: rect.width,
416
- height: rect.height,
417
- opacity: isHoveringNonOpen ? 0.7 : 1,
418
- }}
419
- exit={{ opacity: 0, transition: spring.moderate.exit }}
420
- transition={{
421
- top: { duration: 0 },
422
- left: { duration: 0 },
423
- width: { duration: 0 },
424
- height: { duration: 0 },
425
- opacity: { duration: spring.moderate.exit.duration },
426
- }}
427
- />
428
- ))}
429
- </AnimatePresence>
430
-
431
- {/* Hover background, one layer above the items' open tints */}
432
- <FluidHoverHighlight
433
- hover={hover}
434
- className={cn(shape.bg, 'z-1')}
435
- transition={reflowing ? false : undefined}
436
- />
437
-
438
- {/* Focus ring */}
439
- <AnimatePresence>
440
- {focusRect && (
441
- <motion.div
442
- className={`absolute ${shape.focusRing} pointer-events-none z-20 border border-focus-ring`}
443
- initial={false}
444
- animate={{
445
- left: focusRect.left - 2,
446
- top: focusRect.top - 2,
447
- width: focusRect.width + 4,
448
- height: focusRect.height + 4,
449
- }}
450
- exit={{ opacity: 0, transition: spring.fast.exit }}
451
- transition={{
452
- ...(reflowing ? { duration: 0 } : spring.fast),
453
- opacity: { duration: spring.fast.duration },
454
- }}
455
- />
456
- )}
457
- </AnimatePresence>
458
-
459
- {children}
460
- </div>
461
- )
462
- }}
463
- />
464
- </AccordionGroupContext.Provider>
465
- )
466
-
467
- // A size prop pins every row in the group to one ladder step.
468
- return size ? <SizeProvider size={size}>{group}</SizeProvider> : group
469
- })
470
-
471
- AccordionGroup.displayName = 'AccordionGroup'
472
-
473
- // ─── Accordion (Standalone) ──────────────────────────────────────────────────
474
-
475
- interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
476
- /** The accordion's items (`Accordion.Item`). */
477
- children: ReactNode
478
- /** Whether one or multiple items can be expanded. Defaults to `"single"`. */
479
- type?: 'single' | 'multiple'
480
- /** Allow collapsing all items when type is single. Defaults to `true` (Base UI's single mode is always collapsible). */
481
- collapsible?: boolean
482
- /** Initially expanded item value(s) — uncontrolled. A string for `type="single"`, an array for `type="multiple"`. */
483
- defaultValue?: string | string[]
484
- /** Controlled expanded value(s). Pair with `onValueChange`. */
485
- value?: string | string[]
486
- /** Callback when expanded state changes. Receives a string in single mode, an array in multiple mode. */
487
- onValueChange?: ((value: string) => void) | ((value: string[]) => void)
488
- /** What an open item tints: `item` holds a block across the row and its panel, `trigger` scopes it to the row and shows it on hover. Same prop on `Accordion.Group` and on a standalone `Accordion.Item`. Defaults to `"item"`. */
489
- highlight?: 'trigger' | 'item'
490
- /** Pins the accordion's rows to one step of the size ladder (default 36px, compact 28px). Omitted, they follow the surrounding SizeProvider. */
491
- size?: SizeVariant
492
- }
493
-
494
- type AccordionComponent = ForwardRefExoticComponent<AccordionProps & RefAttributes<HTMLDivElement>> & {
495
- Group: typeof AccordionGroup
496
- Item: typeof AccordionItem
497
- Trigger: typeof AccordionTrigger
498
- Content: typeof AccordionContent
499
- }
500
-
501
- /**
502
- * Collapsible sections with animated expand/collapse and fluid hover in
503
- * grouped mode.
504
- *
505
- * Accordion is the standalone form: every row carries its own hover fill.
506
- * Accordion.Group takes the same props and adds one magnetic highlight that
507
- * glides between rows, so give each item inside it an `index`. Built on Base
508
- * UI Accordion (WAI-ARIA wiring, keyboard navigation, focus management).
509
- * Uncontrolled through `defaultValue`, controlled through `value` +
510
- * `onValueChange`.
511
- *
512
- * The panel grows out of its row (see Morph): a shape drips from where the
513
- * row was pressed (its middle, from the keyboard), widens into the whole
514
- * item, and the content is revealed by a clip from the same shape. Under the
515
- * default `highlight="item"` that shape is the open tint, melted into the
516
- * row through a liquid goo neck; closing folds it back into the row. The
517
- * rows below ride the shape's bottom edge, so nothing jumps.
518
- *
519
- * Statics: Accordion.Group (the grouped root: type, collapsible,
520
- * defaultValue, value, onValueChange, highlight, size), Accordion.Item (value,
521
- * index, disabled), Accordion.Trigger (the row that toggles its item: `icon`
522
- * swaps or hides the chevron, `weightShift={false}` renders a rich label once,
523
- * `render` / `asChild` make your own element the trigger, unstyled and without
524
- * chevron or label copy)
525
- * and Accordion.Content (the collapsible panel: `effect` — `'goo'` or a plain
526
- * `'morph'` — the spring `tier`, `'slow'` or `'moderate'`, and
527
- * `contentClassName` for the inner row).
528
- *
529
- * @example {@include ./examples.mdx}
530
- */
531
- const Accordion = forwardRef<HTMLDivElement, AccordionProps>(
532
- (
533
- {
534
- children,
535
- type = 'single',
536
- collapsible = true,
537
- defaultValue,
538
- value,
539
- onValueChange,
540
- highlight,
541
- size,
542
- className,
543
- ...props
544
- },
545
- ref,
546
- ) => {
547
- void collapsible // Base UI's single-mode is always collapsible.
548
-
549
- const [singleValue, setSingleValue] = useControllableState<string>(
550
- type === 'single' ? (value as string | undefined) : undefined,
551
- type === 'single' ? ((defaultValue as string | undefined) ?? '') : '',
552
- type === 'single' ? (onValueChange as ((v: string) => void) | undefined) : undefined,
553
- )
554
- const [multipleValue, setMultipleValue] = useControllableState<string[]>(
555
- type === 'multiple' ? (value as string[] | undefined) : undefined,
556
- type === 'multiple' ? ((defaultValue as string[] | undefined) ?? []) : [],
557
- type === 'multiple' ? (onValueChange as ((v: string[]) => void) | undefined) : undefined,
558
- )
559
-
560
- const openValues = new Set<string>(type === 'multiple' ? multipleValue : singleValue ? [singleValue] : [])
561
-
562
- const baseValue: string[] = [...openValues]
563
-
564
- const baseOnValueChange = (next: string[]) => {
565
- if (type === 'multiple') setMultipleValue(next)
566
- else setSingleValue(next[0] ?? '')
567
- }
568
-
569
- const root = (
570
- <AccordionPrimitive.Root
571
- value={baseValue}
572
- onValueChange={baseOnValueChange}
573
- multiple={type === 'multiple'}
574
- render={rootProps => {
575
- const { style: _s, ...restRoot } = rootProps as React.HTMLAttributes<HTMLDivElement>
576
- return (
577
- <div {...restRoot} ref={ref} className={cn('w-72 max-w-full flex flex-col', className)} {...props}>
578
- <StandaloneOpenContext.Provider value={openValues}>
579
- <StandaloneHighlightContext.Provider value={highlight}>{children}</StandaloneHighlightContext.Provider>
580
- </StandaloneOpenContext.Provider>
581
- </div>
582
- )
583
- }}
584
- />
585
- )
586
-
587
- // A size prop pins every row to one ladder step.
588
- return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
589
- },
590
- ) as AccordionComponent
591
-
592
- Accordion.displayName = 'Accordion'
593
-
594
- const StandaloneOpenContext = createContext<Set<string>>(new Set())
595
-
596
- const StandaloneHighlightContext = createContext<'trigger' | 'item' | undefined>(undefined)
597
-
598
- // ─── AccordionItem ───────────────────────────────────────────────────────────
599
-
600
- interface AccordionItemProps extends HTMLAttributes<HTMLDivElement> {
601
- /** Unique identifier for this item. */
602
- value: string
603
- /** Position index for fluid hover. Required inside `Accordion.Group`, omit for standalone. */
604
- index?: number
605
- /** Whether this item is disabled. Defaults to `false`. */
606
- disabled?: boolean
607
- /** Standalone equivalent of the group's prop: what an open item tints. Ignored inside a group, which decides for all its rows. Defaults to the standalone root's `highlight`, else `"item"`. */
608
- highlight?: 'trigger' | 'item'
609
- /** The item's `Accordion.Trigger` and `Accordion.Content`. */
610
- children: ReactNode
611
- }
612
-
613
- const AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(
614
- ({ value, index, disabled, highlight: highlightProp, children, className, ...props }, ref) => {
615
- const internalRef = useRef<HTMLDivElement>(null)
616
- const groupCtx = useAccordionGroup()
617
- const standaloneOpen = useContext(StandaloneOpenContext)
618
- const standaloneHighlight = useContext(StandaloneHighlightContext)
619
- const highlight = groupCtx ? groupCtx.highlight : (highlightProp ?? standaloneHighlight ?? 'item')
620
-
621
- const isOpen = groupCtx?.grouped ? groupCtx.openValues.has(value) : standaloneOpen.has(value)
622
-
623
- const triggerRef = useRef<HTMLDivElement>(null)
624
- const press = useRef<number | null>(null)
625
-
626
- useRegisterFluidHoverItem(groupCtx?.grouped ? groupCtx.registerItem : undefined, index, triggerRef)
627
-
628
- useEffect(() => {
629
- if (groupCtx?.grouped && index !== undefined) {
630
- if (isOpen) {
631
- groupCtx.registerFullItem(index, internalRef.current)
632
- } else {
633
- groupCtx.registerFullItem(index, null)
634
- }
635
- return () => groupCtx.registerFullItem(index, null)
636
- }
637
- }, [index, groupCtx, isOpen])
638
-
639
- return (
640
- <AccordionItemContext.Provider value={{ index, value, isOpen, triggerRef, highlight, press }}>
641
- <AccordionPrimitive.Item
642
- // Through Base UI, which merges it into the render function's ref
643
- // with the list-item registration that arrow-key navigation reads.
644
- ref={node => {
645
- ;(internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node
646
- if (typeof ref === 'function') ref(node)
647
- else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
648
- }}
649
- value={value}
650
- disabled={disabled}
651
- render={itemProps => {
652
- const { style: _s, ...restItem } = itemProps as React.HTMLAttributes<HTMLDivElement>
653
- return (
654
- <div
655
- {...restItem}
656
- data-fluid-hover-index={index}
657
- // Positioned in a group too: the content's open tint covers
658
- // the whole item (fluid hover adds positioned ancestors'
659
- // offsets, so the row rects don't move).
660
- className={cn('relative', className)}
661
- {...props}
662
- >
663
- {children}
664
- </div>
665
- )
666
- }}
667
- />
668
- </AccordionItemContext.Provider>
669
- )
670
- },
671
- )
672
-
673
- AccordionItem.displayName = 'AccordionItem'
674
-
675
- // ─── AccordionTrigger ────────────────────────────────────────────────────────
676
-
677
- interface AccordionTriggerProps extends Omit<SlotProps, 'children'> {
678
- /** Trigger label content. With `asChild`, the single child element that becomes the trigger instead, and `render` takes an element the same way: that element is rendered as it is, the whole trigger, look included. It gets Base UI's `aria-expanded`, `aria-controls`, `data-panel-open` and key handling, and sits in the same row, but the label copy, `weightShift`, `icon` and chevron are not rendered (give it its own content), nor are the trigger's padding and ring. */
679
- children?: ReactNode
680
- /** The glyph at the row's end, which turns a quarter turn when the item opens (so give it a right-pointing one), or `false` for none. Defaults to the icon context's `chevron-right`. */
681
- icon?: IconComponent | false
682
- /** Whether the label gets heavier while the item is open. On, the label renders twice in one grid cell (a hidden semibold copy reserves its width, so nothing reflows); off, it renders once and keeps whatever weight its content has, which suits rich content such as a whole `Card.Header` that would otherwise be duplicated in the DOM. The label sits inside the trigger `<button>`, so give it phrasing content (spans), not block elements such as a `div`-based header. Rich content that sets its own text colour does not change on open: only the chevron, the highlight and the open tint signal the state. Defaults to `true`. */
683
- weightShift?: boolean
684
- }
685
-
686
- const AccordionTrigger = forwardRef<HTMLButtonElement, AccordionTriggerProps>(
687
- ({ children, className, icon, weightShift = true, render, asChild, nativeButton, ...props }, ref) => {
688
- const el = slotRender(render, asChild, children)
689
- const ChevronRight = useIcon('chevron-right')
690
- const Glyph = icon === false ? null : (icon ?? ChevronRight)
691
- const groupCtx = useAccordionGroup()
692
- const { index, isOpen, triggerRef, highlight, press } = useAccordionItemContext()
693
- const shape = useShape()
694
- const sizeClasses = useSize()
695
- const [isHovered, setIsHovered] = useState(false)
696
-
697
- const isActive = groupCtx?.grouped ? groupCtx.activeIndex === index : isHovered
698
-
699
- // In layout px, so a scaled ancestor doesn't move the source.
700
- const capture = (event: React.PointerEvent<HTMLDivElement>) => {
701
- const row = event.currentTarget
702
- const box = row.getBoundingClientRect()
703
- press.current = ((event.clientX - box.left) * row.offsetWidth) / box.width
704
- }
705
-
706
- const triggerContent = (
707
- // Render Header as a <div>. Base UI's Header defaults to <h3>, which
708
- // would be more semantic but breaks the ancestor selectors the styles
709
- // rely on.
710
- <AccordionPrimitive.Header render={<div />}>
711
- <AccordionPrimitive.Trigger
712
- ref={ref as React.Ref<HTMLElement>}
713
- // A `render` / `asChild` element is the whole trigger: its own look, no row padding or ring.
714
- className={
715
- el
716
- ? className
717
- : cn(
718
- `relative z-10 flex items-center ${sizeClasses.gap} ${shape.item} ${sizeClasses.px} ${sizeClasses.variant === 'compact' ? 'py-1' : 'py-2'} w-full cursor-pointer outline-none select-none`,
719
- !groupCtx?.grouped &&
720
- 'focus-visible:ring-1 focus-visible:ring-focus-ring focus-visible:ring-offset-0',
721
- className,
722
- )
723
- }
724
- {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
725
- {...(el ? { render: el, nativeButton, id: slotId(el, props.id) } : {})}
726
- >
727
- {el ? undefined : weightShift ? (
728
- <span className={cn('inline-grid flex-1 text-left', sizeClasses.text)}>
729
- <span className="col-start-1 row-start-1 invisible weight-semibold" aria-hidden="true">
730
- {children}
731
- </span>
732
- <span
733
- className={cn(
734
- 'col-start-1 row-start-1 transition-[color,font-variation-settings] duration-fast',
735
- isOpen || isActive ? 'text-foreground' : 'text-muted-foreground',
736
- isOpen ? 'weight-semibold' : 'weight-normal',
737
- )}
738
- >
739
- {children}
740
- </span>
741
- </span>
742
- ) : (
743
- <span
744
- className={cn(
745
- 'block min-w-0 flex-1 text-left transition-[color] duration-fast',
746
- sizeClasses.text,
747
- isOpen || isActive ? 'text-foreground' : 'text-muted-foreground',
748
- )}
749
- >
750
- {children}
751
- </span>
752
- )}
753
-
754
- {/* Chevron */}
755
- {!el && Glyph && (
756
- <motion.span
757
- className="shrink-0 inline-flex items-center justify-center"
758
- animate={{ rotate: isOpen ? 90 : 0 }}
759
- transition={spring.fast}
760
- >
761
- <Glyph
762
- size={sizeClasses.icon}
763
- strokeWidth={isOpen || isActive ? 2 : 1.5}
764
- className={cn(
765
- 'transition-[color,stroke-width] duration-fast',
766
- isOpen || isActive ? 'text-foreground' : 'text-muted-foreground',
767
- )}
768
- />
769
- </motion.span>
770
- )}
771
- </AccordionPrimitive.Trigger>
772
- </AccordionPrimitive.Header>
773
- )
774
-
775
- if (groupCtx?.grouped) {
776
- return (
777
- <div ref={triggerRef} onPointerDown={capture}>
778
- {triggerContent}
779
- </div>
780
- )
781
- }
782
-
783
- return (
784
- <div
785
- className="relative"
786
- onPointerDown={capture}
787
- onMouseEnter={() => setIsHovered(true)}
788
- onMouseLeave={() => setIsHovered(false)}
789
- >
790
- {/* Open tint, scoped to this row: the panel below keeps the page's
791
- own surface, the way a sidebar row highlights without colouring
792
- its sub-tree. */}
793
- <AnimatePresence>
794
- {isOpen && highlight === 'trigger' && isHovered && (
795
- <motion.div
796
- className={`absolute inset-0 ${shape.bg} bg-accent/20 dark:bg-accent/12 pointer-events-none`}
797
- initial={{ opacity: 0 }}
798
- animate={{ opacity: 1 }}
799
- // The expanded tint rides the moderate tier, like the grouped
800
- // one — it marks a state, where the hover fill below tracks the
801
- // pointer and stays fast.
802
- exit={{ opacity: 0, transition: spring.moderate.exit }}
803
- transition={{ duration: spring.moderate.exit.duration }}
804
- />
805
- )}
806
- </AnimatePresence>
807
- <AnimatePresence>
808
- {isHovered && (
809
- <motion.div
810
- className={`absolute inset-0 z-1 ${shape.bg} bg-hover pointer-events-none`}
811
- initial={{ opacity: 0 }}
812
- animate={{ opacity: 1 }}
813
- exit={{ opacity: 0, transition: spring.fast.exit }}
814
- transition={{ duration: spring.fast.duration }}
815
- />
816
- )}
817
- </AnimatePresence>
818
- {triggerContent}
819
- </div>
820
- )
821
- },
822
- )
823
-
824
- AccordionTrigger.displayName = 'AccordionTrigger'
825
-
826
- // ─── AccordionContent ────────────────────────────────────────────────────────
827
-
828
- interface AccordionContentProps extends HTMLAttributes<HTMLDivElement> {
829
- /** Collapsible content. */
830
- children: ReactNode
831
- /** How the panel grows out of its row (see Morph): `goo` melts the open tint out of the row through a liquid neck, `morph` grows it without one. Defaults to `'goo'`. */
832
- effect?: 'goo' | 'morph'
833
- /** Spring tier the panel opens on; it closes on the tier's faster exit tween. Defaults to `'slow'`. */
834
- tier?: 'moderate' | 'slow'
835
- /** Classes for the inner row that holds the children, merged over its defaults (the row inset, the top and bottom padding, the muted body text), so `px-0 text-foreground` takes the content flush and full-strength. */
836
- contentClassName?: string
837
- }
838
-
839
- interface Rect {
840
- x: number
841
- y: number
842
- w: number
843
- h: number
844
- r: number
845
- }
846
-
847
- // The morph engine's rect interpolation (lib/use-morph.ts). `t` stops at 1:
848
- // the slow tier's bounce would push the rows below past their place and back.
849
- function grow(a: Rect, b: Rect, t: number): Rect {
850
- const p = Math.min(1, t)
851
- const at = (from: number, to: number) => from + (to - from) * p
852
- const w = at(a.w, b.w)
853
- const h = at(a.h, b.h)
854
- return { x: at(a.x, b.x), y: at(a.y, b.y), w, h, r: Math.min(at(a.r, b.r), w / 2, h / 2) }
855
- }
856
-
857
- function place(el: HTMLElement | null, { x, y, w, h, r }: Rect) {
858
- if (!el) return
859
- el.style.left = `${x}px`
860
- el.style.top = `${y}px`
861
- el.style.width = `${w}px`
862
- el.style.height = `${h}px`
863
- el.style.borderRadius = `${r}px`
864
- }
865
-
866
- const AccordionContent = forwardRef<HTMLDivElement, AccordionContentProps>(
867
- ({ children, className, effect = 'goo', tier = 'slow', contentClassName, ...props }, ref) => {
868
- const groupCtx = useAccordionGroup()
869
- const { isOpen, highlight, press } = useAccordionItemContext()
870
- const shape = useShape()
871
- const sizeClasses = useSize()
872
- const reduceMotion = useReduceMotion()
873
- const gooId = `accordion-goo-${useId().replace(/:/g, '')}`
874
- const progress = useMotionValue(isOpen ? 1 : 0)
875
- // The tint is there for most of the morph and fades only as the shape
876
- // gets back into the row, so the shape, not a fade, carries the motion.
877
- const tint = useTransform(progress, [0, 0.3], [0, 1])
878
- const tinted = highlight === 'item'
879
- const remeasure = useRef(groupCtx?.remeasure)
880
- remeasure.current = groupCtx?.remeasure
881
-
882
- const refs = {
883
- panel: useRef<HTMLDivElement>(null),
884
- content: useRef<HTMLDivElement>(null),
885
- rest: useRef<HTMLDivElement>(null),
886
- goo: useRef<HTMLDivElement>(null),
887
- row: useRef<HTMLDivElement>(null),
888
- shape: useRef<HTMLDivElement>(null),
889
- }
890
- useImperativeHandle(ref, () => refs.panel.current as HTMLDivElement, [])
891
-
892
- // In the item's own coordinates: `x` is the source's left edge, `row`
893
- // the trigger row's height (the panel's offsetTop), `body` the content's.
894
- const geometry = useRef({ x: 0, width: 0, row: 0, body: 0 })
895
-
896
- // One frame of the morph. The shape grows from a point in the middle of
897
- // the row to the whole item; the panel's layout height is the shape's
898
- // reach below the row, so the rows underneath move with its bottom edge,
899
- // and the content shows only inside the shape.
900
- const render = (p: number) => {
901
- const { x, width, row, body } = geometry.current
902
- const rect = grow(
903
- { x, y: row / 2, w: 0, h: 0, r: width },
904
- { x: 0, y: 0, w: width, h: row + body, r: shape.bgRadius },
905
- p,
906
- )
907
- const reach = rect.y + rect.h - row
908
- const { panel, content } = refs
909
- if (panel.current) panel.current.style.height = `${Math.max(0, reach)}px`
910
- if (content.current) {
911
- content.current.style.clipPath = `inset(${rect.y - row}px ${width - rect.x - rect.w}px ${body - reach}px ${rect.x}px round ${rect.r}px)`
912
- }
913
- place(refs.shape.current, rect)
914
- remeasure.current?.()
915
- }
916
-
917
- // Base UI's Panel would apply `hidden` the moment a controlled item
918
- // closes (no CSS animation for it to wait on) — `display: none` would
919
- // cut the morph off mid-flight. So the `hidden` attribute is ours, and
920
- // only lands once the close has finished.
921
- const [exitComplete, setExitComplete] = useState(!isOpen)
922
- if (isOpen && exitComplete) {
923
- // Reset during render so the panel is laid out before the opening
924
- // morph's first frame.
925
- setExitComplete(false)
926
- }
927
-
928
- // Back to rest: open, the panel's height and the tint are their classes
929
- // again (auto height, one block over the item), so they follow content
930
- // that resizes; closed, the panel goes `hidden`.
931
- const settle = (open: boolean) => {
932
- const { panel, content, goo, rest } = refs
933
- if (panel.current) panel.current.style.height = open ? '' : '0px'
934
- // Closed, the content stays clipped shut: `hidden` lands with React's
935
- // next commit, a task later, and unclipped content would paint below
936
- // the collapsed row for a frame in between.
937
- if (content.current) content.current.style.clipPath = open ? '' : 'inset(50%)'
938
- if (goo.current) goo.current.style.display = ''
939
- if (rest.current) rest.current.style.display = ''
940
- remeasure.current?.()
941
- if (!open) setExitComplete(true)
942
- }
943
-
944
- const wasOpen = useRef(isOpen)
945
- useIsoLayoutEffect(() => {
946
- if (wasOpen.current === isOpen) return
947
- wasOpen.current = isOpen
948
- const { panel, content, goo, rest, row } = refs
949
- if (!panel.current || !content.current) return
950
- const g = geometry.current
951
- g.width = panel.current.offsetWidth
952
- g.row = panel.current.offsetTop
953
- g.body = content.current.offsetHeight
954
- // A reopen mid-close keeps its source, so the shape never jumps.
955
- if (isOpen && progress.get() === 0) g.x = press.current ?? g.width / 2
956
- press.current = null
957
-
958
- if (reduceMotion) {
959
- progress.set(isOpen ? 1 : 0)
960
- settle(isOpen)
961
- return
962
- }
963
- if (goo.current) goo.current.style.display = 'block'
964
- if (rest.current) rest.current.style.display = 'none'
965
- if (row.current) row.current.style.height = `${g.row}px`
966
- render(progress.get())
967
- const { exit, ...enter } = spring[tier]
968
- const controls = animate(progress, isOpen ? 1 : 0, {
969
- ...(isOpen ? enter : exit),
970
- onUpdate: render,
971
- onComplete: () => settle(isOpen),
972
- })
973
- return () => controls.stop()
974
- }, [isOpen])
975
-
976
- // The content keeps its natural size while the panel morphs, so this
977
- // never feeds back: it retargets a running morph and, at rest, lets the
978
- // group re-measure rows a nested accordion pushed around.
979
- useIsoLayoutEffect(() => {
980
- const content = refs.content.current
981
- if (!content) return
982
- const observer = new ResizeObserver(() => {
983
- if (content.offsetHeight > 0) geometry.current.body = content.offsetHeight
984
- remeasure.current?.()
985
- })
986
- observer.observe(content)
987
- return () => observer.disconnect()
988
- }, [])
989
-
990
- // Render through `<AccordionPrimitive.Panel keepMounted>` so the panel
991
- // element persists through the close and the trigger ↔ panel ARIA
992
- // contract stays intact: the panel carries `role="region"`,
993
- // `aria-labelledby` and the id that the Trigger's `aria-controls` points
994
- // to.
995
- return (
996
- <AccordionPrimitive.Panel
997
- keepMounted
998
- render={panelProps => {
999
- const {
1000
- hidden: _baseHidden,
1001
- // Only carries the --accordion-panel-height/width vars, which
1002
- // stay 'auto' since Base UI never measures JS-driven animations.
1003
- style: _baseStyle,
1004
- ...restPanel
1005
- } = panelProps as React.HTMLAttributes<HTMLDivElement> & {
1006
- hidden?: boolean
1007
- }
1008
- return (
1009
- <div {...restPanel} hidden={!isOpen && exitComplete}>
1010
- {/* Neither this nor the panel is positioned, so the tint
1011
- below covers the whole item (row + panel). */}
1012
- <div ref={refs.panel} className={className} {...props}>
1013
- {tinted && (
1014
- <motion.div
1015
- aria-hidden
1016
- className="pointer-events-none absolute inset-0"
1017
- initial={false}
1018
- animate={{ opacity: groupCtx?.dimOpen ? 0.7 : 1 }}
1019
- transition={{ duration: spring.moderate.exit.duration }}
1020
- >
1021
- <motion.div className="absolute inset-0" style={{ opacity: tint }}>
1022
- {/* The tint's strength: solid shapes, so the goo
1023
- threshold sees full alpha, faded as one layer. */}
1024
- <div className="absolute inset-0 opacity-20 dark:opacity-12">
1025
- <div ref={refs.rest} className={cn('absolute inset-0 bg-accent', shape.bg)} />
1026
- {effect === 'goo' && <GooFilter id={gooId} blur={shape.bgRadius * GOO_BLUR_RATIO} />}
1027
- {/* The filter is the effect itself: it melts the row and the growing shape into one. */}
1028
- <div
1029
- ref={refs.goo}
1030
- className="absolute inset-0 hidden"
1031
- style={effect === 'goo' ? { filter: `url(#${gooId})` } : undefined}
1032
- >
1033
- <div ref={refs.row} className={cn('absolute inset-x-0 top-0 bg-accent', shape.bg)} />
1034
- <div ref={refs.shape} className="absolute bg-accent" />
1035
- </div>
1036
- </div>
1037
- </motion.div>
1038
- </motion.div>
1039
- )}
1040
- <div
1041
- ref={refs.content}
1042
- className={cn(
1043
- 'relative pt-1 text-muted-foreground',
1044
- sizeClasses.px,
1045
- sizeClasses.text,
1046
- sizeClasses.variant === 'compact' ? 'pb-2.5' : 'pb-3',
1047
- contentClassName,
1048
- )}
1049
- >
1050
- {children}
1051
- </div>
1052
- </div>
1053
- </div>
1054
- )
1055
- }}
1056
- />
1057
- )
1058
- },
1059
- )
1060
-
1061
- AccordionContent.displayName = 'AccordionContent'
1062
-
1063
- Object.assign(Accordion, {
1064
- Group: AccordionGroup,
1065
- Item: AccordionItem,
1066
- Trigger: AccordionTrigger,
1067
- Content: AccordionContent,
1068
- })
1069
-
1070
- export { Accordion, AccordionContent, AccordionGroup, AccordionItem, AccordionTrigger }
1071
- export default Accordion