@sincpro/web-ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/dist/components/assist/AssistMark.d.ts +64 -0
  4. package/dist/components/assist/AssistMark.js +108 -0
  5. package/dist/components/assist/AssistPending.d.ts +24 -0
  6. package/dist/components/assist/AssistPending.js +26 -0
  7. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  8. package/dist/components/assist/AssistSuggestion.js +55 -0
  9. package/dist/components/assist/ChangeReview.d.ts +56 -0
  10. package/dist/components/assist/ChangeReview.js +121 -0
  11. package/dist/components/assist/assist.d.ts +77 -0
  12. package/dist/components/assist/assist.js +87 -0
  13. package/dist/components/assist/index.d.ts +5 -0
  14. package/dist/components/assist/index.js +7 -0
  15. package/dist/components/chatter/Chatter.d.ts +161 -0
  16. package/dist/components/chatter/Chatter.js +232 -0
  17. package/dist/components/chatter/Messages.d.ts +66 -0
  18. package/dist/components/chatter/Messages.js +43 -0
  19. package/dist/components/chatter/Timeline.d.ts +21 -0
  20. package/dist/components/chatter/Timeline.js +30 -0
  21. package/dist/components/chatter/index.d.ts +3 -0
  22. package/dist/components/chatter/index.js +4 -0
  23. package/dist/components/form/Form.d.ts +52 -0
  24. package/dist/components/form/Form.js +49 -0
  25. package/dist/components/form/Sheet.d.ts +43 -0
  26. package/dist/components/form/Sheet.js +83 -0
  27. package/dist/components/form/index.d.ts +2 -0
  28. package/dist/components/form/index.js +4 -0
  29. package/dist/components/frame/Chart.d.ts +13 -0
  30. package/dist/components/frame/Chart.js +24 -0
  31. package/dist/components/frame/FrameView.d.ts +33 -0
  32. package/dist/components/frame/FrameView.js +125 -0
  33. package/dist/components/frame/index.d.ts +3 -0
  34. package/dist/components/frame/index.js +3 -0
  35. package/dist/components/frame/model.d.ts +43 -0
  36. package/dist/components/frame/model.js +101 -0
  37. package/dist/components/index.d.ts +10 -0
  38. package/dist/components/index.js +11 -0
  39. package/dist/components/lines/LineItems.d.ts +74 -0
  40. package/dist/components/lines/LineItems.js +229 -0
  41. package/dist/components/lines/index.d.ts +1 -0
  42. package/dist/components/lines/index.js +2 -0
  43. package/dist/components/lines/lines.d.ts +23 -0
  44. package/dist/components/lines/lines.js +26 -0
  45. package/dist/components/listing/ColumnsMenu.d.ts +20 -0
  46. package/dist/components/listing/ColumnsMenu.js +46 -0
  47. package/dist/components/listing/GroupingLevels.d.ts +35 -0
  48. package/dist/components/listing/GroupingLevels.js +45 -0
  49. package/dist/components/listing/Listing.d.ts +27 -0
  50. package/dist/components/listing/Listing.js +86 -0
  51. package/dist/components/listing/ListingBar.d.ts +73 -0
  52. package/dist/components/listing/ListingBar.js +105 -0
  53. package/dist/components/listing/Pagination.d.ts +36 -0
  54. package/dist/components/listing/Pagination.js +69 -0
  55. package/dist/components/listing/RecordCard.d.ts +33 -0
  56. package/dist/components/listing/RecordCard.js +22 -0
  57. package/dist/components/listing/RecordLink.d.ts +7 -0
  58. package/dist/components/listing/RecordLink.js +22 -0
  59. package/dist/components/listing/Row.d.ts +106 -0
  60. package/dist/components/listing/Row.js +122 -0
  61. package/dist/components/listing/SortMenu.d.ts +62 -0
  62. package/dist/components/listing/SortMenu.js +105 -0
  63. package/dist/components/listing/ViewSwitcher.d.ts +24 -0
  64. package/dist/components/listing/ViewSwitcher.js +16 -0
  65. package/dist/components/listing/columns.d.ts +47 -0
  66. package/dist/components/listing/columns.js +72 -0
  67. package/dist/components/listing/grouping.d.ts +35 -0
  68. package/dist/components/listing/grouping.js +47 -0
  69. package/dist/components/listing/index.d.ts +17 -0
  70. package/dist/components/listing/index.js +20 -0
  71. package/dist/components/listing/modelColumns.d.ts +50 -0
  72. package/dist/components/listing/modelColumns.js +103 -0
  73. package/dist/components/listing/pages.d.ts +78 -0
  74. package/dist/components/listing/pages.js +135 -0
  75. package/dist/components/listing/selection.d.ts +21 -0
  76. package/dist/components/listing/selection.js +43 -0
  77. package/dist/components/listing/set.d.ts +48 -0
  78. package/dist/components/listing/set.js +24 -0
  79. package/dist/components/listing/tree.d.ts +28 -0
  80. package/dist/components/listing/tree.js +23 -0
  81. package/dist/components/navigation/CommandPalette.d.ts +40 -0
  82. package/dist/components/navigation/CommandPalette.js +114 -0
  83. package/dist/components/navigation/NavigationTree.d.ts +31 -0
  84. package/dist/components/navigation/NavigationTree.js +79 -0
  85. package/dist/components/navigation/commands.d.ts +30 -0
  86. package/dist/components/navigation/commands.js +30 -0
  87. package/dist/components/navigation/index.d.ts +3 -0
  88. package/dist/components/navigation/index.js +4 -0
  89. package/dist/components/record/Breadcrumb.d.ts +42 -0
  90. package/dist/components/record/Breadcrumb.js +98 -0
  91. package/dist/components/record/DirtyActions.d.ts +26 -0
  92. package/dist/components/record/DirtyActions.js +22 -0
  93. package/dist/components/record/Pager.d.ts +18 -0
  94. package/dist/components/record/Pager.js +33 -0
  95. package/dist/components/record/RecordHeader.d.ts +71 -0
  96. package/dist/components/record/RecordHeader.js +72 -0
  97. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  98. package/dist/components/record/RecordSkeleton.js +21 -0
  99. package/dist/components/record/SmartButtons.d.ts +35 -0
  100. package/dist/components/record/SmartButtons.js +38 -0
  101. package/dist/components/record/StatusBar.d.ts +51 -0
  102. package/dist/components/record/StatusBar.js +168 -0
  103. package/dist/components/record/ThumbBar.d.ts +36 -0
  104. package/dist/components/record/ThumbBar.js +86 -0
  105. package/dist/components/record/index.d.ts +6 -0
  106. package/dist/components/record/index.js +8 -0
  107. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  108. package/dist/components/search/CriteriaBuilder.js +278 -0
  109. package/dist/components/search/CriteriaChips.d.ts +22 -0
  110. package/dist/components/search/CriteriaChips.js +64 -0
  111. package/dist/components/search/FilterBuilder.d.ts +142 -0
  112. package/dist/components/search/FilterBuilder.js +421 -0
  113. package/dist/components/search/JoinWord.d.ts +8 -0
  114. package/dist/components/search/JoinWord.js +47 -0
  115. package/dist/components/search/JsonEditor.d.ts +19 -0
  116. package/dist/components/search/JsonEditor.js +78 -0
  117. package/dist/components/search/SearchBar.d.ts +78 -0
  118. package/dist/components/search/SearchBar.js +387 -0
  119. package/dist/components/search/criteria.d.ts +116 -0
  120. package/dist/components/search/criteria.js +159 -0
  121. package/dist/components/search/datePeriods.d.ts +122 -0
  122. package/dist/components/search/datePeriods.js +134 -0
  123. package/dist/components/search/dateRanges.d.ts +54 -0
  124. package/dist/components/search/dateRanges.js +89 -0
  125. package/dist/components/search/index.d.ts +9 -0
  126. package/dist/components/search/index.js +20 -0
  127. package/dist/components/search/model.d.ts +54 -0
  128. package/dist/components/search/model.js +37 -0
  129. package/dist/components/search/query.d.ts +166 -0
  130. package/dist/components/search/query.js +436 -0
  131. package/dist/components/shell/AppFrame.d.ts +14 -0
  132. package/dist/components/shell/AppFrame.js +80 -0
  133. package/dist/components/shell/TopBar.d.ts +73 -0
  134. package/dist/components/shell/TopBar.js +54 -0
  135. package/dist/components/shell/headerSlot.d.ts +17 -0
  136. package/dist/components/shell/headerSlot.js +21 -0
  137. package/dist/components/shell/index.d.ts +2 -0
  138. package/dist/components/shell/index.js +3 -0
  139. package/dist/foundations/aria.d.ts +14 -0
  140. package/dist/foundations/aria.js +18 -0
  141. package/dist/foundations/calendar.d.ts +28 -0
  142. package/dist/foundations/calendar.js +183 -0
  143. package/dist/foundations/copy.d.ts +619 -0
  144. package/dist/foundations/copy.es.d.ts +2 -0
  145. package/dist/foundations/copy.es.js +377 -0
  146. package/dist/foundations/copy.js +472 -0
  147. package/dist/foundations/density.d.ts +16 -0
  148. package/dist/foundations/density.js +21 -0
  149. package/dist/foundations/grouping.d.ts +281 -0
  150. package/dist/foundations/grouping.js +0 -0
  151. package/dist/foundations/index.d.ts +14 -0
  152. package/dist/foundations/index.js +30 -0
  153. package/dist/foundations/layout.d.ts +9 -0
  154. package/dist/foundations/layout.js +9 -0
  155. package/dist/foundations/pivot.d.ts +222 -0
  156. package/dist/foundations/pivot.js +198 -0
  157. package/dist/foundations/pivotSheet.d.ts +61 -0
  158. package/dist/foundations/pivotSheet.js +0 -0
  159. package/dist/foundations/slots.d.ts +67 -0
  160. package/dist/foundations/slots.js +63 -0
  161. package/dist/foundations/sticky.d.ts +53 -0
  162. package/dist/foundations/sticky.js +150 -0
  163. package/dist/foundations/surface.d.ts +9 -0
  164. package/dist/foundations/surface.js +26 -0
  165. package/dist/foundations/theme.d.ts +42 -0
  166. package/dist/foundations/theme.js +88 -0
  167. package/dist/foundations/types.d.ts +23 -0
  168. package/dist/foundations/types.js +50 -0
  169. package/dist/foundations/variants.d.ts +19 -0
  170. package/dist/foundations/variants.js +48 -0
  171. package/dist/foundations/xlsx.d.ts +86 -0
  172. package/dist/foundations/xlsx.js +0 -0
  173. package/dist/index.d.ts +19 -0
  174. package/dist/index.js +19 -0
  175. package/dist/primitives/block/Block.d.ts +66 -0
  176. package/dist/primitives/block/Block.js +34 -0
  177. package/dist/primitives/block/Line.d.ts +18 -0
  178. package/dist/primitives/block/Line.js +45 -0
  179. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  180. package/dist/primitives/block/SectionGrid.js +29 -0
  181. package/dist/primitives/block/Steps.d.ts +76 -0
  182. package/dist/primitives/block/Steps.js +43 -0
  183. package/dist/primitives/block/index.d.ts +4 -0
  184. package/dist/primitives/block/index.js +7 -0
  185. package/dist/primitives/button/Button.d.ts +137 -0
  186. package/dist/primitives/button/Button.js +234 -0
  187. package/dist/primitives/button/index.d.ts +1 -0
  188. package/dist/primitives/button/index.js +2 -0
  189. package/dist/primitives/cell/CellStack.d.ts +47 -0
  190. package/dist/primitives/cell/CellStack.js +33 -0
  191. package/dist/primitives/cell/index.d.ts +1 -0
  192. package/dist/primitives/cell/index.js +3 -0
  193. package/dist/primitives/choice/ComboBox.d.ts +16 -0
  194. package/dist/primitives/choice/ComboBox.js +52 -0
  195. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  196. package/dist/primitives/choice/RadioGroup.js +50 -0
  197. package/dist/primitives/choice/SearchField.d.ts +24 -0
  198. package/dist/primitives/choice/SearchField.js +27 -0
  199. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  200. package/dist/primitives/choice/SegmentedControl.js +69 -0
  201. package/dist/primitives/choice/Slider.d.ts +16 -0
  202. package/dist/primitives/choice/Slider.js +46 -0
  203. package/dist/primitives/choice/Switch.d.ts +14 -0
  204. package/dist/primitives/choice/Switch.js +65 -0
  205. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  206. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  207. package/dist/primitives/choice/index.d.ts +8 -0
  208. package/dist/primitives/choice/index.js +11 -0
  209. package/dist/primitives/choice/options.d.ts +28 -0
  210. package/dist/primitives/choice/options.js +6 -0
  211. package/dist/primitives/date/Calendar.d.ts +31 -0
  212. package/dist/primitives/date/Calendar.js +110 -0
  213. package/dist/primitives/date/DatePicker.d.ts +34 -0
  214. package/dist/primitives/date/DatePicker.js +99 -0
  215. package/dist/primitives/date/index.d.ts +2 -0
  216. package/dist/primitives/date/index.js +4 -0
  217. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  218. package/dist/primitives/dialog/Dialog.js +121 -0
  219. package/dist/primitives/dialog/index.d.ts +1 -0
  220. package/dist/primitives/dialog/index.js +2 -0
  221. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  222. package/dist/primitives/disclosure/Disclosure.js +52 -0
  223. package/dist/primitives/disclosure/index.d.ts +1 -0
  224. package/dist/primitives/disclosure/index.js +2 -0
  225. package/dist/primitives/field/Checks.d.ts +57 -0
  226. package/dist/primitives/field/Checks.js +72 -0
  227. package/dist/primitives/field/DropZone.d.ts +42 -0
  228. package/dist/primitives/field/DropZone.js +52 -0
  229. package/dist/primitives/field/Field.d.ts +166 -0
  230. package/dist/primitives/field/Field.js +240 -0
  231. package/dist/primitives/field/Suggest.d.ts +48 -0
  232. package/dist/primitives/field/Suggest.js +43 -0
  233. package/dist/primitives/field/aria.d.ts +61 -0
  234. package/dist/primitives/field/aria.js +70 -0
  235. package/dist/primitives/field/context.d.ts +30 -0
  236. package/dist/primitives/field/context.js +13 -0
  237. package/dist/primitives/field/index.d.ts +7 -0
  238. package/dist/primitives/field/index.js +11 -0
  239. package/dist/primitives/field/layout.d.ts +24 -0
  240. package/dist/primitives/field/layout.js +10 -0
  241. package/dist/primitives/field/mode.d.ts +9 -0
  242. package/dist/primitives/field/mode.js +13 -0
  243. package/dist/primitives/hint/Hint.d.ts +31 -0
  244. package/dist/primitives/hint/Hint.js +42 -0
  245. package/dist/primitives/hint/index.d.ts +1 -0
  246. package/dist/primitives/hint/index.js +1 -0
  247. package/dist/primitives/index.d.ts +19 -0
  248. package/dist/primitives/index.js +20 -0
  249. package/dist/primitives/menu/Menu.d.ts +100 -0
  250. package/dist/primitives/menu/Menu.js +59 -0
  251. package/dist/primitives/menu/index.d.ts +1 -0
  252. package/dist/primitives/menu/index.js +2 -0
  253. package/dist/primitives/notice/Callout.d.ts +49 -0
  254. package/dist/primitives/notice/Callout.js +28 -0
  255. package/dist/primitives/notice/Notice.d.ts +45 -0
  256. package/dist/primitives/notice/Notice.js +61 -0
  257. package/dist/primitives/notice/Toast.d.ts +55 -0
  258. package/dist/primitives/notice/Toast.js +142 -0
  259. package/dist/primitives/notice/index.d.ts +3 -0
  260. package/dist/primitives/notice/index.js +5 -0
  261. package/dist/primitives/overlay/Popover.d.ts +6 -0
  262. package/dist/primitives/overlay/Popover.js +27 -0
  263. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  264. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  265. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  266. package/dist/primitives/overlay/Tooltip.js +74 -0
  267. package/dist/primitives/overlay/index.d.ts +3 -0
  268. package/dist/primitives/overlay/index.js +5 -0
  269. package/dist/primitives/picker/Picker.d.ts +48 -0
  270. package/dist/primitives/picker/Picker.js +150 -0
  271. package/dist/primitives/picker/index.d.ts +1 -0
  272. package/dist/primitives/picker/index.js +2 -0
  273. package/dist/primitives/surface/Peek.d.ts +60 -0
  274. package/dist/primitives/surface/Peek.js +71 -0
  275. package/dist/primitives/surface/index.d.ts +1 -0
  276. package/dist/primitives/surface/index.js +2 -0
  277. package/dist/primitives/table/CellEditor.d.ts +14 -0
  278. package/dist/primitives/table/CellEditor.js +83 -0
  279. package/dist/primitives/table/DataTable.d.ts +135 -0
  280. package/dist/primitives/table/DataTable.js +244 -0
  281. package/dist/primitives/table/EditableTable.d.ts +96 -0
  282. package/dist/primitives/table/EditableTable.js +423 -0
  283. package/dist/primitives/table/PivotTable.d.ts +14 -0
  284. package/dist/primitives/table/PivotTable.js +0 -0
  285. package/dist/primitives/table/ReportTable.d.ts +38 -0
  286. package/dist/primitives/table/ReportTable.js +106 -0
  287. package/dist/primitives/table/RowActions.d.ts +16 -0
  288. package/dist/primitives/table/RowActions.js +28 -0
  289. package/dist/primitives/table/SheetTable.d.ts +70 -0
  290. package/dist/primitives/table/SheetTable.js +197 -0
  291. package/dist/primitives/table/Table.d.ts +106 -0
  292. package/dist/primitives/table/Table.js +116 -0
  293. package/dist/primitives/table/index.d.ts +7 -0
  294. package/dist/primitives/table/index.js +9 -0
  295. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  296. package/dist/primitives/tabs/Tabs.js +56 -0
  297. package/dist/primitives/tabs/index.d.ts +1 -0
  298. package/dist/primitives/tabs/index.js +1 -0
  299. package/dist/primitives/tag/Tag.d.ts +36 -0
  300. package/dist/primitives/tag/Tag.js +68 -0
  301. package/dist/primitives/tag/Tags.d.ts +57 -0
  302. package/dist/primitives/tag/Tags.js +104 -0
  303. package/dist/primitives/tag/TagsField.d.ts +32 -0
  304. package/dist/primitives/tag/TagsField.js +182 -0
  305. package/dist/primitives/tag/choices.d.ts +38 -0
  306. package/dist/primitives/tag/choices.js +26 -0
  307. package/dist/primitives/tag/index.d.ts +4 -0
  308. package/dist/primitives/tag/index.js +8 -0
  309. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  310. package/dist/primitives/toolbar/Toolbar.js +36 -0
  311. package/dist/primitives/toolbar/index.d.ts +1 -0
  312. package/dist/primitives/toolbar/index.js +2 -0
  313. package/dist/primitives/value/Copyable.d.ts +37 -0
  314. package/dist/primitives/value/Copyable.js +50 -0
  315. package/dist/primitives/value/Fecha.d.ts +12 -0
  316. package/dist/primitives/value/Fecha.js +80 -0
  317. package/dist/primitives/value/Meter.d.ts +29 -0
  318. package/dist/primitives/value/Meter.js +46 -0
  319. package/dist/primitives/value/Metric.d.ts +42 -0
  320. package/dist/primitives/value/Metric.js +38 -0
  321. package/dist/primitives/value/Person.d.ts +90 -0
  322. package/dist/primitives/value/Person.js +208 -0
  323. package/dist/primitives/value/Relation.d.ts +69 -0
  324. package/dist/primitives/value/Relation.js +57 -0
  325. package/dist/primitives/value/Totals.d.ts +26 -0
  326. package/dist/primitives/value/Totals.js +4 -0
  327. package/dist/primitives/value/Value.d.ts +77 -0
  328. package/dist/primitives/value/Value.js +119 -0
  329. package/dist/primitives/value/index.d.ts +8 -0
  330. package/dist/primitives/value/index.js +9 -0
  331. package/dist/tokens.css +1025 -0
  332. package/dist/views/index.d.ts +2 -0
  333. package/dist/views/index.js +3 -0
  334. package/dist/views/list/List.d.ts +113 -0
  335. package/dist/views/list/List.js +97 -0
  336. package/dist/views/list/ListView.d.ts +6 -0
  337. package/dist/views/list/ListView.js +269 -0
  338. package/dist/views/list/columns.d.ts +18 -0
  339. package/dist/views/list/columns.js +27 -0
  340. package/dist/views/list/derive.d.ts +76 -0
  341. package/dist/views/list/derive.js +181 -0
  342. package/dist/views/list/index.d.ts +2 -0
  343. package/dist/views/list/index.js +4 -0
  344. package/dist/views/list/presentations.d.ts +39 -0
  345. package/dist/views/list/presentations.js +103 -0
  346. package/dist/views/list/types.d.ts +272 -0
  347. package/dist/views/list/types.js +1 -0
  348. package/dist/views/list/useNarrowList.d.ts +17 -0
  349. package/dist/views/list/useNarrowList.js +32 -0
  350. package/dist/views/record/Record.d.ts +126 -0
  351. package/dist/views/record/Record.js +141 -0
  352. package/dist/views/record/index.d.ts +1 -0
  353. package/dist/views/record/index.js +2 -0
  354. package/package.json +109 -4
@@ -0,0 +1,30 @@
1
+ /** The commands under each heading, headings in the order they first appear. */
2
+ export function groupCommands(commands) {
3
+ const groups = new Map();
4
+ /* The commands with no heading are listed first, wherever they were written. */
5
+ if (commands.some((c) => c.section === undefined))
6
+ groups.set(undefined, []);
7
+ for (const command of commands) {
8
+ const group = groups.get(command.section);
9
+ if (group === undefined)
10
+ groups.set(command.section, [command]);
11
+ else
12
+ group.push(command);
13
+ }
14
+ return [...groups].map(([section, list]) => ({ section, commands: list }));
15
+ }
16
+ /**
17
+ * Whether a command answers a query. `contains` is the locale's comparison — React Aria's
18
+ * `useFilter` in the palette — so «facturacion» finds «Facturación» and an uppercase query
19
+ * finds a lowercase label.
20
+ *
21
+ * Every word of the query has to appear, in any order, in the label or the keywords:
22
+ * «nueva fac» finds «Nueva factura», and so does «factura nueva».
23
+ */
24
+ export function commandMatches(command, query, contains) {
25
+ const words = query.trim().split(/\s+/).filter(Boolean);
26
+ if (words.length === 0)
27
+ return true;
28
+ const haystack = [command.label, ...(command.keywords ?? [])].join(" ");
29
+ return words.every((word) => contains(haystack, word));
30
+ }
@@ -0,0 +1,3 @@
1
+ export { CommandPalette, useCommandPaletteShortcut, } from "./CommandPalette.js";
2
+ export { type PaletteCommand } from "./commands.js";
3
+ export { NavigationTree, type NavItem, } from "./NavigationTree.js";
@@ -0,0 +1,4 @@
1
+ /* LA NAVEGACIÓN — el árbol lateral de una aplicación y la paleta de comandos. */
2
+ export { CommandPalette, useCommandPaletteShortcut, } from "./CommandPalette.js";
3
+ export {} from "./commands.js";
4
+ export { NavigationTree, } from "./NavigationTree.js";
@@ -0,0 +1,42 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ export interface Crumb {
3
+ label: string;
4
+ /** Absent on the last one: the place you are already at cannot be pressed. */
5
+ onClick?: () => void;
6
+ }
7
+ /**
8
+ * Where I came from, without eating the line.
9
+ *
10
+ * Built on React Aria's `Breadcrumbs`: the LAST crumb is the page I am on, so it
11
+ * gets `aria-current="page"` and stops being pressable — React Aria decides that
12
+ * from its position, not from whether the caller forgot an `onClick`. The others
13
+ * are `Link`s, so they press with Enter, show the system focus ring only from the
14
+ * keyboard, and read as links to a screen reader.
15
+ *
16
+ * **The hierarchy is in the TONE, not in a color.** Ancestors are quiet
17
+ * (`text-muted`), the current page is ink and medium weight: the eye lands on where
18
+ * I am and reads backwards. Painting the ancestors in the link blue made the whole
19
+ * row the same weight — four blue words and no «here».
20
+ *
21
+ * Two things keep it inside its place, and they are two because they are two
22
+ * different problems:
23
+ *
24
+ * 1. **Each crumb asks for `--container-crumb` and no more.** A name in this
25
+ * product reaches fifty characters, so one crumb is enough to eat the band even
26
+ * when the trail has three. The rest is cut with «…» and the full name stays in
27
+ * the `title`.
28
+ * 2. **With more than `folded` crumbs, the MIDDLE ones fold** into a «…» menu.
29
+ * By COUNT, not by width: a six-crumb trail is long on any screen. The first
30
+ * —the root, where you go back to— and the last —where I am— never fold, and
31
+ * what folded is still navigable, in order.
32
+ *
33
+ * Folding and truncating are not the same and that is why both exist: one removes
34
+ * CRUMBS and the other removes LETTERS.
35
+ */
36
+ export declare function Breadcrumb({ crumbs, surface: surfaceProp, folded, }: {
37
+ crumbs: readonly Crumb[];
38
+ surface?: Surface;
39
+ /** From how many crumbs the middle folds. Below this every crumb is drawn:
40
+ * folding two crumbs hides half the trail to save one chevron. */
41
+ folded?: number;
42
+ }): import("react").JSX.Element;
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { useSurface } from "../../foundations/surface.js";
5
+ import { focusRing, tv } from "../../foundations/variants.js";
6
+ import { Menu } from "../../primitives/menu/Menu.js";
7
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
8
+ import { Breadcrumb as AriaBreadcrumb, Breadcrumbs, Link } from "react-aria-components";
9
+ /** The three tones of a trail, per surface: where I came from, where I am, and the
10
+ * chevron between them. The chevron is the faintest thing in the row on purpose —
11
+ * it is punctuation, and punctuation louder than the words makes the words hard to
12
+ * read. */
13
+ const crumbStyles = tv({
14
+ extend: focusRing,
15
+ base: "block min-w-0 rounded-mark text-meta transition-colors duration-(--duration-instant)",
16
+ variants: {
17
+ surface: { canvas: "", chrome: "outline-chrome-link" },
18
+ kind: {
19
+ link: "cursor-pointer hover:underline hover:underline-offset-4",
20
+ inert: "cursor-default",
21
+ current: "cursor-default font-medium",
22
+ },
23
+ },
24
+ compoundVariants: [
25
+ { surface: "canvas", kind: ["link", "inert"], class: "text-muted" },
26
+ { surface: "canvas", kind: "link", class: "hover:text-ink" },
27
+ { surface: "canvas", kind: "current", class: "text-ink" },
28
+ { surface: "chrome", kind: ["link", "inert"], class: "text-chrome-muted" },
29
+ { surface: "chrome", kind: "link", class: "hover:text-chrome-ink" },
30
+ { surface: "chrome", kind: "current", class: "text-chrome-ink" },
31
+ ],
32
+ });
33
+ /**
34
+ * Where I came from, without eating the line.
35
+ *
36
+ * Built on React Aria's `Breadcrumbs`: the LAST crumb is the page I am on, so it
37
+ * gets `aria-current="page"` and stops being pressable — React Aria decides that
38
+ * from its position, not from whether the caller forgot an `onClick`. The others
39
+ * are `Link`s, so they press with Enter, show the system focus ring only from the
40
+ * keyboard, and read as links to a screen reader.
41
+ *
42
+ * **The hierarchy is in the TONE, not in a color.** Ancestors are quiet
43
+ * (`text-muted`), the current page is ink and medium weight: the eye lands on where
44
+ * I am and reads backwards. Painting the ancestors in the link blue made the whole
45
+ * row the same weight — four blue words and no «here».
46
+ *
47
+ * Two things keep it inside its place, and they are two because they are two
48
+ * different problems:
49
+ *
50
+ * 1. **Each crumb asks for `--container-crumb` and no more.** A name in this
51
+ * product reaches fifty characters, so one crumb is enough to eat the band even
52
+ * when the trail has three. The rest is cut with «…» and the full name stays in
53
+ * the `title`.
54
+ * 2. **With more than `folded` crumbs, the MIDDLE ones fold** into a «…» menu.
55
+ * By COUNT, not by width: a six-crumb trail is long on any screen. The first
56
+ * —the root, where you go back to— and the last —where I am— never fold, and
57
+ * what folded is still navigable, in order.
58
+ *
59
+ * Folding and truncating are not the same and that is why both exist: one removes
60
+ * CRUMBS and the other removes LETTERS.
61
+ */
62
+ export function Breadcrumb({ crumbs, surface: surfaceProp, folded = 3, }) {
63
+ const copy = useUiCopy();
64
+ const surface = useSurface(surfaceProp);
65
+ const edge = surface === "chrome" ? "text-chrome-muted/50" : "text-line-strong";
66
+ /* The first and the last always; the middle goes to the menu when there are too many. */
67
+ const folds = crumbs.length > folded;
68
+ const hidden = folds ? crumbs.slice(1, -1) : [];
69
+ const shown = folds
70
+ ? [crumbs[0], null, crumbs[crumbs.length - 1]]
71
+ : crumbs;
72
+ return (_jsx("nav", { "aria-label": copy.record.location, className: "flex min-w-0", children: _jsx(Breadcrumbs, { "aria-label": copy.record.location, className: "flex min-w-0 flex-wrap items-center gap-1", children: shown.map((crumb, i) => {
73
+ const key = crumb === null ? "folded" : `${i}-${crumb.label}`;
74
+ const last = i === shown.length - 1;
75
+ return (_jsxs(AriaBreadcrumb, { id: key, className: "flex min-w-0 items-center gap-1", children: [i === 0 ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 ${edge}`, strokeWidth: 2, "aria-hidden": true })), crumb === null ? (_jsx(Menu, { label: copy.record.location, title: copy.record.location, icon: MoreHorizontal, iconOnly: true, chevron: false, variant: "text", size: "sm", surface: surface === "chrome" ? "chrome" : "canvas", align: "start", groups: [
76
+ {
77
+ options: hidden.map((one, at) => ({
78
+ id: `crumb-${at}`,
79
+ label: one.label,
80
+ disabled: one.onClick === undefined,
81
+ onSelect: one.onClick,
82
+ })),
83
+ },
84
+ ] })) : (_jsx(Link
85
+ /* React Aria already disables the last one; a middle crumb with
86
+ nowhere to go is disabled too, instead of a link that does
87
+ nothing. */
88
+ , {
89
+ /* React Aria already disables the last one; a middle crumb with
90
+ nowhere to go is disabled too, instead of a link that does
91
+ nothing. */
92
+ isDisabled: last ? undefined : crumb.onClick === undefined, onPress: crumb.onClick, className: ({ isFocusVisible }) => crumbStyles({
93
+ surface,
94
+ isFocusVisible,
95
+ kind: last ? "current" : crumb.onClick === undefined ? "inert" : "link",
96
+ }), children: _jsx("span", { className: "block max-w-(--container-crumb) truncate", title: crumb.label, children: crumb.label }) }))] }, key));
97
+ }) }) }));
98
+ }
@@ -0,0 +1,26 @@
1
+ export interface DirtyState {
2
+ onSave: () => void;
3
+ onDiscard: () => void;
4
+ /** It is saving. The button turns off: pressing it twice sends it twice, and
5
+ * when what gets saved is a payment, that matters. */
6
+ saving?: boolean;
7
+ /** What gets saved, when it is not obvious: «guardar la factura», «guardar la
8
+ * regla». */
9
+ label?: string;
10
+ /** Why it CANNOT be saved yet. Present = the button is off and the reason is
11
+ * in its `title` — a button that is off with no reason is a wall. */
12
+ blocked?: string;
13
+ }
14
+ /** Save and discard, in the rail and in place of the action buttons.
15
+ *
16
+ * Context: discard goes to the LEFT of save and with the quieter look. It is
17
+ * the same rule as `FormActions`, and here it matters more: the rail is where
18
+ * the hand is after editing.
19
+ *
20
+ * The «sin guardar» warning is text and not just a color: whoever is leaving
21
+ * the screen has to be able to read what is going on, not deduce it from a
22
+ * dot. */
23
+ export declare function DirtyActions({ onSave, onDiscard, saving, label, blocked, }: DirtyState): import("react").JSX.Element;
24
+ /** The one action a view leads with: the element written with `role="primary"` — the
25
+ * same prop that paints it gold and that `make smoke` counts, so there is no second way to
26
+ * say «this is the main one» that could disagree with the first. */
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { bySurface, useSurface } from "../../foundations/surface.js";
5
+ import { Button } from "../../primitives/button/Button.js";
6
+ import { Save, Undo2 } from "lucide-react";
7
+ /** Save and discard, in the rail and in place of the action buttons.
8
+ *
9
+ * Context: discard goes to the LEFT of save and with the quieter look. It is
10
+ * the same rule as `FormActions`, and here it matters more: the rail is where
11
+ * the hand is after editing.
12
+ *
13
+ * The «sin guardar» warning is text and not just a color: whoever is leaving
14
+ * the screen has to be able to read what is going on, not deduce it from a
15
+ * dot. */
16
+ export function DirtyActions({ onSave, onDiscard, saving = false, label, blocked, }) {
17
+ const copy = useUiCopy();
18
+ /* It follows the rail it sits on instead of assuming the dark one: on a light rail the
19
+ chrome palette was white text on white. */
20
+ const surface = useSurface();
21
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: `inline-flex h-(--size-control-md) shrink-0 items-center gap-1.5 rounded-control px-2.5 text-meta font-medium ${bySurface(surface, "bg-warn/12 text-warn", "bg-chrome-warn/15 text-chrome-warn")}`, children: copy.record.unsaved }), _jsx(Button, { surface: surface, onClick: onDiscard, disabled: saving, icon: Undo2, children: copy.record.discard }), _jsx("span", { ...(blocked === undefined ? {} : { title: blocked }), children: _jsx(Button, { role: "primary", surface: surface, onClick: onSave, disabled: saving || blocked !== undefined, icon: Save, children: saving ? copy.record.saving : (label ?? copy.record.save) }) })] }));
22
+ }
@@ -0,0 +1,18 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ /** Where I am inside the set I came from, and how I move to the next one.
3
+ *
4
+ * **It turns a record into a position.** Without this, opening the third of a
5
+ * list of eighty and wanting to see the fourth forces going back, finding
6
+ * where you were and opening again — three actions for what is one arrow.
7
+ *
8
+ * It goes far from the actions on purpose: moving between records is not doing
9
+ * something to this record. Stuck next to «Eliminar», one extra click on the
10
+ * wrong arrow is a destructive action. */
11
+ export declare function Pager({ index, total, onPrev, onNext, surface: surfaceProp, }: {
12
+ /** 1-based: what gets shown is «12 de 80», not «11». */
13
+ index: number;
14
+ total: number;
15
+ onPrev?: () => void;
16
+ onNext?: () => void;
17
+ surface?: Surface;
18
+ }): import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { useSurface } from "../../foundations/surface.js";
5
+ import { ChevronLeft, ChevronRight } from "lucide-react";
6
+ /* ---------------------------------------------------------------- *
7
+ * THE PAGER
8
+ * ---------------------------------------------------------------- */
9
+ /** Where I am inside the set I came from, and how I move to the next one.
10
+ *
11
+ * **It turns a record into a position.** Without this, opening the third of a
12
+ * list of eighty and wanting to see the fourth forces going back, finding
13
+ * where you were and opening again — three actions for what is one arrow.
14
+ *
15
+ * It goes far from the actions on purpose: moving between records is not doing
16
+ * something to this record. Stuck next to «Eliminar», one extra click on the
17
+ * wrong arrow is a destructive action. */
18
+ export function Pager({ index, total, onPrev, onNext, surface: surfaceProp, }) {
19
+ const copy = useUiCopy();
20
+ const surface = useSurface(surfaceProp);
21
+ const isChrome = surface === "chrome";
22
+ const faint = isChrome ? "text-chrome-muted" : "text-muted";
23
+ const hover = isChrome
24
+ ? "not-disabled:hover:bg-chrome-ink/10 not-disabled:hover:text-chrome-ink"
25
+ : "not-disabled:hover:bg-ink/6 not-disabled:hover:text-ink";
26
+ /* On the chrome the ring is the chrome's link cyan, like the TopBar's: the
27
+ primary navy on a navy band is a ring nobody sees. */
28
+ const ring = isChrome
29
+ ? "focus-visible:outline-chrome-link"
30
+ : "focus-visible:outline-primary";
31
+ const arrow = `inline-flex size-(--size-control-sm) items-center justify-center rounded-pill transition-colors duration-(--duration-instant) disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-2 ${ring} ${faint} ${hover}`;
32
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsxs("span", { className: `text-micro tabular-nums ${faint}`, children: [index, " / ", total.toLocaleString(copy.locale)] }), _jsx("button", { type: "button", onClick: onPrev, disabled: index <= 1, "aria-label": copy.record.prev, title: copy.record.prev, className: arrow, children: _jsx(ChevronLeft, { className: "size-(--size-icon-md)", strokeWidth: 2 }) }), _jsx("button", { type: "button", onClick: onNext, disabled: index >= total, "aria-label": copy.record.next, title: copy.record.next, className: arrow, children: _jsx(ChevronRight, { className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }));
33
+ }
@@ -0,0 +1,71 @@
1
+ import { type Crumb } from "./Breadcrumb.js";
2
+ import { type DirtyState } from "./DirtyActions.js";
3
+ import type { Surface, Tone } from "../../foundations/types.js";
4
+ import type { LucideIcon } from "lucide-react";
5
+ import { type ReactNode } from "react";
6
+ export type { Surface };
7
+ export declare function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface, smart, aspects, dirty, chatter, chatterLayout, children, }: {
8
+ crumbs?: readonly Crumb[];
9
+ pager?: ReactNode;
10
+ /** The action buttons. At most three visible + a `Menu` with `iconOnly`, and
11
+ * the rule for choosing which three: the ones that PRODUCE something new,
12
+ * never the navigation ones. They take the rail's surface from context: write
13
+ * them without `surface`, light by default, dark on `Record.Rail surface="chrome"`. */
14
+ actions?: ReactNode;
15
+ /** La historia del registro: `<Chatter …/>`. En `page` es la última fila de
16
+ * la hoja —el mismo papel, separado por una línea— y no una segunda tarjeta.
17
+ * En `aside` sigue al costado, con su propio marco. */
18
+ chatter?: ReactNode;
19
+ /**
20
+ * Dónde se pone respecto de la hoja.
21
+ *
22
+ * page debajo y a todo el ancho, como hoja propia. **El default**: un
23
+ * formulario necesita su ancho, y una columna al costado se lo
24
+ * come en cuanto la pantalla no sobra.
25
+ * aside al costado cuando hay ancho, debajo cuando no.
26
+ *
27
+ * Lo dice el REGISTRO y no se adivina del hijo: la dirección del flex la pone
28
+ * este contenedor, y un padre que mira qué le pasaron adentro para decidir
29
+ * cómo acomodarse se rompe la próxima vez que alguien envuelva el chatter en
30
+ * otra cosa.
31
+ */
32
+ chatterLayout?: "aside" | "page";
33
+ /** `StatusBar` (where is it at?), the chatter's `Timeline` (what happened to get
34
+ * here?), or both: they are different questions and many records only have one. */
35
+ status?: ReactNode;
36
+ /** The category, small and above the name: «factura de cliente». It makes a
37
+ * name like `NF-02/2026/00137` mean something without deducing it from the
38
+ * format. */
39
+ kicker?: string;
40
+ icon?: LucideIcon;
41
+ title: ReactNode;
42
+ /** The metadata that only DESCRIBES. In mono: the machine wrote it. */
43
+ subtitle?: ReactNode;
44
+ tag?: {
45
+ label: string;
46
+ tone?: Tone;
47
+ };
48
+ /** The record's key figure, as large as the title, on the right. See `RecordIdentity`. */
49
+ figure?: {
50
+ label: string;
51
+ value: ReactNode;
52
+ };
53
+ /** The rail's band. `canvas` (the default) is a light control bar with a hairline below;
54
+ * `chrome` is the dark band, an explicit choice for a record that owns the screen's
55
+ * only dark band (no dark application bar above it). */
56
+ railSurface?: Surface;
57
+ /** The counters that TAKE you somewhere else. They go in the sheet and next
58
+ * to the title — they belong to the record, not to my position in front of
59
+ * it. */
60
+ smart?: ReactNode;
61
+ aspects?: ReactNode;
62
+ /** There are unsaved changes. Its presence REPLACES the action buttons with
63
+ * save/discard, and it is not an aesthetic decision: running «Registrar pago» on
64
+ * an edited and unsaved invoice runs with the old values, and whoever pressed
65
+ * it has no way to know. As long as there are changes, the only thing that
66
+ * can be done is confirm them or throw them away. */
67
+ dirty?: DirtyState;
68
+ /** The record body. It sits IN the paper with the title, so groups, tabs
69
+ * and the page are one white sheet on the ground — Odoo's `<sheet>`. */
70
+ children?: ReactNode;
71
+ }): 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 { Breadcrumb } from "./Breadcrumb.js";
4
+ import { DirtyActions, } from "./DirtyActions.js";
5
+ import { isPrimary, ThumbBar, thumbPlan } from "./ThumbBar.js";
6
+ import { byDensity, useDensity } from "../../foundations/density.js";
7
+ import { SurfaceProvider } from "../../foundations/surface.js";
8
+ import { Line } from "../../primitives/block/Line.js";
9
+ import { Tag } from "../../primitives/tag/Tag.js";
10
+ import { cloneElement, isValidElement } from "react";
11
+ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface = "canvas", smart, aspects, dirty, chatter, chatterLayout = "page", children, }) {
12
+ const d = useDensity();
13
+ const hasRail = crumbs !== undefined ||
14
+ pager !== undefined ||
15
+ actions !== undefined ||
16
+ status !== undefined ||
17
+ dirty !== undefined;
18
+ /* In compact the rail goes away ENTIRELY, and it is not for room: in a
19
+ preview, the breadcrumbs and the pager belong to the surface that opened it
20
+ —the list behind is still there— and the actions are already in the
21
+ drawer's header. A rail inside another rail is two bars saying where I am.
22
+
23
+ The state does survive: it moves to the sheet, below the title. It is the
24
+ only thing in the rail that belongs to the RECORD and not to my position in
25
+ front of it. */
26
+ const withRail = hasRail && d === "comfortable";
27
+ /* The thumb bar's content (see THE THUMB BAR in `ThumbBar`): dirty first, else the primary. */
28
+ const { rail: railActions, primaries, mode: thumb, } = thumbPlan({ withRail, actions, dirty });
29
+ /* In compact the TITLE is not drawn either, for the same reason as in
30
+ `Listing`: the surface header already says it. Drawing it here showed it
31
+ twice in the same 480 px drawer — and the name of a customer in this product
32
+ reaches 50 characters, so the repetition eats two whole lines.
33
+
34
+ What does NOT go away: the `tag`, the state, the counters and the aspects.
35
+ They are what the drawer header does not say and the only way to see what
36
+ hangs off it without pinning the panel. */
37
+ const withIdentity = d === "comfortable";
38
+ /* The paper is Odoo's `<sheet>`: white on the ground, hairline, not a
39
+ shadow. It exists on the PAGE, where the chrome rail is a real band
40
+ (crumbs, pager, actions). A drawer is compact so `withRail` is already
41
+ false. A dialog is comfortable but has no crumbs or actions — and it IS
42
+ already a card. Status alone is not a page. */
43
+ const paper = withRail &&
44
+ (crumbs !== undefined ||
45
+ pager !== undefined ||
46
+ actions !== undefined ||
47
+ dirty !== undefined);
48
+ const identity = (_jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-2.5 px-4 pt-3.5", "gap-2 px-0 pt-0")}`, children: [withRail || smart === undefined ? null : _jsx("div", { className: "min-w-0", children: smart }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-start gap-x-5 gap-y-2", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [Icon === undefined || d === "compact" ? null : (_jsx("span", { className: "mt-1 flex size-9 shrink-0 items-center justify-center rounded-box bg-ink/5 text-muted", children: _jsx(Icon, { className: "size-(--size-icon-lg)", strokeWidth: 1.75 }) })), _jsxs("div", { className: "flex min-w-0 flex-col", children: [kicker === undefined || !withIdentity ? null : (_jsx("span", { className: "text-micro font-medium text-muted", children: kicker })), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1", children: [!withIdentity ? null : (
49
+ /* `display`, not `title`: the record's name is the entry point of the
50
+ screen. At the title size (19 px over a 13 px body, 1.46:1) nothing on
51
+ the sheet stood out and the eye had nowhere to start. */
52
+ _jsx("h1", { className: "min-w-0 truncate text-display font-semibold text-ink", children: title })), tag === undefined ? null : _jsx(Tag, { tone: tag.tone, children: tag.label })] }), subtitle === undefined || !withIdentity ? null : (_jsx("p", { className: "mt-0.5 truncate text-lead text-ink", children: subtitle }))] })] }), figure === undefined || !withIdentity ? null : (_jsxs("div", { className: "ml-auto flex shrink-0 flex-col items-end", children: [_jsx("span", { className: "text-micro font-medium text-muted", children: figure.label }), _jsx("span", { className: "text-display font-semibold tabular-nums text-ink", children: figure.value })] }))] }), withRail || status === undefined ? null : (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: status })), aspects === undefined ? null : (
53
+ /* The line is the sheet's mouth. In comfortable it walks to the
54
+ edges; the current tab sits on it and opens into the page below. */
55
+ _jsx("div", { className: `border-b border-line-strong ${byDensity(d, "-mx-4 px-4", "")}`, children: aspects }))] }));
56
+ return (_jsxs(_Fragment, { children: [withRail ? (_jsx(SurfaceProvider, { value: railSurface, children: _jsx("div", { className: "@container/record", children: _jsxs("div", { className: `flex flex-col gap-2 px-3.5 py-2.5 ${railSurface === "chrome" ? "bg-chrome" : "border-b border-line bg-surface"}`, children: [_jsx(Line, { start: crumbs === undefined || crumbs.length === 0 ? null : (_jsx(Breadcrumb, { crumbs: crumbs, surface: railSurface })), center: smart, end: pager }), actions === undefined && status === undefined && dirty === undefined ? null : (
57
+ /* Row 2 — WHAT I CAN DO and WHERE IT IS AT: actions stuck to the
58
+ left edge, the history and the state stuck to the right one. No
59
+ `flex-wrap`: wrapping split the row in two and the state ended up
60
+ below the actions, which is another hierarchy. */
61
+ _jsxs("div", { className: "flex min-w-0 items-start gap-x-4", children: [dirty !== undefined ? (_jsx("div", { className: "flex shrink-0 items-center gap-1.5 @max-handheld/record:hidden", children: _jsx(DirtyActions, { ...dirty }) })) : actions === undefined ? null : (_jsx("div", { className: "flex shrink-0 items-center gap-1.5", children: primaries.length === 0
62
+ ? actions
63
+ : railActions.map((node) => isPrimary(node) ? (_jsx("span", { "data-thumb-moves": "", className: "contents @max-handheld/record:hidden", children: node }, node.key)) : (node)) })), status === undefined ? null : (_jsx("div", { className: "ml-auto flex min-w-0 flex-col items-end gap-1.5", children: status }))] }))] }) }) })) : null, _jsx("div", { className: paper ? "bg-ground px-3 pt-3 pb-3" : "", children: _jsx("div", { className: chatter !== undefined && chatterLayout === "aside" ? "@container" : "", children: _jsxs("div", { className: chatter !== undefined && chatterLayout === "aside"
64
+ ? "flex min-w-0 flex-col gap-3 @4xl:flex-row @4xl:items-start"
65
+ : "", children: [_jsxs("div", { className: paper
66
+ ? `flex w-full max-w-(--container-sheet) min-w-0 flex-1 flex-col overflow-clip rounded-box border border-line bg-surface ${chatter !== undefined && chatterLayout === "aside" ? "" : "mx-auto"}`
67
+ : "flex min-w-0 flex-1 flex-col bg-surface", children: [identity, children, chatter !== undefined && chatterLayout === "page" ? (_jsx("div", { className: "border-t border-line", children: isValidElement(chatter)
68
+ ? cloneElement(chatter, {
69
+ embedded: true,
70
+ })
71
+ : chatter })) : null] }), chatter !== undefined && chatterLayout === "aside" ? chatter : null] }) }) }), thumb === null ? null : _jsx(ThumbBar, { mode: thumb, dirty: dirty, primaries: primaries })] }));
72
+ }
@@ -0,0 +1,3 @@
1
+ export declare function RecordSkeleton({ label }: {
2
+ label: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { byDensity, useDensity } from "../../foundations/density.js";
4
+ /* ==================================================================== *
5
+ * LA FICHA, MIENTRAS LLEGA.
6
+ *
7
+ * Factura y cliente contestaban la espera con `Empty`: «Cargando…» en el
8
+ * medio de la página. Eso es la frase de «no hay nada», y el registro sí
9
+ * está — todavía no llegó. La silueta es la de la ficha: el riel, el nombre,
10
+ * los cuatro hechos, las solapas y el cuerpo. Cuando el registro aparece,
11
+ * cae en el mismo lugar.
12
+ *
13
+ * Sin brillo. Un pulso ya quiere decir «este estado está vivo».
14
+ * ==================================================================== */
15
+ const HECHOS = ["w-16", "w-12", "w-20", "w-24"];
16
+ const SOLAPAS = ["w-14", "w-16", "w-20"];
17
+ export function RecordSkeleton({ label }) {
18
+ const d = useDensity();
19
+ const page = d === "comfortable";
20
+ return (_jsxs("article", { "aria-busy": "true", className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "sr-only", children: label }), page ? (_jsxs("div", { "aria-hidden": true, className: "flex items-center gap-3 bg-chrome px-3.5 py-2.5", children: [_jsx("span", { className: "h-3 w-24 rounded-control bg-chrome-ink/15" }), _jsx("span", { className: "h-3 w-40 rounded-control bg-chrome-ink/10" }), _jsx("span", { className: "ml-auto h-(--size-control-sm) w-28 rounded-control bg-chrome-ink/15" })] })) : null, _jsx("div", { "aria-hidden": true, className: page ? "border border-line bg-surface" : "bg-surface", children: _jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-3 px-4 pt-3.5 pb-4", "gap-2 py-2")}`, children: [_jsx("span", { className: "h-5 w-72 max-w-full rounded-control bg-ink/8" }), _jsx("div", { className: "flex flex-wrap gap-x-6 gap-y-2", children: HECHOS.map((width) => (_jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "h-2.5 w-10 rounded-control bg-ink/5" }), _jsx("span", { className: `h-3.5 rounded-control bg-ink/8 ${width}` })] }, width))) }), _jsx("div", { className: "flex gap-4 border-b border-line pb-2", children: SOLAPAS.map((width, i) => (_jsx("span", { className: `h-3 rounded-control ${i === 0 ? "bg-ink/10" : "bg-ink/5"} ${width}` }, width))) }), _jsx("span", { className: "h-24 rounded-box bg-ink/5" })] }) })] }));
21
+ }
@@ -0,0 +1,35 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ import type { LucideIcon } from "lucide-react";
3
+ import { type ReactNode } from "react";
4
+ /** What hangs off this record, with its number, and can be pressed.
5
+ *
6
+ * It answers TWO questions with a single control: *is there any?* and *how
7
+ * many?*. Without this you have to open it to discover there is nothing — the
8
+ * most expensive click in any interface.
9
+ *
10
+ * **`—` is not `0`.** «I don't know yet» and «there is none» are two different
11
+ * answers, and confusing them makes somebody decide on data that has not
12
+ * arrived yet. */
13
+ export declare function SmartButton({ icon: Icon, count, label, onClick, disabled, surface, layout, }: {
14
+ icon: LucideIcon;
15
+ count?: number;
16
+ label: string;
17
+ onClick?: () => void;
18
+ disabled?: boolean;
19
+ /** By default it inherits it from the surface that wraps it. */
20
+ surface?: Surface;
21
+ /**
22
+ * `stacked` — the figure on top and the label below. For the sheet, where
23
+ * there is height.
24
+ * `inline` — everything on one line. For the rail.
25
+ *
26
+ * Context: in the rail, a two-line box next to breadcrumbs and a one-line
27
+ * pager imposes its height on the whole row, and the other two end up
28
+ * floating in the middle of a band that is twice as tall as them. That was
29
+ * «something looks off».
30
+ */
31
+ layout?: "stacked" | "inline";
32
+ }): import("react").JSX.Element;
33
+ export declare function SmartButtons({ children }: {
34
+ children: ReactNode;
35
+ }): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { bySurface, useSurface } from "../../foundations/surface.js";
5
+ import { Children, createContext, useContext } from "react";
6
+ /* ---------------------------------------------------------------- *
7
+ * THE SMART BUTTONS
8
+ * ---------------------------------------------------------------- */
9
+ /** On the chrome the counters share one bar. Each button reads that and drops
10
+ * its own frame, the way a stage drops its own arrow. */
11
+ const SmartGroup = createContext(false);
12
+ /** What hangs off this record, with its number, and can be pressed.
13
+ *
14
+ * It answers TWO questions with a single control: *is there any?* and *how
15
+ * many?*. Without this you have to open it to discover there is nothing — the
16
+ * most expensive click in any interface.
17
+ *
18
+ * **`—` is not `0`.** «I don't know yet» and «there is none» are two different
19
+ * answers, and confusing them makes somebody decide on data that has not
20
+ * arrived yet. */
21
+ export function SmartButton({ icon: Icon, count, label, onClick, disabled = false, surface, layout = "stacked", }) {
22
+ const copy = useUiCopy();
23
+ const empty = count === 0;
24
+ const activeSurface = useSurface(surface);
25
+ const isInline = layout === "inline";
26
+ const grouped = useContext(SmartGroup);
27
+ return (_jsxs("button", { type: "button", onClick: onClick, disabled: disabled || onClick === undefined, className: `flex shrink-0 items-center gap-2 text-left transition-[background-color,border-color,box-shadow] duration-(--duration-instant) disabled:cursor-default disabled:opacity-60 focus-visible:outline-2 focus-visible:outline-primary ${grouped
28
+ ? `not-disabled:hover:bg-chrome-ink/10 focus-visible:-outline-offset-2 ${isInline ? "h-(--size-control-sm) px-2.5" : "px-2.5 py-1"}`
29
+ : `rounded-control border focus-visible:outline-offset-2 ${isInline ? "h-(--size-control-sm) px-2" : "px-2.5 py-1"} ${bySurface(activeSurface, "border-ink/12 bg-surface shadow-raised not-disabled:hover:border-primary/40 not-disabled:hover:bg-primary/4", "border-chrome-ink/12 bg-chrome-ink/6 not-disabled:hover:border-chrome-link/40 not-disabled:hover:bg-chrome-ink/12")}`}`, children: [_jsx(Icon, { className: `size-(--size-icon-md) shrink-0 ${bySurface(activeSurface, empty ? "text-muted/50" : "text-muted", empty ? "text-chrome-muted/50" : "text-chrome-muted")}`, strokeWidth: 1.75 }), _jsxs("span", { className: `min-w-0 leading-tight ${isInline ? "flex items-baseline gap-1.5" : ""}`, children: [_jsx("span", { className: `text-meta font-medium tabular-nums ${isInline ? "" : "block"} ${bySurface(activeSurface, empty ? "text-muted" : "text-ink", empty ? "text-chrome-muted" : "text-chrome-ink")}`, children: count === undefined ? "—" : count.toLocaleString(copy.locale) }), _jsx("span", { className: `text-micro ${isInline ? "" : "block"} ${bySurface(activeSurface, "text-muted", "text-chrome-muted")}`, children: label })] })] }));
30
+ }
31
+ export function SmartButtons({ children }) {
32
+ const surface = useSurface();
33
+ if (surface !== "chrome") {
34
+ return _jsx("div", { className: "flex flex-wrap items-stretch gap-1.5", children: children });
35
+ }
36
+ const items = Children.toArray(children);
37
+ return (_jsx(SmartGroup.Provider, { value: true, children: _jsx("div", { className: "flex min-w-0 items-stretch overflow-hidden rounded-control border border-chrome-ink/15 bg-chrome-ink/8", children: items.map((child, i) => (_jsx("div", { className: i === 0 ? "flex min-w-0" : "flex min-w-0 border-l border-chrome-ink/20", children: child }, i))) }) }));
38
+ }
@@ -0,0 +1,51 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ export interface Stage {
3
+ id: string;
4
+ label: string;
5
+ }
6
+ /** Which step something is on, **with the steps that come next in sight**.
7
+ *
8
+ * ── One track, one language ──────────────────────────────────────────
9
+ *
10
+ * The stages are segments of ONE track, Odoo's statusbar: the order is the
11
+ * direction. What is behind is ink with a check, what comes next is muted, and the
12
+ * current stage is the only FILLED segment — `primary` on the canvas, the chrome's
13
+ * ink inverted on the rail. With `failed` that fill turns `danger` and carries an
14
+ * alert mark, so the failure is not only a color.
15
+ *
16
+ * A clipped arrow per step was tried and dropped: the notch between two arrows
17
+ * reads as a gap — four chips, not a path.
18
+ *
19
+ * ── Narrow: it STACKS, it does not hide ──────────────────────────────
20
+ *
21
+ * Four stages side by side do not fit in a 480 px drawer, and the answer is not
22
+ * tucking them behind a chevron: a state you have to open stops answering «where
23
+ * is this at?», which is the only question it has. It stacks — one stage per row —
24
+ * in the same track, with the same three paints and the same marks. What changes
25
+ * is the DIRECTION, not how much is shown nor how it is painted.
26
+ *
27
+ * ── And it can be CHOSEN ─────────────────────────────────────────────
28
+ *
29
+ * With `onSelect` the track is a React Aria `RadioGroup`: one stage at a time,
30
+ * arrow keys move between them, Tab enters at the chosen one. It is not
31
+ * «advancing» — a stage does not change by pressing it — it is choosing which
32
+ * stage we are talking about: in a list grouped by stage, pressing «generando» asks
33
+ * for those. So the record's CURRENT stage (`current`, the fill, `aria-current`)
34
+ * and the stage being VIEWED (`selected`, the underline, the checked radio) are two
35
+ * different things and are painted differently. Without `onSelect` it is a plain
36
+ * ordered list and the current stage is `aria-current="step"`. */
37
+ export declare function StatusBar({ stages, current, failed, surface: surfaceProp, onSelect, selected, layout, }: {
38
+ stages: readonly Stage[];
39
+ current: string;
40
+ failed?: boolean;
41
+ surface?: Surface;
42
+ /** Present = the stages can be chosen. Nothing advances: it chooses which stage
43
+ * is being talked about. */
44
+ onSelect?: (id: string) => void;
45
+ /** Which one is chosen, when they can be chosen. It is not `current`: one thing
46
+ * is where the record is at, another which stage I am looking at. */
47
+ selected?: string;
48
+ /** `trail` side by side, `stacked` one per row. Without it the surface's density
49
+ * decides — it is what knows how much width there is. */
50
+ layout?: "trail" | "stacked";
51
+ }): import("react").JSX.Element;