@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,121 @@
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, tv } from "../../foundations/variants.js";
5
+ import { Button, IconButton } from "../button/Button.js";
6
+ import { Maximize2, X } from "lucide-react";
7
+ import { useId } from "react";
8
+ import { Dialog as AriaDialog, Heading, Modal, ModalOverlay } from "react-aria-components";
9
+ /** The scrim. The page stays visible under it — the dialog is ABOUT something on it.
10
+ * On a phone it drops the padding and pushes the window to the bottom edge. */
11
+ const overlay = tv({
12
+ base: "fixed inset-0 z-50 flex items-center justify-center bg-scrim/40 p-4 transition-opacity duration-(--duration-quick) ease-(--ease-out) max-sm:items-end max-sm:p-0 sm:p-8",
13
+ variants: {
14
+ isEntering: { true: "opacity-0" },
15
+ isExiting: { true: "opacity-0" },
16
+ },
17
+ });
18
+ /** The window. It never grows past the scrim's padding: when the form is long the BODY
19
+ * scrolls and the header (what it is about) and the footer (what to do) stay in view.
20
+ *
21
+ * On a phone it is a bottom sheet: every width is the full width, only the top corners
22
+ * are rounded (the bottom ones sit on the screen's edge), it stops at `--size-sheet`,
23
+ * and it enters from below — the motion says where it lives. */
24
+ const modal = tv({
25
+ base: "flex max-h-full w-full flex-col overflow-hidden rounded-panel border border-line-strong bg-surface text-ink shadow-window outline-0 transition-[opacity,translate] duration-(--duration-panel) ease-(--ease-out) forced-colors:bg-[Canvas] max-sm:max-h-(--size-sheet) max-sm:max-w-none max-sm:rounded-b-none max-sm:border-x-0 max-sm:border-b-0",
26
+ variants: {
27
+ width: { sm: "max-w-md", md: "max-w-2xl", lg: "max-w-4xl" },
28
+ isEntering: { true: "translate-y-2 opacity-0 max-sm:translate-y-full" },
29
+ isExiting: {
30
+ true: "translate-y-1 opacity-0 duration-(--duration-quick) max-sm:translate-y-full",
31
+ },
32
+ },
33
+ });
34
+ /** The scrim and the window recipes. Internal — exported only so a test can read the
35
+ * classes that make the bottom sheet: the modal is a portal and draws nothing on the
36
+ * server. */
37
+ export const dialogStyles = { overlay, modal };
38
+ /** The inside of the window: React Aria's `Dialog` with the header, body and footer.
39
+ *
40
+ * Internal — exported only so a test can read its markup, which the modal (a portal)
41
+ * does not produce on the server. */
42
+ export function DialogSurface({ title, subtitle, context, onClose, onExpand, expandLabel, footer, children, }) {
43
+ const copy = useUiCopy();
44
+ const subtitleId = useId();
45
+ const expand = expandLabel ?? copy.dialog.expand;
46
+ return (_jsxs(AriaDialog, { "aria-describedby": subtitle === undefined ? undefined : subtitleId, className: "flex max-h-[inherit] min-h-0 flex-1 flex-col outline-0", children: [_jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b border-line px-4 py-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [context === undefined ? null : (
47
+ /* The context, on top and small. It is what keeps the first field
48
+ from being «about what?». Same voice as a field label, not a
49
+ console kicker: mono uppercase next to a form title was a
50
+ second chrome fighting the first. */
51
+ _jsxs("p", { className: "mb-0.5 flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5 text-micro text-muted", children: [context.kind === undefined ? null : (_jsx("span", { className: "font-medium", children: context.kind })), _jsx("span", { className: "min-w-0 truncate text-ink", children: context.label }), context.detail === undefined ? null : (_jsx("span", { className: "min-w-0 truncate tabular-nums", children: context.detail }))] })), _jsx(Heading, { slot: "title", className: "text-title font-semibold text-ink", children: title }), subtitle === undefined ? null : (_jsx("p", { id: subtitleId, className: "mt-1 max-w-prose text-meta text-muted", children: subtitle }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [onExpand === undefined ? null : (_jsx(IconButton, { icon: Maximize2, label: expand, onClick: onExpand })), _jsx(IconButton, { icon: X, label: copy.shell.close, onClick: onClose })] })] }), _jsx("div", { className: cn("min-h-0 flex-1 overflow-y-auto px-4 py-4", footer === undefined && "max-sm:safe-bottom"), children: children }), footer === undefined ? null : (_jsx("footer", { className: "flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-line bg-ground px-4 py-3 max-sm:safe-bottom", children: footer }))] }));
52
+ }
53
+ /**
54
+ * What has to be answered before moving on: a short form that blocks the page and keeps
55
+ * saying what it is about.
56
+ *
57
+ * Context: controlled by `open` + `onClose` — the screen that opens it owns the decision of
58
+ * when it is open, and every way out (×, Escape, the scrim) reports through `onClose`. It
59
+ * stays mounted for the exit transition after `open` turns `false`, so the window fades
60
+ * out instead of vanishing.
61
+ */
62
+ export function Dialog({ open, width = "md", onClose, ...surface }) {
63
+ return (_jsx(ModalOverlay, { isOpen: open, onOpenChange: (next) => {
64
+ if (!next)
65
+ onClose();
66
+ }, isDismissable: true, className: overlay, children: _jsx(Modal, { className: (state) => modal({ ...state, width }), children: _jsx(DialogSurface, { onClose: onClose, ...surface }) }) }));
67
+ }
68
+ /** A dialog with steps, for what CANNOT be answered in one go.
69
+ *
70
+ * ── When a form deserves to be a wizard ─────────────────────────────
71
+ *
72
+ * Only when **one answer changes the following questions**. If the fields are
73
+ * independent, splitting them into steps simplifies nothing: it adds clicks and
74
+ * hides half the form, and whoever wants to review what they entered has to
75
+ * navigate backwards. A long but complete form is filled in faster than three
76
+ * short steps.
77
+ *
78
+ * The case that does justify it: a purchase order. Whether it is a local purchase
79
+ * or an import changes which conditions exist — an import asks for currency and
80
+ * incoterm, a local one for payment terms. There, step 2 cannot even be drawn
81
+ * until step 1 has been answered.
82
+ *
83
+ * ── The steps already completed can be clicked ──────────────────────
84
+ *
85
+ * Going back is not a mistake: it is reviewing. What you cannot do is jump
86
+ * ahead, because the next step does not know yet what to ask.
87
+ *
88
+ * ── It is the CONTENT of a `Dialog`, not a second one ────────────────
89
+ *
90
+ * The focus trap, Escape and the return of focus are the dialog's. The wizard
91
+ * only moves between steps, so it has no overlay behaviour of its own. */
92
+ export function Wizard({ steps, current, onStepChange, children, onCancel, onBack, onNext, nextLabel, busy = false, }) {
93
+ /* The words of the wizard's own buttons come from the copy table. */
94
+ const words = useUiCopy().dialog;
95
+ const at = steps.findIndex((s) => s.id === current);
96
+ const step = steps[at];
97
+ const last = at === steps.length - 1;
98
+ const blocked = step?.blocked;
99
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-3", children: [_jsx("ol", { className: "flex min-w-0 flex-wrap items-center gap-0.5 rounded-box bg-ink/4 p-1", children: steps.map((s, i) => {
100
+ const done = i < at;
101
+ const here = i === at;
102
+ return (_jsx("li", { className: "flex items-center", children: _jsxs("button", { type: "button", disabled: i > at, onClick: () => {
103
+ if (!here)
104
+ onStepChange?.(s.id);
105
+ }, "aria-current": here ? "step" : undefined, "data-state": here ? "current" : done ? "done" : "pending", className: `inline-flex items-center gap-1.5 rounded-control px-2 py-1 text-micro transition-colors duration-(--duration-instant) disabled:pointer-events-none focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${here
106
+ ? "bg-surface font-medium text-ink"
107
+ : done
108
+ ? "text-ink hover:bg-surface/70"
109
+ : "text-muted"}`, children: [_jsx("span", { className: `inline-flex size-5 shrink-0 items-center justify-center rounded-pill text-micro tabular-nums ${here
110
+ ? "bg-ink text-surface"
111
+ : done
112
+ ? "bg-ink/10 text-ink"
113
+ : "bg-ink/8 text-muted"}`, children: i + 1 }), s.label] }) }, s.id));
114
+ }) }), _jsx("div", { className: "min-w-0", children: children }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t border-line pt-3", children: [blocked === undefined ? null : (
115
+ /* The reason, next to the disabled button. A grey «next» with no
116
+ explanation is a dead end: the user tries, nothing happens, and
117
+ there is no way to know what is missing. */
118
+ _jsx("span", { className: "mr-auto min-w-0 text-micro text-warn", children: blocked })), _jsxs("span", { className: blocked === undefined
119
+ ? "ml-auto flex items-center gap-2"
120
+ : "flex items-center gap-2", children: [onCancel === undefined ? null : (_jsx(Button, { role: "ghost", onClick: onCancel, children: words.cancel })), at > 0 ? (_jsx(Button, { role: "secondary", onClick: onBack, children: words.back })) : null, _jsx(Button, { role: "primary", onClick: onNext, disabled: blocked !== undefined || busy, "aria-busy": busy || undefined, children: busy ? words.busy : (nextLabel ?? (last ? words.finish : words.next)) })] })] })] }));
121
+ }
@@ -0,0 +1,90 @@
1
+ import { type DialogContext, type DialogWidth } from "./Dialog.js";
2
+ import { type ReactNode, type RefObject } from "react";
3
+ /** Where the drill was opened from: the page's record. The first crumb of the trail. */
4
+ export type DrillOrigin = DialogContext;
5
+ /** Which contexts are open, in order, and the ways to move between them. */
6
+ export interface DrillState {
7
+ /** The ids of the open contexts, from the first opened to the one in front. */
8
+ readonly path: readonly string[];
9
+ /** One level deeper. An id already on the path goes back to it instead: a product
10
+ * that leads to its supplier that leads to the same product is a loop, not depth. */
11
+ open: (id: string) => void;
12
+ /** Up one level. On the first one, it closes. */
13
+ back: () => void;
14
+ /** Back to the context with this id, closing the ones in front of it. */
15
+ to: (id: string) => void;
16
+ /** Close every level: back to the page. */
17
+ close: () => void;
18
+ /** Internal: the control that opened each level, where focus returns when the level
19
+ * above it closes. Written by `open`, read by `Drill`. */
20
+ readonly focusMarks: RefObject<(HTMLElement | null)[]>;
21
+ }
22
+ /** One level deeper: push the id — or, when it is already open, go back to it. Internal,
23
+ * exported for the test. */
24
+ export declare function openPath(path: readonly string[], id: string): readonly string[];
25
+ /** Back to the context with this id; an id not on the path changes nothing. */
26
+ export declare function toPath(path: readonly string[], id: string): readonly string[];
27
+ /** The written contexts that are open, in the order of the path. A context on the path
28
+ * that is not written is skipped: there is nothing to draw for it. */
29
+ export declare function framesOf(children: ReactNode, path: readonly string[]): DrillContextProps[];
30
+ /** The state of a `Drill`: the path of open contexts. Pass it to `<Drill state={…}>` and
31
+ * call `open(id)` from wherever the context is opened — a relation's «Abrir», a
32
+ * `Picker`'s open button, a rail action, a line's cell. */
33
+ export declare function useDrill(initial?: readonly string[]): DrillState;
34
+ export interface DrillContextProps {
35
+ /** What `open(id)` names. Unique inside the drill. */
36
+ id: string;
37
+ /** The context's name: the title while it is in front, its crumb when it is behind.
38
+ * Short — «Línea 3», «Impresora térmica 80 mm», «Registrar pago». */
39
+ title: string;
40
+ /** One sentence under the title; also the dialog's description while in front. */
41
+ subtitle?: string;
42
+ /** This context's actions — Cancel and ONE primary. Drawn only while it is in front. */
43
+ footer?: ReactNode;
44
+ /** No padding of its own: for a child that is already a surface with its own margins
45
+ * (a `Record`). A form or a wizard keeps the padding. */
46
+ flush?: boolean;
47
+ children: ReactNode;
48
+ }
49
+ export interface DrillProps {
50
+ state: DrillState;
51
+ /** The page's record, as the first crumb: «pedido · S00142». Pressing it closes. */
52
+ origin?: DrillOrigin;
53
+ /** One width for every level: a window that changes width at each step jumps under the
54
+ * pointer. `lg` by default — a record with pages and lines needs it. */
55
+ width?: DialogWidth;
56
+ /** The `Drill.Context`s. Which ones are open is the state's path. */
57
+ children: ReactNode;
58
+ }
59
+ /**
60
+ * The inside of the window: the trail, the context in front (and the ones behind it,
61
+ * hidden), and the footer of the one in front.
62
+ *
63
+ * Internal — exported only so a test can read its markup, which the modal (a portal)
64
+ * does not produce on the server.
65
+ */
66
+ export declare function DrillSurface({ frames, origin, onBack, onTo, onClose, focusMarks, }: {
67
+ frames: readonly DrillContextProps[];
68
+ origin?: DrillOrigin;
69
+ onBack: () => void;
70
+ onTo: (id: string) => void;
71
+ onClose: () => void;
72
+ focusMarks?: RefObject<(HTMLElement | null)[]>;
73
+ }): import("react").JSX.Element | null;
74
+ /**
75
+ * ONE dialog that holds a stack of contexts opened from a record — and from each other —
76
+ * with a trail back to the page. See the header of this file.
77
+ *
78
+ * const drill = useDrill();
79
+ * …<RelationValue name={product} onOpen={() => drill.open("product")} />…
80
+ * <Drill state={drill} origin={{ kind: "pedido", label: "S00142" }}>
81
+ * <Drill.Context id="product" title={product} flush>
82
+ * <Record identity={…}>…</Record>
83
+ * </Drill.Context>
84
+ * <Drill.Context id="supplier" title={supplier} footer={…}>…</Drill.Context>
85
+ * </Drill>
86
+ */
87
+ export declare function Drill({ state, origin, width, children }: DrillProps): import("react").JSX.Element;
88
+ export declare namespace Drill {
89
+ var Context: ((props: DrillContextProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"context">;
90
+ }
@@ -0,0 +1,172 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
+ import { flatten, propsOf, slot, slotOf } from "../../foundations/slots.js";
5
+ import { cn, focusRing, tv } from "../../foundations/variants.js";
6
+ import { IconButton } from "../button/Button.js";
7
+ import { dialogStyles, } from "./Dialog.js";
8
+ import { ArrowLeft, ChevronRight, X } from "lucide-react";
9
+ import { useEffect, useId, useRef, useState } from "react";
10
+ import { useKeyboard } from "react-aria";
11
+ import { Dialog as AriaDialog, Heading, Link, Modal, ModalOverlay, } from "react-aria-components";
12
+ function focused() {
13
+ if (typeof document === "undefined")
14
+ return null;
15
+ return document.activeElement instanceof HTMLElement ? document.activeElement : null;
16
+ }
17
+ /** One level deeper: push the id — or, when it is already open, go back to it. Internal,
18
+ * exported for the test. */
19
+ export function openPath(path, id) {
20
+ const at = path.indexOf(id);
21
+ return at === -1 ? [...path, id] : path.slice(0, at + 1);
22
+ }
23
+ /** Back to the context with this id; an id not on the path changes nothing. */
24
+ export function toPath(path, id) {
25
+ const at = path.indexOf(id);
26
+ return at === -1 ? path : path.slice(0, at + 1);
27
+ }
28
+ /** The written contexts that are open, in the order of the path. A context on the path
29
+ * that is not written is skipped: there is nothing to draw for it. */
30
+ export function framesOf(children, path) {
31
+ const written = flatten(children)
32
+ .filter((node) => slotOf(node) === "context")
33
+ .map((node) => propsOf(node))
34
+ .filter((props) => props !== undefined);
35
+ return path
36
+ .map((id) => written.find((context) => context.id === id))
37
+ .filter((frame) => frame !== undefined);
38
+ }
39
+ /** The state of a `Drill`: the path of open contexts. Pass it to `<Drill state={…}>` and
40
+ * call `open(id)` from wherever the context is opened — a relation's «Abrir», a
41
+ * `Picker`'s open button, a rail action, a line's cell. */
42
+ export function useDrill(initial = []) {
43
+ const [path, setPath] = useState(initial);
44
+ const focusMarks = useRef([]);
45
+ return {
46
+ path,
47
+ open: (id) => {
48
+ if (!path.includes(id))
49
+ focusMarks.current[path.length] = focused();
50
+ setPath(openPath(path, id));
51
+ },
52
+ back: () => setPath(path.slice(0, -1)),
53
+ to: (id) => setPath(toPath(path, id)),
54
+ close: () => setPath([]),
55
+ focusMarks,
56
+ };
57
+ }
58
+ const crumb = tv({
59
+ extend: focusRing,
60
+ base: "block max-w-(--container-crumb) min-w-0 cursor-pointer truncate rounded-mark text-muted transition-colors duration-(--duration-instant) hover:text-ink hover:underline hover:underline-offset-4",
61
+ });
62
+ /**
63
+ * The inside of the window: the trail, the context in front (and the ones behind it,
64
+ * hidden), and the footer of the one in front.
65
+ *
66
+ * Internal — exported only so a test can read its markup, which the modal (a portal)
67
+ * does not produce on the server.
68
+ */
69
+ export function DrillSurface({ frames, origin, onBack, onTo, onClose, focusMarks, }) {
70
+ const copy = useUiCopy();
71
+ const depth = frames.length;
72
+ const top = frames[depth - 1];
73
+ const parent = depth > 1 ? frames[depth - 2] : undefined;
74
+ const bodies = useRef(new Map());
75
+ const was = useRef(depth);
76
+ const subtitleId = useId();
77
+ const { keyboardProps } = useKeyboard({
78
+ onKeyDown: (e) => {
79
+ if (e.key !== "Escape") {
80
+ e.continuePropagation();
81
+ return;
82
+ }
83
+ if (depth > 1)
84
+ onBack();
85
+ else
86
+ onClose();
87
+ },
88
+ });
89
+ /* Focus follows the level. Down: into the new context, so the keyboard does not stay on
90
+ a control that is now hidden. Up: back to what opened the level that closed. */
91
+ const topId = top?.id;
92
+ useEffect(() => {
93
+ const before = was.current;
94
+ was.current = depth;
95
+ if (before === depth || depth === 0 || topId === undefined)
96
+ return;
97
+ const box = bodies.current.get(topId);
98
+ if (depth > before) {
99
+ box?.focus();
100
+ return;
101
+ }
102
+ const mark = focusMarks?.current[depth];
103
+ (mark?.isConnected === true ? mark : box)?.focus();
104
+ }, [depth, topId, focusMarks]);
105
+ if (top === undefined)
106
+ return null;
107
+ const trail = [
108
+ ...(origin === undefined
109
+ ? []
110
+ : [
111
+ {
112
+ key: "\u0000origin",
113
+ label: (_jsxs(_Fragment, { children: [origin.kind === undefined ? null : (_jsxs("span", { className: "font-medium", children: [origin.kind, " "] })), _jsx("span", { className: "text-ink", children: origin.label })] })),
114
+ onPress: onClose,
115
+ },
116
+ ]),
117
+ ...frames.slice(0, -1).map((frame) => ({
118
+ key: frame.id,
119
+ label: frame.title,
120
+ onPress: () => onTo(frame.id),
121
+ })),
122
+ ];
123
+ return (_jsx(AriaDialog, { ...(top.subtitle === undefined ? {} : { "aria-describedby": subtitleId }), className: "flex max-h-[inherit] min-h-0 flex-1 flex-col outline-0", children: _jsxs("div", { ...keyboardProps, className: "flex max-h-[inherit] min-h-0 flex-1 flex-col", children: [_jsxs("header", { className: "flex shrink-0 items-start gap-2 border-b border-line px-4 py-3", children: [parent === undefined ? null : (_jsx("span", { className: "-ml-1.5 shrink-0", children: _jsx(IconButton, { icon: ArrowLeft, label: fillCopy(copy.drill.back, { name: parent.title }), onClick: onBack }) })), _jsxs("div", { className: "min-w-0 flex-1", children: [trail.length === 0 ? null : (
124
+ /* Where this was opened from, small and on top — the same place `Dialog`
125
+ says what it is about. Every crumb is a way back; the one in front is
126
+ not a crumb, it is the title. */
127
+ _jsx("nav", { "aria-label": copy.drill.trail, className: "mb-0.5 flex min-w-0", children: _jsx("ol", { className: "flex min-w-0 flex-wrap items-center gap-x-1 gap-y-0.5 text-micro", children: trail.map((one, i) => (_jsxs("li", { className: "flex min-w-0 items-center gap-1", children: [i === 0 ? null : (_jsx(ChevronRight, { className: "size-(--size-icon-sm) shrink-0 text-line-strong", strokeWidth: 2, "aria-hidden": true })), _jsx(Link, { onPress: one.onPress, className: ({ isFocusVisible }) => crumb({ isFocusVisible }), children: one.label })] }, one.key))) }) })), _jsx(Heading, { slot: "title", className: "truncate text-title font-semibold text-ink", children: top.title }), top.subtitle === undefined ? null : (_jsx("p", { id: subtitleId, className: "mt-1 max-w-prose text-meta text-muted", children: top.subtitle }))] }), _jsx(IconButton, { icon: X, label: copy.shell.close, onClick: onClose })] }), frames.map((frame) => {
128
+ const front = frame.id === top.id;
129
+ return (
130
+ /* Each level scrolls on its own and the ones behind are `hidden`, not gone:
131
+ their state — a half-typed payment, the open tab of a product — is still
132
+ there when the level above closes. */
133
+ _jsx("div", { ref: (node) => {
134
+ if (node === null)
135
+ bodies.current.delete(frame.id);
136
+ else
137
+ bodies.current.set(frame.id, node);
138
+ }, hidden: !front, tabIndex: -1, "data-drill-context": frame.id, className: cn("min-h-0 flex-1 overflow-y-auto outline-0", frame.flush !== true && "px-4 py-4", front && frame.footer === undefined && "max-sm:safe-bottom"), children: frame.children }, frame.id));
139
+ }), top.footer === undefined ? null : (_jsx("footer", { className: "flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-line bg-ground px-4 py-3 max-sm:safe-bottom", children: top.footer }))] }) }));
140
+ }
141
+ /** One context of a drill: a title, what goes inside, and its own footer. It does not
142
+ * draw where it is written — the `Drill` draws it when its id is on the path. */
143
+ const DrillContextSlot = slot("context", (_props) => null);
144
+ /**
145
+ * ONE dialog that holds a stack of contexts opened from a record — and from each other —
146
+ * with a trail back to the page. See the header of this file.
147
+ *
148
+ * const drill = useDrill();
149
+ * …<RelationValue name={product} onOpen={() => drill.open("product")} />…
150
+ * <Drill state={drill} origin={{ kind: "pedido", label: "S00142" }}>
151
+ * <Drill.Context id="product" title={product} flush>
152
+ * <Record identity={…}>…</Record>
153
+ * </Drill.Context>
154
+ * <Drill.Context id="supplier" title={supplier} footer={…}>…</Drill.Context>
155
+ * </Drill>
156
+ */
157
+ export function Drill({ state, origin, width = "lg", children }) {
158
+ /* While the window fades out the path is already empty: it keeps drawing the last levels
159
+ it had, so the content does not vanish before the window does. */
160
+ const [lastPath, setLastPath] = useState(state.path);
161
+ if (state.path.length > 0 && state.path !== lastPath)
162
+ setLastPath(state.path);
163
+ const path = state.path.length > 0 ? state.path : lastPath;
164
+ const frames = framesOf(children, path);
165
+ return (_jsx(ModalOverlay, { isOpen: state.path.length > 0, onOpenChange: (next) => {
166
+ if (!next)
167
+ state.close();
168
+ }, isDismissable: true,
169
+ /* Escape is ours: up one level, not out of everything. See the header. */
170
+ isKeyboardDismissDisabled: true, className: dialogStyles.overlay, children: _jsx(Modal, { className: (s) => dialogStyles.modal({ ...s, width }), children: _jsx(DrillSurface, { frames: frames, ...(origin === undefined ? {} : { origin }), onBack: state.back, onTo: state.to, onClose: state.close, focusMarks: state.focusMarks }) }) }));
171
+ }
172
+ Drill.Context = DrillContextSlot;
@@ -0,0 +1,2 @@
1
+ export { Dialog, type DialogContext, type DialogProps, type DialogWidth, Wizard, type WizardProps, type WizardStep, } from "./Dialog.js";
2
+ export { Drill, type DrillContextProps, type DrillOrigin, type DrillProps, type DrillState, useDrill, } from "./Drill.js";
@@ -0,0 +1,4 @@
1
+ /* What has to be answered before moving on. */
2
+ export { Dialog, Wizard, } from "./Dialog.js";
3
+ /* One dialog that holds a stack of contexts opened from a record, and from each other. */
4
+ export { Drill, useDrill, } from "./Drill.js";
@@ -0,0 +1,36 @@
1
+ import { type ReactNode } from "react";
2
+ export type DisclosureProps = {
3
+ /** The name of what folds. */
4
+ title: ReactNode;
5
+ children: ReactNode;
6
+ /** Its key inside an `Accordion`. Needed only when the accordion is controlled or has a
7
+ * `defaultExpanded`. */
8
+ id?: string;
9
+ /** Something about the content, at the right of the row: «3 líneas», «Bs 1.240,00». It is
10
+ * what lets a closed section be read without opening it. */
11
+ detail?: ReactNode;
12
+ defaultExpanded?: boolean;
13
+ expanded?: boolean;
14
+ onExpandedChange?: (expanded: boolean) => void;
15
+ disabled?: boolean;
16
+ /** The heading level of the title, so the outline of the page stays true. */
17
+ level?: 2 | 3 | 4 | 5 | 6;
18
+ };
19
+ export declare function Disclosure({ title, children, id, detail, defaultExpanded, expanded, onExpandedChange, disabled, level, }: DisclosureProps): import("react").JSX.Element;
20
+ /**
21
+ * Several `Disclosure`s that work as one: by default opening one closes the other.
22
+ *
23
+ * Context: a FAQ or the groups of a long form read better one at a time — the open section
24
+ * is the one being read and the rest stay as an index. `allowsMultipleExpanded` is for when
25
+ * the sections are compared, not read (the lines of two groups side by side). The value is
26
+ * a list of the sections' `id`s, not React Aria's `Set<Key>`, like every other control.
27
+ */
28
+ export declare function Accordion({ children, allowsMultipleExpanded, defaultExpanded, expanded, onExpandedChange, disabled, }: {
29
+ /** `Disclosure`s, each with its `id`. */
30
+ children: ReactNode;
31
+ allowsMultipleExpanded?: boolean;
32
+ defaultExpanded?: string[];
33
+ expanded?: string[];
34
+ onExpandedChange?: (ids: string[]) => void;
35
+ disabled?: boolean;
36
+ }): 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 { cn, focusRing, tv } from "../../foundations/variants.js";
4
+ import { ChevronRight } from "lucide-react";
5
+ import { useContext } from "react";
6
+ import { Button as AriaButton, Disclosure as AriaDisclosure, DisclosureGroup as AriaDisclosureGroup, DisclosureGroupStateContext, DisclosurePanel, DisclosureStateContext, Heading, } from "react-aria-components";
7
+ /**
8
+ * A section that folds: the advanced options of a form, a question of a FAQ, the lines of a
9
+ * group.
10
+ *
11
+ * Context: it is a HEADING ROW, not a card. What folds is part of the page it is on — the
12
+ * rest of the form, the rest of the list — so it is separated by hairlines and not boxed:
13
+ * a box would say «this is another thing», and the advanced options of an invoice are still
14
+ * the invoice. The title is set like a field label (`text-meta`, medium) because that is
15
+ * what it is: the name of what is underneath.
16
+ *
17
+ * The chevron points right when closed and turns down when open — the one mark everybody
18
+ * already reads as «there is more here». React Aria owns the rest: the row is a real button
19
+ * with `aria-expanded`, the panel is `hidden="until-found"` when closed (the browser's
20
+ * find-in-page still opens it), and the height animates from the measure it publishes.
21
+ */
22
+ const row = tv({
23
+ extend: focusRing,
24
+ base: "flex w-full items-center gap-2 rounded-control px-2 py-2 text-left text-meta font-medium text-ink transition-colors duration-(--duration-instant) hover:bg-ink/6 pressed:bg-ink/10",
25
+ variants: {
26
+ isDisabled: {
27
+ true: "cursor-not-allowed text-muted hover:bg-transparent forced-colors:text-[GrayText]",
28
+ },
29
+ },
30
+ });
31
+ function Header({ title, detail, level, }) {
32
+ const isExpanded = useContext(DisclosureStateContext)?.isExpanded ?? false;
33
+ return (_jsx(Heading, { level: level, className: "m-0", children: _jsxs(AriaButton, { slot: "trigger", className: (state) => row(state), children: [_jsx(ChevronRight, { "aria-hidden": true, strokeWidth: 2, className: cn("size-(--size-icon-md) shrink-0 text-muted transition-transform duration-(--duration-quick) ease-(--ease-out)", isExpanded && "rotate-90") }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: title }), detail === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro font-normal text-muted tabular-nums", children: detail }))] }) }));
34
+ }
35
+ export function Disclosure({ title, children, id, detail, defaultExpanded, expanded, onExpandedChange, disabled, level = 3, }) {
36
+ // Inside an accordion the group draws the line above; alone, it closes itself on both sides.
37
+ const inGroup = useContext(DisclosureGroupStateContext) !== null;
38
+ return (_jsxs(AriaDisclosure, { id: id, defaultExpanded: defaultExpanded, isExpanded: expanded, onExpandedChange: onExpandedChange, isDisabled: disabled, className: cn("border-line py-1", inGroup ? "border-b" : "border-y"), children: [_jsx(Header, { title: title, detail: detail, level: level }), _jsx(DisclosurePanel, { className: "h-(--disclosure-panel-height) overflow-clip motion-safe:transition-[height] motion-safe:duration-(--duration-panel) motion-safe:ease-(--ease-out)", children: _jsx("div", { className: "px-2 pt-1 pb-3 text-meta text-ink", children: children }) })] }));
39
+ }
40
+ /**
41
+ * Several `Disclosure`s that work as one: by default opening one closes the other.
42
+ *
43
+ * Context: a FAQ or the groups of a long form read better one at a time — the open section
44
+ * is the one being read and the rest stay as an index. `allowsMultipleExpanded` is for when
45
+ * the sections are compared, not read (the lines of two groups side by side). The value is
46
+ * a list of the sections' `id`s, not React Aria's `Set<Key>`, like every other control.
47
+ */
48
+ export function Accordion({ children, allowsMultipleExpanded = false, defaultExpanded, expanded, onExpandedChange, disabled, }) {
49
+ return (_jsx(AriaDisclosureGroup, { allowsMultipleExpanded: allowsMultipleExpanded, defaultExpandedKeys: defaultExpanded, expandedKeys: expanded, onExpandedChange: onExpandedChange === undefined
50
+ ? undefined
51
+ : (keys) => onExpandedChange([...keys].map(String)), isDisabled: disabled, className: "border-t border-line", children: children }));
52
+ }
@@ -0,0 +1 @@
1
+ export { Accordion, Disclosure, type DisclosureProps, } from "./Disclosure.js";
@@ -0,0 +1,2 @@
1
+ /* LO QUE SE PLIEGA — una sección que se abre y se cierra, y un grupo de ellas. */
2
+ export { Accordion, Disclosure, } from "./Disclosure.js";
@@ -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
+ }