@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,95 @@
1
+ import Avatar from './index'
2
+ import SizeProvider from '../../primitives/sizes'
3
+ import { Building2 } from 'lucide-react'
4
+
5
+ # Picture and fallback
6
+
7
+ The picture fades in once it has loaded. Without one, or when it fails, initials (or a person glyph) show on a neutral ground.
8
+
9
+ <div className="flex items-center gap-3">
10
+ <Avatar>
11
+ <Avatar.Image alt="Ada Lovelace" src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'><radialGradient id='a' cx='.3' cy='.25' r='1'><stop stop-color='%23F5C6A5'/><stop offset='1' stop-color='%236B97FF'/></radialGradient><rect width='1' height='1' fill='url(%23a)'/></svg>" />
12
+ <Avatar.Fallback>AL</Avatar.Fallback>
13
+ </Avatar>
14
+ <Avatar>
15
+ <Avatar.Image alt="Grace Hopper" src="https://invalid.example/missing.png" />
16
+ <Avatar.Fallback>GH</Avatar.Fallback>
17
+ </Avatar>
18
+ <Avatar>
19
+ <Avatar.Fallback />
20
+ </Avatar>
21
+ </div>
22
+
23
+ # Pale pictures
24
+
25
+ A 1px inset edge keeps a light picture from dissolving into the surface.
26
+
27
+ <div className="flex items-center gap-3">
28
+ <Avatar>
29
+ <Avatar.Image alt="" src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'><rect width='1' height='1' fill='white'/></svg>" />
30
+ <Avatar.Fallback>W</Avatar.Fallback>
31
+ </Avatar>
32
+ <Avatar shape="square">
33
+ <Avatar.Image alt="" src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'><rect width='1' height='1' fill='white'/></svg>" />
34
+ <Avatar.Fallback>W</Avatar.Fallback>
35
+ </Avatar>
36
+ </div>
37
+
38
+ # Square
39
+
40
+ `shape="square"` is for teams, organizations and things. It takes the shape's item radius, so pill mode rounds it further.
41
+
42
+ <div className="flex items-center gap-3">
43
+ <Avatar shape="square">
44
+ <Avatar.Fallback>AC</Avatar.Fallback>
45
+ </Avatar>
46
+ <Avatar shape="square">
47
+ <Avatar.Fallback>
48
+ <Building2 size={16} strokeWidth={1.5} aria-hidden />
49
+ </Avatar.Fallback>
50
+ </Avatar>
51
+ </div>
52
+
53
+ # Sizes
54
+
55
+ One control tall: 36px, and 28px in a compact region or with `size="compact"`. A size off the ladder goes through `className`.
56
+
57
+ <div className="flex flex-col gap-3">
58
+ <div className="flex items-center gap-3">
59
+ <Avatar>
60
+ <Avatar.Fallback>AL</Avatar.Fallback>
61
+ </Avatar>
62
+ <Avatar size="compact">
63
+ <Avatar.Fallback>AL</Avatar.Fallback>
64
+ </Avatar>
65
+ <Avatar size="compact" className="size-5 text-micro">
66
+ <Avatar.Fallback>A</Avatar.Fallback>
67
+ </Avatar>
68
+ </div>
69
+ <SizeProvider size="compact">
70
+ <div className="flex items-center gap-3">
71
+ <Avatar>
72
+ <Avatar.Fallback>GH</Avatar.Fallback>
73
+ </Avatar>
74
+ <Avatar>
75
+ <Avatar.Fallback />
76
+ </Avatar>
77
+ </div>
78
+ </SizeProvider>
79
+ </div>
80
+
81
+ # A stack
82
+
83
+ Overlapping avatars with a ring of the page around each.
84
+
85
+ <div className="flex -space-x-2">
86
+ <Avatar className="ring-2 ring-background">
87
+ <Avatar.Fallback>AL</Avatar.Fallback>
88
+ </Avatar>
89
+ <Avatar className="ring-2 ring-background">
90
+ <Avatar.Fallback>GH</Avatar.Fallback>
91
+ </Avatar>
92
+ <Avatar className="ring-2 ring-background">
93
+ <Avatar.Fallback>+3</Avatar.Fallback>
94
+ </Avatar>
95
+ </div>
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): a person's or a team's
3
+ * picture with a fallback, on Base UI's Avatar (image loading status, the
4
+ * fallback's delay). The 1px inset image edge follows upstream FF's media
5
+ * outline (see Card.Image); the image fades in on the fast tier.
6
+ */
7
+
8
+ import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar'
9
+ import {
10
+ type ComponentPropsWithoutRef,
11
+ type ForwardRefExoticComponent,
12
+ forwardRef,
13
+ type ReactNode,
14
+ type RefAttributes,
15
+ } from 'react'
16
+ import { useIcon } from '../../lib/icon-context'
17
+ import { useShape } from '../../lib/shape-context'
18
+ import { type SizeVariant, useSize } from '../../lib/size-context'
19
+ import { cn } from '../../lib/utils'
20
+ import { SizeProvider } from '../../primitives/sizes'
21
+
22
+ // Literal tables: UnoCSS only generates the classes it can read whole.
23
+ const BOX = { default: 'size-9', compact: 'size-7' }
24
+
25
+ type AvatarImageProps = ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+
27
+ /** The picture. It fades in once loaded; until then, or if it fails, the fallback shows. */
28
+ const AvatarImage = forwardRef<HTMLImageElement, AvatarImageProps>(({ className, ...props }, ref) => (
29
+ <AvatarPrimitive.Image
30
+ ref={ref}
31
+ {...props}
32
+ className={cn(
33
+ // An outline paints over the image (an inset shadow would sit beneath it).
34
+ 'size-full rounded-[inherit] object-cover outline outline-1 -outline-offset-1 outline-image-edge',
35
+ 'transition-opacity duration-fast data-[starting-style]:opacity-0',
36
+ className,
37
+ )}
38
+ />
39
+ ))
40
+
41
+ AvatarImage.displayName = 'AvatarImage'
42
+
43
+ type AvatarFallbackProps = ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
44
+
45
+ /** Initials or an icon, shown while the image loads or when there is none. Empty, it shows a person glyph. */
46
+ const AvatarFallback = forwardRef<HTMLSpanElement, AvatarFallbackProps>(({ className, children, ...props }, ref) => {
47
+ const size = useSize()
48
+ const User = useIcon('user')
49
+ return (
50
+ <AvatarPrimitive.Fallback
51
+ ref={ref}
52
+ {...props}
53
+ className={cn('flex size-full items-center justify-center rounded-[inherit]', className)}
54
+ >
55
+ {children ?? <User size={size.icon} strokeWidth={1.5} aria-hidden />}
56
+ </AvatarPrimitive.Fallback>
57
+ )
58
+ })
59
+
60
+ AvatarFallback.displayName = 'AvatarFallback'
61
+
62
+ interface AvatarProps extends ComponentPropsWithoutRef<'span'> {
63
+ /** Pins the avatar to one step of the size ladder (36px default, 28px compact). Defaults to the surrounding SizeProvider. */
64
+ size?: SizeVariant
65
+ /** A circle for people; a square for teams, organizations and things, which takes the shape's item radius (rounded or pill). Defaults to `"circle"`. */
66
+ shape?: 'circle' | 'square'
67
+ /** Classes for the root: a size off the ladder (`size-5` in a dense row), a ground. */
68
+ className?: string
69
+ /** `Avatar.Image` and `Avatar.Fallback`. */
70
+ children?: ReactNode
71
+ }
72
+
73
+ type AvatarComponent = ForwardRefExoticComponent<AvatarProps & RefAttributes<HTMLSpanElement>> & {
74
+ Image: typeof AvatarImage
75
+ Fallback: typeof AvatarFallback
76
+ }
77
+
78
+ /**
79
+ * A picture of a person or a team, with initials or an icon when there is
80
+ * none.
81
+ *
82
+ * The avatar is one control tall (36px, 28px compact), so it lines up with
83
+ * the rows and buttons beside it, and follows the surrounding SizeProvider.
84
+ * Base UI's Avatar tracks the image: until it has loaded, or when it fails,
85
+ * the fallback shows on a neutral ground (the gray badge's accent); once it
86
+ * lands it fades in over the fallback. A 1px inset edge keeps a pale picture from dissolving into the
87
+ * surface. People are circles; `shape="square"` takes the shape context's
88
+ * item radius for teams and things.
89
+ *
90
+ * Statics:
91
+ * - `Avatar.Image` — the picture: `src`, `alt`, `onLoadingStatusChange`.
92
+ * - `Avatar.Fallback` — initials or an icon; empty, a person glyph. `delay`
93
+ * (ms) holds it back so a fast image never flashes it.
94
+ *
95
+ * @example {@include ./examples.mdx}
96
+ */
97
+ const Avatar = forwardRef<HTMLSpanElement, AvatarProps>(
98
+ ({ size: sizeProp, shape = 'circle', className, children, ...props }, ref) => {
99
+ const size = useSize(sizeProp)
100
+ const shapes = useShape()
101
+ const avatar = (
102
+ <AvatarPrimitive.Root
103
+ ref={ref}
104
+ {...props}
105
+ className={cn(
106
+ 'relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden align-middle',
107
+ 'bg-accent text-foreground weight-medium',
108
+ BOX[size.variant],
109
+ size.text,
110
+ shape === 'circle' ? 'rounded-full' : shapes.item,
111
+ className,
112
+ )}
113
+ >
114
+ {children}
115
+ </AvatarPrimitive.Root>
116
+ )
117
+ // A size prop pins the fallback's glyph too.
118
+ return sizeProp ? <SizeProvider size={sizeProp}>{avatar}</SizeProvider> : avatar
119
+ },
120
+ ) as AvatarComponent
121
+
122
+ Avatar.displayName = 'Avatar'
123
+
124
+ Object.assign(Avatar, { Image: AvatarImage, Fallback: AvatarFallback })
125
+
126
+ export type { AvatarFallbackProps, AvatarImageProps, AvatarProps }
127
+ export { Avatar, AvatarFallback, AvatarImage }
128
+
129
+ export default Avatar
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): the docs stage of the
3
+ * Badge page's change morph. A Badge that steps through a list of props each
4
+ * time its button is pressed, so the example stays hook-free.
5
+ */
6
+
7
+ import { useState } from 'react'
8
+ import Button from '../button'
9
+ import Badge, { type BadgeProps } from './index'
10
+
11
+ interface ChangeDemoProps {
12
+ /** The badge's props, one entry per press; after the last it starts over. */
13
+ steps: BadgeProps[]
14
+ /** Label of the button that steps. */
15
+ label?: string
16
+ }
17
+
18
+ export default function ChangeDemo({ steps, label = 'Next' }: ChangeDemoProps) {
19
+ const [at, setAt] = useState(0)
20
+
21
+ return (
22
+ <div className="flex items-center gap-3">
23
+ <Button variant="tertiary" size="compact" onClick={() => setAt((at + 1) % steps.length)}>
24
+ {label}
25
+ </Button>
26
+ <Badge {...steps[at]} />
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,120 @@
1
+ import Badge from './index'
2
+ import ChangeDemo from './change-demo'
3
+
4
+ # Solid
5
+
6
+ The default variant: a tinted background in the badge's color.
7
+
8
+ <div className="flex flex-wrap items-center gap-2">
9
+ <Badge color="violet">Fiction</Badge>
10
+ <Badge color="amber">Science</Badge>
11
+ <Badge color="green">Philosophy</Badge>
12
+ <Badge color="blue">History</Badge>
13
+ <Badge color="rose">Poetry</Badge>
14
+ </div>
15
+
16
+ # Dot
17
+
18
+ `variant="dot"` keeps a neutral bordered badge and shows the color as an
19
+ indicator dot.
20
+
21
+ <div className="flex flex-wrap items-center gap-2">
22
+ <Badge variant="dot" color="violet">Fiction</Badge>
23
+ <Badge variant="dot" color="amber">Science</Badge>
24
+ <Badge variant="dot" color="green">Philosophy</Badge>
25
+ <Badge variant="dot" color="blue">History</Badge>
26
+ <Badge variant="dot" color="rose">Poetry</Badge>
27
+ </div>
28
+
29
+ # Outline
30
+
31
+ `variant="outline"` is the dot badge's neutral frame without the dot: a label
32
+ that carries no status.
33
+
34
+ <div className="flex flex-wrap items-center gap-2">
35
+ <Badge variant="outline">Draft</Badge>
36
+ <Badge variant="outline">v2.4</Badge>
37
+ <Badge variant="outline" size="compact">Archived</Badge>
38
+ </div>
39
+
40
+ # Status mapping
41
+
42
+ Hosts with a shadcn-style badge map its variants onto a hue: default and
43
+ secondary → `gray`, destructive → `red`, success → `green`, outline →
44
+ `variant="outline"`. The tint stays a wash with foreground text, never a solid
45
+ fill.
46
+
47
+ <div className="flex flex-wrap items-center gap-2">
48
+ <Badge color="gray">Default</Badge>
49
+ <Badge color="gray">Secondary</Badge>
50
+ <Badge color="red">Destructive</Badge>
51
+ <Badge color="green">Success</Badge>
52
+ <Badge variant="outline">Outline</Badge>
53
+ </div>
54
+
55
+ # Colors
56
+
57
+ All seventeen palette colors, solid and dot.
58
+
59
+ <div className="flex flex-col gap-3">
60
+ <div className="flex flex-wrap items-center gap-2">
61
+ {['gray', 'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'].map((c) => (
62
+ <Badge key={c} color={c}>
63
+ {c.charAt(0).toUpperCase() + c.slice(1)}
64
+ </Badge>
65
+ ))}
66
+ </div>
67
+ <div className="flex flex-wrap items-center gap-2">
68
+ {['gray', 'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'].map((c) => (
69
+ <Badge key={c} variant="dot" color={c}>
70
+ {c.charAt(0).toUpperCase() + c.slice(1)}
71
+ </Badge>
72
+ ))}
73
+ </div>
74
+ </div>
75
+
76
+ # Compact
77
+
78
+ `size="compact"` steps the badge down the size ladder (20px tall, 11px
79
+ text) for dense rows and table cells.
80
+
81
+ <div className="flex flex-wrap items-center gap-2">
82
+ <Badge size="compact" color="blue">History</Badge>
83
+ <Badge size="compact" variant="dot" color="green">Philosophy</Badge>
84
+ <Badge size="compact">Draft</Badge>
85
+ </div>
86
+
87
+ # Changes
88
+
89
+ Press a button. A new label springs the badge to its new width while the
90
+ text crossfades, a new color fades in, and the dot slides in or out with
91
+ the variant. `ChangeDemo` is a docs stage: a Badge that steps through its
92
+ `steps` on each press.
93
+
94
+ <div className="flex w-48 flex-col items-start gap-3">
95
+ <ChangeDemo
96
+ label="Add one"
97
+ steps={[
98
+ { color: 'red', children: 8 },
99
+ { color: 'red', children: 9 },
100
+ { color: 'red', children: 10 },
101
+ { color: 'red', children: 99 },
102
+ { color: 'red', children: '99+' },
103
+ ]}
104
+ />
105
+ <ChangeDemo
106
+ label="Advance"
107
+ steps={[
108
+ { children: 'Draft' },
109
+ { color: 'amber', children: 'In review' },
110
+ { color: 'green', children: 'Published' },
111
+ ]}
112
+ />
113
+ <ChangeDemo
114
+ label="Sync"
115
+ steps={[
116
+ { color: 'blue', children: 'Syncing' },
117
+ { variant: 'dot', color: 'green', children: 'Synced' },
118
+ ]}
119
+ />
120
+ </div>
@@ -0,0 +1,225 @@
1
+ /*
2
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
3
+ * `registry/default/badge.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
4
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
5
+ * Local modifications:
6
+ * - `"use client"` directive dropped (no RSC here).
7
+ * - `@/lib/*` imports rewritten to relative `../../lib/*` paths.
8
+ * - `BadgeProps` re-declares `variant` (inherited from cva's VariantProps
9
+ * upstream) and carries the FF docs API-table descriptions on every prop,
10
+ * so modo's parser lists them; types are unchanged for callers.
11
+ * - TSDoc with the FF docs page's examples added above the component;
12
+ * `export default Badge` added.
13
+ * - Styling reads DS tokens (AGENTS.md styling): `text-[Npx]` →
14
+ * `text-<role>[-compact]`; `font-medium` → `weight-medium`; literal colors →
15
+ * color tokens.
16
+ * - Changes animate (local, in the morph layer's language): a new label or
17
+ * variant springs the badge from its old width to its new one on
18
+ * `spring.moderate` (the content clipped on the way) while the label
19
+ * crossfades with Toast's blur; a color fades over `duration-moderate`, and
20
+ * the dot scales in and out with the label sliding to make room. The content
21
+ * sits in one inner span that carries the morph, so the root keeps the
22
+ * caller's `style`.
23
+ * - `badgeColors`, `badgeVariants` and the size aliases moved to `variants.ts`
24
+ * (server-callable, no JSX or hooks) and re-exported from here.
25
+ * - Variant `outline` (local): the dot badge's neutral hairline frame without
26
+ * the dot; it draws no color, so `color` has no effect on it.
27
+ */
28
+
29
+ import type { VariantProps } from 'class-variance-authority'
30
+ import { AnimatePresence, animate, motion } from 'motion/react'
31
+ import { forwardRef, type HTMLAttributes, type ReactNode, useLayoutEffect, useRef } from 'react'
32
+ import { useReduceMotion } from '../../lib/reduced-motion'
33
+ import { useShape } from '../../lib/shape-context'
34
+ import { useSizeVariant } from '../../lib/size-context'
35
+ import { spring } from '../../lib/springs'
36
+ import { cn } from '../../lib/utils'
37
+ import {
38
+ type BadgeColor,
39
+ type BadgeSize,
40
+ type BadgeSizeCanonical,
41
+ type BadgeVariant,
42
+ badgeColors,
43
+ badgeVariants,
44
+ legacySizeAliases,
45
+ } from './variants'
46
+
47
+ const { exit: _exit, ...enter } = spring.moderate
48
+
49
+ /** The label as text, when it is text: the key its crossfade runs on. */
50
+ function textOf(node: ReactNode) {
51
+ const parts = Array.isArray(node) ? node : [node]
52
+ if (parts.every(part => typeof part === 'string' || typeof part === 'number')) return parts.join('')
53
+ }
54
+
55
+ /* Width morph (local): when the label or the variant changes, the content
56
+ span springs from its old width to its new one instead of jumping, and the
57
+ badge follows it. By the time the change commits the span already has its
58
+ new width, so a ResizeObserver keeps the resting one. At rest the inline
59
+ width is dropped. */
60
+ function useWidthMorph(change: string) {
61
+ const content = useRef<HTMLSpanElement>(null)
62
+ const rest = useRef(0)
63
+ const live = useRef<number | null>(null)
64
+ const reduced = useReduceMotion()
65
+
66
+ useLayoutEffect(() => {
67
+ const el = content.current
68
+ if (!el) return
69
+ const observer = new ResizeObserver(() => {
70
+ if (live.current === null) rest.current = el.offsetWidth
71
+ })
72
+ observer.observe(el)
73
+ return () => observer.disconnect()
74
+ }, [])
75
+
76
+ useLayoutEffect(() => {
77
+ const el = content.current
78
+ const from = live.current ?? rest.current
79
+ if (!el || !from) return
80
+ el.style.width = ''
81
+ const to = el.offsetWidth
82
+ rest.current = to
83
+ live.current = null
84
+ if (reduced || from === to) return
85
+ const write = (width: number) => {
86
+ live.current = width
87
+ el.style.width = `${width}px`
88
+ }
89
+ write(from)
90
+ const controls = animate(from, to, {
91
+ ...enter,
92
+ onUpdate: write,
93
+ onComplete: () => {
94
+ live.current = null
95
+ el.style.width = ''
96
+ },
97
+ })
98
+ return () => controls.stop()
99
+ }, [change, reduced])
100
+
101
+ return content
102
+ }
103
+
104
+ interface BadgeProps
105
+ extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'>,
106
+ Omit<VariantProps<typeof badgeVariants>, 'size'> {
107
+ /** Visual style. Solid uses a tinted background; dot shows a colored indicator; outline is the dot badge's neutral hairline frame without the dot (it ignores `color`). Defaults to `"solid"`. */
108
+ variant?: 'solid' | 'dot' | 'outline'
109
+ /** Step on the size ladder (see Sizes). Legacy sm/md/lg values resolve as aliases (sm → compact; md/lg → default). Defaults to the surrounding SizeProvider, else `"default"`. */
110
+ size?: 'default' | 'compact' | 'sm' | 'md' | 'lg'
111
+ /** Color from the Tailwind palette: gray, red, orange, amber, yellow, lime, green, emerald, teal, cyan, blue, indigo, violet, purple, fuchsia, pink, rose. Defaults to `"gray"`. */
112
+ color?: BadgeColor
113
+ }
114
+
115
+ /**
116
+ * Compact label for status, category, or metadata. Supports solid, dot and
117
+ * outline variants with Tailwind colors.
118
+ *
119
+ * Solid badges tint their background with the chosen color mixed into the
120
+ * page background (gray uses the accent token); dot badges keep a neutral
121
+ * border and show the color as a small indicator; outline badges are the
122
+ * neutral frame alone, for a label that carries no status. The badge rides the size
123
+ * ladder — `size` pins it, otherwise it follows the surrounding SizeProvider
124
+ * — and takes its corner radius from the shape context. Changes animate: a
125
+ * new label or variant springs the width while the label crossfades, and a
126
+ * new color fades in. The palette is also exported as `badgeColors`, the class
127
+ * recipe as `badgeVariants` (both callable on the server).
128
+ *
129
+ * Status labels pick a hue rather than a fill of their own: a destructive or
130
+ * error label is `color="red"`, a success label `color="green"`, a secondary
131
+ * or default one `color="gray"`, and a neutral outlined one
132
+ * `variant="outline"` (see the Status mapping example).
133
+ *
134
+ * @example {@include ./examples.mdx}
135
+ */
136
+ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
137
+ ({ className, variant = 'solid', size: sizeProp, color = 'gray', children, style, ...props }, ref) => {
138
+ const shape = useShape()
139
+ // Resolve the size: explicit prop (legacy aliases mapped onto the
140
+ // canonical ladder) > surrounding SizeProvider > default.
141
+ const contextSize = useSizeVariant()
142
+ const size: BadgeSizeCanonical = sizeProp
143
+ ? (legacySizeAliases[sizeProp] ?? (sizeProp as BadgeSizeCanonical))
144
+ : contextSize === 'compact'
145
+ ? 'compact'
146
+ : 'default'
147
+ const colorValue = badgeColors[color]
148
+ const isSolid = variant === 'solid'
149
+ const dotSize = size === 'compact' ? 6 : 7
150
+
151
+ const colorStyle = isSolid
152
+ ? color === 'gray'
153
+ ? { backgroundColor: 'var(--accent)', color: 'var(--foreground)' }
154
+ : {
155
+ color: 'var(--foreground)',
156
+ backgroundColor: `color-mix(in srgb, ${colorValue} 15%, var(--background))`,
157
+ }
158
+ : {}
159
+
160
+ const dotColor = color === 'gray' ? 'var(--muted-foreground)' : colorValue
161
+ const label = textOf(children)
162
+ const content = useWidthMorph(`${variant} ${size} ${label}`)
163
+
164
+ return (
165
+ <span
166
+ ref={ref}
167
+ className={cn(
168
+ badgeVariants({ variant, size }),
169
+ shape.item,
170
+ 'transition-[background-color] duration-moderate',
171
+ className,
172
+ )}
173
+ style={{ ...colorStyle, ...style }}
174
+ {...props}
175
+ >
176
+ {/* The morphing box: it may shrink below its content while the width
177
+ springs (min-w-0), clipping it sideways only, and it positions
178
+ whatever pops out of the flow on its way out. */}
179
+ <span ref={content} className="relative inline-flex min-w-0 items-center gap-[inherit] overflow-x-clip">
180
+ <AnimatePresence mode="popLayout" initial={false}>
181
+ {variant === 'dot' && (
182
+ <motion.span
183
+ key="dot"
184
+ className="shrink-0 rounded-full transition-[background-color] duration-moderate"
185
+ style={{
186
+ width: dotSize,
187
+ height: dotSize,
188
+ backgroundColor: dotColor,
189
+ }}
190
+ initial={{ opacity: 0, scale: 0 }}
191
+ animate={{ opacity: 1, scale: 1 }}
192
+ exit={{ opacity: 0, scale: 0, transition: spring.moderate.exit }}
193
+ transition={enter}
194
+ />
195
+ )}
196
+ </AnimatePresence>
197
+ <AnimatePresence mode="popLayout" initial={false}>
198
+ {/* text-box needs a block container — the badge root is a flex
199
+ container, so the label gets its own span. Height is fixed
200
+ (h-*), so trimming only recenters the letterforms. */}
201
+ <motion.span
202
+ key={label ?? 'label'}
203
+ layout="position"
204
+ className="[text-box:trim-both_cap_alphabetic]"
205
+ initial={{ opacity: 0, filter: 'blur(4px)' }}
206
+ // At rest the label keeps no filter, which would make it its own stacking context.
207
+ animate={{ opacity: 1, filter: 'blur(0px)', transitionEnd: { filter: 'none' } }}
208
+ exit={{ opacity: 0, filter: 'blur(4px)', transition: spring.moderate.exit }}
209
+ transition={enter}
210
+ >
211
+ {children}
212
+ </motion.span>
213
+ </AnimatePresence>
214
+ </span>
215
+ </span>
216
+ )
217
+ },
218
+ )
219
+
220
+ Badge.displayName = 'Badge'
221
+
222
+ export type { BadgeColor, BadgeProps, BadgeSize, BadgeVariant }
223
+ export { Badge, badgeColors, badgeVariants }
224
+
225
+ export default Badge
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
3
+ * `registry/default/badge.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
4
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
5
+ * Local modifications:
6
+ * - The palette (`badgeColors`), the class recipe (`badgeVariants`) and the size
7
+ * aliases, split out of `index.tsx` so server code can call them: no JSX, hooks
8
+ * or contexts here. `index.tsx` re-exports them.
9
+ * - Styling reads DS tokens (see `index.tsx`).
10
+ * - Variant `outline` (local): the dot badge's neutral hairline frame without the dot.
11
+ */
12
+
13
+ import { cva } from 'class-variance-authority'
14
+
15
+ const badgeColors = {
16
+ gray: 'var(--badge-gray)',
17
+ red: 'var(--badge-red)',
18
+ orange: 'var(--badge-orange)',
19
+ amber: 'var(--badge-amber)',
20
+ yellow: 'var(--badge-yellow)',
21
+ lime: 'var(--badge-lime)',
22
+ green: 'var(--badge-green)',
23
+ emerald: 'var(--badge-emerald)',
24
+ teal: 'var(--badge-teal)',
25
+ cyan: 'var(--badge-cyan)',
26
+ blue: 'var(--badge-blue)',
27
+ indigo: 'var(--badge-indigo)',
28
+ violet: 'var(--badge-violet)',
29
+ purple: 'var(--badge-purple)',
30
+ fuchsia: 'var(--badge-fuchsia)',
31
+ pink: 'var(--badge-pink)',
32
+ rose: 'var(--badge-rose)',
33
+ } as const
34
+
35
+ type BadgeColor = keyof typeof badgeColors
36
+
37
+ type BadgeVariant = 'solid' | 'dot' | 'outline'
38
+
39
+ const badgeVariants = cva('inline-flex items-center weight-medium whitespace-nowrap', {
40
+ variants: {
41
+ variant: {
42
+ solid: '',
43
+ dot: 'border border-border text-foreground',
44
+ // The dot badge without its dot: a neutral hairline label.
45
+ outline: 'border border-border text-foreground',
46
+ },
47
+ // The two-step size ladder shared by every control — see /docs/sizes.
48
+ size: {
49
+ default: 'h-6 px-2.5 text-caption gap-1.5',
50
+ compact: 'h-5 px-2 text-caption-compact gap-1',
51
+ },
52
+ },
53
+ defaultVariants: {
54
+ variant: 'solid',
55
+ size: 'default',
56
+ },
57
+ })
58
+
59
+ type BadgeSizeCanonical = 'default' | 'compact'
60
+
61
+ /** Public size values: the canonical two-size scale plus the pre-sizes-system
62
+ * aliases, kept so existing call sites keep compiling. Aliases resolve onto
63
+ * the canonical ladder (sm → compact; md/lg → default). */
64
+ type BadgeSize = BadgeSizeCanonical | 'sm' | 'md' | 'lg'
65
+
66
+ const legacySizeAliases: Partial<Record<BadgeSize, BadgeSizeCanonical>> = {
67
+ sm: 'compact',
68
+ md: 'default',
69
+ lg: 'default',
70
+ }
71
+
72
+ export type { BadgeColor, BadgeSize, BadgeSizeCanonical, BadgeVariant }
73
+ export { badgeColors, badgeVariants, legacySizeAliases }