@sincpro/web-ui 1.1.0 → 1.3.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 +1 -1
- 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/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/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 +6 -1
- 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 +33 -62
- package/dist/foundations/copy.es.js +6 -0
- package/dist/foundations/copy.js +21 -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/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 +2 -2
- package/dist/primitives/field/Field.js +3 -3
- 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/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 +137 -38
- package/dist/primitives/table/EditableTable.js +2 -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.d.ts +12 -3
- package/dist/primitives/table/Table.js +7 -7
- package/dist/primitives/table/rowWindow.d.ts +39 -0
- package/dist/primitives/table/rowWindow.js +177 -0
- 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/views/list/List.d.ts +14 -1
- package/dist/views/list/List.js +28 -6
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +41 -14
- package/dist/views/list/index.d.ts +2 -2
- package/dist/views/list/presentations.js +3 -1
- package/dist/views/list/types.d.ts +49 -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",
|
|
@@ -90,5 +90,5 @@ export function SheetGroup({ title, hint, icon: Icon, children, }) {
|
|
|
90
90
|
/* A group title is ABOVE its labels, not one of them: at the labels' size and weight
|
|
91
91
|
«Cliente» the group read like «Cliente» the field. One step up in size and weight,
|
|
92
92
|
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("
|
|
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("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
94
|
}
|
|
@@ -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
|
|
@@ -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: [
|
|
@@ -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);
|
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
import type { CriteriaJoin } from "./criteria.js";
|
|
1
|
+
import type { BuiltFilter, CriteriaJoin } from "./criteria.js";
|
|
2
2
|
import { type ModelDefinition } from "./model.js";
|
|
3
|
-
/** Una condición, tal como la declara el motor. `operator` sale del `ops` del campo, así que
|
|
4
|
-
* lo que el constructor ofrece es exactamente lo que el modelo acepta. */
|
|
5
|
-
export interface BuiltCondition {
|
|
6
|
-
field: string;
|
|
7
|
-
operator: string;
|
|
8
|
-
value: unknown;
|
|
9
|
-
}
|
|
10
|
-
/** Lo que el filtro puede ser en su forma real: una condición, un grupo `all`/`any` de más
|
|
11
|
-
* filtros —lo que el árbol arma, a cualquier profundidad—, o un `negate` que llegó de otro
|
|
12
|
-
* lado y que el árbol no fabrica, pero que este componente sabe mostrar y editar como JSON. */
|
|
13
|
-
export type BuiltFilter = BuiltCondition | {
|
|
14
|
-
all: BuiltFilter[];
|
|
15
|
-
} | {
|
|
16
|
-
any: BuiltFilter[];
|
|
17
|
-
} | {
|
|
18
|
-
negate: BuiltFilter;
|
|
19
|
-
};
|
|
20
3
|
/** Si esto es una de las formas que el filtro reconoce — condición, o grupo `all`/`any`/
|
|
21
4
|
* `negate`. Un JSON válido que no es ninguna de éstas —`{}`, `{"foo": 1}`, un array suelto—
|
|
22
5
|
* no es un filtro que este componente sepa dibujar, y se trata como error de forma en vez de
|
|
@@ -355,13 +355,13 @@ export function WhereTree({ model, group, onChange, depth = 0, focus, }) {
|
|
|
355
355
|
? (node) => node?.scrollIntoView({ block: "nearest" })
|
|
356
356
|
: undefined, className: `flex min-w-0 flex-wrap items-center gap-2 rounded-control ${focus !== undefined && child.field === focus
|
|
357
357
|
? "-m-1 p-1 ring-1 ring-primary/60"
|
|
358
|
-
: ""}`, children: [_jsx("select", { value: child.field, onChange: (event) => setChild(index, {
|
|
358
|
+
: ""}`, children: [_jsx("select", { "aria-label": copy.builder.ruleField, value: child.field, onChange: (event) => setChild(index, {
|
|
359
359
|
...child,
|
|
360
360
|
field: event.target.value,
|
|
361
361
|
operator: model.fields[event.target.value]?.ops.includes(child.operator)
|
|
362
362
|
? child.operator
|
|
363
363
|
: (model.fields[event.target.value]?.ops[0] ?? "="),
|
|
364
|
-
}), className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}`, children: names.map((name) => (_jsx("option", { value: name, children: name }, name))) }), _jsx("select", { value: child.operator, onChange: (event) => setChild(index, { ...child, operator: event.target.value }), className: `min-w-0 flex-1 basis-36 rounded-control border px-2 py-1 text-meta ${box}`, children: (model.fields[child.field]?.ops ?? []).map((operator) => (_jsx("option", { value: operator, children: opLabel(operator) }, operator))) }), WITHOUT_VALUE.has(child.operator) ? (_jsx("span", { className: "flex-1 text-meta text-muted", children: copy.builder.noValue })) : WITH_RANGE.has(child.operator) ? (_jsxs("span", { className: "flex min-w-0 flex-1 basis-40 items-center gap-1", children: [_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.from, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` }), _jsx("span", { className: "shrink-0 text-micro text-muted", children: copy.search.and }), _jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.upper ?? "", onChange: (event) => setChild(index, { ...child, upper: event.target.value }), placeholder: copy.builder.to, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` })] })) : (_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.value, className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}` })), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": copy.builder.addRule, onClick: () => addConditionAfter(index), className: "text-muted hover:text-ink", children: _jsx(Plus, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.addNested, onClick: () => addGroupAfter(index), className: "text-muted hover:text-ink", children: _jsx(FolderTree, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.dropRule, disabled: group.children.length === 1, onClick: () => removeAt(index), className: "text-muted disabled:opacity-30", children: _jsx(Trash2, { className: "size-4" }) })] })] }, index)))] }));
|
|
364
|
+
}), className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}`, children: names.map((name) => (_jsx("option", { value: name, children: name }, name))) }), _jsx("select", { "aria-label": copy.builder.ruleOperator, value: child.operator, onChange: (event) => setChild(index, { ...child, operator: event.target.value }), className: `min-w-0 flex-1 basis-36 rounded-control border px-2 py-1 text-meta ${box}`, children: (model.fields[child.field]?.ops ?? []).map((operator) => (_jsx("option", { value: operator, children: opLabel(operator) }, operator))) }), WITHOUT_VALUE.has(child.operator) ? (_jsx("span", { className: "flex-1 text-meta text-muted", children: copy.builder.noValue })) : WITH_RANGE.has(child.operator) ? (_jsxs("span", { className: "flex min-w-0 flex-1 basis-40 items-center gap-1", children: [_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.from, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` }), _jsx("span", { className: "shrink-0 text-micro text-muted", children: copy.search.and }), _jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.upper ?? "", onChange: (event) => setChild(index, { ...child, upper: event.target.value }), placeholder: copy.builder.to, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` })] })) : (_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.value, className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}` })), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": copy.builder.addRule, onClick: () => addConditionAfter(index), className: "text-muted hover:text-ink", children: _jsx(Plus, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.addNested, onClick: () => addGroupAfter(index), className: "text-muted hover:text-ink", children: _jsx(FolderTree, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.dropRule, disabled: group.children.length === 1, onClick: () => removeAt(index), className: "text-muted disabled:opacity-30", children: _jsx(Trash2, { className: "size-4" }) })] })] }, index)))] }));
|
|
365
365
|
}
|
|
366
366
|
export function FilterBuilder({ model, initial, onApply, onCancel, }) {
|
|
367
367
|
const names = filterableNames(model);
|
|
@@ -400,11 +400,11 @@ export function FilterBuilder({ model, initial, onApply, onCancel, }) {
|
|
|
400
400
|
parsed = JSON.parse(text);
|
|
401
401
|
}
|
|
402
402
|
catch {
|
|
403
|
-
setRawError(
|
|
403
|
+
setRawError(copy.builder.invalidJson);
|
|
404
404
|
return;
|
|
405
405
|
}
|
|
406
406
|
if (!isRecognizedFilter(parsed)) {
|
|
407
|
-
setRawError(
|
|
407
|
+
setRawError(copy.builder.notFilter);
|
|
408
408
|
return;
|
|
409
409
|
}
|
|
410
410
|
setRawError(null);
|
|
@@ -8,6 +8,7 @@ import { useTheme } from "../../foundations/theme.js";
|
|
|
8
8
|
import { Button } from "../../primitives/button/index.js";
|
|
9
9
|
import CodeMirror, { EditorView, Prec } from "@uiw/react-codemirror";
|
|
10
10
|
import { Wand2 } from "lucide-react";
|
|
11
|
+
import { useId } from "react";
|
|
11
12
|
/* ====================================================================== *
|
|
12
13
|
* UN JSON QUE SE ESCRIBE A MANO, EN UN EDITOR DE VERDAD.
|
|
13
14
|
*
|
|
@@ -56,11 +57,14 @@ const SURFACE_THEME = EditorView.theme({
|
|
|
56
57
|
});
|
|
57
58
|
export function JsonEditor({ value, onChange, error, height = "16rem", label, }) {
|
|
58
59
|
const copy = useUiCopy();
|
|
60
|
+
const labelId = useId();
|
|
59
61
|
const surface = useSurface();
|
|
60
62
|
const { resolved } = useTheme();
|
|
61
63
|
/* The chrome is dark in both themes; the canvas follows the theme. */
|
|
62
64
|
const dark = surface === "chrome" || resolved === "dark";
|
|
63
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { className: "text-micro text-muted", children: label ?? copy.builder.editor }), _jsxs(Button, { role: "ghost", size: "sm", disabled: value.trim() === "", onClick: () => onChange(formatJson(value)), children: [_jsx(Wand2, { className: "size-3" }), " ", copy.builder.format] })] }), _jsx("div", { className: `overflow-hidden rounded-control border ${error == null ? "border-ink/15" : "border-danger"}`, children: _jsx(CodeMirror, { value: value, onChange: onChange, height: height, theme: dark ? "dark" : "light", extensions: [
|
|
65
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { id: labelId, className: "text-micro text-muted", children: label ?? copy.builder.editor }), _jsxs(Button, { role: "ghost", size: "sm", disabled: value.trim() === "", onClick: () => onChange(formatJson(value)), children: [_jsx(Wand2, { className: "size-3" }), " ", copy.builder.format] })] }), _jsx("div", { className: `overflow-hidden rounded-control border ${error == null ? "border-ink/15" : "border-danger"}`, children: _jsx(CodeMirror, { value: value, onChange: onChange, height: height, theme: dark ? "dark" : "light", extensions: [
|
|
66
|
+
/* The editable area is CodeMirror's own element: named here or nowhere. */
|
|
67
|
+
EditorView.contentAttributes.of({ "aria-labelledby": labelId }),
|
|
64
68
|
json(),
|
|
65
69
|
linter(jsonParseLinter()),
|
|
66
70
|
lintGutter(),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type CriteriaJoin, type CriteriaPath, type SearchCriteria } from "./criteria.js";
|
|
2
|
-
import type { BuiltCondition, BuiltFilter } from "./FilterBuilder.js";
|
|
1
|
+
import { type BuiltCondition, type BuiltFilter, type CriteriaJoin, type CriteriaPath, type SearchCriteria } from "./criteria.js";
|
|
3
2
|
import type { ModelDefinition } from "./model.js";
|
|
4
3
|
import type { Surface } from "../../foundations/types.js";
|
|
5
4
|
import type { MenuGroup } from "../../primitives/menu/Menu.js";
|
|
@@ -381,7 +381,12 @@ export function SearchBar({ facets, model, criteria, onCriteriaChange, placehold
|
|
|
381
381
|
onCriteriaChange([]);
|
|
382
382
|
setOpenGroup(null);
|
|
383
383
|
input.current?.focus();
|
|
384
|
-
}, "aria-label": copy.search.clearAll, title: copy.search.clearAll, className: `my-0.5 inline-flex h-(--size-control-sm) shrink-0 items-center rounded-control px-1.5 transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${bySurface(sup, "text-muted hover:bg-ink/5 hover:text-ink", "text-chrome-muted hover:bg-chrome-ink/10 hover:text-chrome-ink")}`, children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true }) })), columns.length === 0 && onOpenBuilder === undefined ? null : (_jsxs("button", { type: "button", onClick: () => setPanel((v) => !v), "aria-expanded": panel, "aria-controls": panel ? id : undefined,
|
|
384
|
+
}, "aria-label": copy.search.clearAll, title: copy.search.clearAll, className: `my-0.5 inline-flex h-(--size-control-sm) shrink-0 items-center rounded-control px-1.5 transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${bySurface(sup, "text-muted hover:bg-ink/5 hover:text-ink", "text-chrome-muted hover:bg-chrome-ink/10 hover:text-chrome-ink")}`, children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true }) })), columns.length === 0 && onOpenBuilder === undefined ? null : (_jsxs("button", { type: "button", onClick: () => setPanel((v) => !v), "aria-expanded": panel, "aria-controls": panel ? id : undefined, "aria-label": puestos === 0
|
|
385
|
+
? copy.search.tools
|
|
386
|
+
: fillCopy(puestos === 1 ? copy.search.toolsSetOne : copy.search.toolsSetMany, {
|
|
387
|
+
label: copy.search.tools,
|
|
388
|
+
count: puestos.toLocaleString(copy.locale),
|
|
389
|
+
}), title: copy.search.tools, className: `my-0.5 mr-0.5 inline-flex h-(--size-control-sm) shrink-0 items-center gap-1 rounded-control border px-2 text-micro transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${puestos > 0 || panel
|
|
385
390
|
? bySurface(sup, "border-accent/40 bg-accent/18 text-ink", "border-chrome-link/35 bg-chrome-link/15 text-chrome-link")
|
|
386
391
|
: bySurface(sup, "border-transparent text-muted hover:bg-ink/5 hover:text-ink", "border-transparent text-chrome-muted hover:bg-chrome-ink/10 hover:text-chrome-ink")}`, children: [_jsx(ListFilter, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true }), puestos > 0 ? _jsx("span", { className: "text-micro tabular-nums", children: puestos }) : null] }))] }), trailing, open ? (_jsxs("div", { className: `absolute top-full z-30 mt-1 overflow-hidden rounded-panel border border-line-strong bg-surface shadow-float ${asTags ? "left-0 w-max max-w-full" : "right-0 left-0"}`, children: [openJoin === undefined ? null : (_jsxs("div", { className: "flex items-center gap-1.5 border-line border-b px-3 py-1.5 text-micro text-muted", children: [_jsx(CornerDownRight, { className: "size-3 shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "min-w-0 truncate", children: copy.search.landsHere }), _jsx("span", { className: "shrink-0 rounded-pill border border-accent/40 bg-accent/18 px-1.5 text-ink", children: copy.search.joins[openJoin === "not" ? "none" : openJoin] })] })), asTags ? (
|
|
387
392
|
/* UNA LISTA, no una tira. Se está ELIGIENDO: el ojo baja por una
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import type { BuiltCondition, BuiltFilter } from "./FilterBuilder.js";
|
|
2
1
|
import type { GroupGrain, GroupLevel } from "../../foundations/grouping.js";
|
|
2
|
+
/** Una condición, tal como la declara el motor. `operator` sale del `ops` del campo, así que
|
|
3
|
+
* lo que el constructor ofrece es exactamente lo que el modelo acepta. */
|
|
4
|
+
export interface BuiltCondition {
|
|
5
|
+
field: string;
|
|
6
|
+
operator: string;
|
|
7
|
+
value: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** Lo que el filtro puede ser en su forma real: una condición, un grupo `all`/`any` de más
|
|
10
|
+
* filtros —lo que el árbol arma, a cualquier profundidad—, o un `negate` que llegó de otro
|
|
11
|
+
* lado y que el árbol no fabrica, pero que este componente sabe mostrar y editar como JSON. */
|
|
12
|
+
export type BuiltFilter = BuiltCondition | {
|
|
13
|
+
all: BuiltFilter[];
|
|
14
|
+
} | {
|
|
15
|
+
any: BuiltFilter[];
|
|
16
|
+
} | {
|
|
17
|
+
negate: BuiltFilter;
|
|
18
|
+
};
|
|
3
19
|
/** Cómo se juntan las instancias de un nodo. `not` niega lo que tiene adentro. */
|
|
4
20
|
export type CriteriaJoin = "all" | "any" | "not";
|
|
5
21
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { type BuiltCondition, type BuiltFilter, } from "./criteria.js";
|
|
1
2
|
export { type BuiltCriteria, type BuiltLevel, type BuiltSort, CriteriaBuilder, } from "./CriteriaBuilder.js";
|
|
2
|
-
export { blankGroup,
|
|
3
|
+
export { blankGroup, describeFilter, FilterBuilder, type TreeCondition, treeFromFilter, type TreeGroup, type TreeNode, treeToFilter, } from "./FilterBuilder.js";
|
|
3
4
|
export { formatJson, JsonEditor } from "./JsonEditor.js";
|
|
4
5
|
export { SearchBar, type SearchBarProps, type SearchFacet, } from "./SearchBar.js";
|
|
5
6
|
export { at, criteriaId, type CriteriaJoin, type CriteriaPath, filterCriteria, filtersOf, type GroupCriteria, groupCriteria, groupsOf, hasNamed, levelsOf, nextJoin, removeAt, replaceAt, type SearchCriteria, toggleNamed, whereOf, withLevels, } from "./criteria.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* Buscar con facetas, con los recortes metidos adentro del campo. */
|
|
2
|
+
export {} from "./criteria.js";
|
|
2
3
|
export { CriteriaBuilder, } from "./CriteriaBuilder.js";
|
|
3
4
|
export { blankGroup, describeFilter, FilterBuilder, treeFromFilter, treeToFilter, } from "./FilterBuilder.js";
|
|
4
5
|
export { formatJson, JsonEditor } from "./JsonEditor.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
declare const BASIS: {
|
|
3
|
+
readonly narrow: "@4xl:flex-2";
|
|
4
|
+
readonly wide: "@4xl:flex-3";
|
|
5
|
+
};
|
|
6
|
+
/** One column of the screen. `narrow` for the set you pick from, `wide` for the work. */
|
|
7
|
+
export declare function Pane({ children, size, label, }: {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
size?: keyof typeof BASIS;
|
|
10
|
+
/** What this pane is — «Extracto», «Por conciliar». Panes are regions a screen reader
|
|
11
|
+
* jumps between, so each one is named. */
|
|
12
|
+
label: string;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
/** The panes, side by side on a desk and stacked when narrow. Fills the height it is given. */
|
|
15
|
+
export declare function Panes({ children }: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { HeaderSlotContext } from "./headerSlot.js";
|
|
4
|
+
import { stickyReset } from "../../foundations/sticky.js";
|
|
5
|
+
/* ==================================================================== *
|
|
6
|
+
* PANES — a screen of N sets side by side, each one with its own search.
|
|
7
|
+
*
|
|
8
|
+
* Context: Odoo's bank reconciliation is the case — the statement lines on one side, the
|
|
9
|
+
* record being matched and the journal items that could match it on the other, each with
|
|
10
|
+
* its own search, and the person going back and forth between them. A list that fills the
|
|
11
|
+
* page carries its bar up to the screen's header (`useHeaderSlot`), and there is ONE
|
|
12
|
+
* header: with two or three lists they fought over it, and each bar stuck to the page
|
|
13
|
+
* instead of to its own column.
|
|
14
|
+
*
|
|
15
|
+
* A pane is the answer: on a desk each pane SCROLLS ON ITS OWN — a long list on the left
|
|
16
|
+
* does not drag the record on the right out of sight — so what sticks inside it (a list's
|
|
17
|
+
* bar, a table's header) sticks to the pane's edge, and nothing inside goes up to the
|
|
18
|
+
* screen's header. Narrow, the panes stack and the page scrolls: a scroll inside a scroll
|
|
19
|
+
* on a phone is a list nobody can leave.
|
|
20
|
+
*
|
|
21
|
+
* It decides by ITS OWN width (it is the `@container`), not the window's: a workspace panel
|
|
22
|
+
* among four is narrow on a wide screen, and a view mounted without a container around it
|
|
23
|
+
* would never go side by side.
|
|
24
|
+
* ==================================================================== */
|
|
25
|
+
const BASIS = { narrow: "@4xl:flex-2", wide: "@4xl:flex-3" };
|
|
26
|
+
/** One column of the screen. `narrow` for the set you pick from, `wide` for the work. */
|
|
27
|
+
export function Pane({ children, size = "wide", label, }) {
|
|
28
|
+
return (_jsx(HeaderSlotContext.Provider, { value: null, children: _jsx("section", { "aria-label": label, style: stickyReset(),
|
|
29
|
+
/* Stacked it keeps its own height (the page scrolls); side by side it shares the
|
|
30
|
+
height and scrolls inside. */
|
|
31
|
+
className: `flex min-h-0 min-w-0 shrink-0 flex-col @4xl:shrink @4xl:basis-0 @4xl:overflow-y-auto ${BASIS[size]}`, children: children }) }));
|
|
32
|
+
}
|
|
33
|
+
/** The panes, side by side on a desk and stacked when narrow. Fills the height it is given. */
|
|
34
|
+
export function Panes({ children }) {
|
|
35
|
+
return (_jsx("div", { className: "@container flex h-full min-h-0 min-w-0 flex-1 flex-col", children: _jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col divide-line @4xl:flex-row @4xl:divide-x max-@4xl:divide-y", children: children }) }));
|
|
36
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/* EL MARCO DE LA APLICACIÓN — lo que está arriba de toda vista. */
|
|
2
2
|
export { AppFrame } from "./AppFrame.js";
|
|
3
3
|
export { SystrayButton, TopBar, } from "./TopBar.js";
|
|
4
|
+
/* A screen of N sets side by side — the reconciliation: each pane scrolls on its own. */
|
|
5
|
+
export { Pane, Panes } from "./Panes.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** El grano de un nivel de fecha. Sin grano, un campo de fecha parte por
|
|
2
|
+
* instante y cada fila termina en su propio montón, que es no agrupar. */
|
|
3
|
+
export type GroupGrain = "day" | "week" | "month" | "quarter" | "year";
|
|
2
4
|
/**
|
|
3
5
|
* Una fecha recortada a la CLAVE de su grano.
|
|
4
6
|
*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** What changes, at any depth. A word is replaced; a section is merged. */
|
|
3
|
+
export type CopyOverride<C> = {
|
|
4
|
+
[K in keyof C]?: C[K] extends string ? string : C[K] extends readonly unknown[] ? C[K] : C[K] extends object ? CopyOverride<C[K]> : C[K];
|
|
5
|
+
};
|
|
6
|
+
/** `base` with `over` on top: sections merge, everything else is replaced. */
|
|
7
|
+
export declare function mergeCopyOver<C extends object>(base: C, over: CopyOverride<C> | undefined): C;
|
|
8
|
+
export type CopyCatalog<C extends object> = {
|
|
9
|
+
/** The factory words: what is there before anything is installed. */
|
|
10
|
+
english: C;
|
|
11
|
+
/** Sets the session's words — English with `over` on top — and returns them. */
|
|
12
|
+
install: (over?: CopyOverride<C>) => C;
|
|
13
|
+
/** The installed words: for what is not a component. */
|
|
14
|
+
current: () => C;
|
|
15
|
+
/** English with `over` on top, without installing it. */
|
|
16
|
+
merge: (over?: CopyOverride<C>) => C;
|
|
17
|
+
/** Changes the words for its branch, on top of what encloses it. */
|
|
18
|
+
Provider: (props: {
|
|
19
|
+
copy?: CopyOverride<C>;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}) => ReactNode;
|
|
22
|
+
/** The words of this branch: the nearest `Provider`'s, or the installed ones. */
|
|
23
|
+
useCopy: () => C;
|
|
24
|
+
};
|
|
25
|
+
export declare function createCopy<C extends object>(english: C): CopyCatalog<C>;
|
|
26
|
+
/** `{name}` → value. A missing key stays visible in the template. */
|
|
27
|
+
export declare function fillCopy(template: string, params: Record<string, string | number>): string;
|