@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Breadcrumb } from "./Breadcrumb.js";
|
|
4
4
|
import { DirtyActions, } from "./DirtyActions.js";
|
|
5
5
|
import { isPrimary, ThumbBar, thumbPlan } from "./ThumbBar.js";
|
|
@@ -7,8 +7,7 @@ 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
|
-
|
|
11
|
-
export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface = "canvas", smart, aspects, dirty, chatter, chatterLayout = "page", children, }) {
|
|
10
|
+
export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface = "canvas", smart, aspects, dirty, activity, activityLayout = "auto", blocking, indicator, children, }) {
|
|
12
11
|
const d = useDensity();
|
|
13
12
|
const hasRail = crumbs !== undefined ||
|
|
14
13
|
pager !== undefined ||
|
|
@@ -24,6 +23,8 @@ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Ico
|
|
|
24
23
|
only thing in the rail that belongs to the RECORD and not to my position in
|
|
25
24
|
front of it. */
|
|
26
25
|
const withRail = hasRail && d === "comfortable";
|
|
26
|
+
/* The activity column at the RIGHT of the sheet when the page is wide (THE PAGE). */
|
|
27
|
+
const beside = activity !== undefined && activityLayout === "auto";
|
|
27
28
|
/* The thumb bar's content (see THE THUMB BAR in `ThumbBar`): dirty first, else the primary. */
|
|
28
29
|
const { rail: railActions, primaries, mode: thumb, } = thumbPlan({ withRail, actions, dirty });
|
|
29
30
|
/* In compact the TITLE is not drawn either, for the same reason as in
|
|
@@ -45,7 +46,7 @@ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Ico
|
|
|
45
46
|
pager !== undefined ||
|
|
46
47
|
actions !== undefined ||
|
|
47
48
|
dirty !== undefined);
|
|
48
|
-
const identity = (_jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-2.5 px-4 pt-3.5", "gap-2 px-0 pt-0")}`, children: [withRail || smart === undefined ? null :
|
|
49
|
+
const identity = (_jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-2.5 px-4 pt-3.5", "gap-2 px-0 pt-0")}`, children: [withRail || (smart === undefined && indicator === undefined) ? null : (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [smart, indicator] })), _jsxs("div", { className: "flex min-w-0 flex-wrap items-start gap-x-5 gap-y-2", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [Icon === undefined || d === "compact" ? null : (_jsx("span", { className: "mt-1 flex size-9 shrink-0 items-center justify-center rounded-box bg-ink/5 text-muted", children: _jsx(Icon, { className: "size-(--size-icon-lg)", strokeWidth: 1.75 }) })), _jsxs("div", { className: "flex min-w-0 flex-col", children: [kicker === undefined || !withIdentity ? null : (_jsx("span", { className: "text-micro font-medium text-muted", children: kicker })), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1", children: [!withIdentity ? null : (
|
|
49
50
|
/* `display`, not `title`: the record's name is the entry point of the
|
|
50
51
|
screen. At the title size (19 px over a 13 px body, 1.46:1) nothing on
|
|
51
52
|
the sheet stood out and the eye had nowhere to start. */
|
|
@@ -53,20 +54,51 @@ export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Ico
|
|
|
53
54
|
/* The line is the sheet's mouth. In comfortable it walks to the
|
|
54
55
|
edges; the current tab sits on it and opens into the page below. */
|
|
55
56
|
_jsx("div", { className: `border-b border-line-strong ${byDensity(d, "-mx-4 px-4", "")}`, children: aspects }))] }));
|
|
56
|
-
return (_jsxs(_Fragment, { children: [withRail ? (_jsx(SurfaceProvider, { value: railSurface, children: _jsx("div", { className: "@container/record", children: _jsxs("div", { className: `flex flex-col gap-2 px-3.5 py-2.5 ${railSurface === "chrome" ? "bg-chrome" : "border-b border-line bg-surface"}`, children: [_jsx(Line, { start: crumbs === undefined || crumbs.length === 0 ? null : (_jsx(Breadcrumb, { crumbs: crumbs, surface: railSurface })), center:
|
|
57
|
+
return (_jsxs(_Fragment, { children: [withRail ? (_jsx(SurfaceProvider, { value: railSurface, children: _jsx("div", { className: "@container/record", children: _jsxs("div", { className: `flex flex-col gap-2 px-3.5 py-2.5 ${railSurface === "chrome" ? "bg-chrome" : "border-b border-line bg-surface"}`, children: [_jsx(Line, { start: crumbs === undefined || crumbs.length === 0 ? null : (_jsx(Breadcrumb, { crumbs: crumbs, surface: railSurface })), center:
|
|
58
|
+
/* The notices' indicator rides with the counters: both are about the
|
|
59
|
+
record and both open something; neither is my position in front of
|
|
60
|
+
it. The line wraps on a phone, so it is never pushed out of reach. */
|
|
61
|
+
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 && status === undefined && dirty === undefined ? null : (
|
|
57
62
|
/* Row 2 — WHAT I CAN DO and WHERE IT IS AT: actions stuck to the
|
|
58
63
|
left edge, the history and the state stuck to the right one. No
|
|
59
64
|
`flex-wrap`: wrapping split the row in two and the state ended up
|
|
60
65
|
below the actions, which is another hierarchy. */
|
|
61
66
|
_jsxs("div", { className: "flex min-w-0 items-start gap-x-4", children: [dirty !== undefined ? (_jsx("div", { className: "flex shrink-0 items-center gap-1.5 @max-handheld/record:hidden", children: _jsx(DirtyActions, { ...dirty }) })) : actions === undefined ? null : (_jsx("div", { className: "flex shrink-0 items-center gap-1.5", children: primaries.length === 0
|
|
62
67
|
? actions
|
|
63
|
-
: railActions.map((node) => isPrimary(node) ? (_jsx("span", { "data-thumb-moves": "", className: "contents @max-handheld/record:hidden", children: node }, node.key)) : (node)) })), status === undefined ? null : (_jsx("div", { className: "ml-auto flex min-w-0 flex-col items-end gap-1.5", children: status }))] }))] }) }) })) : null,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
: railActions.map((node) => isPrimary(node) ? (_jsx("span", { "data-thumb-moves": "", className: "contents @max-handheld/record:hidden", children: node }, node.key)) : (node)) })), status === undefined ? null : (_jsx("div", { className: "ml-auto flex min-w-0 flex-col items-end gap-1.5", children: status }))] }))] }) }) })) : null, paper ? (
|
|
69
|
+
/* ==================================================================== *
|
|
70
|
+
* THE PAGE — [sheet | Actividad], on the ground.
|
|
71
|
+
*
|
|
72
|
+
* Two columns, joined, placed together and centred: the sheet
|
|
73
|
+
* (≤ --container-sheet, 72rem) and, at its RIGHT, the record's ACTIVITY
|
|
74
|
+
* (--container-activity, 22rem) — no strip of ground between them, so the page
|
|
75
|
+
* reads left to right as one paper: the record, then what it has. The pair is at
|
|
76
|
+
* most --container-record (94rem) wide; on a 1680 px desk both fit whole.
|
|
77
|
+
*
|
|
78
|
+
* A note of a field or a row is not drawn in the column: its mark (`Flag`) stays
|
|
79
|
+
* by its label or at its row — WHERE — and the activity lists it with the record's
|
|
80
|
+
* notices above the timeline — WHAT —, taking you to it on press. Cards aligned in
|
|
81
|
+
* the column were tried and dropped: they and the history fought for one column,
|
|
82
|
+
* and the history is what everybody reads.
|
|
83
|
+
*
|
|
84
|
+
* As the page narrows: below --container-beside (76rem) the activity goes BELOW
|
|
85
|
+
* the sheet as a card at the sheet's width.
|
|
86
|
+
*
|
|
87
|
+
* Folded, the column is a slim strip with its counts; the pair re-centres and the
|
|
88
|
+
* sheet keeps its width. A container query, not the window: with four panels open
|
|
89
|
+
* each is 380 px on a 1,600 screen.
|
|
90
|
+
*
|
|
91
|
+
* The sheet clips with `overflow-clip`, not `overflow-hidden`: hidden would make it
|
|
92
|
+
* a scroll container and the line table header would stick inside it, not under
|
|
93
|
+
* the application bar.
|
|
94
|
+
* ==================================================================== */
|
|
95
|
+
_jsx("div", { className: "bg-ground px-3 pt-3 pb-3", children: _jsx("div", { className: "@container/page", children: _jsxs("div", { className: `mx-auto flex min-w-0 flex-col gap-3 ${beside
|
|
96
|
+
? "max-w-(--container-record) @beside/page:flex-row @beside/page:items-stretch @beside/page:justify-center @beside/page:gap-0"
|
|
97
|
+
: "max-w-(--container-sheet)"}`, children: [_jsxs("div", { className: `flex min-w-0 flex-1 flex-col gap-3 ${beside ? "mx-auto w-full max-w-(--container-sheet) @beside/page:mx-0" : ""}`, children: [blocking, _jsxs("div", { className: `flex min-w-0 flex-col overflow-clip rounded-box border border-line bg-surface ${beside ? "@beside/page:rounded-r-none" : ""}`, children: [identity, children] }), activity !== undefined && activityLayout === "below" ? activity : null] }), beside ? (
|
|
98
|
+
/* After the sheet: the record first, what is around it second — in the
|
|
99
|
+
reading order and on screen. */
|
|
100
|
+
_jsx("div", { className: "mx-auto w-full max-w-(--container-sheet) min-w-0 @beside/page:mx-0 @beside/page:flex @beside/page:w-auto @beside/page:max-w-none @beside/page:shrink-0", children: activity })) : null] }) }) })) : (
|
|
101
|
+
/* No paper (a dialog, a drawer, a `Drill` context): one surface, no ground beside
|
|
102
|
+
it — the band first, the record, and the activity below. */
|
|
103
|
+
_jsxs("div", { className: "flex min-w-0 flex-1 flex-col bg-surface", children: [blocking === undefined ? null : (_jsx("div", { className: byDensity(d, "px-4 pt-3.5", "pb-2"), children: blocking })), identity, children, activity === undefined ? null : (_jsx("div", { className: byDensity(d, "px-4 pb-4", "pb-2"), children: activity }))] })), thumb === null ? null : _jsx(ThumbBar, { mode: thumb, dirty: dirty, primaries: primaries })] }));
|
|
72
104
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type CalloutProps } from "../../primitives/notice/Callout.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export type RecordNoticeSpec = Omit<CalloutProps, "dismissLabel">;
|
|
4
|
+
/** The notices in the order they are drawn: by urgency, written order inside a tone. */
|
|
5
|
+
export declare function orderNotices<T extends {
|
|
6
|
+
tone?: CalloutProps["tone"];
|
|
7
|
+
}>(notices: readonly T[]): T[];
|
|
8
|
+
/** Blocking (`bad`) and the rest, each already in urgency order. */
|
|
9
|
+
export declare function splitNotices(notices: readonly RecordNoticeSpec[]): {
|
|
10
|
+
blocking: RecordNoticeSpec[];
|
|
11
|
+
rest: RecordNoticeSpec[];
|
|
12
|
+
};
|
|
13
|
+
/** The band of what is wrong: the operation error first, then the `bad` notices. */
|
|
14
|
+
export declare function RecordBlocking({ notices, error, errorHeading, }: {
|
|
15
|
+
notices: readonly RecordNoticeSpec[];
|
|
16
|
+
/** The sentence the backend wrote about the last operation. Drawn first, whole. */
|
|
17
|
+
error?: string | null;
|
|
18
|
+
errorHeading?: string;
|
|
19
|
+
}): ReactNode;
|
|
20
|
+
/** The indicator in the rail: the count of the record's notices, toned by the most urgent,
|
|
21
|
+
* that opens their list on press. Nothing is drawn without notices. */
|
|
22
|
+
export declare function RecordNoticeIndicator({ notices, anchored, reveal, }: {
|
|
23
|
+
/** Not blocking, already in urgency order. */
|
|
24
|
+
notices: readonly RecordNoticeSpec[];
|
|
25
|
+
/** The notes of fields and rows: counted too when there is a panel that lists them —
|
|
26
|
+
* without one they are only the marks by their fields, and the popover leaves them out. */
|
|
27
|
+
anchored?: readonly {
|
|
28
|
+
tone?: "bad" | "warn" | "info" | "good";
|
|
29
|
+
}[];
|
|
30
|
+
/** The record has an ACTIVITY panel, and the notices live there: the indicator goes to
|
|
31
|
+
* them (opens the panel, scrolls to its notices, moves the focus) instead of floating
|
|
32
|
+
* a second copy in a popover. `target` is the id of that section. */
|
|
33
|
+
reveal?: {
|
|
34
|
+
target: string;
|
|
35
|
+
onReveal: () => void;
|
|
36
|
+
};
|
|
37
|
+
}): ReactNode;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { useSurface } from "../../foundations/surface.js";
|
|
5
|
+
import { focusRing, tv } from "../../foundations/variants.js";
|
|
6
|
+
import { Callout } from "../../primitives/notice/Callout.js";
|
|
7
|
+
import { Failed } from "../../primitives/notice/Notice.js";
|
|
8
|
+
import { Popover } from "../../primitives/overlay/Popover.js";
|
|
9
|
+
import { tagTone } from "../../primitives/tag/Tag.js";
|
|
10
|
+
import { CircleCheck, Info, TriangleAlert } from "lucide-react";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { Button, Dialog, DialogTrigger, Heading } from "react-aria-components";
|
|
13
|
+
const URGENCY = {
|
|
14
|
+
bad: 0,
|
|
15
|
+
warn: 1,
|
|
16
|
+
info: 2,
|
|
17
|
+
good: 3,
|
|
18
|
+
};
|
|
19
|
+
/** The notices in the order they are drawn: by urgency, written order inside a tone. */
|
|
20
|
+
export function orderNotices(notices) {
|
|
21
|
+
return notices
|
|
22
|
+
.map((notice, at) => ({ notice, at }))
|
|
23
|
+
.sort((a, b) => URGENCY[a.notice.tone ?? "info"] - URGENCY[b.notice.tone ?? "info"] || a.at - b.at)
|
|
24
|
+
.map(({ notice }) => notice);
|
|
25
|
+
}
|
|
26
|
+
/** Blocking (`bad`) and the rest, each already in urgency order. */
|
|
27
|
+
export function splitNotices(notices) {
|
|
28
|
+
const ordered = orderNotices(notices);
|
|
29
|
+
return {
|
|
30
|
+
blocking: ordered.filter((n) => n.tone === "bad"),
|
|
31
|
+
rest: ordered.filter((n) => n.tone !== "bad"),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** The band of what is wrong: the operation error first, then the `bad` notices. */
|
|
35
|
+
export function RecordBlocking({ notices, error, errorHeading, }) {
|
|
36
|
+
const copy = useUiCopy();
|
|
37
|
+
const hasError = error !== undefined && error !== null;
|
|
38
|
+
if (notices.length === 0 && !hasError)
|
|
39
|
+
return null;
|
|
40
|
+
return (_jsxs("div", { role: "group", "aria-label": copy.record.blocking, "data-record-blocking": "", className: "flex min-w-0 flex-col gap-1.5", children: [hasError ? (_jsx(Failed, { heading: errorHeading ?? copy.failed.operation, sentence: error })) : null, notices.map(({ children, ...notice }, i) => (_jsx(Callout, { ...notice, dismissLabel: copy.shell.closeNotice, children: children }, i)))] }));
|
|
41
|
+
}
|
|
42
|
+
const ICON = {
|
|
43
|
+
warn: TriangleAlert,
|
|
44
|
+
info: Info,
|
|
45
|
+
good: CircleCheck,
|
|
46
|
+
};
|
|
47
|
+
const chip = tv({
|
|
48
|
+
extend: focusRing,
|
|
49
|
+
base: "inline-flex h-(--size-control-sm) shrink-0 cursor-default items-center gap-1.5 rounded-pill px-2.5 text-micro font-medium whitespace-nowrap transition-[filter] duration-(--duration-instant) hover:brightness-95 aria-expanded:brightness-95",
|
|
50
|
+
});
|
|
51
|
+
/** The indicator in the rail: the count of the record's notices, toned by the most urgent,
|
|
52
|
+
* that opens their list on press. Nothing is drawn without notices. */
|
|
53
|
+
export function RecordNoticeIndicator({ notices, anchored = [], reveal, }) {
|
|
54
|
+
const copy = useUiCopy();
|
|
55
|
+
const surface = useSurface();
|
|
56
|
+
const [open, setOpen] = useState(false);
|
|
57
|
+
const all = reveal === undefined ? notices : [...notices, ...anchored];
|
|
58
|
+
if (all.length === 0)
|
|
59
|
+
return null;
|
|
60
|
+
/* The most urgent of all of them gives the chip its tone; a field's `bad` note warns —
|
|
61
|
+
what blocks the record is the blocking band, not this chip. */
|
|
62
|
+
const order = ["bad", "warn", "info", "good"];
|
|
63
|
+
const worst = order.find((t) => all.some((n) => (n.tone ?? "info") === t)) ?? "info";
|
|
64
|
+
const tone = (worst === "bad" ? "warn" : worst);
|
|
65
|
+
const Icon = ICON[tone];
|
|
66
|
+
const count = fillCopy(all.length === 1 ? copy.record.noticeOne : copy.record.noticeMany, {
|
|
67
|
+
count: all.length.toLocaleString(copy.locale),
|
|
68
|
+
});
|
|
69
|
+
const face = (_jsxs(_Fragment, { children: [_jsx(Icon, { className: "size-(--size-icon-sm) shrink-0", strokeWidth: 2.25, "aria-hidden": true }), count] }));
|
|
70
|
+
const skin = tagTone(tone, surface === "chrome" ? "chrome" : "canvas");
|
|
71
|
+
if (reveal !== undefined) {
|
|
72
|
+
return (_jsx(Button, { "data-record-notices": "", "aria-controls": reveal.target, onPress: reveal.onReveal, className: ({ isFocusVisible }) => `${chip({ isFocusVisible })} ${skin}`, children: face }));
|
|
73
|
+
}
|
|
74
|
+
return (_jsxs(DialogTrigger, { isOpen: open, onOpenChange: setOpen, children: [_jsx(Button, { "data-record-notices": "", className: ({ isFocusVisible }) => `${chip({ isFocusVisible })} ${skin}`, children: face }), _jsx(Popover, { placement: "bottom", children: _jsxs(Dialog, { "aria-label": copy.record.notices, className: "flex max-h-[inherit] w-(--container-toast) flex-col gap-2 overflow-auto p-3 outline-0 max-sm:w-auto", children: [_jsx(Heading, { slot: "title", className: "text-meta font-semibold text-ink", children: copy.record.notices }), notices.map(({ children, ...notice }, i) => (_jsx(Callout, { ...notice, dismissLabel: copy.shell.closeNotice, children: children }, i)))] }) })] }));
|
|
75
|
+
}
|
|
@@ -38,7 +38,7 @@ import { cloneElement, isValidElement } from "react";
|
|
|
38
38
|
* Same reason as everything else here: a record in a 380 px panel is as
|
|
39
39
|
* narrow as one on a phone. The query is a NAMED container (`record`) on the
|
|
40
40
|
* rail and on the bar — never on the paper: an unnamed container around the
|
|
41
|
-
* sheet would change what `Sheet`, `SectionGrid` and the
|
|
41
|
+
* sheet would change what `Sheet`, `SectionGrid` and the activity panel measure
|
|
42
42
|
* against, and the desk layout with it.
|
|
43
43
|
*
|
|
44
44
|
* ── Why CSS, and why the primary is in the DOM twice ─────────────────
|
|
@@ -69,6 +69,13 @@ export type UiCopy = {
|
|
|
69
69
|
/** Read aloud next to a stage that failed: the red fill alone says nothing to a
|
|
70
70
|
* screen reader. */
|
|
71
71
|
failed: string;
|
|
72
|
+
/** The title of the list of the record's notices, opened from the rail. */
|
|
73
|
+
notices: string;
|
|
74
|
+
/** The rail's indicator: how many notices, singular and plural. */
|
|
75
|
+
noticeOne: string;
|
|
76
|
+
noticeMany: string;
|
|
77
|
+
/** The accessible name of the band of what is wrong with the record. */
|
|
78
|
+
blocking: string;
|
|
72
79
|
};
|
|
73
80
|
shell: {
|
|
74
81
|
modules: string;
|
|
@@ -105,6 +112,10 @@ export type UiCopy = {
|
|
|
105
112
|
choice: {
|
|
106
113
|
/** What an open list says when what was typed matches nothing. */
|
|
107
114
|
noResults: string;
|
|
115
|
+
/** The name of a search box nobody labelled — no `Field`, no `aria-label`, no
|
|
116
|
+
* placeholder. A field a screen reader announces as «edit text» is a field nobody
|
|
117
|
+
* can find. */
|
|
118
|
+
search: string;
|
|
108
119
|
};
|
|
109
120
|
search: {
|
|
110
121
|
tools: string;
|
|
@@ -183,6 +194,8 @@ export type UiCopy = {
|
|
|
183
194
|
retry: string;
|
|
184
195
|
};
|
|
185
196
|
table: {
|
|
197
|
+
/** Under an editable list: write a new record as one more row. */
|
|
198
|
+
add: string;
|
|
186
199
|
row: string;
|
|
187
200
|
rows: string;
|
|
188
201
|
yes: string;
|
|
@@ -212,9 +225,15 @@ export type UiCopy = {
|
|
|
212
225
|
* filtro. Decir «todavía no hay nada» a quien tiene ciento ochenta y
|
|
213
226
|
* escribió mal un nombre es contestar otra pregunta. */
|
|
214
227
|
emptyFiltered: string;
|
|
228
|
+
/** How many records an embedded list holds, on its one-line bar. Two keys and not
|
|
229
|
+
* one: «1 records» is wrong in every language this ships in. */
|
|
230
|
+
countOne: string;
|
|
231
|
+
count: string;
|
|
215
232
|
};
|
|
216
233
|
/** La historia de un registro: eventos y mensajes. */
|
|
217
|
-
|
|
234
|
+
/** The panel of everything around a record: its notices, its conversation and its
|
|
235
|
+
* history (`Activity`). */
|
|
236
|
+
activity: {
|
|
218
237
|
/** Quién hizo algo que no hizo nadie. Es una respuesta, no un hueco. */
|
|
219
238
|
bySystem: string;
|
|
220
239
|
/** Abrir y cerrar el error completo. */
|
|
@@ -232,18 +251,37 @@ export type UiCopy = {
|
|
|
232
251
|
noRecipients: string;
|
|
233
252
|
/** El campo vacío de un mensaje que sí sale. */
|
|
234
253
|
messagePlaceholder: string;
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
/** Nobody wrote yet, and nothing was logged yet: two different answers. */
|
|
255
|
+
emptyMessages: string;
|
|
256
|
+
emptyEvents: string;
|
|
257
|
+
/** The panel's name, and what its fold and unfold buttons say about it. */
|
|
258
|
+
title: string;
|
|
240
259
|
open: string;
|
|
241
260
|
fold: string;
|
|
242
|
-
/** The
|
|
243
|
-
*
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
261
|
+
/** The parts of the timeline: what is true now, what people wrote, what the system
|
|
262
|
+
* logged. */
|
|
263
|
+
alerts: string;
|
|
264
|
+
conversation: string;
|
|
265
|
+
history: string;
|
|
266
|
+
/** The name of the history, under the notices. */
|
|
267
|
+
viewTimeline: string;
|
|
268
|
+
/** The notices beyond the first three, folded: «Ver 4 más». */
|
|
269
|
+
moreAlerts: string;
|
|
270
|
+
fewerAlerts: string;
|
|
271
|
+
/** The timeline's filters: everything, or one kind of entry. */
|
|
272
|
+
filters: string;
|
|
273
|
+
filterAll: string;
|
|
274
|
+
filterConversation: string;
|
|
275
|
+
filterNotes: string;
|
|
276
|
+
filterEvents: string;
|
|
277
|
+
/** The day separators of the timeline that are not a date. */
|
|
278
|
+
today: string;
|
|
279
|
+
yesterday: string;
|
|
280
|
+
/** A filter that leaves nothing. */
|
|
281
|
+
emptyFilter: string;
|
|
282
|
+
/** A note in the timeline takes you to what it belongs to. `{owner}` is that field or
|
|
283
|
+
* line. */
|
|
284
|
+
goTo: string;
|
|
247
285
|
/** The composer's buttons. */
|
|
248
286
|
send: string;
|
|
249
287
|
sending: string;
|
|
@@ -255,6 +293,10 @@ export type UiCopy = {
|
|
|
255
293
|
done: string;
|
|
256
294
|
/** Sin portapapeles —contexto no seguro, permiso negado— se dice. */
|
|
257
295
|
failed: string;
|
|
296
|
+
/** The copy button of a field, named by the field: «Copiar «Cliente»». */
|
|
297
|
+
field: string;
|
|
298
|
+
/** A field with nothing in it: said, not a silent success. */
|
|
299
|
+
empty: string;
|
|
258
300
|
};
|
|
259
301
|
/** Ordenar, cuando la presentación no tiene encabezados donde hacer clic. */
|
|
260
302
|
sorting: {
|
|
@@ -326,6 +368,8 @@ export type UiCopy = {
|
|
|
326
368
|
};
|
|
327
369
|
editable: {
|
|
328
370
|
add: string;
|
|
371
|
+
/** The spreadsheet's: a row, not a line of a document. */
|
|
372
|
+
addRow: string;
|
|
329
373
|
empty: string;
|
|
330
374
|
remove: string;
|
|
331
375
|
};
|
|
@@ -338,6 +382,8 @@ export type UiCopy = {
|
|
|
338
382
|
refine: string;
|
|
339
383
|
open: string;
|
|
340
384
|
browseAll: string;
|
|
385
|
+
/** The quiet row at the end of a list that is fetching its next page. */
|
|
386
|
+
loadingMore: string;
|
|
341
387
|
};
|
|
342
388
|
cell: {
|
|
343
389
|
missing: string;
|
|
@@ -349,6 +395,13 @@ export type UiCopy = {
|
|
|
349
395
|
invalid: string;
|
|
350
396
|
noDate: string;
|
|
351
397
|
};
|
|
398
|
+
/** The dialog that holds a stack of contexts opened from a record (`Drill`). */
|
|
399
|
+
drill: {
|
|
400
|
+
/** Up one level. `{name}` is the context it goes back to. */
|
|
401
|
+
back: string;
|
|
402
|
+
/** The accessible name of the trail of contexts this one was opened from. */
|
|
403
|
+
trail: string;
|
|
404
|
+
};
|
|
352
405
|
dialog: {
|
|
353
406
|
expand: string;
|
|
354
407
|
/** The wizard's own buttons: leave, go back, go on, end — and what the main
|
|
@@ -467,6 +520,12 @@ export type UiCopy = {
|
|
|
467
520
|
empty: string;
|
|
468
521
|
/** Closing the line being edited. */
|
|
469
522
|
done: string;
|
|
523
|
+
/** Sections and notes among the lines (Odoo's `display_type`). */
|
|
524
|
+
addSection: string;
|
|
525
|
+
addNote: string;
|
|
526
|
+
/** The names of their text field, for a screen reader. */
|
|
527
|
+
section: string;
|
|
528
|
+
note: string;
|
|
470
529
|
};
|
|
471
530
|
/** `LineItems`, beyond the `lines` keys: counting, the editor of a new line, removing. */
|
|
472
531
|
lineItems: {
|
|
@@ -496,6 +555,23 @@ export type UiCopy = {
|
|
|
496
555
|
/** The name of the button that opens it, and of the card. `{name}` is the record. */
|
|
497
556
|
summary: string;
|
|
498
557
|
};
|
|
558
|
+
/** The money summary of a document once something is applied against it. */
|
|
559
|
+
totals: {
|
|
560
|
+
/** Read, not shown, before a deducted amount: «Pagado el 12/10/2026, menos Bs. 100». */
|
|
561
|
+
less: string;
|
|
562
|
+
/** The name of a deduction's `i`. `{label}` is the deduction — «Pagado el 12/10/2026». */
|
|
563
|
+
details: string;
|
|
564
|
+
/** Go to the applied record, from its detail. */
|
|
565
|
+
open: string;
|
|
566
|
+
/** Take a deduction back off the document, from its detail. */
|
|
567
|
+
unlink: string;
|
|
568
|
+
/** The title of what could still be applied. */
|
|
569
|
+
credits: string;
|
|
570
|
+
/** The visible word on each available credit's button. */
|
|
571
|
+
apply: string;
|
|
572
|
+
/** Its accessible name, which says WHICH. `{label}` is the credit — «NC/2026/0012». */
|
|
573
|
+
applyCredit: string;
|
|
574
|
+
};
|
|
499
575
|
/** Changing a value where it is drawn: one more tag of a many2many, one cell of a list. */
|
|
500
576
|
inlineEdit: {
|
|
501
577
|
/** The inline input of a tags field, while it holds no tag yet. */
|
|
@@ -513,6 +589,11 @@ export type UiCopy = {
|
|
|
513
589
|
light: string;
|
|
514
590
|
dark: string;
|
|
515
591
|
};
|
|
592
|
+
/** A hierarchy of records — a chart of accounts, product categories, locations. */
|
|
593
|
+
tree: {
|
|
594
|
+
/** What an empty tree says, unless the screen says something closer to its domain. */
|
|
595
|
+
empty: string;
|
|
596
|
+
};
|
|
516
597
|
/** The assistant acting inside a screen: what it suggests, what it proposes to change,
|
|
517
598
|
* what it set, and what it is still working on. */
|
|
518
599
|
assist: {
|
|
@@ -589,11 +670,12 @@ export type UiCopyOverride = {
|
|
|
589
670
|
ops?: Record<string, string>;
|
|
590
671
|
sorting?: Partial<UiCopy["sorting"]>;
|
|
591
672
|
copyable?: Partial<UiCopy["copyable"]>;
|
|
592
|
-
|
|
673
|
+
activity?: Partial<UiCopy["activity"]>;
|
|
593
674
|
editable?: Partial<UiCopy["editable"]>;
|
|
594
675
|
picker?: Partial<UiCopy["picker"]>;
|
|
595
676
|
cell?: Partial<UiCopy["cell"]>;
|
|
596
677
|
time?: Partial<UiCopy["time"]>;
|
|
678
|
+
drill?: Partial<UiCopy["drill"]>;
|
|
597
679
|
dialog?: Partial<UiCopy["dialog"]>;
|
|
598
680
|
report?: Partial<UiCopy["report"]>;
|
|
599
681
|
builder?: Partial<UiCopy["builder"]>;
|
|
@@ -603,8 +685,10 @@ export type UiCopyOverride = {
|
|
|
603
685
|
lineItems?: Partial<UiCopy["lineItems"]>;
|
|
604
686
|
relation?: Partial<UiCopy["relation"]>;
|
|
605
687
|
relationCard?: Partial<UiCopy["relationCard"]>;
|
|
688
|
+
totals?: Partial<UiCopy["totals"]>;
|
|
606
689
|
inlineEdit?: Partial<UiCopy["inlineEdit"]>;
|
|
607
690
|
theme?: Partial<UiCopy["theme"]>;
|
|
691
|
+
tree?: Partial<UiCopy["tree"]>;
|
|
608
692
|
assist?: Partial<UiCopy["assist"]>;
|
|
609
693
|
};
|
|
610
694
|
export declare function mergeCopy(over?: UiCopyOverride): UiCopy;
|
|
@@ -72,6 +72,10 @@ export const SPANISH_COPY = {
|
|
|
72
72
|
saving: "Guardando…",
|
|
73
73
|
discard: "Descartar",
|
|
74
74
|
failed: "falló",
|
|
75
|
+
notices: "Avisos sobre este registro",
|
|
76
|
+
noticeOne: "{count} aviso",
|
|
77
|
+
noticeMany: "{count} avisos",
|
|
78
|
+
blocking: "Lo que está mal en este registro",
|
|
75
79
|
},
|
|
76
80
|
shell: {
|
|
77
81
|
close: "Cerrar",
|
|
@@ -100,6 +104,7 @@ export const SPANISH_COPY = {
|
|
|
100
104
|
},
|
|
101
105
|
choice: {
|
|
102
106
|
noResults: "Sin resultados",
|
|
107
|
+
search: "Buscar",
|
|
103
108
|
},
|
|
104
109
|
dates: {
|
|
105
110
|
last: "últimos {count} {unit}",
|
|
@@ -172,6 +177,7 @@ export const SPANISH_COPY = {
|
|
|
172
177
|
retry: "Reintentar",
|
|
173
178
|
},
|
|
174
179
|
table: {
|
|
180
|
+
add: "Agregar",
|
|
175
181
|
row: "fila",
|
|
176
182
|
rows: "filas",
|
|
177
183
|
yes: "sí",
|
|
@@ -185,8 +191,10 @@ export const SPANISH_COPY = {
|
|
|
185
191
|
list: {
|
|
186
192
|
empty: "Todavía no hay nada acá.",
|
|
187
193
|
emptyFiltered: "Nada coincide con lo que está puesto.",
|
|
194
|
+
countOne: "1 registro",
|
|
195
|
+
count: "{count} registros",
|
|
188
196
|
},
|
|
189
|
-
|
|
197
|
+
activity: {
|
|
190
198
|
bySystem: "el sistema",
|
|
191
199
|
more: "ver el error completo",
|
|
192
200
|
less: "ver menos",
|
|
@@ -197,18 +205,37 @@ export const SPANISH_COPY = {
|
|
|
197
205
|
recipients: "Quiénes lo reciben",
|
|
198
206
|
noRecipients: "todavía no hay a quién mandarlo",
|
|
199
207
|
messagePlaceholder: "Escribí un mensaje…",
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
208
|
+
emptyMessages: "Todavía nadie escribió acá.",
|
|
209
|
+
emptyEvents: "Todavía no se registró nada.",
|
|
210
|
+
title: "Actividad",
|
|
211
|
+
open: "Abrir {label}",
|
|
212
|
+
fold: "Plegar {label}",
|
|
213
|
+
alerts: "Avisos",
|
|
214
|
+
conversation: "Conversación",
|
|
215
|
+
history: "Historial",
|
|
216
|
+
viewTimeline: "Línea de tiempo",
|
|
217
|
+
moreAlerts: "Ver {count} más",
|
|
218
|
+
fewerAlerts: "Ver menos",
|
|
219
|
+
filters: "Mostrar",
|
|
220
|
+
filterAll: "Todo",
|
|
221
|
+
filterConversation: "Mensajes",
|
|
222
|
+
filterNotes: "Notas",
|
|
223
|
+
filterEvents: "Eventos",
|
|
224
|
+
today: "Hoy",
|
|
225
|
+
yesterday: "Ayer",
|
|
226
|
+
emptyFilter: "Todavía no hay nada de esto.",
|
|
227
|
+
goTo: "Ir a {owner}",
|
|
207
228
|
send: "Enviar",
|
|
208
229
|
sending: "Enviando…",
|
|
209
230
|
cancel: "Cancelar",
|
|
210
231
|
},
|
|
211
|
-
copyable: {
|
|
232
|
+
copyable: {
|
|
233
|
+
label: "Copiar",
|
|
234
|
+
done: "Copiado",
|
|
235
|
+
failed: "No se pudo copiar",
|
|
236
|
+
field: "Copiar «{label}»",
|
|
237
|
+
empty: "No hay nada que copiar",
|
|
238
|
+
},
|
|
212
239
|
sorting: {
|
|
213
240
|
label: "Ordenar",
|
|
214
241
|
by: "ordenar por",
|
|
@@ -233,7 +260,12 @@ export const SPANISH_COPY = {
|
|
|
233
260
|
clear: "dejar de agrupar",
|
|
234
261
|
grain: "con qué corte",
|
|
235
262
|
},
|
|
236
|
-
editable: {
|
|
263
|
+
editable: {
|
|
264
|
+
add: "agregar",
|
|
265
|
+
addRow: "agregar una fila",
|
|
266
|
+
empty: "nada todavía",
|
|
267
|
+
remove: "quitar",
|
|
268
|
+
},
|
|
237
269
|
picker: {
|
|
238
270
|
placeholder: "elegí uno…",
|
|
239
271
|
empty: "Nada coincide.",
|
|
@@ -243,8 +275,13 @@ export const SPANISH_COPY = {
|
|
|
243
275
|
refine: "— afiná el filtro",
|
|
244
276
|
open: "Abrir",
|
|
245
277
|
browseAll: "ver todos…",
|
|
278
|
+
loadingMore: "cargando más…",
|
|
246
279
|
},
|
|
247
280
|
cell: { empty: "—", missing: "falta", emptyBin: "(vacío)" },
|
|
281
|
+
drill: {
|
|
282
|
+
back: "Volver a {name}",
|
|
283
|
+
trail: "Abierto desde",
|
|
284
|
+
},
|
|
248
285
|
dialog: {
|
|
249
286
|
expand: "Agrandar a panel",
|
|
250
287
|
cancel: "Cancelar",
|
|
@@ -320,6 +357,10 @@ export const SPANISH_COPY = {
|
|
|
320
357
|
remove: "Quitar la línea",
|
|
321
358
|
empty: "Todavía no hay líneas.",
|
|
322
359
|
done: "Listo",
|
|
360
|
+
addSection: "Agregar una sección",
|
|
361
|
+
addNote: "Agregar una nota",
|
|
362
|
+
section: "Sección",
|
|
363
|
+
note: "Nota",
|
|
323
364
|
},
|
|
324
365
|
lineItems: {
|
|
325
366
|
one: "{count} línea",
|
|
@@ -333,6 +374,15 @@ export const SPANISH_COPY = {
|
|
|
333
374
|
},
|
|
334
375
|
relation: { open: "Abrir", none: "Sin asignar" },
|
|
335
376
|
relationCard: { summary: "Resumen de {name}" },
|
|
377
|
+
totals: {
|
|
378
|
+
less: "menos",
|
|
379
|
+
details: "Detalle: {label}",
|
|
380
|
+
open: "Abrir",
|
|
381
|
+
unlink: "Desvincular",
|
|
382
|
+
credits: "Créditos pendientes",
|
|
383
|
+
apply: "Aplicar",
|
|
384
|
+
applyCredit: "Aplicar {label}",
|
|
385
|
+
},
|
|
336
386
|
inlineEdit: {
|
|
337
387
|
addTag: "Agregar…",
|
|
338
388
|
createTag: "Crear «{name}»",
|
|
@@ -344,6 +394,9 @@ export const SPANISH_COPY = {
|
|
|
344
394
|
light: "Claro",
|
|
345
395
|
dark: "Oscuro",
|
|
346
396
|
},
|
|
397
|
+
tree: {
|
|
398
|
+
empty: "Todavía no hay nada acá.",
|
|
399
|
+
},
|
|
347
400
|
assist: {
|
|
348
401
|
assistant: "Asistente",
|
|
349
402
|
suggests: "sugiere",
|