@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,186 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { AriaLocale } from "../../foundations/aria.js";
4
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
5
+ import { cn } from "../../foundations/variants.js";
6
+ import { fieldGroupStyles, listOptionStyles } from "../field/aria.js";
7
+ import { useFieldIds } from "../field/context.js";
8
+ import { ReadValue } from "../field/Field.js";
9
+ import { useFormMode } from "../field/mode.js";
10
+ import { Popover } from "../overlay/Popover.js";
11
+ import { tagChoices, withoutTag, withTag, } from "./choices.js";
12
+ import { Tags } from "./Tags.js";
13
+ import { Plus } from "lucide-react";
14
+ import { useContext, useEffect, useMemo, useRef, useState, } from "react";
15
+ import { ComboBox as AriaComboBox, ComboBoxStateContext, Group, Input, ListBox, ListBoxItem, useFilter, useLocale, } from "react-aria-components";
16
+ /* ==================================================================== *
17
+ * TAGSFIELD — a many2many written as chips: the categories of a customer, the labels of an
18
+ * invoice.
19
+ *
20
+ * Context: the read form of this value is `Tags` — a strip of chips. Editing it is the same
21
+ * strip plus ONE way in: the chips stay where they are read (removable with their × or
22
+ * Backspace), and an input right after the last one adds the next. A separate «add tag»
23
+ * button that opens a list would be two controls for one value, and a multi-select with
24
+ * checkboxes hides what is chosen behind a closed field — the opposite of a many2many,
25
+ * whose whole point is that the categories are seen.
26
+ *
27
+ * ── React Aria's tag group and React Aria's combobox, side by side ──
28
+ *
29
+ * The chips are `Tags` (React Aria's `TagGroup`): one tab stop, arrows between chips,
30
+ * Backspace or Delete removes the focused one. The input is React Aria's `ComboBox`: typing
31
+ * filters in the copy table's locale («exportacion» finds «exportación»), arrows walk the
32
+ * list, Enter adds. Two additions are ours, both what a person who types tags expects:
33
+ * while typing, the FIRST match is marked, so Enter adds it (typing «may» and Enter is the
34
+ * whole gesture, and the row it takes is the tinted one); and Backspace in the empty input
35
+ * removes the last chip.
36
+ *
37
+ * The chosen tags never come back as options — choosing «mayorista» twice is a duplicate,
38
+ * not a choice. «Create «…»» is offered only when `onCreate` is given and the text names no
39
+ * existing tag (see `tagChoices`).
40
+ *
41
+ * The value is a plain `string[]` of ids, in the order they were added — what the backend
42
+ * stores in a many2many, never React Aria's `Selection`.
43
+ * ==================================================================== */
44
+ /** The key of the «create» row. Not an id: it cannot collide with one. */
45
+ const CREATE = "\u0000create";
46
+ /** The chosen tags as chips, in the order of `value`. An id missing from `options` is still
47
+ * drawn (as itself): hiding it would hide data the record has. */
48
+ function chosenOptions(options, value) {
49
+ const byId = new Map(options.map((option) => [option.value, option]));
50
+ return value.map((id) => byId.get(id) ?? { value: id, label: id });
51
+ }
52
+ /**
53
+ * A many2many as removable chips plus an inline combobox that adds one more.
54
+ *
55
+ * Works inside `<Field label=…>`: the field's label names both the input and the strip of
56
+ * chips, and its hint and error describe the input. In read mode it is `Tags`.
57
+ */
58
+ export function TagsField({ mode, ...props }) {
59
+ if (useFormMode(mode) === "read") {
60
+ const chosen = chosenOptions(props.options, props.value);
61
+ if (chosen.length === 0)
62
+ return _jsx(ReadValue, { value: null });
63
+ return (_jsx("span", { className: "flex min-h-(--size-control-md) items-center", children: _jsx(Tags, { items: chosen.map((option) => ({
64
+ id: option.value,
65
+ label: option.label,
66
+ ...(option.tone === undefined ? {} : { tone: option.tone }),
67
+ })) }) }));
68
+ }
69
+ /* The filter reads the locale from React Aria's context, so the box that provides it has
70
+ to be ABOVE the component that asks for it. */
71
+ return (_jsx(AriaLocale, { children: _jsx(EditableTagsField, { ...props }) }));
72
+ }
73
+ function EditableTagsField({ value, onChange, options, onCreate, placeholder, disabled = false, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, "aria-label": ariaLabel, }) {
74
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
75
+ id: ownId,
76
+ describedBy: ownDescribedBy,
77
+ invalid: ownInvalid,
78
+ });
79
+ const copy = useUiCopy();
80
+ const { contains } = useFilter({ sensitivity: "base" });
81
+ const { locale } = useLocale();
82
+ const collator = useMemo(() => new Intl.Collator(locale, { sensitivity: "base", usage: "search" }), [locale]);
83
+ const [text, setText] = useState("");
84
+ const boxRef = useRef(null);
85
+ const [width, setWidth] = useState(null);
86
+ const inputRef = useRef(null);
87
+ /* The value after an `await` is the one of THEN, not the one of the render that started
88
+ creating: a tag removed meanwhile must not come back. */
89
+ const latest = useRef(value);
90
+ latest.current = value;
91
+ const { available, create } = tagChoices(options, value, text, { contains, same: (a, b) => collator.compare(a.trim(), b) === 0 }, onCreate !== undefined && !disabled);
92
+ const listed = create === null
93
+ ? available
94
+ : [
95
+ ...available,
96
+ { value: CREATE, label: fillCopy(copy.inlineEdit.createTag, { name: create }) },
97
+ ];
98
+ const commit = (key) => {
99
+ if (key === null)
100
+ return;
101
+ const chosen = String(key);
102
+ setText("");
103
+ if (chosen !== CREATE) {
104
+ onChange?.(withTag(latest.current, chosen));
105
+ return;
106
+ }
107
+ if (create === null || onCreate === undefined)
108
+ return;
109
+ void Promise.resolve(onCreate(create)).then((created) => onChange?.(withTag(latest.current, created)));
110
+ };
111
+ const remove = (tagId) => {
112
+ const next = withoutTag(value, tagId);
113
+ onChange?.(next);
114
+ /* The last chip leaves and takes its strip with it: the focus goes to the input instead
115
+ of falling to the page. */
116
+ if (next.length === 0)
117
+ inputRef.current?.focus();
118
+ };
119
+ const chips = chosenOptions(options, value).map((option) => ({
120
+ id: option.value,
121
+ label: option.label,
122
+ ...(option.tone === undefined ? {} : { tone: option.tone }),
123
+ ...(disabled ? {} : { onRemove: () => remove(option.value) }),
124
+ }));
125
+ return (
126
+ /* The chips sit beside the combobox, not inside it: React Aria builds the combobox's
127
+ list from what it holds, and a tag group in there is read as part of that list. The
128
+ popover is anchored to the whole box and as wide as it — the list continues the field,
129
+ and the input alone is a sliver after the last chip. */
130
+ _jsxs(Group, { ref: boxRef, isDisabled: disabled, isInvalid: invalid, className: (state) => fieldGroupStyles({
131
+ ...state,
132
+ /* The box grows with the chips: five categories wrap to a second line instead of
133
+ scrolling out of sight. */
134
+ className: "h-auto min-h-(--size-control-md) flex-wrap gap-1 py-1 pl-1.5",
135
+ }), children: [chips.length === 0 ? null : (_jsx(Tags, { items: chips, ...(labelledBy === undefined
136
+ ? ariaLabel === undefined
137
+ ? {}
138
+ : { label: ariaLabel }
139
+ : { labelledBy }) })), _jsxs(AriaComboBox, { id: id, "aria-labelledby": labelledBy, "aria-label": ariaLabel, "aria-describedby": describedBy, items: listed, selectedKey: null, onSelectionChange: commit, inputValue: text, onInputChange: setText,
140
+ /* Arriving at the field shows what can still be added, the way a many2one opens its
141
+ list on focus. With every tag chosen and no text there is nothing to show, and the
142
+ list stays closed. */
143
+ menuTrigger: "focus", onOpenChange: (isOpen) => {
144
+ if (isOpen)
145
+ setWidth(boxRef.current?.getBoundingClientRect().width ?? null);
146
+ }, isInvalid: invalid, isDisabled: disabled, className: "flex min-w-24 flex-1", children: [_jsx(TagInput, { inputRef: inputRef, placeholder: value.length === 0 ? (placeholder ?? copy.inlineEdit.addTag) : undefined, firstKey: listed[0]?.value ?? null, onTake: commit, onBackspace: () => {
147
+ const last = value.at(-1);
148
+ if (last !== undefined)
149
+ onChange?.(withoutTag(value, last));
150
+ }, empty: text === "" }), _jsx(Popover, { triggerRef: boxRef, ...(width === null
151
+ ? {}
152
+ : { style: { "--trigger-width": `${width}px` } }), placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { items: listed, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, className: (state) => cn(listOptionStyles(state), item.value === CREATE ? "text-primary" : ""), children: item.value === CREATE ? (_jsxs(_Fragment, { children: [_jsx(Plus, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: item.label })] })) : (_jsx("span", { className: "truncate", children: item.label })) })) }) })] })] }));
153
+ }
154
+ /** The input after the chips. It reads the combobox's state to know whether the keyboard
155
+ * marked an option — Enter with nothing marked takes the first match. */
156
+ function TagInput({ inputRef, placeholder, firstKey, onTake, onBackspace, empty, }) {
157
+ const state = useContext(ComboBoxStateContext);
158
+ /* While typing, the first match is MARKED — so the row Enter is going to take is the row
159
+ the eye sees tinted, not a guess. Only when the keyboard has not marked another one. */
160
+ const open = state?.isOpen === true;
161
+ useEffect(() => {
162
+ if (state === null || !open || empty || firstKey === null)
163
+ return;
164
+ const focused = state.selectionManager.focusedKey;
165
+ if (focused === null || state.collection.getItem(focused) === null) {
166
+ state.selectionManager.setFocusedKey(firstKey);
167
+ }
168
+ }, [state, open, empty, firstKey]);
169
+ const onKeyDown = (event) => {
170
+ if (event.key === "Backspace" && empty) {
171
+ onBackspace();
172
+ return;
173
+ }
174
+ if (event.key !== "Enter" || empty || firstKey === null)
175
+ return;
176
+ if (state?.selectionManager.focusedKey != null)
177
+ return;
178
+ /* Stopped in the capture phase, so React Aria's own Enter — «commit nothing, reset the
179
+ text» — does not run after ours. */
180
+ event.preventDefault();
181
+ event.stopPropagation();
182
+ state?.close();
183
+ onTake(firstKey);
184
+ };
185
+ return (_jsx(Input, { ref: inputRef, placeholder: placeholder, onKeyDownCapture: onKeyDown, className: "h-(--size-control-sm) min-w-0 flex-1 bg-transparent px-1 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed" }));
186
+ }
@@ -0,0 +1,38 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ /**
3
+ * One tag that can be put on a record: what is stored (`value`, the id) and what is read
4
+ * (`label`), plus the tone it MEANS when it means something.
5
+ *
6
+ * Context: the same plain shape as `ChoiceOption` — the categories of a customer and the
7
+ * labels of an invoice come from data, and the ids go back to the backend as they came.
8
+ */
9
+ export interface TagOption {
10
+ value: string;
11
+ label: string;
12
+ /** Neutral by default: «mayorista» classifies, it does not warn. */
13
+ tone?: Tone;
14
+ }
15
+ /** How the typed text is compared — the locale's, handed in by the component. */
16
+ export type TagMatch = {
17
+ /** `label` contains `query`, ignoring case and accents. */
18
+ contains: (label: string, query: string) => boolean;
19
+ /** `a` and `b` are the same word, ignoring case and accents. */
20
+ same: (a: string, b: string) => boolean;
21
+ };
22
+ /**
23
+ * What the inline input offers for a given text.
24
+ *
25
+ * Context: pure and outside the component because it is the contract of the field — the
26
+ * chosen ones never come back as options (choosing «mayorista» twice is a duplicate, not a
27
+ * choice), the typed text filters, and «create» is offered only when the text names NO
28
+ * existing tag, chosen or not: creating «Mayorista» next to «mayorista» is how a catalog
29
+ * ends up with both.
30
+ */
31
+ export declare function tagChoices(options: readonly TagOption[], value: readonly string[], text: string, match: TagMatch, creatable: boolean): {
32
+ available: TagOption[];
33
+ create: string | null;
34
+ };
35
+ /** The value with one more id at the end; the same value when it is already there. */
36
+ export declare function withTag(value: readonly string[], id: string): string[];
37
+ /** The value without that id. */
38
+ export declare function withoutTag(value: readonly string[], id: string): string[];
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What the inline input offers for a given text.
3
+ *
4
+ * Context: pure and outside the component because it is the contract of the field — the
5
+ * chosen ones never come back as options (choosing «mayorista» twice is a duplicate, not a
6
+ * choice), the typed text filters, and «create» is offered only when the text names NO
7
+ * existing tag, chosen or not: creating «Mayorista» next to «mayorista» is how a catalog
8
+ * ends up with both.
9
+ */
10
+ export function tagChoices(options, value, text, match, creatable) {
11
+ const chosen = new Set(value);
12
+ const query = text.trim();
13
+ const available = options.filter((option) => !chosen.has(option.value) && (query === "" || match.contains(option.label, query)));
14
+ const create = creatable && query !== "" && !options.some((option) => match.same(option.label, query))
15
+ ? query
16
+ : null;
17
+ return { available, create };
18
+ }
19
+ /** The value with one more id at the end; the same value when it is already there. */
20
+ export function withTag(value, id) {
21
+ return value.includes(id) ? [...value] : [...value, id];
22
+ }
23
+ /** The value without that id. */
24
+ export function withoutTag(value, id) {
25
+ return value.filter((one) => one !== id);
26
+ }
@@ -0,0 +1,4 @@
1
+ export { StatusDot, Tag } from "./Tag.js";
2
+ export { type TagItem, Tags } from "./Tags.js";
3
+ export { type TagOption } from "./choices.js";
4
+ export { TagsField, type TagsFieldProps } from "./TagsField.js";
@@ -0,0 +1,8 @@
1
+ /* Saying a state without writing a sentence: the capsule and the dot. */
2
+ export { StatusDot, Tag } from "./Tag.js";
3
+ /* La tira de fichas: la misma en la barra, en el desplegable y en cualquier
4
+ lugar donde se dibuje una lista de palabras cortas. */
5
+ export { Tags } from "./Tags.js";
6
+ /* El many2many: las fichas elegidas más una entrada que agrega la siguiente. */
7
+ export {} from "./choices.js";
8
+ export { TagsField } from "./TagsField.js";
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function Toolbar({ label, children, orientation, }: {
3
+ /** What the group is, read aloud on entering it: «Acciones de las líneas». A screen with
4
+ * two toolbars is two unnamed groups otherwise. */
5
+ label: string;
6
+ children: ReactNode;
7
+ orientation?: "horizontal" | "vertical";
8
+ }): import("react").JSX.Element;
9
+ /** A hairline between two groups of a `Toolbar`, across its direction. */
10
+ export declare function ToolbarSeparator({ orientation, }: {
11
+ /** `vertical` (the default) splits a horizontal toolbar; `horizontal`, a vertical one. */
12
+ orientation?: "horizontal" | "vertical";
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { tv } from "../../foundations/variants.js";
4
+ import { Separator as AriaSeparator, Toolbar as AriaToolbar, } from "react-aria-components";
5
+ /**
6
+ * A row of controls that is ONE stop for the keyboard: the actions above an editable table,
7
+ * the bar of a rich-text editor.
8
+ *
9
+ * Context: a table with eight actions above it costs eight Tabs before reaching the first
10
+ * cell. Inside a toolbar Tab enters and leaves the group once, and the arrows move between
11
+ * its controls — the way every desktop application has always worked. React Aria does the
12
+ * moving by walking the DOM for focusable elements, so the system's own `Button` and
13
+ * `IconButton` (plain native buttons) work inside it as they are, with nothing to forward.
14
+ *
15
+ * It draws no box of its own: it sits on whatever surface it is placed, like the actions of a
16
+ * record header. The gap is the one between buttons of a row; a `ToolbarSeparator` splits
17
+ * the groups (edit · insert · export) so a long bar still reads as a few decisions.
18
+ */
19
+ const toolbar = tv({
20
+ base: "flex gap-1",
21
+ variants: {
22
+ orientation: {
23
+ horizontal: "flex-row flex-wrap items-center",
24
+ vertical: "flex-col items-start",
25
+ },
26
+ },
27
+ });
28
+ export function Toolbar({ label, children, orientation = "horizontal", }) {
29
+ return (_jsx(AriaToolbar, { "aria-label": label, orientation: orientation, className: (state) => toolbar({ orientation: state.orientation }), children: children }));
30
+ }
31
+ /** A hairline between two groups of a `Toolbar`, across its direction. */
32
+ export function ToolbarSeparator({ orientation = "vertical", }) {
33
+ return (_jsx(AriaSeparator, { orientation: orientation, className: orientation === "vertical"
34
+ ? "mx-1 h-(--size-icon-lg) w-px shrink-0 border-none bg-line-strong forced-colors:bg-[ButtonBorder]"
35
+ : "my-1 h-px w-full border-none bg-line-strong forced-colors:bg-[ButtonBorder]" }));
36
+ }
@@ -0,0 +1 @@
1
+ export { Toolbar, ToolbarSeparator } from "./Toolbar.js";
@@ -0,0 +1,2 @@
1
+ /* LA BARRA DE HERRAMIENTAS — un grupo de controles que se recorre con las flechas. */
2
+ export { Toolbar, ToolbarSeparator } from "./Toolbar.js";
@@ -0,0 +1,56 @@
1
+ import { type LucideIcon } from "lucide-react";
2
+ /** One record of the hierarchy. With `children` it is a group that folds. */
3
+ export interface TreeItem {
4
+ id: string;
5
+ label: string;
6
+ /** What reads beside the name, at the right, in tabular figures: a balance, a stock, a
7
+ * count. Already formatted — the tree does not know a currency from a unit. */
8
+ meta?: string;
9
+ icon?: LucideIcon;
10
+ children?: readonly TreeItem[];
11
+ /** It cannot be chosen — a view account that groups others and takes no entries. It still
12
+ * folds and opens. */
13
+ disabled?: boolean;
14
+ }
15
+ /** One name or the other: a tree with no name is announced as a lone «tree». */
16
+ type TreeName = {
17
+ /** The accessible name: «Plan de cuentas». */
18
+ label: string;
19
+ labelledBy?: never;
20
+ } | {
21
+ /** The id of a visible heading that names the tree — the label of the `Field` it lives
22
+ * in. */
23
+ labelledBy: string;
24
+ label?: never;
25
+ };
26
+ export type TreeProps = TreeName & {
27
+ items: readonly TreeItem[];
28
+ /** The groups open right now. Pass it with `onExpandedChange` to own the expansion. */
29
+ expanded?: readonly string[];
30
+ /** The groups that start open, when the tree owns its expansion. */
31
+ defaultExpanded?: readonly string[];
32
+ onExpandedChange?: (ids: string[]) => void;
33
+ /** `none` reads; `single` chooses one record (a category, a location); `multiple` a set. */
34
+ selectionMode?: "none" | "single" | "multiple";
35
+ /** The chosen ids — a list in both modes, like every other control, so a screen does not
36
+ * switch shapes when one becomes many. */
37
+ selected?: readonly string[];
38
+ defaultSelected?: readonly string[];
39
+ onSelectedChange?: (ids: string[]) => void;
40
+ /** Open the record: Enter, or a click (a double click when the tree chooses one). Without
41
+ * it, a click on a group folds or opens it. */
42
+ onAction?: (id: string) => void;
43
+ /** What an empty tree says. By default, the copy table's `tree.empty`. */
44
+ empty?: string;
45
+ };
46
+ /**
47
+ * A hierarchy of records — the chart of accounts, product categories, warehouse locations —
48
+ * with its groups that fold, a figure per row, and one or many rows to choose.
49
+ *
50
+ * Context: the keyboard is React Aria's `Tree`: arrows walk the visible rows, → opens a group
51
+ * and ← folds it (or goes up to the parent), Home and End jump, and typing a name moves to it.
52
+ * Escape does NOT clear the selection: the tree often lives in a dialog or a popover, and
53
+ * Escape there means «close» — clearing the choice on the way out would lose it without a word.
54
+ */
55
+ export declare function Tree({ items, label, labelledBy, expanded, defaultExpanded, onExpandedChange, selectionMode, selected, defaultSelected, onSelectedChange, onAction, empty, }: TreeProps): import("react").JSX.Element;
56
+ export {};
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { cn, focusRing, tv } from "../../foundations/variants.js";
7
+ import { Check, ChevronRight } from "lucide-react";
8
+ import { Button as AriaButton, Checkbox as AriaCheckbox, Tree as AriaTree, TreeItem as AriaTreeItem, TreeItemContent, } from "react-aria-components";
9
+ const row = tv({
10
+ extend: focusRing,
11
+ base: "group relative flex min-w-0 cursor-default items-center rounded-control text-meta text-ink -outline-offset-2 select-none forced-color-adjust-none",
12
+ variants: {
13
+ isSelected: {
14
+ true: "bg-primary/10 forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
15
+ },
16
+ isHovered: { true: "" },
17
+ },
18
+ compoundVariants: [{ isSelected: false, isHovered: true, class: "bg-ink/4" }],
19
+ });
20
+ /* The chevron column is exactly one indent step wide, so a child's chevron lands under its
21
+ parent's name and the levels read as columns, not as a ragged edge. */
22
+ const chevronButton = tv({
23
+ extend: focusRing,
24
+ base: "inline-flex size-(--spacing-indent) shrink-0 items-center justify-center rounded-mark text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink pressed:bg-ink/10",
25
+ });
26
+ /** Every id of the hierarchy that can be chosen — what «select all» means here. */
27
+ function choosable(items) {
28
+ return items.flatMap((item) => [
29
+ ...(item.disabled === true ? [] : [item.id]),
30
+ ...choosable(item.children ?? []),
31
+ ]);
32
+ }
33
+ /**
34
+ * A hierarchy of records — the chart of accounts, product categories, warehouse locations —
35
+ * with its groups that fold, a figure per row, and one or many rows to choose.
36
+ *
37
+ * Context: the keyboard is React Aria's `Tree`: arrows walk the visible rows, → opens a group
38
+ * and ← folds it (or goes up to the parent), Home and End jump, and typing a name moves to it.
39
+ * Escape does NOT clear the selection: the tree often lives in a dialog or a popover, and
40
+ * Escape there means «close» — clearing the choice on the way out would lose it without a word.
41
+ */
42
+ export function Tree({ items, label, labelledBy, expanded, defaultExpanded, onExpandedChange, selectionMode = "none", selected, defaultSelected, onSelectedChange, onAction, empty, }) {
43
+ const copy = useUiCopy();
44
+ const density = useDensity();
45
+ const pad = byDensity(density, "py-1.5 pe-2", "py-0.5 pe-1.5");
46
+ const many = selectionMode === "multiple";
47
+ const onSelectionChange = (keys) => onSelectedChange?.(keys === "all" ? choosable(items) : [...keys].map(String));
48
+ const render = (node, level) => {
49
+ const children = node.children ?? [];
50
+ const group = children.length > 0;
51
+ const Icon = node.icon;
52
+ /* Each level is one indent step; the first one keeps a sliver of the row's own padding so
53
+ the chevron is not glued to the edge of its tint. */
54
+ const indent = {
55
+ paddingInlineStart: `calc(var(--spacing) * 1 + var(--spacing-indent) * ${level - 1})`,
56
+ };
57
+ return (_jsxs(AriaTreeItem, { id: node.id, textValue: node.label, isDisabled: node.disabled === true, className: ({ isSelected, isHovered, isFocusVisible }) => cn(row({ isSelected, isHovered, isFocusVisible }), pad), style: indent, children: [_jsx(TreeItemContent, { children: ({ isExpanded, isSelected }) => (_jsxs(_Fragment, { children: [group ? (_jsx(AriaButton, { slot: "chevron", className: ({ isFocusVisible }) => chevronButton({ isFocusVisible }), children: _jsx(ChevronRight, { className: `size-(--size-icon-sm) transition-transform duration-(--duration-quick) ease-(--ease-out) ${isExpanded ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true }) })) : (_jsx("span", { "aria-hidden": true, className: "size-(--spacing-indent) shrink-0" })), _jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [many ? _jsx(SelectionCheck, {}) : null, Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-md) shrink-0 text-muted", strokeWidth: 2, "aria-hidden": true })), _jsx("span", { className: cn("min-w-0 flex-1 truncate", group ? "font-medium" : "", isSelected ? "font-medium" : "", node.disabled === true && selectionMode !== "none" ? "text-muted" : ""), children: node.label }), node.meta === undefined ? null : (_jsx("span", { className: "shrink-0 ps-3 text-right tabular-nums whitespace-nowrap text-muted", children: node.meta })), selectionMode === "single" && isSelected ? (_jsx(Check, { className: "size-(--size-icon-sm) shrink-0 text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5, "aria-hidden": true })) : null] })] })) }), children.map((child) => render(child, level + 1))] }, node.id));
58
+ };
59
+ return (_jsx(AriaLocale, { children: _jsx(AriaTree, { ...(labelledBy === undefined
60
+ ? { "aria-label": label }
61
+ : { "aria-labelledby": labelledBy }), selectionMode: selectionMode,
62
+ /* One to choose: a click chooses and a double click opens. Many: the click opens and
63
+ the check chooses. See the header. */
64
+ selectionBehavior: selectionMode === "single" ? "replace" : "toggle", disabledBehavior: "selection", escapeKeyBehavior: "none", ...(selected === undefined ? {} : { selectedKeys: selected }), ...(defaultSelected === undefined ? {} : { defaultSelectedKeys: defaultSelected }), ...(expanded === undefined ? {} : { expandedKeys: expanded }), ...(defaultExpanded === undefined ? {} : { defaultExpandedKeys: defaultExpanded }), ...(onExpandedChange === undefined
65
+ ? {}
66
+ : { onExpandedChange: (keys) => onExpandedChange([...keys].map(String)) }), ...(onSelectedChange === undefined ? {} : { onSelectionChange }), ...(onAction === undefined ? {} : { onAction: (key) => onAction(String(key)) }), renderEmptyState: () => (_jsx("p", { className: "px-2 py-3 text-meta text-muted", children: empty ?? copy.tree.empty })), className: "flex w-full min-w-0 flex-col gap-px outline-0", children: items.map((item) => render(item, 1)) }) }));
67
+ }
68
+ /* The check of a row when many can be chosen. It is React Aria's `selection` slot: it toggles
69
+ the row and never opens it. Drawn like `Checkbox`'s box so a check reads as one thing. */
70
+ function SelectionCheck() {
71
+ return (_jsx(AriaCheckbox, { slot: "selection", className: "group/check inline-flex shrink-0 outline-0", children: ({ isSelected, isFocusVisible }) => (_jsx("span", { "aria-hidden": true, className: cn("inline-flex size-4 items-center justify-center rounded-mark border transition-colors duration-(--duration-instant) forced-color-adjust-none", isSelected
72
+ ? "border-primary bg-primary text-surface forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]"
73
+ : "border-line-strong bg-surface text-transparent forced-colors:border-[ButtonBorder]", focusRing({ isFocusVisible })), children: _jsx(Check, { className: "size-3", strokeWidth: 2.5 }) })) }));
74
+ }
@@ -0,0 +1 @@
1
+ export { Tree, type TreeItem, type TreeProps } from "./Tree.js";
@@ -0,0 +1,3 @@
1
+ /* A hierarchy of records — the chart of accounts, categories, locations: read, folded and
2
+ chosen from. Not the app's menu: that is `NavigationTree`. */
3
+ export { Tree } from "./Tree.js";
@@ -0,0 +1,48 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * UN VALOR QUE SE COPIA.
4
+ *
5
+ * Context: la mitad de los valores de este producto existen para pegarse en otro
6
+ * lado —un NIT, un CUF de factura, una referencia bancaria, un código de
7
+ * autorización de 26 caracteres—. Seleccionarlos a mano falla justo con los que
8
+ * más importa no equivocar: son largos, van en monoespaciada y se cortan.
9
+ *
10
+ * ── Confirma, y por eso el estado vive acá ──────────────────────────
11
+ *
12
+ * Copiar no tiene efecto visible: el portapapeles está afuera de la pantalla. Un
13
+ * botón que no contesta se aprieta tres veces «por las dudas». Por eso el ícono
14
+ * pasa a un tilde por dos segundos — lo mínimo para creerle sin quedarse
15
+ * mirando.
16
+ *
17
+ * ── Y puede fallar ──────────────────────────────────────────────────
18
+ *
19
+ * `navigator.clipboard` no existe fuera de un contexto seguro, y en algunos
20
+ * navegadores exige un permiso que se puede negar. Se contempla: si falla, el
21
+ * botón lo dice en vez de fingir que copió. Una confirmación falsa es peor que
22
+ * ninguna, porque alguien pega lo que tenía antes y no se entera.
23
+ */
24
+ export type CopyableProps = {
25
+ /** Lo que se copia. Siempre texto: el portapapeles no tiene tipos. */
26
+ value: string;
27
+ /** Cómo se ve, si no es el valor tal cual. Un fingerprint se muestra cortado y
28
+ * se copia entero, que es justamente por qué las dos cosas son props
29
+ * distintas. */
30
+ children?: ReactNode;
31
+ /** El valor es de máquina: id, hash, clave. Sin esto, tipografía normal. */
32
+ mono?: boolean;
33
+ /** Sólo el botón, sin el valor al lado. Para una celda o un encabezado donde
34
+ * el valor ya está dibujado. */
35
+ bare?: boolean;
36
+ };
37
+ /**
38
+ * The button alone, for a value that is only known when it is pressed — what a field shows
39
+ * at that moment (`Field`'s copy button reads it from the screen). `Copyable` is this
40
+ * button next to its value.
41
+ */
42
+ export declare function CopyButton({ value, label, }: {
43
+ /** The text, or how to get it at the moment of the press. */
44
+ value: string | (() => string);
45
+ /** Its name at rest. Defaults to the copy table's «Copiar». */
46
+ label?: string;
47
+ }): import("react").JSX.Element;
48
+ export declare function Copyable({ value, children, mono, bare }: CopyableProps): import("react").JSX.Element;
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { Check, Copy } from "lucide-react";
5
+ import { useEffect, useRef, useState } from "react";
6
+ /** Cuánto dura el tilde. Dos segundos: alcanza para verlo sin que el botón se
7
+ * quede en un estado que ya no es cierto. */
8
+ const CONFIRMED_FOR = 2000;
9
+ /**
10
+ * The button alone, for a value that is only known when it is pressed — what a field shows
11
+ * at that moment (`Field`'s copy button reads it from the screen). `Copyable` is this
12
+ * button next to its value.
13
+ */
14
+ export function CopyButton({ value, label, }) {
15
+ const copy = useUiCopy();
16
+ const [state, setState] = useState("idle");
17
+ const timer = useRef(null);
18
+ /* Se limpia al desmontar: sin esto, copiar y cerrar el panel antes de los dos
19
+ segundos deja un `setState` apuntando a un componente que ya no existe. */
20
+ useEffect(() => () => {
21
+ if (timer.current !== null)
22
+ clearTimeout(timer.current);
23
+ }, []);
24
+ async function run() {
25
+ if (timer.current !== null)
26
+ clearTimeout(timer.current);
27
+ const text = typeof value === "function" ? value() : value;
28
+ if (text.trim() === "") {
29
+ /* Nothing on screen to copy: said, not a check mark over an empty clipboard. */
30
+ setState("empty");
31
+ timer.current = setTimeout(() => setState("idle"), CONFIRMED_FOR);
32
+ return;
33
+ }
34
+ try {
35
+ await navigator.clipboard.writeText(text);
36
+ setState("done");
37
+ }
38
+ catch {
39
+ /* Sin portapapeles —contexto no seguro, permiso negado— se DICE. Fingir
40
+ que copió hace que alguien pegue lo que tenía antes sin enterarse. */
41
+ setState("failed");
42
+ }
43
+ timer.current = setTimeout(() => setState("idle"), CONFIRMED_FOR);
44
+ }
45
+ const said = state === "done"
46
+ ? copy.copyable.done
47
+ : state === "failed"
48
+ ? copy.copyable.failed
49
+ : state === "empty"
50
+ ? copy.copyable.empty
51
+ : (label ?? copy.copyable.label);
52
+ return (_jsx("button", { type: "button", onClick: (e) => {
53
+ /* Inside a row that opens or a link: copying is not opening. */
54
+ e.stopPropagation();
55
+ void run();
56
+ }, "aria-label": said, title: said, className: `inline-flex shrink-0 items-center justify-center rounded-control p-0.5 transition-colors duration-(--duration-instant) ${state === "done"
57
+ ? "text-good"
58
+ : state === "failed" || state === "empty"
59
+ ? "text-bad"
60
+ : "text-muted hover:bg-ink/6 hover:text-ink"}`, children: state === "done" ? (_jsx(Check, { className: "size-(--size-icon-sm)", strokeWidth: 2.5 })) : (_jsx(Copy, { className: "size-(--size-icon-sm)", strokeWidth: 2 })) }));
61
+ }
62
+ export function Copyable({ value, children, mono = false, bare = false }) {
63
+ const button = _jsx(CopyButton, { value: value });
64
+ if (bare)
65
+ return button;
66
+ return (
67
+ /* El botón aparece con el puntero y ocupa lugar SIEMPRE: si apareciera de la
68
+ nada, el valor se correría al pasar por encima, y un texto que se mueve
69
+ cuando vas a seleccionarlo es peor que no tener botón. */
70
+ _jsxs("span", { className: "group/copy inline-flex min-w-0 items-center gap-1", children: [_jsx("span", { className: `min-w-0 truncate ${mono ? "font-mono" : ""}`, children: children ?? value }), _jsx("span", { className: "opacity-0 transition-opacity duration-(--duration-instant) group-hover/copy:opacity-100 focus-within:opacity-100", children: button })] }));
71
+ }
@@ -0,0 +1,12 @@
1
+ export declare function formatRelative(when: Date, now?: Date): string;
2
+ export declare function formatDateTime(when: Date): string;
3
+ export declare function DateValue({ when, format,
4
+ /** So it can be tested: without this, a story with «hace 2 min» changes its
5
+ * text every minute and no snapshot works as a reference. */
6
+ now, }: {
7
+ when: Date | string | number;
8
+ /** `relative` for lists and logs; `exact` when the datum gets compared or
9
+ * copied; `date` when the time of day means nothing. */
10
+ format?: "relative" | "exact" | "date";
11
+ now?: Date;
12
+ }): import("react").JSX.Element;