@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,113 @@
1
+ import Field from './index'
2
+ import Input from '../input'
3
+ import Textarea from '../textarea'
4
+ import NumberInput from '../number-input'
5
+ import { Mail } from 'lucide-react'
6
+
7
+ # Field
8
+
9
+ A label above the control, help text below it. The label targets the control and the description is read with it.
10
+
11
+ <Field className="w-72 max-w-full">
12
+ <Field.Label>Email</Field.Label>
13
+ <Input icon={Mail} placeholder="you@example.com" />
14
+ <Field.Description>We only write about your account.</Field.Description>
15
+ </Field>
16
+
17
+ # Error
18
+
19
+ `invalid` turns the label and the box destructive; `Field.Error` shows its message while it has one. Both come from your validation.
20
+
21
+ <Field invalid className="w-72 max-w-full">
22
+ <Field.Label>Email</Field.Label>
23
+ <Input icon={Mail} defaultValue="ada@" />
24
+ <Field.Error>Enter a full address, like ada@example.com.</Field.Error>
25
+ </Field>
26
+
27
+ # Required
28
+
29
+ `required` marks the control required and the label with an asterisk.
30
+
31
+ <Field required name="company" className="w-72 max-w-full">
32
+ <Field.Label>Company</Field.Label>
33
+ <Input placeholder="Acme Inc." />
34
+ </Field>
35
+
36
+ # Native validation
37
+
38
+ With `validationMode`, Base UI checks the control's own rules (`required`, `type="email"`) and the field turns destructive on a failure; an empty `Field.Error` shows the browser's message. Type something that isn't an address and leave the field.
39
+
40
+ <Field required validationMode="onBlur" className="w-72 max-w-full">
41
+ <Field.Label>Work email</Field.Label>
42
+ <Input type="email" icon={Mail} placeholder="you@company.com" />
43
+ <Field.Error />
44
+ </Field>
45
+
46
+ # Disabled and read-only
47
+
48
+ The field hands its state to the control: `disabled` dims everything, `readOnly` keeps the value selectable.
49
+
50
+ <div className="flex flex-col gap-4 w-72 max-w-full">
51
+ <Field disabled>
52
+ <Field.Label>Workspace</Field.Label>
53
+ <Input defaultValue="Design" />
54
+ <Field.Description>Only owners can rename it.</Field.Description>
55
+ </Field>
56
+ <Field readOnly>
57
+ <Field.Label>Invoice number</Field.Label>
58
+ <Input defaultValue="INV-2026-0042" />
59
+ </Field>
60
+ </div>
61
+
62
+ # Any field control
63
+
64
+ Textarea and NumberInput join the same way: the label, description and error wire to them.
65
+
66
+ <div className="flex flex-col gap-4 w-72 max-w-full">
67
+ <Field>
68
+ <Field.Label>Message</Field.Label>
69
+ <Textarea placeholder="Write your message" minRows={2} />
70
+ </Field>
71
+ <Field invalid>
72
+ <Field.Label>Seats</Field.Label>
73
+ <NumberInput defaultValue={0} min={0} />
74
+ <Field.Error>Book at least one seat.</Field.Error>
75
+ </Field>
76
+ </div>
77
+
78
+ # Field.Control
79
+
80
+ A control that isn't a field control gets the field's id, `aria-describedby` and `aria-invalid` through `Field.Control`, here a native select.
81
+
82
+ <Field className="w-72 max-w-full">
83
+ <Field.Label>Country</Field.Label>
84
+ <Field.Control
85
+ render={
86
+ <select className="h-9 w-full bg-transparent px-1.5 text-body text-foreground">
87
+ <option>Slovakia</option>
88
+ <option>Czechia</option>
89
+ <option>Austria</option>
90
+ </select>
91
+ }
92
+ />
93
+ <Field.Description>Where the invoice is sent.</Field.Description>
94
+ </Field>
95
+
96
+ # Compact
97
+
98
+ `size` pins the field and its control to the compact step.
99
+
100
+ <Field size="compact" className="w-64 max-w-full">
101
+ <Field.Label>Filter</Field.Label>
102
+ <Input placeholder="Name contains…" />
103
+ <Field.Description>Matches any part of the name.</Field.Description>
104
+ </Field>
105
+
106
+ # Hidden label
107
+
108
+ `visuallyHidden` keeps the label for assistive tech where the placeholder carries the meaning.
109
+
110
+ <Field className="w-72 max-w-full">
111
+ <Field.Label visuallyHidden>Search</Field.Label>
112
+ <Input variant="bordered" placeholder="Search people" />
113
+ </Field>
@@ -0,0 +1,267 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): a labelled form field on
3
+ * Base UI's Field, with no form library of its own. The label, description
4
+ * and error line up with the text inside the field box
5
+ * (`lib/field-variants.ts`), and the field's state reaches any control built
6
+ * on `useFieldBox` through `FieldStateContext` (`lib/field-classes.ts`), the
7
+ * way InputGroup.Field hands it to its control.
8
+ */
9
+
10
+ import { Field as BaseField } from '@base-ui/react/field'
11
+ import {
12
+ type ForwardRefExoticComponent,
13
+ forwardRef,
14
+ type HTMLAttributes,
15
+ type ReactElement,
16
+ type ReactNode,
17
+ type RefAttributes,
18
+ useMemo,
19
+ } from 'react'
20
+ import { FIELD_TEXT_INSET, FieldStateContext, useFieldState } from '../../../lib/field-classes'
21
+ import { type SizeVariant, useSize } from '../../../lib/size-context'
22
+ import { slotRender } from '../../../lib/slot'
23
+ import { cn } from '../../../lib/utils'
24
+ import { SizeProvider } from '../../../primitives/sizes'
25
+ import { Label } from '../input'
26
+
27
+ /** When native validity is checked. */
28
+ type FieldValidationMode = 'onSubmit' | 'onBlur' | 'onChange'
29
+
30
+ interface FieldProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
31
+ /** The field's parts: `Field.Label`, a control, `Field.Description`, `Field.Error`. */
32
+ children?: ReactNode
33
+ /** Marks the value invalid: the label and the control turn destructive and the control sets `aria-invalid`. Set it from your validation (a form library's field state). Defaults to `false`. */
34
+ invalid?: boolean
35
+ /** Disables the control and dims the field. Defaults to `false`. */
36
+ disabled?: boolean
37
+ /** Shows the value without letting it change (Input, NumberInput and FileDropzone read it). Defaults to `false`. */
38
+ readOnly?: boolean
39
+ /** Marks the control required and the label with an asterisk. Defaults to `false`. */
40
+ required?: boolean
41
+ /** Names the value when the form is submitted; wins over the control's own `name`. */
42
+ name?: string
43
+ /** Whether the value differs from its initial one, for a form library that tracks it (sets `data-dirty`). */
44
+ dirty?: boolean
45
+ /** Whether the control has been visited, for a form library that tracks it (sets `data-touched`). */
46
+ touched?: boolean
47
+ /** When Base UI checks the control's native validity (`required`, `pattern`, `type`): on form submit, on blur, or on every change. A failure turns the field destructive and an empty `Field.Error` shows the browser's message. Defaults to `"onSubmit"`. */
48
+ validationMode?: FieldValidationMode
49
+ /** Pins the field and its control to one step of the size ladder (36px default, 28px compact). Defaults to the surrounding SizeProvider. */
50
+ size?: SizeVariant
51
+ /** Classes for the root. */
52
+ className?: string
53
+ }
54
+
55
+ interface FieldLabelProps {
56
+ /** Label text. */
57
+ children: ReactNode
58
+ /** Keep the label for assistive tech but don't render it, for a field whose placeholder or context carries the meaning. Defaults to `false`. */
59
+ visuallyHidden?: boolean
60
+ /** Classes for the label. */
61
+ className?: string
62
+ }
63
+
64
+ function FieldLabel({ children, visuallyHidden, className }: FieldLabelProps) {
65
+ const field = useFieldState()
66
+
67
+ return (
68
+ <BaseField.Label
69
+ render={
70
+ <Label invalid={field?.invalid} className={cn(visuallyHidden && 'sr-only', className)}>
71
+ {children}
72
+ {field?.required && <span aria-hidden="true"> *</span>}
73
+ </Label>
74
+ }
75
+ />
76
+ )
77
+ }
78
+
79
+ interface FieldDescriptionProps {
80
+ /** Help text under the control: a format, a limit, what the value is for. */
81
+ children: ReactNode
82
+ /** Classes for the description. */
83
+ className?: string
84
+ }
85
+
86
+ function FieldDescription({ children, className }: FieldDescriptionProps) {
87
+ const size = useSize()
88
+
89
+ return (
90
+ <BaseField.Description
91
+ className={cn(
92
+ 'text-muted-foreground weight-normal data-[disabled]:opacity-50',
93
+ size.variant === 'compact' ? 'text-caption-compact' : 'text-caption',
94
+ FIELD_TEXT_INSET[size.variant],
95
+ className,
96
+ )}
97
+ >
98
+ {children}
99
+ </BaseField.Description>
100
+ )
101
+ }
102
+
103
+ /** Which native validity state shows the message (`"valueMissing"`, `"typeMismatch"`, …). */
104
+ type FieldErrorMatch = boolean | keyof ValidityState
105
+
106
+ interface FieldErrorProps {
107
+ /** The message. While it has one, it shows; empty, it hides. */
108
+ children?: ReactNode
109
+ /** Overrides when it shows: `true` always, `false` never, or a native validity state such as `"valueMissing"`. Defaults to showing while it has a message. */
110
+ match?: FieldErrorMatch
111
+ /** Classes for the message. */
112
+ className?: string
113
+ }
114
+
115
+ function FieldError({ children, match, className }: FieldErrorProps) {
116
+ const size = useSize()
117
+ const hasMessage = children != null && children !== false && children !== ''
118
+
119
+ return (
120
+ <BaseField.Error
121
+ // Without a message and a match, Base UI shows the native validation message.
122
+ match={match ?? (hasMessage || undefined)}
123
+ className={cn(
124
+ 'text-destructive weight-medium',
125
+ size.variant === 'compact' ? 'text-caption-compact' : 'text-caption',
126
+ FIELD_TEXT_INSET[size.variant],
127
+ 'transition-opacity duration-fast data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[ending-style]:duration-fast-exit',
128
+ className,
129
+ )}
130
+ // An explicit `children: undefined` would hide Base UI's own message.
131
+ {...(hasMessage ? { children } : {})}
132
+ />
133
+ )
134
+ }
135
+
136
+ interface FieldControlProps {
137
+ /** The element that becomes the control, e.g. a native `<select>` or a third-party input. */
138
+ render?: ReactElement
139
+ /** Compose onto the single child instead. Defaults to `false`. */
140
+ asChild?: boolean
141
+ /** The control, with `asChild`. */
142
+ children?: ReactNode
143
+ }
144
+
145
+ function FieldControl({ render, asChild, children }: FieldControlProps) {
146
+ return <BaseField.Control render={slotRender(render, asChild, children)} />
147
+ }
148
+
149
+ interface ProvideFieldStateProps {
150
+ invalid: boolean
151
+ disabled: boolean
152
+ readOnly: boolean
153
+ required: boolean
154
+ children?: ReactNode
155
+ }
156
+
157
+ function ProvideFieldState({ invalid, disabled, readOnly, required, children }: ProvideFieldStateProps) {
158
+ const state = useMemo(
159
+ () => ({ active: false, invalid, disabled, readOnly, required }),
160
+ [invalid, disabled, readOnly, required],
161
+ )
162
+ return <FieldStateContext.Provider value={state}>{children}</FieldStateContext.Provider>
163
+ }
164
+
165
+ type FieldComponent = ForwardRefExoticComponent<FieldProps & RefAttributes<HTMLDivElement>> & {
166
+ Label: typeof FieldLabel
167
+ Description: typeof FieldDescription
168
+ Error: typeof FieldError
169
+ Control: typeof FieldControl
170
+ }
171
+
172
+ /**
173
+ * A labelled form field: a label above the control, help text and an error
174
+ * message below it, all linked for assistive tech.
175
+ *
176
+ * Put an Input, Textarea, NumberInput, FileDropzone, Select or any other
177
+ * field control inside and it joins on its own: the label targets it, the
178
+ * description and error are read with it, and the field hands it its
179
+ * `invalid`, `disabled`, `readOnly` and `required` state, so the box turns
180
+ * destructive with the label. The label and messages sit inset to line up
181
+ * with the text in the box. The field holds no validation of its own:
182
+ * `invalid` and the error's message come from yours (a form library's field
183
+ * state, a server response), and `Field.Error` shows while it has a message.
184
+ * A native failure (`required`, `pattern`, `type="email"`) caught by Base UI's
185
+ * validation turns it destructive too, and an empty `Field.Error` then shows
186
+ * the browser's message.
187
+ * Behavior comes from Base UI's Field.
188
+ *
189
+ * `size` pins the field and its control to one step of the size ladder;
190
+ * without it they follow the surrounding SizeProvider, which is how to set a
191
+ * form's density when a control's own props keep the native `<input size>`
192
+ * attribute (a character width, not a height). `Field.Control` passes a
193
+ * native `size` through to the element untouched.
194
+ *
195
+ * InputGroup.Field is the preset for a group of text fields: a label and an
196
+ * error string, and the group's hover gliding from field to field. Reach for
197
+ * Field when a field stands alone, needs a description, or holds a control
198
+ * other than a text input.
199
+ *
200
+ * Statics:
201
+ * - `Field.Label` — the label, weight-stable and muted; destructive while
202
+ * the field is invalid, with an asterisk while it is required:
203
+ * `visuallyHidden`.
204
+ * - `Field.Description` — muted help text under the control.
205
+ * - `Field.Error` — the destructive message; it fades in while it has
206
+ * children: `match` (`true`, `false`, or a native validity state).
207
+ * - `Field.Control` — wires a control that isn't a field control (a native
208
+ * element, a third-party input) with the field's id, `aria-describedby`
209
+ * and `aria-invalid`: `render={<select/>}` or `asChild`. Field controls
210
+ * (Input, NumberInput, …) need no wrapper.
211
+ *
212
+ * @example {@include ./examples.mdx}
213
+ */
214
+ const Field = forwardRef<HTMLDivElement, FieldProps>(
215
+ (
216
+ { invalid = false, disabled = false, readOnly = false, required = false, size, className, children, ...props },
217
+ ref,
218
+ ) => {
219
+ const field = (
220
+ // Base UI wires the plumbing: Field.Label's htmlFor targets the
221
+ // control, Description and Error ids land in its aria-describedby, and
222
+ // `invalid` drives aria-invalid / data-invalid.
223
+ <BaseField.Root
224
+ ref={ref}
225
+ invalid={invalid}
226
+ disabled={disabled}
227
+ className={cn('flex min-w-0 flex-col gap-1', className)}
228
+ // The render function sees Base UI's validity, so a native failure
229
+ // (`required`, `pattern`, a type mismatch) turns the label and the box
230
+ // destructive too, not only the `invalid` prop.
231
+ render={(rootProps, rootState) => (
232
+ <div {...rootProps}>
233
+ <ProvideFieldState
234
+ invalid={invalid || rootState.valid === false}
235
+ disabled={disabled}
236
+ readOnly={readOnly}
237
+ required={required}
238
+ >
239
+ {rootProps.children}
240
+ </ProvideFieldState>
241
+ </div>
242
+ )}
243
+ {...props}
244
+ >
245
+ {children}
246
+ </BaseField.Root>
247
+ )
248
+
249
+ return size ? <SizeProvider size={size}>{field}</SizeProvider> : field
250
+ },
251
+ ) as FieldComponent
252
+
253
+ Field.displayName = 'Field'
254
+
255
+ Object.assign(Field, { Label: FieldLabel, Description: FieldDescription, Error: FieldError, Control: FieldControl })
256
+
257
+ export type {
258
+ FieldControlProps,
259
+ FieldDescriptionProps,
260
+ FieldErrorMatch,
261
+ FieldErrorProps,
262
+ FieldLabelProps,
263
+ FieldProps,
264
+ FieldValidationMode,
265
+ }
266
+ export { Field, FieldControl, FieldDescription, FieldError, FieldLabel }
267
+ export default Field
@@ -0,0 +1,73 @@
1
+ import FileDropzone from './index'
2
+ import UploadDemo from './upload-demo'
3
+ import Field from '../field'
4
+
5
+ # Dropzone
6
+
7
+ Drag files over the zone, or click it (or press Enter on it) to pick them. While files are in the air the line says whether all, some or none of them will be taken; files that pass go to the list, the rest to the error.
8
+
9
+ <UploadDemo multiple accept=".pdf,image/*" maxSize={5000000} description="PDF or images, up to 5 MB each, three at most" />
10
+
11
+ # Single file
12
+
13
+ Without `multiple` the zone takes one file: drag several and it says the rest will be skipped; drag only files of another type and it refuses the drop.
14
+
15
+ <div className="w-96 max-w-full">
16
+ <FileDropzone accept="image/*" description="PNG or JPG" />
17
+ </div>
18
+
19
+ # Compact
20
+
21
+ The compact step tightens the padding and steps the text down.
22
+
23
+ <div className="w-80 max-w-full">
24
+ <FileDropzone size="compact" label="Drop a CSV or click to browse" accept=".csv" />
25
+ </div>
26
+
27
+ # Translated
28
+
29
+ Every line is a prop.
30
+
31
+ <div className="w-96 max-w-full">
32
+ <FileDropzone
33
+ multiple
34
+ label="Presuňte súbory sem alebo kliknite"
35
+ activeLabel="Pustite a pridajte"
36
+ partialLabel="Niektoré súbory sa vynechajú"
37
+ rejectLabel="Tieto súbory nemožno pridať"
38
+ description="PDF, najviac 10 MB"
39
+ accept=".pdf"
40
+ />
41
+ </div>
42
+
43
+ # States
44
+
45
+ Invalid takes the field's destructive ring; disabled dims the zone; read-only takes no files.
46
+
47
+ <div className="flex flex-col gap-3 w-96 max-w-full">
48
+ <Field invalid>
49
+ <Field.Label>Contract</Field.Label>
50
+ <FileDropzone accept=".pdf" />
51
+ <Field.Error>Attach the signed contract.</Field.Error>
52
+ </Field>
53
+ <FileDropzone disabled label="Uploads are paused" />
54
+ <FileDropzone readOnly label="No file attached" />
55
+ </div>
56
+
57
+ # File rows
58
+
59
+ A row shows one file: its glyph or thumbnail, the name, the size or a status line, and a remove button. `status` sets the glyph, and `progress` fills the bar while uploading.
60
+
61
+ <div className="flex flex-col w-96 max-w-full">
62
+ <FileDropzone.Row name="Quarterly report.pdf" fileSize={2480000} type="application/pdf" status="done" onRemove={() => {}} />
63
+ <FileDropzone.Row name="Site photo.jpg" fileSize={5320000} type="image/jpeg" status="uploading" progress={64} onRemove={() => {}} />
64
+ <FileDropzone.Row name="Budget.xlsx" fileSize={84000} status="error" error="Upload failed. Try again." onRemove={() => {}} />
65
+ <FileDropzone.Row name="Contract draft.docx" fileSize={1200000} description="Uploaded yesterday" />
66
+ </div>
67
+
68
+ # Compact rows
69
+
70
+ <div className="flex flex-col w-80 max-w-full">
71
+ <FileDropzone.Row size="compact" name="Invoice 0042.pdf" fileSize={182000} locale="sk-SK" onRemove={() => {}} />
72
+ <FileDropzone.Row size="compact" name="Receipt.png" fileSize={940000} type="image/png" status="uploading" onRemove={() => {}} />
73
+ </div>
@@ -0,0 +1,201 @@
1
+ /*
2
+ * Local addition (not part of Fluid Functionalism): one file in a list of
3
+ * picked or uploaded files, shown by FileDropzone's page as `FileDropzone.Row`.
4
+ */
5
+
6
+ import { AnimatePresence, motion } from 'motion/react'
7
+ import type { ReactNode } from 'react'
8
+ import { FIELD_ACTION_RADIUS } from '../../../lib/field-variants'
9
+ import { type IconComponent, useIcon } from '../../../lib/icon-context'
10
+ import { useReduceMotion } from '../../../lib/reduced-motion'
11
+ import { useShape } from '../../../lib/shape-context'
12
+ import { type SizeVariant, useSize } from '../../../lib/size-context'
13
+ import { spring } from '../../../lib/springs'
14
+ import { cn } from '../../../lib/utils'
15
+ import Button from '../../button'
16
+ import Progress, { Spinner } from '../../progress'
17
+ import { formatFileSize } from './variants'
18
+
19
+ /** Where a file is in its upload: `idle` (picked), `uploading`, `done` or `error`. */
20
+ type FileRowStatus = 'idle' | 'uploading' | 'done' | 'error'
21
+
22
+ interface FileRowProps {
23
+ /** The picked file: its name, size and type fill the row. */
24
+ file?: File
25
+ /** File name, for a file you only know by its metadata (one already uploaded). Wins over `file`. */
26
+ name?: string
27
+ /** File size in bytes. Wins over `file`. */
28
+ fileSize?: number
29
+ /** MIME type; an image shows the image glyph. Wins over `file`. */
30
+ type?: string
31
+ /** Image URL shown in place of the glyph, e.g. an object URL of the file or the server's preview. */
32
+ thumbnail?: string
33
+ /** Glyph in place of the file or image one. */
34
+ icon?: IconComponent
35
+ /** Where the file is in its upload; sets the status glyph and the progress bar. Defaults to `"idle"`. */
36
+ status?: FileRowStatus
37
+ /** Upload progress from 0 to 100, shown as a bar while `status` is `"uploading"` (see Progress). Omitted, the bar sweeps. */
38
+ progress?: number
39
+ /** Accessible name of the upload's bar. Defaults to `"Uploading"`. */
40
+ uploadingLabel?: string
41
+ /** The line under the name in place of the size, e.g. "Uploaded 2 min ago". */
42
+ description?: ReactNode
43
+ /** An error message in place of the size line; the row reads as failed. */
44
+ error?: ReactNode
45
+ /** Shows a remove button and is called when it is pressed. */
46
+ onRemove?: () => void
47
+ /** Accessible name of the remove button. Defaults to `"Remove file"`. */
48
+ removeLabel?: string
49
+ /** More controls before the remove button, e.g. a download link or a menu. */
50
+ actions?: ReactNode
51
+ /** Locale for the size (`1,5 MB` in `"sk-SK"`). Defaults to the browser's. */
52
+ locale?: Intl.LocalesArgument
53
+ /** Dims the row and its actions. Defaults to `false`. */
54
+ disabled?: boolean
55
+ /** Pins the row to one step of the size ladder. Defaults to the surrounding SizeProvider. */
56
+ size?: SizeVariant
57
+ /** Classes for the row. */
58
+ className?: string
59
+ }
60
+
61
+ // Literal tables: UnoCSS only generates the classes it can read whole.
62
+ const THUMB = { default: 'size-8', compact: 'size-7' }
63
+ const ROW_PAD = { default: 'py-1.5', compact: 'py-1' }
64
+
65
+ // Literal table: the spinner at the ladder's glyph size.
66
+ const SPINNER = { default: 'size-4', compact: 'size-3.5' }
67
+
68
+ function StatusGlyph({ status, size }: { status: FileRowStatus; size: SizeVariant }) {
69
+ const Done = useIcon('circle-check')
70
+ const Failed = useIcon('circle-alert')
71
+ const reduced = useReduceMotion()
72
+ const icon = useSize(size).icon
73
+
74
+ return (
75
+ // Decorative: the bar carries the progress, the meta line the error.
76
+ <span aria-hidden="true" className="inline-grid shrink-0 place-items-center">
77
+ <AnimatePresence initial={false} mode="popLayout">
78
+ {status !== 'idle' && (
79
+ <motion.span
80
+ key={status}
81
+ className={cn(
82
+ 'col-start-1 row-start-1 flex',
83
+ status === 'done' && 'text-status-success',
84
+ status === 'error' && 'text-destructive',
85
+ status === 'uploading' && 'text-muted-foreground',
86
+ )}
87
+ // Under reduced motion the glyphs only fade.
88
+ initial={{ opacity: 0, scale: reduced ? 1 : 0.6 }}
89
+ animate={{ opacity: 1, scale: 1 }}
90
+ exit={{ opacity: 0, scale: reduced ? 1 : 0.8, transition: spring.fast.exit }}
91
+ transition={spring.fast}
92
+ >
93
+ {status === 'done' && <Done size={icon} strokeWidth={2} />}
94
+ {status === 'error' && <Failed size={icon} strokeWidth={2} />}
95
+ {status === 'uploading' && <Spinner className={SPINNER[size]} />}
96
+ </motion.span>
97
+ )}
98
+ </AnimatePresence>
99
+ </span>
100
+ )
101
+ }
102
+
103
+ /** One file in a list: its glyph or thumbnail, name, size or status, and a remove button. */
104
+ function FileRow({
105
+ file,
106
+ name: nameProp,
107
+ fileSize: fileSizeProp,
108
+ type: typeProp,
109
+ thumbnail,
110
+ icon,
111
+ status = 'idle',
112
+ progress,
113
+ description,
114
+ error,
115
+ onRemove,
116
+ removeLabel = 'Remove file',
117
+ uploadingLabel = 'Uploading',
118
+ actions,
119
+ locale,
120
+ disabled = false,
121
+ size: sizeProp,
122
+ className,
123
+ }: FileRowProps) {
124
+ const size = useSize(sizeProp)
125
+ const shape = useShape()
126
+ const compact = size.variant === 'compact'
127
+ const FileIcon = useIcon('file')
128
+ const ImageIcon = useIcon('image')
129
+ const Remove = useIcon('x')
130
+
131
+ const name = nameProp ?? file?.name ?? ''
132
+ const bytes = fileSizeProp ?? file?.size
133
+ const type = typeProp ?? file?.type ?? ''
134
+ const Glyph = icon ?? (type.startsWith('image/') ? ImageIcon : FileIcon)
135
+ const shown = error ? 'error' : status
136
+ const sizeText = bytes != null ? formatFileSize(bytes, locale) : undefined
137
+ const meta =
138
+ error ??
139
+ description ??
140
+ (status === 'uploading' && progress != null ? `${sizeText ?? ''} · ${Math.round(progress)}%` : sizeText)
141
+
142
+ return (
143
+ <div
144
+ data-status={shown}
145
+ data-disabled={disabled || undefined}
146
+ className={cn(
147
+ 'flex min-w-0 items-center transition-opacity duration-fast',
148
+ size.gap,
149
+ size.itemPx,
150
+ ROW_PAD[size.variant],
151
+ shape.item,
152
+ disabled && 'pointer-events-none opacity-50',
153
+ className,
154
+ )}
155
+ >
156
+ <span
157
+ className={cn(
158
+ 'flex shrink-0 items-center justify-center overflow-hidden bg-muted text-muted-foreground',
159
+ THUMB[size.variant],
160
+ FIELD_ACTION_RADIUS[shape.variant],
161
+ )}
162
+ >
163
+ {thumbnail ? (
164
+ <img src={thumbnail} alt="" className="size-full object-cover" />
165
+ ) : (
166
+ <Glyph size={size.icon} strokeWidth={1.5} />
167
+ )}
168
+ </span>
169
+
170
+ <div className="flex min-w-0 flex-1 flex-col">
171
+ <span className={cn('truncate text-foreground weight-normal', size.text)} title={name}>
172
+ {name}
173
+ </span>
174
+ {meta != null && meta !== '' && (
175
+ <span
176
+ className={cn(
177
+ 'truncate',
178
+ compact ? 'text-caption-compact' : 'text-caption',
179
+ error ? 'text-destructive weight-medium' : 'text-muted-foreground weight-normal',
180
+ )}
181
+ >
182
+ {meta}
183
+ </span>
184
+ )}
185
+ {shown === 'uploading' && <Progress value={progress ?? null} aria-label={uploadingLabel} className="mt-1" />}
186
+ </div>
187
+
188
+ <StatusGlyph status={shown} size={size.variant} />
189
+ {actions}
190
+ {onRemove && (
191
+ <Button variant="ghost" size="icon-compact" aria-label={removeLabel} disabled={disabled} onClick={onRemove}>
192
+ <Remove strokeWidth={1.5} />
193
+ </Button>
194
+ )}
195
+ </div>
196
+ )
197
+ }
198
+
199
+ export type { FileRowProps, FileRowStatus }
200
+ export { FileRow }
201
+ export default FileRow