@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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 +12 -4
- package/dist/app/action-form-shim.d.ts +8 -8
- package/dist/app/action-form-shim.js +20 -22
- package/dist/app/action-form-support.d.ts +1 -0
- package/dist/app/action-form-support.js +1 -1
- 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 +7 -3
- 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.d.ts +11 -0
- package/dist/app/entity-edit-prefill.js +18 -13
- 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 +29 -28
- package/dist/app/extension-sections.js +10 -10
- package/dist/app/feature-schema.js +11 -11
- package/dist/app/form-schema.js +2 -2
- package/dist/app/header-actions-bar.js +2 -2
- 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/edit-mounts.d.ts +2 -2
- package/dist/components/embedded-list-field.js +5 -5
- 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 +15 -2
- package/dist/components/render-edit-action-button.d.ts +1 -3
- package/dist/components/render-edit-action-button.js +2 -2
- 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 +74 -73
- package/dist/components/render-field.d.ts +7 -7
- package/dist/components/render-field.js +62 -61
- package/dist/components/render-list.d.ts +42 -43
- package/dist/components/render-list.js +93 -84
- 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/flow-diagram/render.js +9 -1
- package/dist/headless/apex/index.d.ts +1 -2
- package/dist/headless/apex/index.js +5 -6
- package/dist/headless/form/form-controller.js +14 -6
- package/dist/headless/form/types.d.ts +1 -0
- package/dist/headless/form/zod-bridge.js +2 -2
- package/dist/headless/format/index.js +89 -64
- package/dist/headless/format/money.js +1 -1
- package/dist/headless/index.js +2 -2
- package/dist/headless/view-model/edit.js +4 -2
- package/dist/headless/view-model/list.js +184 -150
- package/dist/headless/view-model/types.d.ts +6 -3
- package/dist/hooks/live-refetch-scheduler.js +4 -0
- 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.d.ts +9 -1
- package/dist/hooks/use-reference-lookup.js +32 -19
- package/dist/hooks/use-store.js +18 -22
- package/dist/hooks/use-stream-handler.js +4 -2
- package/dist/i18n-defaults.js +6 -8
- package/dist/i18n.d.ts +10 -10
- package/dist/i18n.js +25 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -4
- package/dist/live/dispatcher-live.js +2 -2
- package/dist/primitives.d.ts +244 -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 +29 -25
- package/dist/web/app/dashboard-body.js +17 -1
- package/dist/web/app/dashboard-panels.js +12 -2
- 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/index.d.ts +1 -1
- package/dist/web/index.js +1 -1
- 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/shell-header.js +2 -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/uniform-bar-controls.d.ts +1 -0
- package/dist/web/layout/uniform-bar-controls.js +4 -0
- 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/lib/file-ref-download.js +4 -1
- 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/code-input.d.ts +6 -0
- package/dist/web/primitives/code-input.js +31 -0
- 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 +6 -6
- 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 +2 -2
- package/dist/web/primitives/field.js +9 -8
- package/dist/web/primitives/file-thumbnail.d.ts +3 -0
- package/dist/web/primitives/file-thumbnail.js +5 -0
- package/dist/web/primitives/form.d.ts +1 -1
- package/dist/web/primitives/form.js +6 -6
- package/dist/web/primitives/input.js +8 -3
- package/dist/web/primitives/layout-elements.js +7 -5
- 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/page-header.js +3 -1
- package/dist/web/primitives/pager.js +1 -1
- package/dist/web/primitives/row-actions.js +6 -7
- 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/primitives/use-focus-on-mount.d.ts +2 -0
- package/dist/web/primitives/use-focus-on-mount.js +9 -0
- 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/bounded-load-cache.d.ts +1 -1
- package/dist/web/widgets/bounded-load-cache.js +10 -4
- package/dist/web/widgets/charts.d.ts +10 -10
- package/dist/web/widgets/charts.js +8 -8
- package/dist/web/widgets/choice-cards.d.ts +17 -0
- package/dist/web/widgets/choice-cards.js +12 -0
- package/dist/web/widgets/collapsible-section.d.ts +3 -3
- package/dist/web/widgets/collapsible-section.js +7 -7
- package/dist/web/widgets/detail-list.d.ts +5 -3
- package/dist/web/widgets/detail-list.js +5 -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.d.ts +1 -0
- package/dist/web/widgets/index.js +5 -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/pdf-preview.js +1 -1
- package/dist/web/widgets/plan-card.d.ts +2 -0
- package/dist/web/widgets/plan-card.js +1 -1
- 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/upload-zone.js +13 -5
- package/dist/web/widgets/use-draft.d.ts +7 -7
- package/dist/web/widgets/use-draft.js +7 -7
- package/package.json +6 -6
- package/src/changes.json +49 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Evicts the oldest entry once `max` is exceeded and disposes only that one,
|
|
2
|
-
// so several documents on one page keep their own loading tasks alive.
|
|
3
|
-
|
|
2
|
+
// so several documents on one page keep their own loading tasks alive. A
|
|
3
|
+
// resolved value that `shouldKeep` rejects (a handled failure) is dropped so
|
|
4
|
+
// the next mount retries.
|
|
5
|
+
export function createBoundedLoadCache(max, dispose, shouldKeep = () => true) {
|
|
4
6
|
const entries = new Map();
|
|
5
7
|
return (key, load) => {
|
|
6
8
|
const cached = entries.get(key);
|
|
@@ -9,10 +11,14 @@ export function createBoundedLoadCache(max, dispose) {
|
|
|
9
11
|
const loading = load();
|
|
10
12
|
entries.set(key, loading);
|
|
11
13
|
// A failed load must not stick: the next mount retries instead of replaying the rejection.
|
|
12
|
-
|
|
14
|
+
const forget = () => {
|
|
13
15
|
if (entries.get(key) === loading)
|
|
14
16
|
entries.delete(key);
|
|
15
|
-
}
|
|
17
|
+
};
|
|
18
|
+
loading.then((value) => {
|
|
19
|
+
if (!shouldKeep(value))
|
|
20
|
+
forget();
|
|
21
|
+
}, forget);
|
|
16
22
|
if (entries.size > max) {
|
|
17
23
|
const [oldestKey, oldest] = entries.entries().next().value ?? [];
|
|
18
24
|
if (oldestKey !== undefined && oldest !== undefined) {
|
|
@@ -4,10 +4,10 @@ import { type StatusTone } from "./status-badge.tsx";
|
|
|
4
4
|
export type StatusBarEntry = {
|
|
5
5
|
/** Stable key (z.B. ISO-Datum). */
|
|
6
6
|
readonly key: string;
|
|
7
|
-
/**
|
|
7
|
+
/** Bar height from 0 to 1 (e.g. operational=1, degraded=0.75, outage=0.25). */
|
|
8
8
|
readonly level: number;
|
|
9
9
|
readonly tone: StatusTone;
|
|
10
|
-
/** Tooltip
|
|
10
|
+
/** Tooltip text (<title>), supplied translated by the caller. */
|
|
11
11
|
readonly label?: string;
|
|
12
12
|
};
|
|
13
13
|
/** Status bar strip (e.g. 90-day uptime): variable-height bars with
|
|
@@ -17,7 +17,7 @@ export type StatusBarEntry = {
|
|
|
17
17
|
export declare function StatusBarChart({ entries, ariaLabel, startLabel, endLabel, highlightLast, dense, testId, }: {
|
|
18
18
|
readonly entries: readonly StatusBarEntry[];
|
|
19
19
|
readonly ariaLabel: string;
|
|
20
|
-
/**
|
|
20
|
+
/** Left and right axis labels below the chart, supplied translated. */
|
|
21
21
|
readonly startLabel?: string;
|
|
22
22
|
readonly endLabel?: string;
|
|
23
23
|
readonly highlightLast?: boolean;
|
|
@@ -25,15 +25,15 @@ export declare function StatusBarChart({ entries, ariaLabel, startLabel, endLabe
|
|
|
25
25
|
readonly dense?: boolean;
|
|
26
26
|
readonly testId?: string;
|
|
27
27
|
}): ReactNode;
|
|
28
|
-
/** Quadratic
|
|
29
|
-
*
|
|
28
|
+
/** Quadratic midpoint technique: smooths the zigzag line without overshoot
|
|
29
|
+
* (~5 lines instead of a Catmull-Rom/Bezier fit). */
|
|
30
30
|
export declare function smoothPath(pts: ReadonlyArray<{
|
|
31
31
|
readonly x: number;
|
|
32
32
|
readonly y: number;
|
|
33
33
|
}>): string;
|
|
34
34
|
export type TimeseriesPoint = {
|
|
35
35
|
readonly atMs: number;
|
|
36
|
-
/** null =
|
|
36
|
+
/** null = outage/no value → falls to the baseline for a visible drop. */
|
|
37
37
|
readonly value: number | null;
|
|
38
38
|
};
|
|
39
39
|
export type TimeseriesYAxis = {
|
|
@@ -65,16 +65,16 @@ export type TimeseriesReferenceLine = {
|
|
|
65
65
|
readonly label: string;
|
|
66
66
|
readonly tone?: StatusTone;
|
|
67
67
|
};
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
/** Time-series line chart (smoothed line with area fill). The x-axis uses
|
|
69
|
+
* TIME within windowStartMs..windowEndMs rather than the index, so 5-minute
|
|
70
|
+
* data in a 30-day window correctly appears as a narrow strip on the right. */
|
|
71
71
|
export declare function TimeseriesChart({ points, windowStartMs, windowEndMs, tone, ariaLabel, axisLabels, xAxis, yAxis, height, markers, formatMarkerTime, referenceLines, emptyContent, testId, }: {
|
|
72
72
|
readonly points: readonly TimeseriesPoint[];
|
|
73
73
|
readonly windowStartMs: number;
|
|
74
74
|
readonly windowEndMs: number;
|
|
75
75
|
readonly tone?: StatusTone;
|
|
76
76
|
readonly ariaLabel: string;
|
|
77
|
-
/**
|
|
77
|
+
/** Axis row below the chart, formatted and translated by the caller. */
|
|
78
78
|
readonly axisLabels?: {
|
|
79
79
|
readonly start: string;
|
|
80
80
|
readonly mid?: string;
|
|
@@ -5,9 +5,9 @@ import { NARROW_VIEWPORT_BREAKPOINT_PX, useIsViewportBelow, } from "../primitive
|
|
|
5
5
|
import { bucketIndexAtFraction, initialWindowSelection, nearestIndex, pointsWithin, resolveStackedAreaWindow, snapBrushWindow, snapMovedBrushWindow, stackedAreaAxisTicks, stackedAreaDateFormatFor, } from "./chart-window.js";
|
|
6
6
|
import { ModeSwitch } from "./mode-switch.js";
|
|
7
7
|
import { STATUS_TONE_TEXT } from "./status-badge.js";
|
|
8
|
-
// Inline
|
|
9
|
-
// --color-status-* / --color-foreground
|
|
10
|
-
//
|
|
8
|
+
// Inline SVG charts without a chart dependency. Colors come exclusively from the
|
|
9
|
+
// --color-status-* / --color-foreground theme tokens; axis labels are supplied
|
|
10
|
+
// translated by the caller, so the widget makes no locale assumptions.
|
|
11
11
|
const HOUR_MS = 3_600_000;
|
|
12
12
|
const DAY_MS = 24 * HOUR_MS;
|
|
13
13
|
const TONE_VAR = {
|
|
@@ -47,8 +47,8 @@ const SINGLE_POINT_RADIUS = 3;
|
|
|
47
47
|
function clampDotY(y, height) {
|
|
48
48
|
return Math.max(SINGLE_POINT_RADIUS, Math.min(height - SINGLE_POINT_RADIUS, y));
|
|
49
49
|
}
|
|
50
|
-
/** Quadratic
|
|
51
|
-
*
|
|
50
|
+
/** Quadratic midpoint technique: smooths the zigzag line without overshoot
|
|
51
|
+
* (~5 lines instead of a Catmull-Rom/Bezier fit). */
|
|
52
52
|
export function smoothPath(pts) {
|
|
53
53
|
const first = pts[0];
|
|
54
54
|
if (!first)
|
|
@@ -85,9 +85,9 @@ function yTickPosition(index, count) {
|
|
|
85
85
|
return "";
|
|
86
86
|
return "-translate-y-1/2";
|
|
87
87
|
}
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
88
|
+
/** Time-series line chart (smoothed line with area fill). The x-axis uses
|
|
89
|
+
* TIME within windowStartMs..windowEndMs rather than the index, so 5-minute
|
|
90
|
+
* data in a 30-day window correctly appears as a narrow strip on the right. */
|
|
91
91
|
export function TimeseriesChart({ points, windowStartMs, windowEndMs, tone = "ok", ariaLabel, axisLabels, xAxis, yAxis, height, markers, formatMarkerTime, referenceLines, emptyContent, testId, }) {
|
|
92
92
|
const gradientId = useId();
|
|
93
93
|
const descId = useId();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** Card-styled radio group for mutually exclusive choices that need a description or price line. */
|
|
3
|
+
export declare function ChoiceCards<T extends string>({ value, options, onChange, ariaLabel, testId, }: {
|
|
4
|
+
/** `null`: nothing selected. */
|
|
5
|
+
readonly value: T | null;
|
|
6
|
+
readonly options: readonly {
|
|
7
|
+
readonly value: T;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly description?: string;
|
|
10
|
+
/** Right-aligned next to the title, e.g. a price. */
|
|
11
|
+
readonly aside?: string;
|
|
12
|
+
readonly disabled?: boolean;
|
|
13
|
+
}[];
|
|
14
|
+
readonly onChange: (value: T) => void;
|
|
15
|
+
readonly ariaLabel?: string;
|
|
16
|
+
readonly testId?: string;
|
|
17
|
+
}): ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check } from "lucide-react";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
/** Card-styled radio group for mutually exclusive choices that need a description or price line. */
|
|
6
|
+
export function ChoiceCards({ value, options, onChange, ariaLabel, testId, }) {
|
|
7
|
+
const groupName = useId();
|
|
8
|
+
return (_jsx("div", { role: "radiogroup", "aria-label": ariaLabel, "data-testid": testId, className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: options.map((option) => {
|
|
9
|
+
const selected = option.value === value;
|
|
10
|
+
return (_jsxs("label", { "data-testid": testId !== undefined ? `${testId}-${option.value}` : undefined, className: cn("relative flex cursor-pointer flex-col gap-1 rounded-lg border bg-card p-4 transition-colors", "hover:bg-muted/50 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring", selected ? "border-primary ring-1 ring-primary" : "border-border", option.disabled && "cursor-not-allowed opacity-50"), children: [_jsx("input", { type: "radio", className: "sr-only", name: groupName, value: option.value, checked: selected, disabled: option.disabled, onChange: () => onChange(option.value) }), _jsxs("span", { className: "flex items-start justify-between gap-2", children: [_jsx("span", { className: "font-semibold", children: option.title }), _jsxs("span", { className: "flex items-center gap-2", children: [option.aside !== undefined && (_jsx("span", { className: "font-semibold tabular-nums", children: option.aside })), _jsx("span", { "aria-hidden": "true", className: cn("flex size-5 shrink-0 items-center justify-center rounded-full border", selected ? "border-primary bg-primary text-primary-foreground" : "border-input"), children: selected && _jsx(Check, { className: "size-3" }) })] })] }), option.description !== undefined && (_jsx("span", { className: "text-sm text-muted-foreground", children: option.description }))] }, option.value));
|
|
11
|
+
}) }));
|
|
12
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Collapsible section using native <details> with React-controlled state; otherwise
|
|
3
|
+
* the open attribute resets to its initial value on every parent re-render.
|
|
4
|
+
* Provides consistent styling for all “Erweitert”/“Mehr” sections. */
|
|
5
5
|
export declare function CollapsibleSection({ title, defaultOpen, children, testId, }: {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly defaultOpen?: boolean;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronRight } from "lucide-react";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
/** Collapsible section using native <details> with React-controlled state; otherwise
|
|
5
|
+
* the open attribute resets to its initial value on every parent re-render.
|
|
6
|
+
* Provides consistent styling for all “Erweitert”/“Mehr” sections. */
|
|
7
7
|
export function CollapsibleSection({ title, defaultOpen = false, children, testId, }) {
|
|
8
8
|
const [open, setOpen] = useState(defaultOpen);
|
|
9
|
-
// useState(defaultOpen)
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// useState(defaultOpen) reads only the mount-time value; an asynchronously hydrated
|
|
10
|
+
// draft flips `defaultOpen` too late for the lazy initializer. An open-only
|
|
11
|
+
// effect never overrides the user's manual collapse.
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
if (defaultOpen)
|
|
14
14
|
setOpen(true);
|
|
15
15
|
}, [defaultOpen]);
|
|
16
|
-
return (_jsxs("details", { "data-testid": testId, className: "group rounded-lg border bg-card", open: open, onToggle: (e) => setOpen(e.currentTarget.open), children: [_jsxs("summary", { className: "flex cursor-pointer select-none items-center justify-between gap-2 rounded-lg px-4
|
|
16
|
+
return (_jsxs("details", { "data-testid": testId, className: "group rounded-lg border bg-card", open: open, onToggle: (e) => setOpen(e.currentTarget.open), children: [_jsxs("summary", { className: "flex h-9 cursor-pointer select-none items-center justify-between gap-2 rounded-lg px-4 text-sm font-medium hover:bg-muted/50 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: title }), _jsx(ChevronRight, { "aria-hidden": "true", className: "size-4 text-muted-foreground transition-transform group-open:rotate-90" })] }), _jsx("div", { className: "border-t px-4 py-3", children: children })] }));
|
|
17
17
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/**
|
|
3
|
+
* Read-only key-value list for detail views (dimmed label on the left, value on the right). The
|
|
4
|
+
* value is a ReactNode, so badges and chips are supported. `emphasize` highlights a row, such as
|
|
5
|
+
* the final result in a calculator.
|
|
6
|
+
*/
|
|
5
7
|
export declare function DetailList({ rows, testId, }: {
|
|
6
8
|
readonly rows: readonly {
|
|
7
9
|
/** Optional stable key -- falls back to the label when omitted (fine as
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
/**
|
|
4
|
+
* Read-only key-value list for detail views (dimmed label on the left, value on the right). The
|
|
5
|
+
* value is a ReactNode, so badges and chips are supported. `emphasize` highlights a row, such as
|
|
6
|
+
* the final result in a calculator.
|
|
7
|
+
*/
|
|
6
8
|
export function DetailList({ rows, testId, }) {
|
|
7
9
|
return (_jsx("dl", { "data-testid": testId, className: "@container flex flex-col divide-y", children: rows.map((row) => (_jsxs("div", { className: "grid grid-cols-1 gap-0.5 py-2.5 @md:grid-cols-[200px_1fr] @md:gap-4", children: [_jsx("dt", { className: cn("text-sm text-muted-foreground", row.emphasize === true && "font-semibold text-foreground"), children: row.label }), _jsx("dd", { className: cn("text-sm font-medium", row.emphasize === true && "font-semibold"), children: row.value })] }, row.id ?? row.label))) }));
|
|
8
10
|
}
|
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
export { AiTextArea, type AiTextAreaProps, AiTextField, type AiTextFieldProps, } from "./ai-text-field.tsx";
|
|
2
2
|
export type { StackedAreaDateFormat, StackedAreaInitialWindow, StackedAreaRangeOption, StackedAreaRanges, StackedAreaWindowSelection, } from "./chart-window.ts";
|
|
3
3
|
export { type ChartMarker, type ChartSeries, type ChartTone, SegmentBarChart, type SegmentBarRow, StackedAreaChart, StackedBarChart, StatusBarChart, type StatusBarEntry, smoothPath, TimeseriesChart, type TimeseriesHeight, type TimeseriesPoint, type TimeseriesReferenceLine, type TimeseriesXAxis, type TimeseriesYAxis, } from "./charts.tsx";
|
|
4
|
+
export { ChoiceCards } from "./choice-cards.tsx";
|
|
4
5
|
export { CollapsibleSection } from "./collapsible-section.tsx";
|
|
5
6
|
export { type DashboardListColumn, DashboardListTable } from "./dashboard-list.tsx";
|
|
6
7
|
export { DetailList } from "./detail-list.tsx";
|
|
@@ -1,9 +1,10 @@
|
|
|
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
|
+
export { ChoiceCards } from "./choice-cards.js";
|
|
7
8
|
export { CollapsibleSection } from "./collapsible-section.js";
|
|
8
9
|
export { DashboardListTable } from "./dashboard-list.js";
|
|
9
10
|
export { DetailList } from "./detail-list.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,7 +10,7 @@ const MAX_CACHED_DOCUMENTS = 4;
|
|
|
10
10
|
const cachedDocument = createBoundedLoadCache(MAX_CACHED_DOCUMENTS, (loaded) => {
|
|
11
11
|
if (loaded.ok)
|
|
12
12
|
void loaded.loadingTask.destroy();
|
|
13
|
-
});
|
|
13
|
+
}, (loaded) => loaded.ok);
|
|
14
14
|
function decodeBase64(base64) {
|
|
15
15
|
const binary = atob(base64);
|
|
16
16
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -2,6 +2,8 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
export type PlanCardPrice = {
|
|
3
3
|
readonly amount: string;
|
|
4
4
|
readonly period?: string;
|
|
5
|
+
/** Shown before the amount, e.g. "from" when prices differ per provider. */
|
|
6
|
+
readonly prefix?: string;
|
|
5
7
|
};
|
|
6
8
|
export type PlanCardActionSlot = {
|
|
7
9
|
readonly label: string;
|
|
@@ -6,7 +6,7 @@ export function PlanCard({ title, description, price, features, current = false,
|
|
|
6
6
|
const t = useTranslation();
|
|
7
7
|
const compact = variant === "compact";
|
|
8
8
|
const footer = cta !== undefined || ctaLink !== undefined || secondaryAction !== undefined ? (_jsxs("div", { className: "flex w-full flex-col gap-2", children: [ctaLink !== undefined && (_jsx(Link, { href: ctaLink.href, variant: "button", target: ctaLink.target, className: "w-full justify-center", testId: testId !== undefined ? `${testId}-cta` : "plan-card-cta", children: ctaLink.label })), ctaLink === undefined && cta !== undefined && (_jsx(Button, { width: "full", disabled: cta.disabled, onClick: cta.onClick, testId: testId !== undefined ? `${testId}-cta` : "plan-card-cta", children: cta.label })), secondaryAction !== undefined && (_jsx(Button, { variant: "secondary", width: "full", disabled: secondaryAction.disabled, onClick: secondaryAction.onClick, children: secondaryAction.label }))] })) : undefined;
|
|
9
|
-
return (_jsxs(Card, { testId: testId ?? "plan-card", className: cn(current && "border-primary"), slots: { footer }, children: [_jsxs("div", { className: cn("flex items-start justify-between gap-2", compact && "items-center"), children: [_jsx("h2", { className: cn("font-semibold", compact ? "text-base" : "text-lg"), children: title }), current && (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-current` : "plan-card-current", className: "inline-block rounded-xl bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary", children: t("kumiko.planCard.current") }))] }), description !== undefined && !compact && (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })), price !== undefined && (_jsx("div", { className: cn(compact ? "mt-1" : "mt-1 mb-3"), children: price === null ? (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-price-unavailable` : "plan-card-price-unavailable", className: "text-sm text-muted-foreground", children: t("kumiko.planCard.priceUnavailable") })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-2xl font-bold", children: price.amount }), price.period !== undefined && (_jsx("span", { className: "ml-1 text-sm text-muted-foreground", children: price.period }))] })) })), !compact && features !== undefined && features.length > 0 && (_jsx("ul", { className: "space-y-1 text-sm", children: features.map((feature, index) => (_jsxs("li", { className: "flex gap-2", children: [_jsx("span", { "aria-hidden": "true", children: "\u2713" }), _jsx("span", { children: feature })] }, index))) }))] }));
|
|
9
|
+
return (_jsxs(Card, { testId: testId ?? "plan-card", className: cn(current && "border-primary"), slots: { footer }, children: [_jsxs("div", { className: cn("flex items-start justify-between gap-2", compact && "items-center"), children: [_jsx("h2", { className: cn("font-semibold", compact ? "text-base" : "text-lg"), children: title }), current && (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-current` : "plan-card-current", className: "inline-block rounded-xl bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary", children: t("kumiko.planCard.current") }))] }), description !== undefined && !compact && (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })), price !== undefined && (_jsx("div", { className: cn(compact ? "mt-1" : "mt-1 mb-3"), children: price === null ? (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-price-unavailable` : "plan-card-price-unavailable", className: "text-sm text-muted-foreground", children: t("kumiko.planCard.priceUnavailable") })) : (_jsxs(_Fragment, { children: [price.prefix !== undefined && (_jsx("span", { className: "mr-1 text-sm text-muted-foreground", children: price.prefix })), _jsx("span", { className: "text-2xl font-bold", children: price.amount }), price.period !== undefined && (_jsx("span", { className: "ml-1 text-sm text-muted-foreground", children: price.period }))] })) })), !compact && features !== undefined && features.length > 0 && (_jsx("ul", { className: "space-y-1 text-sm", children: features.map((feature, index) => (_jsxs("li", { className: "flex gap-2", children: [_jsx("span", { "aria-hidden": "true", children: "\u2713" }), _jsx("span", { children: feature })] }, index))) }))] }));
|
|
10
10
|
}
|
|
11
11
|
export function PlanGrid({ children, testId }) {
|
|
12
12
|
return (_jsx("div", { "data-testid": testId, className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: children }));
|
|
@@ -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;
|