@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,48 @@
1
+ // biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
2
+ /*
3
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
4
+ * `registry/blocks/sidebar-app/search-field.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
6
+ * Local modifications:
7
+ * - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
8
+ * `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
9
+ * - Styling reads DS tokens (AGENTS.md styling): `text-[11px]` → `text-micro`;
10
+ * `duration-80` → `duration-fast`.
11
+ */
12
+
13
+ import { type ComponentProps } from 'react'
14
+ import { SidebarInput } from '../../components/navigation/sidebar'
15
+ import { useIcon } from '../../lib/icon-context'
16
+ import { useSize } from '../../lib/size-context'
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // The header's search field, on the menu rows' own rhythm. The leading icon
20
+ // sits on the rows' 16px leading axis; pl-8 starts the text on the rows'
21
+ // 32px text axis; and the shortcut chip waits at the trailing edge, revealed
22
+ // on hover/focus — the placeholder owns the field at rest.
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export interface SidebarSearchFieldProps extends Omit<ComponentProps<typeof SidebarInput>, 'className'> {
26
+ /** Keystroke shown in the trailing chip. Pass null to drop the chip. */
27
+ shortcut?: string | null
28
+ }
29
+
30
+ export function SidebarSearchField({ placeholder = 'Search…', shortcut = '⌘K', ...props }: SidebarSearchFieldProps) {
31
+ const iconSize = useSize().icon
32
+ const SearchIcon = useIcon('search')
33
+ return (
34
+ <div className="group/search relative">
35
+ <SearchIcon
36
+ size={iconSize}
37
+ strokeWidth={1.5}
38
+ className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground"
39
+ />
40
+ <SidebarInput placeholder={placeholder} aria-label="Search" className="pl-8 pr-12" {...props} />
41
+ {shortcut && (
42
+ <kbd className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 font-sans text-micro text-muted-foreground opacity-0 transition-opacity duration-fast group-hover/search:opacity-100 group-focus-within/search:opacity-100">
43
+ {shortcut}
44
+ </kbd>
45
+ )}
46
+ </div>
47
+ )
48
+ }
@@ -0,0 +1,67 @@
1
+ // biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
2
+ /*
3
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
4
+ * `registry/blocks/sidebar-user-footer.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
6
+ * Local modifications:
7
+ * - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
8
+ * `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
9
+ * - Styling reads DS tokens (AGENTS.md styling): `text-[13px]` → `text-body`.
10
+ */
11
+
12
+ import { type ReactNode } from 'react'
13
+ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '../../components/navigation/sidebar'
14
+ import { DropdownContent, DropdownMenu, DropdownTrigger } from '../../components/overlays/dropdown'
15
+ import { useIcon } from '../../lib/icon-context'
16
+ import { useSize } from '../../lib/size-context'
17
+ import { cn } from '../../lib/utils'
18
+ import { SIDEBAR_MENU_POPUP } from './menu-grid'
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Footer user row: identity anchors the sidebar's outer edge. The 20px
22
+ // avatar pulls -ml-0.5 to centre on the rows' leading icon axis; the
23
+ // chevron rides a 24px slot pulled -mr-0.5 onto the trailing action axis —
24
+ // the same axes every other sidebar row uses. Its menu opens upward on the
25
+ // shared sidebar menu grid, so labels and trailing glyphs line up with the
26
+ // trigger row exactly.
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export interface SidebarUserFooterProps {
30
+ /** Display name shown in the row. */
31
+ name: ReactNode
32
+ /** 20px avatar — e.g. <img className="size-5 rounded-full" …/>. The row
33
+ * positions it on the leading icon axis; the element owns its look. */
34
+ avatar: ReactNode
35
+ /** Dropdown content (MenuItem rows). */
36
+ menu: ReactNode
37
+ /** Extra classes for the wrapping SidebarMenu — pass "min-w-0 flex-1" when
38
+ * the row shares a horizontal footer line with icon buttons. */
39
+ className?: string
40
+ }
41
+
42
+ export function SidebarUserFooter({ name, avatar, menu, className }: SidebarUserFooterProps) {
43
+ const iconSize = useSize().icon
44
+ const ChevronsUpDown = useIcon('chevrons-up-down')
45
+ return (
46
+ <SidebarMenu aria-label="User" className={cn(className)}>
47
+ <SidebarMenuItem>
48
+ <DropdownMenu>
49
+ <DropdownTrigger
50
+ render={
51
+ <SidebarMenuButton aria-label="Open user menu">
52
+ <span className="-ml-0.5 -mr-0.5 flex size-5 shrink-0 items-center justify-center">{avatar}</span>
53
+ <span className="min-w-0 truncate text-body text-foreground">{name}</span>
54
+ <span className="ml-auto -mr-0.5 flex size-6 shrink-0 items-center justify-center">
55
+ <ChevronsUpDown size={iconSize} strokeWidth={1.5} className="text-muted-foreground" />
56
+ </span>
57
+ </SidebarMenuButton>
58
+ }
59
+ />
60
+ <DropdownContent className={SIDEBAR_MENU_POPUP} side="top" align="start" sideOffset={6}>
61
+ {menu}
62
+ </DropdownContent>
63
+ </DropdownMenu>
64
+ </SidebarMenuItem>
65
+ </SidebarMenu>
66
+ )
67
+ }
@@ -0,0 +1,129 @@
1
+ // biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
2
+ /*
3
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
4
+ * `registry/blocks/sidebar-workspace-header.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
5
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
6
+ * Local modifications:
7
+ * - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
8
+ * `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
9
+ * - Styling reads DS tokens (AGENTS.md styling): inline
10
+ * `fontVariationSettings` → `weight-semibold`; `text-[13px]` / `text-[10px]`
11
+ * → `text-body` / `text-micro-compact`; `duration-80` → `duration-fast`.
12
+ * - `defaultCheckedIndex` passed through to the menu, so a switcher keeps its
13
+ * own pick (the Dropdown's uncontrolled selection).
14
+ * - No peek trigger: the sidebar opens on hover in flow (the provider's
15
+ * `openOnHover`), the topbar's trigger stays beside it, so the tile no
16
+ * longer cross-fades with a trigger over the row.
17
+ */
18
+
19
+ import { type ReactNode } from 'react'
20
+ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '../../components/navigation/sidebar'
21
+ import { DropdownContent, DropdownMenu, DropdownTrigger } from '../../components/overlays/dropdown'
22
+ import { useIcon } from '../../lib/icon-context'
23
+ import { useShape } from '../../lib/shape-context'
24
+ import { useSize } from '../../lib/size-context'
25
+ import { cn } from '../../lib/utils'
26
+ import { SIDEBAR_MENU_POPUP } from './menu-grid'
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Workspace brand row for a sidebar header. The row's pl-8 keeps the name on
30
+ // the rows' 32px text axis, the tile in the leading slot.
31
+ // ---------------------------------------------------------------------------
32
+
33
+ export interface SidebarWorkspaceHeaderProps {
34
+ /** Workspace or product name shown in the row. */
35
+ name: ReactNode
36
+ /** 20px mark for the leading slot — a letter tile (see WorkspaceTile), a
37
+ * logo, an avatar. The header positions and cross-fades it; the mark owns
38
+ * its own colors and rounding. */
39
+ tile: ReactNode
40
+ /** Dropdown content (MenuItem rows). Omit to render a non-interactive logo
41
+ * lockup instead of a workspace switcher. */
42
+ menu?: ReactNode
43
+ /** Index of the checked menu row, forwarded to DropdownContent. */
44
+ checkedIndex?: number
45
+ /** Initially checked menu row, for a switcher that keeps its own pick. */
46
+ defaultCheckedIndex?: number
47
+ }
48
+
49
+ export function SidebarWorkspaceHeader({
50
+ name,
51
+ tile,
52
+ menu,
53
+ checkedIndex,
54
+ defaultCheckedIndex,
55
+ }: SidebarWorkspaceHeaderProps) {
56
+ const iconSize = useSize().icon
57
+ const ChevronDown = useIcon('chevron-down')
58
+
59
+ // The tile sits absolutely in the row's leading slot — 20px at left-1.5
60
+ // centres it on the rows' 16px leading icon axis.
61
+ const tileSlot = (
62
+ <span aria-hidden className="pointer-events-none absolute left-1.5 top-1/2 -translate-y-1/2">
63
+ {tile}
64
+ </span>
65
+ )
66
+ const nameSpan = <span className="min-w-0 truncate text-body weight-semibold text-foreground">{name}</span>
67
+
68
+ if (!menu) {
69
+ // Not interactive, so it renders outside SidebarMenu — a menu row would
70
+ // track the traveling hover background.
71
+ return (
72
+ <div className="relative flex h-8 items-center pl-8 pr-2">
73
+ {tileSlot}
74
+ {nameSpan}
75
+ </div>
76
+ )
77
+ }
78
+ return (
79
+ // @container: the row hides its dropdown chevron once it gets too narrow
80
+ // to show a useful slice of the name (squeezed by trailing header actions
81
+ // or a mid-drag width) — the text keeps whatever room is left.
82
+ <SidebarMenu aria-label="Workspace" className="@container">
83
+ <SidebarMenuItem>
84
+ <DropdownMenu>
85
+ <DropdownTrigger
86
+ render={
87
+ <SidebarMenuButton aria-label="Switch workspace" className="pl-8">
88
+ {tileSlot}
89
+ {nameSpan}
90
+ <span className="ml-auto inline-flex @max-[7rem]:hidden">
91
+ <ChevronDown size={iconSize} strokeWidth={1.5} className="text-muted-foreground" />
92
+ </span>
93
+ </SidebarMenuButton>
94
+ }
95
+ />
96
+ {/* Trigger-width popup on the shared sidebar menu grid: items start
97
+ at the row's edge, icon slots land on the leading axis, and the
98
+ check sits on the chevron's vertical axis. */}
99
+ <DropdownContent
100
+ className={SIDEBAR_MENU_POPUP}
101
+ align="start"
102
+ sideOffset={4}
103
+ checkedIndex={checkedIndex}
104
+ defaultCheckedIndex={defaultCheckedIndex}
105
+ >
106
+ {menu}
107
+ </DropdownContent>
108
+ </DropdownMenu>
109
+ </SidebarMenuItem>
110
+ </SidebarMenu>
111
+ )
112
+ }
113
+
114
+ /** The 20px letter-tile treatment the switcher's trigger and its menu rows
115
+ * share — squared to the shape system, semibold 10px glyph. */
116
+ export function WorkspaceTile({ children, className }: { children: ReactNode; className?: string }) {
117
+ const shape = useShape()
118
+ return (
119
+ <span
120
+ className={cn(
121
+ 'flex size-5 shrink-0 items-center justify-center bg-foreground text-micro-compact weight-semibold text-background',
122
+ shape.bgRadius >= 20 ? 'rounded-full' : 'rounded-md',
123
+ className,
124
+ )}
125
+ >
126
+ {children}
127
+ </span>
128
+ )
129
+ }
@@ -0,0 +1,124 @@
1
+ import InputMessage from './index'
2
+ import Button from '../../button'
3
+ import { ChevronDown, Plus } from 'lucide-react'
4
+
5
+ # Basic
6
+
7
+ No props at all: the composer keeps its own draft, grows as you type and
8
+ clears itself on Enter.
9
+
10
+ <div className="w-full max-w-xl">
11
+ <InputMessage />
12
+ </div>
13
+
14
+ # Suggestions
15
+
16
+ The ghost placeholder renders with a Tab keycap — Tab fills it into the
17
+ composer. ArrowDown/ArrowUp walk the list below (focus stays in the
18
+ textarea) and Enter or click fills the highlighted prompt; the first row
19
+ shows a ↓ hint until a row is highlighted. Typing collapses the list.
20
+
21
+ <div className="w-full max-w-xl">
22
+ <InputMessage
23
+ placeholderSuggestion="Why is every other input box so stiff?"
24
+ suggestions={[
25
+ 'What is Fluid Functionalism about?',
26
+ 'How does Micka tune the springs behind these animations?',
27
+ 'Install the InputMessage component in my project',
28
+ 'Draft a short thank-you note to Micka for the library',
29
+ ]}
30
+ />
31
+ </div>
32
+
33
+ # Attachments
34
+
35
+ `defaultFiles` seeds the composer and turns attachment behavior on: drop a
36
+ PNG, JPEG or PDF anywhere on the panel, or use the render-fn slot's
37
+ `openFilePicker`. Images preview with object-cover, PDFs render their
38
+ first page, anything else falls back to a document glyph — hover a tile
39
+ for its × button.
40
+
41
+ <div className="w-full max-w-xl">
42
+ <InputMessage
43
+ defaultFiles={[
44
+ new File(['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><rect width="80" height="80" fill="#6B97FF" /></svg>'], 'swatch.svg', { type: 'image/svg+xml' }),
45
+ new File(['Quarterly notes'], 'notes.txt', { type: 'text/plain' }),
46
+ ]}
47
+ leftSlot={({ openFilePicker }) => (
48
+ <Button variant="ghost" size="icon-compact" aria-label="Attach" onClick={() => openFilePicker()}>
49
+ <Plus />
50
+ </Button>
51
+ )}
52
+ />
53
+ </div>
54
+
55
+ # Queued messages
56
+
57
+ `defaultStatus="streaming"` plus `defaultQueue` runs the composer as if a
58
+ response were in flight: the send button is a Queue action, so a typed
59
+ message joins the rows above instead of sending. Drag a row (or Alt+↑/↓)
60
+ to reorder, double-click to pull it back into the composer, × to drop it.
61
+ Clear the draft and the button becomes Stop — pressing it ends the
62
+ response, and that streaming→idle edge dispatches the row at the top.
63
+
64
+ <div className="w-full max-w-xl">
65
+ <InputMessage
66
+ defaultStatus="streaming"
67
+ defaultQueue={[
68
+ { id: 'q1', text: 'Also add a dark-mode screenshot', files: [] },
69
+ { id: 'q2', text: 'And link the changelog entry', files: [] },
70
+ ]}
71
+ />
72
+ </div>
73
+
74
+ # Left Slot Only
75
+
76
+ `leftSlot` takes the bottom-left action area — an attach button, a model
77
+ switcher, a mode toggle.
78
+
79
+ <div className="w-full max-w-xl">
80
+ <InputMessage
81
+ leftSlot={
82
+ <Button variant="ghost" size="icon-compact" aria-label="Attach">
83
+ <Plus />
84
+ </Button>
85
+ }
86
+ />
87
+ </div>
88
+
89
+ # Right Slot Only
90
+
91
+ `rightSlot` sits in the bottom-right, just before the built-in send
92
+ button.
93
+
94
+ <div className="w-full max-w-xl">
95
+ <InputMessage
96
+ rightSlot={
97
+ <Button variant="ghost" size="compact" trailingIcon={ChevronDown}>
98
+ Sonnet 4.6
99
+ </Button>
100
+ }
101
+ />
102
+ </div>
103
+
104
+ # Send Handler
105
+
106
+ `onSend` receives the trimmed text and the attached files; an uncontrolled
107
+ composer clears its own draft afterwards. The handler here is an empty
108
+ arrow — in an app it is where the message joins the transcript.
109
+
110
+ <div className="w-full max-w-xl">
111
+ <InputMessage
112
+ onSend={() => {}}
113
+ placeholder="Press Enter to send. Shift+Enter for newline."
114
+ />
115
+ </div>
116
+
117
+ # Disabled
118
+
119
+ Dims the panel to 50% and drops pointer events — textarea, send button and
120
+ drag-and-drop all go inert.
121
+
122
+ <div className="w-full max-w-xl">
123
+ <InputMessage defaultValue="This composer is disabled" disabled />
124
+ </div>
@@ -0,0 +1,173 @@
1
+ /*
2
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
3
+ * `registry/default/file-thumbnail.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); the `@next/next/no-img-element`
7
+ * eslint-disable comment dropped (no Next.js).
8
+ * - `@/lib/{utils,shape-context}` rewritten to `../../../lib/*`.
9
+ * - The lazy `import("pdfjs-dist")` is kept as-is: verified against modo's own
10
+ * esbuild settings (bundle, `platform: "browser"`, `target: "es2022"`, both
11
+ * with and without `splitting: true`) — it resolves cleanly and lands in a
12
+ * ~790kB chunk that is only fetched when a PDF is actually attached. No CDN
13
+ * fallback was needed. The worker still comes from jsDelivr, so no
14
+ * bundler-side worker config is required.
15
+ */
16
+
17
+ import { useEffect, useState } from 'react'
18
+ import { useShape } from '../../../lib/shape-context'
19
+ import { cn } from '../../../lib/utils'
20
+
21
+ // ─── Lazy pdfjs loader ────────────────────────────────────────────────────
22
+ // Imports pdfjs-dist on first PDF, caches the module, and points the worker
23
+ // at the matching CDN build. Consumers don't need bundler-side worker config.
24
+ type PdfjsModule = typeof import('pdfjs-dist')
25
+ let pdfjsPromise: Promise<PdfjsModule> | null = null
26
+
27
+ async function loadPdfjs(): Promise<PdfjsModule> {
28
+ if (!pdfjsPromise) {
29
+ pdfjsPromise = import('pdfjs-dist').then(mod => {
30
+ if (!mod.GlobalWorkerOptions.workerSrc) {
31
+ mod.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${mod.version}/build/pdf.worker.min.mjs`
32
+ }
33
+ return mod
34
+ })
35
+ }
36
+ return pdfjsPromise
37
+ }
38
+
39
+ async function renderPdfFirstPage(file: File, targetWidth: number): Promise<string> {
40
+ const pdfjs = await loadPdfjs()
41
+ const buffer = await file.arrayBuffer()
42
+ const pdf = await pdfjs.getDocument({ data: buffer }).promise
43
+ const page = await pdf.getPage(1)
44
+ const baseViewport = page.getViewport({ scale: 1 })
45
+ const scale = (targetWidth * 2) / baseViewport.width // 2× for retina
46
+ const viewport = page.getViewport({ scale })
47
+ const canvas = document.createElement('canvas')
48
+ canvas.width = viewport.width
49
+ canvas.height = viewport.height
50
+ await page.render({ canvas, viewport }).promise
51
+ return canvas.toDataURL('image/png')
52
+ }
53
+
54
+ // ─── File thumbnail ───────────────────────────────────────────────────────
55
+ // Read-only square preview of a File. Images use object-cover via
56
+ // `URL.createObjectURL`; PDFs render the first page via pdfjs; while either is
57
+ // resolving a spinner is shown. Self-contained (border + surface + sizing) so
58
+ // it can be reused both inside the composer's preview row and to render
59
+ // already-sent attachments in a chat transcript.
60
+ interface FileThumbnailProps {
61
+ file: File
62
+ /** Side length of the square thumbnail in pixels. */
63
+ size: number
64
+ className?: string
65
+ }
66
+
67
+ function FileThumbnail({ file, size, className }: FileThumbnailProps) {
68
+ const shape = useShape()
69
+ const isImage = file.type.startsWith('image/')
70
+ const isPdf = file.type === 'application/pdf'
71
+
72
+ // Create blob URL inside an effect (NOT useMemo) so the cleanup-revoke
73
+ // and the URL-creation stay in sync. In React 18 StrictMode dev, a
74
+ // useMemo-created URL gets revoked by the simulated effect-cleanup but
75
+ // useMemo doesn't re-run on the simulated re-mount (no re-render happens),
76
+ // leaving the DOM with a stale, revoked `blob:` URL — broken image.
77
+ // Putting both in the same effect means the simulated re-mount creates a
78
+ // fresh URL and updates state. The one-frame "before URL" state is
79
+ // covered by the bg-accent (no fallback icon shown for images), so the
80
+ // transition is visually clean.
81
+ const [imageUrl, setImageUrl] = useState<string | null>(null)
82
+ useEffect(() => {
83
+ if (!isImage) {
84
+ // Clear stale state if the `file` prop swaps type on the same mount —
85
+ // otherwise a revoked blob URL would keep winning over the new preview.
86
+ setImageUrl(null)
87
+ return
88
+ }
89
+ const url = URL.createObjectURL(file)
90
+ setImageUrl(url)
91
+ return () => URL.revokeObjectURL(url)
92
+ }, [isImage, file])
93
+
94
+ // PDFs need async rendering — loading flash is unavoidable for the first
95
+ // ~100–300ms while pdfjs loads. Falls back to the generic icon on error
96
+ // (corrupt/password-protected file, CDN worker blocked).
97
+ const [pdfUrl, setPdfUrl] = useState<string | null>(null)
98
+ const [pdfError, setPdfError] = useState(false)
99
+ useEffect(() => {
100
+ setPdfError(false)
101
+ if (!isPdf) {
102
+ setPdfUrl(null)
103
+ return
104
+ }
105
+ let cancelled = false
106
+ renderPdfFirstPage(file, size)
107
+ .then(url => {
108
+ if (!cancelled) setPdfUrl(url)
109
+ })
110
+ .catch(() => {
111
+ if (!cancelled) setPdfError(true)
112
+ })
113
+ return () => {
114
+ cancelled = true
115
+ }
116
+ }, [file, isPdf, size])
117
+
118
+ const previewUrl = imageUrl ?? pdfUrl
119
+ // Spinner only while a preview is genuinely pending; anything that can't
120
+ // produce one (failed PDF, unsupported type) gets the generic icon instead.
121
+ const isPending = (isImage && !imageUrl) || (isPdf && !pdfUrl && !pdfError)
122
+
123
+ return (
124
+ <div
125
+ className={cn('relative shrink-0 overflow-hidden bg-accent border border-border', shape.bg, className)}
126
+ style={{ width: size, height: size }}
127
+ >
128
+ {previewUrl ? (
129
+ <img src={previewUrl} alt={file.name} className="absolute inset-0 w-full h-full object-cover" />
130
+ ) : isPending ? (
131
+ // Circular spinner while we wait for the preview to be ready.
132
+ // Used for both images (brief URL-creation gap) and PDFs (longer
133
+ // pdfjs render). The thin ring is mostly subtle (border-border)
134
+ // with one quadrant accented (border-t-muted-foreground) so the
135
+ // `animate-spin` rotation reads as a moving arc.
136
+ <div className="absolute inset-0 flex items-center justify-center">
137
+ <div
138
+ className="w-6 h-6 rounded-full border-2 border-border border-t-muted-foreground animate-spin"
139
+ aria-label="Loading preview"
140
+ role="status"
141
+ />
142
+ </div>
143
+ ) : (
144
+ // Generic document glyph for files with no renderable preview.
145
+ // Inline SVG (not the icon system) so the thumbnail stays
146
+ // self-contained for registry consumers.
147
+ <div
148
+ className="absolute inset-0 flex items-center justify-center text-muted-foreground"
149
+ role="img"
150
+ aria-label={file.name}
151
+ >
152
+ <svg
153
+ width={Math.max(16, size * 0.35)}
154
+ height={Math.max(16, size * 0.35)}
155
+ viewBox="0 0 24 24"
156
+ fill="none"
157
+ stroke="currentColor"
158
+ strokeWidth="1.5"
159
+ strokeLinecap="round"
160
+ strokeLinejoin="round"
161
+ aria-hidden="true"
162
+ >
163
+ <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
164
+ <path d="M14 3v5h5" />
165
+ </svg>
166
+ </div>
167
+ )}
168
+ </div>
169
+ )
170
+ }
171
+
172
+ export type { FileThumbnailProps }
173
+ export { FileThumbnail, loadPdfjs, renderPdfFirstPage }