@coffer-org/web-ui 5.0.0 → 6.0.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/ui/copy-button.d.ts +6 -0
- package/dist/components/ui/copy-button.js +28 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/layout.js +3 -3
- package/dist/registry.d.ts +2 -1
- package/dist/registry.js +1 -0
- package/dist/render/activate.d.ts +4 -0
- package/dist/render/activate.js +11 -0
- package/dist/render/blocks/balance.js +3 -2
- package/dist/render/blocks/block-shell.d.ts +1 -0
- package/dist/render/blocks/block-shell.js +4 -0
- package/dist/render/blocks/chart.js +2 -1
- package/dist/render/blocks/compare.js +2 -1
- package/dist/render/blocks/countdown.js +2 -1
- package/dist/render/blocks/idcard.js +2 -1
- package/dist/render/blocks/journal.js +2 -1
- package/dist/render/blocks/ledger.js +2 -2
- package/dist/render/blocks/manifest.js +2 -1
- package/dist/render/blocks/masthead.js +2 -1
- package/dist/render/blocks/meter.js +2 -2
- package/dist/render/blocks/nutrition.js +2 -2
- package/dist/render/blocks/plaque.js +2 -1
- package/dist/render/blocks/receipt.js +2 -2
- package/dist/render/blocks/score.js +2 -2
- package/dist/render/blocks/stats.js +3 -2
- package/dist/render/blocks/table.js +5 -4
- package/dist/render/chrome.d.ts +21 -3
- package/dist/render/chrome.js +46 -6
- package/dist/render/core/dimensions.js +1 -1
- package/dist/render/core/embed.js +8 -1
- package/dist/render/core/money.js +1 -1
- package/dist/render/core/range.js +1 -1
- package/dist/render/core/reminder.js +1 -1
- package/dist/render/core/text.js +57 -3
- package/dist/render/core/weight.js +1 -1
- package/dist/render/datum.d.ts +4 -0
- package/dist/render/datum.js +16 -0
- package/dist/render/dispatch.js +4 -2
- package/dist/render/field-frame.d.ts +1 -1
- package/dist/render/field-frame.js +20 -9
- package/dist/render/popover-field.js +10 -19
- package/dist/render/widgets/display.js +2 -1
- package/dist/render/widgets/edit.js +2 -2
- package/dist/render/widgets/fixed-collection.js +2 -1
- package/dist/render/widgets/group-widgets.js +1 -1
- package/dist/render/widgets/group.js +2 -1
- package/dist/render/widgets/table-group.js +4 -3
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Check, Copy } from 'lucide-react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Button } from "./button.js";
|
|
6
|
+
const CONFIRM_MS = 1500;
|
|
7
|
+
export function CopyButton({ getText, className, size = 'iconXs', }) {
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
const [copied, setCopied] = useState(false);
|
|
10
|
+
const timer = useRef(undefined);
|
|
11
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
12
|
+
async function handleCopy() {
|
|
13
|
+
const text = getText();
|
|
14
|
+
if (!text || !navigator.clipboard)
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
await navigator.clipboard.writeText(text);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
setCopied(true);
|
|
23
|
+
clearTimeout(timer.current);
|
|
24
|
+
timer.current = setTimeout(() => setCopied(false), CONFIRM_MS);
|
|
25
|
+
}
|
|
26
|
+
const label = t(copied ? 'actions.copied' : 'actions.copy');
|
|
27
|
+
return (_jsx(Button, { type: "button", variant: "outline", size: size, className: className, onClick: () => void handleCopy(), title: label, "aria-label": label, children: copied ? _jsx(Check, { size: 13 }) : _jsx(Copy, { size: 13 }) }));
|
|
28
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export * from './hooks/use-media-query.ts';
|
|
|
57
57
|
export * from './render/dispatch.tsx';
|
|
58
58
|
export { type Commit } from './render/write.ts';
|
|
59
59
|
export { type SlotHandle, useSlot, useSlotOptional, useSlotRender, useSlots, SlotProvider, SlotScope, Slot, SlotEditor, SlotValue, } from './render/slot.tsx';
|
|
60
|
-
export { DEFAULT_SECTION_ICON, PAGE_STACK, FieldLabel as RenderFieldLabel, FieldError } from './render/chrome.tsx';
|
|
60
|
+
export { DEFAULT_SECTION_ICON, PAGE_STACK, FieldLabel as RenderFieldLabel, FieldError, CHROME_LABEL_CLS, ChromeLabel, } from './render/chrome.tsx';
|
|
61
61
|
export * from './render/field-errors.tsx';
|
|
62
62
|
export * from './render/field-frame.tsx';
|
|
63
63
|
export * from './render/form-types.ts';
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export * from "./lib/use-record-labels.js";
|
|
|
56
56
|
export * from "./hooks/use-media-query.js";
|
|
57
57
|
export * from "./render/dispatch.js";
|
|
58
58
|
export { useSlot, useSlotOptional, useSlotRender, useSlots, SlotProvider, SlotScope, Slot, SlotEditor, SlotValue, } from "./render/slot.js";
|
|
59
|
-
export { DEFAULT_SECTION_ICON, PAGE_STACK, FieldLabel as RenderFieldLabel, FieldError } from "./render/chrome.js";
|
|
59
|
+
export { DEFAULT_SECTION_ICON, PAGE_STACK, FieldLabel as RenderFieldLabel, FieldError, CHROME_LABEL_CLS, ChromeLabel, } from "./render/chrome.js";
|
|
60
60
|
export * from "./render/field-errors.js";
|
|
61
61
|
export * from "./render/field-frame.js";
|
|
62
62
|
export * from "./render/form-types.js";
|
package/dist/layout.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fieldOptions } from "./lib/format.js";
|
|
|
8
8
|
import { withScopeValues, ParentValuesProvider } from "./render/field-scope.js";
|
|
9
9
|
import { parseJSON } from "./lib/json.js";
|
|
10
10
|
import { FlowRow, FlowCtx } from "./render/widgets/flow-row.js";
|
|
11
|
-
import { STACK, LAYOUT_STACK, GroupTitle, Rail, groupIcon } from "./render/chrome.js";
|
|
11
|
+
import { STACK, LAYOUT_STACK, GroupTitle, Rail, groupIcon, CHROME_LABEL_CLS, } from "./render/chrome.js";
|
|
12
12
|
import { focusInPopup } from "./render/field-frame.js";
|
|
13
13
|
import { FieldAddressProvider } from "./render/field-address.js";
|
|
14
14
|
import { SlotProvider } from "./render/slot.js";
|
|
@@ -193,7 +193,7 @@ function GroupSection({ group, children }) {
|
|
|
193
193
|
}
|
|
194
194
|
function RowSection({ group, children }) {
|
|
195
195
|
const { t } = useTranslation();
|
|
196
|
-
return (_jsxs("div", { className: "flex flex-col gap-1 min-w-0", children: [group.label && _jsx("span", { className:
|
|
196
|
+
return (_jsxs("div", { className: "flex flex-col gap-1 min-w-0", children: [group.label && _jsx("span", { className: CHROME_LABEL_CLS, children: t(group.label) }), children] }));
|
|
197
197
|
}
|
|
198
198
|
function SheetSection({ group, renderCell, }) {
|
|
199
199
|
const { t } = useTranslation();
|
|
@@ -234,7 +234,7 @@ function renderNodes(nodes, keyPrefix) {
|
|
|
234
234
|
}
|
|
235
235
|
export function LabelledDivider({ label }) {
|
|
236
236
|
const { t } = useTranslation();
|
|
237
|
-
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className:
|
|
237
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: `shrink-0 ${CHROME_LABEL_CLS}`, children: t(label) }), _jsx("div", { className: "flex-1 border-t border-border" })] }));
|
|
238
238
|
}
|
|
239
239
|
function renderDivider(label, key) {
|
|
240
240
|
return label ? _jsx(LabelledDivider, { label: label }, key) : _jsx("hr", { className: "border-border" }, key);
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FieldClient, FieldMeta, LayoutEl } from '@coffer-org/sdk/fields';
|
|
2
|
-
import type { FieldRenderer, ElementRenderProps, FieldInlineProps, FieldValueProps, FieldPickerProps, PickerSize, EditorMode, BoxGeom, TemplateImporter } from './types.ts';
|
|
2
|
+
import type { FieldRenderer, ElementRenderProps, FieldInlineProps, FieldValueProps, FieldPickerProps, PickerSize, EditorMode, ActivateMode, BoxGeom, TemplateImporter } from './types.ts';
|
|
3
3
|
import type { FC } from 'react';
|
|
4
4
|
export declare const rendererRegistry: Record<string, FieldRenderer>;
|
|
5
5
|
type NoWidenedProps<P> = Exclude<keyof P, keyof ElementRenderProps> extends never ? unknown : never;
|
|
@@ -14,6 +14,7 @@ export declare function registerRenderer<P extends ElementRenderProps>(r: {
|
|
|
14
14
|
pickerSize?: PickerSize | ((field: FieldMeta) => PickerSize);
|
|
15
15
|
Picker?: FC<FieldPickerProps>;
|
|
16
16
|
editor?: EditorMode;
|
|
17
|
+
activate?: ActivateMode;
|
|
17
18
|
keys?: FieldRenderer['keys'];
|
|
18
19
|
} & NoWidenedProps<P>): void;
|
|
19
20
|
export declare function resolveRenderer(field: Pick<FieldClient, 'kind'>): FieldRenderer | undefined;
|
package/dist/registry.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { resolveRenderer } from "../registry.js";
|
|
2
|
+
const MODES = new Set(['edit', 'value']);
|
|
3
|
+
function isActivateMode(v) {
|
|
4
|
+
return typeof v === 'string' && MODES.has(v);
|
|
5
|
+
}
|
|
6
|
+
export function activateMode(field) {
|
|
7
|
+
const override = field.hints?.['activate'];
|
|
8
|
+
if (isActivateMode(override))
|
|
9
|
+
return override;
|
|
10
|
+
return resolveRenderer(field)?.activate ?? 'edit';
|
|
11
|
+
}
|
|
@@ -2,13 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { registerRenderer } from "../../registry.js";
|
|
4
4
|
import { Slot } from "../slot.js";
|
|
5
|
-
import {
|
|
5
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
6
|
+
import { BlockShell, fieldDatumClass } from "./block-shell.js";
|
|
6
7
|
function BalancePage({ el }) {
|
|
7
8
|
const g = el;
|
|
8
9
|
const sourceName = g.view?.source;
|
|
9
10
|
const { t } = useTranslation();
|
|
10
11
|
const caption = g.view?.caption;
|
|
11
|
-
const body = (_jsxs("div", { className: "flex flex-col gap-1", "data-testid": "balance", children: [_jsx(Slot, { name: sourceName, render: (
|
|
12
|
+
const body = (_jsxs("div", { className: "flex flex-col gap-1", "data-testid": "balance", children: [_jsx(Slot, { name: sourceName, render: (handle, defaultDisplay) => (_jsx("span", { className: `text-3xl font-semibold ${fieldDatumClass(handle)}`.trim(), "data-testid": "balance-figure", children: defaultDisplay })) }), caption && (_jsx("div", { className: CHROME_LABEL_CLS, "data-testid": "balance-caption", children: t(caption) }))] }));
|
|
12
13
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
13
14
|
}
|
|
14
15
|
registerRenderer({ kinds: ['balance'], layout: 'block', editor: 'custom', Page: BalancePage });
|
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { isNamedField } from '@coffer-org/sdk/fields';
|
|
4
4
|
import { SectionShell } from "../../layout.js";
|
|
5
|
+
import { datumClass } from "../datum.js";
|
|
5
6
|
export function findSlot(slots, name) {
|
|
6
7
|
return name !== undefined ? slots.find((s) => s.name === name) : undefined;
|
|
7
8
|
}
|
|
@@ -18,3 +19,6 @@ export function fieldLabel(handle, t) {
|
|
|
18
19
|
const key = handle.field.label ?? (isNamedField(handle.field) ? handle.field.type.label : undefined);
|
|
19
20
|
return key ? t(key) : undefined;
|
|
20
21
|
}
|
|
22
|
+
export function fieldDatumClass(handle) {
|
|
23
|
+
return isNamedField(handle.field) ? datumClass(handle.field.type) : '';
|
|
24
|
+
}
|
|
@@ -6,6 +6,7 @@ import { useGroupEdit } from "../widgets/group-edit.js";
|
|
|
6
6
|
import { GroupField } from "../widgets/group-field.js";
|
|
7
7
|
import { FieldSlot } from "../dispatch.js";
|
|
8
8
|
import { useSlot } from "../slot.js";
|
|
9
|
+
import { datumClass } from "../datum.js";
|
|
9
10
|
import { toSeries, sparkline, seriesDelta } from "./series.js";
|
|
10
11
|
import { partField } from "../use-collection-rows.js";
|
|
11
12
|
const BOX = { width: 100, height: 28, pad: 2 };
|
|
@@ -37,7 +38,7 @@ function ChartPage({ el, value, recordCtx, mode }) {
|
|
|
37
38
|
const reading = (p) => (_jsx(FieldSlot, { el: valueFld, field: valueFld.type, value: p.row?.[valueKey] ?? p.value, mode: "view", bare: true, recordCtx: recordCtx }));
|
|
38
39
|
const lowest = points.length ? points.reduce((a, b) => (b.value < a.value ? b : a)) : undefined;
|
|
39
40
|
const highest = points.length ? points.reduce((a, b) => (b.value > a.value ? b : a)) : undefined;
|
|
40
|
-
const chart = last === undefined ? (_jsx("p", { className: "py-1 text-sm text-muted", "data-testid": "chart-empty", children: t('form.noSeries', 'No readings yet') })) : (_jsxs("div", { className: "flex flex-col gap-1.5", "data-testid": "chart-body", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("div", { className:
|
|
41
|
+
const chart = last === undefined ? (_jsx("p", { className: "py-1 text-sm text-muted", "data-testid": "chart-empty", children: t('form.noSeries', 'No readings yet') })) : (_jsxs("div", { className: "flex flex-col gap-1.5", "data-testid": "chart-body", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("div", { className: `min-w-0 text-2xl font-semibold text-text ${datumClass(valueFld.type)}`.trim(), "data-testid": "chart-last", children: reading(last) }), delta !== undefined && (_jsx("span", { className: `text-sm font-medium tabular-nums ${delta > 0 ? 'text-accent' : delta < 0 ? 'text-danger' : 'text-muted'}`, "data-testid": "chart-delta", children: fmtDelta(delta) }))] }), _jsx("svg", { className: "h-12 w-full text-accent", viewBox: `0 0 ${BOX.width} ${BOX.height}`, preserveAspectRatio: "none", "aria-hidden": "true", "data-testid": "chart-spark", children: _jsx("path", { d: spark.path, "data-testid": "chart-path", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke" }) }), _jsxs("div", { className: `flex items-baseline gap-1 text-[10px] text-muted ${datumClass(valueFld.type)}`.trim(), "data-testid": "chart-range", children: [lowest && reading(lowest), _jsx("span", { "aria-hidden": "true", children: "\u2013" }), highest && reading(highest)] })] }));
|
|
41
42
|
const content = editing ? editor : chart;
|
|
42
43
|
if (mode !== 'view')
|
|
43
44
|
return content;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { registerRenderer } from "../../registry.js";
|
|
4
4
|
import { Slot } from "../slot.js";
|
|
5
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
5
6
|
import { BlockShell } from "./block-shell.js";
|
|
6
7
|
function ComparePage({ el }) {
|
|
7
8
|
const g = el;
|
|
@@ -10,7 +11,7 @@ function ComparePage({ el }) {
|
|
|
10
11
|
const right = g.view?.right ?? [];
|
|
11
12
|
const leftLabel = g.view?.leftLabel;
|
|
12
13
|
const rightLabel = g.view?.rightLabel;
|
|
13
|
-
const body = (_jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", "data-testid": "compare", children: [_jsxs("div", { className: "min-w-0 space-y-3", "data-testid": "compare-left", children: [leftLabel && _jsx("div", { className:
|
|
14
|
+
const body = (_jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", "data-testid": "compare", children: [_jsxs("div", { className: "min-w-0 space-y-3", "data-testid": "compare-left", children: [leftLabel && _jsx("div", { className: CHROME_LABEL_CLS, children: t(leftLabel) }), left.map((n) => (_jsx(Slot, { name: n }, n)))] }), _jsxs("div", { className: "min-w-0 space-y-3 sm:border-l sm:border-border sm:pl-4", "data-testid": "compare-right", children: [rightLabel && _jsx("div", { className: CHROME_LABEL_CLS, children: t(rightLabel) }), right.map((n) => (_jsx(Slot, { name: n }, n)))] })] }));
|
|
14
15
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
15
16
|
}
|
|
16
17
|
registerRenderer({ kinds: ['compare'], layout: 'block', editor: 'custom', Page: ComparePage });
|
|
@@ -3,6 +3,7 @@ import { ArrowUp, ArrowDown, Minus } from 'lucide-react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { registerRenderer } from "../../registry.js";
|
|
5
5
|
import { Slot } from "../slot.js";
|
|
6
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
6
7
|
import { BlockShell } from "./block-shell.js";
|
|
7
8
|
const MS_PER_DAY = 86_400_000;
|
|
8
9
|
export function countdownDays(value, today) {
|
|
@@ -30,7 +31,7 @@ function CountdownPage({ el }) {
|
|
|
30
31
|
const direction = days > 0 ? 'remaining' : days < 0 ? 'elapsed' : 'today';
|
|
31
32
|
const Ico = days > 0 ? ArrowUp : days < 0 ? ArrowDown : Minus;
|
|
32
33
|
return (_jsxs("span", { className: "inline-flex items-baseline gap-3", "data-testid": "countdown-figure", "data-direction": direction, children: [_jsxs("span", { className: "inline-flex items-baseline gap-1.5", children: [_jsx(Ico, { size: 16, className: "shrink-0 self-center text-muted", "aria-hidden": "true" }), _jsx("span", { className: "text-3xl font-bold tabular-nums", children: Math.abs(days) })] }), _jsx("span", { className: "text-sm text-muted", "data-testid": "countdown-date", children: defaultDisplay })] }));
|
|
33
|
-
} }), caption && (_jsx("div", { className:
|
|
34
|
+
} }), caption && (_jsx("div", { className: CHROME_LABEL_CLS, "data-testid": "countdown-caption", children: t(caption) }))] }));
|
|
34
35
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
35
36
|
}
|
|
36
37
|
registerRenderer({ kinds: ['countdown'], layout: 'block', editor: 'custom', Page: CountdownPage });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { registerRenderer } from "../../registry.js";
|
|
3
3
|
import { Slot, useSlots } from "../slot.js";
|
|
4
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
4
5
|
import { findSlot, findSlots } from "./block-shell.js";
|
|
5
6
|
function groupedNumber(raw) {
|
|
6
7
|
const compact = raw.replace(/\s+/g, '');
|
|
@@ -19,7 +20,7 @@ function IdcardPage({ el }) {
|
|
|
19
20
|
const slots = useSlots();
|
|
20
21
|
const overline = findSlot(slots, overlineName);
|
|
21
22
|
const metaVisible = findSlots(slots, metaNames).filter((s) => !s.handle.hidden);
|
|
22
|
-
return (_jsxs("div", { className: "space-y-1", "data-testid": "idcard", children: [overline && !overline.handle.hidden && (_jsx("div", { className:
|
|
23
|
+
return (_jsxs("div", { className: "space-y-1", "data-testid": "idcard", children: [overline && !overline.handle.hidden && (_jsx("div", { className: CHROME_LABEL_CLS, "data-testid": "idcard-overline", children: _jsx(Slot, { name: overlineName }) })), _jsx("div", { className: "min-w-0", children: _jsx(Slot, { name: numberName, render: (handle, defaultDisplay) => {
|
|
23
24
|
const raw = typeof handle.value === 'string' || typeof handle.value === 'number' ? String(handle.value) : '';
|
|
24
25
|
const grouped = groupedNumber(raw);
|
|
25
26
|
if (!grouped)
|
|
@@ -13,6 +13,7 @@ import { ChecklistContent } from "../widgets/checklist-content.js";
|
|
|
13
13
|
import { useFieldErrors } from "../field-errors.js";
|
|
14
14
|
import { useCollectionRows, partField } from "../use-collection-rows.js";
|
|
15
15
|
import { useSlot } from "../slot.js";
|
|
16
|
+
import { datumClass } from "../datum.js";
|
|
16
17
|
function cellNode(f, row, key, editing, live, onCommit, recordCtx, error) {
|
|
17
18
|
if (editing) {
|
|
18
19
|
return (_jsx(FieldSlot, { el: f, field: f.type, fieldKey: key, value: row[key] ?? '', onChange: onCommit, mode: "edit", bare: true, recordCtx: recordCtx, error: error }));
|
|
@@ -42,7 +43,7 @@ function JournalPage({ el, value, recordCtx, mode }) {
|
|
|
42
43
|
.filter((f) => isNamedField(f) && f.key !== dateKey && f.key !== textKey)
|
|
43
44
|
.map((f) => ({ key: f.key, type: f.type }));
|
|
44
45
|
const errorFor = (idx, key) => isStorageGroup(group) ? errorAt([group.key, idx, key]) : undefined;
|
|
45
|
-
const content = (_jsxs("div", { className: `flex flex-col ${STACK}`, children: [rows.map((row, idx) => (_jsx(Rail, { children: _jsxs("div", { className: "flex items-start gap-3", "data-testid": `journal-entry-${idx}`, children: [_jsx("div", { className:
|
|
46
|
+
const content = (_jsxs("div", { className: `flex flex-col ${STACK}`, children: [rows.map((row, idx) => (_jsx(Rail, { children: _jsxs("div", { className: "flex items-start gap-3", "data-testid": `journal-entry-${idx}`, children: [_jsx("div", { className: `w-20 shrink-0 text-xs text-muted ${datumClass(dateFld.type)}`.trim(), "data-testid": `journal-date-${idx}`, children: cellNode(dateFld, row, dateKey, editing, live, (v) => updateRow(idx, dateKey, v), recordCtx, errorFor(idx, dateKey)) }), _jsx("div", { className: "min-w-0 flex-1 text-sm", "data-testid": `journal-text-${idx}`, children: cellNode(textFld, row, textKey, editing, live, (v) => updateRow(idx, textKey, v), recordCtx, errorFor(idx, textKey)) }), restFields.length > 0 && (_jsx(ChecklistContent, { fields: restFields, row: row, editing: editing, onChange: (key, v) => updateRow(idx, key, v), recordCtx: recordCtx, error: (key) => errorFor(idx, key) })), rowTools && _jsx(RemoveBtn, { className: "mt-0.5", onClick: () => removeRow(idx) })] }) }, rowId(row)))), rowTools && _jsx(AddBtn, { onClick: addRow, children: t('form.addRow') })] }));
|
|
46
47
|
if (mode !== 'view')
|
|
47
48
|
return content;
|
|
48
49
|
return (_jsxs("section", { className: `${groupScopeClass(depth)} flex flex-col gap-1 min-w-0`, "data-testid": "journal", children: [group.label && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-[13px] font-semibold text-accent", children: t(group.label) }), !live && g.actions && _jsx("span", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: g.actions })] })), _jsx(GroupDepthProvider, { depth: depth + 1, children: content })] }));
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { registerRenderer } from "../../registry.js";
|
|
4
4
|
import { Slot, useSlots } from "../slot.js";
|
|
5
|
-
import { BlockShell, fieldLabel } from "./block-shell.js";
|
|
5
|
+
import { BlockShell, fieldLabel, fieldDatumClass } from "./block-shell.js";
|
|
6
6
|
function LedgerPage({ el }) {
|
|
7
7
|
const g = el;
|
|
8
8
|
const { t } = useTranslation();
|
|
@@ -10,7 +10,7 @@ function LedgerPage({ el }) {
|
|
|
10
10
|
const body = (_jsx("div", { className: "flex flex-col divide-y divide-border", "data-testid": "ledger", children: slots.map((s, i) => {
|
|
11
11
|
if (s.handle.hidden)
|
|
12
12
|
return null;
|
|
13
|
-
return (_jsx("div", { className: "min-w-0 py-2", "data-testid": `ledger-row-${i}`, children: _jsx(Slot, { name: s.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline gap-2", "data-testid": "ledger-leader", children: [_jsx("span", { className: "shrink-0", children: fieldLabel(handle, t) }), _jsx("span", { className: "min-w-0 flex-1 border-b border-dotted border-border", "data-testid": "ledger-dots" }), _jsx("span", { className:
|
|
13
|
+
return (_jsx("div", { className: "min-w-0 py-2", "data-testid": `ledger-row-${i}`, children: _jsx(Slot, { name: s.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline gap-2", "data-testid": "ledger-leader", children: [_jsx("span", { className: "shrink-0", children: fieldLabel(handle, t) }), _jsx("span", { className: "min-w-0 flex-1 border-b border-dotted border-border", "data-testid": "ledger-dots" }), _jsx("span", { className: `shrink-0 text-right font-medium ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay })] })) }) }, s.name));
|
|
14
14
|
}) }));
|
|
15
15
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
16
16
|
}
|
|
@@ -11,6 +11,7 @@ import { FieldSlot } from "../dispatch.js";
|
|
|
11
11
|
import { GroupField } from "../widgets/group-field.js";
|
|
12
12
|
import { ChecklistContent } from "../widgets/checklist-content.js";
|
|
13
13
|
import { useFieldErrors } from "../field-errors.js";
|
|
14
|
+
import { datumClass } from "../datum.js";
|
|
14
15
|
import { useCollectionRows, partField } from "../use-collection-rows.js";
|
|
15
16
|
import { useSlot } from "../slot.js";
|
|
16
17
|
function cellNode(f, row, key, editing, live, onCommit, recordCtx, error) {
|
|
@@ -42,7 +43,7 @@ function ManifestPage({ el, value, recordCtx, mode }) {
|
|
|
42
43
|
.filter((f) => isNamedField(f) && f.key !== qtyKey && f.key !== itemKey)
|
|
43
44
|
.map((f) => ({ key: f.key, type: f.type }));
|
|
44
45
|
const errorFor = (idx, key) => isStorageGroup(group) ? errorAt([group.key, idx, key]) : undefined;
|
|
45
|
-
const content = (_jsxs("div", { className: `flex flex-col ${STACK}`, children: [rows.map((row, idx) => (_jsx(Rail, { children: _jsxs("div", { className: "flex items-center gap-3", "data-testid": `manifest-row-${idx}`, children: [_jsx("div", { className:
|
|
46
|
+
const content = (_jsxs("div", { className: `flex flex-col ${STACK}`, children: [rows.map((row, idx) => (_jsx(Rail, { children: _jsxs("div", { className: "flex items-center gap-3", "data-testid": `manifest-row-${idx}`, children: [_jsx("div", { className: `w-12 shrink-0 text-right text-sm ${datumClass(qtyFld.type)}`.trim(), "data-testid": `manifest-quantity-${idx}`, children: cellNode(qtyFld, row, qtyKey, editing, live, (v) => updateRow(idx, qtyKey, v), recordCtx, errorFor(idx, qtyKey)) }), _jsx("div", { className: "min-w-0 flex-1 text-sm", "data-testid": `manifest-item-${idx}`, children: cellNode(itemFld, row, itemKey, editing, live, (v) => updateRow(idx, itemKey, v), recordCtx, errorFor(idx, itemKey)) }), restFields.length > 0 && (_jsx(ChecklistContent, { fields: restFields, row: row, editing: editing, onChange: (key, v) => updateRow(idx, key, v), recordCtx: recordCtx, error: (key) => errorFor(idx, key) })), rowTools && _jsx(RemoveBtn, { onClick: () => removeRow(idx) })] }) }, rowId(row)))), rowTools && _jsx(AddBtn, { onClick: addRow, children: t('form.addRow') })] }));
|
|
46
47
|
if (mode !== 'view')
|
|
47
48
|
return content;
|
|
48
49
|
return (_jsxs("section", { className: `${groupScopeClass(depth)} flex flex-col gap-1 min-w-0`, "data-testid": "manifest", children: [group.label && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-[13px] font-semibold text-accent", children: t(group.label) }), !live && g.actions && _jsx("span", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: g.actions })] })), _jsx(GroupDepthProvider, { depth: depth + 1, children: content })] }));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { registerRenderer } from "../../registry.js";
|
|
3
3
|
import { Slot, useSlots } from "../slot.js";
|
|
4
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
4
5
|
import { findSlot, findSlots } from "./block-shell.js";
|
|
5
6
|
function MastheadPage({ el }) {
|
|
6
7
|
const g = el;
|
|
@@ -10,6 +11,6 @@ function MastheadPage({ el }) {
|
|
|
10
11
|
const slots = useSlots();
|
|
11
12
|
const overline = findSlot(slots, overlineName);
|
|
12
13
|
const metaVisible = findSlots(slots, metaNames).filter((s) => !s.handle.hidden);
|
|
13
|
-
return (_jsxs("div", { className: "space-y-1", "data-testid": "masthead", children: [overline && !overline.handle.hidden && (_jsx("div", { className:
|
|
14
|
+
return (_jsxs("div", { className: "space-y-1", "data-testid": "masthead", children: [overline && !overline.handle.hidden && (_jsx("div", { className: CHROME_LABEL_CLS, "data-testid": "masthead-overline", children: _jsx(Slot, { name: overlineName }) })), _jsx("div", { className: "min-w-0 text-2xl leading-tight font-bold", "data-testid": "masthead-title", children: _jsx(Slot, { name: titleName }) }), metaVisible.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-x-1 border-t border-b border-border py-1.5 text-sm text-muted", "data-testid": "masthead-meta", children: metaVisible.map((s, i) => (_jsx("span", { className: `inline-flex min-w-0 items-center ${i > 0 ? "before:mr-2 before:text-muted before:content-['·']" : ''}`, "data-testid": `masthead-meta-${i}`, children: _jsx(Slot, { name: s.name }) }, s.name))) }))] }));
|
|
14
15
|
}
|
|
15
16
|
registerRenderer({ kinds: ['masthead'], layout: 'block', editor: 'custom', Page: MastheadPage });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { registerRenderer } from "../../registry.js";
|
|
3
3
|
import { Slot, useSlots } from "../slot.js";
|
|
4
|
-
import { BlockShell, findSlot } from "./block-shell.js";
|
|
4
|
+
import { BlockShell, findSlot, fieldDatumClass } from "./block-shell.js";
|
|
5
5
|
function resolveRange(view, field) {
|
|
6
6
|
const min = Number(view?.min ?? field.hints['min'] ?? 0);
|
|
7
7
|
const maxRaw = view?.max ?? field.hints['max'];
|
|
@@ -25,7 +25,7 @@ function MeterPage({ el }) {
|
|
|
25
25
|
const { min, max } = resolveRange(view, handle.field.type);
|
|
26
26
|
const n = magnitudeOf(handle.value);
|
|
27
27
|
const ratio = Number.isFinite(n) ? Math.max(0, Math.min(1, (n - min) / (max - min))) : 0;
|
|
28
|
-
return (_jsxs("span", { className: "flex min-w-0 flex-col gap-1", "data-testid": "meter-figure", children: [_jsx("span", { className:
|
|
28
|
+
return (_jsxs("span", { className: "flex min-w-0 flex-col gap-1", "data-testid": "meter-figure", children: [_jsx("span", { className: `text-2xl font-semibold ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay }), _jsx("span", { role: "progressbar", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": Number.isFinite(n) ? Math.max(min, Math.min(max, n)) : min, className: "h-2 w-full overflow-hidden rounded-full bg-border", "data-testid": "meter-bar", children: _jsx("span", { className: "block h-full rounded-full bg-accent transition-[width]", style: { width: `${ratio * 100}%` }, "data-testid": "meter-fill" }) })] }));
|
|
29
29
|
} }) }), of && !of.handle.hidden && (_jsx("div", { className: "min-w-0 text-sm text-muted", "data-testid": "meter-of", children: _jsx(Slot, { name: ofName }) }))] }));
|
|
30
30
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
31
31
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { registerRenderer } from "../../registry.js";
|
|
4
4
|
import { Slot, useSlots } from "../slot.js";
|
|
5
|
-
import { BlockShell, fieldLabel, findSlot, findSlots } from "./block-shell.js";
|
|
5
|
+
import { BlockShell, fieldLabel, fieldDatumClass, findSlot, findSlots } from "./block-shell.js";
|
|
6
6
|
function NutritionPage({ el }) {
|
|
7
7
|
const g = el;
|
|
8
8
|
const { t } = useTranslation();
|
|
@@ -15,7 +15,7 @@ function NutritionPage({ el }) {
|
|
|
15
15
|
const visibleRows = rows.filter((r) => !r.handle.hidden);
|
|
16
16
|
if (!energyVisible && visibleRows.length === 0)
|
|
17
17
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: null }));
|
|
18
|
-
const body = (_jsxs("div", { className: "w-full max-w-xs border-2 border-text p-3", "data-testid": "nutrition", children: [energyVisible && (_jsx("div", { className: "mb-2 border-b-4 border-text pb-2", "data-testid": "nutrition-energy-row", children: _jsx(Slot, { name: energyName, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline justify-between gap-3", "data-testid": "nutrition-energy", children: [_jsx("span", { className: "min-w-0 text-base font-bold
|
|
18
|
+
const body = (_jsxs("div", { className: "w-full max-w-xs border-2 border-text p-3", "data-testid": "nutrition", children: [energyVisible && (_jsx("div", { className: "mb-2 border-b-4 border-text pb-2", "data-testid": "nutrition-energy-row", children: _jsx(Slot, { name: energyName, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline justify-between gap-3", "data-testid": "nutrition-energy", children: [_jsx("span", { className: "min-w-0 text-base font-bold tracking-wide", children: fieldLabel(handle, t) }), _jsx("span", { className: `shrink-0 text-2xl font-black ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay })] })) }) })), _jsx("div", { className: "flex flex-col divide-y divide-border", children: rows.map((r, i) => r.handle.hidden ? null : (_jsx("div", { className: "min-w-0 py-1", "data-testid": `nutrition-row-${i}`, children: _jsx(Slot, { name: r.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline justify-between gap-3 text-sm", children: [_jsx("span", { className: "min-w-0", children: fieldLabel(handle, t) }), _jsx("span", { className: `shrink-0 text-right font-medium ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay })] })) }) }, r.name))) })] }));
|
|
19
19
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
20
20
|
}
|
|
21
21
|
registerRenderer({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { registerRenderer } from "../../registry.js";
|
|
3
3
|
import { Slot, useSlots } from "../slot.js";
|
|
4
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
4
5
|
import { BlockShell, findSlot, findSlots } from "./block-shell.js";
|
|
5
6
|
function PlaquePage({ el }) {
|
|
6
7
|
const g = el;
|
|
@@ -9,7 +10,7 @@ function PlaquePage({ el }) {
|
|
|
9
10
|
const slots = useSlots();
|
|
10
11
|
const title = findSlot(slots, titleName);
|
|
11
12
|
const subtitles = findSlots(slots, subtitleNames);
|
|
12
|
-
const body = (_jsxs("div", { className: "flex flex-col items-center gap-3 py-4 text-center", "data-testid": "plaque", children: [title && !title.handle.hidden && (_jsx("div", { className: "min-w-0 w-full border-b border-border pb-3", "data-testid": "plaque-item-0", children: _jsx(Slot, { name: titleName, render: (_handle, defaultDisplay) => (_jsx("div", { className: "text-base font-semibold text-text", "data-testid": "plaque-title", children: defaultDisplay })) }) })), subtitles.map((s, i) => s.handle.hidden ? null : (_jsx("div", { className: "min-w-0", "data-testid": `plaque-item-${i + 1}`, children: _jsx(Slot, { name: s.name, render: (_handle, defaultDisplay) => (_jsx("div", { className:
|
|
13
|
+
const body = (_jsxs("div", { className: "flex flex-col items-center gap-3 py-4 text-center", "data-testid": "plaque", children: [title && !title.handle.hidden && (_jsx("div", { className: "min-w-0 w-full border-b border-border pb-3", "data-testid": "plaque-item-0", children: _jsx(Slot, { name: titleName, render: (_handle, defaultDisplay) => (_jsx("div", { className: "text-base font-semibold text-text", "data-testid": "plaque-title", children: defaultDisplay })) }) })), subtitles.map((s, i) => s.handle.hidden ? null : (_jsx("div", { className: "min-w-0", "data-testid": `plaque-item-${i + 1}`, children: _jsx(Slot, { name: s.name, render: (_handle, defaultDisplay) => (_jsx("div", { className: CHROME_LABEL_CLS, "data-testid": "plaque-subtitle", children: defaultDisplay })) }) }, s.name)))] }));
|
|
13
14
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
14
15
|
}
|
|
15
16
|
registerRenderer({ kinds: ['plaque'], layout: 'block', editor: 'custom', Page: PlaquePage });
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { registerRenderer } from "../../registry.js";
|
|
4
4
|
import { Slot, useSlots } from "../slot.js";
|
|
5
|
-
import { BlockShell, fieldLabel, findSlot, findSlots } from "./block-shell.js";
|
|
5
|
+
import { BlockShell, fieldLabel, fieldDatumClass, findSlot, findSlots } from "./block-shell.js";
|
|
6
6
|
function ReceiptPage({ el }) {
|
|
7
7
|
const g = el;
|
|
8
8
|
const { t } = useTranslation();
|
|
@@ -11,7 +11,7 @@ function ReceiptPage({ el }) {
|
|
|
11
11
|
const slots = useSlots();
|
|
12
12
|
const lines = findSlots(slots, lineNames);
|
|
13
13
|
const total = findSlot(slots, totalName);
|
|
14
|
-
const body = (_jsxs("div", { className: "flex flex-col", "data-testid": "receipt", children: [_jsx("div", { className: "flex flex-col divide-y divide-dashed divide-border", children: lines.map((line, i) => line.handle.hidden ? null : (_jsx("div", { className: "min-w-0 py-1.5", "data-testid": `receipt-line-${i}`, children: _jsx(Slot, { name: line.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline justify-between gap-3", "data-testid": "receipt-line", children: [_jsx("span", { className: "min-w-0 text-muted", children: fieldLabel(handle, t) }), _jsx("span", { className:
|
|
14
|
+
const body = (_jsxs("div", { className: "flex flex-col", "data-testid": "receipt", children: [_jsx("div", { className: "flex flex-col divide-y divide-dashed divide-border", children: lines.map((line, i) => line.handle.hidden ? null : (_jsx("div", { className: "min-w-0 py-1.5", "data-testid": `receipt-line-${i}`, children: _jsx(Slot, { name: line.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline justify-between gap-3", "data-testid": "receipt-line", children: [_jsx("span", { className: "min-w-0 text-muted", children: fieldLabel(handle, t) }), _jsx("span", { className: `shrink-0 text-right ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay })] })) }) }, line.name))) }), total && !total.handle.hidden && (_jsx("div", { className: "mt-2 min-w-0 border-t-2 border-border pt-2", "data-testid": "receipt-total-row", children: _jsx(Slot, { name: totalName, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex w-full min-w-0 items-baseline justify-between gap-3 text-lg font-bold", "data-testid": "receipt-total", children: [_jsx("span", { className: "min-w-0", children: fieldLabel(handle, t) }), _jsx("span", { className: `shrink-0 text-right ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay })] })) }) }))] }));
|
|
15
15
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
16
16
|
}
|
|
17
17
|
registerRenderer({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { registerRenderer } from "../../registry.js";
|
|
3
3
|
import { Slot, useSlots } from "../slot.js";
|
|
4
|
-
import { BlockShell, findSlot } from "./block-shell.js";
|
|
4
|
+
import { BlockShell, findSlot, fieldDatumClass } from "./block-shell.js";
|
|
5
5
|
function ScorePage({ el }) {
|
|
6
6
|
const g = el;
|
|
7
7
|
const sourceName = g.view?.source;
|
|
@@ -9,7 +9,7 @@ function ScorePage({ el }) {
|
|
|
9
9
|
const max = g.view?.max;
|
|
10
10
|
const slots = useSlots();
|
|
11
11
|
const verdict = findSlot(slots, verdictName);
|
|
12
|
-
const body = (_jsxs("div", { className: "flex flex-wrap items-baseline gap-3", "data-testid": "score", children: [_jsx("div", { className: "min-w-0", children: _jsx(Slot, { name: sourceName, render: (
|
|
12
|
+
const body = (_jsxs("div", { className: "flex flex-wrap items-baseline gap-3", "data-testid": "score", children: [_jsx("div", { className: "min-w-0", children: _jsx(Slot, { name: sourceName, render: (handle, defaultDisplay) => (_jsxs("span", { className: `inline-flex items-baseline gap-0.5 text-3xl font-bold ${fieldDatumClass(handle)}`.trim(), "data-testid": "score-figure", children: [defaultDisplay, max != null && _jsxs("span", { className: "text-lg font-normal text-muted", children: ["/", max] })] })) }) }), verdict && !verdict.handle.hidden && (_jsx("div", { className: "min-w-0 text-muted", "data-testid": "score-verdict", children: _jsx(Slot, { name: verdictName }) }))] }));
|
|
13
13
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
14
14
|
}
|
|
15
15
|
registerRenderer({ kinds: ['score'], layout: 'block', editor: 'custom', Page: ScorePage });
|
|
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { registerRenderer } from "../../registry.js";
|
|
4
4
|
import { Slot, useSlots } from "../slot.js";
|
|
5
|
-
import {
|
|
5
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
6
|
+
import { BlockShell, fieldLabel, fieldDatumClass } from "./block-shell.js";
|
|
6
7
|
function StatsPage({ el }) {
|
|
7
8
|
const g = el;
|
|
8
9
|
const { t } = useTranslation();
|
|
@@ -10,7 +11,7 @@ function StatsPage({ el }) {
|
|
|
10
11
|
const body = (_jsx("div", { className: "flex flex-wrap gap-x-8 gap-y-4 sm:flex-nowrap sm:divide-x sm:divide-border py-1", "data-testid": "stats", children: slots.map((s, i) => {
|
|
11
12
|
if (s.handle.hidden)
|
|
12
13
|
return null;
|
|
13
|
-
return (_jsx("div", { className: "min-w-0 flex-1 sm:px-5 sm:first:pl-0", "data-testid": `stats-item-${i}`, children: _jsx(Slot, { name: s.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex min-w-0 flex-col items-start gap-0.5 py-1", "data-testid": "stats-figure", children: [_jsx("span", { className:
|
|
14
|
+
return (_jsx("div", { className: "min-w-0 flex-1 sm:px-5 sm:first:pl-0", "data-testid": `stats-item-${i}`, children: _jsx(Slot, { name: s.name, ownLabel: true, render: (handle, defaultDisplay) => (_jsxs("span", { className: "flex min-w-0 flex-col items-start gap-0.5 py-1", "data-testid": "stats-figure", children: [_jsx("span", { className: `min-w-0 text-2xl font-semibold text-text ${fieldDatumClass(handle)}`.trim(), children: defaultDisplay }), _jsx("span", { className: CHROME_LABEL_CLS, children: fieldLabel(handle, t) })] })) }) }, s.name));
|
|
14
15
|
}) }));
|
|
15
16
|
return (_jsx(BlockShell, { label: g.label, icon: g.icon, children: body }));
|
|
16
17
|
}
|
|
@@ -7,10 +7,11 @@ import { registerRenderer } from "../../registry.js";
|
|
|
7
7
|
import { Icon } from "../../components/Icon.js";
|
|
8
8
|
import { AddBtn } from "../shared.js";
|
|
9
9
|
import { RemoveBtn } from "../multiple-chips.js";
|
|
10
|
-
import { groupScopeClass, useGroupDepth, GroupDepthProvider } from "../chrome.js";
|
|
10
|
+
import { groupScopeClass, useGroupDepth, GroupDepthProvider, CHROME_LABEL_CLS } from "../chrome.js";
|
|
11
11
|
import { useGroupEdit } from "../widgets/group-edit.js";
|
|
12
12
|
import { useHybridScope } from "../hybrid-scope.js";
|
|
13
13
|
import { FieldSlot, fieldKeys } from "../dispatch.js";
|
|
14
|
+
import { datumClass } from "../datum.js";
|
|
14
15
|
import { useFieldErrors } from "../field-errors.js";
|
|
15
16
|
import { sortByField } from "../sort-rows.js";
|
|
16
17
|
import { useCollectionRows } from "../use-collection-rows.js";
|
|
@@ -123,12 +124,12 @@ function TablePage({ el, value, recordCtx, mode }) {
|
|
|
123
124
|
const totalRow = (bucketRows, testId) => (_jsxs("tr", { "data-testid": testId, className: "text-xs text-muted", children: [numbered && _jsx("td", { className: "border-b border-border py-1.5 pr-3" }), cols.map((f, i) => {
|
|
124
125
|
const kind = totals?.[f.key];
|
|
125
126
|
const n = kind && colOf(f.key) ? aggregate(kind, f, bucketRows) : undefined;
|
|
126
|
-
return (_jsxs("td", { className:
|
|
127
|
+
return (_jsxs("td", { className: `border-b border-border py-1.5 pr-4 ${datumClass(f.type)}`.trim(), children: [i === 0 && !totals?.[f.key] ? t('table.total') : null, n === undefined ? null : (_jsx(FieldSlot, { el: f, field: f.type, value: totalValue(f, bucketRows, n), mode: "view", bare: true, recordCtx: recordCtx }))] }, f.key));
|
|
127
128
|
}), rowTools && _jsx("td", { className: "border-b border-border" })] }));
|
|
128
129
|
const { rowId, updateRow, removeRow, addRow } = useCollectionRows(rows, (v) => g.onChange(v));
|
|
129
130
|
const errorFor = (idx, key) => isStorageGroup(group) ? errorAt([group.key, idx, key]) : undefined;
|
|
130
|
-
const bodyRow = (row, idx, seq) => (_jsxs("tr", { "data-testid": `table-row-${idx}`, children: [numbered && _jsx("td", { className: "border-b border-border py-2 pr-3 text-xs text-muted", children: seq }), cols.map((f) => (_jsx("td", { className:
|
|
131
|
-
const content = (_jsxs("div", { className: "flex flex-col gap-2 min-w-0", children: [_jsx("div", { className: "overflow-auto max-h-[60vh]", children: _jsxs("table", { className: "w-full border-separate border-spacing-0 text-sm", "data-testid": "table-block", children: [_jsx("thead", { className: "eink-solid sticky top-0 z-10 bg-bg/95 backdrop-blur", children: _jsxs("tr", { children: [numbered && _jsx("th", { className: "border-b border-border py-1.5 pr-3" }), cols.map((f) => (_jsx("th", { className:
|
|
131
|
+
const bodyRow = (row, idx, seq) => (_jsxs("tr", { "data-testid": `table-row-${idx}`, children: [numbered && _jsx("td", { className: "border-b border-border py-2 pr-3 text-xs text-muted", children: seq }), cols.map((f) => (_jsx("td", { className: `border-b border-border py-2 pr-4 align-top ${datumClass(f.type)}`.trim(), children: cellNode(f, row, editing, live, (v) => updateRow(idx, f.key, v), recordCtx, errorFor(idx, f.key)) }, f.key))), rowTools && (_jsx("td", { className: "border-b border-border py-2 align-top", children: _jsx(RemoveBtn, { className: "mt-0.5", onClick: () => removeRow(idx) }) }))] }, rowId(row)));
|
|
132
|
+
const content = (_jsxs("div", { className: "flex flex-col gap-2 min-w-0", children: [_jsx("div", { className: "overflow-auto max-h-[60vh]", children: _jsxs("table", { className: "w-full border-separate border-spacing-0 text-sm", "data-testid": "table-block", children: [_jsx("thead", { className: "eink-solid sticky top-0 z-10 bg-bg/95 backdrop-blur", children: _jsxs("tr", { children: [numbered && _jsx("th", { className: "border-b border-border py-1.5 pr-3" }), cols.map((f) => (_jsx("th", { className: `border-b border-border py-1.5 pr-4 text-left ${CHROME_LABEL_CLS}`, children: _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 focus-visible:outline-2 focus-visible:outline-accent", onClick: () => cycle(f.key), children: [t(f.type.label), !editing && sort?.key === f.key && (_jsx(Icon, { name: sort.dir === 'asc' ? 'lucide:arrow-up' : 'lucide:arrow-down', className: "size-3" }))] }) }, f.key))), rowTools && _jsx("th", { className: "border-b border-border" })] }) }), buckets.map((b) => (_jsxs("tbody", { children: [b.key !== null && (_jsx("tr", { "data-testid": `table-group-${b.key}`, children: _jsxs("th", { colSpan: cols.length + (numbered ? 1 : 0) + (rowTools ? 1 : 0), className: `border-b border-border pt-4 pb-1 text-left ${CHROME_LABEL_CLS} ${groupFld ? datumClass(groupFld.type) : ''}`.trim(), children: [b.key === NO_VALUE || !groupFld ? (b.label) : (_jsx(FieldSlot, { el: groupFld, field: groupFld.type, value: b.rows[0]?.[groupKey ?? ''], mode: "view", bare: true, recordCtx: recordCtx })), ' ', _jsx("span", { className: "text-muted/70", children: b.rows.length })] }) })), b.rows.map((row, i) => bodyRow(row, indexOf.get(row) ?? -1, i + 1)), hasTotals && b.key !== null && totalRow(b.rows, `table-subtotal-${b.key}`)] }, b.key ?? 'all'))), hasTotals && _jsx("tfoot", { children: totalRow(rows, 'table-total') })] }) }), rowTools && _jsx(AddBtn, { onClick: addRow, children: t('form.addRow') })] }));
|
|
132
133
|
if (mode !== 'view')
|
|
133
134
|
return content;
|
|
134
135
|
return (_jsxs("section", { className: `${groupScopeClass(depth)} flex flex-col gap-1 min-w-0`, "data-testid": "table", children: [group.label && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-[13px] font-semibold text-accent", children: t(group.label) }), !live && g.actions && _jsx("span", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: g.actions })] })), _jsx(GroupDepthProvider, { depth: depth + 1, children: content })] }));
|
package/dist/render/chrome.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import type { BoxGeom } from '../types.ts';
|
|
3
3
|
import type { FieldMeta, ContainerNode } from '@coffer-org/sdk/fields';
|
|
4
4
|
export declare const STACK = "gap-3.5";
|
|
@@ -14,6 +14,10 @@ export declare function FieldLabel({ label, required, actions, reserveAction, }:
|
|
|
14
14
|
reserveAction?: boolean;
|
|
15
15
|
htmlFor?: string;
|
|
16
16
|
}): import("react").JSX.Element;
|
|
17
|
+
export declare const CHROME_LABEL_CLS = "min-w-0 text-[11px] font-medium tracking-wide text-muted";
|
|
18
|
+
export declare function ChromeLabel({ children, ...rest }: {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
} & HTMLAttributes<HTMLSpanElement>): import("react").JSX.Element;
|
|
17
21
|
export declare function FieldLabelText({ label, required }: {
|
|
18
22
|
label: string;
|
|
19
23
|
required?: boolean;
|
|
@@ -41,11 +45,18 @@ export declare function groupScopeClass(depth: number): string;
|
|
|
41
45
|
export declare function groupRevealClass(depth: number): string;
|
|
42
46
|
export declare const ACTION_CHIP = "h-7 w-7 rounded-md border border-border bg-surface/80 text-muted hover:border-accent/60 hover:text-accent";
|
|
43
47
|
export declare const ACTION_CHIP_ACCENT = "h-7 w-7 rounded-md border border-accent/40 bg-accent/10 text-accent hover:border-accent hover:bg-accent/20 hover:text-accent";
|
|
44
|
-
export declare function EditToggle({ onEdit,
|
|
48
|
+
export declare function EditToggle({ onEdit, scope }: {
|
|
45
49
|
onEdit: () => void;
|
|
46
|
-
variant?: 'inline' | 'overlay';
|
|
47
50
|
scope?: 'field' | 'group';
|
|
48
51
|
}): import("react").JSX.Element;
|
|
52
|
+
export declare function ValueActions({ getText, onEdit }: {
|
|
53
|
+
getText: () => string;
|
|
54
|
+
onEdit: () => void;
|
|
55
|
+
}): import("react").JSX.Element;
|
|
56
|
+
export declare function ValueActionsOverlay({ onEdit, children }: {
|
|
57
|
+
onEdit: () => void;
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
}): import("react").JSX.Element;
|
|
49
60
|
export declare function SaveCancel({ size, busy, disabled, onSave, onCancel, }: {
|
|
50
61
|
size: 'xs' | 'sm';
|
|
51
62
|
busy: boolean;
|
|
@@ -59,6 +70,13 @@ export declare function FieldBox({ geom, field, children }: {
|
|
|
59
70
|
field: FieldMeta;
|
|
60
71
|
children: ReactNode;
|
|
61
72
|
}): import("react").JSX.Element;
|
|
73
|
+
export declare function TriggerBox({ expanded, label, onOpen, haspopup, children, }: {
|
|
74
|
+
expanded?: boolean;
|
|
75
|
+
label?: string;
|
|
76
|
+
onOpen: () => void;
|
|
77
|
+
haspopup?: 'dialog';
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
}): import("react").JSX.Element;
|
|
62
80
|
export declare function FieldError({ error }: {
|
|
63
81
|
error?: string;
|
|
64
82
|
}): import("react").JSX.Element | null;
|