@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,1044 +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/select.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/*` and
8
- * `@/hooks/*` → `../../../lib/*` (`SizeProvider` → `../../../primitives/sizes`);
9
- * `@/lib/elevated` → `../../../primitives/surface`; `@/components/ui/scroll-area`
10
- * → `../../../primitives/scroll-area`; `@/components/ui/fluid-hover-highlight` →
11
- * `../../../lib/fluid-hover-highlight`.
12
- * - `SelectProps` members re-declared one per line with the FF docs API-table
13
- * descriptions (modo's props table lists only members declared in the
14
- * interface body, and drops members whose type spans lines).
15
- * - Uncontrolled selection was already upstream (`defaultValue` seeds the
16
- * internal value, `onValueChange` still fires while controlled) — verified,
17
- * kept as is rather than rerouted through `useControllableState`.
18
- * - Compound statics `Select.Trigger/.Content/.Item/.Group/.Label/.Separator`
19
- * attached as expando properties on the root function (TS infers them, so
20
- * `<Select.Item>` typechecks); upstream's named exports are kept.
21
- * - modo item: TSDoc + examples from the FF "Select" docs page, and a default
22
- * export.
23
- * - Styling reads DS tokens (AGENTS.md styling): `text-[Npx]` →
24
- * `text-<role>[-compact]`; the hex focus-ring fallback →
25
- * `ring-focus-ring` / `border-focus-ring`; `duration-80|120|160` and
26
- * tier-length JS durations → `duration-<tier>` / `spring.*`.
27
- * - The popup morphs out of its trigger through the shared morph layer
28
- * (`lib/use-morph.ts` + `MorphSurface`, goo by default on
29
- * `spring.goo`; `Select.Content` takes `from` / `effect` /
30
- * `hideSource` / `tier`). The `scaleY` motion wrapper and the `actionsRef`
31
- * deferred unmount (with its fallback timer) are gone: the morph holds Base
32
- * UI's unmount itself. The popup paints its level through the morph's
33
- * surface layers instead of `render={<Elevated/>}`, re-providing the level
34
- * with `SurfaceProvider` as `Elevated` did.
35
- * - `effect="morph"` with `hideSource` opens the menu in place over its
36
- * trigger: the positioner's `sideOffset` becomes the engine's `inPlaceOffset`.
37
- * - `Select.Item`'s `index` is optional: rows take their place in document
38
- * order (`lib/row-order.tsx`), inside groups, conditionals and maps alike;
39
- * the checked row is found by its own index rather than its DOM position.
40
- * A row's label may be any node (the trigger shows it, through Base UI's
41
- * `items`), with `textValue` for typeahead.
42
- * - `Select.Value` (local): the trigger's value slot as a part, so a trigger
43
- * can take it as children (shadcn's `<SelectValue placeholder>`).
44
- * - `Select.Content` takes `side` / `align` / `sideOffset` (local).
45
- * - The keyboard focus ring is the shared `FocusRing` (`lib/focus-ring.tsx`,
46
- * pinned to the popup's rounded shape), so it also snaps under reduced motion.
47
- * - `triggerVariants` moved to `variants.ts` (server-callable, no JSX or hooks)
48
- * and re-exported here.
49
- * - Root `open` / `defaultOpen` / `onOpenChange(open, details)` (local): the 300ms
50
- * pick acknowledgment runs only while the open state is uncontrolled; a
51
- * controlled select follows the caller's `open` and reports every request.
52
- * - `Select.Content` takes `alignOffset` / `collisionPadding` (Positioner),
53
- * `finalFocus` (Popup) and rest HTML props (Popup). `initialFocus` is not
54
- * offered: Base UI's select popup always focuses the picked row itself.
55
- * - `Select.Trigger` / `Select.Item` take `render` / `asChild` (`lib/slot.ts`);
56
- * the trigger takes `wrapperClassName` for its outer div. `Select.Value`
57
- * and `Select.Group` forward their ref.
58
- */
59
-
60
- import { Select as SelectPrimitive } from '@base-ui/react/select'
61
- import type { VariantProps } from 'class-variance-authority'
62
- import { AnimatePresence, motion } from 'motion/react'
63
- import {
64
- Children,
65
- type ComponentProps,
66
- cloneElement,
67
- createContext,
68
- forwardRef,
69
- type HTMLAttributes,
70
- isValidElement,
71
- type ReactElement,
72
- type ReactNode,
73
- type Ref,
74
- useCallback,
75
- useContext,
76
- useEffect,
77
- useImperativeHandle,
78
- useMemo,
79
- useRef,
80
- useState,
81
- version,
82
- } from 'react'
83
- import { FluidHoverHighlight } from '../../../lib/fluid-hover-highlight'
84
- import { FocusRing } from '../../../lib/focus-ring'
85
- import type { IconComponent } from '../../../lib/icon-context'
86
- import { MorphSurface } from '../../../lib/morph-layers'
87
- import { isDisabledRow, popupScrollAreaClass, popupViewportClass } from '../../../lib/popup'
88
- import { RowOrderContext, useRowOrder, useRowSlot } from '../../../lib/row-order'
89
- import { shapeMap, useShape } from '../../../lib/shape-context'
90
- import { type SizeVariant, useSize } from '../../../lib/size-context'
91
- import { type SlotProps, slotId, slotRender } from '../../../lib/slot'
92
- import { spring } from '../../../lib/springs'
93
- import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
94
- import { SurfaceProvider, useSurface } from '../../../lib/surface-context'
95
- import { useFluidHover, useRegisterFluidHoverItem } from '../../../lib/use-fluid-hover'
96
- import { useKeyboardNavGate } from '../../../lib/use-keyboard-nav-gate'
97
- import { inPlaceOffset, type MorphOrigin, opensInPlace, useMorph, useMorphOrigin } from '../../../lib/use-morph'
98
- import { cn } from '../../../lib/utils'
99
- import { ScrollArea } from '../../../primitives/scroll-area'
100
- import { SizeProvider } from '../../../primitives/sizes'
101
- import { triggerVariants } from './variants'
102
-
103
- // ---------------------------------------------------------------------------
104
- // Select context
105
- //
106
- // Built on Base UI's Select primitive, which owns positioning (collision
107
- // flipping, anchor tracking), dismissal (outside press, focus-out, Escape
108
- // nesting inside dialogs), list keyboard navigation + typeahead, combobox
109
- // ARIA, and the hidden form input. This layer keeps the
110
- // fluid-hover overlays, the morph open/close animation (lib/use-morph.ts),
111
- // and the animated checkmark.
112
- // ---------------------------------------------------------------------------
113
-
114
- // How long a selection holds the popup open before closing, so the
115
- // acknowledgment — the checkmark drawing in and the selected background
116
- // springing to the picked row — is visible instead of being cut off by the
117
- // ~60ms close fade. Escape and outside presses still close immediately.
118
- const selectionAckMs = 300
119
-
120
- interface SelectContextValue {
121
- value: string
122
- open: boolean
123
- origin: React.RefObject<MorphOrigin>
124
- /** Local: whether the picked row's label is plain text (trimmed and truncated) or a node (laid out as is). */
125
- textLabel: boolean
126
- }
127
-
128
- const SelectContext = createContext<SelectContextValue | null>(null)
129
-
130
- function useSelectContext() {
131
- const ctx = useContext(SelectContext)
132
- if (!ctx) throw new Error('Select compound components must be inside <Select>')
133
- return ctx
134
- }
135
-
136
- // Content context for fluid hover
137
- interface SelectContentContextValue {
138
- registerItem: (index: number, element: HTMLElement | null) => void
139
- activeIndex: number | null
140
- checkedIndex?: number
141
- }
142
-
143
- const SelectContentContext = createContext<SelectContentContextValue | null>(null)
144
-
145
- // ---------------------------------------------------------------------------
146
- // Select (root)
147
- // ---------------------------------------------------------------------------
148
-
149
- // The trigger follows the global pill/rounded shape; the popup does not.
150
- // Like Dropdown and Combobox, the list keeps the smaller "rounded" radii
151
- // whatever the rest of the UI is shaped: pill corners on a popover distort
152
- // its padding and break the concentric fit of the rows' hover and selection
153
- // backgrounds inside it.
154
- const popupShape = shapeMap.rounded
155
-
156
- // An element's ref: `props.ref` on React 19, `element.ref` on 18. Reading `element.ref` warns on 19, so it
157
- // is only touched when `props.ref` is absent (React 18 never has it).
158
- function elementRef(el: ReactElement | undefined): Ref<HTMLDivElement> | undefined {
159
- if (!el) return undefined
160
- // React 19 keeps the ref in props, React 18 on the element; each warns when read the other way.
161
- if (parseInt(version, 10) >= 19) return (el.props as { ref?: Ref<HTMLDivElement> }).ref
162
- return (el as unknown as { ref?: Ref<HTMLDivElement> }).ref
163
- }
164
-
165
- const ownChildren = (el: ReactElement) => (el.props as { children?: ReactNode }).children
166
-
167
- // With `asChild` a row's children are the element that becomes the row, and its own children are
168
- // the label; a `render` element's own children are the label when the row has none.
169
- function itemLabel(asChild: boolean | undefined, render: ReactElement | undefined, children: ReactNode) {
170
- if (asChild && isValidElement(children)) return ownChildren(children)
171
- return children === undefined && render ? ownChildren(render) : children
172
- }
173
-
174
- /**
175
- * Walk the children tree collecting `{ value, label }` pairs from SelectItem
176
- * elements. Passed to Base UI's `items` prop so the trigger can resolve the
177
- * label of an initial value before the popup has ever mounted (items only
178
- * render while open). The label is the row's children, whatever node they
179
- * are; a row with none shows its raw value.
180
- */
181
- function collectSelectItems(node: ReactNode, out: { value: string; label: ReactNode }[] = []) {
182
- Children.forEach(node, child => {
183
- if (!isValidElement(child)) return
184
- const props = child.props as { value?: unknown; children?: ReactNode; asChild?: boolean; render?: ReactElement }
185
- if (typeof props.value === 'string') {
186
- out.push({
187
- value: props.value,
188
- label: itemLabel(props.asChild, props.render, props.children) ?? props.value,
189
- })
190
- } else if (props.children) {
191
- collectSelectItems(props.children, out)
192
- }
193
- })
194
- return out
195
- }
196
-
197
- /** Base UI's change details for the select popup: `reason`, `event`, `trigger`, `cancel()`. */
198
- type SelectOpenChangeDetails = SelectPrimitive.Root.ChangeEventDetails
199
-
200
- interface SelectProps {
201
- /** `Select.Trigger` and `Select.Content`. */
202
- children: ReactNode
203
- /** Controlled selected value. Pair with `onValueChange`. */
204
- value?: string
205
- /** Uncontrolled default value — the select then keeps the selection itself. */
206
- defaultValue?: string
207
- /** Called when the selected value changes. */
208
- onValueChange?: (value: string) => void
209
- /** Controlled open state. Pair with `onOpenChange`. A controlled select follows it exactly: it closes only when you say so, and the 300ms pick acknowledgment is skipped. */
210
- open?: boolean
211
- /** Uncontrolled initial open state. Defaults to `false`. */
212
- defaultOpen?: boolean
213
- /** Called when the popup asks to open or close, with Base UI's change details (`reason`, `event`, `trigger`, `cancel()`). Uncontrolled, a pick reports its close once the 300ms acknowledgment ends. */
214
- onOpenChange?: (open: boolean, details: SelectOpenChangeDetails) => void
215
- /** Disables the entire select. Defaults to `false`. */
216
- disabled?: boolean
217
- /** Name for form submission (renders a hidden native input). */
218
- name?: string
219
- /** Marks the select as required in forms. Defaults to `false`. */
220
- required?: boolean
221
- /** Pins trigger and popup to one step of the size ladder (default 36px, compact 28px — see Sizes). Omitted, both follow the surrounding SizeProvider. */
222
- size?: SizeVariant
223
- }
224
-
225
- /**
226
- * An animated select menu: fluid hover on the rows, a popup that oozes out of
227
- * its trigger, and a checkmark that draws itself as the selection lands.
228
- *
229
- * Built on Base UI's Select — positioning and collision flipping, dismissal,
230
- * typeahead, combobox ARIA and the hidden form input all come from the
231
- * primitive. This layer adds the magnetic hover background, the selected
232
- * background that springs from the old row to the picked one, a keyboard
233
- * focus ring that only appears once you actually use the keyboard, and the
234
- * popup's open and close through the shared morph (see Morph), goo by
235
- * default. A pick holds the popup open for 300ms so that acknowledgment is
236
- * seen; Escape and outside presses close at once. Uncontrolled through
237
- * `defaultValue`, controlled through `value` + `onValueChange`. The popup's
238
- * open state follows the same rule: `defaultOpen`, or `open` +
239
- * `onOpenChange(open, details)`. A controlled `open` is obeyed exactly, so
240
- * a handler may refuse a close; it skips the 300ms hold, which only runs
241
- * while the select keeps its own open state.
242
- *
243
- * Rows need no `index`: they take their place in document order, inside a
244
- * `Select.Group` or out of a `map`. The trigger shows the picked row's
245
- * label, whatever node it is (an icon and text, a badge), and a row's
246
- * `textValue` is what typeahead matches when its children aren't plain text.
247
- *
248
- * Statics:
249
- * - `Select.Trigger` — the field: `variant`, `icon`, `placeholder`, `error`;
250
- * children replace its value slot (a `Select.Value`). `render` / `asChild`
251
- * swap the button for your own element (it brings its own look); an
252
- * element with children of its own drops the icon, `placeholder` and
253
- * chevron too, one without keeps them;
254
- * `wrapperClassName` styles the div that holds the `error` message.
255
- * - `Select.Value` — the value slot: the picked row's label, else the muted
256
- * `placeholder`.
257
- * - `Select.Content` — the popup: `side`, `align`, `sideOffset`, `alignOffset`,
258
- * `collisionPadding`, `finalFocus`, the morph options `from`, `effect`,
259
- * `hideSource`, `tier`, and HTML attributes (`id`, `aria-*`, `data-*`) for
260
- * the popup element.
261
- * - `Select.Item` — one row: `value`, optional `index`, `textValue`, `icon`,
262
- * `disabled`; its children are the label. `render` / `asChild` make the
263
- * row your own element, a link for instance.
264
- * - `Select.Group`, `Select.Label`, `Select.Separator` — sections, their
265
- * captions and hairlines between them.
266
- *
267
- * @example {@include ./examples.mdx}
268
- */
269
- function Select({
270
- children,
271
- value,
272
- defaultValue,
273
- onValueChange,
274
- open: openProp,
275
- defaultOpen = false,
276
- onOpenChange,
277
- disabled = false,
278
- name,
279
- required,
280
- size,
281
- }: SelectProps) {
282
- const [internalValue, setInternalValue] = useState(defaultValue ?? '')
283
- const [internalOpen, setInternalOpen] = useState(defaultOpen)
284
- const { origin, capture } = useMorphOrigin()
285
- const currentValue = value !== undefined ? value : internalValue
286
- const controlledOpen = openProp !== undefined
287
- const open = controlledOpen ? openProp : internalOpen
288
-
289
- const items = useMemo(() => collectSelectItems(children), [children])
290
-
291
- const handleValueChange = useCallback(
292
- (next: string | null) => {
293
- const v = next ?? ''
294
- if (value === undefined) setInternalValue(v)
295
- onValueChange?.(v)
296
- },
297
- [value, onValueChange],
298
- )
299
-
300
- const ackTimeoutRef = useRef<number | null>(null)
301
- const cancelAckClose = useCallback(() => {
302
- if (ackTimeoutRef.current !== null) {
303
- clearTimeout(ackTimeoutRef.current)
304
- ackTimeoutRef.current = null
305
- }
306
- }, [])
307
- useEffect(() => cancelAckClose, [cancelAckClose])
308
-
309
- const onOpenChangeRef = useRef(onOpenChange)
310
- onOpenChangeRef.current = onOpenChange
311
- const controlledOpenRef = useRef(controlledOpen)
312
- controlledOpenRef.current = controlledOpen
313
-
314
- // Picking an item acknowledges before closing: the close is deferred by
315
- // selectionAckMs so the checkmark draw and the selected background's spring
316
- // to the picked row are seen. Every other close reason (Escape, outside
317
- // press, trigger toggle, focus-out) closes immediately and cancels any
318
- // pending acknowledgment; re-picking within the window restarts it. A
319
- // controlled `open` is the caller's: every request is reported at once and
320
- // nothing is deferred.
321
- const handleOpenChange = useCallback(
322
- (nextOpen: boolean, eventDetails: SelectOpenChangeDetails) => {
323
- if (nextOpen) capture(eventDetails)
324
- cancelAckClose()
325
- if (!nextOpen && eventDetails.reason === 'item-press' && !controlledOpenRef.current) {
326
- ackTimeoutRef.current = window.setTimeout(() => {
327
- ackTimeoutRef.current = null
328
- setInternalOpen(false)
329
- onOpenChangeRef.current?.(false, eventDetails)
330
- }, selectionAckMs)
331
- return
332
- }
333
- if (!controlledOpenRef.current) setInternalOpen(nextOpen)
334
- onOpenChangeRef.current?.(nextOpen, eventDetails)
335
- },
336
- [cancelAckClose, capture],
337
- )
338
-
339
- const picked = items.find(item => item.value === currentValue)?.label
340
- const textLabel = picked === undefined || typeof picked === 'string' || typeof picked === 'number'
341
- const ctx = useMemo(() => ({ value: currentValue, open, origin, textLabel }), [currentValue, open, origin, textLabel])
342
-
343
- // A size prop pins the whole compound (trigger + portalled popup — React
344
- // context crosses portals) to one step of the ladder.
345
- const root = (
346
- <SelectContext.Provider value={ctx}>
347
- <SelectPrimitive.Root
348
- // Always controlled; "" (no selection) maps to Base UI's null.
349
- value={currentValue === '' ? null : currentValue}
350
- onValueChange={handleValueChange}
351
- open={open}
352
- onOpenChange={handleOpenChange}
353
- items={items}
354
- disabled={disabled}
355
- name={name}
356
- required={required}
357
- // Non-modal: the page keeps scrolling and the Positioner tracks the
358
- // anchor, so the popup follows its trigger instead of detaching.
359
- modal={false}
360
- >
361
- {children}
362
- </SelectPrimitive.Root>
363
- </SelectContext.Provider>
364
- )
365
-
366
- return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
367
- }
368
-
369
- Select.displayName = 'Select'
370
-
371
- // ---------------------------------------------------------------------------
372
- // SelectValue
373
- // ---------------------------------------------------------------------------
374
-
375
- interface SelectValueProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'placeholder'> {
376
- /** Shown, muted, while nothing is selected. Defaults to `"Select…"`. */
377
- placeholder?: ReactNode
378
- /** Extra classes for the value text. */
379
- className?: string
380
- }
381
-
382
- /** The trigger's value slot: the picked row's label (its children), else the muted placeholder. */
383
- const SelectValue = forwardRef<HTMLSpanElement, SelectValueProps>(
384
- ({ placeholder = 'Select…', className, ...props }, ref) => {
385
- const { textLabel } = useSelectContext()
386
- return (
387
- <SelectPrimitive.Value
388
- ref={ref}
389
- placeholder={placeholder}
390
- // py-1/-my-1: truncate's overflow:hidden clips at the padding
391
- // box, and the trimmed box excludes ascenders/descenders — the
392
- // padding gives glyphs room while the negative margin keeps the
393
- // trimmed layout box.
394
- className={cn(
395
- 'min-w-0 flex-1 text-left data-[placeholder]:text-muted-foreground',
396
- // A node label (a badge, an icon and text) keeps its own box: trimming
397
- // and clipping it would cut it top and bottom.
398
- textLabel ? 'truncate [text-box:trim-both_cap_alphabetic] py-1 -my-1' : 'flex items-center',
399
- className,
400
- )}
401
- {...props}
402
- />
403
- )
404
- },
405
- )
406
-
407
- SelectValue.displayName = 'SelectValue'
408
-
409
- // ---------------------------------------------------------------------------
410
- // SelectTrigger
411
- // ---------------------------------------------------------------------------
412
-
413
- interface SelectTriggerProps extends Omit<SlotProps, 'children' | 'placeholder'>, VariantProps<typeof triggerVariants> {
414
- /** Replaces the built-in value slot, typically with a `Select.Value` (shadcn's `<SelectValue placeholder>`). The icon and chevron stay. With `asChild`, the single child element that becomes the trigger instead, and `render` takes an element the same way: that element replaces the whole field. If it has children of its own, they are the entire body, so the `icon`, `placeholder` and chevron are not rendered and the field look, including the `error` border tint, is not applied (the `error` message and `aria-invalid` remain). If it has none, it still gets the value slot, `icon` and chevron, unstyled. */
415
- children?: ReactNode
416
- /** Visual style of the trigger. Defaults to `"bordered"`. */
417
- variant?: 'bordered' | 'borderless'
418
- /** Optional icon displayed before the value. */
419
- icon?: IconComponent
420
- /** Text shown when no value is selected. Defaults to `"Select…"`. */
421
- placeholder?: string
422
- /** Error message shown below the trigger; also tints the border and sets `aria-invalid`. */
423
- error?: string
424
- /** Size override for the trigger alone. Prefer the `size` prop on `<Select>` (or a surrounding SizeProvider) so the popup matches. */
425
- size?: SizeVariant
426
- /** Extra classes for the trigger button. The field has a minimum width of 160px (128px compact); pass `min-w-0` to lift that floor. */
427
- className?: string
428
- /** Extra classes for the div around the trigger, which holds the `error` message under it. It is a plain flex column, so `contents` makes it vanish from the layout. */
429
- wrapperClassName?: string
430
- }
431
-
432
- const SelectTrigger = forwardRef<HTMLButtonElement, SelectTriggerProps>(
433
- (
434
- {
435
- className,
436
- wrapperClassName,
437
- variant,
438
- icon: Icon,
439
- placeholder = 'Select…',
440
- error,
441
- size,
442
- render,
443
- asChild,
444
- children,
445
- ...props
446
- },
447
- ref,
448
- ) => {
449
- const shape = useShape()
450
- const sizeClasses = useSize(size)
451
- const compact = sizeClasses.variant === 'compact'
452
- // The element a `render` / `asChild` caller brings is the whole trigger, look included: it gets only
453
- // the `group` class, so the field's border, size, padding and `error` border tint are not applied
454
- // (the `error` message below it and `aria-invalid` still are). What goes inside depends on the
455
- // element's own children:
456
- // - it has some: they are the whole body and replace the field's, so the icon, `placeholder` and
457
- // chevron are not rendered (add a `Select.Value` among them to show the picked label);
458
- // - it has none: it gets the field's body, `icon` (if any), value slot with `placeholder`, and
459
- // chevron, in an unstyled trigger.
460
- const el = slotRender(render, asChild, asChild ? children : undefined)
461
- const content = el ? itemLabel(asChild, render, children) : children
462
-
463
- return (
464
- <div className={cn('flex flex-col gap-1', wrapperClassName)}>
465
- <SelectPrimitive.Trigger
466
- ref={ref}
467
- render={el}
468
- aria-invalid={!!error || undefined}
469
- className={
470
- el
471
- ? cn('group', className)
472
- : cn(
473
- triggerVariants({ variant }),
474
- sizeClasses.control,
475
- sizeClasses.text,
476
- sizeClasses.px,
477
- sizeClasses.gap,
478
- compact ? 'min-w-[128px]' : 'min-w-[160px]',
479
- shape.input,
480
- error && 'border-destructive/50 hover:border-destructive/50',
481
- className,
482
- )
483
- }
484
- {...props}
485
- id={slotId(el, props.id)}
486
- >
487
- <span className={cn('flex items-center min-w-0 flex-1', sizeClasses.gap)}>
488
- {Icon && (
489
- <Icon
490
- size={sizeClasses.icon}
491
- strokeWidth={1.5}
492
- className="shrink-0 text-muted-foreground transition-[color,stroke-width] duration-fast group-hover:text-foreground group-hover:stroke-[2]"
493
- />
494
- )}
495
- {content ?? <SelectValue placeholder={placeholder} />}
496
- </span>
497
-
498
- <svg
499
- width={sizeClasses.icon}
500
- height={sizeClasses.icon}
501
- viewBox="0 0 24 24"
502
- fill="none"
503
- stroke="currentColor"
504
- strokeWidth={2}
505
- strokeLinecap="round"
506
- strokeLinejoin="round"
507
- className="shrink-0 text-muted-foreground transition-colors duration-fast group-hover:text-foreground"
508
- >
509
- <path d="M6 9l6 6 6-6" />
510
- </svg>
511
- </SelectPrimitive.Trigger>
512
- {error && <span className="text-caption text-destructive pl-3">{error}</span>}
513
- </div>
514
- )
515
- },
516
- )
517
-
518
- SelectTrigger.displayName = 'SelectTrigger'
519
-
520
- // ---------------------------------------------------------------------------
521
- // SelectContent
522
- // ---------------------------------------------------------------------------
523
-
524
- type SelectPositionerProps = ComponentProps<typeof SelectPrimitive.Positioner>
525
- type SelectPopupProps = ComponentProps<typeof SelectPrimitive.Popup>
526
-
527
- // Handlers the popup drives itself (fluid hover, the keyboard ring gate): not passed through.
528
- type PopupOwnHandlers =
529
- | 'onKeyDownCapture'
530
- | 'onMouseEnter'
531
- | 'onMouseMove'
532
- | 'onMouseLeave'
533
- | 'onClick'
534
- | 'onFocus'
535
- | 'onBlur'
536
-
537
- interface SelectContentProps extends Omit<HTMLAttributes<HTMLDivElement>, PopupOwnHandlers | 'className' | 'children'> {
538
- /** Where the menu grows from (see Morph): the trigger, the pointer, its own center, a viewport edge, or a ref to any element. Defaults to `'trigger'`. */
539
- from?: 'trigger' | 'pointer' | 'center' | 'top' | 'right' | 'bottom' | 'left' | React.RefObject<HTMLElement | null>
540
- /** How it grows (see Morph): with the liquid goo neck, a plain morph, a slide or a fade. Defaults to `'goo'`. */
541
- effect?: 'goo' | 'morph' | 'slide' | 'fade'
542
- /** Hide the trigger while open, so it reads as turning into the menu. Defaults to `false`. */
543
- hideSource?: boolean
544
- /** Spring tier of a plain morph, slide or fade; goo runs on its own `spring.goo`. Defaults to `'moderate'`. */
545
- tier?: 'moderate' | 'slow'
546
- /** Side of the trigger the menu prefers; it flips when there is no room. Defaults to `"bottom"`. */
547
- side?: 'top' | 'bottom' | 'left' | 'right'
548
- /** Alignment against the trigger. Defaults to `"start"`. */
549
- align?: 'start' | 'center' | 'end'
550
- /** Gap to the trigger, in px. Ignored when the menu opens in place over the trigger (`effect="morph"` with `hideSource`). Defaults to `6`. */
551
- sideOffset?: number
552
- /** Shift along the aligned edge in px, after `align` (a number, or Base UI's function of side, align and sizes). Defaults to `0`. */
553
- alignOffset?: SelectPositionerProps['alignOffset']
554
- /** Space kept between the menu and the edge of its collision boundary, in px or per side. Defaults to `5`. */
555
- collisionPadding?: SelectPositionerProps['collisionPadding']
556
- /** Where focus goes when the menu closes: a ref, `false` to leave it, `true` for the default, or a function of how it closed (`'mouse'`, `'touch'`, `'pen'`, `'keyboard'`). Defaults to the trigger. */
557
- finalFocus?: SelectPopupProps['finalFocus']
558
- /** Additional classes for the menu container. */
559
- className?: string
560
- /** `Select.Item`, `Select.Group`, `Select.Label`, `Select.Separator`. */
561
- children: ReactNode
562
- }
563
-
564
- const SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(
565
- (
566
- {
567
- from,
568
- effect,
569
- hideSource,
570
- tier = 'moderate',
571
- side = 'bottom',
572
- align = 'start',
573
- sideOffset = 6,
574
- alignOffset,
575
- collisionPadding,
576
- finalFocus,
577
- className,
578
- children,
579
- ...props
580
- },
581
- ref,
582
- ) => {
583
- const { open, value, origin } = useSelectContext()
584
- const rowOrder = useRowOrder()
585
- const morph = useMorph(open, origin, {
586
- from,
587
- effect,
588
- hideSource,
589
- tier,
590
- // A custom `finalFocus` places focus itself; the morph's own return to the trigger would undo it.
591
- returnFocus: typeof finalFocus === 'boolean' ? finalFocus : finalFocus === undefined,
592
- })
593
- useImperativeHandle(ref, () => morph.popup as HTMLDivElement, [morph.popup])
594
- // Lifts 2 levels off its substrate with a fixed shadow (see Elevated).
595
- const level = Math.min(useSurface() + 2, 8)
596
- const shape = popupShape
597
- const containerRef = useRef<HTMLDivElement>(null)
598
-
599
- const hover = useFluidHover(containerRef, { isItemDisabled: isDisabledRow })
600
- const { activeIndex, setActiveIndex, itemRects, isMeasured, handlers, registerItem, remeasure } = hover
601
-
602
- const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
603
-
604
- // Keyboard focus ring gate: seeded from the trigger's :focus-visible at
605
- // open, earned by navigation keys inside the popup.
606
- const { keyboardNavRef, trackKeyboardNav } = useKeyboardNavGate(open)
607
- const [checkedIndex, setCheckedIndex] = useState<number | undefined>(undefined)
608
-
609
- // Fresh rects once per open. Measuring is the hook's job — it owns the
610
- // one coalesced pass that item registration and container resizes both
611
- // feed into, and a second pass from elsewhere is what used to land a
612
- // corrected rect on an already-mounted overlay. The popup keeps its items
613
- // registered while it sits hidden between opens, so registration alone
614
- // would never trigger a fresh pass on reopen.
615
- useEffect(() => {
616
- if (!open) return
617
- remeasure()
618
- }, [open, remeasure])
619
-
620
- // Detect the checked row. Deliberately does NOT remeasure on a value
621
- // change while open: the rows haven't moved, so the published rects stay
622
- // trustworthy and only checkedIndex switches — which lets the selected
623
- // marker spring from the old row to the picked one (the selection
624
- // acknowledgment) instead of unmounting and snapping.
625
- useEffect(() => {
626
- if (!open) return
627
- // Double rAF: first waits for React commit, second for layout
628
- let inner: number
629
- const outer = requestAnimationFrame(() => {
630
- inner = requestAnimationFrame(() => {
631
- const container = containerRef.current
632
- if (container) {
633
- // The row's own index: rows may be numbered out of DOM order (an explicit `index`).
634
- const row = Array.from(container.querySelectorAll<HTMLElement>('[data-fluid-hover-index]')).find(
635
- el => el.getAttribute('data-value') === value,
636
- )
637
- setCheckedIndex(row ? Number(row.getAttribute('data-fluid-hover-index')) : undefined)
638
- }
639
- })
640
- })
641
- return () => {
642
- cancelAnimationFrame(outer)
643
- cancelAnimationFrame(inner)
644
- }
645
- }, [open, value])
646
-
647
- // Reset every overlay index as the close begins. checkedIndex otherwise
648
- // lags one open behind value (picking an item closes the popup before the
649
- // effect above re-syncs it), and a leftover activeIndex is worse: Base UI
650
- // keeps the popup mounted through the exit tween, so on reopen the hover
651
- // pill would still be sitting on the previously active row and spring from
652
- // there to the row that auto-focus lands on.
653
- useEffect(() => {
654
- if (open) return
655
- setCheckedIndex(undefined)
656
- setActiveIndex(null)
657
- setFocusedIndex(null)
658
- }, [open, setActiveIndex])
659
-
660
- // Overlays read rects only once the hook reports the item set fully
661
- // measured. Positioning one from an incomplete pass mounts it at the wrong
662
- // row, and the correcting pass then springs it across the list.
663
- const checkedRect = isMeasured && checkedIndex != null ? itemRects[checkedIndex] : null
664
- const focusRect = isMeasured && focusedIndex !== null ? itemRects[focusedIndex] : null
665
-
666
- const contentCtx = useMemo(
667
- () => ({ registerItem, activeIndex, checkedIndex }),
668
- [registerItem, activeIndex, checkedIndex],
669
- )
670
-
671
- return (
672
- <SelectPrimitive.Portal>
673
- <SelectPrimitive.Positioner
674
- side={side}
675
- align={align}
676
- sideOffset={opensInPlace({ effect, hideSource }) ? inPlaceOffset : sideOffset}
677
- alignOffset={alignOffset}
678
- collisionPadding={collisionPadding}
679
- alignItemWithTrigger={false}
680
- className="z-50 outline-none"
681
- >
682
- <SelectContentContext.Provider value={contentCtx}>
683
- <SelectPrimitive.Popup
684
- ref={morph.popupRef}
685
- finalFocus={finalFocus}
686
- {...props}
687
- // Capture phase: the primitive moves focus during its own keydown
688
- // handling, so the nav flag must be set before then.
689
- onKeyDownCapture={trackKeyboardNav}
690
- onMouseEnter={() => {
691
- handlers.onMouseEnter()
692
- setFocusedIndex(null)
693
- }}
694
- onMouseMove={handlers.onMouseMove}
695
- onMouseLeave={handlers.onMouseLeave}
696
- onClick={handlers.onClick}
697
- onFocus={e => {
698
- const indexAttr = (e.target as HTMLElement)
699
- .closest('[data-fluid-hover-index]')
700
- ?.getAttribute('data-fluid-hover-index')
701
- if (indexAttr != null) {
702
- const idx = Number(indexAttr)
703
- setActiveIndex(idx)
704
- setFocusedIndex(
705
- keyboardNavRef.current && (e.target as HTMLElement).matches(':focus-visible') ? idx : null,
706
- )
707
- }
708
- }}
709
- onBlur={e => {
710
- // The popup itself takes focus when the pointer leaves a row; only a
711
- // departure from the whole popup ends the hover session.
712
- if (e.currentTarget.contains(e.relatedTarget as Node)) return
713
- setFocusedIndex(null)
714
- setActiveIndex(null)
715
- }}
716
- className="relative select-none outline-none"
717
- >
718
- <SurfaceProvider value={level}>
719
- <MorphSurface
720
- morph={morph}
721
- bg={SURFACE_BG[level]}
722
- shadow={SURFACE_SHADOW[3]}
723
- radius={shape.container}
724
- className={cn(
725
- // min-w tracks the trigger via the Positioner's --anchor-width
726
- // var, matching the pre-migration minWidth: triggerRect.width.
727
- `flex flex-col min-w-[var(--anchor-width)] max-h-[min(300px,var(--available-height))] overflow-hidden ${shape.container}`,
728
- className,
729
- )}
730
- >
731
- {/* The list scrolls inside a ScrollArea; this wrapper is the rows'
732
- offsetParent, so the overlays scroll with them. */}
733
- <ScrollArea
734
- className={popupScrollAreaClass}
735
- viewportClassName={cn(popupViewportClass, 'scroll-fade')}
736
- >
737
- <div ref={containerRef} className="relative flex flex-col p-1">
738
- {/* The three overlays are torn down as the close begins rather
739
- than exit-animated, because an overlay still mounted when the
740
- popup reopens is one AnimatePresence re-adopts under its old
741
- key: `initial` never runs again, so it keeps the position of the
742
- row it had before and animates from there to the new one. The
743
- popup's own fade covers their disappearance. */}
744
- {/* Selected background */}
745
- {open && (
746
- <AnimatePresence>
747
- {checkedRect && (
748
- <motion.div
749
- className={`absolute ${shape.bg} bg-active pointer-events-none`}
750
- // Position lives in `animate` so an in-session value
751
- // change springs the marker to the picked row (the
752
- // selection acknowledgment). Safe against the reopen
753
- // slide: the `open &&` teardown means no marker
754
- // survives a close, and a fresh mount with
755
- // initial={false} renders snapped at these values.
756
- initial={false}
757
- animate={{
758
- top: checkedRect.top,
759
- left: checkedRect.left,
760
- width: checkedRect.width,
761
- height: checkedRect.height,
762
- opacity: 1,
763
- }}
764
- exit={{ opacity: 0, transition: spring.moderate.exit }}
765
- transition={{
766
- ...spring.moderate,
767
- opacity: { duration: spring.fast.duration },
768
- }}
769
- />
770
- )}
771
- </AnimatePresence>
772
- )}
773
-
774
- {/* Hover background */}
775
- <FluidHoverHighlight hover={hover} hidden={!open} className={shape.bg} />
776
-
777
- {/* Focus ring */}
778
- {open && <FocusRing rect={focusRect} shape="rounded" />}
779
-
780
- <RowOrderContext.Provider value={rowOrder}>{children}</RowOrderContext.Provider>
781
- </div>
782
- </ScrollArea>
783
- </MorphSurface>
784
- </SurfaceProvider>
785
- </SelectPrimitive.Popup>
786
- </SelectContentContext.Provider>
787
- </SelectPrimitive.Positioner>
788
- </SelectPrimitive.Portal>
789
- )
790
- },
791
- )
792
-
793
- SelectContent.displayName = 'SelectContent'
794
-
795
- // ---------------------------------------------------------------------------
796
- // SelectItem
797
- // ---------------------------------------------------------------------------
798
-
799
- interface SelectItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
800
- /** The element that becomes the row, e.g. a router link (it must forward its ref); it gets the row's look and behaviour. Its children fill the label unless `children` is given. */
801
- render?: ReactElement
802
- /** Make the single child element the row, its own children being the label. Both spellings work. Defaults to `false`. */
803
- asChild?: boolean
804
- /** Optional icon displayed before the label. */
805
- icon?: IconComponent
806
- /** Position among the menu's rows — what fluid hover measures rows by. Defaults to the row's place in document order. */
807
- index?: number
808
- /** Unique value for this option. */
809
- value: string
810
- /** The row's text for typeahead, when its children are not a plain string. */
811
- textValue?: string
812
- /** Disables this individual item. Defaults to `false`. */
813
- disabled?: boolean
814
- /** The row label. A string label is also what the trigger shows for this value. With `asChild`, the single child element that becomes the row. */
815
- children?: ReactNode
816
- }
817
-
818
- const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
819
- (
820
- {
821
- className,
822
- children: childrenProp,
823
- render,
824
- asChild,
825
- icon: Icon,
826
- value,
827
- index: indexProp,
828
- textValue,
829
- disabled = false,
830
- ...props
831
- },
832
- ref,
833
- ) => {
834
- const el = slotRender(render, asChild, asChild ? childrenProp : undefined)
835
- const children = itemLabel(asChild, render, childrenProp)
836
- const selectCtx = useSelectContext()
837
- const contentCtx = useContext(SelectContentContext)
838
- const internalRef = useRef<HTMLDivElement>(null)
839
- const shape = popupShape
840
- const sizeClasses = useSize()
841
- const compact = sizeClasses.variant === 'compact'
842
- const hasMounted = useRef(false)
843
-
844
- useEffect(() => {
845
- hasMounted.current = true
846
- }, [])
847
-
848
- // Register with fluid hover. Depends on the (stable) registerItem
849
- // rather than the content context, which is rebuilt on every activeIndex
850
- // change: keying the effect to the whole context re-ran it per mousemove,
851
- // unregistering and re-registering every row and so keeping the hook's
852
- // measurement permanently unsettled while the pointer moved.
853
- const registerItem = contentCtx?.registerItem
854
- const { index } = useRowSlot(internalRef, indexProp)
855
- useRegisterFluidHoverItem(registerItem, index, internalRef)
856
-
857
- const isActive = contentCtx?.activeIndex === index
858
- const isChecked = selectCtx.value === value
859
- const skipAnimation = !hasMounted.current
860
-
861
- const rowProps = {
862
- ref: (node: HTMLDivElement | null) => {
863
- ;(internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node
864
- for (const r of [ref, elementRef(el)]) {
865
- if (typeof r === 'function') r(node)
866
- else if (r) (r as React.MutableRefObject<HTMLDivElement | null>).current = node
867
- }
868
- },
869
- 'data-fluid-hover-index': index,
870
- 'data-value': value,
871
- className: cn(
872
- // Fixed height (was py-2 around a 19.5px line box ≈ 35.5px) so
873
- // the text-box trim on the item text doesn't shrink the row.
874
- // shrink-0: the popup is a max-height flex column, so without it
875
- // a long list compresses rows to fit instead of scrolling.
876
- `relative z-10 flex ${sizeClasses.control} shrink-0 items-center ${sizeClasses.gap} ${shape.item} ${sizeClasses.itemPx} ${sizeClasses.text} cursor-pointer outline-none select-none`,
877
- 'transition-[color] duration-fast',
878
- isActive || isChecked ? 'text-foreground' : 'text-muted-foreground',
879
- disabled && 'opacity-50 pointer-events-none',
880
- className,
881
- ),
882
- ...props,
883
- }
884
-
885
- return (
886
- <SelectPrimitive.Item
887
- value={value}
888
- disabled={disabled}
889
- label={textValue ?? (typeof children === 'string' ? children : undefined)}
890
- render={
891
- el ? (
892
- cloneElement(el, {
893
- ...rowProps,
894
- className: cn(rowProps.className, (el.props as { className?: string }).className),
895
- })
896
- ) : (
897
- <div {...rowProps} />
898
- )
899
- }
900
- >
901
- {Icon && (
902
- <Icon
903
- size={sizeClasses.icon}
904
- strokeWidth={isActive || isChecked ? 2 : 1.5}
905
- className="shrink-0 transition-[color,stroke-width] duration-fast"
906
- />
907
- )}
908
-
909
- <SelectPrimitive.ItemText
910
- // py-1/-my-1 keeps truncate's overflow:hidden from clipping
911
- // ascenders/descenders outside the trimmed box.
912
- render={
913
- <span
914
- className={cn(
915
- 'flex-1 min-w-0',
916
- // Nodes keep their own box; only text is trimmed and truncated.
917
- typeof children === 'string' || typeof children === 'number'
918
- ? 'truncate [text-box:trim-both_cap_alphabetic] py-1 -my-1'
919
- : 'flex items-center',
920
- )}
921
- />
922
- }
923
- >
924
- {children}
925
- </SelectPrimitive.ItemText>
926
-
927
- {/* Always-rendered fixed slot so the check appearing/disappearing
928
- never changes the row's intrinsic width — without it the whole
929
- popup resizes when a selection lands. */}
930
- <span aria-hidden className={cn('shrink-0', compact ? 'w-3.5 h-3.5' : 'w-4 h-4')}>
931
- <AnimatePresence>
932
- {isChecked && (
933
- <motion.svg
934
- key="check"
935
- width={sizeClasses.icon}
936
- height={sizeClasses.icon}
937
- viewBox="0 0 24 24"
938
- fill="none"
939
- stroke="currentColor"
940
- strokeWidth={2}
941
- strokeLinecap="round"
942
- strokeLinejoin="round"
943
- className="text-foreground"
944
- initial={{ opacity: 1 }}
945
- animate={{ opacity: 1 }}
946
- exit={{ opacity: 1 }}
947
- >
948
- <motion.path
949
- d="M4 12L9 17L20 6"
950
- initial={{ pathLength: skipAnimation ? 1 : 0 }}
951
- animate={{
952
- pathLength: 1,
953
- transition: { duration: spring.fast.duration, ease: 'easeOut' },
954
- }}
955
- exit={{
956
- pathLength: 0,
957
- transition: { duration: 0.04, ease: 'easeIn' },
958
- }}
959
- />
960
- </motion.svg>
961
- )}
962
- </AnimatePresence>
963
- </span>
964
- </SelectPrimitive.Item>
965
- )
966
- },
967
- )
968
-
969
- SelectItem.displayName = 'SelectItem'
970
-
971
- // ---------------------------------------------------------------------------
972
- // SelectGroup + SelectLabel + SelectSeparator
973
- // ---------------------------------------------------------------------------
974
-
975
- const SelectGroup = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
976
- ({ children, className, ...props }, ref) => (
977
- <div ref={ref} role="group" className={className} {...props}>
978
- {children}
979
- </div>
980
- ),
981
- )
982
-
983
- SelectGroup.displayName = 'SelectGroup'
984
-
985
- const SelectLabel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
986
- // Group labels are the caption role of the type scale — see /docs/sizes.
987
- const compact = useSize().variant === 'compact'
988
- return (
989
- <div
990
- ref={ref}
991
- className={cn(
992
- 'px-2 py-1.5 shrink-0 text-muted-foreground',
993
- compact ? 'text-caption-compact' : 'text-caption',
994
- className,
995
- )}
996
- {...props}
997
- />
998
- )
999
- })
1000
-
1001
- SelectLabel.displayName = 'SelectLabel'
1002
-
1003
- const SelectSeparator = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
1004
- <div ref={ref} role="separator" className={cn('my-1 -mx-1 h-px shrink-0 bg-border/60', className)} {...props} />
1005
- ))
1006
-
1007
- SelectSeparator.displayName = 'SelectSeparator'
1008
-
1009
- // Compound statics: `<Select.Trigger>`, `<Select.Item>`, … Assigned directly
1010
- // (not through Object.assign) so TypeScript's expando inference types them
1011
- // onto the root function declaration.
1012
- Select.Trigger = SelectTrigger
1013
- Select.Value = SelectValue
1014
- Select.Content = SelectContent
1015
- Select.Item = SelectItem
1016
- Select.Group = SelectGroup
1017
- Select.Label = SelectLabel
1018
- Select.Separator = SelectSeparator
1019
-
1020
- // ---------------------------------------------------------------------------
1021
- // Exports
1022
- // ---------------------------------------------------------------------------
1023
-
1024
- export type {
1025
- SelectContentProps,
1026
- SelectItemProps,
1027
- SelectOpenChangeDetails,
1028
- SelectProps,
1029
- SelectTriggerProps,
1030
- SelectValueProps,
1031
- }
1032
- export {
1033
- Select,
1034
- SelectContent,
1035
- SelectGroup,
1036
- SelectItem,
1037
- SelectLabel,
1038
- SelectSeparator,
1039
- SelectTrigger,
1040
- SelectValue,
1041
- triggerVariants,
1042
- }
1043
-
1044
- export default Select