@cosmicdrift/kumiko-renderer 0.385.0 → 0.387.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/LICENSE +21 -57
- package/dist/app/action-form-body.d.ts +2 -2
- package/dist/app/action-form-body.js +2 -2
- package/dist/app/action-form-shim.d.ts +8 -8
- package/dist/app/action-form-shim.js +20 -22
- package/dist/app/app-features-context.js +4 -4
- package/dist/app/column-renderers.d.ts +7 -9
- package/dist/app/column-renderers.js +15 -18
- package/dist/app/config-edit-body.js +1 -1
- package/dist/app/config-edit-shim.d.ts +7 -8
- package/dist/app/config-edit-shim.js +18 -20
- package/dist/app/custom-screens.d.ts +3 -3
- package/dist/app/custom-screens.js +8 -8
- package/dist/app/dashboard-body.d.ts +2 -2
- package/dist/app/dashboard-body.js +2 -2
- package/dist/app/entity-edit-prefill.js +9 -9
- package/dist/app/entity-edit-screen.js +4 -4
- package/dist/app/entity-list-screen.js +3 -3
- package/dist/app/extension-form-submit.d.ts +1 -1
- package/dist/app/extension-form-submit.js +14 -15
- package/dist/app/extension-sections.d.ts +24 -25
- package/dist/app/extension-sections.js +10 -10
- package/dist/app/feature-schema.js +11 -11
- package/dist/app/form-schema.js +1 -1
- package/dist/app/header-actions-bar.js +1 -1
- package/dist/app/kumiko-screen.js +8 -10
- package/dist/app/layout-fields.js +1 -1
- package/dist/app/list-facets.js +3 -3
- package/dist/app/nav.d.ts +10 -10
- package/dist/app/nav.js +13 -13
- package/dist/app/projection-detail-body.js +4 -4
- package/dist/app/projection-detail-shim.d.ts +6 -5
- package/dist/app/projection-detail-shim.js +23 -22
- package/dist/app/projection-list-body.js +10 -10
- package/dist/app/projection-list-shim.d.ts +5 -5
- package/dist/app/projection-list-shim.js +17 -18
- package/dist/app/qn.js +10 -12
- package/dist/app/qualify-screen-id.js +1 -1
- package/dist/app/screen-access.js +2 -2
- package/dist/app/screen-shared.js +1 -1
- package/dist/app/secret-mint-body.js +4 -4
- package/dist/app/write-failed-error.js +8 -7
- package/dist/components/embedded-list-field.js +4 -4
- package/dist/components/grid-cell-for-field.d.ts +1 -1
- package/dist/components/reference-create-dialog.js +3 -3
- package/dist/components/related-list-section.d.ts +1 -1
- package/dist/components/related-list-section.js +28 -5
- package/dist/components/render-edit-logic.js +1 -1
- package/dist/components/render-edit-types.d.ts +6 -6
- package/dist/components/render-edit.js +63 -65
- package/dist/components/render-field.d.ts +7 -7
- package/dist/components/render-field.js +57 -59
- package/dist/components/render-list.d.ts +43 -44
- package/dist/components/render-list.js +63 -67
- package/dist/components/use-wizard-form-draft.js +10 -10
- package/dist/context/dispatcher-context.js +5 -5
- package/dist/context/user-roles-context.js +2 -2
- package/dist/headless/apex/index.d.ts +1 -2
- package/dist/headless/apex/index.js +5 -6
- package/dist/headless/form/form-controller.js +6 -6
- package/dist/headless/form/zod-bridge.js +2 -2
- package/dist/headless/format/money.js +1 -1
- package/dist/headless/index.js +2 -2
- package/dist/headless/view-model/edit.js +2 -2
- package/dist/headless/view-model/list.js +3 -4
- package/dist/headless/view-model/types.d.ts +3 -3
- package/dist/hooks/reference-limits.js +11 -12
- package/dist/hooks/use-list-url-state.d.ts +13 -13
- package/dist/hooks/use-list-url-state.js +21 -22
- package/dist/hooks/use-query.js +3 -3
- package/dist/hooks/use-reference-lookup.js +16 -17
- package/dist/hooks/use-store.js +18 -22
- package/dist/hooks/use-stream-handler.js +4 -2
- package/dist/i18n-defaults.js +2 -8
- package/dist/i18n.d.ts +10 -10
- package/dist/i18n.js +25 -25
- package/dist/index.js +5 -4
- package/dist/live/dispatcher-live.js +2 -2
- package/dist/primitives.d.ts +225 -221
- package/dist/primitives.js +1 -1
- package/dist/sse/live-events.d.ts +5 -5
- package/dist/sse/live-events.js +17 -17
- package/dist/tokens.d.ts +8 -8
- package/dist/tokens.js +16 -17
- package/dist/web/app/browser-locale.d.ts +9 -9
- package/dist/web/app/browser-locale.js +19 -19
- package/dist/web/app/client-plugin.d.ts +44 -52
- package/dist/web/app/client-plugin.js +4 -5
- package/dist/web/app/create-app.d.ts +28 -31
- package/dist/web/app/create-app.js +21 -22
- package/dist/web/app/document-lang-sync.js +1 -1
- package/dist/web/app/draft-storage.js +1 -1
- package/dist/web/app/nav-providers-context.d.ts +3 -3
- package/dist/web/app/nav-providers-context.js +3 -3
- package/dist/web/app/nav.d.ts +18 -17
- package/dist/web/app/nav.js +66 -64
- package/dist/web/app/rich-content-editor.js +1 -1
- package/dist/web/components/config-cascade.js +17 -18
- package/dist/web/components/config-source-badge.js +4 -4
- package/dist/web/layout/app-layout.d.ts +9 -9
- package/dist/web/layout/app-layout.js +2 -2
- package/dist/web/layout/avatar.d.ts +4 -3
- package/dist/web/layout/avatar.js +15 -17
- package/dist/web/layout/default-app-shell.d.ts +22 -23
- package/dist/web/layout/editor-panel.js +3 -3
- package/dist/web/layout/nav-tree.js +86 -84
- package/dist/web/layout/profile-menu.d.ts +1 -1
- package/dist/web/layout/sidebar-brand.d.ts +7 -7
- package/dist/web/layout/sidebar-user.d.ts +1 -1
- package/dist/web/layout/sidebar-user.js +4 -4
- package/dist/web/layout/sidebar.d.ts +8 -8
- package/dist/web/layout/sidebar.js +7 -7
- package/dist/web/layout/target-resolver-stub.d.ts +5 -5
- package/dist/web/layout/target-resolver-stub.js +20 -20
- package/dist/web/layout/target-url.d.ts +7 -7
- package/dist/web/layout/target-url.js +22 -22
- package/dist/web/layout/theme-toggle.js +4 -5
- package/dist/web/layout/workspace-shell.d.ts +21 -21
- package/dist/web/layout/workspace-shell.js +29 -30
- package/dist/web/lib/cn.d.ts +4 -4
- package/dist/web/lib/cn.js +4 -4
- package/dist/web/primitives/action-menu.d.ts +8 -8
- package/dist/web/primitives/action-menu.js +14 -13
- package/dist/web/primitives/calendar-popover.d.ts +1 -1
- package/dist/web/primitives/calendar-popover.js +10 -10
- package/dist/web/primitives/card-styles.js +1 -1
- package/dist/web/primitives/card.js +2 -2
- package/dist/web/primitives/combobox.d.ts +12 -12
- package/dist/web/primitives/combobox.js +34 -35
- package/dist/web/primitives/data-table-cell.js +2 -2
- package/dist/web/primitives/data-table.js +2 -3
- package/dist/web/primitives/date-field.js +2 -2
- package/dist/web/primitives/date-input.js +4 -4
- package/dist/web/primitives/default-form.js +2 -2
- package/dist/web/primitives/dropdown-menu.js +14 -15
- package/dist/web/primitives/embedded-list-input.js +3 -3
- package/dist/web/primitives/facet-filter.js +1 -1
- package/dist/web/primitives/field.js +9 -8
- package/dist/web/primitives/form.d.ts +1 -1
- package/dist/web/primitives/form.js +6 -6
- package/dist/web/primitives/input.js +2 -2
- package/dist/web/primitives/layout-elements.js +1 -1
- package/dist/web/primitives/layout.js +2 -2
- package/dist/web/primitives/located-timestamp-input.js +4 -4
- package/dist/web/primitives/money-input.js +13 -17
- package/dist/web/primitives/pager.js +1 -1
- package/dist/web/primitives/row-actions.js +3 -4
- package/dist/web/primitives/secret-reveal.js +1 -1
- package/dist/web/primitives/timestamp-input.d.ts +9 -9
- package/dist/web/primitives/timestamp-input.js +21 -21
- package/dist/web/primitives/toast.js +12 -13
- package/dist/web/sse/live-events.d.ts +6 -6
- package/dist/web/sse/live-events.js +3 -3
- package/dist/web/tokens.d.ts +11 -12
- package/dist/web/tokens.js +35 -37
- package/dist/web/version/update-checker.js +14 -13
- package/dist/web/widgets/ai-text-field.js +1 -1
- package/dist/web/widgets/charts.d.ts +10 -10
- package/dist/web/widgets/charts.js +8 -8
- package/dist/web/widgets/collapsible-section.d.ts +3 -3
- package/dist/web/widgets/collapsible-section.js +6 -6
- package/dist/web/widgets/detail-list.d.ts +1 -3
- package/dist/web/widgets/detail-list.js +1 -3
- package/dist/web/widgets/feed-list.d.ts +2 -2
- package/dist/web/widgets/feed-list.js +2 -2
- package/dist/web/widgets/form-fields.d.ts +14 -14
- package/dist/web/widgets/form-fields.js +14 -13
- package/dist/web/widgets/index.js +4 -4
- package/dist/web/widgets/infinity-list.js +3 -3
- package/dist/web/widgets/mode-switch.d.ts +2 -2
- package/dist/web/widgets/mode-switch.js +2 -2
- package/dist/web/widgets/progress-list.d.ts +1 -2
- package/dist/web/widgets/progress-list.js +1 -2
- package/dist/web/widgets/query-table.d.ts +6 -6
- package/dist/web/widgets/result-panel.d.ts +16 -16
- package/dist/web/widgets/result-panel.js +10 -10
- package/dist/web/widgets/section-card.d.ts +3 -3
- package/dist/web/widgets/section-card.js +3 -3
- package/dist/web/widgets/stat.d.ts +4 -4
- package/dist/web/widgets/stat.js +6 -6
- package/dist/web/widgets/states.d.ts +4 -4
- package/dist/web/widgets/states.js +4 -4
- package/dist/web/widgets/use-draft.d.ts +7 -7
- package/dist/web/widgets/use-draft.js +7 -7
- package/package.json +6 -6
|
@@ -4,8 +4,8 @@ export type FeedRow = {
|
|
|
4
4
|
readonly primary: string;
|
|
5
5
|
readonly trailing?: string;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
7
|
+
/** Non-tabular compact list (e.g. "upcoming appointments") — primary text plus
|
|
8
|
+
* an optional right-aligned trailing value for each row. */
|
|
9
9
|
export declare function FeedList({ rows, emptyContent, testId, }: {
|
|
10
10
|
readonly rows: readonly FeedRow[];
|
|
11
11
|
readonly emptyContent?: ReactNode;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/** Non-tabular compact list (e.g. "upcoming appointments") — primary text plus
|
|
3
|
+
* an optional right-aligned trailing value for each row. */
|
|
4
4
|
export function FeedList({ rows, emptyContent, testId, }) {
|
|
5
5
|
if (rows.length === 0) {
|
|
6
6
|
return _jsx("div", { "data-testid": testId, children: emptyContent });
|
|
@@ -12,10 +12,10 @@ export interface NumberFieldProps {
|
|
|
12
12
|
/** `false` renders without thousands separators (e.g. a year). Default `true`. */
|
|
13
13
|
readonly grouping?: boolean;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
/** Number field = Field + Input(kind:"number"); avoids repeating the
|
|
16
|
+
* id/name/value/onChange wiring at each screen. `value` may be `undefined`
|
|
17
|
+
* (empty field) and is mapped internally to the Input's `""` empty state.
|
|
18
|
+
* Units (€/%) belong in the label (`t("…Summe (€)")`), not in a badge. */
|
|
19
19
|
export declare function NumberField({ label, id, name, value, onChange, required, disabled, hideLabel, testId, grouping, }: NumberFieldProps): ReactNode;
|
|
20
20
|
export declare const MoneyField: typeof NumberField;
|
|
21
21
|
export declare const PercentField: typeof NumberField;
|
|
@@ -34,7 +34,7 @@ export interface TextFieldProps extends FieldBase {
|
|
|
34
34
|
readonly placeholder?: string;
|
|
35
35
|
readonly autoComplete?: string;
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
37
|
+
/** Text field = Field + Input(kind:"text"). */
|
|
38
38
|
export declare function TextField({ label, id, name, value, onChange, required, disabled, placeholder, autoComplete, hideLabel, testId, }: TextFieldProps): ReactNode;
|
|
39
39
|
export interface SelectFieldProps extends FieldBase {
|
|
40
40
|
readonly value: string;
|
|
@@ -44,7 +44,7 @@ export interface SelectFieldProps extends FieldBase {
|
|
|
44
44
|
readonly label: string;
|
|
45
45
|
}[];
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
47
|
+
/** Select field = Field + Input(kind:"select"). */
|
|
48
48
|
export declare function SelectField({ label, id, name, value, onChange, options, required, disabled, hideLabel, testId, }: SelectFieldProps): ReactNode;
|
|
49
49
|
export interface DateFieldProps extends FieldBase {
|
|
50
50
|
readonly value: string;
|
|
@@ -52,7 +52,7 @@ export interface DateFieldProps extends FieldBase {
|
|
|
52
52
|
readonly min?: string;
|
|
53
53
|
readonly max?: string;
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
55
|
+
/** Date field = Field + Input(kind:"date"), ISO yyyy-mm-dd. */
|
|
56
56
|
export declare function DateField({ label, id, name, value, onChange, min, max, required, disabled, hideLabel, testId, }: DateFieldProps): ReactNode;
|
|
57
57
|
export interface BooleanFieldProps extends FieldBase {
|
|
58
58
|
readonly value: boolean;
|
|
@@ -65,7 +65,7 @@ export interface TextareaFieldProps extends FieldBase {
|
|
|
65
65
|
readonly onChange: (v: string) => void;
|
|
66
66
|
readonly rows?: number;
|
|
67
67
|
}
|
|
68
|
-
/**
|
|
68
|
+
/** Multi-line text field = Field + Input(kind:"textarea"). */
|
|
69
69
|
export declare function TextareaField({ label, id, name, value, onChange, rows, required, disabled, hideLabel, testId, }: TextareaFieldProps): ReactNode;
|
|
70
70
|
export interface RangeFieldProps extends FieldBase {
|
|
71
71
|
readonly value: number;
|
|
@@ -74,20 +74,20 @@ export interface RangeFieldProps extends FieldBase {
|
|
|
74
74
|
readonly max: number;
|
|
75
75
|
readonly step?: number;
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
77
|
+
/** Slider = Field + Input(kind:"range"). */
|
|
78
78
|
export declare function RangeField({ label, id, name, value, onChange, min, max, step, required, disabled, hideLabel, testId, }: RangeFieldProps): ReactNode;
|
|
79
79
|
export interface FileFieldProps extends FieldBase {
|
|
80
|
-
/** FileRef
|
|
80
|
+
/** FileRef UUID of the stored file, or null. */
|
|
81
81
|
readonly value: string | null;
|
|
82
82
|
readonly onChange: (fileId: string | null) => void;
|
|
83
83
|
readonly accept?: readonly string[];
|
|
84
|
-
/** "image"
|
|
84
|
+
/** "image" shows a preview; "file" shows only the file name. Default "file". */
|
|
85
85
|
readonly variant?: "file" | "image";
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
86
|
+
/** Binds the upload to a specific entity field; the `/api/files` endpoint
|
|
87
|
+
* then validates `accept`/`maxSize` server-side against that field's definition. */
|
|
88
88
|
readonly entityType?: string;
|
|
89
89
|
readonly fieldName?: string;
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
91
|
+
/** File upload = Field + Input(kind:"file"|"image"), based on FileRef. */
|
|
92
92
|
export declare function FileField({ label, id, name, value, onChange, accept, variant, entityType, fieldName, required, disabled, hideLabel, testId, }: FileFieldProps): ReactNode;
|
|
93
93
|
export {};
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { usePrimitives } from "../../index.js";
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
/** Number field = Field + Input(kind:"number"); avoids repeating the
|
|
4
|
+
* id/name/value/onChange wiring at each screen. `value` may be `undefined`
|
|
5
|
+
* (empty field) and is mapped internally to the Input's `""` empty state.
|
|
6
|
+
* Units (€/%) belong in the label (`t("…Summe (€)")`), not in a badge. */
|
|
7
7
|
export function NumberField({ label, id, name, value, onChange, required, disabled, hideLabel, testId, grouping, }) {
|
|
8
8
|
const { Field, Input } = usePrimitives();
|
|
9
9
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "number", id: id, name: name, value: value ?? "", onChange: onChange, required: required, disabled: disabled, ...(grouping !== undefined && { grouping }) }) }));
|
|
10
10
|
}
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// Aliases convey the field's intent at the call site (more readable than
|
|
12
|
+
// using NumberField everywhere) without pretending to be wrapper functions;
|
|
13
|
+
// both are identical to NumberField and are not intended for future
|
|
14
|
+
// money-/percentage-specific formatting.
|
|
14
15
|
export const MoneyField = NumberField;
|
|
15
16
|
export const PercentField = NumberField;
|
|
16
|
-
/**
|
|
17
|
+
/** Text field = Field + Input(kind:"text"). */
|
|
17
18
|
export function TextField({ label, id, name, value, onChange, required, disabled, placeholder, autoComplete, hideLabel, testId, }) {
|
|
18
19
|
const { Field, Input } = usePrimitives();
|
|
19
20
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "text", id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled, ...(placeholder !== undefined && { placeholder }), ...(autoComplete !== undefined && { autoComplete }) }) }));
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
+
/** Select field = Field + Input(kind:"select"). */
|
|
22
23
|
export function SelectField({ label, id, name, value, onChange, options, required, disabled, hideLabel, testId, }) {
|
|
23
24
|
const { Field, Input } = usePrimitives();
|
|
24
25
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "select", id: id, name: name, value: value, onChange: onChange, options: options, required: required, disabled: disabled }) }));
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
+
/** Date field = Field + Input(kind:"date"), ISO yyyy-mm-dd. */
|
|
27
28
|
export function DateField({ label, id, name, value, onChange, min, max, required, disabled, hideLabel, testId, }) {
|
|
28
29
|
const { Field, Input } = usePrimitives();
|
|
29
30
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "date", id: id, name: name, value: value, onChange: (v) => onChange(v ?? ""), required: required, disabled: disabled, ...(min !== undefined && { min }), ...(max !== undefined && { max }) }) }));
|
|
@@ -33,17 +34,17 @@ export function BooleanField({ label, id, name, value, onChange, required, disab
|
|
|
33
34
|
const { Field, Input } = usePrimitives();
|
|
34
35
|
return (_jsx(Field, { id: id, label: label, required: required, layout: "inline", hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "boolean", id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled }) }));
|
|
35
36
|
}
|
|
36
|
-
/**
|
|
37
|
+
/** Multi-line text field = Field + Input(kind:"textarea"). */
|
|
37
38
|
export function TextareaField({ label, id, name, value, onChange, rows, required, disabled, hideLabel, testId, }) {
|
|
38
39
|
const { Field, Input } = usePrimitives();
|
|
39
40
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "textarea", id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled, ...(rows !== undefined && { rows }) }) }));
|
|
40
41
|
}
|
|
41
|
-
/**
|
|
42
|
+
/** Slider = Field + Input(kind:"range"). */
|
|
42
43
|
export function RangeField({ label, id, name, value, onChange, min, max, step, required, disabled, hideLabel, testId, }) {
|
|
43
44
|
const { Field, Input } = usePrimitives();
|
|
44
45
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "range", id: id, name: name, value: value, onChange: onChange, min: min, max: max, required: required, disabled: disabled, ...(step !== undefined && { step }) }) }));
|
|
45
46
|
}
|
|
46
|
-
/**
|
|
47
|
+
/** File upload = Field + Input(kind:"file"|"image"), based on FileRef. */
|
|
47
48
|
export function FileField({ label, id, name, value, onChange, accept, variant = "file", entityType, fieldName, required, disabled, hideLabel, testId, }) {
|
|
48
49
|
const { Field, Input } = usePrimitives();
|
|
49
50
|
return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: variant, id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled, ...(accept !== undefined && { accept }), ...(entityType !== undefined && { entityType }), ...(fieldName !== undefined && { fieldName }) }) }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Mid-
|
|
2
|
-
// Banner)
|
|
3
|
-
//
|
|
4
|
-
// styleguide
|
|
1
|
+
// Mid-level widgets — compositions built from primitives (Card, DataTable,
|
|
2
|
+
// Banner) and theme tokens. For custom screens, check here before building.
|
|
3
|
+
// Catalog: docs.kumiko.rocks → Guides → Widgets; visual overview in the
|
|
4
|
+
// styleguide sample.
|
|
5
5
|
export { AiTextArea, AiTextField, } from "./ai-text-field.js";
|
|
6
6
|
export { SegmentBarChart, StackedAreaChart, StackedBarChart, StatusBarChart, smoothPath, TimeseriesChart, } from "./charts.js";
|
|
7
7
|
export { CollapsibleSection } from "./collapsible-section.js";
|
|
@@ -28,7 +28,7 @@ export function InfinityList({ query, payload, pageSize = 50, rows, nextCursor,
|
|
|
28
28
|
// Discards a response whose request was superseded by a newer one before
|
|
29
29
|
// it resolved (e.g. two searches fired in quick succession) — without
|
|
30
30
|
// this, a slow earlier response can land after a faster later one and
|
|
31
|
-
// overwrite it, leaving stale rows on screen
|
|
31
|
+
// overwrite it, leaving stale rows on screen.
|
|
32
32
|
const requestSeq = useRef(0);
|
|
33
33
|
// IDs of the most recently loaded first page. refreshFirstPage() needs
|
|
34
34
|
// this to tell "row dropped from page 1 elsewhere" (must be pruned) apart
|
|
@@ -84,7 +84,7 @@ export function InfinityList({ query, payload, pageSize = 50, rows, nextCursor,
|
|
|
84
84
|
// to the front (newest-first feeds), rows it dropped (edited/deleted
|
|
85
85
|
// elsewhere) are pruned, and everything beyond page 1 stays untouched.
|
|
86
86
|
// A full reload would collapse already-accumulated pages and jump the
|
|
87
|
-
// scroll position
|
|
87
|
+
// scroll position.
|
|
88
88
|
// biome-ignore lint/correctness/useExhaustiveDependencies: payload goes through payloadKey
|
|
89
89
|
const refreshFirstPage = useCallback(async () => {
|
|
90
90
|
// Read, don't bump: a concurrent load() (e.g. the mount fetch still
|
|
@@ -127,7 +127,7 @@ export function InfinityList({ query, payload, pageSize = 50, rows, nextCursor,
|
|
|
127
127
|
});
|
|
128
128
|
}, [dispatcher, query, pageSize, payloadKey]);
|
|
129
129
|
// Trailing debounce + in-flight coalesce: bulk SSE storms must not fan out
|
|
130
|
-
// into one query per event
|
|
130
|
+
// into one query per event. A refresh already running schedules
|
|
131
131
|
// exactly one trailing pass after it settles.
|
|
132
132
|
const liveTimerRef = useRef(null);
|
|
133
133
|
const liveInFlightRef = useRef(false);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
export type ModeSwitchVariant = "outline" | "pill";
|
|
3
|
-
/** Segmented
|
|
4
|
-
*
|
|
3
|
+
/** Segmented control for mutually exclusive modes—the prominent
|
|
4
|
+
* alternative to the buried <select>. */
|
|
5
5
|
export declare function ModeSwitch<T extends string>({ value, options, onChange, ariaLabel, variant, className, testId, }: {
|
|
6
6
|
/** `null`: no option active. */
|
|
7
7
|
readonly value: T | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
-
/** Segmented
|
|
4
|
-
*
|
|
3
|
+
/** Segmented control for mutually exclusive modes—the prominent
|
|
4
|
+
* alternative to the buried <select>. */
|
|
5
5
|
export function ModeSwitch({ value, options, onChange, ariaLabel, variant = "outline", className, testId, }) {
|
|
6
6
|
const pill = variant === "pill";
|
|
7
7
|
return (_jsx("div", { "data-testid": testId, role: "group", "aria-label": ariaLabel, className: cn("flex min-h-8", pill
|
|
@@ -10,8 +10,7 @@ export type ProgressListRow = {
|
|
|
10
10
|
/** Small line under the bar, e.g. "42 % repaid". */
|
|
11
11
|
readonly sub?: string;
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
14
|
-
* Tilgungsfortschritt pro Kredit). */
|
|
13
|
+
/** Label/value header and progress bar for each entry (for example, loan repayment progress). */
|
|
15
14
|
export declare function ProgressList({ rows, emptyContent, testId, }: {
|
|
16
15
|
readonly rows: readonly ProgressListRow[];
|
|
17
16
|
readonly emptyContent?: ReactNode;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { ProgressBar } from "./progress-bar.js";
|
|
4
|
-
/**
|
|
5
|
-
* Tilgungsfortschritt pro Kredit). */
|
|
4
|
+
/** Label/value header and progress bar for each entry (for example, loan repayment progress). */
|
|
6
5
|
export function ProgressList({ rows, emptyContent, testId, }) {
|
|
7
6
|
if (rows.length === 0) {
|
|
8
7
|
return _jsx("div", { "data-testid": testId, children: emptyContent });
|
|
@@ -2,25 +2,25 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import { type DataTableProps } from "../../index.ts";
|
|
3
3
|
export type QueryTableColumn = {
|
|
4
4
|
readonly field: string;
|
|
5
|
-
/** Translated
|
|
5
|
+
/** Translated header label (resolving the i18n key is the caller's responsibility). */
|
|
6
6
|
readonly label: string;
|
|
7
|
-
/** Field
|
|
7
|
+
/** Field type for cell formatting. Default: "text". */
|
|
8
8
|
readonly type?: string;
|
|
9
9
|
};
|
|
10
10
|
export type QueryTableProps<TData = unknown> = {
|
|
11
11
|
/** Dispatcher-Query-Type (`<feature>:query:<entity>:<verb>`). */
|
|
12
12
|
readonly query: string;
|
|
13
13
|
readonly payload?: unknown;
|
|
14
|
-
/** SSE
|
|
14
|
+
/** SSE invalidation — refetches on entity events (useQuery live mode). */
|
|
15
15
|
readonly live?: boolean;
|
|
16
16
|
readonly columns: readonly QueryTableColumn[];
|
|
17
|
-
/**
|
|
17
|
+
/** Extracts rows from the query result. Default: the result itself as an array. */
|
|
18
18
|
readonly rows?: (data: TData) => readonly Readonly<Record<string, unknown>>[];
|
|
19
|
-
/** Row
|
|
19
|
+
/** Row ID for keys and row clicks. Default: `row.id`, otherwise the index. */
|
|
20
20
|
readonly rowId?: (row: Readonly<Record<string, unknown>>, index: number) => string;
|
|
21
21
|
readonly onRowClick?: DataTableProps["onRowClick"];
|
|
22
22
|
readonly rowActions?: DataTableProps["rowActions"];
|
|
23
|
-
/** Empty
|
|
23
|
+
/** Empty content — defaults to the standard list empty state. */
|
|
24
24
|
readonly emptyState?: ReactNode;
|
|
25
25
|
readonly toolbarTitle?: ReactNode;
|
|
26
26
|
readonly toolbarEnd?: ReactNode;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Result section for a calculator: SectionCard with an empty state (Banner)
|
|
3
|
+
* or a metric list (DetailList) plus optional extras (table, notes).
|
|
4
|
+
* Replaces hand-built `<dl>`/banner blocks in custom screens. */
|
|
5
5
|
export declare function ResultPanel({ title, subtitle, empty, emptyText, rows, footer, children, testId, }: {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly subtitle?: string;
|
|
@@ -12,7 +12,7 @@ export declare function ResultPanel({ title, subtitle, empty, emptyText, rows, f
|
|
|
12
12
|
readonly value: ReactNode;
|
|
13
13
|
readonly emphasize?: boolean;
|
|
14
14
|
}[];
|
|
15
|
-
/** Action
|
|
15
|
+
/** Action slot at the bottom of the card (e.g. "Move to financing"). */
|
|
16
16
|
readonly footer?: ReactNode;
|
|
17
17
|
readonly children?: ReactNode;
|
|
18
18
|
readonly testId?: string;
|
|
@@ -27,18 +27,18 @@ export interface ResultTableFooterRow {
|
|
|
27
27
|
readonly value: ReactNode;
|
|
28
28
|
readonly emphasize?: boolean;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
* ponytail:
|
|
33
|
-
*
|
|
30
|
+
/** Static, prop-driven result table for calculated rows (tranches, scenarios).
|
|
31
|
+
* Avoids repeating the `<table>`+tabular-nums pattern in each screen.
|
|
32
|
+
* ponytail: intentionally minimal and read-only—for sorting, paging, or facets,
|
|
33
|
+
* use usePrimitives().DataTable or QueryTable instead. */
|
|
34
34
|
export declare function ResultTable<Row>({ columns, rows, rowKey, testId, className, card, footer, }: {
|
|
35
35
|
readonly columns: readonly ResultColumn<Row>[];
|
|
36
36
|
readonly rows: readonly Row[];
|
|
37
37
|
readonly rowKey: (row: Row, index: number) => string;
|
|
38
38
|
readonly testId?: string;
|
|
39
39
|
readonly className?: string;
|
|
40
|
-
/**
|
|
41
|
-
* bg-muted
|
|
40
|
+
/** Framed appearance like the CRUD list (DataTable): rounded border container +
|
|
41
|
+
* bg-muted header band. Default = bare (table only, without a container). */
|
|
42
42
|
readonly card?: boolean;
|
|
43
43
|
/** Summary rows (Subtotal/Tax/Total) rendered as real `<tr>`s in the same
|
|
44
44
|
* `<table>` instead of a separate flex-div next to it — guarantees
|
|
@@ -49,12 +49,12 @@ export declare function ResultTable<Row>({ columns, rows, rowKey, testId, classN
|
|
|
49
49
|
export interface ComparisonMetric<Col> {
|
|
50
50
|
readonly label: string;
|
|
51
51
|
readonly value: (col: Col, index: number) => ReactNode;
|
|
52
|
-
/** Index
|
|
52
|
+
/** Index of the best column for this row (highlighted); -1 = none. */
|
|
53
53
|
readonly bestIndex?: (cols: readonly Col[]) => number;
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
/** Transposed comparison table: row = metric, column = variant, with the best
|
|
56
|
+
* column optionally highlighted for each metric. For scenario/offer comparisons
|
|
57
|
+
* where ResultTable (row = record) is not suitable. */
|
|
58
58
|
export declare function ComparisonTable<Col>({ columns, columnHeader, columnKey, metrics, metricLabel, testId, className, card, }: {
|
|
59
59
|
readonly columns: readonly Col[];
|
|
60
60
|
readonly columnHeader: (col: Col, index: number) => string;
|
|
@@ -63,7 +63,7 @@ export declare function ComparisonTable<Col>({ columns, columnHeader, columnKey,
|
|
|
63
63
|
readonly metricLabel: string;
|
|
64
64
|
readonly testId?: string;
|
|
65
65
|
readonly className?: string;
|
|
66
|
-
/**
|
|
67
|
-
* bg-muted
|
|
66
|
+
/** Framed appearance like the CRUD list (DataTable): rounded border container +
|
|
67
|
+
* bg-muted header band. Default = bare. */
|
|
68
68
|
readonly card?: boolean;
|
|
69
69
|
}): ReactNode;
|
|
@@ -3,24 +3,24 @@ import { usePrimitives } from "../../index.js";
|
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { DetailList } from "./detail-list.js";
|
|
5
5
|
import { SectionCard } from "./section-card.js";
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
/** Result section for a calculator: SectionCard with an empty state (Banner)
|
|
7
|
+
* or a metric list (DetailList) plus optional extras (table, notes).
|
|
8
|
+
* Replaces hand-built `<dl>`/banner blocks in custom screens. */
|
|
9
9
|
export function ResultPanel({ title, subtitle, empty, emptyText, rows, footer, children, testId, }) {
|
|
10
10
|
const { Banner } = usePrimitives();
|
|
11
11
|
return (_jsx(SectionCard, { title: title, subtitle: subtitle, footer: footer, testId: testId, children: empty === true ? (_jsx(Banner, { variant: "info", padded: true, children: emptyText })) : (_jsxs("div", { className: "flex flex-col gap-4", children: [rows !== undefined && rows.length > 0 && _jsx(DetailList, { rows: rows }), children] })) }));
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* ponytail:
|
|
16
|
-
*
|
|
13
|
+
/** Static, prop-driven result table for calculated rows (tranches, scenarios).
|
|
14
|
+
* Avoids repeating the `<table>`+tabular-nums pattern in each screen.
|
|
15
|
+
* ponytail: intentionally minimal and read-only—for sorting, paging, or facets,
|
|
16
|
+
* use usePrimitives().DataTable or QueryTable instead. */
|
|
17
17
|
export function ResultTable({ columns, rows, rowKey, testId, className, card, footer, }) {
|
|
18
18
|
const table = (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { "data-testid": testId, className: cn("w-full text-sm", className), children: [_jsx("thead", { className: cn(card === true && "bg-muted"), children: _jsx("tr", { className: "border-b text-left text-muted-foreground", children: columns.map((col) => (_jsx("th", { className: cn("py-1.5 font-medium", card === true && "px-3", col.align === "right" && "text-right"), children: col.header }, col.header))) }) }), _jsxs("tbody", { children: [rows.map((row, i) => (_jsx("tr", { className: "border-b last:border-0", children: columns.map((col) => (_jsx("td", { className: cn("py-1.5", card === true && "px-3", col.align === "right" && "text-right tabular-nums"), children: col.cell(row) }, col.header))) }, rowKey(row, i)))), footer?.map((row, i) => (_jsxs("tr", { className: cn(i === 0 && "border-t", row.emphasize === true && "font-semibold"), children: [_jsxs("td", { colSpan: columns.length > 1 ? Math.max(columns.length - 1, 1) : undefined, className: cn("py-1.5 text-muted-foreground", card === true && "px-3", row.emphasize === true && "text-foreground", columns.length === 1 && "flex items-center justify-between gap-4"), children: [row.label, columns.length === 1 && (_jsx("span", { className: cn("text-right tabular-nums", row.emphasize === true && "text-foreground"), children: row.value }))] }), columns.length > 1 && (_jsx("td", { className: cn("py-1.5 text-right tabular-nums", card === true && "px-3"), children: row.value }))] }, row.label)))] })] }) }));
|
|
19
19
|
return card === true ? (_jsx("div", { className: "overflow-hidden rounded-lg border bg-card", children: table })) : (table);
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
/** Transposed comparison table: row = metric, column = variant, with the best
|
|
22
|
+
* column optionally highlighted for each metric. For scenario/offer comparisons
|
|
23
|
+
* where ResultTable (row = record) is not suitable. */
|
|
24
24
|
export function ComparisonTable({ columns, columnHeader, columnKey, metrics, metricLabel, testId, className, card, }) {
|
|
25
25
|
const table = (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { "data-testid": testId, className: cn("w-full min-w-[24rem] text-sm", className), children: [_jsx("thead", { className: cn(card === true && "bg-muted"), children: _jsxs("tr", { className: "border-b text-left text-muted-foreground", children: [_jsx("th", { className: cn("py-1.5 font-medium", card === true && "px-3"), children: metricLabel }), columns.map((col, i) => (_jsx("th", { className: cn("py-1.5 text-right font-medium", card === true && "px-3"), children: columnHeader(col, i) }, columnKey(col, i))))] }) }), _jsx("tbody", { children: metrics.map((metric) => {
|
|
26
26
|
const best = metric.bestIndex !== undefined ? metric.bestIndex(columns) : -1;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Section with a title and optional action slot (range switcher, filter) —
|
|
3
|
+
* replaces repeated `<section className={CARD}>…<h2>` blocks in
|
|
4
|
+
* custom screens. Thin wrapper around the Card primitive. */
|
|
5
5
|
export declare function SectionCard({ title, subtitle, action, footer, children, testId, }: {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly subtitle?: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { usePrimitives } from "../../index.js";
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
/** Section with a title and optional action slot (range switcher, filter) —
|
|
4
|
+
* replaces repeated `<section className={CARD}>…<h2>` blocks in
|
|
5
|
+
* custom screens. Thin wrapper around the Card primitive. */
|
|
6
6
|
export function SectionCard({ title, subtitle, action, footer, children, testId, }) {
|
|
7
7
|
const { Card } = usePrimitives();
|
|
8
8
|
return (_jsx(Card, { slots: { title, subtitle, headerActions: action, footer }, testId: testId, children: _jsx("div", { className: "flex h-full flex-col gap-4", children: children }) }));
|
|
@@ -10,8 +10,8 @@ export declare function Sparkline({ points, className, }: {
|
|
|
10
10
|
readonly points: readonly number[];
|
|
11
11
|
readonly className?: string;
|
|
12
12
|
}): ReactNode;
|
|
13
|
-
/** Icon-
|
|
14
|
-
*
|
|
13
|
+
/** Icon-bearing metric tile with an optional delta chip, trend line,
|
|
14
|
+
* and sparkline. `icon` is a ready-made node (the app supplies its SVG). */
|
|
15
15
|
export declare function StatCard({ icon, label, value, sub, tone, accentColor, delta, trend, spark, testId, children, }: {
|
|
16
16
|
readonly icon?: ReactNode;
|
|
17
17
|
readonly label: string;
|
|
@@ -41,8 +41,8 @@ export declare function StatStripCell({ icon, label, value, sub, tone, accentCol
|
|
|
41
41
|
readonly spark?: readonly number[];
|
|
42
42
|
readonly testId?: string;
|
|
43
43
|
}): ReactNode;
|
|
44
|
-
/**
|
|
45
|
-
* StatCard
|
|
44
|
+
/** Compact metric tile (without an icon) — for dense KPI grids.
|
|
45
|
+
* StatCard is the icon-bearing variant. */
|
|
46
46
|
export declare function MiniStat({ label, value, tone, emphasize, testId, labelTestId, valueTestId, }: {
|
|
47
47
|
readonly label: string;
|
|
48
48
|
readonly value: string;
|
package/dist/web/widgets/stat.js
CHANGED
|
@@ -15,8 +15,8 @@ const TONE_VALUE = {
|
|
|
15
15
|
warn: "text-destructive",
|
|
16
16
|
negative: "text-status-bad",
|
|
17
17
|
};
|
|
18
|
-
// Inline
|
|
19
|
-
//
|
|
18
|
+
// Inline SVG sparkline (no chart dependency for a 28px curve): line +
|
|
19
|
+
// subtle area gradient, colored via currentColor (caller sets text-tone).
|
|
20
20
|
export function Sparkline({ points, className, }) {
|
|
21
21
|
if (points.length < 2)
|
|
22
22
|
return null;
|
|
@@ -42,8 +42,8 @@ function StatIconChip({ icon, tone, accentColor, }) {
|
|
|
42
42
|
backgroundColor: `color-mix(in srgb, ${accentColor} 12%, transparent)`,
|
|
43
43
|
}, children: icon }));
|
|
44
44
|
}
|
|
45
|
-
/** Icon-
|
|
46
|
-
*
|
|
45
|
+
/** Icon-bearing metric tile with an optional delta chip, trend line,
|
|
46
|
+
* and sparkline. `icon` is a ready-made node (the app supplies its SVG). */
|
|
47
47
|
export function StatCard({ icon, label, value, sub, tone = "default", accentColor, delta, trend, spark, testId, children, }) {
|
|
48
48
|
const { Card } = usePrimitives();
|
|
49
49
|
// StatCard is a reusable small-tile widget (a metrics dashboard grid, not
|
|
@@ -57,8 +57,8 @@ export function StatCard({ icon, label, value, sub, tone = "default", accentColo
|
|
|
57
57
|
export function StatStripCell({ icon, label, value, sub, tone = "default", accentColor, delta, spark, testId, }) {
|
|
58
58
|
return (_jsxs("div", { "data-testid": testId, className: "flex min-w-0 flex-col gap-1 px-4 py-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2 text-xs font-medium text-muted-foreground", children: [icon !== undefined && _jsx(StatIconChip, { icon: icon, tone: tone, accentColor: accentColor }), _jsx("span", { children: label })] }), delta !== undefined && (_jsxs("span", { className: cn("inline-flex shrink-0 items-center gap-0.5 whitespace-nowrap rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums", TONE_CHIP[delta.tone ?? tone]), children: [delta.direction === "up" ? "↑" : "↓", delta.value] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: cn("text-xl font-semibold tabular-nums", TONE_VALUE[tone]), children: value }), spark !== undefined && (_jsx(Sparkline, { points: spark, className: cn("h-7 w-20 shrink-0", TONE_VALUE[tone]) }))] }), sub !== undefined && _jsx("div", { className: "text-xs text-muted-foreground", children: sub })] }));
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
* StatCard
|
|
60
|
+
/** Compact metric tile (without an icon) — for dense KPI grids.
|
|
61
|
+
* StatCard is the icon-bearing variant. */
|
|
62
62
|
export function MiniStat({ label, value, tone = "default", emphasize = false, testId, labelTestId, valueTestId, }) {
|
|
63
63
|
const { Card } = usePrimitives();
|
|
64
64
|
return (_jsxs(Card, { options: { padded: false }, className: cn("p-3", emphasize && "ring-1 ring-primary/30"), testId: testId, children: [_jsx("div", { className: "text-xs text-muted-foreground", "data-testid": labelTestId, children: label }), _jsx("div", { className: cn("mt-0.5 font-semibold tabular-nums", TONE_VALUE[tone], emphasize ? "text-lg" : "text-sm"), "data-testid": valueTestId, children: value })] }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { DispatcherError } from "../../headless/index.ts";
|
|
3
|
-
/**
|
|
4
|
-
* entityList
|
|
3
|
+
/** Empty state with an optional icon and CTA, using the dashed-box style of
|
|
4
|
+
* entityList empty states as a standalone widget for custom screens. */
|
|
5
5
|
export declare function EmptyState({ icon, title, description, action, testId, }: {
|
|
6
6
|
readonly icon?: ReactNode;
|
|
7
7
|
readonly title: string;
|
|
@@ -9,13 +9,13 @@ export declare function EmptyState({ icon, title, description, action, testId, }
|
|
|
9
9
|
readonly action?: ReactNode;
|
|
10
10
|
readonly testId?: string;
|
|
11
11
|
}): ReactNode;
|
|
12
|
-
/**
|
|
12
|
+
/** Loading state with skeleton rows styled like cards; `rows` controls the height. */
|
|
13
13
|
export declare function LoadingState({ rows, className, testId, }: {
|
|
14
14
|
readonly rows?: number;
|
|
15
15
|
readonly className?: string;
|
|
16
16
|
readonly testId?: string;
|
|
17
17
|
}): ReactNode;
|
|
18
|
-
/**
|
|
18
|
+
/** Error state based on the Banner primitive, with a retry button. */
|
|
19
19
|
export declare function ErrorState({ error, onRetry, testId, }: {
|
|
20
20
|
readonly error: DispatcherError;
|
|
21
21
|
readonly onRetry?: () => void;
|
|
@@ -2,17 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { dispatcherErrorText, usePrimitives, useTranslation } from "../../index.js";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { Skeleton } from "../ui/skeleton.js";
|
|
5
|
-
/**
|
|
6
|
-
* entityList
|
|
5
|
+
/** Empty state with an optional icon and CTA, using the dashed-box style of
|
|
6
|
+
* entityList empty states as a standalone widget for custom screens. */
|
|
7
7
|
export function EmptyState({ icon, title, description, action, testId, }) {
|
|
8
8
|
return (_jsxs("div", { "data-testid": testId, className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-6 py-10 text-center", children: [icon !== undefined && _jsx("div", { className: "text-muted-foreground", children: icon }), _jsx("div", { className: "text-sm font-medium", children: title }), description !== undefined && (_jsx("div", { className: "text-sm text-muted-foreground", children: description })), action !== undefined && _jsx("div", { className: "mt-2", children: action })] }));
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/** Loading state with skeleton rows styled like cards; `rows` controls the height. */
|
|
11
11
|
export function LoadingState({ rows = 3, className, testId, }) {
|
|
12
12
|
const t = useTranslation();
|
|
13
13
|
return (_jsx("output", { "data-testid": testId, "aria-label": t("kumiko.widget.loading"), className: cn("flex w-full flex-col gap-2", className), children: Array.from({ length: rows }, (_, i) => (_jsx(Skeleton, { className: "h-9 w-full" }, i))) }));
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** Error state based on the Banner primitive, with a retry button. */
|
|
16
16
|
export function ErrorState({ error, onRetry, testId, }) {
|
|
17
17
|
const { Banner, Button } = usePrimitives();
|
|
18
18
|
const t = useTranslation();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `idPrefix`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
1
|
+
/** Form state for calculator screens: a draft object plus `patch` for
|
|
2
|
+
* partial updates, and `field(name)` that provides `{ id, name, value, onChange }`
|
|
3
|
+
* props ready for field widgets (NumberField/MoneyField/PercentField).
|
|
4
|
+
* Replaces the repeated draft interface and patch helper in each screen.
|
|
5
|
+
* `idPrefix` prevents duplicate DOM `id`s when multiple `useDraft` forms with
|
|
6
|
+
* same-named fields are mounted on the same page; `name` remains the plain key
|
|
7
|
+
* for the form payload. */
|
|
8
8
|
export declare function useDraft<T extends object>(defaults: T, options?: {
|
|
9
9
|
readonly idPrefix?: string;
|
|
10
10
|
}): {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useCallback, useState } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `idPrefix`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
/** Form state for calculator screens: a draft object plus `patch` for
|
|
3
|
+
* partial updates, and `field(name)` that provides `{ id, name, value, onChange }`
|
|
4
|
+
* props ready for field widgets (NumberField/MoneyField/PercentField).
|
|
5
|
+
* Replaces the repeated draft interface and patch helper in each screen.
|
|
6
|
+
* `idPrefix` prevents duplicate DOM `id`s when multiple `useDraft` forms with
|
|
7
|
+
* same-named fields are mounted on the same page; `name` remains the plain key
|
|
8
|
+
* for the form payload. */
|
|
9
9
|
export function useDraft(defaults, options) {
|
|
10
10
|
const idPrefix = options?.idPrefix;
|
|
11
11
|
const [draft, setDraft] = useState(defaults);
|