@sincpro/web-ui 0.2.0 → 1.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 (132) hide show
  1. package/README.md +3 -3
  2. package/dist/components/activity/ActivityBadge.d.ts +16 -0
  3. package/dist/components/activity/ActivityBadge.js +29 -0
  4. package/dist/components/activity/index.d.ts +1 -0
  5. package/dist/components/activity/index.js +1 -0
  6. package/dist/components/calendar/RecordCalendar.d.ts +48 -0
  7. package/dist/components/calendar/RecordCalendar.js +101 -0
  8. package/dist/components/calendar/index.d.ts +1 -0
  9. package/dist/components/calendar/index.js +1 -0
  10. package/dist/components/form/Sheet.js +5 -1
  11. package/dist/components/gantt/RecordGantt.d.ts +44 -0
  12. package/dist/components/gantt/RecordGantt.js +110 -0
  13. package/dist/components/gantt/index.d.ts +1 -0
  14. package/dist/components/gantt/index.js +1 -0
  15. package/dist/components/index.d.ts +3 -0
  16. package/dist/components/index.js +3 -0
  17. package/dist/components/lines/LineItems.d.ts +5 -2
  18. package/dist/components/lines/LineItems.js +3 -3
  19. package/dist/components/listing/BoardColumns.d.ts +39 -0
  20. package/dist/components/listing/BoardColumns.js +138 -0
  21. package/dist/components/listing/ListingBar.d.ts +5 -0
  22. package/dist/components/listing/ListingBar.js +5 -2
  23. package/dist/components/listing/PivotExplorer.d.ts +34 -0
  24. package/dist/components/listing/PivotExplorer.js +86 -0
  25. package/dist/components/listing/RecordCard.d.ts +6 -8
  26. package/dist/components/listing/RecordCard.js +13 -7
  27. package/dist/components/listing/Row.d.ts +21 -2
  28. package/dist/components/listing/Row.js +29 -13
  29. package/dist/components/listing/index.d.ts +2 -0
  30. package/dist/components/listing/index.js +2 -0
  31. package/dist/components/listing/set.d.ts +1 -1
  32. package/dist/components/navigation/index.d.ts +0 -2
  33. package/dist/components/navigation/index.js +2 -3
  34. package/dist/components/record/RecordAudit.d.ts +17 -0
  35. package/dist/components/record/RecordAudit.js +27 -0
  36. package/dist/components/record/RecordHeader.d.ts +7 -1
  37. package/dist/components/record/RecordHeader.js +30 -18
  38. package/dist/components/record/RecordRibbon.d.ts +13 -0
  39. package/dist/components/record/RecordRibbon.js +22 -0
  40. package/dist/components/record/index.d.ts +2 -0
  41. package/dist/components/record/index.js +2 -0
  42. package/dist/components/search/CriteriaChips.d.ts +6 -1
  43. package/dist/components/search/CriteriaChips.js +71 -20
  44. package/dist/components/search/SearchBar.d.ts +15 -1
  45. package/dist/components/search/SearchBar.js +34 -16
  46. package/dist/components/search/query.d.ts +14 -11
  47. package/dist/components/search/query.js +36 -3
  48. package/dist/components/timeline/RecordTimeline.d.ts +60 -0
  49. package/dist/components/timeline/RecordTimeline.js +214 -0
  50. package/dist/components/timeline/TimelineToolbar.d.ts +30 -0
  51. package/dist/components/timeline/TimelineToolbar.js +31 -0
  52. package/dist/components/timeline/index.d.ts +3 -0
  53. package/dist/components/timeline/index.js +3 -0
  54. package/dist/components/timeline/time.d.ts +43 -0
  55. package/dist/components/timeline/time.js +135 -0
  56. package/dist/foundations/copy.d.ts +110 -14
  57. package/dist/foundations/copy.es.js +85 -9
  58. package/dist/foundations/copy.js +85 -9
  59. package/dist/foundations/grouping.d.ts +3 -0
  60. package/dist/foundations/index.d.ts +1 -1
  61. package/dist/foundations/match.d.ts +23 -0
  62. package/dist/foundations/match.js +63 -0
  63. package/dist/foundations/slots.d.ts +1 -1
  64. package/dist/foundations/slots.js +8 -3
  65. package/dist/foundations/types.d.ts +7 -0
  66. package/dist/foundations/types.js +0 -36
  67. package/dist/primitives/block/index.d.ts +1 -1
  68. package/dist/primitives/block/index.js +1 -1
  69. package/dist/primitives/cell/CellStack.d.ts +7 -1
  70. package/dist/primitives/cell/CellStack.js +5 -2
  71. package/dist/primitives/choice/ComboBox.js +8 -4
  72. package/dist/primitives/choice/KanbanState.d.ts +14 -0
  73. package/dist/primitives/choice/KanbanState.js +37 -0
  74. package/dist/primitives/choice/Priority.d.ts +20 -0
  75. package/dist/primitives/choice/Priority.js +41 -0
  76. package/dist/primitives/choice/Slider.d.ts +6 -1
  77. package/dist/primitives/choice/Slider.js +2 -2
  78. package/dist/primitives/choice/index.d.ts +2 -0
  79. package/dist/primitives/choice/index.js +2 -0
  80. package/dist/primitives/dialog/Dialog.d.ts +9 -3
  81. package/dist/primitives/dialog/Dialog.js +41 -5
  82. package/dist/primitives/dialog/index.d.ts +0 -1
  83. package/dist/primitives/dialog/index.js +0 -2
  84. package/dist/primitives/notice/index.d.ts +1 -1
  85. package/dist/primitives/notice/index.js +1 -1
  86. package/dist/primitives/overlay/Preview.js +13 -4
  87. package/dist/primitives/picker/MatchedText.d.ts +10 -0
  88. package/dist/primitives/picker/MatchedText.js +12 -0
  89. package/dist/primitives/picker/Picker.d.ts +42 -7
  90. package/dist/primitives/picker/Picker.js +107 -29
  91. package/dist/primitives/table/EditableTable.d.ts +6 -1
  92. package/dist/primitives/table/EditableTable.js +70 -9
  93. package/dist/primitives/table/ReportTable.js +2 -1
  94. package/dist/primitives/table/RowActions.js +7 -2
  95. package/dist/primitives/table/Table.d.ts +5 -0
  96. package/dist/primitives/table/Table.js +17 -8
  97. package/dist/primitives/tag/Tags.d.ts +2 -0
  98. package/dist/primitives/tag/Tags.js +2 -1
  99. package/dist/primitives/tag/TagsField.d.ts +17 -0
  100. package/dist/primitives/tag/TagsField.js +46 -11
  101. package/dist/primitives/tag/choices.d.ts +6 -1
  102. package/dist/primitives/tag/choices.js +6 -2
  103. package/dist/primitives/value/Avatar.d.ts +16 -0
  104. package/dist/primitives/value/Avatar.js +91 -0
  105. package/dist/primitives/value/Person.d.ts +6 -51
  106. package/dist/primitives/value/Person.js +9 -148
  107. package/dist/primitives/value/Relation.d.ts +21 -9
  108. package/dist/primitives/value/Relation.js +35 -24
  109. package/dist/primitives/value/contact.d.ts +30 -0
  110. package/dist/primitives/value/contact.js +51 -0
  111. package/dist/primitives/value/index.d.ts +1 -1
  112. package/dist/primitives/value/index.js +1 -1
  113. package/dist/tokens.css +136 -1
  114. package/dist/views/list/List.d.ts +25 -2
  115. package/dist/views/list/List.js +48 -8
  116. package/dist/views/list/ListView.d.ts +1 -1
  117. package/dist/views/list/ListView.js +40 -9
  118. package/dist/views/list/columns.js +8 -1
  119. package/dist/views/list/presentations.d.ts +5 -2
  120. package/dist/views/list/presentations.js +32 -2
  121. package/dist/views/list/types.d.ts +44 -0
  122. package/dist/views/list/useNarrowList.d.ts +10 -0
  123. package/dist/views/list/useNarrowList.js +20 -6
  124. package/dist/views/record/Record.d.ts +14 -0
  125. package/dist/views/record/Record.js +22 -7
  126. package/package.json +11 -4
  127. package/dist/components/navigation/CommandPalette.d.ts +0 -40
  128. package/dist/components/navigation/CommandPalette.js +0 -114
  129. package/dist/components/navigation/commands.d.ts +0 -30
  130. package/dist/components/navigation/commands.js +0 -30
  131. package/dist/primitives/dialog/Drill.d.ts +0 -90
  132. package/dist/primitives/dialog/Drill.js +0 -172
@@ -1,10 +1,11 @@
1
+ import type { BoardStages } from "../../components/listing/BoardColumns.js";
1
2
  import type { ListIdentity } from "../../components/listing/ListingBar.js";
2
3
  import type { SortField } from "../../components/listing/SortMenu.js";
3
4
  import type { Surface } from "../../foundations/types.js";
4
5
  import type { DataColumn, DataTableCreate } from "../../primitives/table/DataTable.js";
5
6
  import type { RowAction } from "../../primitives/table/RowActions.js";
6
7
  import type { SortState } from "../../primitives/table/Table.js";
7
- import { type ListCrumb, type ListEmpty, type ListGroup, type ListPivot, type ListPresent, type ListReport, type ListSheet, type RowOf } from "./types.js";
8
+ import { type ListCalendar, type ListCrumb, type ListEmpty, type ListGantt, type ListGroup, type ListPivot, type ListPresent, type ListReport, type ListSheet, type ListTimeline, type RowOf } from "./types.js";
8
9
  import type { ReactNode } from "react";
9
10
  type BarProps = {
10
11
  children?: ReactNode;
@@ -50,6 +51,8 @@ type TableProps<T extends object> = {
50
51
  * se puede ver como tablero. */
51
52
  type BoardProps<T> = {
52
53
  card?: (item: T) => ReactNode;
54
+ /** One column per stage, with drag (and Alt + ← / →) to move a card between them. */
55
+ stages?: BoardStages<T>;
53
56
  };
54
57
  /** La planilla. Sin columnas propias, sale de las que declaró `List.Table` — la
55
58
  * de datos CRUDOS sí las trae, porque ahí salen del archivo y el código no sabe
@@ -75,6 +78,11 @@ export type ListProps<T extends object> = {
75
78
  count: number;
76
79
  clear: () => void;
77
80
  actions?: ReactNode;
81
+ /** Every record that matches, not just the page — see `ListViewProps`. */
82
+ matching?: {
83
+ total: number;
84
+ onSelect: () => void;
85
+ };
78
86
  };
79
87
  /** El orden, que también es del conjunto. */
80
88
  sort?: SortState | null;
@@ -88,6 +96,18 @@ export type ListProps<T extends object> = {
88
96
  onOpen?: (item: T) => void;
89
97
  onPeek?: (item: T) => void;
90
98
  isPeeked?: (item: T) => boolean;
99
+ /** What a press on the row's body does when both ways in exist. `peek` (the default) looks
100
+ * without leaving the list; `open` goes to the record — a workspace whose rule is «a row
101
+ * opens the page» (the composer's) says `open`, and 👁 stays for the look. */
102
+ rowClick?: "peek" | "open";
103
+ /** The list CHOOSES instead of opening — a «Buscar más…», a chooser a workspace asked for:
104
+ * a press on a row (its body, its name, a card) calls this, and the row offers no 👁 / ↗.
105
+ * For several at once, give `selection` too and toggle it here. */
106
+ onChoose?: (item: T) => void;
107
+ /** Nothing in it can be acted on: no opening, peeking, selecting, creating nor row
108
+ * actions — a list to read, like the lines of a record in view mode. One switch, so whoever
109
+ * passes the handlers generically does not have to strip them one by one. */
110
+ readOnly?: boolean;
91
111
  /** Los tres estados, en un solo lugar. */
92
112
  state?: {
93
113
  loading?: boolean;
@@ -99,7 +119,7 @@ export type ListProps<T extends object> = {
99
119
  empty?: ListEmpty;
100
120
  children?: ReactNode;
101
121
  };
102
- export declare function List<T extends object>({ items, keyOf, embedded, surface, group, selection, sort, onSortChange, sortable, present, onPresentChange, onOpen, onPeek, isPeeked, state, filtered, empty, children, }: ListProps<T>): import("react").JSX.Element;
122
+ export declare function List<T extends object>({ items, keyOf, embedded, surface, group, selection: selectWith, sort, onSortChange, sortable, present, onPresentChange, onOpen: openWith, onPeek: peekWith, isPeeked, readOnly, rowClick, onChoose, state, filtered, empty, children, }: ListProps<T>): import("react").JSX.Element;
103
123
  export declare namespace List {
104
124
  var Bar: ((props: BarProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"bar">;
105
125
  var Crumbs: ((props: CrumbsProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"crumbs">;
@@ -115,5 +135,8 @@ export declare namespace List {
115
135
  var Sheet: <T>(props: SheetProps<T>) => ReactNode;
116
136
  var Report: ((props: ListReport) => ReactNode) & import("../../foundations/slots.js").Slotted<"report">;
117
137
  var Pivot: <T>(props: ListPivot<T>) => ReactNode;
138
+ var Calendar: <T>(props: ListCalendar<T>) => ReactNode;
139
+ var Gantt: <T>(props: ListGantt<T>) => ReactNode;
140
+ var Timeline: <T>(props: ListTimeline<T>) => ReactNode;
118
141
  }
119
142
  export {};
@@ -40,10 +40,32 @@ const PRESENT_OF = {
40
40
  sheet: "sheet",
41
41
  report: "report",
42
42
  pivot: "pivot",
43
+ calendar: "calendar",
44
+ gantt: "gantt",
45
+ timeline: "timeline",
43
46
  };
44
- const FACES = ["rows", "table", "board", "sheet", "report", "pivot"];
45
- export function List({ items, keyOf, embedded, surface, group, selection, sort, onSortChange, sortable, present, onPresentChange, onOpen, onPeek, isPeeked, state, filtered, empty, children, }) {
47
+ const FACES = [
48
+ "rows",
49
+ "table",
50
+ "board",
51
+ "sheet",
52
+ "report",
53
+ "pivot",
54
+ "calendar",
55
+ "gantt",
56
+ "timeline",
57
+ ];
58
+ /** Records grouped in rows across time, N per row — a planning (`RecordTimeline`). */
59
+ const TimelineSlot = slot("timeline", (_props) => null);
60
+ /** When each record happens, on a month, a week, a day or an agenda (`RecordCalendar`). */
61
+ const CalendarSlot = slot("calendar", (_props) => null);
62
+ /** Each record as a span on a timeline, with its dependencies (`RecordGantt`). */
63
+ const GanttSlot = slot("gantt", (_props) => null);
64
+ export function List({ items, keyOf, embedded, surface, group, selection: selectWith, sort, onSortChange, sortable, present, onPresentChange, onOpen: openWith, onPeek: peekWith, isPeeked, readOnly = false, rowClick, onChoose, state, filtered, empty, children, }) {
46
65
  const top = flatten(children);
66
+ const onOpen = readOnly ? undefined : openWith;
67
+ const onPeek = readOnly ? undefined : peekWith;
68
+ const selection = readOnly ? undefined : selectWith;
47
69
  const bar = flatten(propsOf(pick(top, "bar"))?.children);
48
70
  const crumbs = propsOf(pick(bar, "crumbs"))?.crumbs;
49
71
  const identity = propsOf(pick(bar, "identity"));
@@ -58,6 +80,9 @@ export function List({ items, keyOf, embedded, surface, group, selection, sort,
58
80
  const sheet = propsOf(pick(top, "sheet"));
59
81
  const report = propsOf(pick(top, "report"));
60
82
  const pivot = propsOf(pick(top, "pivot"));
83
+ const calendar = propsOf(pick(top, "calendar"));
84
+ const gantt = propsOf(pick(top, "gantt"));
85
+ const timeline = propsOf(pick(top, "timeline"));
61
86
  /* Las caras que hay son las que están escritas, EN ESE ORDEN. El conmutador
62
87
  no puede ofrecer una que no se pueda dibujar, porque la que se dibuja y la
63
88
  que se ofrece son el mismo nodo. */
@@ -68,22 +93,34 @@ export function List({ items, keyOf, embedded, surface, group, selection, sort,
68
93
  /* La escotilla: lo que el arquetipo no previó se dibuja igual, debajo. Que
69
94
  aparezca algo acá es la señal de que al arquetipo le falta una pieza. */
70
95
  const rest = loose(top, ["bar", "metrics", ...FACES]);
71
- return (_jsx(ListView, { items: items, keyOf: keyOf, row: rows?.row ?? (() => ({ name: "" })), ...(rows?.header === undefined ? {} : { rowHeader: rows.header }), ...(identity === undefined ? {} : { identity }), ...(crumbs === undefined ? {} : { crumbs }), ...(embedded === undefined ? {} : { embedded }), ...(surface === undefined ? {} : { surface }), ...(search === undefined ? {} : { search }), ...(actions === undefined ? {} : { actions }), ...(pagination === undefined ? {} : { pagination }), ...(views === undefined ? {} : { views }), ...(metrics === undefined ? {} : { metrics }), ...(table === undefined
96
+ return (_jsx(ListView, { items: items, keyOf: keyOf, row: rows === undefined
97
+ ? () => ({ name: "" })
98
+ : readOnly
99
+ ? (item) => {
100
+ const { actions: _actions, ...rest } = rows.row(item);
101
+ return rest;
102
+ }
103
+ : rows.row, ...(rows?.header === undefined ? {} : { rowHeader: rows.header }), ...(identity === undefined ? {} : { identity }), ...(crumbs === undefined ? {} : { crumbs }), ...(embedded === undefined ? {} : { embedded }), ...(surface === undefined ? {} : { surface }), ...(search === undefined ? {} : { search }), ...(actions === undefined ? {} : { actions }), ...(pagination === undefined ? {} : { pagination }), ...(views === undefined ? {} : { views }), ...(metrics === undefined ? {} : { metrics }), ...(table === undefined
72
104
  ? {}
73
105
  : {
74
- columns: table.columns,
106
+ /* Read-only: a cell that opens an editor is an action too. */
107
+ columns: readOnly
108
+ ? table.columns.map(({ edit: _edit, ...c }) => c)
109
+ : table.columns,
75
110
  ...(table.totals === undefined ? {} : { totals: table.totals }),
76
- ...(table.rowActions === undefined ? {} : { rowActions: table.rowActions }),
77
- ...(table.create === undefined ? {} : { create: table.create }),
111
+ ...(table.rowActions === undefined || readOnly
112
+ ? {}
113
+ : { rowActions: table.rowActions }),
114
+ ...(table.create === undefined || readOnly ? {} : { create: table.create }),
78
115
  ...(table.visibleColumnIds === undefined
79
116
  ? {}
80
117
  : { visibleColumnIds: table.visibleColumnIds }),
81
118
  ...(table.onVisibleColumnIdsChange === undefined
82
119
  ? {}
83
120
  : { onVisibleColumnIdsChange: table.onVisibleColumnIdsChange }),
84
- }), ...(board?.card === undefined ? {} : { card: board.card }), ...(sheet?.columns === undefined || sheet.cell === undefined
121
+ }), ...(board?.card === undefined ? {} : { card: board.card }), ...(board?.stages === undefined ? {} : { boardStages: board.stages }), ...(sheet?.columns === undefined || sheet.cell === undefined
85
122
  ? {}
86
- : { sheet: sheet }), ...(report === undefined ? {} : { report }), ...(pivot === undefined ? {} : { pivot }), ...(presents.length === 0 ? {} : { presents }), ...(present === undefined ? {} : { present }), ...(onPresentChange === undefined ? {} : { onPresentChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(sortable === undefined ? {} : { sortable }), ...(onOpen === undefined ? {} : { onOpen }), ...(onPeek === undefined ? {} : { onPeek }), ...(isPeeked === undefined ? {} : { isPeeked }), ...(state === undefined ? {} : { state }), ...(filtered === undefined ? {} : { filtered }), ...(empty === undefined ? {} : { empty }), children: rest.length === 0 ? undefined : rest }));
123
+ : { sheet: sheet }), ...(report === undefined ? {} : { report }), ...(pivot === undefined ? {} : { pivot }), ...(calendar === undefined ? {} : { calendar }), ...(gantt === undefined ? {} : { gantt }), ...(timeline === undefined ? {} : { timeline }), ...(presents.length === 0 ? {} : { presents }), ...(present === undefined ? {} : { present }), ...(onPresentChange === undefined ? {} : { onPresentChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(sortable === undefined ? {} : { sortable }), ...(onOpen === undefined ? {} : { onOpen }), ...(onPeek === undefined ? {} : { onPeek }), ...(isPeeked === undefined ? {} : { isPeeked }), ...(rowClick === undefined ? {} : { rowClick }), ...(onChoose === undefined || readOnly ? {} : { onChoose }), ...(state === undefined ? {} : { state }), ...(filtered === undefined ? {} : { filtered }), ...(empty === undefined ? {} : { empty }), children: rest.length === 0 ? undefined : rest }));
87
124
  }
88
125
  List.Bar = Bar;
89
126
  List.Crumbs = Crumbs;
@@ -99,3 +136,6 @@ List.Board = Board;
99
136
  List.Sheet = SheetSlot;
100
137
  List.Report = Report;
101
138
  List.Pivot = Pivot;
139
+ List.Calendar = CalendarSlot;
140
+ List.Gantt = GanttSlot;
141
+ List.Timeline = TimelineSlot;
@@ -1,5 +1,5 @@
1
1
  import type { ListViewProps } from "./types.js";
2
- export declare function ListView<T extends object>({ items, keyOf, identity, crumbs, embedded, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, sheet, report, pivot, present: requested, presents, onPresentChange, sort, onSortChange, sortable, totals, rowActions, create, group, selection, onOpen, onPeek, state, filtered, empty, children, }: ListViewProps<T>): import("react").JSX.Element;
2
+ export declare function ListView<T extends object>({ items, keyOf, identity, crumbs, embedded, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, boardStages, calendar, gantt, timeline, sheet, report, pivot, present: requested, presents, onPresentChange, sort, onSortChange, sortable, totals, rowActions, create, group, selection, onOpen, onPeek, rowClick, onChoose, state, filtered, empty, children, }: ListViewProps<T>): import("react").JSX.Element;
3
3
  /** What a list offers when it fails and can be retried. */
4
4
  export declare function RetryAction({ onRetry }: {
5
5
  onRetry: () => void;
@@ -1,14 +1,19 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { RecordCalendar } from "../../components/calendar/RecordCalendar.js";
4
+ import { RecordGantt } from "../../components/gantt/RecordGantt.js";
5
+ import { BoardColumns } from "../../components/listing/BoardColumns.js";
3
6
  import { useColumnControl, visibleColumns } from "../../components/listing/columns.js";
4
7
  import { ColumnsMenu, specsFromColumns, } from "../../components/listing/ColumnsMenu.js";
5
8
  import { Listing } from "../../components/listing/Listing.js";
6
9
  import { ListingBar } from "../../components/listing/ListingBar.js";
10
+ import { PivotExplorer } from "../../components/listing/PivotExplorer.js";
7
11
  import { RecordCard } from "../../components/listing/RecordCard.js";
8
12
  import { GroupHeader, Row, RowHeader } from "../../components/listing/Row.js";
9
13
  import { sortItems, SortMenu, } from "../../components/listing/SortMenu.js";
10
14
  import { ViewSwitcher } from "../../components/listing/ViewSwitcher.js";
11
15
  import { Breadcrumb } from "../../components/record/Breadcrumb.js";
16
+ import { RecordTimeline } from "../../components/timeline/RecordTimeline.js";
12
17
  import { useUiCopy } from "../../foundations/copy.js";
13
18
  import { useDensity } from "../../foundations/density.js";
14
19
  import { pathKey, pileIsOpen, pilesOf, } from "../../foundations/grouping.js";
@@ -21,7 +26,7 @@ import { groupedColumnIds } from "./columns.js";
21
26
  import { groupLevelsOf, reportColumnsOf, reportRowsOf, sheetFromColumns, sheetRowsOf, } from "./derive.js";
22
27
  import { SheetPresentation, TablePresentation, } from "./presentations.js";
23
28
  import { useNarrowList } from "./useNarrowList.js";
24
- import { Grid3x3, LayoutGrid, List, Sheet, Sigma, Table2 } from "lucide-react";
29
+ import { CalendarDays, CalendarRange, ChartGantt, Grid3x3, LayoutGrid, List, Sheet, Sigma, Table2, } from "lucide-react";
25
30
  import { Fragment, useCallback, useMemo } from "react";
26
31
  /**
27
32
  * EL DIBUJO de una lista. **La puerta es `List`**, que se escribe como árbol y
@@ -57,8 +62,11 @@ const PRESENT_ICONS = [
57
62
  { id: "report", icon: Sigma },
58
63
  /* La cuadrícula: dos ejes, y lo que importa está adentro. */
59
64
  { id: "pivot", icon: Grid3x3 },
65
+ { id: "calendar", icon: CalendarDays },
66
+ { id: "gantt", icon: ChartGantt },
67
+ { id: "timeline", icon: CalendarRange },
60
68
  ];
61
- export function ListView({ items, keyOf, identity, crumbs, embedded = false, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, sheet, report, pivot, present: requested = "rows", presents, onPresentChange, sort, onSortChange, sortable, totals = false, rowActions, create, group, selection, onOpen, onPeek, state, filtered = false, empty, children, }) {
69
+ export function ListView({ items, keyOf, identity, crumbs, embedded = false, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, boardStages, calendar, gantt, timeline, sheet, report, pivot, present: requested = "rows", presents, onPresentChange, sort, onSortChange, sortable, totals = false, rowActions, create, group, selection, onOpen, onPeek, rowClick = "peek", onChoose, state, filtered = false, empty, children, }) {
62
70
  const { frame, offered, present } = useNarrowList(presents, requested);
63
71
  /* Embedded, the list sits on the record's paper: there is no dark band to inherit, and a
64
72
  navy switcher inside a white sheet would be a second chrome on the screen. */
@@ -134,6 +142,7 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
134
142
  /* Embedded, the line is drawn only if it has something to OFFER: a count alone repeats
135
143
  what the rows (and the footer) already say. */
136
144
  const barHasControls = !embedded ||
145
+ identity?.title !== undefined ||
137
146
  search !== undefined ||
138
147
  viewsSlot !== undefined ||
139
148
  actions !== undefined ||
@@ -150,12 +159,26 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
150
159
  count: selection.count,
151
160
  onClear: selection.clear,
152
161
  ...(selection.actions === undefined ? {} : { actions: selection.actions }),
162
+ ...(selection.matching === undefined ? {} : { matching: selection.matching }),
153
163
  },
154
164
  }), ...(search === undefined ? {} : { search }), ...(pagination === undefined ? {} : { pagination }), ...(viewsSlot === undefined ? {} : { views: viewsSlot }) }));
165
+ /* The body of a row, a card: the look first unless the list says the page. */
166
+ /* Choosing, the press chooses and the ways in (👁 ↗) are not offered: a chooser's row answers. */
167
+ const choosing = onChoose !== undefined;
168
+ const press = choosing
169
+ ? onChoose
170
+ : rowClick === "open"
171
+ ? (onOpen ?? onPeek)
172
+ : (onPeek ?? onOpen);
155
173
  const renderRow = (item) => {
156
174
  const d = row(item);
157
175
  const id = keyOf(item);
158
- return (_jsx(Row, { ...(d.icon === undefined ? {} : { icon: d.icon }), name: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(d.actions === undefined ? {} : { actions: d.actions }), ...(onOpen === undefined ? {} : { onOpen: () => onOpen(item) }), ...(onPeek === undefined ? {} : { onPeek: () => onPeek(item) }), ...(selection === undefined
176
+ return (_jsx(Row, { ...(d.icon === undefined ? {} : { icon: d.icon }), ...(d.avatar === undefined ? {} : { avatar: d.avatar }), name: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(d.actions === undefined ? {} : { actions: d.actions }), ...(choosing
177
+ ? { onOpen: () => onChoose(item), openers: false }
178
+ : {
179
+ ...(onOpen === undefined ? {} : { onOpen: () => onOpen(item) }),
180
+ ...(onPeek === undefined ? {} : { onPeek: () => onPeek(item) }),
181
+ }), click: rowClick, ...(selection === undefined
159
182
  ? {}
160
183
  : { selected: selection.has(id), onSelectedChange: () => selection.toggle(id) }), children: d.children }, id));
161
184
  };
@@ -215,7 +238,7 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
215
238
  return list.map((pile) => {
216
239
  const key = pathKey(pile.path);
217
240
  const open = isOpen(pile);
218
- return (_jsxs(Fragment, { children: [_jsx(GroupHeader, { field: pile.field, label: pile.label, count: pile.count, depth: pile.depth, open: open, ...(group?.onToggle === undefined
241
+ return (_jsxs(Fragment, { children: [_jsx(GroupHeader, { field: pile.field, label: pile.label, count: pile.count, ...(pile.detail === undefined ? {} : { detail: pile.detail }), depth: pile.depth, open: open, ...(group?.onToggle === undefined
219
242
  ? {}
220
243
  : { onToggle: () => group.onToggle?.(key) }) }), !open
221
244
  ? null
@@ -235,11 +258,11 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
235
258
  return _jsx(Fragment, { children: card(item) }, keyOf(item));
236
259
  }
237
260
  const d = row(item);
238
- return (_jsx(RecordCard, { title: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(onOpen === undefined ? {} : { onOpen: () => onOpen(item) }) }, keyOf(item)));
261
+ return (_jsx(RecordCard, { title: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(press === undefined ? {} : { onOpen: () => press(item) }) }, keyOf(item)));
239
262
  };
240
263
  const body = present === "table" && shownColumns !== undefined ? (
241
264
  /* The table receives EVERYTHING that belongs to the set: see `TablePresentation`. */
242
- _jsx(TablePresentation, { items: items, columns: shownColumns, keyOf: keyOf, levels: groupLevels, ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), totals: totals, ...(rowActions === undefined ? {} : { rowActions }), ...(create === undefined ? {} : { create }), ...(onPeek === undefined ? {} : { onPeek }), ...(onOpen === undefined ? {} : { onOpen }) })) : present === "sheet" && sheetSpec !== undefined ? (
265
+ _jsx(TablePresentation, { items: items, columns: shownColumns, keyOf: keyOf, levels: groupLevels, ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), totals: totals, ...(rowActions === undefined ? {} : { rowActions }), ...(create === undefined ? {} : { create }), ...(onPeek === undefined || choosing ? {} : { onPeek }), ...(onOpen === undefined || choosing ? {} : { onOpen }), ...(choosing ? { onChoose } : {}), rowClick: rowClick })) : present === "sheet" && sheetSpec !== undefined ? (
243
266
  /* La planilla recibe el conjunto por ÍNDICE y no una matriz ya armada: ver `SheetPresentation`. */
244
267
  _jsx(SheetPresentation, { spec: sheetSpec, entries: sheetRows, ordered: ordered, ...(group === undefined ? {} : { group }) })) : present === "report" && report !== undefined ? (
245
268
  /* El informe declarado por el dominio no se ORDENA desde la barra: el
@@ -255,20 +278,28 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
255
278
  are written. Declared by the domain, the columns are the domain's. */
256
279
  columns: report.rows === undefined
257
280
  ? reportColumnsOf(report.columns, columns)
258
- : report.columns, rows: report.rows ?? reportRows, ...(report.defaultOpen === undefined ? {} : { defaultOpen: report.defaultOpen }), ...(report.height === undefined ? {} : { height: report.height }) })) : present === "pivot" && pivotShown !== undefined ? (
281
+ : report.columns, rows: report.rows ?? reportRows, ...(report.defaultOpen === undefined ? {} : { defaultOpen: report.defaultOpen }), ...(report.height === undefined ? {} : { height: report.height }) })) : present === "pivot" && pivot?.spec !== undefined && pivot.fields !== undefined ? (
282
+ /* Explored: the person moves the axes. Its rows follow the search bar's grouping. */
283
+ _jsx("div", { className: "p-3", children: _jsx(PivotExplorer, { items: ordered, of: pivot.spec.of, fields: pivot.fields, measures: pivot.spec.measures, ...(pivot.defaultMeasures === undefined
284
+ ? {}
285
+ : { defaultMeasures: pivot.defaultMeasures }), ...((group?.levels.length ?? 0) > 0 ? { rows: group.levels } : {}), defaultRows: pivot.spec.rows, defaultColumns: pivot.spec.columns, ...(pivot.onCell === undefined ? {} : { onCell: pivot.onCell }), ...(pivot.height === undefined ? {} : { height: pivot.height }) }) })) : present === "pivot" && pivotShown !== undefined ? (
259
286
  /* El pivote recibe la grilla y nada más: no hay selección que pasarle
260
287
  —una casilla por montón elegiría cosas que no son registros—, no hay
261
288
  orden —cada eje viene en el orden de sus claves— y no hay menú de
262
289
  columnas, porque las columnas son un eje y no una lista de campos que
263
290
  se puedan esconder de a uno. */
264
- _jsx(PivotTable, { pivot: pivotShown, ...(pivot?.onCell === undefined ? {} : { onCell: pivot.onCell }), ...(pivot?.height === undefined ? {} : { height: pivot.height }) })) : present === "board" ? (_jsx("div", { className: "flex flex-col", children: wrapGroups((inside) => (_jsx("div", { className: "grid gap-3 p-3 @2xl:grid-cols-2 @5xl:grid-cols-3", children: inside.map(renderCard) }))) })) : (
291
+ _jsx(PivotTable, { pivot: pivotShown, ...(pivot?.onCell === undefined ? {} : { onCell: pivot.onCell }), ...(pivot?.height === undefined ? {} : { height: pivot.height }) })) : present === "calendar" && calendar !== undefined ? (_jsx("div", { className: "p-3", children: _jsx(RecordCalendar, { ...calendar, items: ordered, keyOf: keyOf, ...(press === undefined ? {} : { onOpen: press }) }) })) : present === "gantt" && gantt !== undefined ? (_jsx("div", { className: "p-3", children: _jsx(RecordGantt, { ...gantt, items: ordered, keyOf: keyOf, ...(press === undefined ? {} : { onOpen: press }) }) })) : present === "timeline" && timeline !== undefined ? (_jsx("div", { className: "p-3", children: _jsx(RecordTimeline, { ...timeline, items: ordered, keyOf: keyOf, ...(press === undefined ? {} : { onOpen: press }) }) })) : present === "board" && boardStages !== undefined ? (_jsx(BoardColumns, { items: ordered, keyOf: keyOf, stages: boardStages, card: renderCard })) : present === "board" ? (_jsx("div", { className: "flex flex-col", children: wrapGroups((inside) => (_jsx("div", { className: "grid gap-3 p-3 @2xl:grid-cols-2 @5xl:grid-cols-3", children: inside.map(renderCard) }))) })) : (
265
292
  /* El encabezado va ANTES de los montones y no adentro de cada uno:
266
293
  rotula la fila, y la fila es la misma en los cinco montones. */
267
294
  _jsxs(_Fragment, { children: [rowHeader === undefined || items.length === 0 ? null : (_jsx(RowHeader, { name: rowHeader.name, ...(rowHeader.meta === undefined ? {} : { meta: rowHeader.meta }), ...(rowHeader.tags === undefined ? {} : { tags: rowHeader.tags }), withSelection: selection !== undefined,
268
295
  /* Los espaciadores se preguntan por la PRIMERA fila: el ícono y las
269
296
  acciones son de la lista entera, no de una fila — si la primera no
270
297
  tiene ícono, ninguna lo tiene. */
271
- withIcon: firstRow?.icon !== undefined, withActions: onOpen !== undefined || firstRow?.actions !== undefined })), wrapGroups((inside) => inside.map(renderRow))] }));
298
+ withIcon: firstRow?.icon !== undefined, withAvatar: firstRow?.avatar !== undefined, trail: {
299
+ ...(firstRow?.actions === undefined ? {} : { actions: firstRow.actions }),
300
+ peek: onPeek !== undefined && !choosing,
301
+ open: onOpen !== undefined && !choosing,
302
+ } })), wrapGroups((inside) => inside.map(renderRow))] }));
272
303
  return (
273
304
  /* The list is its own container (`@container`: the presentations adapt to the width
274
305
  the LIST has, not the window) and fills the space it is given. Before, every page
@@ -16,7 +16,14 @@ export function groupedColumnIds(group, columns) {
16
16
  .filter((id) => id !== undefined);
17
17
  }
18
18
  /** The presentations that need width: a grid of columns. */
19
- export const WIDE_PRESENTS = ["table", "sheet", "report", "pivot"];
19
+ export const WIDE_PRESENTS = [
20
+ "table",
21
+ "sheet",
22
+ "report",
23
+ "pivot",
24
+ "gantt",
25
+ "timeline",
26
+ ];
20
27
  /** The id of the column that answers for a model field, when there is one. */
21
28
  function columnAnswering(field, columns) {
22
29
  if (columns === undefined)
@@ -15,11 +15,14 @@ type TableProps<T extends object> = {
15
15
  levels: NonNullable<Parameters<typeof DataTable<T>>[0]["levels"]>;
16
16
  group?: ListGroup<T>;
17
17
  totals: boolean;
18
- } & Pick<ListViewProps<T>, "sort" | "onSortChange" | "selection" | "rowActions" | "create" | "onPeek" | "onOpen">;
18
+ } & Pick<ListViewProps<T>, "sort" | "onSortChange" | "selection" | "rowActions" | "create" | "onPeek" | "onOpen"> & {
19
+ rowClick?: "peek" | "open";
20
+ onChoose?: (item: T) => void;
21
+ };
19
22
  /** The table receives EVERYTHING that belongs to the set —sorting, selection, grouping,
20
23
  * which columns are on screen— and not a trimmed down version: that was exactly the trap
21
24
  * this archetype exists to avoid, that switching presentation loses capabilities. */
22
- export declare function TablePresentation<T extends object>({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, }: TableProps<T>): import("react").JSX.Element;
25
+ export declare function TablePresentation<T extends object>({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, rowClick, onChoose, }: TableProps<T>): import("react").JSX.Element;
23
26
  type SheetProps<T extends object> = {
24
27
  spec: ListSheet<T>;
25
28
  /** The flattened bands and rows when the set is grouped; `null` when it is not. */
@@ -3,10 +3,40 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useUiCopy } from "../../foundations/copy.js";
4
4
  import { DataTable } from "../../primitives/table/DataTable.js";
5
5
  import { SheetTable } from "../../primitives/table/SheetTable.js";
6
+ import { Eye, SquareArrowOutUpRight } from "lucide-react";
6
7
  /** The table receives EVERYTHING that belongs to the set —sorting, selection, grouping,
7
8
  * which columns are on screen— and not a trimmed down version: that was exactly the trap
8
9
  * this archetype exists to avoid, that switching presentation loses capabilities. */
9
- export function TablePresentation({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, }) {
10
+ export function TablePresentation({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, rowClick = "peek", onChoose, }) {
11
+ const copy = useUiCopy();
12
+ const press = onChoose ?? (rowClick === "open" ? (onOpen ?? onPeek) : (onPeek ?? onOpen));
13
+ /* The row's ways in go FIRST among its actions, so they are always the inline ones and
14
+ always in the same place; the domain's actions follow (and fold into ⋯ past two). */
15
+ const withOpeners = onPeek === undefined && onOpen === undefined
16
+ ? rowActions
17
+ : (item) => [
18
+ ...(onPeek === undefined
19
+ ? []
20
+ : [
21
+ {
22
+ id: "peek",
23
+ label: copy.list.peek,
24
+ icon: Eye,
25
+ onSelect: () => onPeek(item),
26
+ },
27
+ ]),
28
+ ...(onOpen === undefined
29
+ ? []
30
+ : [
31
+ {
32
+ id: "open",
33
+ label: copy.list.open,
34
+ icon: SquareArrowOutUpRight,
35
+ onSelect: () => onOpen(item),
36
+ },
37
+ ]),
38
+ ...(rowActions?.(item) ?? []),
39
+ ];
10
40
  return (_jsx(DataTable, { data: items, columns: columns, getRowId: keyOf, ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(levels.length === 0 ? {} : { levels: levels }), ...(group?.collapsed === undefined ? {} : { collapsed: group.collapsed }), ...(group?.onToggle === undefined ? {} : { onGroupToggle: group.onToggle }), ...(selection === undefined
11
41
  ? {}
12
42
  : {
@@ -22,7 +52,7 @@ export function TablePresentation({ items, columns, keyOf, levels, group, totals
22
52
  selection.toggle(id);
23
53
  }
24
54
  },
25
- }), ...(totals ? { totals: true } : {}), ...(rowActions === undefined ? {} : { rowActions }), ...(create === undefined ? {} : { create }), ...((onPeek ?? onOpen) === undefined ? {} : { onRowClick: onPeek ?? onOpen }) }));
55
+ }), ...(totals ? { totals: true } : {}), ...(withOpeners === undefined ? {} : { rowActions: withOpeners }), ...(create === undefined ? {} : { create }), ...(press === undefined ? {} : { onRowClick: press }) }));
26
56
  }
27
57
  /** The sheet receives the set by INDEX and not a ready-made matrix: `SheetTable` is
28
58
  * virtualized precisely so as not to materialize a million rows, and handing it copies
@@ -1,6 +1,11 @@
1
+ import type { RecordCalendarProps } from "../../components/calendar/RecordCalendar.js";
2
+ import type { RecordGanttProps } from "../../components/gantt/RecordGantt.js";
3
+ import type { BoardStages } from "../../components/listing/BoardColumns.js";
1
4
  import type { ListIdentity } from "../../components/listing/ListingBar.js";
2
5
  import type { ListPresent } from "../../components/listing/set.js";
3
6
  import type { SortField } from "../../components/listing/SortMenu.js";
7
+ import type { RecordTimelineProps } from "../../components/timeline/RecordTimeline.js";
8
+ import type { GroupableField } from "../../foundations/grouping.js";
4
9
  import type { Grouping, GroupLevel } from "../../foundations/grouping.js";
5
10
  import type { Pivot, PivotKey, PivotSpec } from "../../foundations/pivot.js";
6
11
  import type { Surface, Tone } from "../../foundations/types.js";
@@ -23,6 +28,11 @@ export type { ListPresent };
23
28
  /** What an item looks like when the presentation is rows. */
24
29
  export type RowOf = {
25
30
  icon?: LucideIcon;
31
+ /** A person or a company: its face in the icon's place — the photo or the initials. */
32
+ avatar?: {
33
+ name: string;
34
+ src?: string;
35
+ };
26
36
  name: string;
27
37
  tags?: ReactNode;
28
38
  meta?: string;
@@ -153,6 +163,12 @@ export type ListPivot<T> = {
153
163
  /** Qué pasa al tocar un cruce: abrir las filas que cayeron ahí. */
154
164
  onCell?: (row: PivotKey, column: PivotKey) => void;
155
165
  height?: number | string;
166
+ /** The fields the person may pivot by. With them (and `spec`) the pivot is EXPLORED — axes,
167
+ * measures and download on the grid (`PivotExplorer`) — and its rows follow the search
168
+ * bar's grouping while there is one; `spec.rows` / `spec.columns` are where it starts. */
169
+ fields?: readonly GroupableField[];
170
+ /** Which of `spec.measures` start on. The first one by default. */
171
+ defaultMeasures?: readonly string[];
156
172
  };
157
173
  export type ListViewProps<T extends object> = {
158
174
  items: readonly T[];
@@ -226,6 +242,14 @@ export type ListViewProps<T extends object> = {
226
242
  create?: DataTableCreate<T>;
227
243
  /** The board presentation. */
228
244
  card?: (item: T) => ReactNode;
245
+ /** The board by stages: one column each, cards moved between them. */
246
+ boardStages?: BoardStages<T>;
247
+ /** The calendar presentation: when each record happens. */
248
+ calendar?: ListCalendar<T>;
249
+ /** The Gantt presentation: each record as a span on a timeline. */
250
+ gantt?: ListGantt<T>;
251
+ /** The planning presentation: records grouped in rows across time (Odoo's Gantt). */
252
+ timeline?: ListTimeline<T>;
229
253
  /**
230
254
  * La PLANILLA. Con esto, `present="sheet"` dibuja un `SheetTable`.
231
255
  *
@@ -266,10 +290,24 @@ export type ListViewProps<T extends object> = {
266
290
  count: number;
267
291
  clear: () => void;
268
292
  actions?: ReactNode;
293
+ /** Every record that matches, not just the page: «Elegir los 187 que coinciden». The total
294
+ * is the server's; `onSelect` makes the selection mean «all of them». */
295
+ matching?: {
296
+ total: number;
297
+ onSelect: () => void;
298
+ };
269
299
  };
270
300
  onOpen?: (item: T) => void;
271
301
  onPeek?: (item: T) => void;
272
302
  isPeeked?: (item: T) => boolean;
303
+ /** What a press on the row's body does when both ways in exist. `peek` (the default) looks
304
+ * without leaving the list; `open` goes to the record — a workspace whose rule is «a row
305
+ * opens the page» (the composer's) says `open`, and 👁 stays for the look. */
306
+ rowClick?: "peek" | "open";
307
+ /** The list CHOOSES instead of opening — a «Buscar más…», a chooser a workspace asked for:
308
+ * a press on a row (its body, its name, a card) calls this, and the row offers no 👁 / ↗.
309
+ * For several at once, give `selection` too and toggle it here. */
310
+ onChoose?: (item: T) => void;
273
311
  /** The three states, in a single place. Every view used to re-assemble them
274
312
  * with three nested ternaries, and that is why «empty because of a filter»
275
313
  * and «really empty» got mixed up in half the screens. */
@@ -282,3 +320,9 @@ export type ListViewProps<T extends object> = {
282
320
  /** The escape hatch. */
283
321
  children?: ReactNode;
284
322
  };
323
+ /** `List.Calendar`: the calendar's own props; the records, their key and «open» are the list's. */
324
+ export type ListCalendar<T> = Omit<RecordCalendarProps<T>, "items" | "keyOf" | "onOpen">;
325
+ /** `List.Gantt`: the chart's own props; the records, their key and «open» are the list's. */
326
+ export type ListGantt<T> = Omit<RecordGanttProps<T>, "items" | "keyOf" | "onOpen">;
327
+ /** `List.Timeline`: the planning's own props; the records, their key and «open» are the list's. */
328
+ export type ListTimeline<T> = Omit<RecordTimelineProps<T>, "items" | "keyOf" | "onOpen">;
@@ -9,9 +9,19 @@ import { type RefObject } from "react";
9
9
  *
10
10
  * Attach `frame` to the list's root element; `offered` is what the switcher may show and
11
11
  * `present` what to draw (a wide one that was asked for falls back to rows).
12
+ *
13
+ * Measured BEFORE paint: waiting for the observer's first report drew the table and swapped it
14
+ * for rows a frame later, so a click on a row of a list in a panel landed on a detached `<tr>`.
12
15
  */
13
16
  export declare function useNarrowList(presents: readonly ListPresent[] | undefined, requested: ListPresent): {
14
17
  frame: RefObject<HTMLDivElement | null>;
15
18
  offered: readonly ListPresent[];
16
19
  present: ListPresent;
17
20
  };
21
+ /** What a list offers and draws at its width. Only a list that HAS rows folds into them: one
22
+ * that declares just a table fell back to rows it had no `row` for, and drew empty lines — a
23
+ * table that scrolls sideways says more. */
24
+ export declare function narrowed(presents: readonly ListPresent[] | undefined, requested: ListPresent, narrow: boolean): {
25
+ offered: readonly ListPresent[];
26
+ present: ListPresent;
27
+ };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { HANDHELD_WIDTH } from "../../foundations/layout.js";
3
3
  import { WIDE_PRESENTS } from "./columns.js";
4
- import { useEffect, useRef, useState } from "react";
4
+ import { useLayoutEffect, useRef, useState } from "react";
5
5
  /**
6
6
  * A narrow LIST — a phone, a drawer, a panel among four — does not offer the presentations
7
7
  * that need width: a seven-column table at 360 px is a horizontal scroll nobody reads.
@@ -11,13 +11,19 @@ import { useEffect, useRef, useState } from "react";
11
11
  *
12
12
  * Attach `frame` to the list's root element; `offered` is what the switcher may show and
13
13
  * `present` what to draw (a wide one that was asked for falls back to rows).
14
+ *
15
+ * Measured BEFORE paint: waiting for the observer's first report drew the table and swapped it
16
+ * for rows a frame later, so a click on a row of a list in a panel landed on a detached `<tr>`.
14
17
  */
15
18
  export function useNarrowList(presents, requested) {
16
19
  const frame = useRef(null);
17
20
  const [narrow, setNarrow] = useState(false);
18
- useEffect(() => {
21
+ useLayoutEffect(() => {
19
22
  const el = frame.current;
20
- if (el === null || typeof ResizeObserver === "undefined")
23
+ if (el === null)
24
+ return;
25
+ setNarrow(el.getBoundingClientRect().width < HANDHELD_WIDTH);
26
+ if (typeof ResizeObserver === "undefined")
21
27
  return;
22
28
  const watch = new ResizeObserver(([entry]) => {
23
29
  if (entry !== undefined)
@@ -26,7 +32,15 @@ export function useNarrowList(presents, requested) {
26
32
  watch.observe(el);
27
33
  return () => watch.disconnect();
28
34
  }, []);
29
- const offered = (presents ?? []).filter((p) => !narrow || !WIDE_PRESENTS.includes(p));
30
- const present = narrow && WIDE_PRESENTS.includes(requested) ? "rows" : requested;
31
- return { frame, offered, present };
35
+ return { frame, ...narrowed(presents, requested, narrow) };
36
+ }
37
+ /** What a list offers and draws at its width. Only a list that HAS rows folds into them: one
38
+ * that declares just a table fell back to rows it had no `row` for, and drew empty lines — a
39
+ * table that scrolls sideways says more. */
40
+ export function narrowed(presents, requested, narrow) {
41
+ const folds = narrow && (presents === undefined || presents.includes("rows"));
42
+ return {
43
+ offered: (presents ?? []).filter((p) => !folds || !WIDE_PRESENTS.includes(p)),
44
+ present: folds && WIDE_PRESENTS.includes(requested) ? "rows" : requested,
45
+ };
32
46
  }
@@ -2,6 +2,7 @@ import { type ActivityProps } from "../../components/activity/Activity.js";
2
2
  import { type SheetField, type SheetLayout } from "../../components/form/Sheet.js";
3
3
  import type { Crumb } from "../../components/record/Breadcrumb.js";
4
4
  import type { DirtyState } from "../../components/record/DirtyActions.js";
5
+ import { type RecordStamp } from "../../components/record/RecordAudit.js";
5
6
  import { type RecordNoticeSpec } from "../../components/record/RecordNotices.js";
6
7
  import { RecordSkeleton } from "../../components/record/RecordSkeleton.js";
7
8
  import { type Stage } from "../../components/record/StatusBar.js";
@@ -68,6 +69,8 @@ type StatusProps = {
68
69
  /** Cuál de las etapas es la de ahora. Sin esto, la primera. */
69
70
  current?: string;
70
71
  failed?: boolean;
72
+ /** The stages can be chosen: pressing one moves the record there (a CRM's pipeline). */
73
+ onSelect?: (id: string) => void;
71
74
  /** La escotilla del estado, para lo que no es ninguna de las dos. */
72
75
  children?: ReactNode;
73
76
  };
@@ -76,6 +79,9 @@ type PaperProps = {
76
79
  * valor, que es la forma de Odoo. */
77
80
  layout?: SheetLayout;
78
81
  columns?: 1 | 2 | 3 | 4;
82
+ /** `reading` (default): the sheet stops at a reading width, centred, with the rail lined
83
+ * up to it. `full`: the whole page, for a record whose body is mostly a wide table. */
84
+ width?: "reading" | "full";
79
85
  children?: ReactNode;
80
86
  };
81
87
  type FactsProps = {
@@ -142,4 +148,12 @@ export declare namespace Record {
142
148
  var Pages: ((props: PagesProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"pages">;
143
149
  var Page: ((props: PageProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"page">;
144
150
  var Notice: ((props: RecordNoticeSpec) => ReactNode) & import("../../foundations/slots.js").Slotted<"notice">;
151
+ var Ribbon: ((props: {
152
+ label: string;
153
+ tone?: Tone;
154
+ }) => ReactNode) & import("../../foundations/slots.js").Slotted<"ribbon">;
155
+ var Audit: ((props: {
156
+ created?: RecordStamp;
157
+ updated?: RecordStamp;
158
+ }) => ReactNode) & import("../../foundations/slots.js").Slotted<"audit">;
145
159
  }