@sincpro/web-ui 1.2.0 → 1.4.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/dist/components/activity/Activity.js +1 -1
- package/dist/components/activity/Messages.d.ts +4 -2
- package/dist/components/activity/Messages.js +4 -4
- package/dist/components/activity/Timeline.js +1 -1
- package/dist/components/assist/AssistMark.js +1 -1
- package/dist/components/assist/AssistSuggestion.js +1 -1
- package/dist/components/assist/ChangeReview.js +2 -2
- package/dist/components/calendar/RecordCalendar.js +4 -1
- package/dist/components/form/Sheet.js +5 -2
- package/dist/components/lines/LineItems.d.ts +1 -1
- package/dist/components/lines/LineItems.js +2 -2
- package/dist/components/listing/ListingBar.js +1 -1
- package/dist/components/listing/RecordCard.d.ts +4 -0
- package/dist/components/listing/RecordCard.js +4 -1
- package/dist/components/listing/Row.d.ts +7 -0
- package/dist/components/listing/Row.js +7 -1
- package/dist/components/listing/ViewSwitcher.d.ts +7 -2
- package/dist/components/listing/index.d.ts +1 -1
- package/dist/components/listing/set.d.ts +4 -1
- package/dist/components/record/Breadcrumb.js +5 -1
- package/dist/components/record/DirtyActions.d.ts +20 -6
- package/dist/components/record/DirtyActions.js +55 -4
- package/dist/components/record/RecordHeader.d.ts +1 -1
- package/dist/components/record/RecordHeader.js +32 -13
- package/dist/components/record/RecordSkeleton.js +1 -1
- package/dist/components/search/CriteriaBuilder.d.ts +1 -1
- package/dist/components/search/CriteriaBuilder.js +3 -3
- package/dist/components/search/FilterBuilder.d.ts +1 -18
- package/dist/components/search/FilterBuilder.js +4 -4
- package/dist/components/search/JsonEditor.js +5 -1
- package/dist/components/search/SearchBar.d.ts +1 -2
- package/dist/components/search/SearchBar.js +12 -3
- package/dist/components/search/criteria.d.ts +17 -1
- package/dist/components/search/index.d.ts +2 -1
- package/dist/components/search/index.js +1 -0
- package/dist/components/shell/Panes.d.ts +18 -0
- package/dist/components/shell/Panes.js +36 -0
- package/dist/components/shell/index.d.ts +1 -0
- package/dist/components/shell/index.js +2 -0
- package/dist/foundations/calendar.d.ts +3 -1
- package/dist/foundations/catalog.d.ts +27 -0
- package/dist/foundations/catalog.js +48 -0
- package/dist/foundations/copy.d.ts +40 -62
- package/dist/foundations/copy.es.js +10 -0
- package/dist/foundations/copy.js +25 -93
- package/dist/{primitives/field/context.d.ts → foundations/form.d.ts} +9 -0
- package/dist/{primitives/field/mode.js → foundations/form.js} +11 -0
- package/dist/foundations/grouping.d.ts +9 -3
- package/dist/foundations/index.d.ts +2 -0
- package/dist/foundations/index.js +5 -0
- package/dist/foundations/sticky.d.ts +4 -0
- package/dist/foundations/sticky.js +1 -0
- package/dist/primitives/button/Button.d.ts +12 -2
- package/dist/primitives/button/Button.js +12 -2
- package/dist/primitives/cell/CellStack.js +1 -1
- package/dist/primitives/choice/ComboBox.d.ts +1 -1
- package/dist/primitives/choice/ComboBox.js +4 -4
- package/dist/primitives/choice/RadioGroup.d.ts +1 -1
- package/dist/primitives/choice/RadioGroup.js +3 -3
- package/dist/primitives/choice/SearchField.js +1 -1
- package/dist/primitives/choice/SegmentedControl.d.ts +1 -1
- package/dist/primitives/choice/SegmentedControl.js +2 -2
- package/dist/primitives/choice/Slider.d.ts +1 -1
- package/dist/primitives/choice/Slider.js +2 -2
- package/dist/primitives/choice/Switch.d.ts +1 -1
- package/dist/primitives/choice/Switch.js +2 -2
- package/dist/primitives/date/Calendar.js +1 -1
- package/dist/primitives/date/DatePicker.d.ts +1 -1
- package/dist/primitives/date/DatePicker.js +2 -2
- package/dist/primitives/date/DateTime.d.ts +1 -1
- package/dist/primitives/date/DateTime.js +2 -2
- package/dist/primitives/field/Checks.d.ts +1 -1
- package/dist/primitives/field/Checks.js +4 -4
- package/dist/primitives/field/DropZone.d.ts +1 -1
- package/dist/primitives/field/DropZone.js +2 -2
- package/dist/primitives/field/Field.d.ts +10 -5
- package/dist/primitives/field/Field.js +10 -6
- package/dist/primitives/field/Suggest.d.ts +1 -1
- package/dist/primitives/field/Suggest.js +2 -2
- package/dist/primitives/field/index.d.ts +1 -3
- package/dist/primitives/field/index.js +4 -4
- package/dist/primitives/notice/Notice.js +3 -1
- package/dist/primitives/picker/Picker.d.ts +1 -1
- package/dist/primitives/picker/Picker.js +4 -4
- package/dist/primitives/{tag → picker}/TagsField.d.ts +2 -2
- package/dist/primitives/{tag → picker}/TagsField.js +5 -5
- package/dist/primitives/picker/index.d.ts +2 -0
- package/dist/primitives/picker/index.js +5 -1
- package/dist/primitives/table/CellCopy.js +1 -1
- package/dist/primitives/table/CellEditor.js +2 -4
- package/dist/primitives/table/DataTable.d.ts +11 -1
- package/dist/primitives/table/DataTable.js +111 -38
- package/dist/primitives/table/EditableTable.js +6 -2
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.js +4 -3
- package/dist/primitives/table/SheetTable.js +8 -14
- package/dist/primitives/table/Table.js +2 -2
- package/dist/primitives/tabs/Tabs.d.ts +22 -4
- package/dist/primitives/tabs/Tabs.js +77 -23
- package/dist/primitives/tabs/index.d.ts +1 -1
- package/dist/primitives/tabs/index.js +1 -1
- package/dist/primitives/tag/Tags.js +4 -1
- package/dist/primitives/tag/index.d.ts +0 -2
- package/dist/primitives/tag/index.js +0 -3
- package/dist/primitives/value/Avatar.js +8 -1
- package/dist/primitives/value/{Fecha.d.ts → DateValue.d.ts} +5 -2
- package/dist/primitives/value/{Fecha.js → DateValue.js} +9 -8
- package/dist/primitives/value/Relation.d.ts +1 -1
- package/dist/primitives/value/Relation.js +2 -2
- package/dist/primitives/value/Value.js +4 -1
- package/dist/primitives/value/index.d.ts +1 -1
- package/dist/primitives/value/index.js +1 -1
- package/dist/tokens.css +42 -0
- package/dist/views/list/List.d.ts +22 -2
- package/dist/views/list/List.js +28 -6
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +47 -16
- package/dist/views/list/index.d.ts +2 -2
- package/dist/views/list/presentations.js +3 -1
- package/dist/views/list/types.d.ts +57 -1
- package/dist/views/list/useNarrowList.d.ts +2 -2
- package/dist/views/list/useNarrowList.js +5 -5
- package/dist/views/record/Record.js +46 -42
- package/package.json +1 -1
- package/dist/primitives/field/context.js +0 -13
- package/dist/primitives/field/mode.d.ts +0 -9
- /package/dist/primitives/field/{copy.d.ts → clipboard.d.ts} +0 -0
- /package/dist/primitives/field/{copy.js → clipboard.js} +0 -0
- /package/dist/primitives/{tag/choices.d.ts → picker/tagChoices.d.ts} +0 -0
- /package/dist/primitives/{tag/choices.js → picker/tagChoices.js} +0 -0
|
@@ -9,7 +9,7 @@ import { SegmentedControl } from "../../primitives/choice/SegmentedControl.js";
|
|
|
9
9
|
import { useNoteEntries } from "../../primitives/field/note.js";
|
|
10
10
|
import { Callout } from "../../primitives/notice/Callout.js";
|
|
11
11
|
import { Tag } from "../../primitives/tag/Tag.js";
|
|
12
|
-
import { DateValue } from "../../primitives/value/
|
|
12
|
+
import { DateValue } from "../../primitives/value/DateValue.js";
|
|
13
13
|
import { Avatar } from "../../primitives/value/Person.js";
|
|
14
14
|
import { Activity as ActivityIcon, ArrowRight, CheckCircle2, ChevronDown, ChevronRight, ChevronUp, CornerDownRight, History, Info, Lock, MessageSquare, OctagonAlert, PanelRightClose, PanelRightOpen, TriangleAlert, Zap, } from "lucide-react";
|
|
15
15
|
import { Fragment, useId, useState } from "react";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type UiCopy } from "../../foundations/copy.js";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
2
3
|
/**
|
|
3
4
|
* The box to write in. What gets written is drawn by `Activity`, together with
|
|
@@ -51,9 +52,10 @@ export type MessageKind = {
|
|
|
51
52
|
*
|
|
52
53
|
* Es una función y no una constante porque sus palabras salen de la copia, y una
|
|
53
54
|
* constante las congelaría con las que hubiera al cargar el módulo —o sea, las
|
|
54
|
-
* de fábrica— por más que después se instale otra.
|
|
55
|
+
* de fábrica— por más que después se instale otra. Sin `copy`, las instaladas; un
|
|
56
|
+
* componente le pasa las de su rama.
|
|
55
57
|
*/
|
|
56
|
-
export declare function writeANote(): MessageKind;
|
|
58
|
+
export declare function writeANote(copy?: UiCopy): MessageKind;
|
|
57
59
|
export type MessageComposerProps = {
|
|
58
60
|
/** The ways of writing this record takes. Its own list because it is the
|
|
59
61
|
* application that knows whether there is WhatsApp, and to whose number. */
|
|
@@ -10,10 +10,10 @@ import { useState } from "react";
|
|
|
10
10
|
*
|
|
11
11
|
* Es una función y no una constante porque sus palabras salen de la copia, y una
|
|
12
12
|
* constante las congelaría con las que hubiera al cargar el módulo —o sea, las
|
|
13
|
-
* de fábrica— por más que después se instale otra.
|
|
13
|
+
* de fábrica— por más que después se instale otra. Sin `copy`, las instaladas; un
|
|
14
|
+
* componente le pasa las de su rama.
|
|
14
15
|
*/
|
|
15
|
-
export function writeANote() {
|
|
16
|
-
const copy = currentCopy();
|
|
16
|
+
export function writeANote(copy = currentCopy()) {
|
|
17
17
|
return {
|
|
18
18
|
id: "note",
|
|
19
19
|
label: copy.activity.writeNote,
|
|
@@ -26,7 +26,7 @@ export function MessageComposer({ kinds, onSend, sending = false, footer, }) {
|
|
|
26
26
|
/* La manera por default se arma acá adentro y no en la firma: un default de
|
|
27
27
|
parámetro se evalúa en cada render, sí, pero escrito como constante del
|
|
28
28
|
módulo congelaría las palabras de fábrica. */
|
|
29
|
-
const ways = kinds ?? [writeANote()];
|
|
29
|
+
const ways = kinds ?? [writeANote(copy)];
|
|
30
30
|
const [writing, setWriting] = useState(undefined);
|
|
31
31
|
const [body, setBody] = useState("");
|
|
32
32
|
const ready = body.trim() !== "" && !sending;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { SurfaceProvider, useSurface } from "../../foundations/surface.js";
|
|
4
4
|
import { Empty } from "../../primitives/notice/Notice.js";
|
|
5
|
-
import { DateValue } from "../../primitives/value/
|
|
5
|
+
import { DateValue } from "../../primitives/value/DateValue.js";
|
|
6
6
|
import { Children } from "react";
|
|
7
7
|
const DOT_TONE = {
|
|
8
8
|
neutral: { canvas: "bg-muted", chrome: "bg-chrome-muted" },
|
|
@@ -5,7 +5,7 @@ import { currentCopy, fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
|
5
5
|
import { cn, focusRing } from "../../foundations/variants.js";
|
|
6
6
|
import { PopoverTrigger } from "../../primitives/overlay/PopoverTrigger.js";
|
|
7
7
|
import { Tooltip, TooltipTrigger } from "../../primitives/overlay/Tooltip.js";
|
|
8
|
-
import { DateValue } from "../../primitives/value/
|
|
8
|
+
import { DateValue } from "../../primitives/value/DateValue.js";
|
|
9
9
|
import { Link } from "react-aria-components";
|
|
10
10
|
/* ==================================================================== *
|
|
11
11
|
* THE ASSISTANT'S MARK — how a screen says «this came from the assistant».
|
|
@@ -3,8 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { acceptedMessage, applyWithUndo, } from "./assist.js";
|
|
4
4
|
import { AssistLabel, AssistSourceLink } from "./AssistMark.js";
|
|
5
5
|
import { useUiCopy } from "../../foundations/copy.js";
|
|
6
|
+
import { FieldIdsContext } from "../../foundations/form.js";
|
|
6
7
|
import { Button, IconButton } from "../../primitives/button/Button.js";
|
|
7
|
-
import { FieldIdsContext } from "../../primitives/field/context.js";
|
|
8
8
|
import { useToastIfAny } from "../../primitives/notice/Toast.js";
|
|
9
9
|
import { Check, X } from "lucide-react";
|
|
10
10
|
import { useContext, useId } from "react";
|
|
@@ -79,7 +79,7 @@ function ReviewBody({ review }) {
|
|
|
79
79
|
const copy = useUiCopy();
|
|
80
80
|
const { groups, order, current, choose } = review;
|
|
81
81
|
const state = chosenState(order, current);
|
|
82
|
-
return (_jsxs("div", { className: "@container flex min-w-0 flex-col", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-b border-line px-4 py-2", children: [_jsx(Checkbox, { checked: state === "all", mixed: state === "some", label: copy.assist.all, onChange: (on) => choose(on ? [...order] : []) }), _jsx("span", { className: "text-micro text-muted tabular-nums", children: fillCopy(copy.assist.chosen, { count: current.length, total: order.length }) })] }), groups.map((group, i) => (_jsxs("section", { className: "flex min-w-0 flex-col", children: [group.title === undefined ? null : (_jsx("
|
|
82
|
+
return (_jsxs("div", { className: "@container flex min-w-0 flex-col", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-b border-line px-4 py-2", children: [_jsx(Checkbox, { checked: state === "all", mixed: state === "some", label: copy.assist.all, onChange: (on) => choose(on ? [...order] : []) }), _jsx("span", { className: "text-micro text-muted tabular-nums", children: fillCopy(copy.assist.chosen, { count: current.length, total: order.length }) })] }), groups.map((group, i) => (_jsxs("section", { className: "flex min-w-0 flex-col", children: [group.title === undefined ? null : (_jsx("h3", { className: "px-4 pt-3 pb-1 text-micro font-medium text-muted", children: group.title })), _jsx("ul", { className: "flex min-w-0 flex-col", children: group.changes.map((change) => (_jsx(ChangeRow, { change: change, on: current.includes(change.id), onToggle: (on) => choose(toggleChosen(order, current, change.id, on)) }, change.id))) })] }, group.title ?? i)))] }));
|
|
83
83
|
}
|
|
84
84
|
/** «Elegí al menos un cambio.», «Descartar» and «Aplicar N cambios». */
|
|
85
85
|
function ReviewActions({ review, emphasis, onDiscard, after, }) {
|
|
@@ -102,7 +102,7 @@ export function ChangeReview(props) {
|
|
|
102
102
|
const copy = useUiCopy();
|
|
103
103
|
const review = useReview(props);
|
|
104
104
|
const titleId = useId();
|
|
105
|
-
return (_jsxs("section", { "aria-labelledby": titleId, "data-assist": "review", className: "flex min-w-0 flex-col overflow-clip rounded-box border border-dashed border-info/40 bg-surface", children: [_jsxs("header", { className: "flex min-w-0 flex-col gap-1 border-b border-line px-4 py-3", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1", children: [_jsx(AssistLabel, {}), source === undefined ? null : _jsx(AssistSourceLink, { source: source })] }), _jsx("
|
|
105
|
+
return (_jsxs("section", { "aria-labelledby": titleId, "data-assist": "review", className: "flex min-w-0 flex-col overflow-clip rounded-box border border-dashed border-info/40 bg-surface", children: [_jsxs("header", { className: "flex min-w-0 flex-col gap-1 border-b border-line px-4 py-3", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1", children: [_jsx(AssistLabel, {}), source === undefined ? null : _jsx(AssistSourceLink, { source: source })] }), _jsx("h2", { id: titleId, className: "text-lead font-semibold text-ink", children: title ?? copy.assist.review }), summary === undefined ? null : (_jsx("p", { className: "max-w-prose text-meta text-muted", children: summary }))] }), _jsx(ReviewBody, { review: review }), _jsx("footer", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2 border-t border-line bg-ground px-4 py-2.5", children: _jsx(ReviewActions, { review: review, emphasis: emphasis, onDiscard: onDiscard }) })] }));
|
|
106
106
|
}
|
|
107
107
|
ChangeReview.Group = Group;
|
|
108
108
|
ChangeReview.Change = Change;
|
|
@@ -79,7 +79,10 @@ export function RecordCalendar({ items, keyOf, titleOf, startOf, endOf, allDayOf
|
|
|
79
79
|
listPlugin,
|
|
80
80
|
interactionPlugin,
|
|
81
81
|
formaThemePlugin,
|
|
82
|
-
], locales: [esLocale], locale: copy.locale.toLowerCase().startsWith("es") ? "es" : "en", initialView: VIEW[view], ...(date === undefined ? {} : { initialDate: date }),
|
|
82
|
+
], locales: [esLocale], locale: copy.locale.toLowerCase().startsWith("es") ? "es" : "en", initialView: VIEW[view], ...(date === undefined ? {} : { initialDate: date }),
|
|
83
|
+
/* «Hoy» is what the button SAYS, and so what it is called: the locale's hint named
|
|
84
|
+
it «Este mes» — a name that hides the visible word from voice control. */
|
|
85
|
+
todayText: copy.calendar.today, todayHint: () => copy.calendar.today, headerToolbar: {
|
|
83
86
|
start: "prev,next today",
|
|
84
87
|
center: "title",
|
|
85
88
|
end: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
|
@@ -29,7 +29,10 @@ function Facts({ fields, columns, layout, }) {
|
|
|
29
29
|
/* The note's card goes INSIDE the value: a `<dl>` holds only its terms and values.
|
|
30
30
|
With a note the value is a flex box, so the absolute card keeps the value's top
|
|
31
31
|
as its static position while being read after it. */
|
|
32
|
-
const value = (_jsxs("dd", { ...(noteId === undefined ? {} : { "aria-describedby": noteId }),
|
|
32
|
+
const value = (_jsxs("dd", { ...(noteId === undefined ? {} : { "aria-describedby": noteId }),
|
|
33
|
+
/* `wrap-anywhere`: an email or a code has no space to break at, and in two
|
|
34
|
+
columns it ran over the fact beside it. */
|
|
35
|
+
className: `min-w-0 text-meta wrap-anywhere ${card === null ? "" : "flex"} ${empty ? "text-muted italic" : "text-ink"} ${field.mono === true ? "font-mono tabular-nums" : ""}`, children: [empty ? "—" : field.value, card] }));
|
|
33
36
|
const name = (_jsxs("dt", { className: `flex items-center gap-1 text-micro text-muted ${horizontal ? "shrink-0" : "min-w-0"}`, children: [_jsx("span", { children: field.label }), field.hint === undefined ? null : _jsx(Hint, { children: field.hint }), field.note === undefined ? null : _jsx(NoteFlag, { note: field.note })] }));
|
|
34
37
|
if (horizontal) {
|
|
35
38
|
/* A header fact is a pair: the number sits next to its name. The
|
|
@@ -90,5 +93,5 @@ export function SheetGroup({ title, hint, icon: Icon, children, }) {
|
|
|
90
93
|
/* A group title is ABOVE its labels, not one of them: at the labels' size and weight
|
|
91
94
|
«Cliente» the group read like «Cliente» the field. One step up in size and weight,
|
|
92
95
|
and a hairline that says where the group starts. */
|
|
93
|
-
_jsxs("header", { className: "flex min-w-0 items-center gap-2 border-b border-line pb-1.5", children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-sm) shrink-0 text-muted", strokeWidth: 1.75, "aria-hidden": true })), _jsx("
|
|
96
|
+
_jsxs("header", { className: "flex min-w-0 items-center gap-2 border-b border-line pb-1.5", children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-sm) shrink-0 text-muted", strokeWidth: 1.75, "aria-hidden": true })), _jsx("h2", { className: "min-w-0 truncate text-body font-semibold text-ink", children: title }), hint === undefined ? null : _jsx(Hint, { children: hint })] })) : hint === undefined ? null : (_jsx(Hint, { children: hint })), _jsx("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: children })] }));
|
|
94
97
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { type FormMode } from "../../foundations/form.js";
|
|
1
2
|
import { type DialogContext } from "../../primitives/dialog/Dialog.js";
|
|
2
|
-
import { type FormMode } from "../../primitives/field/mode.js";
|
|
3
3
|
import type { FlagTone } from "../../primitives/notice/Flag.js";
|
|
4
4
|
import type { DataColumn } from "../../primitives/table/DataTable.js";
|
|
5
5
|
import { type EditColumn, type EditLayout } from "../../primitives/table/EditableTable.js";
|
|
@@ -4,12 +4,12 @@ import { commitDraft, filterLines, planRemoval, } from "./lines.js";
|
|
|
4
4
|
import { useColumnControl, } from "../listing/columns.js";
|
|
5
5
|
import { ColumnsMenu } from "../listing/ColumnsMenu.js";
|
|
6
6
|
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
7
|
+
import { FieldIdsContext } from "../../foundations/form.js";
|
|
8
|
+
import { useFormMode } from "../../foundations/form.js";
|
|
7
9
|
import { Button, IconButton } from "../../primitives/button/Button.js";
|
|
8
10
|
import { SearchField } from "../../primitives/choice/SearchField.js";
|
|
9
11
|
import { Dialog } from "../../primitives/dialog/Dialog.js";
|
|
10
|
-
import { FieldIdsContext } from "../../primitives/field/context.js";
|
|
11
12
|
import { TextInput } from "../../primitives/field/Field.js";
|
|
12
|
-
import { useFormMode } from "../../primitives/field/mode.js";
|
|
13
13
|
import { Empty } from "../../primitives/notice/Notice.js";
|
|
14
14
|
import { useToast } from "../../primitives/notice/Toast.js";
|
|
15
15
|
import { EditableTable, } from "../../primitives/table/EditableTable.js";
|
|
@@ -101,7 +101,7 @@ export function ListingBar({ actions, title, description, counters, selection, s
|
|
|
101
101
|
(count === undefined ? null : (_jsx("span", { role: "status", className: "shrink-0 text-meta text-muted tabular-nums", children: count === 1
|
|
102
102
|
? copy.list.countOne
|
|
103
103
|
: fillCopy(copy.list.count, { count: count.toLocaleString(copy.locale) }) })));
|
|
104
|
-
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: [title === undefined ? null : (_jsx("
|
|
104
|
+
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: [title === undefined ? null : (_jsx("h2", { className: "min-w-0 shrink-0 truncate text-body font-semibold text-ink", children: title })), 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 }))] }) }));
|
|
105
105
|
}
|
|
106
106
|
/* El campo de búsqueda va en el CENTRO de la línea, y centrado contra la
|
|
107
107
|
línea: pide `--container-search` y los dos lados se reparten lo que sobra
|
|
@@ -19,10 +19,14 @@ export declare function RecordCard({ title, subtitle, tags, meta, figure, media,
|
|
|
19
19
|
tags?: ReactNode;
|
|
20
20
|
meta?: ReactNode;
|
|
21
21
|
figure?: ReactNode;
|
|
22
|
+
/** The picture of the record. A product's is a square (`Thumb`); a PERSON's is round, with
|
|
23
|
+
* the initials when there is no photo — `person`, like `RelationCard`'s. `alt` names the
|
|
24
|
+
* person when the title is not plain text. */
|
|
22
25
|
media?: {
|
|
23
26
|
src?: string;
|
|
24
27
|
alt?: string;
|
|
25
28
|
icon?: LucideIcon;
|
|
29
|
+
person?: true;
|
|
26
30
|
};
|
|
27
31
|
/** The card's last line, its controls: priority, kanban state, next activity, the owner's
|
|
28
32
|
* avatar — each its own button, pressable without opening the card. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Thumb } from "../../primitives/cell/CellStack.js";
|
|
4
|
+
import { Avatar } from "../../primitives/value/Avatar.js";
|
|
4
5
|
/**
|
|
5
6
|
* One record as a card — the board presentation of a list.
|
|
6
7
|
*
|
|
@@ -21,7 +22,9 @@ export function RecordCard({ title, subtitle, tags, meta, figure, media, footer,
|
|
|
21
22
|
const titleText = _jsx("span", { className: "min-w-0 truncate", children: title });
|
|
22
23
|
return (_jsxs("div", { className: `relative flex w-full min-w-0 flex-col gap-2 rounded-box border border-line bg-surface p-4 text-left ${onOpen === undefined
|
|
23
24
|
? ""
|
|
24
|
-
: "transition-colors duration-(--duration-instant) focus-within:border-line-strong hover:border-line-strong"}`, children: [media === undefined ? null :
|
|
25
|
+
: "transition-colors duration-(--duration-instant) focus-within:border-line-strong hover:border-line-strong"}`, children: [media === undefined ? null : media.person === true ? (
|
|
26
|
+
/* The name is written right below it: the photo's `alt` would say it twice. */
|
|
27
|
+
_jsx("span", { "aria-hidden": true, className: "flex shrink-0", children: _jsx(Avatar, { name: media.alt ?? (typeof title === "string" ? title : ""), size: "xl", ...(media.src === undefined ? {} : { src: media.src }) }) })) : (_jsx(Thumb, { size: "lg", ...(media.src === undefined ? {} : { src: media.src }), ...(media.alt === undefined ? {} : { alt: media.alt }), ...(media.icon === undefined ? {} : { icon: media.icon }) })), tags === undefined ? null : _jsx("div", { className: "flex flex-wrap gap-1.5", children: tags }), _jsxs("div", { className: "flex min-w-0 items-baseline justify-between gap-3", children: [onOpen === undefined ? (_jsx("p", { className: "flex min-w-0 text-lead font-medium text-ink", children: titleText })) : (_jsx("button", { type: "button", onClick: onOpen, className: "flex min-w-0 text-left text-lead font-medium text-ink outline-none after:absolute after:inset-0 after:rounded-box focus-visible:after:outline-2 focus-visible:after:outline-primary", children: titleText })), figure === undefined ? null : (_jsx("p", { className: "shrink-0 text-lead font-semibold tabular-nums text-ink", children: figure }))] }), subtitle === undefined ? null : (
|
|
25
28
|
/* Above the stretched title, at its own width: the subtitle may be a link to ANOTHER
|
|
26
29
|
record (the customer), which must open that one and not the card. */
|
|
27
30
|
_jsx("p", { className: "relative z-10 -mt-1.5 w-fit max-w-full min-w-0 truncate text-meta text-muted", children: subtitle })), meta === undefined ? null : (_jsx("p", { className: "text-micro tabular-nums text-muted", children: meta })), footer === undefined ? null : (_jsx("div", { className: "relative z-10 flex min-w-0 items-center gap-1.5", children: footer }))] }));
|
|
@@ -127,3 +127,10 @@ export declare function GroupHeader({ field, label, count, detail, depth, open,
|
|
|
127
127
|
open?: boolean;
|
|
128
128
|
onToggle?: () => void;
|
|
129
129
|
}): import("react").JSX.Element;
|
|
130
|
+
/** Under a pile that shows its first page: «Ver 38 más». Indented like the pile's rows. */
|
|
131
|
+
export declare function GroupMore({ left, depth, onMore, }: {
|
|
132
|
+
/** How many the pile counts that are not here yet. */
|
|
133
|
+
left: number;
|
|
134
|
+
depth?: number;
|
|
135
|
+
onMore: () => void;
|
|
136
|
+
}): import("react").JSX.Element;
|
|
@@ -127,7 +127,7 @@ export function RowHeader({ name, meta, tags, withSelection = false, withIcon =
|
|
|
127
127
|
export function GroupHeader({ field, label, count, detail, depth = 0, open = true, onToggle, }) {
|
|
128
128
|
const d = useDensity();
|
|
129
129
|
const copy = useUiCopy();
|
|
130
|
-
const content = (_jsxs(_Fragment, { children: [onToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })), field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted
|
|
130
|
+
const content = (_jsxs(_Fragment, { children: [onToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })), field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted", children: field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: label }), count === undefined ? null : (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro tabular-nums text-muted", children: count.toLocaleString(copy.locale) })), detail === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro tabular-nums text-muted", children: detail }))] }));
|
|
131
131
|
/* La sangría va en el relleno de la izquierda y no en un margen: el
|
|
132
132
|
encabezado tiene fondo y borde inferior, y un margen los cortaría antes del
|
|
133
133
|
borde de la lista — la banda dejaría de leerse como una franja. El hover
|
|
@@ -137,3 +137,9 @@ export function GroupHeader({ field, label, count, detail, depth = 0, open = tru
|
|
|
137
137
|
const mark = (_jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute inset-x-1 inset-y-0.5 rounded-control group-hover/head:bg-ink/4" }));
|
|
138
138
|
return onToggle === undefined ? (_jsx("div", { className: box, style: indent, children: content })) : (_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, style: indent, className: `group/head relative ${box} w-full text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`, children: [mark, content] }));
|
|
139
139
|
}
|
|
140
|
+
/** Under a pile that shows its first page: «Ver 38 más». Indented like the pile's rows. */
|
|
141
|
+
export function GroupMore({ left, depth = 0, onMore, }) {
|
|
142
|
+
const d = useDensity();
|
|
143
|
+
const copy = useUiCopy();
|
|
144
|
+
return (_jsx("div", { className: `border-b border-line ${byDensity(d, "px-3 py-1", "px-2.5 py-0.5")}`, style: depth === 0 ? undefined : { paddingLeft: `calc(var(--spacing-indent) * ${depth})` }, children: _jsx("button", { type: "button", onClick: onMore, className: "rounded-control text-meta text-primary transition-colors duration-(--duration-instant) hover:underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary", children: fillCopy(copy.table.pileMore, { count: left.toLocaleString(copy.locale) }) }) }));
|
|
145
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Surface } from "../../foundations/types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ComponentType } from "react";
|
|
3
3
|
/** How the same list is shown: table, board, timeline.
|
|
4
4
|
*
|
|
5
5
|
* The icons go together and touching —a segmented group and not three loose
|
|
@@ -13,8 +13,13 @@ import type { LucideIcon } from "lucide-react";
|
|
|
13
13
|
export interface ViewOption {
|
|
14
14
|
id: string;
|
|
15
15
|
label: string;
|
|
16
|
-
icon
|
|
16
|
+
/** Any icon component — lucide's are what the package uses, not what it requires. */
|
|
17
|
+
icon: ViewIcon;
|
|
17
18
|
}
|
|
19
|
+
export type ViewIcon = ComponentType<{
|
|
20
|
+
className?: string;
|
|
21
|
+
strokeWidth?: number;
|
|
22
|
+
}>;
|
|
18
23
|
export declare function ViewSwitcher({ options, current, onSelect, surface, }: {
|
|
19
24
|
options: readonly ViewOption[];
|
|
20
25
|
current: string;
|
|
@@ -10,7 +10,7 @@ export { GroupHeader, Row } from "./Row.js";
|
|
|
10
10
|
export { type Selection, useSelection } from "./selection.js";
|
|
11
11
|
export { type SetControl, useSetControl } from "./set.js";
|
|
12
12
|
export { toggleCollapsed, type TreeControl, useTreeControl, } from "./tree.js";
|
|
13
|
-
export { type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
|
|
13
|
+
export { type ViewIcon, type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
|
|
14
14
|
export { type BoardColumn, BoardColumns, type BoardStages, } from "./BoardColumns.js";
|
|
15
15
|
export { type GroupingControl, useGroupingControl, } from "./grouping.js";
|
|
16
16
|
export { GroupingLevels, type GroupingLevelsProps, } from "./GroupingLevels.js";
|
|
@@ -33,7 +33,10 @@ import { type Pages } from "./pages.js";
|
|
|
33
33
|
* también; lo que se lee está en el cruce. Tampoco se
|
|
34
34
|
* ordena: el orden de cada eje es el de sus claves.
|
|
35
35
|
*/
|
|
36
|
-
export type
|
|
36
|
+
export type BuiltInPresent = "rows" | "table" | "board" | "sheet" | "report" | "pivot" | "calendar" | "gantt" | "timeline";
|
|
37
|
+
/** A presentation of a list: one the package draws, or the `id` of a `List.Present` the
|
|
38
|
+
* application wrote. `string & {}` keeps the built-in names in autocomplete. */
|
|
39
|
+
export type ListPresent = BuiltInPresent | (string & {});
|
|
37
40
|
export interface SetControl<Row, Present extends string = ListPresent> extends Pages<Row> {
|
|
38
41
|
present: Present;
|
|
39
42
|
setPresent: (next: Present) => void;
|
|
@@ -69,7 +69,11 @@ export function Breadcrumb({ crumbs, surface: surfaceProp, folded = 3, }) {
|
|
|
69
69
|
const shown = folds
|
|
70
70
|
? [crumbs[0], null, crumbs[crumbs.length - 1]]
|
|
71
71
|
: crumbs;
|
|
72
|
-
|
|
72
|
+
/* Named by WHERE it ends, not only by what it is: two records side by side (panes, a
|
|
73
|
+
panel next to the page) are two trails, and a screen reader lists landmarks by name. */
|
|
74
|
+
const last = crumbs[crumbs.length - 1];
|
|
75
|
+
const name = last === undefined ? copy.record.location : `${copy.record.location}: ${last.label}`;
|
|
76
|
+
return (_jsx("nav", { "aria-label": name, className: "flex min-w-0", children: _jsx(Breadcrumbs, { "aria-label": name, className: "flex min-w-0 flex-wrap items-center gap-1", children: shown.map((crumb, i) => {
|
|
73
77
|
const key = crumb === null ? "folded" : `${i}-${crumb.label}`;
|
|
74
78
|
const last = i === shown.length - 1;
|
|
75
79
|
return (_jsxs(AriaBreadcrumb, { id: key, className: "flex min-w-0 items-center gap-1", children: [i === 0 ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 ${edge}`, strokeWidth: 2, "aria-hidden": true })), crumb === null ? (_jsx(Menu, { label: copy.record.location, title: copy.record.location, icon: MoreHorizontal, iconOnly: true, chevron: false, variant: "text", size: "sm", surface: surface === "chrome" ? "chrome" : "canvas", align: "start", groups: [
|
|
@@ -7,9 +7,13 @@ export interface DirtyState {
|
|
|
7
7
|
/** What gets saved, when it is not obvious: «guardar la factura», «guardar la
|
|
8
8
|
* regla». */
|
|
9
9
|
label?: string;
|
|
10
|
-
/** Why it CANNOT be saved yet. Present = the button is off and the reason is
|
|
11
|
-
*
|
|
10
|
+
/** Why it CANNOT be saved yet. Present = the button is off and the reason is written
|
|
11
|
+
* where the «unsaved» mark was — a button that is off with no reason is a wall, and a
|
|
12
|
+
* reason in a `title` reaches neither the keyboard nor a finger. */
|
|
12
13
|
blocked?: string;
|
|
14
|
+
/** How many fields changed: «2 cambios sin guardar» instead of a bare «sin guardar». The
|
|
15
|
+
* fields themselves are marked by their `Field changed`. */
|
|
16
|
+
changes?: number;
|
|
13
17
|
}
|
|
14
18
|
/** Save and discard, in the rail and in place of the action buttons.
|
|
15
19
|
*
|
|
@@ -20,7 +24,17 @@ export interface DirtyState {
|
|
|
20
24
|
* The «sin guardar» warning is text and not just a color: whoever is leaving
|
|
21
25
|
* the screen has to be able to read what is going on, not deduce it from a
|
|
22
26
|
* dot. */
|
|
23
|
-
export declare function DirtyActions({ onSave, onDiscard, saving, label, blocked, }: DirtyState): import("react").JSX.Element;
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
+
export declare function DirtyActions({ onSave, onDiscard, saving, label, blocked, changes, }: DirtyState): import("react").JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the record has JUST been saved: `dirty` went from saving straight to gone.
|
|
30
|
+
*
|
|
31
|
+
* Context: saving ended with the rail silently swapping Save for the usual actions — the
|
|
32
|
+
* screen changed and said nothing, so each application wrote its own toast, or none. A
|
|
33
|
+
* failed save does not count: `saving` turns off and `dirty` stays.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useJustSaved(dirty: DirtyState | undefined): boolean;
|
|
36
|
+
/** «✓ Guardado», where Save was, for a moment. The live region is always there — a region
|
|
37
|
+
* inserted together with its words is not reliably announced. */
|
|
38
|
+
export declare function SavedMark({ saved }: {
|
|
39
|
+
saved: boolean;
|
|
40
|
+
}): import("react").JSX.Element;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, 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 { bySurface, useSurface } from "../../foundations/surface.js";
|
|
5
5
|
import { Button } from "../../primitives/button/Button.js";
|
|
6
|
-
import { Save, Undo2 } from "lucide-react";
|
|
6
|
+
import { Check, Save, Undo2 } from "lucide-react";
|
|
7
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
7
8
|
/** Save and discard, in the rail and in place of the action buttons.
|
|
8
9
|
*
|
|
9
10
|
* Context: discard goes to the LEFT of save and with the quieter look. It is
|
|
@@ -13,10 +14,60 @@ import { Save, Undo2 } from "lucide-react";
|
|
|
13
14
|
* The «sin guardar» warning is text and not just a color: whoever is leaving
|
|
14
15
|
* the screen has to be able to read what is going on, not deduce it from a
|
|
15
16
|
* dot. */
|
|
16
|
-
export function DirtyActions({ onSave, onDiscard, saving = false, label, blocked, }) {
|
|
17
|
+
export function DirtyActions({ onSave, onDiscard, saving = false, label, blocked, changes, }) {
|
|
17
18
|
const copy = useUiCopy();
|
|
19
|
+
const blockedId = useId();
|
|
18
20
|
/* It follows the rail it sits on instead of assuming the dark one: on a light rail the
|
|
19
21
|
chrome palette was white text on white. */
|
|
20
22
|
const surface = useSurface();
|
|
21
|
-
return (_jsxs(_Fragment, { children: [_jsx("span", { className: `inline-flex h-(--size-control-md)
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { ...(blocked === undefined ? {} : { id: blockedId }), className: `inline-flex h-(--size-control-md) min-w-0 shrink items-center gap-1.5 truncate rounded-control px-2.5 text-meta font-medium ${bySurface(surface, "bg-warn/12 text-warn", "bg-chrome-warn/15 text-chrome-warn")}`, children: blocked ??
|
|
24
|
+
(changes === undefined || changes === 0
|
|
25
|
+
? copy.record.unsaved
|
|
26
|
+
: fillCopy(changes === 1 ? copy.record.unsavedOne : copy.record.unsavedMany, {
|
|
27
|
+
count: changes.toLocaleString(copy.locale),
|
|
28
|
+
})) }), _jsx(Button, { surface: surface, onClick: onDiscard, disabled: saving, icon: Undo2, children: copy.record.discard }), _jsx(Button, { role: "primary", surface: surface, onClick: onSave, busy: saving, disabled: blocked !== undefined, icon: Save, ...(blocked === undefined ? {} : { "aria-describedby": blockedId }), children: saving ? copy.record.saving : (label ?? copy.record.save) })] }));
|
|
29
|
+
}
|
|
30
|
+
/** The one action a view leads with: the element written with `role="primary"` — the
|
|
31
|
+
* same prop that paints it gold and that `make smoke` counts, so there is no second way to
|
|
32
|
+
* say «this is the main one» that could disagree with the first. */
|
|
33
|
+
/** How long «Guardado» stays: long enough to be seen without looking for it, short enough
|
|
34
|
+
* not to stay as one more thing on the rail. */
|
|
35
|
+
const SAVED_FOR = 2400;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the record has JUST been saved: `dirty` went from saving straight to gone.
|
|
38
|
+
*
|
|
39
|
+
* Context: saving ended with the rail silently swapping Save for the usual actions — the
|
|
40
|
+
* screen changed and said nothing, so each application wrote its own toast, or none. A
|
|
41
|
+
* failed save does not count: `saving` turns off and `dirty` stays.
|
|
42
|
+
*/
|
|
43
|
+
export function useJustSaved(dirty) {
|
|
44
|
+
const wasSaving = useRef(false);
|
|
45
|
+
const [saved, setSaved] = useState(false);
|
|
46
|
+
const saving = dirty?.saving === true;
|
|
47
|
+
const present = dirty !== undefined;
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (saving) {
|
|
50
|
+
wasSaving.current = true;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (present) {
|
|
54
|
+
wasSaving.current = false;
|
|
55
|
+
setSaved(false);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!wasSaving.current)
|
|
59
|
+
return;
|
|
60
|
+
wasSaving.current = false;
|
|
61
|
+
setSaved(true);
|
|
62
|
+
const timer = setTimeout(() => setSaved(false), SAVED_FOR);
|
|
63
|
+
return () => clearTimeout(timer);
|
|
64
|
+
}, [saving, present]);
|
|
65
|
+
return saved;
|
|
66
|
+
}
|
|
67
|
+
/** «✓ Guardado», where Save was, for a moment. The live region is always there — a region
|
|
68
|
+
* inserted together with its words is not reliably announced. */
|
|
69
|
+
export function SavedMark({ saved }) {
|
|
70
|
+
const copy = useUiCopy();
|
|
71
|
+
const surface = useSurface();
|
|
72
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { role: "status", className: "sr-only", children: saved ? copy.record.saved : "" }), saved ? (_jsxs("span", { "aria-hidden": true, className: `inline-flex h-(--size-control-md) shrink-0 items-center gap-1.5 rounded-control px-2.5 text-meta font-medium transition-[opacity,translate] duration-(--duration-quick) ease-(--ease-out) starting:-translate-y-0.5 starting:opacity-0 ${bySurface(surface, "bg-good/12 text-good", "bg-chrome-good/15 text-chrome-good")}`, children: [_jsx(Check, { className: "size-(--size-icon-sm)", strokeWidth: 2.5 }), copy.record.saved] })) : null] }));
|
|
22
73
|
}
|
|
@@ -2,7 +2,7 @@ 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 type
|
|
5
|
+
import { type ReactNode } from "react";
|
|
6
6
|
export type { Surface };
|
|
7
7
|
export declare function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface, smart, aspects, dirty, activity, activityLayout, width, ribbon, blocking, indicator, children, }: {
|
|
8
8
|
crumbs?: readonly Crumb[];
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Breadcrumb } from "./Breadcrumb.js";
|
|
4
|
-
import { DirtyActions, } from "./DirtyActions.js";
|
|
4
|
+
import { DirtyActions, SavedMark, useJustSaved, } from "./DirtyActions.js";
|
|
5
5
|
import { isPrimary, ThumbBar, thumbPlan } from "./ThumbBar.js";
|
|
6
6
|
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
7
7
|
import { SurfaceProvider } from "../../foundations/surface.js";
|
|
8
8
|
import { Line } from "../../primitives/block/Line.js";
|
|
9
9
|
import { Tag } from "../../primitives/tag/Tag.js";
|
|
10
|
+
import { useEffect, useRef } from "react";
|
|
10
11
|
export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface = "canvas", smart, aspects, dirty, activity, activityLayout = "auto", width = "reading", ribbon, blocking, indicator, children, }) {
|
|
11
12
|
const d = useDensity();
|
|
12
|
-
|
|
13
|
+
/* «Guardado» is said where Save was; a record whose rail exists only while it has changes
|
|
14
|
+
keeps it for that moment, or the answer would leave with the question. */
|
|
15
|
+
const saved = useJustSaved(dirty);
|
|
16
|
+
/* Save is gone the moment it has saved, and the focus that was on it fell to the page —
|
|
17
|
+
a keyboard was left at the top of the document. It comes back to the record's name:
|
|
18
|
+
where reading the record starts, and announced together with «Guardado». */
|
|
19
|
+
const nameRef = useRef(null);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!saved)
|
|
22
|
+
return;
|
|
23
|
+
const lost = document.activeElement === null || document.activeElement === document.body;
|
|
24
|
+
if (lost)
|
|
25
|
+
nameRef.current?.focus({ preventScroll: true });
|
|
26
|
+
}, [saved]);
|
|
27
|
+
const hasRail = saved ||
|
|
28
|
+
crumbs !== undefined ||
|
|
13
29
|
pager !== undefined ||
|
|
14
30
|
actions !== undefined ||
|
|
15
31
|
status !== undefined ||
|
|
@@ -40,12 +56,14 @@ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Ico
|
|
|
40
56
|
shadow. It exists on the PAGE, where the chrome rail is a real band
|
|
41
57
|
(crumbs, pager, actions). A drawer is compact so `withRail` is already
|
|
42
58
|
false. A dialog is comfortable but has no crumbs or actions — and it IS
|
|
43
|
-
already a card. Status alone is not a page.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
already a card. Status alone is not a page.
|
|
60
|
+
|
|
61
|
+
And `dirty` does not decide it. Whether the record sits on paper is a fact of WHERE it
|
|
62
|
+
is mounted, and that does not change while someone types; `dirty` does, with the first
|
|
63
|
+
letter. Deciding by it swapped the whole tree under the paper at that letter — the field
|
|
64
|
+
being written was unmounted and lost the focus. Save and Discard still appear: in the
|
|
65
|
+
rail, which is a sibling and moves nothing. */
|
|
66
|
+
const paper = withRail && (crumbs !== undefined || pager !== undefined || actions !== undefined);
|
|
49
67
|
/* ONE column for the rail and the page: the rail used to span the screen while the sheet
|
|
50
68
|
stopped at 72rem, so on a wide desk the buttons sat 250 px left of the paper they act on
|
|
51
69
|
and the record read as two things. */
|
|
@@ -58,7 +76,7 @@ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Ico
|
|
|
58
76
|
/* `display`, not `title`: the record's name is the entry point of the
|
|
59
77
|
screen. At the title size (19 px over a 13 px body, 1.46:1) nothing on
|
|
60
78
|
the sheet stood out and the eye had nowhere to start. */
|
|
61
|
-
_jsx("h1", { className: "min-w-0 truncate text-display font-semibold text-ink", children: title })), tag === undefined ? null : _jsx(Tag, { tone: tag.tone, children: tag.label })] }), subtitle === undefined || !withIdentity ? null : (_jsx("p", { className: "mt-0.5 truncate text-lead text-ink", children: subtitle }))] })] }), figure === undefined || !withIdentity ? null : (_jsxs("div", { className: "ml-auto flex shrink-0 flex-col items-end", children: [_jsx("span", { className: "text-micro font-medium text-muted", children: figure.label }), _jsx("span", { className: "text-display font-semibold tabular-nums text-ink", children: figure.value })] }))] }), withRail || status === undefined ? null : (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: status })), aspects === undefined ? null : (
|
|
79
|
+
_jsx("h1", { ref: nameRef, tabIndex: -1, className: "min-w-0 truncate text-display font-semibold text-ink outline-0", children: title })), tag === undefined ? null : _jsx(Tag, { tone: tag.tone, children: tag.label })] }), subtitle === undefined || !withIdentity ? null : (_jsx("p", { className: "mt-0.5 truncate text-lead text-ink", children: subtitle }))] })] }), figure === undefined || !withIdentity ? null : (_jsxs("div", { className: "ml-auto flex shrink-0 flex-col items-end", children: [_jsx("span", { className: "text-micro font-medium text-muted", children: figure.label }), _jsx("span", { className: "text-display font-semibold tabular-nums text-ink", children: figure.value })] }))] }), withRail || status === undefined ? null : (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: status })), aspects === undefined ? null : (
|
|
62
80
|
/* The line is the sheet's mouth. In comfortable it walks to the
|
|
63
81
|
edges; the current tab sits on it and opens into the page below. */
|
|
64
82
|
_jsx("div", { className: `border-b border-line-strong ${byDensity(d, "-mx-4 px-4", "")}`, children: aspects }))] }));
|
|
@@ -68,14 +86,15 @@ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Ico
|
|
|
68
86
|
it. The line wraps on a phone, so it is never pushed out of reach. */
|
|
69
87
|
smart === undefined && indicator === undefined ? undefined : (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-center gap-2", children: [smart, indicator] })), end: pager }), actions === undefined &&
|
|
70
88
|
status === undefined &&
|
|
71
|
-
dirty === undefined
|
|
89
|
+
dirty === undefined &&
|
|
90
|
+
!saved ? null : (
|
|
72
91
|
/* Row 2 — WHAT I CAN DO and WHERE IT IS AT: actions stuck to the
|
|
73
92
|
left edge, the history and the state stuck to the right one. No
|
|
74
93
|
`flex-wrap`: wrapping split the row in two and the state ended up
|
|
75
94
|
below the actions, which is another hierarchy. */
|
|
76
|
-
_jsxs("div", { className: "flex min-w-0 items-start gap-x-4", children: [dirty !== undefined ? (_jsx("div", { className: "flex shrink-0 items-center gap-1.5 @max-handheld/record:hidden", children: _jsx(DirtyActions, { ...dirty }) })) : actions === undefined ?
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
_jsxs("div", { className: "flex min-w-0 items-start gap-x-4", children: [dirty !== undefined ? (_jsx("div", { className: "flex shrink-0 items-center gap-1.5 @max-handheld/record:hidden", children: _jsx(DirtyActions, { ...dirty }) })) : actions === undefined ? (_jsx(SavedMark, { saved: saved })) : (_jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [primaries.length === 0
|
|
96
|
+
? actions
|
|
97
|
+
: railActions.map((node) => isPrimary(node) ? (_jsx("span", { "data-thumb-moves": "", className: "contents @max-handheld/record:hidden", children: node }, node.key)) : (node)), _jsx(SavedMark, { saved: saved })] })), status === undefined ? null : (_jsx("div", { className: "ml-auto flex min-w-0 flex-col items-end gap-1.5", children: status }))] }))] }) }) }) })) : null, paper ? (
|
|
79
98
|
/* ==================================================================== *
|
|
80
99
|
* THE PAGE — [sheet | Actividad], on the ground.
|
|
81
100
|
*
|
|
@@ -17,5 +17,5 @@ const SOLAPAS = ["w-14", "w-16", "w-20"];
|
|
|
17
17
|
export function RecordSkeleton({ label }) {
|
|
18
18
|
const d = useDensity();
|
|
19
19
|
const page = d === "comfortable";
|
|
20
|
-
return (_jsxs("article", { "aria-busy": "true", className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "sr-only", children: label }), page ? (_jsxs("div", { "aria-hidden": true, className: "flex items-center gap-3 bg-chrome px-3.5 py-2.5", children: [_jsx("span", { className: "h-3 w-24 rounded-control bg-chrome-ink/15" }), _jsx("span", { className: "h-3 w-40 rounded-control bg-chrome-ink/10" }), _jsx("span", { className: "ml-auto h-(--size-control-sm) w-28 rounded-control bg-chrome-ink/15" })] })) : null, _jsx("div", { "aria-hidden": true, className: page ? "border border-line bg-surface" : "bg-surface", children: _jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-3 px-4 pt-3.5 pb-4", "gap-2 py-2")}`, children: [_jsx("span", { className: "h-5 w-72 max-w-full rounded-control bg-ink/8" }), _jsx("div", { className: "flex flex-wrap gap-x-6 gap-y-2", children: HECHOS.map((width) => (_jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "h-2.5 w-10 rounded-control bg-ink/5" }), _jsx("span", { className: `h-3.5 rounded-control bg-ink/8 ${width}` })] }, width))) }), _jsx("div", { className: "flex gap-4 border-b border-line pb-2", children: SOLAPAS.map((width, i) => (_jsx("span", { className: `h-3 rounded-control ${i === 0 ? "bg-ink/10" : "bg-ink/5"} ${width}` }, width))) }), _jsx("span", { className: "h-24 rounded-box bg-ink/5" })] }) })] }));
|
|
20
|
+
return (_jsxs("article", { "aria-busy": "true", className: "flex min-w-0 animate-reveal-late flex-col", children: [_jsx("span", { className: "sr-only", children: label }), page ? (_jsxs("div", { "aria-hidden": true, className: "flex items-center gap-3 bg-chrome px-3.5 py-2.5", children: [_jsx("span", { className: "h-3 w-24 rounded-control bg-chrome-ink/15" }), _jsx("span", { className: "h-3 w-40 rounded-control bg-chrome-ink/10" }), _jsx("span", { className: "ml-auto h-(--size-control-sm) w-28 rounded-control bg-chrome-ink/15" })] })) : null, _jsx("div", { "aria-hidden": true, className: page ? "border border-line bg-surface" : "bg-surface", children: _jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-3 px-4 pt-3.5 pb-4", "gap-2 py-2")}`, children: [_jsx("span", { className: "h-5 w-72 max-w-full rounded-control bg-ink/8" }), _jsx("div", { className: "flex flex-wrap gap-x-6 gap-y-2", children: HECHOS.map((width) => (_jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "h-2.5 w-10 rounded-control bg-ink/5" }), _jsx("span", { className: `h-3.5 rounded-control bg-ink/8 ${width}` })] }, width))) }), _jsx("div", { className: "flex gap-4 border-b border-line pb-2", children: SOLAPAS.map((width, i) => (_jsx("span", { className: `h-3 rounded-control ${i === 0 ? "bg-ink/10" : "bg-ink/5"} ${width}` }, width))) }), _jsx("span", { className: "h-24 rounded-box bg-ink/5" })] }) })] }));
|
|
21
21
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { blankGroup, ExpressionView, treeFromFilter, treeToFilter, WhereTree, } from "./FilterBuilder.js";
|
|
4
4
|
import { JsonEditor } from "./JsonEditor.js";
|
|
5
5
|
import { filterableNames, } from "./model.js";
|
|
6
|
-
import {
|
|
6
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
7
7
|
import { GRAIN_ORDER } from "../../foundations/grouping.js";
|
|
8
8
|
import { useSurface } from "../../foundations/surface.js";
|
|
9
9
|
import { Rule } from "../../primitives/block/Block.js";
|
|
@@ -174,11 +174,11 @@ export function CriteriaBuilder({ model, initial, focus, onApply, onCancel, }) {
|
|
|
174
174
|
parsed = JSON.parse(text);
|
|
175
175
|
}
|
|
176
176
|
catch {
|
|
177
|
-
setRawError(
|
|
177
|
+
setRawError(copy.builder.invalidJson);
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
181
|
-
setRawError(
|
|
181
|
+
setRawError(copy.builder.notCriteria);
|
|
182
182
|
return;
|
|
183
183
|
}
|
|
184
184
|
setRawError(null);
|