@sincpro/web-ui 0.0.0-stage → 0.2.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 (376) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -2
  3. package/dist/components/activity/Activity.d.ts +69 -0
  4. package/dist/components/activity/Activity.js +279 -0
  5. package/dist/components/activity/Messages.d.ts +66 -0
  6. package/dist/components/activity/Messages.js +46 -0
  7. package/dist/components/activity/Timeline.d.ts +21 -0
  8. package/dist/components/activity/Timeline.js +30 -0
  9. package/dist/components/activity/index.d.ts +3 -0
  10. package/dist/components/activity/index.js +4 -0
  11. package/dist/components/assist/AssistMark.d.ts +64 -0
  12. package/dist/components/assist/AssistMark.js +108 -0
  13. package/dist/components/assist/AssistPending.d.ts +24 -0
  14. package/dist/components/assist/AssistPending.js +26 -0
  15. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  16. package/dist/components/assist/AssistSuggestion.js +55 -0
  17. package/dist/components/assist/ChangeReview.d.ts +56 -0
  18. package/dist/components/assist/ChangeReview.js +121 -0
  19. package/dist/components/assist/assist.d.ts +77 -0
  20. package/dist/components/assist/assist.js +87 -0
  21. package/dist/components/assist/index.d.ts +5 -0
  22. package/dist/components/assist/index.js +7 -0
  23. package/dist/components/form/Form.d.ts +52 -0
  24. package/dist/components/form/Form.js +49 -0
  25. package/dist/components/form/Sheet.d.ts +47 -0
  26. package/dist/components/form/Sheet.js +90 -0
  27. package/dist/components/form/index.d.ts +2 -0
  28. package/dist/components/form/index.js +4 -0
  29. package/dist/components/frame/Chart.d.ts +13 -0
  30. package/dist/components/frame/Chart.js +24 -0
  31. package/dist/components/frame/FrameView.d.ts +33 -0
  32. package/dist/components/frame/FrameView.js +125 -0
  33. package/dist/components/frame/index.d.ts +3 -0
  34. package/dist/components/frame/index.js +3 -0
  35. package/dist/components/frame/model.d.ts +43 -0
  36. package/dist/components/frame/model.js +101 -0
  37. package/dist/components/index.d.ts +10 -0
  38. package/dist/components/index.js +11 -0
  39. package/dist/components/lines/LineItems.d.ts +111 -0
  40. package/dist/components/lines/LineItems.js +317 -0
  41. package/dist/components/lines/index.d.ts +1 -0
  42. package/dist/components/lines/index.js +2 -0
  43. package/dist/components/lines/lines.d.ts +23 -0
  44. package/dist/components/lines/lines.js +26 -0
  45. package/dist/components/listing/ColumnsMenu.d.ts +20 -0
  46. package/dist/components/listing/ColumnsMenu.js +46 -0
  47. package/dist/components/listing/GroupingLevels.d.ts +35 -0
  48. package/dist/components/listing/GroupingLevels.js +45 -0
  49. package/dist/components/listing/Listing.d.ts +27 -0
  50. package/dist/components/listing/Listing.js +86 -0
  51. package/dist/components/listing/ListingBar.d.ts +112 -0
  52. package/dist/components/listing/ListingBar.js +144 -0
  53. package/dist/components/listing/Pagination.d.ts +36 -0
  54. package/dist/components/listing/Pagination.js +69 -0
  55. package/dist/components/listing/RecordCard.d.ts +33 -0
  56. package/dist/components/listing/RecordCard.js +22 -0
  57. package/dist/components/listing/RecordLink.d.ts +7 -0
  58. package/dist/components/listing/RecordLink.js +22 -0
  59. package/dist/components/listing/Row.d.ts +110 -0
  60. package/dist/components/listing/Row.js +123 -0
  61. package/dist/components/listing/SortMenu.d.ts +62 -0
  62. package/dist/components/listing/SortMenu.js +105 -0
  63. package/dist/components/listing/ViewSwitcher.d.ts +24 -0
  64. package/dist/components/listing/ViewSwitcher.js +16 -0
  65. package/dist/components/listing/columns.d.ts +47 -0
  66. package/dist/components/listing/columns.js +72 -0
  67. package/dist/components/listing/grouping.d.ts +35 -0
  68. package/dist/components/listing/grouping.js +47 -0
  69. package/dist/components/listing/index.d.ts +17 -0
  70. package/dist/components/listing/index.js +20 -0
  71. package/dist/components/listing/modelColumns.d.ts +50 -0
  72. package/dist/components/listing/modelColumns.js +103 -0
  73. package/dist/components/listing/pages.d.ts +78 -0
  74. package/dist/components/listing/pages.js +135 -0
  75. package/dist/components/listing/selection.d.ts +21 -0
  76. package/dist/components/listing/selection.js +43 -0
  77. package/dist/components/listing/set.d.ts +48 -0
  78. package/dist/components/listing/set.js +24 -0
  79. package/dist/components/listing/tree.d.ts +28 -0
  80. package/dist/components/listing/tree.js +23 -0
  81. package/dist/components/navigation/CommandPalette.d.ts +40 -0
  82. package/dist/components/navigation/CommandPalette.js +114 -0
  83. package/dist/components/navigation/NavigationTree.d.ts +31 -0
  84. package/dist/components/navigation/NavigationTree.js +79 -0
  85. package/dist/components/navigation/commands.d.ts +30 -0
  86. package/dist/components/navigation/commands.js +30 -0
  87. package/dist/components/navigation/index.d.ts +3 -0
  88. package/dist/components/navigation/index.js +4 -0
  89. package/dist/components/record/Breadcrumb.d.ts +42 -0
  90. package/dist/components/record/Breadcrumb.js +98 -0
  91. package/dist/components/record/DirtyActions.d.ts +26 -0
  92. package/dist/components/record/DirtyActions.js +22 -0
  93. package/dist/components/record/Pager.d.ts +18 -0
  94. package/dist/components/record/Pager.js +33 -0
  95. package/dist/components/record/RecordHeader.d.ts +75 -0
  96. package/dist/components/record/RecordHeader.js +104 -0
  97. package/dist/components/record/RecordNotices.d.ts +37 -0
  98. package/dist/components/record/RecordNotices.js +75 -0
  99. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  100. package/dist/components/record/RecordSkeleton.js +21 -0
  101. package/dist/components/record/SmartButtons.d.ts +35 -0
  102. package/dist/components/record/SmartButtons.js +38 -0
  103. package/dist/components/record/StatusBar.d.ts +51 -0
  104. package/dist/components/record/StatusBar.js +168 -0
  105. package/dist/components/record/ThumbBar.d.ts +36 -0
  106. package/dist/components/record/ThumbBar.js +86 -0
  107. package/dist/components/record/index.d.ts +6 -0
  108. package/dist/components/record/index.js +8 -0
  109. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  110. package/dist/components/search/CriteriaBuilder.js +278 -0
  111. package/dist/components/search/CriteriaChips.d.ts +22 -0
  112. package/dist/components/search/CriteriaChips.js +64 -0
  113. package/dist/components/search/FilterBuilder.d.ts +142 -0
  114. package/dist/components/search/FilterBuilder.js +421 -0
  115. package/dist/components/search/JoinWord.d.ts +8 -0
  116. package/dist/components/search/JoinWord.js +47 -0
  117. package/dist/components/search/JsonEditor.d.ts +19 -0
  118. package/dist/components/search/JsonEditor.js +78 -0
  119. package/dist/components/search/SearchBar.d.ts +78 -0
  120. package/dist/components/search/SearchBar.js +387 -0
  121. package/dist/components/search/criteria.d.ts +116 -0
  122. package/dist/components/search/criteria.js +159 -0
  123. package/dist/components/search/datePeriods.d.ts +122 -0
  124. package/dist/components/search/datePeriods.js +134 -0
  125. package/dist/components/search/dateRanges.d.ts +54 -0
  126. package/dist/components/search/dateRanges.js +89 -0
  127. package/dist/components/search/index.d.ts +9 -0
  128. package/dist/components/search/index.js +20 -0
  129. package/dist/components/search/model.d.ts +54 -0
  130. package/dist/components/search/model.js +37 -0
  131. package/dist/components/search/query.d.ts +166 -0
  132. package/dist/components/search/query.js +436 -0
  133. package/dist/components/shell/AppFrame.d.ts +14 -0
  134. package/dist/components/shell/AppFrame.js +80 -0
  135. package/dist/components/shell/TopBar.d.ts +73 -0
  136. package/dist/components/shell/TopBar.js +54 -0
  137. package/dist/components/shell/headerSlot.d.ts +17 -0
  138. package/dist/components/shell/headerSlot.js +21 -0
  139. package/dist/components/shell/index.d.ts +2 -0
  140. package/dist/components/shell/index.js +3 -0
  141. package/dist/foundations/aria.d.ts +14 -0
  142. package/dist/foundations/aria.js +18 -0
  143. package/dist/foundations/calendar.d.ts +28 -0
  144. package/dist/foundations/calendar.js +183 -0
  145. package/dist/foundations/copy.d.ts +703 -0
  146. package/dist/foundations/copy.es.d.ts +2 -0
  147. package/dist/foundations/copy.es.js +430 -0
  148. package/dist/foundations/copy.js +520 -0
  149. package/dist/foundations/density.d.ts +16 -0
  150. package/dist/foundations/density.js +21 -0
  151. package/dist/foundations/grouping.d.ts +281 -0
  152. package/dist/foundations/grouping.js +0 -0
  153. package/dist/foundations/index.d.ts +14 -0
  154. package/dist/foundations/index.js +30 -0
  155. package/dist/foundations/layout.d.ts +9 -0
  156. package/dist/foundations/layout.js +9 -0
  157. package/dist/foundations/pivot.d.ts +222 -0
  158. package/dist/foundations/pivot.js +198 -0
  159. package/dist/foundations/pivotSheet.d.ts +61 -0
  160. package/dist/foundations/pivotSheet.js +0 -0
  161. package/dist/foundations/slots.d.ts +67 -0
  162. package/dist/foundations/slots.js +63 -0
  163. package/dist/foundations/sticky.d.ts +53 -0
  164. package/dist/foundations/sticky.js +150 -0
  165. package/dist/foundations/surface.d.ts +9 -0
  166. package/dist/foundations/surface.js +26 -0
  167. package/dist/foundations/theme.d.ts +42 -0
  168. package/dist/foundations/theme.js +88 -0
  169. package/dist/foundations/types.d.ts +23 -0
  170. package/dist/foundations/types.js +50 -0
  171. package/dist/foundations/variants.d.ts +19 -0
  172. package/dist/foundations/variants.js +48 -0
  173. package/dist/foundations/xlsx.d.ts +86 -0
  174. package/dist/foundations/xlsx.js +0 -0
  175. package/dist/index.d.ts +19 -0
  176. package/dist/index.js +19 -0
  177. package/dist/primitives/block/Block.d.ts +66 -0
  178. package/dist/primitives/block/Block.js +34 -0
  179. package/dist/primitives/block/Line.d.ts +18 -0
  180. package/dist/primitives/block/Line.js +45 -0
  181. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  182. package/dist/primitives/block/SectionGrid.js +29 -0
  183. package/dist/primitives/block/Steps.d.ts +76 -0
  184. package/dist/primitives/block/Steps.js +43 -0
  185. package/dist/primitives/block/index.d.ts +4 -0
  186. package/dist/primitives/block/index.js +7 -0
  187. package/dist/primitives/button/Button.d.ts +137 -0
  188. package/dist/primitives/button/Button.js +234 -0
  189. package/dist/primitives/button/index.d.ts +1 -0
  190. package/dist/primitives/button/index.js +2 -0
  191. package/dist/primitives/cell/CellStack.d.ts +47 -0
  192. package/dist/primitives/cell/CellStack.js +33 -0
  193. package/dist/primitives/cell/index.d.ts +1 -0
  194. package/dist/primitives/cell/index.js +3 -0
  195. package/dist/primitives/choice/ComboBox.d.ts +19 -0
  196. package/dist/primitives/choice/ComboBox.js +52 -0
  197. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  198. package/dist/primitives/choice/RadioGroup.js +50 -0
  199. package/dist/primitives/choice/SearchField.d.ts +33 -0
  200. package/dist/primitives/choice/SearchField.js +46 -0
  201. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  202. package/dist/primitives/choice/SegmentedControl.js +69 -0
  203. package/dist/primitives/choice/Slider.d.ts +16 -0
  204. package/dist/primitives/choice/Slider.js +46 -0
  205. package/dist/primitives/choice/Switch.d.ts +14 -0
  206. package/dist/primitives/choice/Switch.js +65 -0
  207. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  208. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  209. package/dist/primitives/choice/index.d.ts +8 -0
  210. package/dist/primitives/choice/index.js +11 -0
  211. package/dist/primitives/choice/options.d.ts +28 -0
  212. package/dist/primitives/choice/options.js +6 -0
  213. package/dist/primitives/date/Calendar.d.ts +31 -0
  214. package/dist/primitives/date/Calendar.js +134 -0
  215. package/dist/primitives/date/DatePicker.d.ts +42 -0
  216. package/dist/primitives/date/DatePicker.js +103 -0
  217. package/dist/primitives/date/DateTime.d.ts +35 -0
  218. package/dist/primitives/date/DateTime.js +100 -0
  219. package/dist/primitives/date/index.d.ts +3 -0
  220. package/dist/primitives/date/index.js +6 -0
  221. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  222. package/dist/primitives/dialog/Dialog.js +121 -0
  223. package/dist/primitives/dialog/Drill.d.ts +90 -0
  224. package/dist/primitives/dialog/Drill.js +172 -0
  225. package/dist/primitives/dialog/index.d.ts +2 -0
  226. package/dist/primitives/dialog/index.js +4 -0
  227. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  228. package/dist/primitives/disclosure/Disclosure.js +52 -0
  229. package/dist/primitives/disclosure/index.d.ts +1 -0
  230. package/dist/primitives/disclosure/index.js +2 -0
  231. package/dist/primitives/field/Checks.d.ts +57 -0
  232. package/dist/primitives/field/Checks.js +72 -0
  233. package/dist/primitives/field/DropZone.d.ts +42 -0
  234. package/dist/primitives/field/DropZone.js +52 -0
  235. package/dist/primitives/field/Field.d.ts +178 -0
  236. package/dist/primitives/field/Field.js +251 -0
  237. package/dist/primitives/field/Suggest.d.ts +48 -0
  238. package/dist/primitives/field/Suggest.js +43 -0
  239. package/dist/primitives/field/aria.d.ts +61 -0
  240. package/dist/primitives/field/aria.js +70 -0
  241. package/dist/primitives/field/context.d.ts +30 -0
  242. package/dist/primitives/field/context.js +13 -0
  243. package/dist/primitives/field/copy.d.ts +16 -0
  244. package/dist/primitives/field/copy.js +69 -0
  245. package/dist/primitives/field/index.d.ts +9 -0
  246. package/dist/primitives/field/index.js +15 -0
  247. package/dist/primitives/field/layout.d.ts +24 -0
  248. package/dist/primitives/field/layout.js +10 -0
  249. package/dist/primitives/field/mode.d.ts +9 -0
  250. package/dist/primitives/field/mode.js +13 -0
  251. package/dist/primitives/field/note.d.ts +55 -0
  252. package/dist/primitives/field/note.js +117 -0
  253. package/dist/primitives/hint/Hint.d.ts +31 -0
  254. package/dist/primitives/hint/Hint.js +42 -0
  255. package/dist/primitives/hint/index.d.ts +1 -0
  256. package/dist/primitives/hint/index.js +1 -0
  257. package/dist/primitives/index.d.ts +20 -0
  258. package/dist/primitives/index.js +21 -0
  259. package/dist/primitives/menu/Menu.d.ts +128 -0
  260. package/dist/primitives/menu/Menu.js +89 -0
  261. package/dist/primitives/menu/index.d.ts +1 -0
  262. package/dist/primitives/menu/index.js +2 -0
  263. package/dist/primitives/notice/Callout.d.ts +49 -0
  264. package/dist/primitives/notice/Callout.js +28 -0
  265. package/dist/primitives/notice/Flag.d.ts +13 -0
  266. package/dist/primitives/notice/Flag.js +31 -0
  267. package/dist/primitives/notice/Notice.d.ts +45 -0
  268. package/dist/primitives/notice/Notice.js +61 -0
  269. package/dist/primitives/notice/Toast.d.ts +55 -0
  270. package/dist/primitives/notice/Toast.js +142 -0
  271. package/dist/primitives/notice/index.d.ts +4 -0
  272. package/dist/primitives/notice/index.js +6 -0
  273. package/dist/primitives/overlay/Popover.d.ts +6 -0
  274. package/dist/primitives/overlay/Popover.js +27 -0
  275. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  276. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  277. package/dist/primitives/overlay/Preview.d.ts +19 -0
  278. package/dist/primitives/overlay/Preview.js +60 -0
  279. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  280. package/dist/primitives/overlay/Tooltip.js +74 -0
  281. package/dist/primitives/overlay/index.d.ts +4 -0
  282. package/dist/primitives/overlay/index.js +7 -0
  283. package/dist/primitives/picker/Picker.d.ts +89 -0
  284. package/dist/primitives/picker/Picker.js +204 -0
  285. package/dist/primitives/picker/index.d.ts +1 -0
  286. package/dist/primitives/picker/index.js +2 -0
  287. package/dist/primitives/surface/Peek.d.ts +60 -0
  288. package/dist/primitives/surface/Peek.js +71 -0
  289. package/dist/primitives/surface/index.d.ts +1 -0
  290. package/dist/primitives/surface/index.js +2 -0
  291. package/dist/primitives/table/CellCopy.d.ts +20 -0
  292. package/dist/primitives/table/CellCopy.js +23 -0
  293. package/dist/primitives/table/CellEditor.d.ts +14 -0
  294. package/dist/primitives/table/CellEditor.js +83 -0
  295. package/dist/primitives/table/DataTable.d.ts +189 -0
  296. package/dist/primitives/table/DataTable.js +330 -0
  297. package/dist/primitives/table/EditableTable.d.ts +191 -0
  298. package/dist/primitives/table/EditableTable.js +617 -0
  299. package/dist/primitives/table/PivotTable.d.ts +14 -0
  300. package/dist/primitives/table/PivotTable.js +0 -0
  301. package/dist/primitives/table/ReportTable.d.ts +38 -0
  302. package/dist/primitives/table/ReportTable.js +106 -0
  303. package/dist/primitives/table/RowActions.d.ts +16 -0
  304. package/dist/primitives/table/RowActions.js +28 -0
  305. package/dist/primitives/table/SheetTable.d.ts +73 -0
  306. package/dist/primitives/table/SheetTable.js +230 -0
  307. package/dist/primitives/table/Table.d.ts +106 -0
  308. package/dist/primitives/table/Table.js +116 -0
  309. package/dist/primitives/table/index.d.ts +7 -0
  310. package/dist/primitives/table/index.js +9 -0
  311. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  312. package/dist/primitives/tabs/Tabs.js +56 -0
  313. package/dist/primitives/tabs/index.d.ts +1 -0
  314. package/dist/primitives/tabs/index.js +1 -0
  315. package/dist/primitives/tag/Tag.d.ts +36 -0
  316. package/dist/primitives/tag/Tag.js +68 -0
  317. package/dist/primitives/tag/Tags.d.ts +57 -0
  318. package/dist/primitives/tag/Tags.js +104 -0
  319. package/dist/primitives/tag/TagsField.d.ts +35 -0
  320. package/dist/primitives/tag/TagsField.js +186 -0
  321. package/dist/primitives/tag/choices.d.ts +38 -0
  322. package/dist/primitives/tag/choices.js +26 -0
  323. package/dist/primitives/tag/index.d.ts +4 -0
  324. package/dist/primitives/tag/index.js +8 -0
  325. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  326. package/dist/primitives/toolbar/Toolbar.js +36 -0
  327. package/dist/primitives/toolbar/index.d.ts +1 -0
  328. package/dist/primitives/toolbar/index.js +2 -0
  329. package/dist/primitives/tree/Tree.d.ts +56 -0
  330. package/dist/primitives/tree/Tree.js +74 -0
  331. package/dist/primitives/tree/index.d.ts +1 -0
  332. package/dist/primitives/tree/index.js +3 -0
  333. package/dist/primitives/value/Copyable.d.ts +48 -0
  334. package/dist/primitives/value/Copyable.js +71 -0
  335. package/dist/primitives/value/Fecha.d.ts +12 -0
  336. package/dist/primitives/value/Fecha.js +89 -0
  337. package/dist/primitives/value/Meter.d.ts +29 -0
  338. package/dist/primitives/value/Meter.js +46 -0
  339. package/dist/primitives/value/Metric.d.ts +42 -0
  340. package/dist/primitives/value/Metric.js +38 -0
  341. package/dist/primitives/value/Person.d.ts +90 -0
  342. package/dist/primitives/value/Person.js +208 -0
  343. package/dist/primitives/value/ProgressBar.d.ts +31 -0
  344. package/dist/primitives/value/ProgressBar.js +53 -0
  345. package/dist/primitives/value/Relation.d.ts +74 -0
  346. package/dist/primitives/value/Relation.js +74 -0
  347. package/dist/primitives/value/Totals.d.ts +121 -0
  348. package/dist/primitives/value/Totals.js +54 -0
  349. package/dist/primitives/value/Value.d.ts +40 -0
  350. package/dist/primitives/value/Value.js +80 -0
  351. package/dist/primitives/value/index.d.ts +9 -0
  352. package/dist/primitives/value/index.js +10 -0
  353. package/dist/tokens.css +1062 -0
  354. package/dist/views/index.d.ts +2 -0
  355. package/dist/views/index.js +3 -0
  356. package/dist/views/list/List.d.ts +119 -0
  357. package/dist/views/list/List.js +101 -0
  358. package/dist/views/list/ListView.d.ts +6 -0
  359. package/dist/views/list/ListView.js +295 -0
  360. package/dist/views/list/columns.d.ts +18 -0
  361. package/dist/views/list/columns.js +27 -0
  362. package/dist/views/list/derive.d.ts +87 -0
  363. package/dist/views/list/derive.js +207 -0
  364. package/dist/views/list/index.d.ts +2 -0
  365. package/dist/views/list/index.js +4 -0
  366. package/dist/views/list/presentations.d.ts +39 -0
  367. package/dist/views/list/presentations.js +105 -0
  368. package/dist/views/list/types.d.ts +284 -0
  369. package/dist/views/list/types.js +1 -0
  370. package/dist/views/list/useNarrowList.d.ts +17 -0
  371. package/dist/views/list/useNarrowList.js +32 -0
  372. package/dist/views/record/Record.d.ts +145 -0
  373. package/dist/views/record/Record.js +211 -0
  374. package/dist/views/record/index.d.ts +1 -0
  375. package/dist/views/record/index.js +2 -0
  376. package/package.json +113 -4
@@ -0,0 +1,42 @@
1
+ import { type FormMode } from "./mode.js";
2
+ /**
3
+ * Drop a file, or pick one. Progress is a real bar; failure is a sentence.
4
+ *
5
+ * This is the control. Where it lives —a `Dialog`, a wizard step— is the
6
+ * consumer's. Putting a dashed box on top of a list was the product drawing
7
+ * the control in the wrong place, not the absence of a control.
8
+ *
9
+ * Context: the zone is React Aria's `DropZone` and the button its `FileTrigger`.
10
+ * Dragging is a mouse gesture, so the zone is never the only way in: the
11
+ * «choose file» button opens the system's file dialog from the keyboard, and the
12
+ * zone itself takes a paste (Ctrl/⌘+V of a copied file) and announces a drop to
13
+ * a screen reader. While a file uploads the zone refuses another one — a second
14
+ * drop in the middle of an upload replaces nothing and confuses everything.
15
+ *
16
+ * `accept` filters the file dialog. A drop is NOT filtered: the browser does not
17
+ * say a dragged file's extension until it lands, and a drop that silently does
18
+ * nothing reads as a broken zone. The file reaches `onFile`, and the consumer
19
+ * answers with `error` when it cannot read it.
20
+ */
21
+ export type DropZoneProps = {
22
+ /** The browser's `accept` list: extensions and MIME types, comma-separated
23
+ * (`".pdf,application/pdf"`). */
24
+ accept?: string;
25
+ label?: string;
26
+ busy?: boolean;
27
+ /** 0 to 100, same scale as `ProgressBar`. */
28
+ progress?: number;
29
+ fileName?: string;
30
+ fileDetail?: string;
31
+ error?: string | null;
32
+ onFile: (file: File) => void;
33
+ onCancel?: () => void;
34
+ onRetry?: () => void;
35
+ id?: string;
36
+ describedBy?: string;
37
+ invalid?: boolean;
38
+ /** Forces the mode. By default it inherits the form's: in read mode it draws the
39
+ * file's name. */
40
+ mode?: FormMode;
41
+ };
42
+ export declare function DropZone({ accept, label, busy, progress, fileName, fileDetail, error, onFile, onCancel, onRetry, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, mode, }: DropZoneProps): import("react").JSX.Element;
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { cn } from "../../foundations/variants.js";
5
+ import { Button, buttonClasses } from "../button/Button.js";
6
+ import { useFieldIds } from "./context.js";
7
+ import { ReadValue } from "./Field.js";
8
+ import { useFormMode } from "./mode.js";
9
+ import { Failed } from "../notice/Notice.js";
10
+ import { ProgressBar } from "../value/ProgressBar.js";
11
+ import { Button as AriaButton, DropZone as AriaDropZone, FileTrigger, } from "react-aria-components";
12
+ export function DropZone({ accept, label, busy = false, progress, fileName, fileDetail, error, onFile, onCancel, onRetry, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, mode, }) {
13
+ const copy = useUiCopy();
14
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
15
+ id: ownId,
16
+ describedBy: ownDescribedBy,
17
+ invalid: ownInvalid,
18
+ });
19
+ const readMode = useFormMode(mode) === "read";
20
+ if (readMode)
21
+ return _jsx(ReadValue, { value: fileName ?? null });
22
+ const sentence = label ?? copy.field.drop;
23
+ const types = accept
24
+ ?.split(",")
25
+ .map((t) => t.trim())
26
+ .filter((t) => t !== "");
27
+ async function onDrop(items) {
28
+ if (busy)
29
+ return;
30
+ for (const item of items) {
31
+ if (item.kind === "file") {
32
+ onFile(await item.getFile());
33
+ return;
34
+ }
35
+ }
36
+ }
37
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs(AriaDropZone, { ...(labelledBy === undefined
38
+ ? { "aria-label": sentence }
39
+ : { "aria-labelledby": labelledBy }), "aria-describedby": describedBy, isDisabled: busy, getDropOperation: () => (busy ? "cancel" : "copy"), onDrop: (event) => void onDrop(event.items), className: ({ isDropTarget, isFocusVisible }) => cn("flex flex-col items-center gap-3 rounded-box border px-4 py-6 text-center transition-colors duration-(--duration-instant) outline-primary outline-offset-2", isDropTarget
40
+ ? "border-primary bg-primary/8 forced-colors:border-[Highlight]"
41
+ : invalid
42
+ ? "border-danger bg-surface"
43
+ : "border-line-strong bg-surface", isFocusVisible ? "outline-2" : "outline-0"), children: [_jsx("p", { className: "text-body text-muted", children: sentence }), busy ? (_jsxs("div", { className: "flex w-full max-w-sm flex-col items-center gap-2", children: [fileName === undefined ? null : (_jsx("p", { className: "max-w-full truncate text-body text-ink", children: fileName })), _jsx("div", { className: "w-full", children: _jsx(ProgressBar, { value: progress ?? 0, label: copy.field.uploadProgress, width: "w-full" }) }), fileDetail === undefined ? null : (_jsx("p", { className: "text-meta text-muted", children: fileDetail }))] })) : (_jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [_jsx(FileTrigger, { ...(types === undefined ? {} : { acceptedFileTypes: types }), onSelect: (files) => {
44
+ const file = files?.[0];
45
+ if (file !== undefined)
46
+ onFile(file);
47
+ }, children: _jsx(AriaButton, { id: id, "data-emphasis": "secondary", className: buttonClasses("secondary", "md"), children: copy.field.chooseFile }) }), onCancel === undefined ? null : (_jsx(Button, { role: "ghost", onClick: onCancel, children: copy.field.cancel }))] }))] }), error === undefined || error === null ? null : (_jsx(Failed, { heading: copy.field.fileUnreadable, sentence: error, ...(onRetry === undefined
48
+ ? {}
49
+ : {
50
+ action: (_jsx(Button, { role: "ghost", onClick: onRetry, children: copy.field.tryAnother })),
51
+ }) }))] }));
52
+ }
@@ -0,0 +1,178 @@
1
+ import { type FieldIds } from "./context.js";
2
+ import { type FormMode } from "./mode.js";
3
+ import { type FieldNote } from "./note.js";
4
+ import { type ReactNode } from "react";
5
+ export declare function Field({ label, hint, error, required, children, htmlFor, layout, action, mode, note, copy: copyAs, }: {
6
+ label: string;
7
+ /** What is expected. A `?` on the label that opens it on press. */
8
+ hint?: string;
9
+ /** What has to be corrected. It goes BELOW, close to the cursor, and it says
10
+ * what to put. */
11
+ error?: string;
12
+ required?: boolean;
13
+ /** The control. Written directly — `<Field label="Nombre"><TextInput … /></Field>` — it
14
+ * reads its id, description and error state from the field. The render prop
15
+ * `{(aria) => …}` is still there for a control that is not one of the system's. */
16
+ children: ReactNode | ((ids: FieldIds) => ReactNode);
17
+ htmlFor?: string;
18
+ /** `inline` puts the label on the LEFT, in a fixed-width column. It is what
19
+ * turns twenty stacked fields into a record that reads at a glance: every
20
+ * value starts on the same vertical, so the eye goes down a column instead of
21
+ * bouncing. It needs width — in a narrow drawer, the label eats the field.
22
+ * Omit it to inherit the sheet: `horizontal` there is this, `stacked` is the
23
+ * label above. */
24
+ layout?: "stacked" | "inline";
25
+ /** A button attached to the label: "generate", "clear", "pick from the
26
+ * catalog". It goes above and not below the control because it belongs to the
27
+ * FIELD, not to the form — what goes below is what closes the whole form. */
28
+ action?: ReactNode;
29
+ /** Forces the mode of this field. By default it inherits it from the view. */
30
+ mode?: FormMode;
31
+ /** A note that belongs to THIS field — «vence en 3 días», «tiene una NC sin aplicar ·
32
+ * Aplicar». A card in the record's margin when there is one, a `Flag` by the label when
33
+ * there is not; its sentence describes the control (`aria-describedby`). See `note.tsx`. */
34
+ note?: FieldNote;
35
+ /** The copy button by the label (see `copy.tsx`): on by default, copying what the control
36
+ * shows. A string copies that instead; `false` removes the button from this field. */
37
+ copy?: string | false;
38
+ }): import("react").JSX.Element;
39
+ /**
40
+ * A label with its VALUE, without a control below. What was missing to show a
41
+ * record without pretending it is being edited.
42
+ *
43
+ * Context: until now, showing a value with its name in a form forced you to put
44
+ * a disabled `TextInput`. That draws the box, the border and the height of
45
+ * something you write into, and a whole record like that is twelve boxes empty
46
+ * of meaning.
47
+ *
48
+ * It uses the same geometry as `Field`, so a field that goes from being looked
49
+ * at to being edited does not move anything around it.
50
+ */
51
+ export declare function DataField({ label, value, hint, layout, mono, action, note, copy: copyAs, }: {
52
+ label: string;
53
+ /** `undefined` and `null` are drawn with the missing mark: saying there is no
54
+ * data is information, and a blank line does not give it. */
55
+ value: ReactNode;
56
+ hint?: string;
57
+ layout?: "stacked" | "inline";
58
+ /** The value was written by the machine: an id, a code, an account number. */
59
+ mono?: boolean;
60
+ action?: ReactNode;
61
+ /** A note that belongs to this value — the same as `Field`'s. */
62
+ note?: FieldNote;
63
+ /** The copy button by the label, as `Field`'s: on by default for a value that is there. */
64
+ copy?: string | false;
65
+ }): import("react").JSX.Element;
66
+ /** What a control draws when the form is LOOKED AT instead of written into.
67
+ *
68
+ * It is a single piece and not an `if` repeated in every control: if each one
69
+ * solves its read mode its own way, the record ends up with six different ways
70
+ * of saying the same thing. */
71
+ export declare function ReadValue({ value, mono }: {
72
+ value: ReactNode;
73
+ mono?: boolean;
74
+ }): import("react").JSX.Element;
75
+ /** The shared look of everything that is written into or chosen from.
76
+ *
77
+ * It exists as a function and not as a loose class so that a new control cannot
78
+ * be born with another height, another radius or another focus. That was the
79
+ * original defect: every file wrote its own `<input className="…">`. */
80
+ export declare function controlClasses(invalid?: boolean): string;
81
+ /** What every text control shares: the field's wiring, overridable prop by prop. */
82
+ type ControlProps = {
83
+ id?: string;
84
+ describedBy?: string;
85
+ invalid?: boolean;
86
+ disabled?: boolean;
87
+ /** Forces the mode. By default it inherits the form's: in read mode it draws the value. */
88
+ mode?: FormMode;
89
+ /** The name of the control when it is NOT inside a `Field` — a cell of an editable table,
90
+ * the box of a message. Inside a field, the field's label names it. */
91
+ "aria-label"?: string;
92
+ };
93
+ /**
94
+ * One line of text: a business name, a reference, a NIT.
95
+ *
96
+ * Context: it is React Aria's `TextField` — the field's label, hint and error reach the real
97
+ * `<input>` through `useFieldIds`, and React Aria adds the rest of what a text input owes a
98
+ * screen reader. What it draws is `controlClasses()`, the one box every control shares.
99
+ */
100
+ export declare function TextInput({ id: ownId, value, onChange, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, mono, spellCheck, "aria-label": ariaLabel, }: ControlProps & {
101
+ value: string;
102
+ onChange?: (next: string) => void;
103
+ placeholder?: string;
104
+ /** The value was written by the machine and gets compared with the one next to it: an
105
+ * id, a key, an account code. See the family rule in Typography. */
106
+ mono?: boolean;
107
+ /** `false` for a value that is a token and not prose — a product code, a reference, an
108
+ * account code — so the browser stops underlining identifiers as misspellings. */
109
+ spellCheck?: boolean;
110
+ }): import("react").JSX.Element;
111
+ /**
112
+ * A number typed the way the person writes it: «1.234,56» in Bolivia, «1,234.56» in the
113
+ * United States.
114
+ *
115
+ * Context: it was an `<input type="number">`, and that input only understands the
116
+ * browser's idea of a number — on a Spanish form, «1.234,56» was either refused or read as
117
+ * one point two. It is React Aria's `NumberField` now, inside `AriaLocale`: it parses and
118
+ * formats in the locale of the copy table, not the browser's, so an amount reads the same
119
+ * here as in the listing next to it. `format` takes `Intl.NumberFormatOptions` — a
120
+ * currency, a percentage, two fixed decimals — and the field types and shows it that way.
121
+ *
122
+ * The arrow keys (and the two small buttons) step by `step`; on leaving the field the value
123
+ * is clamped to `min`/`max` and snapped to `step`, so what the form submits is always a
124
+ * number the field would have accepted. The value stays a plain `number`, and `""` is the
125
+ * empty field — never `NaN`, which is how React Aria says it inside.
126
+ */
127
+ export declare function NumberInput({ id: ownId, value, onChange, min, max, step, unit, format, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, "aria-label": ariaLabel, }: ControlProps & {
128
+ value: number | "";
129
+ onChange?: (next: number | "") => void;
130
+ min?: number;
131
+ max?: number;
132
+ step?: number;
133
+ /** The unit, attached to the field: "días", "u.", "kg". A number without a unit in a
134
+ * form is half a question. For money, prefer `format` with a currency. */
135
+ unit?: string;
136
+ /** How the number is written: `{ style: "currency", currency: "BOB" }`,
137
+ * `{ style: "percent" }`, `{ minimumFractionDigits: 2 }`. In the copy's locale. */
138
+ format?: Intl.NumberFormatOptions;
139
+ placeholder?: string;
140
+ }): import("react").JSX.Element;
141
+ /**
142
+ * Several lines of prose: the notes of an order, the reason of a credit note.
143
+ *
144
+ * Context: React Aria's `TextField` with a `TextArea` inside — the same wiring as
145
+ * `TextInput`, the same box, taller and resizable only downwards, because a note grows in
146
+ * lines, not in width.
147
+ */
148
+ export declare function TextArea({ id: ownId, value, onChange, placeholder, rows, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, "aria-label": ariaLabel, }: ControlProps & {
149
+ value: string;
150
+ onChange?: (next: string) => void;
151
+ placeholder?: string;
152
+ rows?: number;
153
+ }): import("react").JSX.Element;
154
+ export interface SelectOption {
155
+ value: string;
156
+ label: string;
157
+ disabled?: boolean;
158
+ }
159
+ /** The system's select, for SHORT and closed lists: the payment terms, the currency, the
160
+ * warehouse.
161
+ *
162
+ * Context: it was a native `<select>`, and its open list was the operating system's — its
163
+ * font, its colors, its radius, in the middle of a form drawn with the system's. It is
164
+ * React Aria's `Select` now: the trigger is the system's control box and the list floats
165
+ * on the system's `Popover`, row for row the same as `ComboBox`'s. The keyboard is still a
166
+ * select's: arrows open it and move, typing a letter jumps to the option, Enter picks.
167
+ *
168
+ * As soon as the list has to show two pieces of data per option —a name and its tax id—
169
+ * or grows past a screen, it is a `ComboBox` (type to find) or a `Picker` (records with
170
+ * their metadata). An option here is ONE LINE OF TEXT. */
171
+ export declare function Select({ id: ownId, value, options, onChange, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, placeholder, mode, "aria-label": ariaLabel, }: ControlProps & {
172
+ /** The chosen option's `value`; `""` is nothing chosen, and draws the placeholder. */
173
+ value: string;
174
+ options: readonly SelectOption[];
175
+ onChange?: (next: string) => void;
176
+ placeholder?: string;
177
+ }): import("react").JSX.Element;
178
+ export {};
@@ -0,0 +1,251 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
+ import { AriaLocale } from "../../foundations/aria.js";
4
+ import { useUiCopy } from "../../foundations/copy.js";
5
+ import { byDensity, useDensity } from "../../foundations/density.js";
6
+ import { focusRing, tv } from "../../foundations/variants.js";
7
+ import { FieldGroup, listOptionStyles } from "./aria.js";
8
+ import { FieldIdsContext, useFieldIds, } from "./context.js";
9
+ import { FieldCopy, useFieldCopy } from "./copy.js";
10
+ import { useSheetLayout } from "./layout.js";
11
+ import { FormModeProvider, useFormMode, } from "./mode.js";
12
+ import { describedByOf, NoteCard, NoteFlag, } from "./note.js";
13
+ import { Hint } from "../hint/Hint.js";
14
+ import { Popover } from "../overlay/Popover.js";
15
+ import { AlertCircle, Check, ChevronDown, ChevronUp } from "lucide-react";
16
+ import { useId, useRef } from "react";
17
+ import { Button as AriaButton, Input, ListBox, ListBoxItem, NumberField as AriaNumberField, Select as AriaSelect, SelectValue, TextArea as AriaTextArea, TextField as AriaTextField, } from "react-aria-components";
18
+ /* ==================================================================== *
19
+ * THE CONTROLS — the label is not optional and the error is not a color.
20
+ *
21
+ * The state we came from is on record: twenty raw controls across six files,
22
+ * **none of them with an associated label or `aria-describedby`**, and all of
23
+ * them 24 px tall. That is not a matter of abstract accessibility: a `label`
24
+ * without `htmlFor` is a label that does not enlarge the click area, and a hint
25
+ * that is not linked is a hint a screen reader never reads.
26
+ *
27
+ * ── The error says WHAT to do, not that something is wrong ──────────
28
+ *
29
+ * "Invalid value" is no use. An error message has to say what was expected
30
+ * —"it has to be a number between 0 and 1"— because whoever made the mistake
31
+ * already knows they made it: what they don't know is what to put.
32
+ *
33
+ * And it is never ONLY color: it carries an icon and text. A red border is
34
+ * invisible to someone who doesn't tell red apart, and also to someone who is
35
+ * looking at another part of the screen.
36
+ *
37
+ * ── The hint sits on the label, the error BELOW the control ─────────
38
+ *
39
+ * Help is a `?` next to the name, opened with a press. A paragraph above the
40
+ * control ate a line on every field. The error still goes below, close to
41
+ * the cursor, and it says what to put.
42
+ * ==================================================================== */
43
+ export function Field({ label, hint, error, required = false, children, htmlFor, layout, action, mode, note, copy: copyAs, }) {
44
+ const d = useDensity();
45
+ const copy = useUiCopy();
46
+ const copies = useFieldCopy() && copyAs !== false;
47
+ const shown = useRef(null);
48
+ const auto = useId();
49
+ const id = htmlFor ?? auto;
50
+ const sheetLayout = useSheetLayout();
51
+ const inline = layout === "inline" || (layout === undefined && sheetLayout === "horizontal");
52
+ const hintId = `${id}-hint`;
53
+ const errorId = `${id}-error`;
54
+ const noteId = `${id}-note`;
55
+ /* The error first: what has to be corrected is heard before what is worth knowing. */
56
+ const describedBy = describedByOf(hint === undefined ? null : hintId, error === undefined ? null : errorId, note === undefined ? null : noteId);
57
+ const labelNode = (_jsxs("span", { className: `flex min-w-0 items-center gap-1 text-micro font-medium text-muted ${inline ? "pt-2" : ""}`, children: [_jsxs("label", { id: `${id}-label`, htmlFor: id, className: inline ? "min-w-0 truncate" : "", children: [label, required ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "text-ink", "aria-hidden": true, children: [" ", "*"] }), _jsxs("span", { className: "sr-only", children: [", ", copy.field.required] })] })) : null] }), hint === undefined ? null : _jsx(Hint, { id: hintId, children: hint }), note === undefined ? null : _jsx(NoteFlag, { note: note }), copies ? (_jsx(FieldCopy, { label: label, scope: shown, ...(typeof copyAs === "string" ? { value: copyAs } : {}) })) : null] }));
58
+ /* Last in the field so it is read last; absolutely placed, it still sits at the field's
59
+ top — the static position of an absolute child of a flex or grid box is its start. */
60
+ const noteCard = note === undefined ? null : _jsx(NoteCard, { note: note, id: noteId, owner: label });
61
+ /* The field's `mode` propagates INWARDS and is not used here: what knows how
62
+ to draw itself as data is the control, not the label. That way a computed
63
+ field stays in read mode inside a form that is being edited, without the
64
+ consumer having to pass it to the control as well. */
65
+ const ids = {
66
+ id,
67
+ labelId: `${id}-label`,
68
+ describedBy,
69
+ invalid: error !== undefined,
70
+ };
71
+ /* The render prop gets the same context as a control written directly: a system control
72
+ inside it then also finds the label that names it, not only the id it is handed. */
73
+ const content = (_jsx(FieldIdsContext.Provider, { value: ids, children: _jsx("div", { ref: shown, className: "contents", children: typeof children === "function" ? children(ids) : children }) }));
74
+ const body = (_jsxs(_Fragment, { children: [mode === undefined ? (content) : (_jsx(FormModeProvider, { value: mode, children: content })), error === undefined ? null : (_jsxs("p", { id: errorId, role: "alert", className: "flex items-start gap-1.5 text-micro text-danger", children: [_jsx(AlertCircle, { className: "mt-px size-(--size-icon-sm) shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "max-w-prose", children: error })] }))] }));
75
+ if (inline) {
76
+ return (_jsxs("div", { className: "grid-label grid min-w-0 items-start gap-x-4 gap-y-1 group/field", children: [labelNode, _jsx("div", { className: "flex min-w-0 flex-col gap-1", children: body }), action === undefined ? null : (_jsx("div", { "data-under-control": true, className: "flex items-center gap-2", children: action })), noteCard] }));
77
+ }
78
+ return (_jsxs("div", { className: `group/field flex min-w-0 flex-col ${byDensity(d, "gap-1", "gap-0.5")}`, children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [labelNode, action === undefined ? null : _jsx("span", { className: "ml-auto shrink-0", children: action })] }), body, noteCard] }));
79
+ }
80
+ /**
81
+ * A label with its VALUE, without a control below. What was missing to show a
82
+ * record without pretending it is being edited.
83
+ *
84
+ * Context: until now, showing a value with its name in a form forced you to put
85
+ * a disabled `TextInput`. That draws the box, the border and the height of
86
+ * something you write into, and a whole record like that is twelve boxes empty
87
+ * of meaning.
88
+ *
89
+ * It uses the same geometry as `Field`, so a field that goes from being looked
90
+ * at to being edited does not move anything around it.
91
+ */
92
+ export function DataField({ label, value, hint, layout, mono = false, action, note, copy: copyAs, }) {
93
+ const d = useDensity();
94
+ const shown = useRef(null);
95
+ const allowed = useFieldCopy() && copyAs !== false;
96
+ const noteId = `${useId()}-note`;
97
+ const sheetLayout = useSheetLayout();
98
+ const inline = layout === "inline" || (layout === undefined && sheetLayout === "horizontal");
99
+ const empty = value === undefined || value === null || value === "";
100
+ const unset = useUiCopy().field.unset;
101
+ const labelNode = (_jsxs("span", { className: `flex min-w-0 items-center gap-1 text-micro font-medium text-muted ${inline ? "pt-0.5" : ""}`, children: [_jsx("span", { className: inline ? "min-w-0 truncate" : "", children: label }), hint === undefined ? null : _jsx(Hint, { children: hint }), note === undefined ? null : _jsx(NoteFlag, { note: note }), allowed && !empty ? (_jsx(FieldCopy, { label: label, scope: shown, ...(typeof copyAs === "string" ? { value: copyAs } : {}) })) : null] }));
102
+ const noteCard = note === undefined ? null : _jsx(NoteCard, { note: note, id: noteId, owner: label });
103
+ const valueNode = (_jsx("span", { ref: shown, ...(note === undefined ? {} : { "aria-describedby": noteId }), className: `min-w-0 text-meta ${empty ? "text-muted italic" : "text-ink"} ${mono ? "font-mono tabular-nums" : ""}`, children: empty ? unset : value }));
104
+ if (inline) {
105
+ return (_jsxs("div", { className: "grid-label grid min-w-0 items-baseline gap-x-4 gap-y-0.5 group/field", children: [labelNode, _jsxs("span", { className: "flex min-w-0 items-baseline gap-2", children: [valueNode, action] }), noteCard] }));
106
+ }
107
+ return (_jsxs("div", { className: `group/field flex min-w-0 flex-col ${byDensity(d, "gap-1", "gap-0.5")}`, children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [labelNode, action === undefined ? null : _jsx("span", { className: "ml-auto shrink-0", children: action })] }), valueNode, noteCard] }));
108
+ }
109
+ /** What a control draws when the form is LOOKED AT instead of written into.
110
+ *
111
+ * It is a single piece and not an `if` repeated in every control: if each one
112
+ * solves its read mode its own way, the record ends up with six different ways
113
+ * of saying the same thing. */
114
+ export function ReadValue({ value, mono = false }) {
115
+ const unset = useUiCopy().field.unset;
116
+ const empty = value === undefined || value === null || value === "";
117
+ return (_jsx("span", { className: `flex min-h-(--size-control-md) min-w-0 items-center text-meta ${empty ? "text-muted italic" : "text-ink"} ${mono ? "font-mono tabular-nums" : ""}`, children: empty ? unset : value }));
118
+ }
119
+ /** The shared look of everything that is written into or chosen from.
120
+ *
121
+ * It exists as a function and not as a loose class so that a new control cannot
122
+ * be born with another height, another radius or another focus. That was the
123
+ * original defect: every file wrote its own `<input className="…">`. */
124
+ export function controlClasses(invalid = false) {
125
+ return `h-(--size-control-md) w-full min-w-0 rounded-control border bg-surface px-2.5 text-meta text-ink transition-colors duration-(--duration-instant) placeholder:text-muted focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:bg-ground disabled:text-muted ${invalid
126
+ ? "border-danger focus-visible:border-danger focus-visible:outline-danger"
127
+ : "border-line-strong focus-visible:border-primary focus-visible:outline-primary"}`;
128
+ }
129
+ /* `aria-describedby` goes on the INPUT and not on React Aria's field: the field adds the ids
130
+ of its own description and error slots, which this system never renders (`Field` draws
131
+ them), and the input's own prop replaces that list with the field's real one. */
132
+ /** The accessible name of a control. Inside a `Field` the field's label names it; outside,
133
+ * the consumer's `aria-label`, and as a last resort the placeholder — which is what a
134
+ * browser reads for a bare `<input placeholder>` anyway, so a control outside a form is
135
+ * never nameless. */
136
+ function nameOf(labelledBy, ariaLabel, placeholder) {
137
+ return labelledBy === undefined ? (ariaLabel ?? placeholder) : ariaLabel;
138
+ }
139
+ /**
140
+ * One line of text: a business name, a reference, a NIT.
141
+ *
142
+ * Context: it is React Aria's `TextField` — the field's label, hint and error reach the real
143
+ * `<input>` through `useFieldIds`, and React Aria adds the rest of what a text input owes a
144
+ * screen reader. What it draws is `controlClasses()`, the one box every control shares.
145
+ */
146
+ export function TextInput({ id: ownId, value, onChange, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, mono = false, spellCheck, "aria-label": ariaLabel, }) {
147
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
148
+ id: ownId,
149
+ describedBy: ownDescribedBy,
150
+ invalid: ownInvalid,
151
+ });
152
+ if (useFormMode(mode) === "read")
153
+ return _jsx(ReadValue, { value: value, mono: mono });
154
+ return (_jsx(AriaTextField, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), value: value, onChange: (next) => onChange?.(next), isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: _jsx(Input, { "aria-describedby": describedBy, placeholder: placeholder, spellCheck: spellCheck, className: `${controlClasses(invalid)} ${mono ? "font-mono tabular-nums" : ""}` }) }));
155
+ }
156
+ /** The amount, the quantity, the percentage — read in the copy's locale, every time. */
157
+ function useReadNumber(format) {
158
+ const { locale } = useUiCopy();
159
+ const formatter = new Intl.NumberFormat(locale, format);
160
+ return (value) => formatter.format(value);
161
+ }
162
+ const stepper = "flex min-h-0 flex-1 cursor-default items-center justify-center px-1 text-muted transition-colors duration-(--duration-instant) outline-0 hover:bg-ink/6 hover:text-ink pressed:bg-ink/10 disabled:cursor-not-allowed disabled:text-line-strong disabled:hover:bg-transparent forced-colors:disabled:text-[GrayText]";
163
+ /**
164
+ * A number typed the way the person writes it: «1.234,56» in Bolivia, «1,234.56» in the
165
+ * United States.
166
+ *
167
+ * Context: it was an `<input type="number">`, and that input only understands the
168
+ * browser's idea of a number — on a Spanish form, «1.234,56» was either refused or read as
169
+ * one point two. It is React Aria's `NumberField` now, inside `AriaLocale`: it parses and
170
+ * formats in the locale of the copy table, not the browser's, so an amount reads the same
171
+ * here as in the listing next to it. `format` takes `Intl.NumberFormatOptions` — a
172
+ * currency, a percentage, two fixed decimals — and the field types and shows it that way.
173
+ *
174
+ * The arrow keys (and the two small buttons) step by `step`; on leaving the field the value
175
+ * is clamped to `min`/`max` and snapped to `step`, so what the form submits is always a
176
+ * number the field would have accepted. The value stays a plain `number`, and `""` is the
177
+ * empty field — never `NaN`, which is how React Aria says it inside.
178
+ */
179
+ export function NumberInput({ id: ownId, value, onChange, min, max, step, unit, format, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, "aria-label": ariaLabel, }) {
180
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
181
+ id: ownId,
182
+ describedBy: ownDescribedBy,
183
+ invalid: ownInvalid,
184
+ });
185
+ const read = useReadNumber(format);
186
+ if (useFormMode(mode) === "read") {
187
+ return (_jsx(ReadValue, { value: value === "" ? null : (_jsxs("span", { className: "tabular-nums", children: [read(value), unit === undefined ? "" : ` ${unit}`] })) }));
188
+ }
189
+ return (_jsx(AriaLocale, { children: _jsxs(AriaNumberField, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), value: value === "" ? Number.NaN : value, onChange: (next) => onChange?.(Number.isNaN(next) ? "" : next), minValue: min, maxValue: max, step: step, formatOptions: format, isInvalid: invalid, isDisabled: disabled, className: "flex w-full min-w-0 items-center gap-2", children: [_jsxs(FieldGroup, { children: [_jsx(Input, { "aria-describedby": describedBy, placeholder: placeholder, className: "h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink tabular-nums outline-0 placeholder:text-muted disabled:cursor-not-allowed" }), _jsxs("span", { className: "flex h-full shrink-0 flex-col border-l border-line", children: [_jsx(AriaButton, { slot: "increment", className: stepper, children: _jsx(ChevronUp, { "aria-hidden": true, className: "size-(--size-icon-sm)", strokeWidth: 2 }) }), _jsx(AriaButton, { slot: "decrement", className: `${stepper} border-t border-line`, children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-sm)", strokeWidth: 2 }) })] })] }), unit === undefined ? null : (_jsx("span", { className: "shrink-0 text-meta whitespace-nowrap text-muted", children: unit }))] }) }));
190
+ }
191
+ /**
192
+ * Several lines of prose: the notes of an order, the reason of a credit note.
193
+ *
194
+ * Context: React Aria's `TextField` with a `TextArea` inside — the same wiring as
195
+ * `TextInput`, the same box, taller and resizable only downwards, because a note grows in
196
+ * lines, not in width.
197
+ */
198
+ export function TextArea({ id: ownId, value, onChange, placeholder, rows = 3, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, "aria-label": ariaLabel, }) {
199
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
200
+ id: ownId,
201
+ describedBy: ownDescribedBy,
202
+ invalid: ownInvalid,
203
+ });
204
+ /* The paragraph in read mode keeps its line breaks: a long text without them
205
+ is a block, and whoever wrote it put them there for a reason. */
206
+ if (useFormMode(mode) === "read") {
207
+ return value === "" ? (_jsx(ReadValue, { value: null })) : (_jsx("span", { className: "text-meta whitespace-pre-line text-ink", children: value }));
208
+ }
209
+ return (_jsx(AriaTextField, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), value: value, onChange: (next) => onChange?.(next), isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: _jsx(AriaTextArea, { "aria-describedby": describedBy, rows: rows, placeholder: placeholder, className: `${controlClasses(invalid)} h-auto resize-y py-1.5 leading-relaxed` }) }));
210
+ }
211
+ const selectTrigger = tv({
212
+ extend: focusRing,
213
+ base: "flex h-(--size-control-md) w-full min-w-0 cursor-default items-center gap-2 rounded-control border bg-surface pr-2 pl-2.5 text-start text-meta text-ink transition-colors duration-(--duration-instant) forced-colors:bg-[Field]",
214
+ variants: {
215
+ isFocusVisible: {
216
+ true: "border-primary",
217
+ },
218
+ isInvalid: {
219
+ false: "border-line-strong forced-colors:border-[ButtonBorder]",
220
+ true: "border-danger outline-danger forced-colors:border-[Mark]",
221
+ },
222
+ isDisabled: {
223
+ true: "cursor-not-allowed bg-ground text-muted forced-colors:text-[GrayText]",
224
+ },
225
+ },
226
+ });
227
+ /** The system's select, for SHORT and closed lists: the payment terms, the currency, the
228
+ * warehouse.
229
+ *
230
+ * Context: it was a native `<select>`, and its open list was the operating system's — its
231
+ * font, its colors, its radius, in the middle of a form drawn with the system's. It is
232
+ * React Aria's `Select` now: the trigger is the system's control box and the list floats
233
+ * on the system's `Popover`, row for row the same as `ComboBox`'s. The keyboard is still a
234
+ * select's: arrows open it and move, typing a letter jumps to the option, Enter picks.
235
+ *
236
+ * As soon as the list has to show two pieces of data per option —a name and its tax id—
237
+ * or grows past a screen, it is a `ComboBox` (type to find) or a `Picker` (records with
238
+ * their metadata). An option here is ONE LINE OF TEXT. */
239
+ export function Select({ id: ownId, value, options, onChange, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, placeholder, mode, "aria-label": ariaLabel, }) {
240
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
241
+ id: ownId,
242
+ describedBy: ownDescribedBy,
243
+ invalid: ownInvalid,
244
+ });
245
+ /* In read mode the option's LABEL is drawn, not its value: `CONTADO` is the backend's
246
+ key, and what the form promised to show was «Al contado». */
247
+ if (useFormMode(mode) === "read") {
248
+ return _jsx(ReadValue, { value: options.find((o) => o.value === value)?.label ?? null });
249
+ }
250
+ return (_jsx(AriaLocale, { children: _jsxs(AriaSelect, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), "aria-describedby": describedBy, value: value === "" ? null : value, onChange: (next) => onChange?.(next === null ? "" : String(next)), placeholder: placeholder, isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: [_jsxs(AriaButton, { className: (state) => selectTrigger({ ...state, isInvalid: invalid }), children: [_jsx(SelectValue, { className: "min-w-0 flex-1 truncate data-placeholder:text-muted", children: ({ isPlaceholder, selectedText, defaultChildren }) => isPlaceholder ? defaultChildren : selectedText }), _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-md) shrink-0 text-muted", strokeWidth: 2 })] }), _jsx(Popover, { placement: "bottom start", className: "min-w-(--trigger-width)", children: _jsx(ListBox, { items: options, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (option) => (_jsx(ListBoxItem, { id: option.value, textValue: option.label, isDisabled: option.disabled, className: listOptionStyles, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: `min-w-0 flex-1 truncate ${isSelected ? "font-medium" : ""}`, children: option.label }), _jsx("span", { className: "flex size-(--size-icon-md) shrink-0 items-center justify-center", children: isSelected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-md) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2 })) : null })] })) })) }) })] }) }));
251
+ }
@@ -0,0 +1,48 @@
1
+ import { type FormMode } from "./mode.js";
2
+ /**
3
+ * A text with suggestions: the open field that proposes.
4
+ *
5
+ * Context: there were two text controls and neither served the most common case of an ERP
6
+ * form. `Select` is a CLOSED list —what is not there cannot be written— and `TextInput` is
7
+ * free but does not help. The unit of a product, the bank of a payment, the city of a
8
+ * customer: fields where values already exist and they have to be offered **without
9
+ * forbidding a new one**.
10
+ *
11
+ * What not having it costs shows in the data: without suggestions «Bs.», «BOB» and
12
+ * «bolivianos» end up in the same column, and then somebody writes a `CASE` to unify them.
13
+ *
14
+ * ── Why it is not a `<datalist>` ────────────────────────────────────
15
+ *
16
+ * Because every browser draws it its own way and none follows the tokens: in the middle of a
17
+ * form with the system's type, radius and colors, an operating-system dropdown appears. It is
18
+ * React Aria's `ComboBox` with `allowsCustomValue` instead: the list floats on the system's
19
+ * `Popover`, the keyboard is a combobox's (arrows move, Enter picks, Escape closes), and
20
+ * whatever is typed stays typed. The filter is React Aria's `contains` in the copy's locale,
21
+ * so «potosi» finds «Potosí».
22
+ *
23
+ * ── What it is NOT ──────────────────────────────────────────────────
24
+ *
25
+ * It is not `ComboBox`, whose value must be one of its options, nor `Picker`, which chooses
26
+ * ONE record among thousands with its own search, metadata rows and dialog. This is a text
27
+ * field that also proposes: the list is short, it is of values and not of records, and what
28
+ * is stored is what was written.
29
+ */
30
+ export type SuggestProps = {
31
+ id?: string;
32
+ value: string;
33
+ onChange?: (next: string) => void;
34
+ /** What already exists. Short: if they are hundreds, what is needed is a `Picker`. */
35
+ suggestions: readonly string[];
36
+ placeholder?: string;
37
+ describedBy?: string;
38
+ invalid?: boolean;
39
+ disabled?: boolean;
40
+ mono?: boolean;
41
+ mode?: FormMode;
42
+ /** How many are shown at once. More than this is a list to be read, and reading a list
43
+ * is what this control exists to avoid. */
44
+ max?: number;
45
+ /** The name of the field when it is not inside a `Field`. */
46
+ "aria-label"?: string;
47
+ };
48
+ export declare function Suggest({ mode, ...props }: SuggestProps): import("react").JSX.Element;
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { AriaLocale } from "../../foundations/aria.js";
4
+ import { FieldGroup, listOptionStyles } from "./aria.js";
5
+ import { useFieldIds } from "./context.js";
6
+ import { ReadValue } from "./Field.js";
7
+ import { useFormMode } from "./mode.js";
8
+ import { Popover } from "../overlay/Popover.js";
9
+ import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, useFilter, } from "react-aria-components";
10
+ const MAX = 8;
11
+ export function Suggest({ mode, ...props }) {
12
+ if (useFormMode(mode) === "read")
13
+ return _jsx(ReadValue, { value: props.value, mono: props.mono });
14
+ /* The filter reads the locale from React Aria's context, so the box that provides it has
15
+ to be ABOVE the component that asks for it. */
16
+ return (_jsx(AriaLocale, { children: _jsx(EditableSuggest, { ...props }) }));
17
+ }
18
+ function EditableSuggest({ id: ownId, value, onChange, suggestions, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mono = false, max = MAX, "aria-label": ariaLabel, }) {
19
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
20
+ id: ownId,
21
+ describedBy: ownDescribedBy,
22
+ invalid: ownInvalid,
23
+ });
24
+ const { contains } = useFilter({ sensitivity: "base" });
25
+ /* Filtered by what is typed, hiding the one that ALREADY is exactly the value: offering
26
+ what someone just finished typing is a suggestion that suggests nothing. The list is
27
+ filtered here and not by React Aria because of that rule and of `max`. */
28
+ const needle = value.trim();
29
+ /* «Exactly» ignores case but NOT accents: «potosi» still offers «Potosí», which is the
30
+ spelling the person was reaching for. */
31
+ const exact = needle.toLocaleLowerCase();
32
+ const shown = suggestions
33
+ .filter((one) => one.toLocaleLowerCase() !== exact)
34
+ .filter((one) => needle === "" || contains(one, needle))
35
+ .slice(0, max)
36
+ .map((one) => ({ id: one }));
37
+ return (_jsxs(AriaComboBox, { id: id, "aria-labelledby": labelledBy, "aria-label": labelledBy === undefined ? (ariaLabel ?? placeholder) : ariaLabel,
38
+ /* An open field: what is typed is the value, a suggestion only fills it faster. */
39
+ allowsCustomValue: true, inputValue: value, onInputChange: (next) => onChange?.(next), items: shown,
40
+ /* It proposes as soon as the field is entered, like the hand-written one did: the
41
+ suggestions are most useful BEFORE anything is typed. */
42
+ menuTrigger: "focus", isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: [_jsx(FieldGroup, { children: _jsx(Input, { "aria-describedby": describedBy, placeholder: placeholder, className: `h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed ${mono ? "font-mono" : ""}` }) }), _jsx(Popover, { placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.id, textValue: item.id, className: listOptionStyles, children: _jsx("span", { className: `min-w-0 truncate ${mono ? "font-mono" : ""}`, children: item.id }) })) }) })] }));
43
+ }