@sincpro/web-ui 0.0.0-stage → 0.1.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 (354) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/dist/components/assist/AssistMark.d.ts +64 -0
  4. package/dist/components/assist/AssistMark.js +108 -0
  5. package/dist/components/assist/AssistPending.d.ts +24 -0
  6. package/dist/components/assist/AssistPending.js +26 -0
  7. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  8. package/dist/components/assist/AssistSuggestion.js +55 -0
  9. package/dist/components/assist/ChangeReview.d.ts +56 -0
  10. package/dist/components/assist/ChangeReview.js +121 -0
  11. package/dist/components/assist/assist.d.ts +77 -0
  12. package/dist/components/assist/assist.js +87 -0
  13. package/dist/components/assist/index.d.ts +5 -0
  14. package/dist/components/assist/index.js +7 -0
  15. package/dist/components/chatter/Chatter.d.ts +161 -0
  16. package/dist/components/chatter/Chatter.js +232 -0
  17. package/dist/components/chatter/Messages.d.ts +66 -0
  18. package/dist/components/chatter/Messages.js +43 -0
  19. package/dist/components/chatter/Timeline.d.ts +21 -0
  20. package/dist/components/chatter/Timeline.js +30 -0
  21. package/dist/components/chatter/index.d.ts +3 -0
  22. package/dist/components/chatter/index.js +4 -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 +43 -0
  26. package/dist/components/form/Sheet.js +83 -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 +74 -0
  40. package/dist/components/lines/LineItems.js +229 -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 +73 -0
  52. package/dist/components/listing/ListingBar.js +105 -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 +106 -0
  60. package/dist/components/listing/Row.js +122 -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 +71 -0
  96. package/dist/components/record/RecordHeader.js +72 -0
  97. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  98. package/dist/components/record/RecordSkeleton.js +21 -0
  99. package/dist/components/record/SmartButtons.d.ts +35 -0
  100. package/dist/components/record/SmartButtons.js +38 -0
  101. package/dist/components/record/StatusBar.d.ts +51 -0
  102. package/dist/components/record/StatusBar.js +168 -0
  103. package/dist/components/record/ThumbBar.d.ts +36 -0
  104. package/dist/components/record/ThumbBar.js +86 -0
  105. package/dist/components/record/index.d.ts +6 -0
  106. package/dist/components/record/index.js +8 -0
  107. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  108. package/dist/components/search/CriteriaBuilder.js +278 -0
  109. package/dist/components/search/CriteriaChips.d.ts +22 -0
  110. package/dist/components/search/CriteriaChips.js +64 -0
  111. package/dist/components/search/FilterBuilder.d.ts +142 -0
  112. package/dist/components/search/FilterBuilder.js +421 -0
  113. package/dist/components/search/JoinWord.d.ts +8 -0
  114. package/dist/components/search/JoinWord.js +47 -0
  115. package/dist/components/search/JsonEditor.d.ts +19 -0
  116. package/dist/components/search/JsonEditor.js +78 -0
  117. package/dist/components/search/SearchBar.d.ts +78 -0
  118. package/dist/components/search/SearchBar.js +387 -0
  119. package/dist/components/search/criteria.d.ts +116 -0
  120. package/dist/components/search/criteria.js +159 -0
  121. package/dist/components/search/datePeriods.d.ts +122 -0
  122. package/dist/components/search/datePeriods.js +134 -0
  123. package/dist/components/search/dateRanges.d.ts +54 -0
  124. package/dist/components/search/dateRanges.js +89 -0
  125. package/dist/components/search/index.d.ts +9 -0
  126. package/dist/components/search/index.js +20 -0
  127. package/dist/components/search/model.d.ts +54 -0
  128. package/dist/components/search/model.js +37 -0
  129. package/dist/components/search/query.d.ts +166 -0
  130. package/dist/components/search/query.js +436 -0
  131. package/dist/components/shell/AppFrame.d.ts +14 -0
  132. package/dist/components/shell/AppFrame.js +80 -0
  133. package/dist/components/shell/TopBar.d.ts +73 -0
  134. package/dist/components/shell/TopBar.js +54 -0
  135. package/dist/components/shell/headerSlot.d.ts +17 -0
  136. package/dist/components/shell/headerSlot.js +21 -0
  137. package/dist/components/shell/index.d.ts +2 -0
  138. package/dist/components/shell/index.js +3 -0
  139. package/dist/foundations/aria.d.ts +14 -0
  140. package/dist/foundations/aria.js +18 -0
  141. package/dist/foundations/calendar.d.ts +28 -0
  142. package/dist/foundations/calendar.js +183 -0
  143. package/dist/foundations/copy.d.ts +619 -0
  144. package/dist/foundations/copy.es.d.ts +2 -0
  145. package/dist/foundations/copy.es.js +377 -0
  146. package/dist/foundations/copy.js +472 -0
  147. package/dist/foundations/density.d.ts +16 -0
  148. package/dist/foundations/density.js +21 -0
  149. package/dist/foundations/grouping.d.ts +281 -0
  150. package/dist/foundations/grouping.js +0 -0
  151. package/dist/foundations/index.d.ts +14 -0
  152. package/dist/foundations/index.js +30 -0
  153. package/dist/foundations/layout.d.ts +9 -0
  154. package/dist/foundations/layout.js +9 -0
  155. package/dist/foundations/pivot.d.ts +222 -0
  156. package/dist/foundations/pivot.js +198 -0
  157. package/dist/foundations/pivotSheet.d.ts +61 -0
  158. package/dist/foundations/pivotSheet.js +0 -0
  159. package/dist/foundations/slots.d.ts +67 -0
  160. package/dist/foundations/slots.js +63 -0
  161. package/dist/foundations/sticky.d.ts +53 -0
  162. package/dist/foundations/sticky.js +150 -0
  163. package/dist/foundations/surface.d.ts +9 -0
  164. package/dist/foundations/surface.js +26 -0
  165. package/dist/foundations/theme.d.ts +42 -0
  166. package/dist/foundations/theme.js +88 -0
  167. package/dist/foundations/types.d.ts +23 -0
  168. package/dist/foundations/types.js +50 -0
  169. package/dist/foundations/variants.d.ts +19 -0
  170. package/dist/foundations/variants.js +48 -0
  171. package/dist/foundations/xlsx.d.ts +86 -0
  172. package/dist/foundations/xlsx.js +0 -0
  173. package/dist/index.d.ts +19 -0
  174. package/dist/index.js +19 -0
  175. package/dist/primitives/block/Block.d.ts +66 -0
  176. package/dist/primitives/block/Block.js +34 -0
  177. package/dist/primitives/block/Line.d.ts +18 -0
  178. package/dist/primitives/block/Line.js +45 -0
  179. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  180. package/dist/primitives/block/SectionGrid.js +29 -0
  181. package/dist/primitives/block/Steps.d.ts +76 -0
  182. package/dist/primitives/block/Steps.js +43 -0
  183. package/dist/primitives/block/index.d.ts +4 -0
  184. package/dist/primitives/block/index.js +7 -0
  185. package/dist/primitives/button/Button.d.ts +137 -0
  186. package/dist/primitives/button/Button.js +234 -0
  187. package/dist/primitives/button/index.d.ts +1 -0
  188. package/dist/primitives/button/index.js +2 -0
  189. package/dist/primitives/cell/CellStack.d.ts +47 -0
  190. package/dist/primitives/cell/CellStack.js +33 -0
  191. package/dist/primitives/cell/index.d.ts +1 -0
  192. package/dist/primitives/cell/index.js +3 -0
  193. package/dist/primitives/choice/ComboBox.d.ts +16 -0
  194. package/dist/primitives/choice/ComboBox.js +52 -0
  195. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  196. package/dist/primitives/choice/RadioGroup.js +50 -0
  197. package/dist/primitives/choice/SearchField.d.ts +24 -0
  198. package/dist/primitives/choice/SearchField.js +27 -0
  199. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  200. package/dist/primitives/choice/SegmentedControl.js +69 -0
  201. package/dist/primitives/choice/Slider.d.ts +16 -0
  202. package/dist/primitives/choice/Slider.js +46 -0
  203. package/dist/primitives/choice/Switch.d.ts +14 -0
  204. package/dist/primitives/choice/Switch.js +65 -0
  205. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  206. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  207. package/dist/primitives/choice/index.d.ts +8 -0
  208. package/dist/primitives/choice/index.js +11 -0
  209. package/dist/primitives/choice/options.d.ts +28 -0
  210. package/dist/primitives/choice/options.js +6 -0
  211. package/dist/primitives/date/Calendar.d.ts +31 -0
  212. package/dist/primitives/date/Calendar.js +110 -0
  213. package/dist/primitives/date/DatePicker.d.ts +34 -0
  214. package/dist/primitives/date/DatePicker.js +99 -0
  215. package/dist/primitives/date/index.d.ts +2 -0
  216. package/dist/primitives/date/index.js +4 -0
  217. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  218. package/dist/primitives/dialog/Dialog.js +121 -0
  219. package/dist/primitives/dialog/index.d.ts +1 -0
  220. package/dist/primitives/dialog/index.js +2 -0
  221. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  222. package/dist/primitives/disclosure/Disclosure.js +52 -0
  223. package/dist/primitives/disclosure/index.d.ts +1 -0
  224. package/dist/primitives/disclosure/index.js +2 -0
  225. package/dist/primitives/field/Checks.d.ts +57 -0
  226. package/dist/primitives/field/Checks.js +72 -0
  227. package/dist/primitives/field/DropZone.d.ts +42 -0
  228. package/dist/primitives/field/DropZone.js +52 -0
  229. package/dist/primitives/field/Field.d.ts +166 -0
  230. package/dist/primitives/field/Field.js +240 -0
  231. package/dist/primitives/field/Suggest.d.ts +48 -0
  232. package/dist/primitives/field/Suggest.js +43 -0
  233. package/dist/primitives/field/aria.d.ts +61 -0
  234. package/dist/primitives/field/aria.js +70 -0
  235. package/dist/primitives/field/context.d.ts +30 -0
  236. package/dist/primitives/field/context.js +13 -0
  237. package/dist/primitives/field/index.d.ts +7 -0
  238. package/dist/primitives/field/index.js +11 -0
  239. package/dist/primitives/field/layout.d.ts +24 -0
  240. package/dist/primitives/field/layout.js +10 -0
  241. package/dist/primitives/field/mode.d.ts +9 -0
  242. package/dist/primitives/field/mode.js +13 -0
  243. package/dist/primitives/hint/Hint.d.ts +31 -0
  244. package/dist/primitives/hint/Hint.js +42 -0
  245. package/dist/primitives/hint/index.d.ts +1 -0
  246. package/dist/primitives/hint/index.js +1 -0
  247. package/dist/primitives/index.d.ts +19 -0
  248. package/dist/primitives/index.js +20 -0
  249. package/dist/primitives/menu/Menu.d.ts +100 -0
  250. package/dist/primitives/menu/Menu.js +59 -0
  251. package/dist/primitives/menu/index.d.ts +1 -0
  252. package/dist/primitives/menu/index.js +2 -0
  253. package/dist/primitives/notice/Callout.d.ts +49 -0
  254. package/dist/primitives/notice/Callout.js +28 -0
  255. package/dist/primitives/notice/Notice.d.ts +45 -0
  256. package/dist/primitives/notice/Notice.js +61 -0
  257. package/dist/primitives/notice/Toast.d.ts +55 -0
  258. package/dist/primitives/notice/Toast.js +142 -0
  259. package/dist/primitives/notice/index.d.ts +3 -0
  260. package/dist/primitives/notice/index.js +5 -0
  261. package/dist/primitives/overlay/Popover.d.ts +6 -0
  262. package/dist/primitives/overlay/Popover.js +27 -0
  263. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  264. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  265. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  266. package/dist/primitives/overlay/Tooltip.js +74 -0
  267. package/dist/primitives/overlay/index.d.ts +3 -0
  268. package/dist/primitives/overlay/index.js +5 -0
  269. package/dist/primitives/picker/Picker.d.ts +48 -0
  270. package/dist/primitives/picker/Picker.js +150 -0
  271. package/dist/primitives/picker/index.d.ts +1 -0
  272. package/dist/primitives/picker/index.js +2 -0
  273. package/dist/primitives/surface/Peek.d.ts +60 -0
  274. package/dist/primitives/surface/Peek.js +71 -0
  275. package/dist/primitives/surface/index.d.ts +1 -0
  276. package/dist/primitives/surface/index.js +2 -0
  277. package/dist/primitives/table/CellEditor.d.ts +14 -0
  278. package/dist/primitives/table/CellEditor.js +83 -0
  279. package/dist/primitives/table/DataTable.d.ts +135 -0
  280. package/dist/primitives/table/DataTable.js +244 -0
  281. package/dist/primitives/table/EditableTable.d.ts +96 -0
  282. package/dist/primitives/table/EditableTable.js +423 -0
  283. package/dist/primitives/table/PivotTable.d.ts +14 -0
  284. package/dist/primitives/table/PivotTable.js +0 -0
  285. package/dist/primitives/table/ReportTable.d.ts +38 -0
  286. package/dist/primitives/table/ReportTable.js +106 -0
  287. package/dist/primitives/table/RowActions.d.ts +16 -0
  288. package/dist/primitives/table/RowActions.js +28 -0
  289. package/dist/primitives/table/SheetTable.d.ts +70 -0
  290. package/dist/primitives/table/SheetTable.js +197 -0
  291. package/dist/primitives/table/Table.d.ts +106 -0
  292. package/dist/primitives/table/Table.js +116 -0
  293. package/dist/primitives/table/index.d.ts +7 -0
  294. package/dist/primitives/table/index.js +9 -0
  295. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  296. package/dist/primitives/tabs/Tabs.js +56 -0
  297. package/dist/primitives/tabs/index.d.ts +1 -0
  298. package/dist/primitives/tabs/index.js +1 -0
  299. package/dist/primitives/tag/Tag.d.ts +36 -0
  300. package/dist/primitives/tag/Tag.js +68 -0
  301. package/dist/primitives/tag/Tags.d.ts +57 -0
  302. package/dist/primitives/tag/Tags.js +104 -0
  303. package/dist/primitives/tag/TagsField.d.ts +32 -0
  304. package/dist/primitives/tag/TagsField.js +182 -0
  305. package/dist/primitives/tag/choices.d.ts +38 -0
  306. package/dist/primitives/tag/choices.js +26 -0
  307. package/dist/primitives/tag/index.d.ts +4 -0
  308. package/dist/primitives/tag/index.js +8 -0
  309. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  310. package/dist/primitives/toolbar/Toolbar.js +36 -0
  311. package/dist/primitives/toolbar/index.d.ts +1 -0
  312. package/dist/primitives/toolbar/index.js +2 -0
  313. package/dist/primitives/value/Copyable.d.ts +37 -0
  314. package/dist/primitives/value/Copyable.js +50 -0
  315. package/dist/primitives/value/Fecha.d.ts +12 -0
  316. package/dist/primitives/value/Fecha.js +80 -0
  317. package/dist/primitives/value/Meter.d.ts +29 -0
  318. package/dist/primitives/value/Meter.js +46 -0
  319. package/dist/primitives/value/Metric.d.ts +42 -0
  320. package/dist/primitives/value/Metric.js +38 -0
  321. package/dist/primitives/value/Person.d.ts +90 -0
  322. package/dist/primitives/value/Person.js +208 -0
  323. package/dist/primitives/value/Relation.d.ts +69 -0
  324. package/dist/primitives/value/Relation.js +57 -0
  325. package/dist/primitives/value/Totals.d.ts +26 -0
  326. package/dist/primitives/value/Totals.js +4 -0
  327. package/dist/primitives/value/Value.d.ts +77 -0
  328. package/dist/primitives/value/Value.js +119 -0
  329. package/dist/primitives/value/index.d.ts +8 -0
  330. package/dist/primitives/value/index.js +9 -0
  331. package/dist/tokens.css +1025 -0
  332. package/dist/views/index.d.ts +2 -0
  333. package/dist/views/index.js +3 -0
  334. package/dist/views/list/List.d.ts +113 -0
  335. package/dist/views/list/List.js +97 -0
  336. package/dist/views/list/ListView.d.ts +6 -0
  337. package/dist/views/list/ListView.js +269 -0
  338. package/dist/views/list/columns.d.ts +18 -0
  339. package/dist/views/list/columns.js +27 -0
  340. package/dist/views/list/derive.d.ts +76 -0
  341. package/dist/views/list/derive.js +181 -0
  342. package/dist/views/list/index.d.ts +2 -0
  343. package/dist/views/list/index.js +4 -0
  344. package/dist/views/list/presentations.d.ts +39 -0
  345. package/dist/views/list/presentations.js +103 -0
  346. package/dist/views/list/types.d.ts +272 -0
  347. package/dist/views/list/types.js +1 -0
  348. package/dist/views/list/useNarrowList.d.ts +17 -0
  349. package/dist/views/list/useNarrowList.js +32 -0
  350. package/dist/views/record/Record.d.ts +126 -0
  351. package/dist/views/record/Record.js +141 -0
  352. package/dist/views/record/index.d.ts +1 -0
  353. package/dist/views/record/index.js +2 -0
  354. package/package.json +109 -4
@@ -0,0 +1,57 @@
1
+ import { type FormMode } from "./mode.js";
2
+ export interface CheckItem {
3
+ value: string;
4
+ label: string;
5
+ /** The second line: what the thing is, not a second name. */
6
+ detail?: string;
7
+ disabled?: boolean;
8
+ }
9
+ /**
10
+ * One yes/no answer of a form: «Exento de IVA», «Enviar la factura por correo al
11
+ * publicar».
12
+ *
13
+ * Context: it is React Aria's `Checkbox` — a native checkbox input inside the label, so the
14
+ * whole sentence is the click target, Space toggles it and the form reads it as a checkbox.
15
+ * `mixed` is its indeterminate state: the parent of a group where only some are on, and
16
+ * pressing it turns them ALL on — never all off, because «some» is closer to «all» than to
17
+ * «none» in what the person was doing.
18
+ *
19
+ * Inside a `Field` it is named by the field's label AND its own sentence, and described by
20
+ * the field's hint and error — the same wiring as `Switch`.
21
+ */
22
+ export declare function Checkbox({ checked, mixed, onChange, label, disabled, mode, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, }: {
23
+ checked: boolean;
24
+ /** Some of the children are on. Pressing it turns them all on. */
25
+ mixed?: boolean;
26
+ onChange?: (next: boolean) => void;
27
+ label: string;
28
+ disabled?: boolean;
29
+ mode?: FormMode;
30
+ id?: string;
31
+ describedBy?: string;
32
+ invalid?: boolean;
33
+ }): import("react").JSX.Element;
34
+ /**
35
+ * Several answers, all in sight, more than one allowed: the taxes of a product, the
36
+ * warehouses a user can see, the documents of an export.
37
+ *
38
+ * Context: it is React Aria's `ListBox` with `selectionMode="multiple"` and not a stack of
39
+ * checkboxes, because what it is to a keyboard is ONE stop with a list inside — Tab lands on
40
+ * it, the arrows move, Space toggles, Home and End jump, a letter finds the row — and twelve
41
+ * Tab stops is how a form becomes a chore. A disabled row is drawn and skipped. The value is
42
+ * a plain `string[]`, never React Aria's `Selection`.
43
+ *
44
+ * `mono` is for when the label was written by the machine — an account code, a tax code —
45
+ * and the detail was written by a person. The two faces are the point.
46
+ */
47
+ export declare function CheckList({ label, items, value, onChange, mono, emptyLabel, describedBy: ownDescribedBy, mode, }: {
48
+ label: string;
49
+ items: readonly CheckItem[];
50
+ value: readonly string[];
51
+ onChange?: (next: string[]) => void;
52
+ mono?: boolean;
53
+ /** What read mode says when nothing is chosen. By default, the copy table's `field.none`. */
54
+ emptyLabel?: string;
55
+ describedBy?: string;
56
+ mode?: FormMode;
57
+ }): import("react").JSX.Element;
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { useFieldIds } from "./context.js";
5
+ import { useFormMode } from "./mode.js";
6
+ import { Check, Minus } from "lucide-react";
7
+ import { useId } from "react";
8
+ import { Checkbox as AriaCheckbox, ListBox, ListBoxItem, Text, } from "react-aria-components";
9
+ function Mark({ on, mixed, focus = false, invalid = false, }) {
10
+ return (_jsx("span", { "aria-hidden": true, className: `inline-flex size-4 shrink-0 items-center justify-center rounded-mark border transition-colors duration-(--duration-instant) forced-color-adjust-none ${on
11
+ ? "border-primary bg-primary text-surface forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]"
12
+ : `bg-surface text-transparent ${invalid ? "border-danger" : "border-line-strong"} forced-colors:border-[ButtonBorder]`} ${focus ? "outline-2 outline-offset-2 outline-primary forced-colors:outline-[Highlight]" : ""}`, children: mixed ? (_jsx(Minus, { className: "size-3", strokeWidth: 2.5 })) : (_jsx(Check, { className: "size-3", strokeWidth: 2.5 })) }));
13
+ }
14
+ /**
15
+ * One yes/no answer of a form: «Exento de IVA», «Enviar la factura por correo al
16
+ * publicar».
17
+ *
18
+ * Context: it is React Aria's `Checkbox` — a native checkbox input inside the label, so the
19
+ * whole sentence is the click target, Space toggles it and the form reads it as a checkbox.
20
+ * `mixed` is its indeterminate state: the parent of a group where only some are on, and
21
+ * pressing it turns them ALL on — never all off, because «some» is closer to «all» than to
22
+ * «none» in what the person was doing.
23
+ *
24
+ * Inside a `Field` it is named by the field's label AND its own sentence, and described by
25
+ * the field's hint and error — the same wiring as `Switch`.
26
+ */
27
+ export function Checkbox({ checked, mixed = false, onChange, label, disabled = false, mode, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, }) {
28
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
29
+ id: ownId,
30
+ describedBy: ownDescribedBy,
31
+ invalid: ownInvalid,
32
+ });
33
+ const textId = useId();
34
+ const on = checked || mixed;
35
+ if (useFormMode(mode) === "read") {
36
+ return (_jsxs("span", { className: "flex min-h-(--size-control-md) items-center gap-2 text-meta text-ink", children: [_jsx(Mark, { on: on, mixed: mixed && !checked }), label] }));
37
+ }
38
+ return (_jsx(AriaCheckbox, { id: id, "aria-labelledby": labelledBy === undefined ? undefined : `${labelledBy} ${textId}`, "aria-describedby": describedBy, isSelected: checked, isIndeterminate: mixed, onChange: (next) => onChange?.(mixed ? true : next), isDisabled: disabled, isInvalid: invalid, className: "group inline-flex min-w-0 cursor-pointer items-center gap-2.5 text-meta text-ink disabled:cursor-not-allowed disabled:opacity-45", children: ({ isSelected, isIndeterminate, isFocusVisible, isInvalid }) => (_jsxs(_Fragment, { children: [_jsx(Mark, { on: isSelected || isIndeterminate, mixed: isIndeterminate, focus: isFocusVisible, invalid: isInvalid }), _jsx("span", { id: textId, className: "min-w-0", children: label })] })) }));
39
+ }
40
+ /** The chosen values, in the order the person picked them — what `onChange` hands back. */
41
+ function valuesOf(keys, items) {
42
+ if (keys === "all")
43
+ return items.filter((item) => !item.disabled).map((item) => item.value);
44
+ return [...keys].map(String);
45
+ }
46
+ /**
47
+ * Several answers, all in sight, more than one allowed: the taxes of a product, the
48
+ * warehouses a user can see, the documents of an export.
49
+ *
50
+ * Context: it is React Aria's `ListBox` with `selectionMode="multiple"` and not a stack of
51
+ * checkboxes, because what it is to a keyboard is ONE stop with a list inside — Tab lands on
52
+ * it, the arrows move, Space toggles, Home and End jump, a letter finds the row — and twelve
53
+ * Tab stops is how a form becomes a chore. A disabled row is drawn and skipped. The value is
54
+ * a plain `string[]`, never React Aria's `Selection`.
55
+ *
56
+ * `mono` is for when the label was written by the machine — an account code, a tax code —
57
+ * and the detail was written by a person. The two faces are the point.
58
+ */
59
+ export function CheckList({ label, items, value, onChange, mono = false, emptyLabel, describedBy: ownDescribedBy, mode, }) {
60
+ const labelId = useId();
61
+ const { describedBy } = useFieldIds({ describedBy: ownDescribedBy });
62
+ const none = useUiCopy().field.none;
63
+ if (useFormMode(mode) === "read") {
64
+ const picked = items.filter((item) => value.includes(item.value));
65
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("p", { className: "text-micro text-muted", children: label }), _jsx("p", { className: `text-meta text-ink ${mono ? "font-mono" : ""}`, children: picked.length === 0
66
+ ? (emptyLabel ?? none)
67
+ : picked.map((item) => item.label).join(", ") })] }));
68
+ }
69
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [_jsx("p", { id: labelId, className: "text-micro text-muted", children: label }), _jsx(ListBox, { "aria-labelledby": labelId, "aria-describedby": describedBy, items: items, selectionMode: "multiple", selectedKeys: value, onSelectionChange: (keys) => onChange?.(valuesOf(keys, items)), disabledKeys: items.filter((item) => item.disabled).map((item) => item.value), shouldFocusWrap: true, className: "flex flex-col overflow-hidden rounded-box border border-line bg-surface outline-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, className: ({ isSelected, isFocusVisible, isDisabled }) => `flex min-w-0 cursor-pointer items-center gap-2.5 border-b border-line px-3 py-2 text-left outline-0 last:border-b-0 ${isFocusVisible
70
+ ? "outline-2 -outline-offset-2 outline-primary forced-colors:outline-[Highlight]"
71
+ : ""} ${isDisabled ? "cursor-not-allowed opacity-45" : ""} ${isSelected ? "bg-primary/8" : "hover:bg-ink/4"}`, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx(Mark, { on: isSelected, mixed: false }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx(Text, { slot: "label", className: `truncate text-meta text-ink ${mono ? "font-mono" : ""}`, children: item.label }), item.detail === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro text-muted", children: item.detail }))] })] })) })) })] }));
72
+ }
@@ -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/Value.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,166 @@
1
+ import { type FieldIds } from "./context.js";
2
+ import { type FormMode } from "./mode.js";
3
+ import { type ReactNode } from "react";
4
+ export declare function Field({ label, hint, error, required, children, htmlFor, layout, action, mode, }: {
5
+ label: string;
6
+ /** What is expected. A `?` on the label that opens it on press. */
7
+ hint?: string;
8
+ /** What has to be corrected. It goes BELOW, close to the cursor, and it says
9
+ * what to put. */
10
+ error?: string;
11
+ required?: boolean;
12
+ /** The control. Written directly — `<Field label="Nombre"><TextInput … /></Field>` — it
13
+ * reads its id, description and error state from the field. The render prop
14
+ * `{(aria) => …}` is still there for a control that is not one of the system's. */
15
+ children: ReactNode | ((ids: FieldIds) => ReactNode);
16
+ htmlFor?: string;
17
+ /** `inline` puts the label on the LEFT, in a fixed-width column. It is what
18
+ * turns twenty stacked fields into a record that reads at a glance: every
19
+ * value starts on the same vertical, so the eye goes down a column instead of
20
+ * bouncing. It needs width — in a narrow drawer, the label eats the field.
21
+ * Omit it to inherit the sheet: `horizontal` there is this, `stacked` is the
22
+ * label above. */
23
+ layout?: "stacked" | "inline";
24
+ /** A button attached to the label: "generate", "clear", "pick from the
25
+ * catalog". It goes above and not below the control because it belongs to the
26
+ * FIELD, not to the form — what goes below is what closes the whole form. */
27
+ action?: ReactNode;
28
+ /** Forces the mode of this field. By default it inherits it from the view. */
29
+ mode?: FormMode;
30
+ }): import("react").JSX.Element;
31
+ /**
32
+ * A label with its VALUE, without a control below. What was missing to show a
33
+ * record without pretending it is being edited.
34
+ *
35
+ * Context: until now, showing a value with its name in a form forced you to put
36
+ * a disabled `TextInput`. That draws the box, the border and the height of
37
+ * something you write into, and a whole record like that is twelve boxes empty
38
+ * of meaning.
39
+ *
40
+ * It uses the same geometry as `Field`, so a field that goes from being looked
41
+ * at to being edited does not move anything around it.
42
+ */
43
+ export declare function DataField({ label, value, hint, layout, mono, action, }: {
44
+ label: string;
45
+ /** `undefined` and `null` are drawn with the missing mark: saying there is no
46
+ * data is information, and a blank line does not give it. */
47
+ value: ReactNode;
48
+ hint?: string;
49
+ layout?: "stacked" | "inline";
50
+ /** The value was written by the machine: an id, a code, an account number. */
51
+ mono?: boolean;
52
+ action?: ReactNode;
53
+ }): import("react").JSX.Element;
54
+ /** What a control draws when the form is LOOKED AT instead of written into.
55
+ *
56
+ * It is a single piece and not an `if` repeated in every control: if each one
57
+ * solves its read mode its own way, the record ends up with six different ways
58
+ * of saying the same thing. */
59
+ export declare function ReadValue({ value, mono }: {
60
+ value: ReactNode;
61
+ mono?: boolean;
62
+ }): import("react").JSX.Element;
63
+ /** The shared look of everything that is written into or chosen from.
64
+ *
65
+ * It exists as a function and not as a loose class so that a new control cannot
66
+ * be born with another height, another radius or another focus. That was the
67
+ * original defect: every file wrote its own `<input className="…">`. */
68
+ export declare function controlClasses(invalid?: boolean): string;
69
+ /** What every text control shares: the field's wiring, overridable prop by prop. */
70
+ type ControlProps = {
71
+ id?: string;
72
+ describedBy?: string;
73
+ invalid?: boolean;
74
+ disabled?: boolean;
75
+ /** Forces the mode. By default it inherits the form's: in read mode it draws the value. */
76
+ mode?: FormMode;
77
+ /** The name of the control when it is NOT inside a `Field` — a cell of an editable table,
78
+ * the box of a message. Inside a field, the field's label names it. */
79
+ "aria-label"?: string;
80
+ };
81
+ /**
82
+ * One line of text: a business name, a reference, a NIT.
83
+ *
84
+ * Context: it is React Aria's `TextField` — the field's label, hint and error reach the real
85
+ * `<input>` through `useFieldIds`, and React Aria adds the rest of what a text input owes a
86
+ * screen reader. What it draws is `controlClasses()`, the one box every control shares.
87
+ */
88
+ export declare function TextInput({ id: ownId, value, onChange, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, mono, spellCheck, "aria-label": ariaLabel, }: ControlProps & {
89
+ value: string;
90
+ onChange?: (next: string) => void;
91
+ placeholder?: string;
92
+ /** The value was written by the machine and gets compared with the one next to it: an
93
+ * id, a key, an account code. See the family rule in Typography. */
94
+ mono?: boolean;
95
+ /** `false` for a value that is a token and not prose — a product code, a reference, an
96
+ * account code — so the browser stops underlining identifiers as misspellings. */
97
+ spellCheck?: boolean;
98
+ }): import("react").JSX.Element;
99
+ /**
100
+ * A number typed the way the person writes it: «1.234,56» in Bolivia, «1,234.56» in the
101
+ * United States.
102
+ *
103
+ * Context: it was an `<input type="number">`, and that input only understands the
104
+ * browser's idea of a number — on a Spanish form, «1.234,56» was either refused or read as
105
+ * one point two. It is React Aria's `NumberField` now, inside `AriaLocale`: it parses and
106
+ * formats in the locale of the copy table, not the browser's, so an amount reads the same
107
+ * here as in the listing next to it. `format` takes `Intl.NumberFormatOptions` — a
108
+ * currency, a percentage, two fixed decimals — and the field types and shows it that way.
109
+ *
110
+ * The arrow keys (and the two small buttons) step by `step`; on leaving the field the value
111
+ * is clamped to `min`/`max` and snapped to `step`, so what the form submits is always a
112
+ * number the field would have accepted. The value stays a plain `number`, and `""` is the
113
+ * empty field — never `NaN`, which is how React Aria says it inside.
114
+ */
115
+ export declare function NumberInput({ id: ownId, value, onChange, min, max, step, unit, format, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, "aria-label": ariaLabel, }: ControlProps & {
116
+ value: number | "";
117
+ onChange?: (next: number | "") => void;
118
+ min?: number;
119
+ max?: number;
120
+ step?: number;
121
+ /** The unit, attached to the field: "días", "u.", "kg". A number without a unit in a
122
+ * form is half a question. For money, prefer `format` with a currency. */
123
+ unit?: string;
124
+ /** How the number is written: `{ style: "currency", currency: "BOB" }`,
125
+ * `{ style: "percent" }`, `{ minimumFractionDigits: 2 }`. In the copy's locale. */
126
+ format?: Intl.NumberFormatOptions;
127
+ placeholder?: string;
128
+ }): import("react").JSX.Element;
129
+ /**
130
+ * Several lines of prose: the notes of an order, the reason of a credit note.
131
+ *
132
+ * Context: React Aria's `TextField` with a `TextArea` inside — the same wiring as
133
+ * `TextInput`, the same box, taller and resizable only downwards, because a note grows in
134
+ * lines, not in width.
135
+ */
136
+ export declare function TextArea({ id: ownId, value, onChange, placeholder, rows, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, "aria-label": ariaLabel, }: ControlProps & {
137
+ value: string;
138
+ onChange?: (next: string) => void;
139
+ placeholder?: string;
140
+ rows?: number;
141
+ }): import("react").JSX.Element;
142
+ export interface SelectOption {
143
+ value: string;
144
+ label: string;
145
+ disabled?: boolean;
146
+ }
147
+ /** The system's select, for SHORT and closed lists: the payment terms, the currency, the
148
+ * warehouse.
149
+ *
150
+ * Context: it was a native `<select>`, and its open list was the operating system's — its
151
+ * font, its colors, its radius, in the middle of a form drawn with the system's. It is
152
+ * React Aria's `Select` now: the trigger is the system's control box and the list floats
153
+ * on the system's `Popover`, row for row the same as `ComboBox`'s. The keyboard is still a
154
+ * select's: arrows open it and move, typing a letter jumps to the option, Enter picks.
155
+ *
156
+ * As soon as the list has to show two pieces of data per option —a name and its tax id—
157
+ * or grows past a screen, it is a `ComboBox` (type to find) or a `Picker` (records with
158
+ * their metadata). An option here is ONE LINE OF TEXT. */
159
+ export declare function Select({ id: ownId, value, options, onChange, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, placeholder, mode, "aria-label": ariaLabel, }: ControlProps & {
160
+ /** The chosen option's `value`; `""` is nothing chosen, and draws the placeholder. */
161
+ value: string;
162
+ options: readonly SelectOption[];
163
+ onChange?: (next: string) => void;
164
+ placeholder?: string;
165
+ }): import("react").JSX.Element;
166
+ export {};
@@ -0,0 +1,240 @@
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 { useSheetLayout } from "./layout.js";
10
+ import { FormModeProvider, useFormMode, } from "./mode.js";
11
+ import { Hint } from "../hint/Hint.js";
12
+ import { Popover } from "../overlay/Popover.js";
13
+ import { AlertCircle, Check, ChevronDown, ChevronUp } from "lucide-react";
14
+ import { useId } from "react";
15
+ import { Button as AriaButton, Input, ListBox, ListBoxItem, NumberField as AriaNumberField, Select as AriaSelect, SelectValue, TextArea as AriaTextArea, TextField as AriaTextField, } from "react-aria-components";
16
+ /* ==================================================================== *
17
+ * THE CONTROLS — the label is not optional and the error is not a color.
18
+ *
19
+ * The state we came from is on record: twenty raw controls across six files,
20
+ * **none of them with an associated label or `aria-describedby`**, and all of
21
+ * them 24 px tall. That is not a matter of abstract accessibility: a `label`
22
+ * without `htmlFor` is a label that does not enlarge the click area, and a hint
23
+ * that is not linked is a hint a screen reader never reads.
24
+ *
25
+ * ── The error says WHAT to do, not that something is wrong ──────────
26
+ *
27
+ * "Invalid value" is no use. An error message has to say what was expected
28
+ * —"it has to be a number between 0 and 1"— because whoever made the mistake
29
+ * already knows they made it: what they don't know is what to put.
30
+ *
31
+ * And it is never ONLY color: it carries an icon and text. A red border is
32
+ * invisible to someone who doesn't tell red apart, and also to someone who is
33
+ * looking at another part of the screen.
34
+ *
35
+ * ── The hint sits on the label, the error BELOW the control ─────────
36
+ *
37
+ * Help is a `?` next to the name, opened with a press. A paragraph above the
38
+ * control ate a line on every field. The error still goes below, close to
39
+ * the cursor, and it says what to put.
40
+ * ==================================================================== */
41
+ export function Field({ label, hint, error, required = false, children, htmlFor, layout, action, mode, }) {
42
+ const d = useDensity();
43
+ const copy = useUiCopy();
44
+ const auto = useId();
45
+ const id = htmlFor ?? auto;
46
+ const sheetLayout = useSheetLayout();
47
+ const inline = layout === "inline" || (layout === undefined && sheetLayout === "horizontal");
48
+ const hintId = `${id}-hint`;
49
+ const errorId = `${id}-error`;
50
+ const describedBy = [hint === undefined ? null : hintId, error === undefined ? null : errorId]
51
+ .filter((x) => x !== null)
52
+ .join(" ") || undefined;
53
+ 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 })] }));
54
+ /* The field's `mode` propagates INWARDS and is not used here: what knows how
55
+ to draw itself as data is the control, not the label. That way a computed
56
+ field stays in read mode inside a form that is being edited, without the
57
+ consumer having to pass it to the control as well. */
58
+ const ids = {
59
+ id,
60
+ labelId: `${id}-label`,
61
+ describedBy,
62
+ invalid: error !== undefined,
63
+ };
64
+ /* The render prop gets the same context as a control written directly: a system control
65
+ inside it then also finds the label that names it, not only the id it is handed. */
66
+ const content = (_jsx(FieldIdsContext.Provider, { value: ids, children: typeof children === "function" ? children(ids) : children }));
67
+ 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 })] }))] }));
68
+ if (inline) {
69
+ return (_jsxs("div", { className: "grid-label grid min-w-0 items-start gap-x-4 gap-y-1", 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 }))] }));
70
+ }
71
+ return (_jsxs("div", { className: `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] }));
72
+ }
73
+ /**
74
+ * A label with its VALUE, without a control below. What was missing to show a
75
+ * record without pretending it is being edited.
76
+ *
77
+ * Context: until now, showing a value with its name in a form forced you to put
78
+ * a disabled `TextInput`. That draws the box, the border and the height of
79
+ * something you write into, and a whole record like that is twelve boxes empty
80
+ * of meaning.
81
+ *
82
+ * It uses the same geometry as `Field`, so a field that goes from being looked
83
+ * at to being edited does not move anything around it.
84
+ */
85
+ export function DataField({ label, value, hint, layout, mono = false, action, }) {
86
+ const d = useDensity();
87
+ const sheetLayout = useSheetLayout();
88
+ const inline = layout === "inline" || (layout === undefined && sheetLayout === "horizontal");
89
+ const empty = value === undefined || value === null || value === "";
90
+ const unset = useUiCopy().field.unset;
91
+ 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 })] }));
92
+ const valueNode = (_jsx("span", { className: `min-w-0 text-meta ${empty ? "text-muted italic" : "text-ink"} ${mono ? "font-mono tabular-nums" : ""}`, children: empty ? unset : value }));
93
+ if (inline) {
94
+ return (_jsxs("div", { className: "grid-label grid min-w-0 items-baseline gap-x-4 gap-y-0.5", children: [labelNode, _jsxs("span", { className: "flex min-w-0 items-baseline gap-2", children: [valueNode, action] })] }));
95
+ }
96
+ return (_jsxs("div", { className: `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] }));
97
+ }
98
+ /** What a control draws when the form is LOOKED AT instead of written into.
99
+ *
100
+ * It is a single piece and not an `if` repeated in every control: if each one
101
+ * solves its read mode its own way, the record ends up with six different ways
102
+ * of saying the same thing. */
103
+ export function ReadValue({ value, mono = false }) {
104
+ const unset = useUiCopy().field.unset;
105
+ const empty = value === undefined || value === null || value === "";
106
+ 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 }));
107
+ }
108
+ /** The shared look of everything that is written into or chosen from.
109
+ *
110
+ * It exists as a function and not as a loose class so that a new control cannot
111
+ * be born with another height, another radius or another focus. That was the
112
+ * original defect: every file wrote its own `<input className="…">`. */
113
+ export function controlClasses(invalid = false) {
114
+ 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
115
+ ? "border-danger focus-visible:border-danger focus-visible:outline-danger"
116
+ : "border-line-strong focus-visible:border-primary focus-visible:outline-primary"}`;
117
+ }
118
+ /* `aria-describedby` goes on the INPUT and not on React Aria's field: the field adds the ids
119
+ of its own description and error slots, which this system never renders (`Field` draws
120
+ them), and the input's own prop replaces that list with the field's real one. */
121
+ /** The accessible name of a control. Inside a `Field` the field's label names it; outside,
122
+ * the consumer's `aria-label`, and as a last resort the placeholder — which is what a
123
+ * browser reads for a bare `<input placeholder>` anyway, so a control outside a form is
124
+ * never nameless. */
125
+ function nameOf(labelledBy, ariaLabel, placeholder) {
126
+ return labelledBy === undefined ? (ariaLabel ?? placeholder) : ariaLabel;
127
+ }
128
+ /**
129
+ * One line of text: a business name, a reference, a NIT.
130
+ *
131
+ * Context: it is React Aria's `TextField` — the field's label, hint and error reach the real
132
+ * `<input>` through `useFieldIds`, and React Aria adds the rest of what a text input owes a
133
+ * screen reader. What it draws is `controlClasses()`, the one box every control shares.
134
+ */
135
+ export function TextInput({ id: ownId, value, onChange, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, mono = false, spellCheck, "aria-label": ariaLabel, }) {
136
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
137
+ id: ownId,
138
+ describedBy: ownDescribedBy,
139
+ invalid: ownInvalid,
140
+ });
141
+ if (useFormMode(mode) === "read")
142
+ return _jsx(ReadValue, { value: value, mono: mono });
143
+ 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" : ""}` }) }));
144
+ }
145
+ /** The amount, the quantity, the percentage — read in the copy's locale, every time. */
146
+ function useReadNumber(format) {
147
+ const { locale } = useUiCopy();
148
+ const formatter = new Intl.NumberFormat(locale, format);
149
+ return (value) => formatter.format(value);
150
+ }
151
+ 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]";
152
+ /**
153
+ * A number typed the way the person writes it: «1.234,56» in Bolivia, «1,234.56» in the
154
+ * United States.
155
+ *
156
+ * Context: it was an `<input type="number">`, and that input only understands the
157
+ * browser's idea of a number — on a Spanish form, «1.234,56» was either refused or read as
158
+ * one point two. It is React Aria's `NumberField` now, inside `AriaLocale`: it parses and
159
+ * formats in the locale of the copy table, not the browser's, so an amount reads the same
160
+ * here as in the listing next to it. `format` takes `Intl.NumberFormatOptions` — a
161
+ * currency, a percentage, two fixed decimals — and the field types and shows it that way.
162
+ *
163
+ * The arrow keys (and the two small buttons) step by `step`; on leaving the field the value
164
+ * is clamped to `min`/`max` and snapped to `step`, so what the form submits is always a
165
+ * number the field would have accepted. The value stays a plain `number`, and `""` is the
166
+ * empty field — never `NaN`, which is how React Aria says it inside.
167
+ */
168
+ export function NumberInput({ id: ownId, value, onChange, min, max, step, unit, format, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, "aria-label": ariaLabel, }) {
169
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
170
+ id: ownId,
171
+ describedBy: ownDescribedBy,
172
+ invalid: ownInvalid,
173
+ });
174
+ const read = useReadNumber(format);
175
+ if (useFormMode(mode) === "read") {
176
+ return (_jsx(ReadValue, { value: value === "" ? null : (_jsxs("span", { className: "tabular-nums", children: [read(value), unit === undefined ? "" : ` ${unit}`] })) }));
177
+ }
178
+ 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 }))] }) }));
179
+ }
180
+ /**
181
+ * Several lines of prose: the notes of an order, the reason of a credit note.
182
+ *
183
+ * Context: React Aria's `TextField` with a `TextArea` inside — the same wiring as
184
+ * `TextInput`, the same box, taller and resizable only downwards, because a note grows in
185
+ * lines, not in width.
186
+ */
187
+ export function TextArea({ id: ownId, value, onChange, placeholder, rows = 3, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, "aria-label": ariaLabel, }) {
188
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
189
+ id: ownId,
190
+ describedBy: ownDescribedBy,
191
+ invalid: ownInvalid,
192
+ });
193
+ /* The paragraph in read mode keeps its line breaks: a long text without them
194
+ is a block, and whoever wrote it put them there for a reason. */
195
+ if (useFormMode(mode) === "read") {
196
+ return value === "" ? (_jsx(ReadValue, { value: null })) : (_jsx("span", { className: "text-meta whitespace-pre-line text-ink", children: value }));
197
+ }
198
+ 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` }) }));
199
+ }
200
+ const selectTrigger = tv({
201
+ extend: focusRing,
202
+ 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]",
203
+ variants: {
204
+ isFocusVisible: {
205
+ true: "border-primary",
206
+ },
207
+ isInvalid: {
208
+ false: "border-line-strong forced-colors:border-[ButtonBorder]",
209
+ true: "border-danger outline-danger forced-colors:border-[Mark]",
210
+ },
211
+ isDisabled: {
212
+ true: "cursor-not-allowed bg-ground text-muted forced-colors:text-[GrayText]",
213
+ },
214
+ },
215
+ });
216
+ /** The system's select, for SHORT and closed lists: the payment terms, the currency, the
217
+ * warehouse.
218
+ *
219
+ * Context: it was a native `<select>`, and its open list was the operating system's — its
220
+ * font, its colors, its radius, in the middle of a form drawn with the system's. It is
221
+ * React Aria's `Select` now: the trigger is the system's control box and the list floats
222
+ * on the system's `Popover`, row for row the same as `ComboBox`'s. The keyboard is still a
223
+ * select's: arrows open it and move, typing a letter jumps to the option, Enter picks.
224
+ *
225
+ * As soon as the list has to show two pieces of data per option —a name and its tax id—
226
+ * or grows past a screen, it is a `ComboBox` (type to find) or a `Picker` (records with
227
+ * their metadata). An option here is ONE LINE OF TEXT. */
228
+ export function Select({ id: ownId, value, options, onChange, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, placeholder, mode, "aria-label": ariaLabel, }) {
229
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
230
+ id: ownId,
231
+ describedBy: ownDescribedBy,
232
+ invalid: ownInvalid,
233
+ });
234
+ /* In read mode the option's LABEL is drawn, not its value: `CONTADO` is the backend's
235
+ key, and what the form promised to show was «Al contado». */
236
+ if (useFormMode(mode) === "read") {
237
+ return _jsx(ReadValue, { value: options.find((o) => o.value === value)?.label ?? null });
238
+ }
239
+ 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 })] })) })) }) })] }) }));
240
+ }