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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. package/DESIGN.md +608 -0
  2. package/README.md +61 -39
  3. package/blocks/sidebar-app/examples.mdx +33 -0
  4. package/blocks/sidebar-app/index.tsx +218 -0
  5. package/blocks/sidebar-app/inset-topbar.tsx +25 -0
  6. package/blocks/sidebar-app/menu-grid.ts +26 -0
  7. package/blocks/sidebar-app/nav-data.ts +36 -0
  8. package/blocks/sidebar-app/search-field.tsx +48 -0
  9. package/blocks/sidebar-app/user-footer.tsx +67 -0
  10. package/blocks/sidebar-app/workspace-header.tsx +129 -0
  11. package/components/AI/input-message/examples.mdx +124 -0
  12. package/components/AI/input-message/file-thumbnail.tsx +173 -0
  13. package/components/AI/input-message/index.tsx +1383 -0
  14. package/components/AI/thinking-indicator/examples.mdx +27 -0
  15. package/components/AI/thinking-indicator/index.tsx +210 -0
  16. package/components/accordion/examples.mdx +148 -0
  17. package/components/accordion/index.tsx +1034 -0
  18. package/components/avatar/examples.mdx +95 -0
  19. package/components/avatar/index.tsx +129 -0
  20. package/components/badge/change-demo.tsx +29 -0
  21. package/components/badge/examples.mdx +120 -0
  22. package/components/badge/index.tsx +225 -0
  23. package/components/badge/variants.ts +73 -0
  24. package/components/button/examples.mdx +164 -0
  25. package/components/button/index.tsx +416 -0
  26. package/components/button/loading-demo.tsx +25 -0
  27. package/components/button/variants.ts +166 -0
  28. package/components/card/examples.mdx +212 -0
  29. package/components/card/index.tsx +1037 -0
  30. package/components/copy/examples.mdx +87 -0
  31. package/components/copy/index.tsx +482 -0
  32. package/components/inputs/checkbox/examples.mdx +83 -0
  33. package/components/inputs/checkbox/index.tsx +611 -0
  34. package/components/inputs/checkbox/select-all-demo.tsx +48 -0
  35. package/components/inputs/color-picker/examples.mdx +72 -0
  36. package/components/inputs/color-picker/index.tsx +1955 -0
  37. package/components/inputs/color-picker/variants.ts +401 -0
  38. package/components/inputs/combobox/examples.mdx +273 -0
  39. package/components/inputs/combobox/index.tsx +1436 -0
  40. package/components/inputs/combobox/remote-demo.tsx +101 -0
  41. package/components/inputs/date-time/calendar/examples.mdx +34 -0
  42. package/components/inputs/date-time/calendar/index.tsx +351 -0
  43. package/components/inputs/date-time/calendar/use-month-window.ts +163 -0
  44. package/components/inputs/date-time/date-picker/examples.mdx +166 -0
  45. package/components/inputs/date-time/date-picker/index.tsx +385 -0
  46. package/components/inputs/date-time/date-picker/masked-date-input.tsx +336 -0
  47. package/components/inputs/date-time/date-range-picker/examples.mdx +29 -0
  48. package/components/inputs/date-time/date-range-picker/index.tsx +217 -0
  49. package/components/inputs/date-time/duration-picker/examples.mdx +16 -0
  50. package/components/inputs/date-time/duration-picker/index.tsx +189 -0
  51. package/components/inputs/date-time/month-picker/examples.mdx +16 -0
  52. package/components/inputs/date-time/month-picker/index.tsx +190 -0
  53. package/components/inputs/date-time/picker-shell.tsx +395 -0
  54. package/components/inputs/date-time/time-columns.tsx +91 -0
  55. package/components/inputs/date-time/time-picker/examples.mdx +26 -0
  56. package/components/inputs/date-time/time-picker/index.tsx +166 -0
  57. package/components/inputs/field/examples.mdx +113 -0
  58. package/components/inputs/field/index.tsx +267 -0
  59. package/components/inputs/file-dropzone/examples.mdx +73 -0
  60. package/components/inputs/file-dropzone/file-row.tsx +201 -0
  61. package/components/inputs/file-dropzone/index.tsx +296 -0
  62. package/components/inputs/file-dropzone/upload-demo.tsx +67 -0
  63. package/components/inputs/file-dropzone/variants.ts +94 -0
  64. package/components/inputs/input/examples.mdx +135 -0
  65. package/components/inputs/input/index.tsx +494 -0
  66. package/components/inputs/input/password-demo.tsx +30 -0
  67. package/components/inputs/label/examples.mdx +67 -0
  68. package/components/inputs/label/index.tsx +85 -0
  69. package/components/inputs/number-input/examples.mdx +56 -0
  70. package/components/inputs/number-input/index.tsx +214 -0
  71. package/components/inputs/radio/examples.mdx +77 -0
  72. package/components/inputs/radio/index.tsx +636 -0
  73. package/components/inputs/search/examples.mdx +74 -0
  74. package/components/inputs/search/index.tsx +333 -0
  75. package/components/inputs/select/examples.mdx +218 -0
  76. package/components/inputs/select/index.tsx +862 -0
  77. package/components/inputs/select/variants.ts +32 -0
  78. package/components/inputs/slider/examples.mdx +127 -0
  79. package/components/inputs/slider/index.tsx +1725 -0
  80. package/components/inputs/switch/examples.mdx +94 -0
  81. package/components/inputs/switch/index.tsx +504 -0
  82. package/components/inputs/textarea/examples.mdx +88 -0
  83. package/components/inputs/textarea/index.tsx +161 -0
  84. package/components/navigation/navigation-menu/examples.mdx +109 -0
  85. package/components/navigation/navigation-menu/index.tsx +613 -0
  86. package/components/navigation/pagination/examples.mdx +64 -0
  87. package/components/navigation/pagination/index.tsx +395 -0
  88. package/components/navigation/sidebar/examples.mdx +319 -0
  89. package/components/navigation/sidebar/index.tsx +453 -0
  90. package/components/navigation/sidebar/sidebar-core.tsx +1408 -0
  91. package/components/navigation/sidebar/sidebar-menu.tsx +1495 -0
  92. package/components/navigation/sidebar/sidebar.css +74 -0
  93. package/components/navigation/sidebar/variants.ts +55 -0
  94. package/components/navigation/tabs/examples.mdx +89 -0
  95. package/components/navigation/tabs/index.tsx +494 -0
  96. package/components/navigation/tabs-subtle/examples.mdx +49 -0
  97. package/components/navigation/tabs-subtle/index.tsx +512 -0
  98. package/components/overlays/alert-dialog/examples.mdx +123 -0
  99. package/components/overlays/alert-dialog/index.tsx +258 -0
  100. package/components/overlays/alert-dialog/pending-demo.tsx +36 -0
  101. package/components/overlays/command/examples.mdx +113 -0
  102. package/components/overlays/command/index.tsx +416 -0
  103. package/components/overlays/context-menu/examples.mdx +56 -0
  104. package/components/overlays/context-menu/index.tsx +279 -0
  105. package/components/overlays/dialog/examples.mdx +144 -0
  106. package/components/overlays/dialog/index.tsx +366 -0
  107. package/components/overlays/dropdown/dropdown-search.tsx +395 -0
  108. package/components/overlays/dropdown/examples.mdx +294 -0
  109. package/components/overlays/dropdown/index.tsx +1168 -0
  110. package/components/overlays/dropdown/menu-item.tsx +464 -0
  111. package/components/overlays/dropdown/menu-parts.tsx +660 -0
  112. package/components/overlays/popover/anchored-demo.tsx +59 -0
  113. package/components/overlays/popover/examples.mdx +180 -0
  114. package/components/overlays/popover/index.tsx +315 -0
  115. package/components/overlays/preview-card/examples.mdx +61 -0
  116. package/components/overlays/preview-card/index.tsx +204 -0
  117. package/components/overlays/sheet/examples.mdx +89 -0
  118. package/components/overlays/sheet/index.tsx +247 -0
  119. package/components/overlays/toast/examples.mdx +68 -0
  120. package/components/overlays/toast/global-demo.tsx +72 -0
  121. package/components/overlays/toast/index.tsx +474 -0
  122. package/components/overlays/toast/manager.ts +193 -0
  123. package/components/overlays/tooltip/examples.mdx +106 -0
  124. package/components/overlays/tooltip/index.tsx +519 -0
  125. package/components/progress/examples.mdx +59 -0
  126. package/components/progress/index.tsx +174 -0
  127. package/components/progress/progress.css +49 -0
  128. package/components/progress/step-demo.tsx +27 -0
  129. package/components/table/examples.mdx +184 -0
  130. package/components/table/index.tsx +510 -0
  131. package/components/table/sort-demo.tsx +61 -0
  132. package/dist/base.css +100 -0
  133. package/dist/blocks/sidebar-app/index.js +39 -26
  134. package/dist/components/AI/input-message/index.js +22 -16
  135. package/dist/components/AI/thinking-indicator/index.js +9 -6
  136. package/dist/components/accordion/index.js +16 -13
  137. package/dist/components/avatar/index.d.ts +46 -0
  138. package/dist/components/avatar/index.js +18 -0
  139. package/dist/components/badge/index.d.ts +18 -11
  140. package/dist/components/badge/index.js +11 -7
  141. package/dist/components/badge/variants.d.ts +33 -0
  142. package/dist/components/badge/variants.js +10 -0
  143. package/dist/components/button/index.d.ts +53 -17
  144. package/dist/components/button/index.js +10 -6
  145. package/dist/components/button/variants.d.ts +39 -0
  146. package/dist/components/button/variants.js +8 -0
  147. package/dist/components/card/index.d.ts +16 -3
  148. package/dist/components/card/index.js +16 -11
  149. package/dist/components/copy/index.js +13 -9
  150. package/dist/components/inputs/checkbox/index.d.ts +28 -9
  151. package/dist/components/inputs/checkbox/index.js +17 -13
  152. package/dist/components/inputs/color-picker/index.d.ts +13 -13
  153. package/dist/components/inputs/color-picker/index.js +25 -18
  154. package/dist/components/inputs/color-picker/variants.d.ts +61 -0
  155. package/dist/components/inputs/color-picker/variants.js +28 -0
  156. package/dist/components/inputs/combobox/index.d.ts +60 -17
  157. package/dist/components/inputs/combobox/index.js +26 -18
  158. package/dist/components/inputs/date-time/calendar/index.js +14 -11
  159. package/dist/components/inputs/date-time/date-picker/index.d.ts +53 -11
  160. package/dist/components/inputs/date-time/date-picker/index.js +33 -25
  161. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +3 -3
  162. package/dist/components/inputs/date-time/date-range-picker/index.js +32 -24
  163. package/dist/components/inputs/date-time/duration-picker/index.d.ts +3 -3
  164. package/dist/components/inputs/date-time/duration-picker/index.js +32 -24
  165. package/dist/components/inputs/date-time/month-picker/index.d.ts +3 -3
  166. package/dist/components/inputs/date-time/month-picker/index.js +31 -23
  167. package/dist/components/inputs/date-time/time-picker/index.d.ts +3 -3
  168. package/dist/components/inputs/date-time/time-picker/index.js +32 -24
  169. package/dist/components/inputs/field/index.d.ts +116 -0
  170. package/dist/components/inputs/field/index.js +28 -0
  171. package/dist/components/inputs/file-dropzone/index.d.ts +165 -0
  172. package/dist/components/inputs/file-dropzone/index.js +32 -0
  173. package/dist/components/inputs/file-dropzone/variants.d.ts +35 -0
  174. package/dist/components/inputs/file-dropzone/variants.js +12 -0
  175. package/dist/components/inputs/input/index.d.ts +95 -13
  176. package/dist/components/inputs/input/index.js +17 -9
  177. package/dist/components/inputs/label/index.d.ts +32 -0
  178. package/dist/components/inputs/label/index.js +11 -0
  179. package/dist/components/inputs/number-input/index.d.ts +84 -0
  180. package/dist/components/inputs/number-input/index.js +20 -0
  181. package/dist/components/inputs/radio/index.d.ts +20 -5
  182. package/dist/components/inputs/radio/index.js +15 -12
  183. package/dist/components/inputs/search/index.js +25 -18
  184. package/dist/components/inputs/select/index.d.ts +41 -11
  185. package/dist/components/inputs/select/index.js +27 -14
  186. package/dist/components/inputs/select/variants.d.ts +5 -0
  187. package/dist/components/inputs/select/variants.js +6 -0
  188. package/dist/components/inputs/slider/index.js +7 -5
  189. package/dist/components/inputs/switch/index.d.ts +8 -2
  190. package/dist/components/inputs/switch/index.js +14 -12
  191. package/dist/components/inputs/textarea/index.d.ts +4 -0
  192. package/dist/components/inputs/textarea/index.js +8 -6
  193. package/dist/components/navigation/navigation-menu/index.d.ts +114 -0
  194. package/dist/components/navigation/navigation-menu/index.js +40 -0
  195. package/dist/components/navigation/pagination/index.d.ts +73 -0
  196. package/dist/components/navigation/pagination/index.js +39 -0
  197. package/dist/components/navigation/sidebar/index.d.ts +44 -21
  198. package/dist/components/navigation/sidebar/index.js +41 -31
  199. package/dist/components/navigation/sidebar/variants.d.ts +25 -0
  200. package/dist/components/navigation/sidebar/variants.js +28 -0
  201. package/dist/components/navigation/tabs/index.d.ts +8 -2
  202. package/dist/components/navigation/tabs/index.js +14 -10
  203. package/dist/components/navigation/tabs-subtle/index.js +12 -10
  204. package/dist/components/overlays/alert-dialog/index.d.ts +121 -15
  205. package/dist/components/overlays/alert-dialog/index.js +22 -13
  206. package/dist/components/overlays/command/index.js +24 -17
  207. package/dist/components/overlays/context-menu/index.d.ts +33 -17
  208. package/dist/components/overlays/context-menu/index.js +27 -18
  209. package/dist/components/overlays/dialog/index.js +17 -12
  210. package/dist/components/overlays/dropdown/index.d.ts +257 -31
  211. package/dist/components/overlays/dropdown/index.js +42 -17
  212. package/dist/components/overlays/popover/index.d.ts +31 -9
  213. package/dist/components/overlays/popover/index.js +12 -9
  214. package/dist/components/overlays/preview-card/index.js +11 -8
  215. package/dist/components/overlays/sheet/index.js +18 -13
  216. package/dist/components/overlays/toast/index.d.ts +123 -47
  217. package/dist/components/overlays/toast/index.js +18 -10
  218. package/dist/components/overlays/tooltip/index.d.ts +113 -21
  219. package/dist/components/overlays/tooltip/index.js +15 -6
  220. package/dist/components/progress/index.d.ts +56 -0
  221. package/dist/components/progress/index.js +15 -0
  222. package/dist/components/table/index.d.ts +63 -8
  223. package/dist/components/table/index.js +19 -9
  224. package/dist/index.d.ts +2201 -736
  225. package/dist/index.js +223 -82
  226. package/dist/lib/date-locale.d.ts +14 -0
  227. package/dist/lib/date-locale.js +16 -0
  228. package/dist/lib/field-classes.d.ts +144 -0
  229. package/dist/lib/field-classes.js +40 -0
  230. package/dist/lib/field-variants.d.ts +47 -0
  231. package/dist/lib/field-variants.js +26 -0
  232. package/dist/lib/fluid-hover-highlight.d.ts +112 -0
  233. package/dist/lib/fluid-hover-highlight.js +17 -0
  234. package/dist/lib/focus-ring.d.ts +18 -0
  235. package/dist/lib/focus-ring.js +10 -0
  236. package/dist/lib/goo-indicator.d.ts +46 -0
  237. package/dist/lib/goo-indicator.js +14 -0
  238. package/dist/lib/icon-context.d.ts +30 -0
  239. package/dist/lib/icon-context.js +12 -0
  240. package/dist/lib/inert.d.ts +9 -0
  241. package/dist/lib/inert.js +8 -0
  242. package/dist/lib/morph-layers.d.ts +93 -0
  243. package/dist/lib/morph-layers.js +13 -0
  244. package/dist/lib/morph-part.d.ts +21 -0
  245. package/dist/lib/morph-part.js +12 -0
  246. package/dist/lib/popup.d.ts +20 -0
  247. package/dist/lib/popup.js +12 -0
  248. package/dist/lib/reduced-motion.d.ts +9 -0
  249. package/dist/lib/reduced-motion.js +6 -0
  250. package/dist/lib/row-index.d.ts +10 -0
  251. package/dist/lib/row-index.js +8 -0
  252. package/dist/lib/row-order.d.ts +28 -0
  253. package/dist/lib/row-order.js +10 -0
  254. package/dist/lib/scroll-column.d.ts +27 -0
  255. package/dist/lib/scroll-column.js +18 -0
  256. package/dist/lib/shape-context.d.ts +30 -0
  257. package/dist/lib/shape-context.js +12 -0
  258. package/dist/lib/size-context.d.ts +94 -0
  259. package/dist/lib/size-context.js +20 -0
  260. package/dist/lib/size-scale.d.ts +79 -0
  261. package/dist/lib/size-scale.js +8 -0
  262. package/dist/lib/slot.d.ts +12 -0
  263. package/dist/lib/slot.js +6 -0
  264. package/dist/lib/springs.d.ts +44 -0
  265. package/dist/lib/springs.js +8 -0
  266. package/dist/lib/surface-classes.d.ts +19 -0
  267. package/dist/lib/surface-classes.js +10 -0
  268. package/dist/lib/surface-context.d.ts +9 -0
  269. package/dist/lib/surface-context.js +8 -0
  270. package/dist/lib/use-controllable-state.d.ts +2 -0
  271. package/dist/lib/use-controllable-state.js +6 -0
  272. package/dist/lib/use-fluid-hover.d.ts +138 -0
  273. package/dist/lib/use-fluid-hover.js +14 -0
  274. package/dist/lib/use-is-mobile.d.ts +2 -0
  275. package/dist/lib/use-is-mobile.js +6 -0
  276. package/dist/lib/use-keyboard-nav-gate.d.ts +20 -0
  277. package/dist/lib/use-keyboard-nav-gate.js +7 -0
  278. package/dist/lib/use-merge-split.d.ts +47 -0
  279. package/dist/lib/use-merge-split.js +15 -0
  280. package/dist/lib/use-morph.d.ts +101 -0
  281. package/dist/lib/use-morph.js +20 -0
  282. package/dist/lib/use-touch-primary.d.ts +2 -0
  283. package/dist/lib/use-touch-primary.js +6 -0
  284. package/dist/lib/utils.d.ts +3 -0
  285. package/dist/lib/utils.js +6 -0
  286. package/dist/preset.d.ts +17 -0
  287. package/dist/preset.js +22 -2
  288. package/dist/primitives/code/index.js +7 -5
  289. package/dist/primitives/craft/index.js +9 -6
  290. package/dist/primitives/fluid-hover/index.js +9 -8
  291. package/dist/primitives/morph/index.js +15 -10
  292. package/dist/primitives/motion/index.js +10 -8
  293. package/dist/primitives/scroll-area/index.js +4 -3
  294. package/dist/primitives/separator/index.d.ts +25 -0
  295. package/dist/primitives/separator/index.js +9 -0
  296. package/dist/primitives/sizes/index.js +6 -4
  297. package/dist/primitives/skeleton/index.d.ts +35 -0
  298. package/dist/primitives/skeleton/index.js +12 -0
  299. package/dist/primitives/surface/index.js +6 -4
  300. package/dist/shared/{components-3mkepka5.js → components-0jmqhx2k.js} +28 -25
  301. package/dist/shared/components-124epb7f.js +53 -0
  302. package/dist/shared/{components-gz8zf3ec.js → components-1k0wv53d.js} +15 -43
  303. package/dist/shared/components-26snnbs9.js +322 -0
  304. package/dist/shared/{components-2dxkv6zm.js → components-2c0qcfx4.js} +8 -7
  305. package/dist/shared/{components-473g814w.js → components-2mt9te6d.js} +243 -137
  306. package/dist/shared/{components-hawvy2qz.js → components-2z48g4fp.js} +9 -30
  307. package/dist/shared/components-34txk9rj.js +35 -0
  308. package/dist/shared/components-3h92d94m.js +23 -0
  309. package/dist/shared/{components-zfnmn5d4.js → components-3sp623ae.js} +36 -31
  310. package/dist/shared/{components-jwst3vtj.js → components-3wxkjgqn.js} +186 -76
  311. package/dist/shared/{components-s2bz1xpk.js → components-49bsynbw.js} +8 -5
  312. package/dist/shared/components-4bwsd46w.js +71 -0
  313. package/dist/shared/components-4n4f5m3e.js +297 -0
  314. package/dist/shared/{components-0q0sqfh4.js → components-4y816hwp.js} +27 -49
  315. package/dist/shared/components-52ngsgca.js +399 -0
  316. package/dist/shared/{components-y9y0g8er.js → components-52r5763g.js} +22 -19
  317. package/dist/shared/components-559tf7yb.js +28 -0
  318. package/dist/shared/components-5mkga9hj.js +440 -0
  319. package/dist/shared/{components-kwa6mrf9.js → components-5s0nfqvw.js} +14 -9
  320. package/dist/shared/{components-dk0gjr5a.js → components-60atskhp.js} +19 -18
  321. package/dist/shared/{components-rr7a6ja9.js → components-658b26nb.js} +21 -18
  322. package/dist/shared/{components-xxvdw7k1.js → components-65vwfjs6.js} +37 -34
  323. package/dist/shared/{components-frw34b65.js → components-6j2fh74k.js} +114 -123
  324. package/dist/shared/components-6zg1yft7.js +167 -0
  325. package/dist/shared/components-6zng858q.js +53 -0
  326. package/dist/shared/components-72w4ge68.js +64 -0
  327. package/dist/shared/{components-cv16mryv.js → components-78x40gee.js} +13 -7
  328. package/dist/shared/{components-b2wyq2cq.js → components-7d4r7hh1.js} +39 -57
  329. package/dist/shared/components-7ntvvegr.js +316 -0
  330. package/dist/shared/components-8nwsqjn8.js +34 -0
  331. package/dist/shared/{components-5xc2j8jz.js → components-8tt3xbb5.js} +19 -18
  332. package/dist/shared/{components-vc7vq5nz.js → components-9as3rf68.js} +6 -3
  333. package/dist/shared/components-9mys9jna.js +255 -0
  334. package/dist/shared/components-adkarj8n.js +35 -0
  335. package/dist/shared/{components-b3c5et5b.js → components-ae9fncz4.js} +32 -31
  336. package/dist/shared/{components-hqeq3w9q.js → components-awra5sff.js} +24 -16
  337. package/dist/shared/components-b1e239g3.js +28 -0
  338. package/dist/shared/{components-q5xj5c16.js → components-b1f2bjf1.js} +9 -8
  339. package/dist/shared/{components-qmmjx1qw.js → components-bn42vq6d.js} +71 -72
  340. package/dist/shared/components-c96g82th.js +1444 -0
  341. package/dist/shared/components-c9yr4ed1.js +44 -0
  342. package/dist/shared/components-d3shbwqq.js +95 -0
  343. package/dist/shared/{components-jjw69c4c.js → components-dcc02hs2.js} +7 -4
  344. package/dist/shared/{components-men63rwj.js → components-de99ykt7.js} +3 -2
  345. package/dist/shared/components-dhgk6mbt.js +16 -0
  346. package/dist/shared/components-drxrtnp2.js +25 -0
  347. package/dist/shared/{components-6p8tfcd6.js → components-ds9fqat9.js} +21 -18
  348. package/dist/shared/{components-mjthm1kf.js → components-egy4tyjt.js} +11 -8
  349. package/dist/shared/components-f0xfpkhh.js +82 -0
  350. package/dist/shared/components-f7h57m6t.js +59 -0
  351. package/dist/shared/{components-d6xtjht3.js → components-fjkpbg6n.js} +33 -30
  352. package/dist/shared/{components-gc5vtq2f.js → components-gh0qeey3.js} +63 -29
  353. package/dist/shared/{components-39jq9va1.js → components-gvbapfen.js} +29 -24
  354. package/dist/shared/{components-5vgpnxgy.js → components-gzesg7hy.js} +19 -16
  355. package/dist/shared/components-h6p86be3.js +19 -0
  356. package/dist/shared/components-h83az0bz.js +33 -0
  357. package/dist/shared/components-jjrj6ymn.js +104 -0
  358. package/dist/shared/{components-wvp1m52g.js → components-jp5v0m0f.js} +15 -14
  359. package/dist/shared/{components-zrkzcayz.js → components-jqpefjsn.js} +54 -6
  360. package/dist/shared/{components-1z6tdadk.js → components-k74j4hwh.js} +20 -17
  361. package/dist/shared/components-kswxnm3h.js +24 -0
  362. package/dist/shared/{components-jbbmdr74.js → components-mkkbbgb7.js} +36 -17
  363. package/dist/shared/{components-pbmqctfa.js → components-mpw667rb.js} +169 -65
  364. package/dist/shared/{components-s5ngg32c.js → components-paa08427.js} +121 -47
  365. package/dist/shared/{components-vfbhxf5r.js → components-pd42emw3.js} +6 -5
  366. package/dist/shared/{components-hw9yk32z.js → components-pmfszv7a.js} +35 -23
  367. package/dist/shared/{components-chacnk61.js → components-pw06rbxq.js} +19 -19
  368. package/dist/shared/components-q03cktx8.js +246 -0
  369. package/dist/shared/{components-kvr7f9hy.js → components-qdm3yh26.js} +19 -10
  370. package/dist/shared/{components-27nnvrwe.js → components-qs4rkqsj.js} +15 -14
  371. package/dist/shared/{components-s083f38z.js → components-r6gbmkew.js} +22 -21
  372. package/dist/shared/{components-ta7shw28.js → components-r8eqs7gz.js} +4 -4
  373. package/dist/shared/{components-sn1rxx1d.js → components-rm02h9nt.js} +12 -11
  374. package/dist/shared/{components-c6s6b43g.js → components-rrbpmfx4.js} +3 -2
  375. package/dist/shared/{components-n7mjr357.js → components-tz1fv35x.js} +3 -2
  376. package/dist/shared/components-tz4gaf1v.js +23 -0
  377. package/dist/shared/{components-zx6b6yby.js → components-vf45j12c.js} +4 -18
  378. package/dist/shared/{components-3ky44908.js → components-vfjc9jzz.js} +2 -3
  379. package/dist/shared/{components-39vmfq9s.js → components-vsa625vk.js} +21 -20
  380. package/dist/shared/components-wawnmkph.js +500 -0
  381. package/dist/shared/components-wktrrb4m.js +38 -0
  382. package/dist/shared/{components-8snj490h.js → components-wwd363me.js} +78 -32
  383. package/dist/shared/{components-5pnw8183.js → components-xcwvgcrd.js} +23 -22
  384. package/dist/shared/components-xdqmt6ed.js +20 -0
  385. package/dist/shared/{components-h2ajmhwn.js → components-y1w3a6wx.js} +107 -35
  386. package/dist/shared/{components-ar66gnt0.js → components-y6578b8h.js} +37 -34
  387. package/dist/shared/{components-59efxjd9.js → components-yhgq0qxx.js} +13 -12
  388. package/dist/shared/{components-jthe1pwb.js → components-yz84gpkq.js} +101 -404
  389. package/dist/shared/components-z3v0n0de.js +11 -0
  390. package/dist/shared/{components-7r3etddy.js → components-z9n19fyk.js} +19 -54
  391. package/dist/shared/components-zbvjzh1p.js +141 -0
  392. package/dist/styles.css +241 -16
  393. package/dist/tailwind.css +13 -0
  394. package/dist/tokens.css +34 -0
  395. package/index.ts +60 -0
  396. package/lib/date-locale.ts +50 -0
  397. package/lib/field-classes.ts +115 -0
  398. package/lib/field-variants.ts +117 -0
  399. package/lib/fluid-hover-highlight.tsx +139 -0
  400. package/lib/focus-ring.tsx +46 -0
  401. package/lib/goo-indicator.tsx +261 -0
  402. package/lib/icon-context.tsx +310 -0
  403. package/lib/inert.ts +22 -0
  404. package/lib/morph-layers.tsx +102 -0
  405. package/lib/morph-part.tsx +49 -0
  406. package/lib/popup.ts +52 -0
  407. package/lib/reduced-motion.ts +19 -0
  408. package/lib/row-index.tsx +29 -0
  409. package/lib/row-order.tsx +115 -0
  410. package/lib/scroll-column.tsx +244 -0
  411. package/lib/shape-context.tsx +124 -0
  412. package/lib/size-context.tsx +66 -0
  413. package/lib/size-scale.ts +115 -0
  414. package/lib/slot.ts +25 -0
  415. package/lib/springs.ts +62 -0
  416. package/lib/surface-classes.ts +58 -0
  417. package/lib/surface-context.tsx +21 -0
  418. package/lib/use-controllable-state.ts +44 -0
  419. package/lib/use-fluid-hover.ts +541 -0
  420. package/lib/use-is-mobile.ts +25 -0
  421. package/lib/use-keyboard-nav-gate.ts +45 -0
  422. package/lib/use-merge-split.tsx +209 -0
  423. package/lib/use-morph.ts +557 -0
  424. package/lib/use-touch-primary.tsx +43 -0
  425. package/lib/utils.ts +37 -0
  426. package/package.json +177 -4
  427. package/preset.ts +196 -0
  428. package/primitives/code/examples.mdx +27 -0
  429. package/primitives/code/index.tsx +80 -0
  430. package/primitives/craft/demos.tsx +173 -0
  431. package/primitives/craft/examples.mdx +138 -0
  432. package/primitives/craft/index.tsx +76 -0
  433. package/primitives/fluid-hover/examples.mdx +84 -0
  434. package/primitives/fluid-hover/index.tsx +316 -0
  435. package/primitives/morph/examples.mdx +135 -0
  436. package/primitives/morph/index.tsx +192 -0
  437. package/primitives/motion/examples.mdx +100 -0
  438. package/primitives/motion/index.tsx +160 -0
  439. package/primitives/scroll-area/examples.mdx +58 -0
  440. package/primitives/scroll-area/index.tsx +200 -0
  441. package/primitives/separator/examples.mdx +30 -0
  442. package/primitives/separator/index.tsx +53 -0
  443. package/primitives/sizes/examples.mdx +167 -0
  444. package/primitives/sizes/index.tsx +89 -0
  445. package/primitives/skeleton/examples.mdx +51 -0
  446. package/primitives/skeleton/index.tsx +72 -0
  447. package/primitives/skeleton/skeleton.css +15 -0
  448. package/primitives/surface/examples.mdx +173 -0
  449. package/primitives/surface/index.tsx +109 -0
  450. package/primitives/surface/surface.css +176 -0
  451. package/dist/shared/components-0sn7ypjw.js +0 -61
  452. package/dist/shared/components-1434p1mh.js +0 -123
  453. package/dist/shared/components-a86dytt0.js +0 -986
  454. package/dist/shared/components-gf5s6ap9.js +0 -42
  455. package/dist/shared/components-hyq23phv.js +0 -310
  456. package/dist/shared/components-ng72k97e.js +0 -128
  457. package/dist/shared/components-raknhppb.js +0 -64
  458. package/dist/shared/components-vynjtrk6.js +0 -19
  459. package/dist/shared/components-wy9jpqny.js +0 -146
  460. /package/dist/shared/{components-1v7rxytx.js → components-3ey0hzpb.js} +0 -0
  461. /package/dist/shared/{components-ezxhrmer.js → components-jcnjay5g.js} +0 -0
@@ -0,0 +1,474 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): gooey toasts after Sileo
3
+ * (github.com/hiaaryan/sileo `src/sileo.tsx` + `src/styles.css` @
4
+ * 9793f844349983e140cf33cebbb8f51626d41407 — MIT, notice: LICENSE.sileo). A
5
+ * pill with the state's icon and title melts into a body with the description
6
+ * and an action, through the shared goo filter; the body opens a beat after
7
+ * the toast lands and folds back before it leaves (Sileo's autopilot), or
8
+ * whenever it is hovered. Base UI's Toast owns the queue, timers, swipe to
9
+ * dismiss and announcements; Sileo's own store, timers and swipe are not
10
+ * ported. The toast is drawn like every other morph surface: the Elevated
11
+ * level's background shapes melted by the shared goo filter, their shadows
12
+ * beneath, the content on top. Colors are the status tokens, the sizes the
13
+ * control ladder's, and the motion the spring tiers; reduced motion keeps the
14
+ * fades and snaps the shapes. The global `toast()` (`manager.ts`) feeds a
15
+ * `Toast` mounted with `manager={toast}` from anywhere, React or not; titles
16
+ * and descriptions take any node, `icon` replaces the state's glyph and
17
+ * `action` takes a button's `{ label, onClick }` or any node. The state
18
+ * glyphs come from the icon set (`useIcons`), so an `IconProvider` swaps them.
19
+ */
20
+
21
+ import { type ToastManager, Toast as ToastPrimitive } from '@base-ui/react/toast'
22
+ import { AnimatePresence, motion } from 'motion/react'
23
+ import {
24
+ cloneElement,
25
+ createContext,
26
+ forwardRef,
27
+ isValidElement,
28
+ type ReactNode,
29
+ type Ref,
30
+ useContext,
31
+ useEffect,
32
+ useId,
33
+ useLayoutEffect,
34
+ useMemo,
35
+ useRef,
36
+ useState,
37
+ } from 'react'
38
+ import { type IconComponent, type IconName, useIcons } from '../../../lib/icon-context'
39
+ import { GooFilter } from '../../../lib/morph-layers'
40
+ import { useReduceMotion } from '../../../lib/reduced-motion'
41
+ import { useShape } from '../../../lib/shape-context'
42
+ import { sizeMap } from '../../../lib/size-context'
43
+ import { type SlotProps, slotRender } from '../../../lib/slot'
44
+ import { spring } from '../../../lib/springs'
45
+ import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
46
+ import { SurfaceProvider, useSurface } from '../../../lib/surface-context'
47
+ import { GOO_BLUR_RATIO, holdExit } from '../../../lib/use-morph'
48
+ import { cn } from '../../../lib/utils'
49
+ import Button from '../../button'
50
+ import {
51
+ type ToastActionOptions,
52
+ type ToastData,
53
+ type ToastHandle,
54
+ type ToastInput,
55
+ type ToastOptions,
56
+ type ToastPromiseOptions,
57
+ type ToastSource,
58
+ type ToastType,
59
+ toast,
60
+ toManagerOptions,
61
+ } from './manager'
62
+
63
+ type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
64
+
65
+ const ICONS: Record<ToastType, IconName> = {
66
+ success: 'check',
67
+ loading: 'loader-circle',
68
+ error: 'x',
69
+ warning: 'circle-alert',
70
+ info: 'info',
71
+ action: 'arrow-right',
72
+ }
73
+
74
+ // Literal per state so the class extractor sees each one.
75
+ const TONES: Record<ToastType, string> = {
76
+ success: 'text-status-success bg-status-success/16',
77
+ loading: 'text-status-loading bg-status-loading/16',
78
+ error: 'text-status-error bg-status-error/16',
79
+ warning: 'text-status-warning bg-status-warning/16',
80
+ info: 'text-status-info bg-status-info/16',
81
+ action: 'text-status-action bg-status-action/16',
82
+ }
83
+
84
+ const VIEWPORT: Record<ToastPosition, string> = {
85
+ 'top-left': 'top-4 left-4 items-start',
86
+ 'top-center': 'top-4 left-1/2 -translate-x-1/2 items-center',
87
+ 'top-right': 'top-4 right-4 items-end',
88
+ 'bottom-left': 'bottom-4 left-4 flex-col-reverse items-start',
89
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2 flex-col-reverse items-center',
90
+ 'bottom-right': 'bottom-4 right-4 flex-col-reverse items-end',
91
+ }
92
+
93
+ // The pill is one control tall, and the goo melts as much as its radius allows.
94
+ const PILL = sizeMap.default.controlHeight
95
+ const BLUR = (PILL / 2) * GOO_BLUR_RATIO
96
+ const SNAP = { duration: 0 }
97
+ const AUTO_EXPAND_MS = 150
98
+ const AUTO_COLLAPSE_BEFORE_MS = 2000
99
+
100
+ const ToastSettings = createContext<{ position: ToastPosition; timeout: number }>({
101
+ position: 'top-right',
102
+ timeout: 6000,
103
+ })
104
+
105
+ /**
106
+ * Shows toasts from inside a `Toast`: `show`, the per-state shortcuts,
107
+ * `promise`, `update` and `close` (alias `dismiss`), plus the `toasts` showing.
108
+ * Under a `Toast` with `manager={toast}` it works on the same stack as the
109
+ * global `toast()`.
110
+ */
111
+ function useToast() {
112
+ const manager = ToastPrimitive.useToastManager<ToastData>()
113
+
114
+ return useMemo(() => {
115
+ const show = (o: ToastInput) => manager.add({ type: 'info', ...toManagerOptions(o) })
116
+ const typed = (type: ToastType) => (o: ToastInput) =>
117
+ show(typeof o === 'string' ? { title: o, type } : { ...o, type })
118
+ const result = <T,>(r: ToastPromiseOptions<T>['success']) =>
119
+ typeof r === 'function' ? (value: T) => toManagerOptions(r(value)) : toManagerOptions(r)
120
+
121
+ return {
122
+ toasts: manager.toasts,
123
+ show,
124
+ success: typed('success'),
125
+ error: typed('error'),
126
+ warning: typed('warning'),
127
+ info: typed('info'),
128
+ promise: <T,>(promise: Promise<T>, o: ToastPromiseOptions<T>) =>
129
+ manager.promise(promise, {
130
+ loading: toManagerOptions(o.loading),
131
+ success: result(o.success),
132
+ error: result(o.error),
133
+ }),
134
+ update: (id: string, o: ToastInput) => manager.update(id, toManagerOptions(o)),
135
+ close: manager.close,
136
+ dismiss: manager.close,
137
+ }
138
+ }, [manager])
139
+ }
140
+
141
+ interface Box {
142
+ x: number
143
+ y: number
144
+ width: number
145
+ height: number
146
+ }
147
+
148
+ function Shape({ box, className, shapeRef }: { box: Box; className: string; shapeRef?: Ref<HTMLDivElement> }) {
149
+ const reduced = useReduceMotion()
150
+ return (
151
+ <motion.div
152
+ ref={shapeRef}
153
+ aria-hidden
154
+ className={cn('pointer-events-none absolute top-0 left-0', className)}
155
+ initial={false}
156
+ animate={{ ...box }}
157
+ transition={reduced ? SNAP : spring.slow}
158
+ />
159
+ )
160
+ }
161
+
162
+ /** The pill's glyph: the state's icon, or the toast's own `icon` (a component, or any node). */
163
+ function StatusIcon({ type, icon }: { type: ToastType; icon: ToastData['icon'] }) {
164
+ const icons = useIcons()
165
+ // A component is a function or a forwardRef / memo object; an element is a node.
166
+ const component =
167
+ typeof icon === 'function' ||
168
+ (typeof icon === 'object' && icon !== null && '$$typeof' in icon && !isValidElement(icon))
169
+ if (icon != null && icon !== false && !component) return <>{icon}</>
170
+ const Icon = component ? (icon as IconComponent) : icons[ICONS[type]]
171
+ return <Icon size={14} strokeWidth={2.5} className={type === 'loading' && !component ? 'animate-spin' : undefined} />
172
+ }
173
+
174
+ function ToastItem({ toast }: { toast: ToastPrimitive.Root.ToastObject<ToastData> }) {
175
+ const { position, timeout } = useContext(ToastSettings)
176
+ const shape = useShape()
177
+ // Lifts 2 levels off its substrate with a fixed shadow, like a popover.
178
+ const level = Math.min(useSurface() + 2, 8)
179
+ const gooId = `toast-goo-${useId().replace(/:/g, '')}`
180
+ const type = (toast.type ?? 'info') as ToastType
181
+ const customAction = toast.data?.action
182
+ const top = position.startsWith('top')
183
+ const ending = toast.transitionStatus === 'ending'
184
+ const hasBody = Boolean(toast.description || toast.actionProps || customAction)
185
+ const [hovered, setHovered] = useState(false)
186
+ const [auto, setAuto] = useState(false)
187
+ const expanded = hasBody && type !== 'loading' && !ending && (hovered || auto)
188
+ const root = useRef<HTMLDivElement>(null)
189
+ const header = useRef<HTMLDivElement>(null)
190
+ const body = useRef<HTMLDivElement>(null)
191
+ const bodyShape = useRef<HTMLDivElement>(null)
192
+ const [size, setSize] = useState({ width: 0, pill: PILL, body: 0, radius: 0 })
193
+
194
+ useEffect(() => {
195
+ if (!hasBody || type === 'loading') return
196
+ const life = toast.timeout ?? timeout
197
+ const open = setTimeout(() => setAuto(true), AUTO_EXPAND_MS)
198
+ const close =
199
+ life > 0 ? setTimeout(() => setAuto(false), Math.max(AUTO_EXPAND_MS, life - AUTO_COLLAPSE_BEFORE_MS)) : undefined
200
+ return () => {
201
+ clearTimeout(open)
202
+ clearTimeout(close)
203
+ }
204
+ }, [hasBody, type, toast.timeout, timeout])
205
+
206
+ // The header and the body content keep their natural size (the header is
207
+ // `w-max`, outside the pill), so measuring them never feeds back the
208
+ // animated shapes. A body that appears later (a promise's result) is
209
+ // observed from then on.
210
+ useLayoutEffect(() => {
211
+ const measure = () =>
212
+ setSize({
213
+ width: root.current?.offsetWidth ?? 0,
214
+ pill: Math.max(PILL, header.current?.offsetWidth ?? 0),
215
+ body: body.current?.offsetHeight ?? 0,
216
+ radius: bodyShape.current ? Number.parseFloat(getComputedStyle(bodyShape.current).borderTopLeftRadius) || 0 : 0,
217
+ })
218
+ const observer = new ResizeObserver(measure)
219
+ for (const el of [header.current, body.current]) if (el) observer.observe(el)
220
+ measure()
221
+ return () => observer.disconnect()
222
+ }, [hasBody])
223
+
224
+ useLayoutEffect(() => {
225
+ if (!ending || !root.current) return
226
+ const hold = holdExit(root.current, 'moderate')
227
+ return () => hold.cancel()
228
+ }, [ending])
229
+
230
+ // The pill sits on the toast's side of the screen; the body opens away from
231
+ // the edge. While open, the pill reaches into the body by the body's corner
232
+ // radius, covering that corner, so the outer edge runs straight and the goo
233
+ // only rounds the inner corner (Sileo's open pill). The body's padding on
234
+ // the pill side clears that reach. The header and the body content ride the
235
+ // same values as their shapes, so they never drift apart mid-spring.
236
+ const bodyHeight = expanded ? size.body : 0
237
+ const height = PILL + bodyHeight
238
+ const pillX = position.endsWith('left')
239
+ ? 0
240
+ : position.endsWith('right')
241
+ ? size.width - size.pill
242
+ : (size.width - size.pill) / 2
243
+ const pillHeight = PILL + Math.min(bodyHeight, size.radius)
244
+ const pill = { x: pillX, y: top ? 0 : height - pillHeight, width: size.pill, height: pillHeight }
245
+ const bodyBox = { x: 0, y: top ? PILL : 0, width: size.width, height: bodyHeight }
246
+ const reduced = useReduceMotion()
247
+ // Reduced motion keeps the fades and drops the travel.
248
+ const away = reduced ? { opacity: 0 } : { opacity: 0, y: top ? -6 : 6, scale: 0.95 }
249
+ const move = reduced ? SNAP : spring.slow
250
+
251
+ return (
252
+ <ToastPrimitive.Root
253
+ ref={root}
254
+ toast={toast}
255
+ swipeDirection={[top ? 'up' : 'down', position.endsWith('left') ? 'left' : 'right']}
256
+ onMouseEnter={() => setHovered(true)}
257
+ onMouseLeave={() => setHovered(false)}
258
+ render={
259
+ <motion.div
260
+ initial={{ ...away, height: PILL }}
261
+ animate={ending ? { ...away, height } : { opacity: 1, y: 0, scale: 1, height }}
262
+ transition={ending ? spring.moderate.exit : { ...spring.moderate, height: move }}
263
+ />
264
+ }
265
+ className="relative w-[min(22rem,calc(100vw-2rem))] select-none outline-none [translate:var(--toast-swipe-movement-x)_var(--toast-swipe-movement-y)]"
266
+ >
267
+ <GooFilter id={gooId} blur={BLUR} />
268
+ <Shape box={pill} className={cn('rounded-full', SURFACE_SHADOW[3])} />
269
+ {hasBody && <Shape box={bodyBox} className={cn(shape.container, SURFACE_SHADOW[3])} />}
270
+ {/* The filter is the effect itself: it melts the two token-filled shapes into one. */}
271
+ <div aria-hidden className="pointer-events-none absolute inset-0" style={{ filter: `url(#${gooId})` }}>
272
+ <Shape box={pill} className={cn('rounded-full', SURFACE_BG[level])} />
273
+ {hasBody && <Shape box={bodyBox} shapeRef={bodyShape} className={cn(shape.container, SURFACE_BG[level])} />}
274
+ </div>
275
+
276
+ <SurfaceProvider value={level}>
277
+ <motion.div
278
+ ref={header}
279
+ className="absolute top-0 left-0 flex h-9 w-max items-center py-1.5 pr-4 pl-1.5"
280
+ initial={false}
281
+ animate={{ x: pillX, y: top ? 0 : height - PILL }}
282
+ transition={move}
283
+ >
284
+ <ToastPrimitive.Title className="relative inline-flex items-center whitespace-nowrap">
285
+ <AnimatePresence mode="popLayout" initial={false}>
286
+ <motion.span
287
+ // A string title keys itself; a node title crossfades on each update.
288
+ key={`${type}-${typeof toast.title === 'string' ? toast.title : toast.updateKey}`}
289
+ className="inline-flex items-center gap-2"
290
+ initial={{ opacity: 0, filter: 'blur(4px)' }}
291
+ animate={{ opacity: 1, filter: 'blur(0px)' }}
292
+ exit={{ opacity: 0, filter: 'blur(4px)', transition: spring.moderate.exit }}
293
+ transition={spring.moderate}
294
+ >
295
+ <span className={cn('flex size-6 items-center justify-center rounded-full', TONES[type])}>
296
+ <StatusIcon type={type} icon={toast.data?.icon} />
297
+ </span>
298
+ <span className="text-body weight-medium text-foreground">{toast.title}</span>
299
+ </motion.span>
300
+ </AnimatePresence>
301
+ </ToastPrimitive.Title>
302
+ </motion.div>
303
+ {hasBody && (
304
+ <motion.div
305
+ className="absolute top-0 left-0 w-full overflow-hidden"
306
+ initial={false}
307
+ animate={{ y: bodyBox.y, height: bodyHeight, opacity: expanded ? 1 : 0 }}
308
+ transition={{ ...move, opacity: expanded ? spring.slow : spring.slow.exit }}
309
+ >
310
+ <div ref={body} className={cn('flex flex-col items-start gap-3 p-4', top ? 'pt-6' : 'pb-6')}>
311
+ <ToastPrimitive.Description className="text-caption text-muted-foreground" />
312
+ {toast.actionProps && <ToastPrimitive.Action render={<Button size="compact" variant="secondary" />} />}
313
+ {customAction}
314
+ </div>
315
+ </motion.div>
316
+ )}
317
+ </SurfaceProvider>
318
+ </ToastPrimitive.Root>
319
+ )
320
+ }
321
+
322
+ function ToastList() {
323
+ const { toasts } = ToastPrimitive.useToastManager<ToastData>()
324
+ return toasts.map(toast => <ToastItem key={toast.id} toast={toast} />)
325
+ }
326
+
327
+ interface ToastTriggerProps extends Omit<SlotProps, 'type' | 'title'> {
328
+ /** The toast's title, in the pill. */
329
+ title?: ReactNode
330
+ /** Text for the body the pill melts into. */
331
+ description?: ReactNode
332
+ /** State: sets the icon and its color. Defaults to `'info'`. */
333
+ type?: 'success' | 'loading' | 'error' | 'warning' | 'info' | 'action'
334
+ /** Ms before it leaves; 0 keeps it until dismissed. Defaults to the `Toast`'s `timeout`. */
335
+ timeout?: number
336
+ /** Replaces the state's icon in the pill: an icon component, or any node. */
337
+ icon?: IconComponent | ReactNode
338
+ /** A button in the body (`{ label, onClick }`), or any node. */
339
+ action?: ToastActionOptions | ReactNode
340
+ /** Runs a task: the toast shows `title` while it is pending, then `success` or `error`. */
341
+ promise?: () => Promise<unknown>
342
+ /** The toast once `promise` resolves. */
343
+ success?: ToastOptions
344
+ /** The toast if `promise` rejects. */
345
+ error?: ToastOptions
346
+ }
347
+
348
+ const ToastTrigger = forwardRef<HTMLButtonElement, ToastTriggerProps>(
349
+ (
350
+ {
351
+ title,
352
+ description,
353
+ type,
354
+ timeout,
355
+ icon,
356
+ action,
357
+ promise,
358
+ success,
359
+ error,
360
+ render,
361
+ asChild,
362
+ children,
363
+ onClick,
364
+ ...props
365
+ },
366
+ ref,
367
+ ) => {
368
+ const toasts = useToast()
369
+ const el = slotRender(render, asChild, children)
370
+ const press = (event: React.MouseEvent<HTMLButtonElement>) => {
371
+ onClick?.(event)
372
+ if (promise) {
373
+ // The error toast is the report; the rejection Base UI hands back would only surface unhandled.
374
+ toasts
375
+ .promise(promise(), { loading: { title, description, icon }, success: success ?? {}, error: error ?? {} })
376
+ .catch(() => {})
377
+ return
378
+ }
379
+ toasts.show({ title, description, type, timeout, icon, action })
380
+ }
381
+ return el ? (
382
+ cloneElement(el, { ...props, ref, onClick: press })
383
+ ) : (
384
+ <button ref={ref} type="button" {...props} onClick={press}>
385
+ {children}
386
+ </button>
387
+ )
388
+ },
389
+ )
390
+ ToastTrigger.displayName = 'ToastTrigger'
391
+
392
+ interface ToastProps {
393
+ /** Where toasts stack; the pill sits on that side and the body opens away from the edge. Defaults to `'top-right'`. */
394
+ position?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
395
+ /** How many toasts show at once; older ones wait. Defaults to `3`. */
396
+ limit?: number
397
+ /** Ms a toast stays; hovering pauses it. Defaults to `6000`. */
398
+ timeout?: number
399
+ /** Also renders the toasts this manager shows: pass the global `toast` so `toast()` calls from anywhere land here (mount one such `Toast` per app), or a manager from Base UI's `Toast.createToastManager()`. */
400
+ manager?: ToastSource
401
+ /** The part of the app that can show toasts, with `Toast.Trigger`s or `useToast()`. Optional: a `Toast` with a `manager` can stand alone. */
402
+ children?: ReactNode
403
+ }
404
+
405
+ /**
406
+ * Gooey toasts: a pill with the state's icon and title that melts into a body
407
+ * with the details.
408
+ *
409
+ * Each toast lands as a pill one control tall; when it has a description or
410
+ * an action, the pill melts into a body a beat later through the shared goo
411
+ * filter (see Morph), and folds back before it leaves, or stays open while
412
+ * hovered. Six states (`success`, `loading`, `error`, `warning`, `info`,
413
+ * `action`) set the icon and its status color; a promise toast spins as
414
+ * `loading` and crossfades into its result. Built on Base UI's Toast:
415
+ * timers pause on hover, a swipe toward the edge dismisses, and screen
416
+ * readers hear every toast.
417
+ *
418
+ * `Toast` wraps the part of the app that shows toasts and renders them at
419
+ * its `position`. Show one with `Toast.Trigger`, or call `useToast()`
420
+ * inside it: `show`, `success`, `error`, `warning`, `info`, `promise`,
421
+ * `update` and `close`.
422
+ *
423
+ * Or mount one `<Toast manager={toast} />` near the root and call the global
424
+ * `toast()` from anywhere, inside React or not: `toast({ title, description,
425
+ * type, timeout, icon, action, onClose })`, `toast.success`, `.error`,
426
+ * `.warning`, `.info`, `.loading`, `.promise`, `.update(id, …)` and
427
+ * `.dismiss(id?)`. Each call returns a handle with the toast's `id`,
428
+ * `update` and `dismiss`; calls made before the `Toast` mounts wait for it.
429
+ * Titles and descriptions take any node, `icon` replaces the state's glyph,
430
+ * and `action` is a button (`{ label, onClick }`) or any node.
431
+ *
432
+ * Statics:
433
+ * - `Toast.Trigger` — a control that shows a toast when pressed:
434
+ * `render={<Button/>}` or `asChild`, with the toast's `title`,
435
+ * `description`, `type`, `timeout`, `action`, or a `promise` with its
436
+ * `success` / `error` toasts.
437
+ *
438
+ * @example {@include ./examples.mdx}
439
+ */
440
+ function Toast({ position = 'top-right', limit = 3, timeout = 6000, manager, children }: ToastProps) {
441
+ const settings = useMemo(() => ({ position, timeout }), [position, timeout])
442
+
443
+ return (
444
+ // The provider only ever subscribes to the manager, which is all a `ToastSource` promises.
445
+ <ToastPrimitive.Provider limit={limit} timeout={timeout} toastManager={manager as ToastManager | undefined}>
446
+ <ToastSettings.Provider value={settings}>
447
+ {children}
448
+ <ToastPrimitive.Portal>
449
+ <ToastPrimitive.Viewport className={cn('fixed z-[60] flex flex-col gap-2 outline-none', VIEWPORT[position])}>
450
+ <ToastList />
451
+ </ToastPrimitive.Viewport>
452
+ </ToastPrimitive.Portal>
453
+ </ToastSettings.Provider>
454
+ </ToastPrimitive.Provider>
455
+ )
456
+ }
457
+
458
+ Toast.Trigger = ToastTrigger
459
+
460
+ export type {
461
+ ToastActionOptions,
462
+ ToastHandle,
463
+ ToastInput,
464
+ ToastOptions,
465
+ ToastPosition,
466
+ ToastPromiseOptions,
467
+ ToastProps,
468
+ ToastSource,
469
+ ToastTriggerProps,
470
+ ToastType,
471
+ }
472
+ export { Toast, ToastTrigger, toast, useToast }
473
+
474
+ export default Toast
@@ -0,0 +1,193 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): the toast options and the
3
+ * global `toast()` that works outside React. One module-level Base UI toast
4
+ * manager (`Toast.createToastManager`) carries every call to the `Toast`
5
+ * mounted with `manager={toast}`; calls made before that `Toast` subscribes
6
+ * (a child's mount effect runs before its parent's) wait in a queue and play
7
+ * once it does, so no toast is lost on the first render. On the server (no
8
+ * `window`) nothing will ever subscribe, so calls there are dropped instead of
9
+ * queued for the life of the process.
10
+ */
11
+
12
+ import { type ToastManager, Toast as ToastPrimitive } from '@base-ui/react/toast'
13
+ import { isValidElement, type ReactNode } from 'react'
14
+ import type { IconComponent } from '../../../lib/icon-context'
15
+
16
+ type ToastType = 'success' | 'loading' | 'error' | 'warning' | 'info' | 'action'
17
+
18
+ /** A button in the toast's body. */
19
+ interface ToastActionOptions {
20
+ label: ReactNode
21
+ onClick?: () => void
22
+ }
23
+
24
+ interface ToastOptions {
25
+ /** Reuse an id to replace a toast that is still showing. */
26
+ id?: string
27
+ title?: ReactNode
28
+ description?: ReactNode
29
+ type?: ToastType
30
+ /** Ms before it leaves; 0 keeps it until dismissed. */
31
+ timeout?: number
32
+ /** Same as `timeout`; `timeout` wins when both are set. */
33
+ duration?: number
34
+ /** Replaces the state's icon in the pill: an icon component, or any node. */
35
+ icon?: IconComponent | ReactNode
36
+ /** A button in the body (`{ label, onClick }`), or any node. */
37
+ action?: ToastActionOptions | ReactNode
38
+ /** Runs once the toast starts to leave, however it was closed. */
39
+ onClose?: () => void
40
+ /** `high` is announced at once by screen readers; `low` waits its turn. */
41
+ priority?: 'low' | 'high'
42
+ }
43
+
44
+ /** What a toast keeps beside Base UI's own fields. */
45
+ interface ToastData {
46
+ icon?: IconComponent | ReactNode
47
+ action?: ReactNode
48
+ }
49
+
50
+ type ToastInput = string | ToastOptions
51
+ type ToastResult<T> = ToastInput | ((value: T) => ToastInput)
52
+
53
+ interface ToastPromiseOptions<T> {
54
+ loading: ToastInput
55
+ success: ToastResult<T>
56
+ error: ToastResult<unknown>
57
+ }
58
+
59
+ /** A shown toast: its id, and updating or dismissing it. */
60
+ interface ToastHandle {
61
+ id: string
62
+ update: (options: ToastInput) => void
63
+ dismiss: () => void
64
+ }
65
+
66
+ const isActionButton = (action: unknown): action is ToastActionOptions =>
67
+ typeof action === 'object' && action !== null && !isValidElement(action) && 'label' in action
68
+
69
+ /** Drops `undefined` keys: Base UI merges an update over the toast, so a missing key must not clear a field. */
70
+ function defined<T extends object>(value: T): T {
71
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T
72
+ }
73
+
74
+ /** Options as Base UI's manager takes them: the action button as `actionProps`, the rest in `data`. */
75
+ function toManagerOptions(input: ToastInput) {
76
+ const { action, icon, duration, timeout, ...rest } = typeof input === 'string' ? { title: input } : input
77
+ const button = isActionButton(action)
78
+ return defined({
79
+ ...rest,
80
+ timeout: timeout ?? duration,
81
+ actionProps: button ? { children: action.label, onClick: action.onClick } : undefined,
82
+ data:
83
+ icon !== undefined || (action !== undefined && !button)
84
+ ? { icon, action: button ? undefined : action }
85
+ : undefined,
86
+ })
87
+ }
88
+
89
+ const withType = (input: ToastInput, type: ToastType): ToastOptions =>
90
+ typeof input === 'string' ? { title: input, type } : { ...input, type }
91
+
92
+ const resultOptions = <T>(result: ToastResult<T>) =>
93
+ typeof result === 'function' ? (value: T) => toManagerOptions(result(value)) : toManagerOptions(result)
94
+
95
+ /** Base UI's manager, holding its events until a provider listens. */
96
+ function createQueuedManager(): ToastManager<ToastData> {
97
+ const manager = ToastPrimitive.createToastManager<ToastData>()
98
+ let listeners = 0
99
+ let queue: (() => void)[] = []
100
+ let count = 0
101
+ // No window, no provider to come: drop the call rather than hold it forever.
102
+ const browser = () => typeof window !== 'undefined'
103
+ const whenHeard = (run: () => void) => {
104
+ if (listeners) run()
105
+ else if (browser()) queue.push(run)
106
+ }
107
+
108
+ return {
109
+ ' subscribe': listener => {
110
+ const unsubscribe = manager[' subscribe'](listener)
111
+ listeners++
112
+ const queued = queue
113
+ queue = []
114
+ for (const run of queued) run()
115
+ return () => {
116
+ listeners--
117
+ unsubscribe()
118
+ }
119
+ },
120
+ add: options => {
121
+ // A known id, so the caller can update or dismiss a toast still queued.
122
+ const id = options.id ?? `toast-global-${++count}`
123
+ whenHeard(() => manager.add({ ...options, id }))
124
+ return id
125
+ },
126
+ close: id => whenHeard(() => manager.close(id)),
127
+ update: (id, updates) => whenHeard(() => manager.update(id, updates)),
128
+ promise: (promise, options) => {
129
+ if (listeners) return manager.promise(promise, options)
130
+ // The toast reports a rejection; the caller still gets the original promise.
131
+ if (browser()) queue.push(() => manager.promise(promise, options).catch(() => {}))
132
+ return promise
133
+ },
134
+ }
135
+ }
136
+
137
+ const manager = createQueuedManager()
138
+
139
+ function show(input: ToastInput, type?: ToastType): ToastHandle {
140
+ const options = toManagerOptions(input)
141
+ const id = manager.add({ type: 'info', ...options, ...(type && { type }) })
142
+ return {
143
+ id,
144
+ update: next => manager.update(id, toManagerOptions(next)),
145
+ dismiss: () => manager.close(id),
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Shows a toast from anywhere — an event handler, a fetch wrapper, code
151
+ * outside React — in the `Toast` mounted with `manager={toast}`. Returns a
152
+ * handle with the id, `update` and `dismiss`.
153
+ */
154
+ const toast = Object.assign((input: ToastInput) => show(input), {
155
+ /** A `success` toast. */
156
+ success: (input: ToastInput) => show(input, 'success'),
157
+ /** An `error` toast. */
158
+ error: (input: ToastInput) => show(input, 'error'),
159
+ /** A `warning` toast. */
160
+ warning: (input: ToastInput) => show(input, 'warning'),
161
+ /** An `info` toast. */
162
+ info: (input: ToastInput) => show(input, 'info'),
163
+ /** A spinning `loading` toast. It stays while its type is `loading`; once updated to another type it leaves on its own `timeout` or the `Toast`'s. */
164
+ loading: (input: ToastInput) => show(withType(input, 'loading')),
165
+ /** Runs `promise` behind a `loading` toast that turns into its `success` or `error` toast. Returns the promise. */
166
+ promise: <T>(promise: Promise<T>, options: ToastPromiseOptions<T>) =>
167
+ manager.promise(promise, {
168
+ loading: toManagerOptions(options.loading),
169
+ success: resultOptions(options.success),
170
+ error: resultOptions(options.error),
171
+ }),
172
+ /** Changes a toast that is showing. */
173
+ update: (id: string, input: ToastInput) => manager.update(id, toManagerOptions(input)),
174
+ /** Closes one toast, or every toast without an id. */
175
+ dismiss: (id?: string) => manager.close(id),
176
+ // Base UI's provider subscribes through this key, so `toast` itself is what `<Toast manager>` takes.
177
+ ' subscribe': manager[' subscribe'],
178
+ })
179
+
180
+ /** What `<Toast manager>` takes: the global `toast`, or a manager from Base UI's `Toast.createToastManager()`. */
181
+ type ToastSource = Pick<ToastManager, ' subscribe'>
182
+
183
+ export type {
184
+ ToastActionOptions,
185
+ ToastData,
186
+ ToastHandle,
187
+ ToastInput,
188
+ ToastOptions,
189
+ ToastPromiseOptions,
190
+ ToastSource,
191
+ ToastType,
192
+ }
193
+ export { toast, toManagerOptions }