@sincpro/web-ui 0.1.0 → 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.
- package/README.md +37 -10
- package/dist/components/activity/Activity.d.ts +69 -0
- package/dist/components/activity/Activity.js +279 -0
- package/dist/components/{chatter → activity}/Messages.d.ts +2 -2
- package/dist/components/{chatter → activity}/Messages.js +9 -6
- package/dist/components/{chatter → activity}/index.d.ts +1 -1
- package/dist/components/activity/index.js +4 -0
- package/dist/components/assist/AssistMark.d.ts +3 -3
- package/dist/components/assist/AssistMark.js +1 -1
- package/dist/components/assist/assist.d.ts +1 -1
- package/dist/components/assist/assist.js +1 -1
- package/dist/components/form/Sheet.d.ts +4 -0
- package/dist/components/form/Sheet.js +11 -4
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/lines/LineItems.d.ts +42 -5
- package/dist/components/lines/LineItems.js +106 -18
- package/dist/components/listing/Listing.js +1 -1
- package/dist/components/listing/ListingBar.d.ts +41 -2
- package/dist/components/listing/ListingBar.js +43 -4
- package/dist/components/listing/Row.d.ts +5 -1
- package/dist/components/listing/Row.js +3 -2
- package/dist/components/record/RecordHeader.d.ts +21 -17
- package/dist/components/record/RecordHeader.js +46 -14
- package/dist/components/record/RecordNotices.d.ts +37 -0
- package/dist/components/record/RecordNotices.js +75 -0
- package/dist/components/record/ThumbBar.js +1 -1
- package/dist/foundations/copy.d.ts +96 -12
- package/dist/foundations/copy.es.js +63 -10
- package/dist/foundations/copy.js +55 -7
- package/dist/primitives/choice/ComboBox.d.ts +3 -0
- package/dist/primitives/choice/ComboBox.js +2 -2
- package/dist/primitives/choice/SearchField.d.ts +10 -1
- package/dist/primitives/choice/SearchField.js +21 -2
- package/dist/primitives/date/Calendar.js +30 -6
- package/dist/primitives/date/DatePicker.d.ts +11 -3
- package/dist/primitives/date/DatePicker.js +12 -8
- package/dist/primitives/date/DateTime.d.ts +35 -0
- package/dist/primitives/date/DateTime.js +100 -0
- package/dist/primitives/date/index.d.ts +1 -0
- package/dist/primitives/date/index.js +4 -2
- package/dist/primitives/dialog/Drill.d.ts +90 -0
- package/dist/primitives/dialog/Drill.js +172 -0
- package/dist/primitives/dialog/index.d.ts +1 -0
- package/dist/primitives/dialog/index.js +2 -0
- package/dist/primitives/field/DropZone.js +1 -1
- package/dist/primitives/field/Field.d.ts +14 -2
- package/dist/primitives/field/Field.js +25 -14
- package/dist/primitives/field/copy.d.ts +16 -0
- package/dist/primitives/field/copy.js +69 -0
- package/dist/primitives/field/index.d.ts +2 -0
- package/dist/primitives/field/index.js +4 -0
- package/dist/primitives/field/note.d.ts +55 -0
- package/dist/primitives/field/note.js +117 -0
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/menu/Menu.d.ts +28 -0
- package/dist/primitives/menu/Menu.js +47 -17
- package/dist/primitives/notice/Callout.js +1 -1
- package/dist/primitives/notice/Flag.d.ts +13 -0
- package/dist/primitives/notice/Flag.js +31 -0
- package/dist/primitives/notice/index.d.ts +1 -0
- package/dist/primitives/notice/index.js +1 -0
- package/dist/primitives/overlay/Preview.d.ts +19 -0
- package/dist/primitives/overlay/Preview.js +60 -0
- package/dist/primitives/overlay/index.d.ts +1 -0
- package/dist/primitives/overlay/index.js +3 -1
- package/dist/primitives/picker/Picker.d.ts +41 -0
- package/dist/primitives/picker/Picker.js +62 -8
- package/dist/primitives/table/CellCopy.d.ts +20 -0
- package/dist/primitives/table/CellCopy.js +23 -0
- package/dist/primitives/table/DataTable.d.ts +55 -1
- package/dist/primitives/table/DataTable.js +126 -40
- package/dist/primitives/table/EditableTable.d.ts +97 -2
- package/dist/primitives/table/EditableTable.js +223 -29
- package/dist/primitives/table/SheetTable.d.ts +4 -1
- package/dist/primitives/table/SheetTable.js +38 -5
- package/dist/primitives/table/index.d.ts +2 -2
- package/dist/primitives/table/index.js +1 -1
- package/dist/primitives/tabs/Tabs.d.ts +2 -2
- package/dist/primitives/tabs/Tabs.js +1 -1
- package/dist/primitives/tag/TagsField.d.ts +3 -0
- package/dist/primitives/tag/TagsField.js +6 -2
- package/dist/primitives/tree/Tree.d.ts +56 -0
- package/dist/primitives/tree/Tree.js +74 -0
- package/dist/primitives/tree/index.d.ts +1 -0
- package/dist/primitives/tree/index.js +3 -0
- package/dist/primitives/value/Copyable.d.ts +11 -0
- package/dist/primitives/value/Copyable.js +27 -6
- package/dist/primitives/value/Fecha.js +10 -1
- package/dist/primitives/value/ProgressBar.d.ts +31 -0
- package/dist/primitives/value/ProgressBar.js +53 -0
- package/dist/primitives/value/Relation.d.ts +6 -1
- package/dist/primitives/value/Relation.js +27 -10
- package/dist/primitives/value/Totals.d.ts +102 -7
- package/dist/primitives/value/Totals.js +52 -2
- package/dist/primitives/value/Value.d.ts +0 -37
- package/dist/primitives/value/Value.js +0 -39
- package/dist/primitives/value/index.d.ts +4 -3
- package/dist/primitives/value/index.js +4 -3
- package/dist/tokens.css +44 -7
- package/dist/views/list/List.d.ts +8 -2
- package/dist/views/list/List.js +6 -2
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +34 -8
- package/dist/views/list/derive.d.ts +12 -1
- package/dist/views/list/derive.js +29 -3
- package/dist/views/list/presentations.d.ts +2 -2
- package/dist/views/list/presentations.js +5 -3
- package/dist/views/list/types.d.ts +13 -1
- package/dist/views/record/Record.d.ts +27 -8
- package/dist/views/record/Record.js +97 -27
- package/dist/views/record/index.d.ts +1 -1
- package/package.json +5 -1
- package/dist/components/chatter/Chatter.d.ts +0 -161
- package/dist/components/chatter/Chatter.js +0 -232
- package/dist/components/chatter/index.js +0 -4
- /package/dist/components/{chatter → activity}/Timeline.d.ts +0 -0
- /package/dist/components/{chatter → activity}/Timeline.js +0 -0
package/dist/components/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* COMPONENTS — primitives composed to answer ONE question of the product, by family. */
|
|
2
|
+
export * from "./activity/index.js";
|
|
2
3
|
export * from "./assist/index.js";
|
|
3
|
-
export * from "./chatter/index.js";
|
|
4
4
|
export * from "./form/index.js";
|
|
5
5
|
export * from "./frame/index.js";
|
|
6
6
|
export * from "./lines/index.js";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type DialogContext } from "../../primitives/dialog/Dialog.js";
|
|
2
2
|
import { type FormMode } from "../../primitives/field/mode.js";
|
|
3
|
+
import type { FlagTone } from "../../primitives/notice/Flag.js";
|
|
3
4
|
import type { DataColumn } from "../../primitives/table/DataTable.js";
|
|
4
5
|
import { type EditColumn, type EditLayout } from "../../primitives/table/EditableTable.js";
|
|
5
|
-
import { type TotalsRow } from "../../primitives/value/Totals.js";
|
|
6
|
+
import { type TotalsProps, type TotalsRow } from "../../primitives/value/Totals.js";
|
|
6
7
|
import { type ReactNode } from "react";
|
|
7
8
|
/**
|
|
8
9
|
* A column of the lines. The same contract as `DataTable`'s columns —`value` is
|
|
@@ -10,11 +11,25 @@ import { type ReactNode } from "react";
|
|
|
10
11
|
* reads as plain text— plus `edit`, the control that replaces the cell when the
|
|
11
12
|
* row is being edited in place.
|
|
12
13
|
*/
|
|
13
|
-
export type LineItemColumn<T> = Pick<DataColumn<T>, "id" | "header" | "value" | "cell" | "text" | "numeric" | "width"> & Pick<EditColumn<T>, "stack" | "brief"> & {
|
|
14
|
+
export type LineItemColumn<T> = Pick<DataColumn<T>, "id" | "header" | "value" | "cell" | "text" | "numeric" | "width" | "optional" | "defaultHidden"> & Pick<EditColumn<T>, "stack" | "brief"> & {
|
|
14
15
|
/** The control while the row is edited in place. Without it the cell stays read
|
|
15
16
|
* only — a computed amount, a code. Only simple values belong here; the whole
|
|
16
17
|
* line is edited in the `editor`. */
|
|
17
18
|
edit?: (line: T, set: (next: T) => void) => ReactNode;
|
|
19
|
+
/** What the lines of a section add up to in this column, already formatted. Only
|
|
20
|
+
* with `structure`; without it the section says nothing in this column. */
|
|
21
|
+
subtotal?: (lines: readonly T[]) => ReactNode;
|
|
22
|
+
};
|
|
23
|
+
/** Which lines are sections or notes, and how their text is read and written. See
|
|
24
|
+
* «Sections and notes» in the file's header. */
|
|
25
|
+
export type LineStructure<T> = {
|
|
26
|
+
kind: (line: T) => "line" | "section" | "note";
|
|
27
|
+
text: (line: T) => string;
|
|
28
|
+
withText: (line: T, text: string) => T;
|
|
29
|
+
/** A blank section; enables «Agregar una sección». */
|
|
30
|
+
newSection?: () => T;
|
|
31
|
+
/** A blank note; enables «Agregar una nota». */
|
|
32
|
+
newNote?: () => T;
|
|
18
33
|
};
|
|
19
34
|
/** What the popup editor receives besides the draft. */
|
|
20
35
|
export type LineEditorState = {
|
|
@@ -56,11 +71,33 @@ export type LineItemsProps<T> = {
|
|
|
56
71
|
/** The search shows up only with MORE lines than this. Four lines are read, not
|
|
57
72
|
* searched; a search box over them is a control that answers nothing. */
|
|
58
73
|
searchFrom?: number;
|
|
59
|
-
/** The money summary under the lines, on the right: the system's `Totals`.
|
|
60
|
-
|
|
74
|
+
/** The money summary under the lines, on the right: the system's `Totals`. Its rows,
|
|
75
|
+
* or all of it — payments deducted, the balance due, the credits that can be applied. */
|
|
76
|
+
totals?: readonly TotalsRow[] | TotalsProps;
|
|
61
77
|
/** `auto` (the default) draws the lines as stacked cards below
|
|
62
78
|
* `--container-handheld`; `table` and `stacked` force one. */
|
|
63
79
|
layout?: EditLayout;
|
|
80
|
+
/** How «Agregar una línea» adds: `editor` opens the blank line in the dialog;
|
|
81
|
+
* `inline` puts it in the table, open, under the others (see the file's header).
|
|
82
|
+
* By default the dialog when there is an `editor`, the table when there is not. */
|
|
83
|
+
adding?: "inline" | "editor";
|
|
84
|
+
/** Open the whole line somewhere else than this block's dialog — the app's `Drill`, so
|
|
85
|
+
* the line opens as «S00142 › Línea 3» and can go deeper (its product, its lot) without
|
|
86
|
+
* a modal over a modal. When given, it replaces `editor` for existing lines. */
|
|
87
|
+
onOpenLine?: (id: string) => void;
|
|
88
|
+
/** A warning about one line, on the line: «Sin existencias», «Precio bajo el costo».
|
|
89
|
+
* `EditableTable`'s `flag`, passed through. */
|
|
90
|
+
flag?: (line: T) => {
|
|
91
|
+
tone: FlagTone;
|
|
92
|
+
label: string;
|
|
93
|
+
detail?: ReactNode;
|
|
94
|
+
owner?: string;
|
|
95
|
+
} | null;
|
|
96
|
+
/** Sections and notes among the lines. See the file's header. */
|
|
97
|
+
structure?: LineStructure<T>;
|
|
98
|
+
/** Which columns are on screen, when the owner remembers it (a preference). */
|
|
99
|
+
visibleColumnIds?: readonly string[];
|
|
100
|
+
onVisibleColumnIdsChange?: (ids: readonly string[]) => void;
|
|
64
101
|
};
|
|
65
102
|
/**
|
|
66
103
|
* The lines of a document inside its record: a count and a search above, the lines
|
|
@@ -71,4 +108,4 @@ export type LineItemsProps<T> = {
|
|
|
71
108
|
* removing asked first, what an empty document said. One component, built on
|
|
72
109
|
* `EditableTable` and `Dialog`, makes the one2many the same block everywhere.
|
|
73
110
|
*/
|
|
74
|
-
export declare function LineItems<T>({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom, totals, layout, }: LineItemsProps<T>): import("react").JSX.Element;
|
|
111
|
+
export declare function LineItems<T>({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom, totals, layout, adding, onOpenLine, flag, structure, visibleColumnIds, onVisibleColumnIdsChange, }: LineItemsProps<T>): import("react").JSX.Element;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { commitDraft, filterLines, planRemoval, } from "./lines.js";
|
|
4
|
+
import { useColumnControl, } from "../listing/columns.js";
|
|
5
|
+
import { ColumnsMenu } from "../listing/ColumnsMenu.js";
|
|
4
6
|
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
5
7
|
import { Button, IconButton } from "../../primitives/button/Button.js";
|
|
6
8
|
import { SearchField } from "../../primitives/choice/SearchField.js";
|
|
7
9
|
import { Dialog } from "../../primitives/dialog/Dialog.js";
|
|
8
10
|
import { FieldIdsContext } from "../../primitives/field/context.js";
|
|
11
|
+
import { TextInput } from "../../primitives/field/Field.js";
|
|
9
12
|
import { useFormMode } from "../../primitives/field/mode.js";
|
|
10
13
|
import { Empty } from "../../primitives/notice/Notice.js";
|
|
11
14
|
import { useToast } from "../../primitives/notice/Toast.js";
|
|
12
15
|
import { EditableTable, } from "../../primitives/table/EditableTable.js";
|
|
13
|
-
import { Totals } from "../../primitives/value/Totals.js";
|
|
16
|
+
import { Totals, } from "../../primitives/value/Totals.js";
|
|
14
17
|
import { Pencil, Plus, Trash2 } from "lucide-react";
|
|
15
18
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
16
19
|
/** The notice is optional: without a `ToastProvider` the line is still removed, just
|
|
@@ -35,7 +38,7 @@ function plain(v) {
|
|
|
35
38
|
* removing asked first, what an empty document said. One component, built on
|
|
36
39
|
* `EditableTable` and `Dialog`, makes the one2many the same block everywhere.
|
|
37
40
|
*/
|
|
38
|
-
export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom = 8, totals, layout = "auto", }) {
|
|
41
|
+
export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom = 8, totals, layout = "auto", adding, onOpenLine, flag, structure, visibleColumnIds, onVisibleColumnIdsChange, }) {
|
|
39
42
|
const copy = useUiCopy();
|
|
40
43
|
const toast = useOptionalToast();
|
|
41
44
|
const read = useFormMode(mode) === "read";
|
|
@@ -54,7 +57,17 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
54
57
|
const canChange = !read && onChange !== undefined;
|
|
55
58
|
const canAdd = !read && onAdd !== undefined && newLine !== undefined;
|
|
56
59
|
const canRemove = !read && onRemove !== undefined;
|
|
57
|
-
|
|
60
|
+
/* The whole line opens either in this block's own dialog (`editor`) or wherever the app
|
|
61
|
+
opens records (`onOpenLine`, a `Drill` context) — never both, so a line's form never
|
|
62
|
+
sits on top of another modal. */
|
|
63
|
+
const canEdit = (canChange && editor !== undefined) || onOpenLine !== undefined;
|
|
64
|
+
const openLine = (id, line) => {
|
|
65
|
+
if (onOpenLine !== undefined)
|
|
66
|
+
onOpenLine(id);
|
|
67
|
+
else
|
|
68
|
+
openEditor(id, line);
|
|
69
|
+
};
|
|
70
|
+
const inline = canAdd && (adding ?? (editor === undefined ? "inline" : "editor")) === "inline";
|
|
58
71
|
const labelOf = (line) => {
|
|
59
72
|
if (lineLabel !== undefined)
|
|
60
73
|
return lineLabel(line);
|
|
@@ -63,11 +76,35 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
63
76
|
return getLineId(line);
|
|
64
77
|
return first.text?.(line) ?? plain(first.value(line));
|
|
65
78
|
};
|
|
66
|
-
const
|
|
79
|
+
const kindOf = (line) => structure?.kind(line) ?? "line";
|
|
80
|
+
/* Sections and notes are not lines: they are not counted, and a search leaves only lines. */
|
|
81
|
+
const lineCount = structure === undefined ? lines.length : lines.filter((l) => kindOf(l) === "line").length;
|
|
82
|
+
const searchable = lineCount > searchFrom;
|
|
67
83
|
const q = searchable ? query.trim() : "";
|
|
68
|
-
const shown = useMemo(() =>
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
const shown = useMemo(() => {
|
|
85
|
+
const found = filterLines(lines, q, (line) => searchText !== undefined
|
|
86
|
+
? searchText(line)
|
|
87
|
+
: columns.map((c) => c.text?.(line) ?? plain(c.value(line))).join(" "), copy.locale);
|
|
88
|
+
return q === "" || structure === undefined
|
|
89
|
+
? found
|
|
90
|
+
: found.filter((l) => structure.kind(l) === "line");
|
|
91
|
+
}, [lines, columns, searchText, q, copy.locale, structure]);
|
|
92
|
+
/* ── which columns are on screen ── */
|
|
93
|
+
const specs = useMemo(() => columns.map((c) => ({
|
|
94
|
+
id: c.id,
|
|
95
|
+
label: c.header,
|
|
96
|
+
optional: c.optional === true,
|
|
97
|
+
...(c.defaultHidden === true ? { defaultHidden: true } : {}),
|
|
98
|
+
})), [columns]);
|
|
99
|
+
/* `visibleColumnIds` alone is a fixed choice, the way a controlled input without
|
|
100
|
+
`onChange` is: the owner said which columns, and the menu cannot change them. */
|
|
101
|
+
const columnControl = useColumnControl(specs, visibleColumnIds === undefined && onVisibleColumnIdsChange === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: {
|
|
104
|
+
...(visibleColumnIds === undefined ? {} : { visibleIds: visibleColumnIds }),
|
|
105
|
+
onVisibleIdsChange: onVisibleColumnIdsChange ?? (() => { }),
|
|
106
|
+
});
|
|
107
|
+
const onScreen = columns.filter((c) => columnControl.isVisible(c.id));
|
|
71
108
|
useEffect(() => {
|
|
72
109
|
const target = focusNext.current;
|
|
73
110
|
if (target === null)
|
|
@@ -122,6 +159,49 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
122
159
|
else
|
|
123
160
|
openEditor(null, blank);
|
|
124
161
|
};
|
|
162
|
+
/* In place: the table opens the row whose id this returns. */
|
|
163
|
+
const addInline = (make = newLine) => {
|
|
164
|
+
if (make === undefined)
|
|
165
|
+
return undefined;
|
|
166
|
+
const blank = make();
|
|
167
|
+
setQuery("");
|
|
168
|
+
onAdd?.(blank);
|
|
169
|
+
return getLineId(blank);
|
|
170
|
+
};
|
|
171
|
+
/* A section and a note are always written in place, editor or not. */
|
|
172
|
+
const bandAdds = read || onAdd === undefined || structure === undefined
|
|
173
|
+
? []
|
|
174
|
+
: [
|
|
175
|
+
...(structure.newSection === undefined
|
|
176
|
+
? []
|
|
177
|
+
: [
|
|
178
|
+
{
|
|
179
|
+
label: copy.lines.addSection,
|
|
180
|
+
onAdd: () => addInline(structure.newSection),
|
|
181
|
+
},
|
|
182
|
+
]),
|
|
183
|
+
...(structure.newNote === undefined
|
|
184
|
+
? []
|
|
185
|
+
: [{ label: copy.lines.addNote, onAdd: () => addInline(structure.newNote) }]),
|
|
186
|
+
];
|
|
187
|
+
const inPlace = inline || bandAdds.length > 0;
|
|
188
|
+
const bands = structure === undefined
|
|
189
|
+
? undefined
|
|
190
|
+
: {
|
|
191
|
+
kind: (line) => {
|
|
192
|
+
const k = structure.kind(line);
|
|
193
|
+
return k === "line" ? null : k;
|
|
194
|
+
},
|
|
195
|
+
render: (line) => structure.text(line),
|
|
196
|
+
...(canChange
|
|
197
|
+
? {
|
|
198
|
+
edit: (line, set) => (_jsx(TextInput, { "aria-label": structure.kind(line) === "section"
|
|
199
|
+
? copy.lines.section
|
|
200
|
+
: copy.lines.note, value: structure.text(line), onChange: (text) => set(structure.withText(line, text)) })),
|
|
201
|
+
}
|
|
202
|
+
: {}),
|
|
203
|
+
total: (column, under) => columns.find((c) => c.id === column.id)?.subtotal?.(under) ?? null,
|
|
204
|
+
};
|
|
125
205
|
const remove = (line) => {
|
|
126
206
|
const id = getLineId(line);
|
|
127
207
|
const { index, focus } = planRemoval(lines, shown, id, getLineId);
|
|
@@ -140,7 +220,7 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
140
220
|
});
|
|
141
221
|
};
|
|
142
222
|
/* ── the table ──────────────────────────────────────────────────── */
|
|
143
|
-
const tableColumns =
|
|
223
|
+
const tableColumns = onScreen.map((c) => ({
|
|
144
224
|
id: c.id,
|
|
145
225
|
header: c.header,
|
|
146
226
|
...(c.width === undefined ? {} : { width: c.width }),
|
|
@@ -170,6 +250,7 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
170
250
|
would be two more targets on a small screen. Without an editor the trash
|
|
171
251
|
is the only way to remove, so it stays — at the card's top right, lit. */
|
|
172
252
|
stack: canEdit ? "hidden" : "trail",
|
|
253
|
+
onBands: true,
|
|
173
254
|
render: (line) => {
|
|
174
255
|
const id = getLineId(line);
|
|
175
256
|
const nameId = `${formId}-line-${id}`;
|
|
@@ -178,20 +259,21 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
178
259
|
and a trash can lit on each of twenty lines are forty invitations. They
|
|
179
260
|
fade instead of unmounting, so Tab still reaches them. Pressing them does
|
|
180
261
|
not put the row into in-place editing. */
|
|
181
|
-
_jsxs("span", { "data-line": id, className: "flex items-center justify-end gap-0.5 whitespace-nowrap opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 focus-within:opacity-100 in-data-stacked:opacity-100", onClick: (e) => e.stopPropagation(), children: [_jsx("span", { id: nameId, className: "sr-only", children: labelOf(line) }), canEdit ? (_jsx(IconButton, { icon: Pencil, label: copy.lines.edit, "aria-describedby": nameId, haspopup: "dialog", onClick: () =>
|
|
262
|
+
_jsxs("span", { "data-line": id, className: "flex items-center justify-end gap-0.5 whitespace-nowrap opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 focus-within:opacity-100 in-data-stacked:opacity-100", onClick: (e) => e.stopPropagation(), children: [_jsx("span", { id: nameId, className: "sr-only", children: labelOf(line) }), canEdit && kindOf(line) === "line" ? (_jsx(IconButton, { icon: Pencil, label: copy.lines.edit, "aria-describedby": nameId, haspopup: "dialog", onClick: () => openLine(id, line) })) : null, canRemove ? (_jsx(IconButton, { icon: Trash2, label: copy.lines.remove, tone: "danger", "aria-describedby": nameId, onClick: () => remove(line) })) : null] }));
|
|
182
263
|
},
|
|
183
264
|
});
|
|
184
265
|
}
|
|
185
|
-
|
|
266
|
+
/* In place, «add» is the table's own row under the grid: where the line will appear. */
|
|
267
|
+
const addButton = canAdd && !inline ? (_jsx(Button, { role: "secondary", size: "sm", icon: Plus, onClick: add, "data-line-add": "", children: copy.lines.add })) : null;
|
|
186
268
|
const count = q === ""
|
|
187
|
-
? fillCopy(
|
|
188
|
-
count:
|
|
269
|
+
? fillCopy(lineCount === 1 ? copy.lineItems.one : copy.lineItems.many, {
|
|
270
|
+
count: lineCount.toLocaleString(copy.locale),
|
|
189
271
|
})
|
|
190
272
|
: fillCopy(copy.lineItems.shown, {
|
|
191
273
|
shown: shown.length.toLocaleString(copy.locale),
|
|
192
|
-
count:
|
|
274
|
+
count: lineCount.toLocaleString(copy.locale),
|
|
193
275
|
});
|
|
194
|
-
return (_jsxs("section", { ref: root, "aria-label": copy.lineItems.table, className: "flex min-w-0 flex-col gap-4", children: [lines.length === 0 ? (
|
|
276
|
+
return (_jsxs("section", { ref: root, "aria-label": copy.lineItems.table, className: "flex min-w-0 flex-col gap-4", children: [lines.length === 0 && !canAdd ? (
|
|
195
277
|
/* Said, with its way out: an empty document is a question («what is sold
|
|
196
278
|
here?»), not a blank box. In read mode there is no way out to offer. */
|
|
197
279
|
_jsx(Empty, { align: "start", sentence: copy.lines.empty, action: addButton ?? undefined })) : (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 px-1", children: [searchable ? (_jsxs("div", { className: "w-full max-w-(--container-search) min-w-0 flex-1", children: [_jsx("span", { id: searchLabelId, className: "sr-only", children: copy.lines.search }), _jsx(FieldIdsContext.Provider, { value: {
|
|
@@ -199,19 +281,25 @@ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange
|
|
|
199
281
|
labelId: searchLabelId,
|
|
200
282
|
describedBy: undefined,
|
|
201
283
|
invalid: false,
|
|
202
|
-
}, children: _jsx(SearchField, { value: query, onChange: setQuery, placeholder: copy.lines.search }) })] })) : null, _jsx("span", { className: "text-meta text-muted tabular-nums", "aria-live": "polite", children: count }), addButton
|
|
284
|
+
}, children: _jsx(SearchField, { value: query, onChange: setQuery, placeholder: copy.lines.search }) })] })) : null, _jsx("span", { className: "text-meta text-muted tabular-nums", "aria-live": "polite", children: count }), columnControl.hasOptional || addButton !== null ? (_jsxs("span", { className: "ml-auto flex items-center gap-2", children: [_jsx(ColumnsMenu, { control: columnControl }), addButton] })) : null] }), _jsx(EditableTable, { rows: shown, columns: tableColumns, getRowId: getLineId, empty: lines.length === 0
|
|
285
|
+
? copy.lines.empty
|
|
286
|
+
: fillCopy(copy.lineItems.noMatch, { query: query.trim() }), layout: layout, ...(inline ? { onAdd: () => addInline(), addLabel: copy.lines.add } : {}), ...(bandAdds.length === 0 ? {} : { adds: bandAdds }), ...(bands === undefined ? {} : { bands }), ...(flag === undefined ? {} : { flag }), ...(inPlace && onRemove !== undefined
|
|
287
|
+
? /* A blank row nobody wrote in is taken back without a notice: nobody
|
|
288
|
+
removed anything, so there is nothing to undo. */
|
|
289
|
+
{ onDiscard: onRemove }
|
|
290
|
+
: {}), ...(canEdit
|
|
203
291
|
? {
|
|
204
292
|
onOpen: (id) => {
|
|
205
293
|
const line = lines.find((l) => getLineId(l) === id);
|
|
206
294
|
if (line !== undefined)
|
|
207
|
-
|
|
295
|
+
openLine(id, line);
|
|
208
296
|
},
|
|
209
297
|
}
|
|
210
298
|
: {}), ...(canChange && columns.some((c) => c.edit !== undefined)
|
|
211
299
|
? { onChange: (id, next) => onChange?.(id, next) }
|
|
212
|
-
: {}) })] })), totals === undefined || totals.length === 0 ? null : (
|
|
300
|
+
: {}) })] })), totals === undefined || ("length" in totals && totals.length === 0) ? null : (
|
|
213
301
|
/* Inset like the table's cells, so the last figure lines up with the amounts. */
|
|
214
|
-
_jsx("div", { className: "px-4", children: _jsx(Totals, { rows: totals }) })), editor === undefined || session === null ? null : (_jsx(Dialog, { open: open, width: "sm", title: session.id === null ? copy.lineItems.newLine : copy.lines.edit, ...(context === undefined ? {} : { context }), ...(session.id === null ? {} : { subtitle: labelOf(session.draft) }), onClose: () => setOpen(false), footer: _jsxs(_Fragment, { children: [session.id === null || !canRemove ? null : (
|
|
302
|
+
_jsx("div", { className: "px-4", children: "length" in totals ? _jsx(Totals, { rows: totals }) : _jsx(Totals, { ...totals }) })), editor === undefined || session === null ? null : (_jsx(Dialog, { open: open, width: "sm", title: session.id === null ? copy.lineItems.newLine : copy.lines.edit, ...(context === undefined ? {} : { context }), ...(session.id === null ? {} : { subtitle: labelOf(session.draft) }), onClose: () => setOpen(false), footer: _jsxs(_Fragment, { children: [session.id === null || !canRemove ? null : (
|
|
215
303
|
/* Apart, at the other end from «Listo»: removing is not the way the
|
|
216
304
|
form is meant to close. */
|
|
217
305
|
_jsx("span", { className: "mr-auto", children: _jsx(IconButton, { icon: Trash2, label: copy.lines.remove, tone: "danger", onClick: () => {
|
|
@@ -82,5 +82,5 @@ export function Listing({ bar, metrics, sticky = false, surface, children, }) {
|
|
|
82
82
|
background is its white and its radius; a frame around a list that is
|
|
83
83
|
already inside a framed panel is a nested rectangle, and nested
|
|
84
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: [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 })] }));
|
|
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
86
|
}
|
|
@@ -32,8 +32,40 @@ import type { ReactNode } from "react";
|
|
|
32
32
|
* checkboxes, and the row they were about to tick is no longer where it was.
|
|
33
33
|
*
|
|
34
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
|
-
|
|
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, }: {
|
|
37
69
|
/** What starts something new. It stays visible even when there is a selection. */
|
|
38
70
|
actions?: ReactNode;
|
|
39
71
|
title?: string;
|
|
@@ -62,6 +94,13 @@ export declare function ListingBar({ actions, title, description, counters, sele
|
|
|
62
94
|
/** `chrome` paints the whole band navy and everything inside inherits it:
|
|
63
95
|
* the search box, the pagination and the switcher. See `system/surface.tsx`. */
|
|
64
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;
|
|
65
104
|
}): import("react").JSX.Element;
|
|
66
105
|
export type ListIdentity = {
|
|
67
106
|
title?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useUiCopy } from "../../foundations/copy.js";
|
|
3
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
4
4
|
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
5
5
|
import { SurfaceProvider, useSurface } from "../../foundations/surface.js";
|
|
6
6
|
import { Line } from "../../primitives/block/Line.js";
|
|
@@ -37,12 +37,44 @@ import { X } from "lucide-react";
|
|
|
37
37
|
* checkboxes, and the row they were about to tick is no longer where it was.
|
|
38
38
|
*
|
|
39
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
|
-
|
|
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, }) {
|
|
42
74
|
const selecting = selection !== undefined && selection.count > 0;
|
|
43
75
|
const d = useDensity();
|
|
44
76
|
const copy = useUiCopy();
|
|
45
|
-
const activeSurface = useSurface(surface);
|
|
77
|
+
const activeSurface = useSurface(inline ? "canvas" : surface);
|
|
46
78
|
const isChrome = activeSurface === "chrome";
|
|
47
79
|
/* In compact the title and the description are NOT drawn, and it is not for
|
|
48
80
|
lack of room: in a preview or on a phone, the surface header already says
|
|
@@ -61,6 +93,13 @@ export function ListingBar({ actions, title, description, counters, selection, s
|
|
|
61
93
|
ALREADY filtered, so searching there would change the set underneath the
|
|
62
94
|
selection. */
|
|
63
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
|
+
}
|
|
64
103
|
/* El campo de búsqueda va en el CENTRO de la línea, y centrado contra la
|
|
65
104
|
línea: pide `--container-search` y los dos lados se reparten lo que sobra
|
|
66
105
|
en mitades iguales. Antes tomaba el resto, así que un rastro largo a la
|
|
@@ -18,9 +18,13 @@ import type { ReactNode } from "react";
|
|
|
18
18
|
*
|
|
19
19
|
* «some metadata that might help is not visible» — and it was literal: to know
|
|
20
20
|
* whether `FAC/2026/0118` was paid or overdue you had to open it. */
|
|
21
|
-
export declare function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, onSelectedChange, actions, more, onMoreSelect, openHint, children, }: {
|
|
21
|
+
export declare function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, onSelectedChange, actions, more, onMoreSelect, openHint, children, copy: copyAs, }: {
|
|
22
22
|
icon?: LucideIcon;
|
|
23
23
|
name: string;
|
|
24
|
+
/** The copy button among the row's actions: on by default, copying the row's name — the
|
|
25
|
+
* number of an invoice, a customer — without opening it. A string copies that instead;
|
|
26
|
+
* `false` removes it. */
|
|
27
|
+
copy?: string | false;
|
|
24
28
|
onOpen?: () => void;
|
|
25
29
|
onPeek?: () => void;
|
|
26
30
|
tags?: ReactNode;
|
|
@@ -5,6 +5,7 @@ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
|
5
5
|
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
6
6
|
import { stickyTop } from "../../foundations/sticky.js";
|
|
7
7
|
import { Menu } from "../../primitives/menu/Menu.js";
|
|
8
|
+
import { CopyButton } from "../../primitives/value/Copyable.js";
|
|
8
9
|
import { ChevronRight, MoreHorizontal, SquareArrowOutUpRight } from "lucide-react";
|
|
9
10
|
/* ---------------------------------------------------------------- *
|
|
10
11
|
* THE RICH ROW — the other density
|
|
@@ -26,7 +27,7 @@ import { ChevronRight, MoreHorizontal, SquareArrowOutUpRight } from "lucide-reac
|
|
|
26
27
|
*
|
|
27
28
|
* «some metadata that might help is not visible» — and it was literal: to know
|
|
28
29
|
* whether `FAC/2026/0118` was paid or overdue you had to open it. */
|
|
29
|
-
export function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, onSelectedChange, actions, more, onMoreSelect, openHint, children, }) {
|
|
30
|
+
export function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, onSelectedChange, actions, more, onMoreSelect, openHint, children, copy: copyAs, }) {
|
|
30
31
|
const d = useDensity();
|
|
31
32
|
const copy = useUiCopy();
|
|
32
33
|
const openAs = openHint ?? copy.shell.openAsPanel;
|
|
@@ -52,7 +53,7 @@ export function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, on
|
|
|
52
53
|
having to open a set to know whether it is a thousand rows or a
|
|
53
54
|
million— and it truncates instead of wrapping into itself: three
|
|
54
55
|
lines per row in a 480 px drawer means three rows fit on screen. */
|
|
55
|
-
_jsx("span", { className: "min-w-0 truncate text-micro tabular-nums text-muted", children: meta }))] }), tags === undefined ? null : (_jsx("span", { className: "flex min-w-fit shrink basis-(--container-tags) items-center justify-end gap-1.5", children: tags })), _jsxs("span", { className: "flex shrink-0 items-center gap-1 opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 focus-within:opacity-100", children: [actions, onOpen === undefined ? null : (_jsx("button", { type: "button", onClick: (e) => {
|
|
56
|
+
_jsx("span", { className: "min-w-0 truncate text-micro tabular-nums text-muted", children: meta }))] }), tags === undefined ? null : (_jsx("span", { className: "flex min-w-fit shrink basis-(--container-tags) items-center justify-end gap-1.5", children: tags })), _jsxs("span", { className: "flex shrink-0 items-center gap-1 opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 focus-within:opacity-100 pointer-coarse:opacity-100", children: [actions, copyAs === false ? null : (_jsx(CopyButton, { value: copyAs ?? name, label: fillCopy(copy.copyable.field, { label: name }) })), onOpen === undefined ? null : (_jsx("button", { type: "button", onClick: (e) => {
|
|
56
57
|
e.stopPropagation();
|
|
57
58
|
onOpen();
|
|
58
59
|
}, title: openAs, "aria-label": `${openAs}: ${name}`, className: "inline-flex size-(--size-control-sm) items-center justify-center rounded-pill text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: _jsx(SquareArrowOutUpRight, { className: "size-(--size-icon-sm)", strokeWidth: 2 }) })), more === undefined || more.length === 0 ? null : (_jsx("span", { onClick: (e) => e.stopPropagation(), role: "presentation", children: _jsx(Menu, { label: fillCopy(copy.selection.moreOf, { name }), groups: more, icon: MoreHorizontal, iconOnly: true, chevron: false, align: "end", width: "sm", variant: "text", ...(onMoreSelect === undefined ? {} : { onSelect: onMoreSelect }) }) }))] })] }) }));
|
|
@@ -2,9 +2,9 @@ import { type Crumb } from "./Breadcrumb.js";
|
|
|
2
2
|
import { type DirtyState } from "./DirtyActions.js";
|
|
3
3
|
import type { Surface, Tone } from "../../foundations/types.js";
|
|
4
4
|
import type { LucideIcon } from "lucide-react";
|
|
5
|
-
import {
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
6
|
export type { Surface };
|
|
7
|
-
export declare function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface, smart, aspects, dirty,
|
|
7
|
+
export declare function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface, smart, aspects, dirty, activity, activityLayout, blocking, indicator, children, }: {
|
|
8
8
|
crumbs?: readonly Crumb[];
|
|
9
9
|
pager?: ReactNode;
|
|
10
10
|
/** The action buttons. At most three visible + a `Menu` with `iconOnly`, and
|
|
@@ -12,25 +12,23 @@ export declare function RecordHeader({ crumbs, pager, actions, status, kicker, i
|
|
|
12
12
|
* never the navigation ones. They take the rail's surface from context: write
|
|
13
13
|
* them without `surface`, light by default, dark on `Record.Rail surface="chrome"`. */
|
|
14
14
|
actions?: ReactNode;
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
chatter?: ReactNode;
|
|
15
|
+
/** Everything around the record: its notices, conversation and history — an
|
|
16
|
+
* `Activity`. Never inside the sheet: it is about the record, not part of the form. */
|
|
17
|
+
activity?: ReactNode;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
19
|
+
* Where the activity goes, as the record's width allows (THE PAGE, below).
|
|
21
20
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* auto a column at the RIGHT of the sheet, joined to it, when the page is
|
|
22
|
+
* `--container-beside` wide, and a card BELOW the sheet when it is not.
|
|
23
|
+
* **The default.**
|
|
24
|
+
* below always below the sheet, at its width.
|
|
26
25
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* otra cosa.
|
|
26
|
+
* It is said by the RECORD and not guessed from the child: the flex direction is set by
|
|
27
|
+
* this container, and a parent that inspects what it was handed to decide how to lay
|
|
28
|
+
* itself out breaks the next time someone wraps the panel in something else.
|
|
31
29
|
*/
|
|
32
|
-
|
|
33
|
-
/** `StatusBar` (where is it at?), the
|
|
30
|
+
activityLayout?: "auto" | "below";
|
|
31
|
+
/** `StatusBar` (where is it at?), the activity's `Timeline` (what happened to get
|
|
34
32
|
* here?), or both: they are different questions and many records only have one. */
|
|
35
33
|
status?: ReactNode;
|
|
36
34
|
/** The category, small and above the name: «factura de cliente». It makes a
|
|
@@ -65,6 +63,12 @@ export declare function RecordHeader({ crumbs, pager, actions, status, kicker, i
|
|
|
65
63
|
* it has no way to know. As long as there are changes, the only thing that
|
|
66
64
|
* can be done is confirm them or throw them away. */
|
|
67
65
|
dirty?: DirtyState;
|
|
66
|
+
/** What is WRONG with the record — `RecordBlocking`. Always visible, outside the sheet:
|
|
67
|
+
* between the rail and the paper, never among the fields (see `RecordNotices`). */
|
|
68
|
+
blocking?: ReactNode;
|
|
69
|
+
/** The rest of the record's notices, as one indicator — `RecordNoticeIndicator`. In the
|
|
70
|
+
* rail next to the counters; without a rail, first in the record, next to them. */
|
|
71
|
+
indicator?: ReactNode;
|
|
68
72
|
/** The record body. It sits IN the paper with the title, so groups, tabs
|
|
69
73
|
* and the page are one white sheet on the ground — Odoo's `<sheet>`. */
|
|
70
74
|
children?: ReactNode;
|