@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,20 @@
1
+ import type { ColumnControl, ColumnSpec } from "./columns.js";
2
+ import type { Surface } from "../../foundations/types.js";
3
+ /**
4
+ * The Odoo columns submenu: required fields stay checked and cannot be
5
+ * unchecked; optional fields toggle. It stays open while toggling so hiding
6
+ * three columns is three clicks, not three open-close round trips.
7
+ *
8
+ * Drawn only when the set declared optional columns. A menu of required-only
9
+ * fields would be a list of things you cannot do.
10
+ */
11
+ export declare function ColumnsMenu({ control, surface, }: {
12
+ control: ColumnControl;
13
+ surface?: Surface;
14
+ }): import("react").JSX.Element | null;
15
+ export declare function specsFromColumns(columns: readonly {
16
+ id: string;
17
+ header: string;
18
+ optional?: boolean;
19
+ defaultHidden?: boolean;
20
+ }[], fallbackOptional: boolean): ColumnSpec[];
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { Menu } from "../../primitives/menu/Menu.js";
5
+ import { Columns3 } from "lucide-react";
6
+ /**
7
+ * The Odoo columns submenu: required fields stay checked and cannot be
8
+ * unchecked; optional fields toggle. It stays open while toggling so hiding
9
+ * three columns is three clicks, not three open-close round trips.
10
+ *
11
+ * Drawn only when the set declared optional columns. A menu of required-only
12
+ * fields would be a list of things you cannot do.
13
+ */
14
+ export function ColumnsMenu({ control, surface, }) {
15
+ const copy = useUiCopy();
16
+ if (!control.hasOptional)
17
+ return null;
18
+ const requiredOptions = control.required.map((column) => ({
19
+ id: column.id,
20
+ label: column.label,
21
+ checked: true,
22
+ disabled: true,
23
+ hint: copy.frame.required,
24
+ }));
25
+ const optionalOptions = control.optional.map((column) => ({
26
+ id: column.id,
27
+ label: column.label,
28
+ checked: control.isVisible(column.id),
29
+ }));
30
+ const groups = requiredOptions.length === 0
31
+ ? [{ label: copy.frame.show, options: optionalOptions }]
32
+ : [
33
+ { label: copy.frame.required, options: requiredOptions },
34
+ { label: copy.frame.optional, options: optionalOptions },
35
+ ];
36
+ const hiddenOptional = control.optional.some((column) => !control.isVisible(column.id));
37
+ return (_jsx(Menu, { label: copy.frame.columns, icon: Columns3, groups: groups, onSelect: control.toggle, closeOnSelect: false, active: hiddenOptional, ...(surface === undefined ? {} : { surface }), size: "sm" }));
38
+ }
39
+ export function specsFromColumns(columns, fallbackOptional) {
40
+ return columns.map((column) => ({
41
+ id: column.id,
42
+ label: column.header,
43
+ optional: column.optional ?? fallbackOptional,
44
+ ...(column.defaultHidden === true ? { defaultHidden: true } : {}),
45
+ }));
46
+ }
@@ -0,0 +1,35 @@
1
+ import type { GroupingControl } from "./grouping.js";
2
+ import { type GroupableField, type GroupGrain } from "../../foundations/grouping.js";
3
+ /**
4
+ * «AGRUPAR POR», adentro del panel de recortes.
5
+ *
6
+ * Context: va en la MISMA columna donde están los filtros y los favoritos, y no
7
+ * en un control aparte. Filtrar y agrupar son la misma pregunta —«qué parte de
8
+ * esto estoy mirando»— y separarlos obliga a abrir dos cosas para armar un
9
+ * recorte, sin poder ver los dos estados a la vez.
10
+ *
11
+ * ── Una fecha se DESPLIEGA, no se elige ─────────────────────────────
12
+ *
13
+ * Un campo común es una opción: se tilda y listo. Un campo de fecha no, porque
14
+ * «agrupar por fecha» no quiere decir nada hasta decir con qué corte — y los
15
+ * cortes no son excluyentes: «por mes y adentro por día» es una pregunta
16
+ * legítima y frecuente. Así que una fecha se abre y ofrece sus granos como
17
+ * CASILLAS, que se tildan de a varias.
18
+ *
19
+ * ── Y se ordenan solas ──────────────────────────────────────────────
20
+ *
21
+ * Tildar «día» y después «mes» da «mes › día», nunca al revés. No es una
22
+ * preferencia: un día entero vive adentro de un solo mes, así que partir primero
23
+ * por día deja un nivel de mes que no parte nada. La dirección la pone el
24
+ * calendario y por eso la pone el sistema — ver `normalizeLevels`.
25
+ */
26
+ export type GroupingLevelsProps = {
27
+ /** Por qué campos se puede partir. Sin ninguno no se dibuja nada: una columna
28
+ * vacía en el panel es peor que una columna que no está. */
29
+ fields: readonly GroupableField[];
30
+ control: GroupingControl;
31
+ /** Cómo se llama cada grano, cuando el producto lo dice con otras palabras.
32
+ * Sin esto, las de la copia en el idioma activo. */
33
+ grainLabel?: (grain: GroupGrain) => string;
34
+ };
35
+ export declare function GroupingLevels({ fields, control, grainLabel }: GroupingLevelsProps): import("react").JSX.Element | null;
@@ -0,0 +1,45 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { grainName } from "../../foundations/calendar.js";
4
+ import { useUiCopy } from "../../foundations/copy.js";
5
+ import { GRAIN_ORDER, levelKey, } from "../../foundations/grouping.js";
6
+ import { ChevronDown, ChevronRight, ChevronUp, X } from "lucide-react";
7
+ import { useState } from "react";
8
+ function fieldLabel(fields, field) {
9
+ return fields.find((one) => one.field === field)?.label ?? field;
10
+ }
11
+ export function GroupingLevels({ fields, control, grainLabel }) {
12
+ const copy = useUiCopy();
13
+ /* Qué fechas están desplegadas. Arranca desplegada la que ya tenga algún grano
14
+ puesto: si no, lo puesto queda escondido detrás de un chevron y hay que
15
+ adivinar dónde. */
16
+ const [opened, setOpened] = useState(() => control.levels.filter((one) => one.grain !== undefined).map((one) => one.field));
17
+ if (fields.length === 0)
18
+ return null;
19
+ const placed = control.levels;
20
+ const grain = (one) => grainLabel?.(one) ?? grainName(one);
21
+ const isOpen = (field) => opened.includes(field);
22
+ const toggleOpen = (field) => setOpened((current) => current.includes(field) ? current.filter((one) => one !== field) : [...current, field]);
23
+ return (_jsxs("div", { className: "flex flex-col gap-0.5", children: [placed.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx("ol", { className: "flex flex-wrap gap-1 px-1 pb-1.5", children: placed.map((level, index) => (_jsx("li", { children: _jsxs("span", { className: "inline-flex items-center gap-1 rounded-pill border border-accent/40 bg-accent/18 py-0.5 pr-0.5 pl-1.5 text-micro text-ink", children: [_jsx("span", { className: "tabular-nums opacity-70", children: index + 1 }), _jsxs("span", { className: "max-w-32 truncate", children: [fieldLabel(fields, level.field), level.grain === undefined ? "" : ` · ${grain(level.grain)}`] }), _jsx("button", { type: "button", onClick: () => control.move(level.field, -1), disabled: index === 0, "aria-label": copy.grouping.up, title: copy.grouping.up, className: "rounded-pill p-0.5 hover:bg-primary/15 disabled:opacity-30 disabled:hover:bg-transparent", children: _jsx(ChevronUp, { className: "size-3", strokeWidth: 2.5 }) }), _jsx("button", { type: "button", onClick: () => control.move(level.field, 1), disabled: index === placed.length - 1, "aria-label": copy.grouping.down, title: copy.grouping.down, className: "rounded-pill p-0.5 hover:bg-primary/15 disabled:opacity-30 disabled:hover:bg-transparent", children: _jsx(ChevronDown, { className: "size-3", strokeWidth: 2.5 }) }), _jsx("button", { type: "button", onClick: () => control.remove(level.field, level.grain), "aria-label": copy.grouping.remove, title: copy.grouping.remove, className: "rounded-pill p-0.5 hover:bg-primary/15", children: _jsx(X, { className: "size-3", strokeWidth: 2.5 }) })] }) }, levelKey(level)))) }), _jsx("div", { className: "mb-1 h-px bg-line", role: "separator" })] })), fields.map((one) => {
24
+ const grains = one.grains;
25
+ /* Un campo común: una casilla y nada más. */
26
+ if (grains === undefined || grains.length === 0) {
27
+ const on = control.has(one.field);
28
+ return (_jsxs("button", { type: "button", onClick: () => control.toggle(one.field), "aria-pressed": on, className: `flex items-center gap-2 rounded-control px-1.5 py-1 text-left transition-colors duration-(--duration-instant) hover:bg-primary/8 ${on ? "bg-primary/6" : ""}`, children: [_jsx(Mark, { on: on }), _jsx("span", { className: `min-w-0 truncate text-meta ${on ? "font-medium text-ink" : "text-ink"}`, children: one.label })] }, one.field));
29
+ }
30
+ /* Una FECHA: se despliega y ofrece sus cortes como casillas. */
31
+ const open = isOpen(one.field);
32
+ const puestos = placed.filter((level) => level.field === one.field);
33
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => toggleOpen(one.field), "aria-expanded": open, className: "flex items-center gap-1.5 rounded-control px-1.5 py-1 text-left transition-colors duration-(--duration-instant) hover:bg-primary/8", children: [_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: `min-w-0 flex-1 truncate text-meta ${puestos.length > 0 ? "font-medium text-ink" : "text-ink"}`, children: one.label }), puestos.length === 0 ? null : (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-accent/40 bg-accent/18 px-2 py-0.5 text-micro tabular-nums text-ink", children: puestos.length }))] }), !open ? null : (_jsx("div", { className: "flex flex-col pl-(--spacing-indent)", children: GRAIN_ORDER.filter((g) => grains.includes(g)).map((g) => {
34
+ const on = control.has(one.field, g);
35
+ return (_jsxs("button", { type: "button", onClick: () => control.toggle(one.field, g), "aria-pressed": on, className: `flex items-center gap-2 rounded-control px-1.5 py-1 text-left transition-colors duration-(--duration-instant) hover:bg-primary/8 ${on ? "bg-primary/6" : ""}`, children: [_jsx(Mark, { on: on }), _jsx("span", { className: `min-w-0 truncate text-meta ${on ? "font-medium text-ink" : "text-muted"}`, children: grain(g) })] }, g));
36
+ }) }))] }, one.field));
37
+ }), placed.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-1 h-px bg-line", role: "separator" }), _jsx("button", { type: "button", onClick: () => control.clear(), className: "rounded-control px-2 py-1 text-left text-meta text-muted transition-colors duration-(--duration-instant) hover:bg-ink/4 hover:text-ink", children: copy.grouping.clear })] }))] }));
38
+ }
39
+ /** La marca de tildado. La columna existe SIEMPRE, tildada o no: sin ella,
40
+ * tildar corre el texto y la lista salta. Apagada se dibuja el aro, la misma
41
+ * marca que el panel de filtros, para que las dos columnas se lean como
42
+ * casillas y no como texto suelto con un hueco a la izquierda. */
43
+ function Mark({ on }) {
44
+ return (_jsx("span", { className: "flex size-(--size-icon-sm) shrink-0 items-center justify-center", children: on ? (_jsx("span", { className: "size-1.5 rounded-pill bg-primary" })) : (_jsx("span", { className: "size-3 rounded-pill border border-line-strong" })) }));
45
+ }
@@ -0,0 +1,27 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ import type { ReactNode } from "react";
3
+ export declare function Listing({ bar, metrics, sticky, surface, children, }: {
4
+ /** The `ListingBar`: actions, title, search, pagination and views. */
5
+ bar?: ReactNode;
6
+ /**
7
+ * La barra se queda arriba al scrollear, debajo de lo que ya esté pegado.
8
+ *
9
+ * Es opt-in y no el default porque el fondo tiene que ser el de lo que hay
10
+ * DETRÁS, y eso depende de dónde esté puesta: el arquetipo de lista vive
11
+ * sobre el piso de la página, pero un `FrameView` —«el vistazo» de un
12
+ * registro— vive sobre el PAPEL blanco, y ahí la banda color piso se ve como
13
+ * una mancha. Se descubrió comparando historias, no razonando.
14
+ */
15
+ sticky?: boolean;
16
+ /** Sobre qué está puesta la lista. Sólo se usa para el fondo de la banda que
17
+ * se pega: `chrome` pinta el mismo azul que ya pinta la barra —o sea,
18
+ * invisible— y cualquier otra pinta el PISO, que es lo que hay detrás de una
19
+ * lista de página. Las que viven adentro de algo blanco —un cajón, una vista
20
+ * previa— son compactas, y en compacto la barra no se pega. */
21
+ surface?: Surface;
22
+ /** The metrics that also narrow down. They go BELOW the bar and above the
23
+ * rows: they belong to the set I am looking at, not to the frame that frames
24
+ * it — above the bar they read as part of the navigation. */
25
+ metrics?: ReactNode;
26
+ children: ReactNode;
27
+ }): import("react").JSX.Element;
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useHeaderSlot } from "../shell/headerSlot.js";
4
+ import { byDensity, useDensity } from "../../foundations/density.js";
5
+ import { useStickyLayer } from "../../foundations/sticky.js";
6
+ import { createPortal } from "react-dom";
7
+ /* ==================================================================== *
8
+ * LISTING — the shape of a screen of N records. A single one, for all of them.
9
+ *
10
+ * The request was literal: «in every list window don't forget that it has
11
+ * [Search (Filters)] [List]». Up to here every view built that by hand —one
12
+ * put the totals on top, another the search box below the upload panel, another
13
+ * had no search box at all— and the result is that two lists of the same
14
+ * product get used differently.
15
+ *
16
+ * Four bands, always in the same order:
17
+ *
18
+ * bar crumbs · search · pagination
19
+ * actions · title · columns · views
20
+ * body the rows, in a card with a border
21
+ *
22
+ * ── The list goes in a card and the background stays behind ─────────
23
+ *
24
+ * It is what separates an application from a document. Without the card the
25
+ * rows float over nothing and the screen reads as running text; the card puts
26
+ * a boundary around the set, and that boundary is what makes it read as «one
27
+ * thing with elements inside».
28
+ *
29
+ * ── Two densities, and both are needed ──────────────────────────────
30
+ *
31
+ * `table` aligned, sortable columns. It wins when there is width: the data
32
+ * gets compared across rows without reading, which is what a table
33
+ * exists for.
34
+ * `rows` two lines per record, big name and metadata underneath. It wins
35
+ * when there is NO width —a narrow panel, the preview— because a
36
+ * six-column table in 380 px is not a table, it is an accordion.
37
+ *
38
+ * It is not an aesthetic preference: it is the available width. That is why
39
+ * the consumer passes it and this piece does not choose it — only whoever
40
+ * composes knows which surface it is going to land on.
41
+ *
42
+ * ── The row lays itself out; nobody tells it how ────────────────────
43
+ *
44
+ * WHICH presentation is the consumer's call. How a row arranges itself inside
45
+ * the one that was chosen is nobody's: there is no width at which a row «turns
46
+ * narrow», and no breakpoint written anywhere. Every zone asks for what it
47
+ * needs —the name `--container-name`, the tags `--container-tags`, the
48
+ * metadata what it says— and the line does what a line does: what does not fit
49
+ * wraps, and the name keeps the first one.
50
+ *
51
+ * **That is why it holds up with a checkbox, an icon, actions, no tags, or a
52
+ * metadata three times longer.** None of that is declared anywhere: they all
53
+ * take their width from the same line, so a list that selects wraps a little
54
+ * earlier than one that does not, on its own. Asking the density instead —a
55
+ * decision per SURFACE— got it wrong in the one place it mattered: a list in a
56
+ * 480 px drawer is comfortable —it draws its title, its counters, its full
57
+ * padding— and narrow at the same time, and the name was left with whatever
58
+ * was over between a metadata that did not shrink and a tag column of 8 rem:
59
+ * about a dozen pixels, «ve…».
60
+ * ==================================================================== */
61
+ export function Listing({ bar, metrics, sticky = false, surface, children, }) {
62
+ const d = useDensity();
63
+ /* La barra se PEGA arriba y las filas se van debajo de ella.
64
+ Context: con 187 filas, scrollear para ver la última dejaba sin buscador,
65
+ sin paginador y sin el conmutador de presentación — o sea que recortar
66
+ después de mirar obligaba a subir de nuevo. Se pega debajo de lo que ya
67
+ esté pegado (la barra de la aplicación) y empuja la pila para el
68
+ encabezado de la tabla, que se pega debajo de ella. Ver `system/sticky.tsx`. */
69
+ const band = useStickyLayer();
70
+ /* En compacto NO se pega, y no es por falta de espacio: compacto es un cajón
71
+ o una vista previa, o sea una caja corta con su propia cabecera y su propio
72
+ scroll. Una banda pegada ahí se pinta encima del papel blanco del cajón —se
73
+ ve como una mancha— para resolver un scroll que en cinco filas no existe.
74
+ Es la misma razón por la que `ListingBar` tampoco dibuja la identidad ahí. */
75
+ const pegada = sticky && d === "comfortable";
76
+ /* With a sidebar the bar is the screen's header: it goes in the frame's full-width slot,
77
+ above the sidebar (see `shell/headerSlot.ts`), and the frame does the sticking. */
78
+ const slot = useHeaderSlot();
79
+ const inSlot = pegada && slot !== null;
80
+ return (
81
+ /* Context: the box of rows carries no border. What separates it from the
82
+ background is its white and its radius; a frame around a list that is
83
+ already inside a framed panel is a nested rectangle, and nested
84
+ rectangles are what make a product read like a cloud console. */
85
+ _jsxs("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: [bar === undefined || bar === null ? null : inSlot ? (createPortal(bar, slot)) : (_jsx("div", { ref: band.ref, className: `min-w-0 ${pegada ? `sticky z-30 ${surface === "chrome" ? "bg-chrome" : "bg-ground"}` : ""}`, style: pegada ? band.self : undefined, children: bar })), inSlot && metrics !== undefined ? _jsx("div", { className: "pt-2", children: metrics }) : metrics, _jsx("div", { className: "min-w-0 overflow-clip rounded-box bg-surface", style: pegada && !inSlot ? band.below : undefined, children: children })] }));
86
+ }
@@ -0,0 +1,112 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ import type { ReactNode } from "react";
3
+ /** THE BAR — two rows, two questions.
4
+ *
5
+ * ┌──────────┬──────────────────────────────┬──────────────┐
6
+ * │ crumbs │ 🔍 search… ⚙3 │ 1–8 of 187 ‹ › │
7
+ * ├──────────┼──────────────────────────────┼──────────────┤
8
+ * │ [New] │ Facturas · 180 · 7 · 8 │ [columns] [▤][▦] │
9
+ * └──────────┴──────────────────────────────┴──────────────┘
10
+ * WHERE / HOW MUCH I SEE HOW I LOOK
11
+ *
12
+ * Pagination answers how much of the filtered set is on screen, so it stays
13
+ * with the search. Columns and the presentation switcher answer how the set
14
+ * is looked at, so they sit with the actions — not next to the page range.
15
+ * Putting the four of them on the search line is how the field and «1–80»
16
+ * painted over each other the moment the window shrank.
17
+ *
18
+ * ── Why it is two lines tall ────────────────────────────────────────
19
+ *
20
+ * Because the three things that go on the left do not fit in one: a
21
+ * comfortable button is 34 px tall, a list title may have to wrap onto two
22
+ * lines, and below it goes a sentence saying what the list is made of. Forcing
23
+ * it into a single line means shrinking the button —the defect we already
24
+ * fixed— or truncating the title, which is worse: a list that cannot be named
25
+ * in full is indistinguishable from the one next to it.
26
+ *
27
+ * ── The selection transforms the bar, it does not add one ───────────
28
+ *
29
+ * When rows get selected, the identity zone turns into «3 elegidas ✕» plus the
30
+ * actions that apply. **In the same place and without pushing anything.** A
31
+ * bar that appears shifts the list downwards right when somebody is ticking
32
+ * checkboxes, and the row they were about to tick is no longer where it was.
33
+ *
34
+ * The actions on the left do NOT go away: «Nuevo» stays available with three
35
+ * rows selected, because selecting is not entering a mode.
36
+ *
37
+ * ── Inside a record it is ONE line (`inline`) ────────────────────────
38
+ *
39
+ * ┌───────────────────────────────────────────────────────────────┐
40
+ * │ 🔍 search the lines… 12 records [sort][cols][▤▦] │
41
+ * └───────────────────────────────────────────────────────────────┘
42
+ * WHAT I LOOK FOR (fills) HOW MANY HOW I LOOK
43
+ *
44
+ * A collection that is a FIELD of a record — the lines of an order, the
45
+ * contacts of a customer, the journal items of an invoice — is not a screen.
46
+ * Everything the two rows above answer is already answered by the record
47
+ * around it: where I am (the record's crumbs), what this is (the tab it sits
48
+ * in), what I can do (the record's actions). Drawn with the page bar, it
49
+ * spent two rows and a rule on the one question left —the search, alone and
50
+ * centred on the first row; «Ordenar» and the switcher alone on the second—
51
+ * and pushed the rows, which are what the tab is for, below the fold.
52
+ *
53
+ * So it is a single line, and the order is the order of the questions: the
54
+ * search FILLS from the left (it is the only thing with variable content),
55
+ * then how many there are, then how they are looked at, then what starts a
56
+ * new one. No band, no padding, no hairline: it sits on the record's paper,
57
+ * aligned with the fields above it, and a light band inside a white sheet is
58
+ * a rectangle inside a rectangle.
59
+ *
60
+ * On a phone the line breaks by itself, with no threshold written anywhere:
61
+ * the search asks for `--container-name` before it gives up its place, so
62
+ * when the controls do not fit next to it they drop to a second line, pushed
63
+ * to the right, and the search keeps the whole first one.
64
+ *
65
+ * The count is a live region: typing in the search changes it, and somebody
66
+ * who cannot see the rows thin out hears how many are left. With
67
+ * `pagination` the page range replaces it — it already says how many. */
68
+ export declare function ListingBar({ actions, title, description, counters, selection, search, pagination, views, crumbs, surface, inline, count, }: {
69
+ /** What starts something new. It stays visible even when there is a selection. */
70
+ actions?: ReactNode;
71
+ title?: string;
72
+ /** One sentence: what this list is made of, or what can be done with it. It
73
+ * goes below the title and that is why it can be a real sentence. */
74
+ description?: string;
75
+ /** The totals per category. The ones in the pagination say how many there
76
+ * are; these say what they are made of, which is another question. */
77
+ counters?: readonly {
78
+ label: string;
79
+ value: number | string;
80
+ }[];
81
+ /** When there are selected rows. It replaces the identity, in the same place. */
82
+ selection?: {
83
+ count: number;
84
+ onClear?: () => void;
85
+ actions?: ReactNode;
86
+ };
87
+ search?: ReactNode;
88
+ pagination?: ReactNode;
89
+ /** Columns menu and presentation switcher. They sit on the actions row, on
90
+ * the right: they are how I look at the set, not how many of it I see. */
91
+ views?: ReactNode;
92
+ /** Where I come from. It goes above the row and only on the chrome. */
93
+ crumbs?: ReactNode;
94
+ /** `chrome` paints the whole band navy and everything inside inherits it:
95
+ * the search box, the pagination and the switcher. See `system/surface.tsx`. */
96
+ surface?: Surface;
97
+ /** One line, for a collection inside a record: search, count, views, actions. The
98
+ * title, the description, the counters and the crumbs are not drawn — the record
99
+ * around it already says them. Always on the canvas. See the header. */
100
+ inline?: boolean;
101
+ /** How many records there are, said on the inline line when there is no
102
+ * `pagination` to say it. Ignored by the two-row bar: its identity has counters. */
103
+ count?: number;
104
+ }): import("react").JSX.Element;
105
+ export type ListIdentity = {
106
+ title?: string;
107
+ description?: string;
108
+ counters?: readonly {
109
+ label: string;
110
+ value: number | string;
111
+ }[];
112
+ };
@@ -0,0 +1,144 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
+ import { byDensity, useDensity } from "../../foundations/density.js";
5
+ import { SurfaceProvider, useSurface } from "../../foundations/surface.js";
6
+ import { Line } from "../../primitives/block/Line.js";
7
+ import { X } from "lucide-react";
8
+ /** THE BAR — two rows, two questions.
9
+ *
10
+ * ┌──────────┬──────────────────────────────┬──────────────┐
11
+ * │ crumbs │ 🔍 search… ⚙3 │ 1–8 of 187 ‹ › │
12
+ * ├──────────┼──────────────────────────────┼──────────────┤
13
+ * │ [New] │ Facturas · 180 · 7 · 8 │ [columns] [▤][▦] │
14
+ * └──────────┴──────────────────────────────┴──────────────┘
15
+ * WHERE / HOW MUCH I SEE HOW I LOOK
16
+ *
17
+ * Pagination answers how much of the filtered set is on screen, so it stays
18
+ * with the search. Columns and the presentation switcher answer how the set
19
+ * is looked at, so they sit with the actions — not next to the page range.
20
+ * Putting the four of them on the search line is how the field and «1–80»
21
+ * painted over each other the moment the window shrank.
22
+ *
23
+ * ── Why it is two lines tall ────────────────────────────────────────
24
+ *
25
+ * Because the three things that go on the left do not fit in one: a
26
+ * comfortable button is 34 px tall, a list title may have to wrap onto two
27
+ * lines, and below it goes a sentence saying what the list is made of. Forcing
28
+ * it into a single line means shrinking the button —the defect we already
29
+ * fixed— or truncating the title, which is worse: a list that cannot be named
30
+ * in full is indistinguishable from the one next to it.
31
+ *
32
+ * ── The selection transforms the bar, it does not add one ───────────
33
+ *
34
+ * When rows get selected, the identity zone turns into «3 elegidas ✕» plus the
35
+ * actions that apply. **In the same place and without pushing anything.** A
36
+ * bar that appears shifts the list downwards right when somebody is ticking
37
+ * checkboxes, and the row they were about to tick is no longer where it was.
38
+ *
39
+ * The actions on the left do NOT go away: «Nuevo» stays available with three
40
+ * rows selected, because selecting is not entering a mode.
41
+ *
42
+ * ── Inside a record it is ONE line (`inline`) ────────────────────────
43
+ *
44
+ * ┌───────────────────────────────────────────────────────────────┐
45
+ * │ 🔍 search the lines… 12 records [sort][cols][▤▦] │
46
+ * └───────────────────────────────────────────────────────────────┘
47
+ * WHAT I LOOK FOR (fills) HOW MANY HOW I LOOK
48
+ *
49
+ * A collection that is a FIELD of a record — the lines of an order, the
50
+ * contacts of a customer, the journal items of an invoice — is not a screen.
51
+ * Everything the two rows above answer is already answered by the record
52
+ * around it: where I am (the record's crumbs), what this is (the tab it sits
53
+ * in), what I can do (the record's actions). Drawn with the page bar, it
54
+ * spent two rows and a rule on the one question left —the search, alone and
55
+ * centred on the first row; «Ordenar» and the switcher alone on the second—
56
+ * and pushed the rows, which are what the tab is for, below the fold.
57
+ *
58
+ * So it is a single line, and the order is the order of the questions: the
59
+ * search FILLS from the left (it is the only thing with variable content),
60
+ * then how many there are, then how they are looked at, then what starts a
61
+ * new one. No band, no padding, no hairline: it sits on the record's paper,
62
+ * aligned with the fields above it, and a light band inside a white sheet is
63
+ * a rectangle inside a rectangle.
64
+ *
65
+ * On a phone the line breaks by itself, with no threshold written anywhere:
66
+ * the search asks for `--container-name` before it gives up its place, so
67
+ * when the controls do not fit next to it they drop to a second line, pushed
68
+ * to the right, and the search keeps the whole first one.
69
+ *
70
+ * The count is a live region: typing in the search changes it, and somebody
71
+ * who cannot see the rows thin out hears how many are left. With
72
+ * `pagination` the page range replaces it — it already says how many. */
73
+ export function ListingBar({ actions, title, description, counters, selection, search, pagination, views, crumbs, surface, inline = false, count, }) {
74
+ const selecting = selection !== undefined && selection.count > 0;
75
+ const d = useDensity();
76
+ const copy = useUiCopy();
77
+ const activeSurface = useSurface(inline ? "canvas" : surface);
78
+ const isChrome = activeSurface === "chrome";
79
+ /* In compact the title and the description are NOT drawn, and it is not for
80
+ lack of room: in a preview or on a phone, the surface header already says
81
+ what I am looking at. Repeating it here burns the only line there is for
82
+ the search box — which is what is actually needed. */
83
+ const withIdentity = d === "comfortable";
84
+ /* Two rows, and the split is the same one as in a record:
85
+ 1 · WHERE I AM and HOW MANY THERE ARE — crumbs, search, page range.
86
+ 2 · WHAT I CAN DO and HOW I LOOK — actions, identity, columns / views.
87
+
88
+ Views used to sit next to the pagination. On a narrow window the search
89
+ field and «1–80 / 211» painted over each other; columns and the switcher
90
+ were the extra weight on a line that only needed how MANY. */
91
+ const center = selecting ? (_jsxs("div", { className: "flex min-w-0 items-center gap-x-2", children: [_jsxs("span", { className: `inline-flex h-(--size-control-md) shrink-0 items-center gap-1.5 rounded-control pr-1 pl-2.5 text-meta font-medium ${isChrome ? "bg-chrome-ink/14 text-chrome-ink" : "bg-ink/8 text-ink"}`, children: [_jsx("span", { className: "tabular-nums", children: selection?.count.toLocaleString(copy.locale) }), copy.selection.chosen, _jsx("button", { type: "button", onClick: selection?.onClear, "aria-label": copy.selection.clear, title: copy.selection.clear, className: `inline-flex size-5 items-center justify-center rounded-pill transition-colors duration-(--duration-instant) ${isChrome ? "hover:bg-chrome-ink/20" : "hover:bg-primary/20"}`, children: _jsx(X, { className: "size-3", strokeWidth: 2.5 }) })] }), selection?.actions === undefined ? null : (_jsx("div", { className: "flex min-w-0 items-center gap-1.5", children: selection.actions }))] })) : search === undefined ? null : (
92
+ /* Selecting replaces the search box: what gets selected is over what is
93
+ ALREADY filtered, so searching there would change the set underneath the
94
+ selection. */
95
+ _jsx("div", { className: "w-full min-w-0", children: search }));
96
+ if (inline) {
97
+ const howMany = pagination ??
98
+ (count === undefined ? null : (_jsx("span", { role: "status", className: "shrink-0 text-meta text-muted tabular-nums", children: count === 1
99
+ ? copy.list.countOne
100
+ : fillCopy(copy.list.count, { count: count.toLocaleString(copy.locale) }) })));
101
+ return (_jsx(SurfaceProvider, { value: "canvas", children: _jsxs("div", { className: `flex min-w-0 flex-wrap items-center gap-y-1.5 ${byDensity(d, "gap-x-3", "gap-x-2")}`, children: [center === null ? null : (_jsx("div", { className: "flex min-w-0 flex-1 basis-(--container-name) items-center overflow-x-clip overflow-y-visible", children: center })), howMany, views === undefined ? null : (_jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: views })), actions === undefined ? null : (_jsx("div", { className: `flex shrink-0 items-center gap-1.5 ${views === undefined ? "ml-auto" : ""}`, children: actions }))] }) }));
102
+ }
103
+ /* El campo de búsqueda va en el CENTRO de la línea, y centrado contra la
104
+ línea: pide `--container-search` y los dos lados se reparten lo que sobra
105
+ en mitades iguales. Antes tomaba el resto, así que un rastro largo a la
106
+ izquierda lo corría —y dos listas seguidas dibujaban su buscador en dos
107
+ lugares distintos—. Ver `primitives/block/Line.tsx`. */
108
+ const topRow = (_jsx(Line, { asks: "var(--container-crumb)", centerAsks: center === null ? "auto" : "var(--container-search)", gap: byDensity(d, "gap-x-4 gap-y-2", "gap-x-2 gap-y-1.5"), align: "items-start", start: crumbs,
109
+ /* `overflow-x-clip` y NO `overflow-hidden`: lo que hay que recortar es el
110
+ ancho —que el campo no pinte encima de «1–80»—, y `hidden` en un eje
111
+ fuerza `auto` en el otro, así que también recortaba hacia ABAJO. El
112
+ panel de filtros del `SearchBar` cuelga con `absolute top-full`, o sea
113
+ justo afuera de esta caja: apretar el embudo abría un panel que nadie
114
+ veía. `clip` es el único valor que deja `visible` en el otro eje. */
115
+ center: center === null ? null : (_jsx("div", { className: "flex w-full min-w-0 items-center overflow-x-clip overflow-y-visible", children: center })), end: pagination }));
116
+ const bottomRow = actions === undefined && !withIdentity && views === undefined ? null : (
117
+ /* On a narrow list (a phone, a drawer) the name and its counters take the whole first
118
+ line and the action and the controls go below: squeezed between them, the
119
+ description wrapped one word per line. */
120
+ _jsxs("div", { className: `flex min-w-0 flex-wrap items-center gap-y-2 ${byDensity(d, "gap-x-4", "gap-x-2")}`, children: [actions === undefined ? null : (_jsx("div", { className: "flex shrink-0 items-center gap-1.5", children: actions })), !withIdentity ? null : (_jsxs("div", { className: "flex min-w-0 flex-1 flex-col @max-2xl:order-first @max-2xl:basis-full", children: [title === undefined ? null : (
121
+ /* No `truncate`: the title may wrap onto two lines. A list that
122
+ cannot be named in full is indistinguishable from the one next
123
+ to it. */
124
+ /* `title` and semibold: the name of the list is the entry point of the
125
+ screen. At `lead` it was smaller than the metrics under it, so the
126
+ largest thing on a list page was a number (measured: 1.46:1 over the
127
+ body, below the ~1.5 an eye needs to find where to start). */
128
+ _jsx("h2", { className: `text-title leading-tight font-semibold ${isChrome ? "text-chrome-ink" : "text-ink"}`, children: title })), description === undefined && counters === undefined ? null : (_jsxs("div", { className: "mt-0.5 flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-0.5", children: [counters?.map((c) => (_jsxs("span", { className: "flex items-baseline gap-1", children: [_jsx("span", { className: `text-meta font-medium tabular-nums ${isChrome ? "text-chrome-ink" : "text-ink"}`, children: typeof c.value === "number"
129
+ ? c.value.toLocaleString(copy.locale)
130
+ : c.value }), _jsx("span", { className: `text-micro ${isChrome ? "text-chrome-muted" : "text-muted"}`, children: c.label })] }, c.label))), description === undefined ? null : (_jsx("span", { className: `min-w-0 text-micro ${isChrome ? "text-chrome-muted" : "text-muted"}`, children: description }))] }))] })), views === undefined ? null : (_jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: views }))] }));
131
+ const bar = (_jsxs("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2", "gap-1.5")}`, children: [!isChrome && bottomRow !== null ? (_jsx("div", { className: "border-b border-line pb-2", children: topRow })) : (topRow), bottomRow] }));
132
+ /* On the canvas the bar is a light control band: white, a hairline below, the same
133
+ padding as the dark one — a list inside an application whose bar is already dark keeps
134
+ ONE dark band per screen. Without its own band the bar sat flush on the page and its
135
+ controls had nothing to belong to. */
136
+ if (!isChrome) {
137
+ return (_jsx(SurfaceProvider, { value: "canvas", children: _jsx("div", { className: `flex min-w-0 flex-col border-b border-line bg-surface ${byDensity(d, "px-3.5 py-2.5", "px-2.5 py-2")}`, children: bar }) }));
138
+ }
139
+ /* On the chrome the bar IS the band: the background reaches the edges and
140
+ everything inside inherits the surface, so there is no way to forget to
141
+ pass it — which is how the status bar ended up with the canvas palette on
142
+ top of the navy. */
143
+ return (_jsx(SurfaceProvider, { value: "chrome", children: _jsx("div", { className: `flex min-w-0 flex-col bg-chrome ${byDensity(d, "px-3.5 py-2.5", "px-2.5 py-2")}`, children: bar }) }));
144
+ }
@@ -0,0 +1,36 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ /** How many of how many, and how I move to the next page.
3
+ *
4
+ * **It lives ONCE and it is mounted in two places.** The normal destination is
5
+ * the search bar: you search, you narrow down and you paginate on the same
6
+ * line, without looking down to the footer to discover there are another
7
+ * hundred and sixty. The footer (`ListingFooter`) existed only for the lists
8
+ * that do NOT have a search box — there is no narrowing line there to put it on.
9
+ *
10
+ * Two implementations of one pagination diverge at the first fix, so this is
11
+ * the only one and both places receive it.
12
+ *
13
+ * ── The range, not the page number ──────────────────────────────────
14
+ *
15
+ * «1–25 de 187» answers three questions —where I am, how much I see, how much
16
+ * there is— and «page 1 of 8» answers a single one, in units that depend on a
17
+ * page size the user did not choose.
18
+ *
19
+ * ── Why the page size is visible ────────────────────────────────────
20
+ *
21
+ * Because it is the difference between scrolling eight times and scrolling
22
+ * once. In a tool where somebody compares a hundred and eighty invoices,
23
+ * leaving it fixed at 25 forces paginating over something that is not a
24
+ * product decision. */
25
+ export declare function Pagination({ from, to, total, pageSize, pageSizes, onPageSizeChange, onPrev, onNext, surface, }: {
26
+ from: number;
27
+ to: number;
28
+ total: number;
29
+ pageSize?: number;
30
+ pageSizes?: readonly number[];
31
+ onPageSizeChange?: (next: number) => void;
32
+ onPrev?: () => void;
33
+ onNext?: () => void;
34
+ /** By default it inherits it from the surface that wraps it. */
35
+ surface?: Surface;
36
+ }): import("react").JSX.Element;