@cosmicdrift/kumiko-renderer 0.327.0 → 0.328.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/action-form-shim.d.ts +14 -0
- package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -44
- package/dist/app/app-features-context.d.ts +8 -0
- package/dist/app/app-features-context.js +16 -0
- package/dist/app/column-renderers.d.ts +30 -0
- package/dist/app/column-renderers.js +34 -0
- package/dist/app/config-edit-shim.d.ts +11 -0
- package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
- package/dist/app/content-editors.d.ts +38 -0
- package/dist/app/content-editors.js +26 -0
- package/dist/app/content-preview.d.ts +11 -0
- package/dist/app/content-preview.js +30 -0
- package/dist/app/custom-screens.d.ts +11 -0
- package/dist/app/custom-screens.js +20 -0
- package/dist/app/dashboard-body.d.ts +16 -0
- package/dist/app/dashboard-body.js +11 -0
- package/dist/app/extension-form-submit.d.ts +32 -0
- package/dist/app/extension-form-submit.js +73 -0
- package/dist/app/extension-sections.d.ts +72 -0
- package/dist/app/extension-sections.js +34 -0
- package/dist/app/feature-schema.d.ts +4 -0
- package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
- package/dist/app/form-schema.d.ts +4 -0
- package/dist/app/form-schema.js +85 -0
- package/dist/app/kumiko-screen.d.ts +43 -0
- package/dist/app/kumiko-screen.js +2206 -0
- package/dist/app/layout-fields.d.ts +3 -0
- package/dist/app/layout-fields.js +20 -0
- package/dist/app/list-facets.d.ts +48 -0
- package/dist/app/list-facets.js +185 -0
- package/dist/app/nav.d.ts +61 -0
- package/dist/app/nav.js +139 -0
- package/dist/app/projection-detail-shim.d.ts +8 -0
- package/dist/app/projection-detail-shim.js +71 -0
- package/dist/app/projection-list-shim.d.ts +13 -0
- package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -38
- package/dist/app/qn.d.ts +7 -0
- package/{src/app/qn.ts → dist/app/qn.js} +9 -11
- package/dist/app/qualify-screen-id.d.ts +2 -0
- package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
- package/dist/app/reference-facet-bridge.d.ts +10 -0
- package/dist/app/reference-facet-bridge.js +22 -0
- package/dist/app/return-to.d.ts +31 -0
- package/dist/app/return-to.js +188 -0
- package/dist/app/row-actions.d.ts +77 -0
- package/dist/app/row-actions.js +416 -0
- package/dist/app/screen-access.d.ts +8 -0
- package/dist/app/screen-access.js +48 -0
- package/dist/app/secret-mint-body.d.ts +10 -0
- package/dist/app/secret-mint-body.js +140 -0
- package/dist/app/secrets-edit-body.d.ts +8 -0
- package/dist/app/secrets-edit-body.js +76 -0
- package/dist/app/use-embedded-screen.d.ts +6 -0
- package/dist/app/use-embedded-screen.js +19 -0
- package/dist/app/variable-chips.d.ts +7 -0
- package/dist/app/variable-chips.js +16 -0
- package/dist/app/write-failed-error.d.ts +6 -0
- package/dist/app/write-failed-error.js +21 -0
- package/dist/components/embedded-list-field.d.ts +10 -0
- package/dist/components/embedded-list-field.js +259 -0
- package/dist/components/grid-cell-for-field.d.ts +25 -0
- package/dist/components/grid-cell-for-field.js +31 -0
- package/dist/components/multi-select-checkboxes.d.ts +14 -0
- package/dist/components/multi-select-checkboxes.js +36 -0
- package/dist/components/reference-create-dialog.d.ts +13 -0
- package/dist/components/reference-create-dialog.js +46 -0
- package/dist/components/related-list-section.d.ts +25 -0
- package/dist/components/related-list-section.js +273 -0
- package/dist/components/render-edit-action-button.d.ts +22 -0
- package/dist/components/render-edit-action-button.js +43 -0
- package/dist/components/render-edit-logic.d.ts +6 -0
- package/dist/components/render-edit-logic.js +63 -0
- package/dist/components/render-edit-types.d.ts +209 -0
- package/dist/components/render-edit-types.js +1 -0
- package/dist/components/render-edit.d.ts +6 -0
- package/dist/components/render-edit.js +1169 -0
- package/dist/components/render-field.d.ts +38 -0
- package/dist/components/render-field.js +544 -0
- package/dist/components/render-list.d.ts +117 -0
- package/dist/components/render-list.js +232 -0
- package/dist/components/write-form-section.d.ts +17 -0
- package/dist/components/write-form-section.js +104 -0
- package/dist/context/dispatcher-context.d.ts +10 -0
- package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
- package/dist/context/draft-storage-context.d.ts +12 -0
- package/dist/context/draft-storage-context.js +18 -0
- package/dist/context/user-roles-context.d.ts +7 -0
- package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
- package/dist/format-when.d.ts +1 -0
- package/{src/format-when.ts → dist/format-when.js} +7 -7
- package/dist/hooks/reference-limits.d.ts +3 -0
- package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
- package/dist/hooks/use-ai-text.d.ts +52 -0
- package/dist/hooks/use-ai-text.js +90 -0
- package/dist/hooks/use-disclosure.d.ts +7 -0
- package/dist/hooks/use-disclosure.js +8 -0
- package/dist/hooks/use-form.d.ts +7 -0
- package/dist/hooks/use-form.js +36 -0
- package/dist/hooks/use-list-url-state.d.ts +43 -0
- package/dist/hooks/use-list-url-state.js +131 -0
- package/dist/hooks/use-mutation.d.ts +9 -0
- package/dist/hooks/use-mutation.js +37 -0
- package/dist/hooks/use-query.d.ts +14 -0
- package/dist/hooks/use-query.js +118 -0
- package/dist/hooks/use-reference-lookup.d.ts +14 -0
- package/dist/hooks/use-reference-lookup.js +63 -0
- package/dist/hooks/use-store.d.ts +3 -0
- package/dist/hooks/use-store.js +38 -0
- package/dist/hooks/use-stream-handler.d.ts +14 -0
- package/dist/hooks/use-stream-handler.js +93 -0
- package/dist/i18n-defaults.d.ts +2 -0
- package/dist/i18n-defaults.js +201 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.js +166 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +55 -0
- package/dist/inside-drawer.d.ts +2 -0
- package/dist/inside-drawer.js +9 -0
- package/dist/page-header-slot.d.ts +2 -0
- package/dist/page-header-slot.js +9 -0
- package/dist/primitives.d.ts +1351 -0
- package/dist/primitives.js +73 -0
- package/dist/screen-fills-height.d.ts +3 -0
- package/dist/screen-fills-height.js +3 -0
- package/dist/share-links.d.ts +4 -0
- package/dist/share-links.js +11 -0
- package/dist/sort-by-accessor.d.ts +5 -0
- package/dist/sort-by-accessor.js +16 -0
- package/dist/sse/live-events.d.ts +23 -0
- package/dist/sse/live-events.js +28 -0
- package/dist/tokens.d.ts +83 -0
- package/dist/tokens.js +80 -0
- package/package.json +16 -9
- package/src/changes.json +52 -0
- package/src/__tests__/error-i18n-defaults.test.ts +0 -82
- package/src/__tests__/format-when.test.ts +0 -12
- package/src/__tests__/i18n.test.tsx +0 -283
- package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
- package/src/__tests__/qn.test.ts +0 -83
- package/src/__tests__/share-links.test.ts +0 -26
- package/src/__tests__/sort-by-accessor.test.ts +0 -48
- package/src/__tests__/stringify-nav-params.test.ts +0 -33
- package/src/__tests__/use-list-url-state.test.tsx +0 -250
- package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
- package/src/app/__tests__/action-form-shim.test.ts +0 -137
- package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
- package/src/app/__tests__/config-edit-shim.test.ts +0 -57
- package/src/app/__tests__/content-editors.test.tsx +0 -128
- package/src/app/__tests__/content-preview.test.tsx +0 -184
- package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
- package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
- package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
- package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
- package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
- package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
- package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
- package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -111
- package/src/app/__tests__/feature-schema.test.ts +0 -40
- package/src/app/__tests__/form-schema.test.ts +0 -247
- package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
- package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
- package/src/app/__tests__/nav.test.ts +0 -198
- package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
- package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
- package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
- package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
- package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
- package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
- package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
- package/src/app/__tests__/qualified-names.test.ts +0 -37
- package/src/app/__tests__/return-to.test.ts +0 -396
- package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
- package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
- package/src/app/__tests__/screen-access-allows.test.ts +0 -34
- package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
- package/src/app/__tests__/variable-chips.test.tsx +0 -70
- package/src/app/app-features-context.tsx +0 -25
- package/src/app/column-renderers.tsx +0 -68
- package/src/app/content-editors.tsx +0 -85
- package/src/app/content-preview.tsx +0 -67
- package/src/app/custom-screens.tsx +0 -29
- package/src/app/dashboard-body.tsx +0 -33
- package/src/app/extension-form-submit.tsx +0 -123
- package/src/app/extension-sections.tsx +0 -111
- package/src/app/form-schema.ts +0 -91
- package/src/app/kumiko-screen.tsx +0 -3614
- package/src/app/layout-fields.ts +0 -31
- package/src/app/list-facets.ts +0 -259
- package/src/app/nav.tsx +0 -258
- package/src/app/projection-detail-shim.ts +0 -85
- package/src/app/reference-facet-bridge.tsx +0 -58
- package/src/app/return-to.tsx +0 -234
- package/src/app/row-actions.ts +0 -639
- package/src/app/screen-access.ts +0 -63
- package/src/app/secret-mint-body.tsx +0 -259
- package/src/app/secrets-edit-body.tsx +0 -194
- package/src/app/use-embedded-screen.ts +0 -27
- package/src/app/variable-chips.tsx +0 -39
- package/src/app/write-failed-error.ts +0 -27
- package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
- package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
- package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
- package/src/components/__tests__/related-list-section.test.tsx +0 -1669
- package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
- package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
- package/src/components/__tests__/render-edit-logic.test.ts +0 -314
- package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
- package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
- package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
- package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
- package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
- package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
- package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
- package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
- package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
- package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
- package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
- package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
- package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
- package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
- package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
- package/src/components/__tests__/write-form-section.test.tsx +0 -375
- package/src/components/embedded-list-field.tsx +0 -354
- package/src/components/grid-cell-for-field.tsx +0 -60
- package/src/components/multi-select-checkboxes.tsx +0 -93
- package/src/components/reference-create-dialog.tsx +0 -91
- package/src/components/related-list-section.tsx +0 -467
- package/src/components/render-edit-action-button.tsx +0 -106
- package/src/components/render-edit-logic.ts +0 -85
- package/src/components/render-edit-types.ts +0 -216
- package/src/components/render-edit.tsx +0 -1700
- package/src/components/render-field.tsx +0 -942
- package/src/components/render-list.tsx +0 -561
- package/src/components/write-form-section.tsx +0 -199
- package/src/context/draft-storage-context.tsx +0 -39
- package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
- package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
- package/src/hooks/__tests__/use-form.test.tsx +0 -65
- package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
- package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
- package/src/hooks/use-ai-text.ts +0 -172
- package/src/hooks/use-disclosure.ts +0 -20
- package/src/hooks/use-form.ts +0 -78
- package/src/hooks/use-list-url-state.ts +0 -171
- package/src/hooks/use-mutation.ts +0 -61
- package/src/hooks/use-query.ts +0 -132
- package/src/hooks/use-reference-lookup.ts +0 -76
- package/src/hooks/use-store.ts +0 -48
- package/src/hooks/use-stream-handler.ts +0 -134
- package/src/i18n-defaults.ts +0 -220
- package/src/i18n.tsx +0 -286
- package/src/index.ts +0 -272
- package/src/primitives.tsx +0 -1446
- package/src/share-links.ts +0 -12
- package/src/sort-by-accessor.ts +0 -20
- package/src/sse/live-events.tsx +0 -56
- package/src/tokens.tsx +0 -170
package/src/primitives.tsx
DELETED
|
@@ -1,1446 +0,0 @@
|
|
|
1
|
-
// Primitives-Contract. Plattform-neutral — die Types beschreiben die
|
|
2
|
-
// semantische Oberfläche, die konkrete Implementation (HTML, React
|
|
3
|
-
// Native) kommt aus `@cosmicdrift/kumiko-renderer-web` oder `@cosmicdrift/kumiko-renderer-native`.
|
|
4
|
-
//
|
|
5
|
-
// Die Renderer (RenderEdit, RenderList, RenderField, KumikoScreen)
|
|
6
|
-
// konsumieren nur diesen Context. Es gibt KEIN Default-Registry hier
|
|
7
|
-
// — jede App muss eine vollständige Registry stellen; die Plattform-
|
|
8
|
-
// Packages liefern defaultPrimitives mit, die via createKumikoApp
|
|
9
|
-
// durchgereicht werden.
|
|
10
|
-
//
|
|
11
|
-
// Core-Primitives (Kumikos eigene Components konsumieren sie):
|
|
12
|
-
//
|
|
13
|
-
// Button — submit/button, disabled, onClick, variant
|
|
14
|
-
// Banner — role=alert Box für Fehler/Info, optional Actions
|
|
15
|
-
// Field — label + issues um ein Input-Control
|
|
16
|
-
// Input — discriminated union über text/number/boolean/date
|
|
17
|
-
// DataTable — Spalten + Zeilen + onRowClick, Empty-State intern
|
|
18
|
-
// EmbeddedListInput — row array + totals table for createEmbeddedListField
|
|
19
|
-
// Form — submit-Wrapper (Web: <form>, Native: View + onSubmit)
|
|
20
|
-
// Section — titled Gruppe von Feldern (Web: <fieldset>+<legend>)
|
|
21
|
-
// Grid — columns-basiertes Layout innerhalb einer Section
|
|
22
|
-
// Text — semantische Text-Variante (body/small/code/required-mark)
|
|
23
|
-
//
|
|
24
|
-
// App-Primitives (der App-Dev füttert eigene):
|
|
25
|
-
//
|
|
26
|
-
// Das `AppPrimitives`-Interface ist leer — Devs erweitern es via
|
|
27
|
-
// Module-Augmentation mit ihren eigenen Components. Die wandern
|
|
28
|
-
// automatisch in `PrimitivesRegistry` und `usePrimitives()` liefert
|
|
29
|
-
// sie typed aus. Kumikos Components nutzen sie NICHT (und kennen sie
|
|
30
|
-
// nicht), aber der App-Code hat ein einheitliches Primitive-Vokabular
|
|
31
|
-
// über Core + Custom.
|
|
32
|
-
|
|
33
|
-
import type {
|
|
34
|
-
ConfigCascade,
|
|
35
|
-
ConfigScope,
|
|
36
|
-
ConfigValueSource,
|
|
37
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
38
|
-
import type {
|
|
39
|
-
FieldIconKey,
|
|
40
|
-
FormWidth,
|
|
41
|
-
IconKey,
|
|
42
|
-
NavIconKey,
|
|
43
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
44
|
-
import type {
|
|
45
|
-
FieldIssue,
|
|
46
|
-
ListColumnViewModel,
|
|
47
|
-
ListRowViewModel,
|
|
48
|
-
RuntimeRenderer,
|
|
49
|
-
} from "@cosmicdrift/kumiko-headless";
|
|
50
|
-
import {
|
|
51
|
-
type ComponentType,
|
|
52
|
-
createContext,
|
|
53
|
-
type FormEvent,
|
|
54
|
-
type KeyboardEvent,
|
|
55
|
-
type ReactNode,
|
|
56
|
-
type Ref,
|
|
57
|
-
useContext,
|
|
58
|
-
} from "react";
|
|
59
|
-
|
|
60
|
-
export type { RuntimeRenderer };
|
|
61
|
-
|
|
62
|
-
// ---- Prop-Types (die Primitive-Contract-Oberfläche) ----
|
|
63
|
-
|
|
64
|
-
/** Escape hatch for `data-*` test hooks (e.g. an E2E selector) that don't
|
|
65
|
-
* warrant a dedicated typed prop. Web merges these onto the rendered
|
|
66
|
-
* element; native impls ignore it (precedent: `className`). */
|
|
67
|
-
export type DataAttributes = Readonly<Record<`data-${string}`, string>>;
|
|
68
|
-
|
|
69
|
-
/** Structural marker a top-level `actions` node can carry so DefaultForm's
|
|
70
|
-
* sticky-footer split (fw#1918) treats it as the pinned primary action even
|
|
71
|
-
* though it isn't a `Button` with `type="submit"` — e.g. RenderEdit's
|
|
72
|
-
* footer-slot mount, whose actual rendered button is opaque app code the
|
|
73
|
-
* split can't inspect (see `ScreenSlots.footerPrimary`). */
|
|
74
|
-
export const STICKY_PRIMARY_ACTION_PROP = "kumikoStickyPrimaryAction" as const;
|
|
75
|
-
|
|
76
|
-
export type StickyPrimaryActionMarker = Partial<Record<typeof STICKY_PRIMARY_ACTION_PROP, boolean>>;
|
|
77
|
-
|
|
78
|
-
/** Standard-Button. `loading` zeigt einen Spinner statt der Children
|
|
79
|
-
* und sollte mit `disabled` kombiniert werden, wenn die Action wirklich
|
|
80
|
-
* blockiert bis das Loading durch ist (z.B. async submit). Native-
|
|
81
|
-
* Impls können den Spinner als Activity-Indicator rendern. */
|
|
82
|
-
export type ButtonProps = {
|
|
83
|
-
readonly type?: "button" | "submit";
|
|
84
|
-
readonly onClick?: () => void | Promise<void>;
|
|
85
|
-
readonly disabled?: boolean;
|
|
86
|
-
/** Spinner statt Children rendern. Caller sollte `disabled` mit-
|
|
87
|
-
* setzen wenn die Action blockiert bis das Loading abgeschlossen
|
|
88
|
-
* ist (verhindert Double-Submit). */
|
|
89
|
-
readonly loading?: boolean;
|
|
90
|
-
/** Semantic class — default="primary". Custom impls decide what this
|
|
91
|
-
* becomes visually; the renderers use "primary" for Save, "danger" for
|
|
92
|
-
* Delete, "secondary" for a Confirm state, "link" for inline actions in
|
|
93
|
-
* running text (no background, underline), "danger-ghost" for a
|
|
94
|
-
* destructive action as red text instead of a red fill. */
|
|
95
|
-
readonly variant?: "primary" | "secondary" | "danger" | "link" | "danger-ghost";
|
|
96
|
-
/** Größe — default="md". "sm" für kompakte Inline-Aktionen (Toolbar,
|
|
97
|
-
* Listen-Zeilen), "icon" für quadratische Icon-only-Buttons. */
|
|
98
|
-
readonly size?: "sm" | "md" | "icon";
|
|
99
|
-
/** Barrierefreies Label — Pflicht bei icon-only-Buttons (children ist nur
|
|
100
|
-
* ein Icon/Zeichen), sonst hat der Button keinen zugänglichen Namen. */
|
|
101
|
-
readonly ariaLabel?: string;
|
|
102
|
-
/** Breite — default="auto" (inhaltsbreit). "full" streckt CTA-Buttons auf
|
|
103
|
-
* die Container-Breite (Karten/Panels). Andere Breiten sind Layout-Sache
|
|
104
|
-
* des Containers, kein Button-Prop (Kit hält arbiträres Sizing draußen). */
|
|
105
|
-
readonly width?: "full" | "auto";
|
|
106
|
-
/** Optional for icon-only buttons (`size="icon"` with a resolved `icon`)
|
|
107
|
-
* — the icon carries the content then, `ariaLabel` the accessible name.
|
|
108
|
-
* Required in practice otherwise: without children and without `icon`
|
|
109
|
-
* the button stays empty. */
|
|
110
|
-
readonly children?: ReactNode;
|
|
111
|
-
readonly testId?: string;
|
|
112
|
-
/** Layout extras — Web merges via cn(), native impls ignore it
|
|
113
|
-
* (precedent: LinkProps.className). */
|
|
114
|
-
readonly className?: string;
|
|
115
|
-
/** DOM ref on the button — escape hatch for cases that need more than
|
|
116
|
-
* a click (e.g. binding a drop-target handler). Web forwards it, native
|
|
117
|
-
* impls ignore it (no native equivalent). */
|
|
118
|
-
readonly ref?: Ref<HTMLButtonElement>;
|
|
119
|
-
/** Icon before the label. */
|
|
120
|
-
readonly icon?: NavIconKey;
|
|
121
|
-
/** Icon after the label (e.g. "Continue →"). */
|
|
122
|
-
readonly iconEnd?: NavIconKey;
|
|
123
|
-
readonly dataAttributes?: DataAttributes;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/** Navigations-Link. `variant="button"` rendert die Button-Optik auf einem
|
|
127
|
-
* semantischen Anchor (z.B. „Zum Login" nach Reset-Success), `muted` den
|
|
128
|
-
* dezenten Sekundär-Link. Interne SPA-Navigation läuft über den Browser-
|
|
129
|
-
* Default (History-Integration liegt beim Nav-Layer, nicht am Primitive). */
|
|
130
|
-
export type LinkProps = {
|
|
131
|
-
readonly href: string;
|
|
132
|
-
readonly variant?: "default" | "button" | "muted";
|
|
133
|
-
/** `_blank` setzt in der Web-Impl automatisch rel="noreferrer". */
|
|
134
|
-
readonly target?: "_blank";
|
|
135
|
-
/** Layout-Zusätze (self-center, text-xs) — Web merged via cn(),
|
|
136
|
-
* Native-Impls ignorieren es (Präzedenz: CardProps.className). */
|
|
137
|
-
readonly className?: string;
|
|
138
|
-
readonly children: ReactNode;
|
|
139
|
-
readonly testId?: string;
|
|
140
|
-
readonly dataAttributes?: DataAttributes;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/** Banner für inline-Message ODER Page-State (z.B. "Loading…",
|
|
144
|
-
* "Screen not found"). `padded` setzt einen Außenabstand damit der
|
|
145
|
-
* Banner nicht edge-to-edge an den Main-Border klebt — relevant
|
|
146
|
-
* seit `<main>` kein eigenes Padding mehr hat. */
|
|
147
|
-
export type BannerProps = {
|
|
148
|
-
/** "error" for alerts (conflict, network error), "info" for neutral
|
|
149
|
-
* placeholders (not-found, loading), "loading" for load states, "warning"
|
|
150
|
-
* for a non-blocking but attention-grabbing notice (e.g. secretMint's
|
|
151
|
-
* "this is shown only once"). */
|
|
152
|
-
readonly variant?: "error" | "info" | "loading" | "warning";
|
|
153
|
-
readonly children: ReactNode;
|
|
154
|
-
/** Optional — weitere Knöpfe/Elemente rechts vom Text (z.B. "Neu
|
|
155
|
-
* laden"). Inline, nicht als eigener Block. */
|
|
156
|
-
readonly actions?: ReactNode;
|
|
157
|
-
/** Setzt einen Außenabstand um den Banner — für Page-States wo der
|
|
158
|
-
* Banner alleine im Main rendert (KumikoScreen "not-found",
|
|
159
|
-
* "loading", etc.). Web fügt p-6 als Margin um den Banner. */
|
|
160
|
-
readonly padded?: boolean;
|
|
161
|
-
readonly testId?: string;
|
|
162
|
-
/** Set when a `<Field>` wraps this Banner as its control (no actual
|
|
163
|
-
* input exists for the label to target — see render-field.tsx's
|
|
164
|
-
* unsupported-field-type fallback, #1834). The web implementation uses
|
|
165
|
-
* it to derive `role="group"` + `aria-labelledby`, not `htmlFor`: a
|
|
166
|
-
* `<label for>` on a non-labelable element like a `<div>` is inert for
|
|
167
|
-
* screen readers. */
|
|
168
|
-
readonly id?: string;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export type FieldProps = {
|
|
172
|
-
readonly id: string;
|
|
173
|
-
readonly label: string;
|
|
174
|
-
readonly required?: boolean;
|
|
175
|
-
readonly issues?: readonly FieldIssue[];
|
|
176
|
-
readonly labelAppendix?: ReactNode;
|
|
177
|
-
readonly fieldAppendix?: ReactNode;
|
|
178
|
-
readonly children: ReactNode;
|
|
179
|
-
/** Label-Layout. `stacked` (Default): Label über dem Control. `inline`:
|
|
180
|
-
* Control + Label nebeneinander (Box links, Label rechts) — für
|
|
181
|
-
* Checkbox/Switch-Felder (boolean), wie das shadcn-Muster. */
|
|
182
|
-
readonly layout?: "stacked" | "inline";
|
|
183
|
-
/** Collapses the visible label to `sr-only` — stays associated via
|
|
184
|
-
* `htmlFor` (no separate `aria-label` needed). For tables/grids whose
|
|
185
|
-
* column header already carries the label for each row. */
|
|
186
|
-
readonly hideLabel?: boolean;
|
|
187
|
-
readonly testId?: string;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
/** Discriminated union — jede Input-Sorte hat ihre eigene value/onChange
|
|
191
|
-
* Signatur. Custom-Impls dispatchen intern, rendern anders (Toggle
|
|
192
|
-
* statt Checkbox), oder nur einzelne kinds unterschiedlich. */
|
|
193
|
-
export type InputProps =
|
|
194
|
-
| {
|
|
195
|
-
readonly kind: "text";
|
|
196
|
-
readonly id: string;
|
|
197
|
-
readonly name: string;
|
|
198
|
-
readonly value: string;
|
|
199
|
-
readonly onChange: (v: string) => void;
|
|
200
|
-
readonly disabled?: boolean;
|
|
201
|
-
readonly required?: boolean;
|
|
202
|
-
readonly hasError?: boolean;
|
|
203
|
-
/** Hint-Text wenn das Feld leer ist. Used für Search-Inputs in
|
|
204
|
-
* Toolbars ("Suchen…") wo kein Label sinnvoll ist. */
|
|
205
|
-
readonly placeholder?: string;
|
|
206
|
-
/** Browser-Autofill / Native-Keyboard-Hint. Web setzt das auf
|
|
207
|
-
* `<input autocomplete=...>`, Native auf `textContentType`. */
|
|
208
|
-
readonly autoComplete?: string;
|
|
209
|
-
/** `data-testid` aufs echte `<input>` — App-Tests adressieren das
|
|
210
|
-
* Element direkt (getByTestId + readOnly/disabled-Assertions). */
|
|
211
|
-
readonly testId?: string;
|
|
212
|
-
/** Read-only Input (z.B. gewürfelter Free-Tier-Slug). Nicht `disabled`
|
|
213
|
-
* — bleibt fokussier-/kopierbar. */
|
|
214
|
-
readonly readOnly?: boolean;
|
|
215
|
-
/** Closed FieldIconKey vocabulary (FIELD_ICONS registry, renderer-web). */
|
|
216
|
-
readonly icon?: FieldIconKey;
|
|
217
|
-
readonly dataAttributes?: DataAttributes;
|
|
218
|
-
/** Raw keydown access (e.g. Enter-to-submit at the field instead of
|
|
219
|
-
* the surrounding Form). Web forwards it, native impls ignore it
|
|
220
|
-
* (precedent: `ButtonProps.ref`). */
|
|
221
|
-
readonly onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
222
|
-
}
|
|
223
|
-
| {
|
|
224
|
-
readonly kind: "email";
|
|
225
|
-
readonly id: string;
|
|
226
|
-
readonly name: string;
|
|
227
|
-
readonly value: string;
|
|
228
|
-
readonly onChange: (v: string) => void;
|
|
229
|
-
readonly disabled?: boolean;
|
|
230
|
-
readonly required?: boolean;
|
|
231
|
-
readonly hasError?: boolean;
|
|
232
|
-
readonly placeholder?: string;
|
|
233
|
-
/** Default "email". Apps die "username" wollen (Login-Form mit
|
|
234
|
-
* Username-or-Email) reichen das durch. */
|
|
235
|
-
readonly autoComplete?: string;
|
|
236
|
-
readonly testId?: string;
|
|
237
|
-
}
|
|
238
|
-
| {
|
|
239
|
-
readonly kind: "password";
|
|
240
|
-
readonly id: string;
|
|
241
|
-
readonly name: string;
|
|
242
|
-
readonly value: string;
|
|
243
|
-
readonly onChange: (v: string) => void;
|
|
244
|
-
readonly disabled?: boolean;
|
|
245
|
-
readonly required?: boolean;
|
|
246
|
-
readonly hasError?: boolean;
|
|
247
|
-
/** Hint text shown when the field is empty (analog to kind:"text"). */
|
|
248
|
-
readonly placeholder?: string;
|
|
249
|
-
/** "current-password" für Login, "new-password" für Reset/Signup —
|
|
250
|
-
* Browser-Password-Manager nutzen das für die Speicherentscheidung.
|
|
251
|
-
* Native: textContentType="password" / "newPassword". */
|
|
252
|
-
readonly autoComplete?: "current-password" | "new-password";
|
|
253
|
-
readonly testId?: string;
|
|
254
|
-
}
|
|
255
|
-
| {
|
|
256
|
-
readonly kind: "number";
|
|
257
|
-
readonly id: string;
|
|
258
|
-
readonly name: string;
|
|
259
|
-
readonly value: number | "";
|
|
260
|
-
readonly onChange: (v: number | undefined) => void;
|
|
261
|
-
readonly disabled?: boolean;
|
|
262
|
-
readonly required?: boolean;
|
|
263
|
-
readonly hasError?: boolean;
|
|
264
|
-
/** Hint text shown when the field is empty (analog to kind:"text"). */
|
|
265
|
-
readonly placeholder?: string;
|
|
266
|
-
readonly testId?: string;
|
|
267
|
-
/** Closed FieldIconKey vocabulary (FIELD_ICONS registry, renderer-web). */
|
|
268
|
-
readonly icon?: FieldIconKey;
|
|
269
|
-
/** `<input step>`. "any" disables the native stepMismatch constraint
|
|
270
|
-
* (needed for decimal fields — integer fields leave this unset). */
|
|
271
|
-
readonly step?: number | "any";
|
|
272
|
-
/** Resolved display suffix (static or from a sibling field) — never
|
|
273
|
-
* part of the numeric value. */
|
|
274
|
-
readonly unit?: string;
|
|
275
|
-
}
|
|
276
|
-
| {
|
|
277
|
-
readonly kind: "range";
|
|
278
|
-
readonly id: string;
|
|
279
|
-
readonly name: string;
|
|
280
|
-
readonly value: number;
|
|
281
|
-
readonly onChange: (v: number) => void;
|
|
282
|
-
readonly min: number;
|
|
283
|
-
readonly max: number;
|
|
284
|
-
readonly step?: number;
|
|
285
|
-
readonly disabled?: boolean;
|
|
286
|
-
readonly required?: boolean;
|
|
287
|
-
readonly hasError?: boolean;
|
|
288
|
-
}
|
|
289
|
-
| {
|
|
290
|
-
readonly kind: "boolean";
|
|
291
|
-
readonly id: string;
|
|
292
|
-
readonly name: string;
|
|
293
|
-
readonly value: boolean;
|
|
294
|
-
readonly onChange: (v: boolean) => void;
|
|
295
|
-
readonly disabled?: boolean;
|
|
296
|
-
readonly required?: boolean;
|
|
297
|
-
readonly hasError?: boolean;
|
|
298
|
-
}
|
|
299
|
-
| {
|
|
300
|
-
readonly kind: "file" | "image";
|
|
301
|
-
readonly id: string;
|
|
302
|
-
readonly name: string;
|
|
303
|
-
/** FileRef-UUID (gespeicherte Datei) oder null (kein Upload). */
|
|
304
|
-
readonly value: string | null;
|
|
305
|
-
readonly onChange: (fileId: string | null) => void;
|
|
306
|
-
readonly disabled?: boolean;
|
|
307
|
-
readonly required?: boolean;
|
|
308
|
-
readonly hasError?: boolean;
|
|
309
|
-
/** Erlaubte Extensions/MIME (z.B. ["jpg","png"]) → `accept`-Attribut. */
|
|
310
|
-
readonly accept?: readonly string[];
|
|
311
|
-
readonly maxSize?: string;
|
|
312
|
-
/** Für den Upload-POST `/api/files` — der Endpoint prüft maxSize/accept
|
|
313
|
-
* gegen die Field-Def von (entityType, fieldName). */
|
|
314
|
-
readonly entityType?: string;
|
|
315
|
-
readonly fieldName?: string;
|
|
316
|
-
/** Only for kind:"image" — variant name for the preview
|
|
317
|
-
* (`/api/files/:id/variant/:name`). Absent → the original. */
|
|
318
|
-
readonly imageVariant?: string;
|
|
319
|
-
/** Only for kind:"image" — forwarded to the file input's `capture`
|
|
320
|
-
* attribute so a phone opens the camera instead of the file picker. */
|
|
321
|
-
readonly capture?: "environment" | "user";
|
|
322
|
-
}
|
|
323
|
-
| {
|
|
324
|
-
readonly kind: "date";
|
|
325
|
-
readonly id: string;
|
|
326
|
-
readonly name: string;
|
|
327
|
-
readonly value: string;
|
|
328
|
-
readonly onChange: (v: string | undefined) => void;
|
|
329
|
-
/** Locale für Anzeige UND Eingabe-Parsing. Default = Browser-Locale
|
|
330
|
-
* via navigator.language. Apps mit eigenem LocaleResolver können
|
|
331
|
-
* ihren current locale durchreichen. */
|
|
332
|
-
readonly locale?: string;
|
|
333
|
-
/** Datumsgrenzen als ISO `yyyy-mm-dd` — begrenzen Picker (Jahres-
|
|
334
|
-
* Dropdown-Range + ausgegraute Tage). */
|
|
335
|
-
readonly min?: string;
|
|
336
|
-
readonly max?: string;
|
|
337
|
-
readonly disabled?: boolean;
|
|
338
|
-
readonly required?: boolean;
|
|
339
|
-
readonly hasError?: boolean;
|
|
340
|
-
}
|
|
341
|
-
| {
|
|
342
|
-
readonly kind: "select";
|
|
343
|
-
readonly id: string;
|
|
344
|
-
readonly name: string;
|
|
345
|
-
readonly value: string;
|
|
346
|
-
readonly onChange: (v: string) => void;
|
|
347
|
-
/** Erlaubte Werte. Leeres Array → Dropdown ohne Optionen, ein
|
|
348
|
-
* required Field ist dann nicht erfüllbar (Author-Hinweis, nicht
|
|
349
|
-
* Endnutzer). String-Form für statische Listen (Wert == Label),
|
|
350
|
-
* {value,label}-Form für DB-getragene Refs (Tier 2.7e-3). */
|
|
351
|
-
readonly options:
|
|
352
|
-
| readonly string[]
|
|
353
|
-
| readonly { readonly value: string; readonly label: string }[];
|
|
354
|
-
readonly disabled?: boolean;
|
|
355
|
-
readonly required?: boolean;
|
|
356
|
-
readonly hasError?: boolean;
|
|
357
|
-
/** Requested presentation. `"radio"` renders the options as a visible
|
|
358
|
-
* radio group (WAI-ARIA radiogroup, one click per choice),
|
|
359
|
-
* `"dropdown"` renders the combobox. Omitted = the implementation
|
|
360
|
-
* decides; the web impl keeps its short-option-set heuristic. Custom
|
|
361
|
-
* impls may ignore it — it is a request, not a contract. */
|
|
362
|
-
readonly display?: "radio" | "dropdown";
|
|
363
|
-
}
|
|
364
|
-
| ({
|
|
365
|
-
// Tier 2.1c: Combobox / Searchable-Select. Single-Mode (multiple
|
|
366
|
-
// false oder weggelassen): value ist string, onChange string.
|
|
367
|
-
// Multi-Mode (multiple: true): value ist string[], onChange
|
|
368
|
-
// string[]. Reference-Felder (Tier 2.7e-3+) nutzen das hier ohne
|
|
369
|
-
// explizit Combobox zu setzen — der Renderer wählt Combobox
|
|
370
|
-
// automatisch für reference-Field-Types.
|
|
371
|
-
// Discriminated-Union per `multiple`: Single-Mode hat string-
|
|
372
|
-
// value/onChange, Multi-Mode hat readonly string[] — Caller
|
|
373
|
-
// muss den Mode beim Build wählen, der Compiler zwingt die
|
|
374
|
-
// richtige Signatur.
|
|
375
|
-
readonly kind: "combobox";
|
|
376
|
-
readonly id: string;
|
|
377
|
-
readonly name: string;
|
|
378
|
-
readonly options: readonly { readonly value: string; readonly label: string }[];
|
|
379
|
-
readonly disabled?: boolean;
|
|
380
|
-
readonly required?: boolean;
|
|
381
|
-
readonly hasError?: boolean;
|
|
382
|
-
readonly placeholder?: string;
|
|
383
|
-
readonly searchPlaceholder?: string;
|
|
384
|
-
readonly emptyText?: string;
|
|
385
|
-
/** Tier 2.7e Remote-Search: gesetzt = Combobox läuft im Remote-
|
|
386
|
-
* Mode (cmdk-Local-Filter aus, Search-Input ruft onSearchChange
|
|
387
|
-
* debounced an den Caller). */
|
|
388
|
-
readonly onSearchChange?: (q: string) => void;
|
|
389
|
-
readonly loading?: boolean;
|
|
390
|
-
/** Fixed "+ createLabel" footer row that fires instead of
|
|
391
|
-
* selecting an option — omit for the plain select-existing
|
|
392
|
-
* combobox (kumiko-framework#1681). */
|
|
393
|
-
readonly onCreate?: () => void;
|
|
394
|
-
readonly createLabel?: string;
|
|
395
|
-
} & (
|
|
396
|
-
| {
|
|
397
|
-
readonly multiple?: false;
|
|
398
|
-
readonly value: string;
|
|
399
|
-
readonly onChange: (v: string) => void;
|
|
400
|
-
}
|
|
401
|
-
| {
|
|
402
|
-
readonly multiple: true;
|
|
403
|
-
readonly value: readonly string[];
|
|
404
|
-
readonly onChange: (v: readonly string[]) => void;
|
|
405
|
-
}
|
|
406
|
-
))
|
|
407
|
-
| {
|
|
408
|
-
readonly kind: "money";
|
|
409
|
-
readonly id: string;
|
|
410
|
-
readonly name: string;
|
|
411
|
-
/** Internal-Format: Cents/Minor-Units als Integer (z.B. 1299 für
|
|
412
|
-
* 12,99 EUR). Renderer übersetzt fürs UI in Major-Units mit
|
|
413
|
-
* Locale-formatierten Dezimalstellen. Empty-State = `""`. */
|
|
414
|
-
readonly value: number | "";
|
|
415
|
-
readonly onChange: (v: number | undefined) => void;
|
|
416
|
-
/** ISO-4217 Currency-Code, z.B. "EUR" / "USD" / "CHF". Default
|
|
417
|
-
* "EUR". Renderer zeigt das Symbol als Suffix und formatiert die
|
|
418
|
-
* Decimal-Stellen entsprechend (EUR/USD/CHF haben 2, JPY hat 0). */
|
|
419
|
-
readonly currency?: string;
|
|
420
|
-
/** Locale für Zahlen-Formatierung. Default "de-DE" (Komma als
|
|
421
|
-
* Dezimaltrenner). Apps mit eigenem LocaleResolver können ihren
|
|
422
|
-
* current locale durchreichen. */
|
|
423
|
-
readonly locale?: string;
|
|
424
|
-
readonly disabled?: boolean;
|
|
425
|
-
readonly required?: boolean;
|
|
426
|
-
readonly hasError?: boolean;
|
|
427
|
-
}
|
|
428
|
-
| {
|
|
429
|
-
readonly kind: "timestamp";
|
|
430
|
-
readonly id: string;
|
|
431
|
-
readonly name: string;
|
|
432
|
-
/** ISO-8601 Datetime-String. UTC-Instant mit `Z`
|
|
433
|
-
* ("2026-04-25T13:45:00Z") oder Wall-Clock ohne Offset
|
|
434
|
-
* ("2026-04-25T13:45", nur bei wallClock). Empty-State = `""`.
|
|
435
|
-
* Web rendert getrennte Datums- (tippbar + Kalender) und
|
|
436
|
-
* Uhrzeit-Eingabe und konvertiert. */
|
|
437
|
-
readonly value: string;
|
|
438
|
-
readonly onChange: (v: string | undefined) => void;
|
|
439
|
-
/** true = locatedTimestamp (Wall-Clock ohne Offset, Server
|
|
440
|
-
* validiert z.iso.datetime({local:true})). false/undefined =
|
|
441
|
-
* UTC-Instant, onChange MUSS mit `Z`-Suffix emittieren. */
|
|
442
|
-
readonly wallClock?: boolean;
|
|
443
|
-
/** Locale für Anzeige UND Eingabe-Parsing des Datums-Teils.
|
|
444
|
-
* Default = Browser-Locale. */
|
|
445
|
-
readonly locale?: string;
|
|
446
|
-
/** Datumsgrenzen als ISO-Datetime — begrenzen den Kalender auf
|
|
447
|
-
* Tages-Granularität. */
|
|
448
|
-
readonly min?: string;
|
|
449
|
-
readonly max?: string;
|
|
450
|
-
readonly disabled?: boolean;
|
|
451
|
-
readonly required?: boolean;
|
|
452
|
-
readonly hasError?: boolean;
|
|
453
|
-
}
|
|
454
|
-
| {
|
|
455
|
-
readonly kind: "locatedTimestamp";
|
|
456
|
-
readonly id: string;
|
|
457
|
-
readonly name: string;
|
|
458
|
-
/** Located-Timestamp-Wert: Wall-Clock `at` (ohne Offset) + IANA-`tz`.
|
|
459
|
-
* `utc` ist beim Read da (server-berechnet) und wird beim Write
|
|
460
|
-
* ignoriert. Empty-State = `""`. onChange emittiert `{ at, tz }`. */
|
|
461
|
-
readonly value: { readonly at: string; readonly tz: string; readonly utc?: string } | "";
|
|
462
|
-
readonly onChange: (v: { readonly at: string; readonly tz: string } | undefined) => void;
|
|
463
|
-
/** Locale für den Datums-Teil. Default = Browser-Locale. */
|
|
464
|
-
readonly locale?: string;
|
|
465
|
-
/** Datumsgrenzen als ISO-Datetime — begrenzen den Kalender. */
|
|
466
|
-
readonly min?: string;
|
|
467
|
-
readonly max?: string;
|
|
468
|
-
readonly disabled?: boolean;
|
|
469
|
-
readonly required?: boolean;
|
|
470
|
-
readonly hasError?: boolean;
|
|
471
|
-
}
|
|
472
|
-
| {
|
|
473
|
-
readonly kind: "tz";
|
|
474
|
-
readonly id: string;
|
|
475
|
-
readonly name: string;
|
|
476
|
-
/** IANA zone name, e.g. "Europe/Berlin". Empty-state = "". */
|
|
477
|
-
readonly value: string;
|
|
478
|
-
readonly onChange: (v: string | undefined) => void;
|
|
479
|
-
readonly disabled?: boolean;
|
|
480
|
-
readonly required?: boolean;
|
|
481
|
-
readonly hasError?: boolean;
|
|
482
|
-
}
|
|
483
|
-
| {
|
|
484
|
-
readonly kind: "textarea";
|
|
485
|
-
readonly id: string;
|
|
486
|
-
readonly name: string;
|
|
487
|
-
readonly value: string;
|
|
488
|
-
readonly onChange: (v: string) => void;
|
|
489
|
-
/** Visible rows. Acts as a minimum height — the field starts this tall
|
|
490
|
-
* and still grows with its content. Omitted, the default primitive
|
|
491
|
-
* falls back to its own floor (#2677). */
|
|
492
|
-
readonly rows?: number;
|
|
493
|
-
readonly disabled?: boolean;
|
|
494
|
-
readonly required?: boolean;
|
|
495
|
-
readonly hasError?: boolean;
|
|
496
|
-
/** Hint text shown when the field is empty (analog to kind:"text"). */
|
|
497
|
-
readonly placeholder?: string;
|
|
498
|
-
/** Read-only Textarea. Nicht `disabled` — bleibt fokussier-/
|
|
499
|
-
* kopierbar (analog zu kind:"text"). */
|
|
500
|
-
readonly readOnly?: boolean;
|
|
501
|
-
/** Ctrl/Cmd+Enter submits instead of inserting a newline. Plain
|
|
502
|
-
* Enter still inserts a newline. */
|
|
503
|
-
readonly onSubmitShortcut?: () => void;
|
|
504
|
-
readonly dataAttributes?: DataAttributes;
|
|
505
|
-
/** Raw keydown access (e.g. plain-Enter-submits/Shift+Enter-newline at
|
|
506
|
-
* the field instead of the surrounding Form). Composes with
|
|
507
|
-
* `onSubmitShortcut` — both fire on the same keystroke when set. Web
|
|
508
|
-
* forwards it, native impls ignore it (precedent: `ButtonProps.ref`). */
|
|
509
|
-
readonly onKeyDown?: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
// Sort-Wire-Format. `null`-State unterscheidet "User hat noch nichts
|
|
513
|
-
// gesortiert" vom "ich sortiere nach X" — wichtig für 3-State-Toggle
|
|
514
|
-
// (asc → desc → null → asc …). Renderer kennt den aktuellen sort und
|
|
515
|
-
// callt onSortChange wenn der User auf einen sortable-Header klickt.
|
|
516
|
-
export type DataTableSortDir = "asc" | "desc";
|
|
517
|
-
export type DataTableSort = {
|
|
518
|
-
readonly field: string;
|
|
519
|
-
readonly dir: DataTableSortDir;
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
// Render-Modus der Row-Action-Spalte (siehe DataTableProps.rowActionMode).
|
|
523
|
-
export type DataTableRowActionMode = "adaptive" | "inline";
|
|
524
|
-
|
|
525
|
-
// Resolved-Form einer Row-Action (KumikoScreen baut das aus
|
|
526
|
-
// EntityListScreenDefinition.rowActions): Labels schon translated,
|
|
527
|
-
// handler-QN aufgelöst zu einer onTrigger-Function die den dispatcher
|
|
528
|
-
// kennt. DataTable rendert das ohne weiteres i18n/dispatcher-Wissen.
|
|
529
|
-
export type DataTableRowAction = {
|
|
530
|
-
/** Stable id für aria-labels und data-testids. */
|
|
531
|
-
readonly id: string;
|
|
532
|
-
/** Translated Label. */
|
|
533
|
-
readonly label: string;
|
|
534
|
-
/** Visual style — "danger" renders the red variant in the default
|
|
535
|
-
* primitive. Whether it also forces a confirm dialog is governed by
|
|
536
|
-
* `confirmRequired` (falls back to `style === "danger"` when unset). */
|
|
537
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
538
|
-
/** Translated Confirm-Prompt (Description im Dialog) — wenn gesetzt,
|
|
539
|
-
* öffnet ein Modal vor der Ausführung. Bei style=danger ohne expliziten
|
|
540
|
-
* confirm sollte der Renderer einen generischen Default zeigen. */
|
|
541
|
-
readonly confirm?: string;
|
|
542
|
-
/** Translated Confirm-Button-Label im Dialog. Default = `label`
|
|
543
|
-
* (Action-Label wird wiederverwendet). */
|
|
544
|
-
readonly confirmLabel?: string;
|
|
545
|
-
/** Overrides the default "danger implies a confirm dialog" rule. Schema-driven
|
|
546
|
-
* navigate/drawer actions set it to false: the colour marks the action as
|
|
547
|
-
* destructive, but the target form is the confirmation. */
|
|
548
|
-
readonly confirmRequired?: boolean;
|
|
549
|
-
/** Wird mit der ListRowViewModel der geklickten Row aufgerufen. Async
|
|
550
|
-
* erlaubt — der Renderer kann während der Promise-Resolution einen
|
|
551
|
-
* Loading-State auf dem Button zeigen. */
|
|
552
|
-
readonly onTrigger: (row: ListRowViewModel) => Promise<void> | void;
|
|
553
|
-
/** Conditional Visibility pro Row (z.B. "Start" nur wenn status==="scheduled"). */
|
|
554
|
-
readonly isVisible?: (row: ListRowViewModel) => boolean;
|
|
555
|
-
/** Resolved icon (author `RowAction.icon` or the id-derived default) —
|
|
556
|
-
* drives both the icon-left-of-text render and the icon-only collapse
|
|
557
|
-
* rule (see `shouldRenderActionsIconOnly`). */
|
|
558
|
-
readonly icon?: IconKey;
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
/** Teil-C action-icon collapse rule: a group of more than two actions where
|
|
562
|
-
* every member carries an icon renders icon-only instead of wall-to-wall
|
|
563
|
-
* text buttons; any icon-less member keeps the whole group on text so it
|
|
564
|
-
* doesn't fall apart visually mid-group. */
|
|
565
|
-
export function shouldRenderActionsIconOnly(
|
|
566
|
-
actions: readonly { readonly icon?: IconKey }[],
|
|
567
|
-
): boolean {
|
|
568
|
-
return actions.length > 2 && actions.every((a) => a.icon !== undefined);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Ein Faceted-Filter-Slot in der Toolbar: ein Outline-Dropdown-Button
|
|
572
|
-
// (wie shadcns "Columns"-Toggle) mit Multi-Select-Checkboxen. KumikoScreen
|
|
573
|
-
// baut das aus den filterable select/boolean-Feldern des Entity.
|
|
574
|
-
export type DataTableFacet = {
|
|
575
|
-
/** Entity-Feldname (= URL-State-Key + payload.filters[].field). */
|
|
576
|
-
readonly field: string;
|
|
577
|
-
/** Translated Button-Label ("Status", "Active"). */
|
|
578
|
-
readonly label: string;
|
|
579
|
-
/** Auswählbare Werte mit Anzeige-Label. value = roher Filterwert
|
|
580
|
-
* (select-option / "true"|"false"), als String transportiert. */
|
|
581
|
-
readonly options: readonly { readonly value: string; readonly label: string }[];
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
// Time-range filter slot in the toolbar (fw#3104): two date inputs
|
|
585
|
-
// (from/to) instead of an options dropdown. Values are `YYYY-MM-DD` or ""
|
|
586
|
-
// (bound open) — the caller converts them into query params.
|
|
587
|
-
export type DataTableDateRangeFacet = {
|
|
588
|
-
/** The facet's namespace (its date field) — a key, not a filter value. */
|
|
589
|
-
readonly field: string;
|
|
590
|
-
/** Translated label ("When"). */
|
|
591
|
-
readonly label: string;
|
|
592
|
-
readonly from: string;
|
|
593
|
-
readonly to: string;
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
export type DataTableProps = {
|
|
597
|
-
readonly columns: readonly ListColumnViewModel[];
|
|
598
|
-
readonly rows: readonly ListRowViewModel[];
|
|
599
|
-
readonly onRowClick?: (row: ListRowViewModel) => void;
|
|
600
|
-
/** Aktuelle Sortierung (oder null = unsorted). Wenn columns ein
|
|
601
|
-
* `sortable: true`-Feld haben und onSortChange gesetzt ist, rendert
|
|
602
|
-
* der Renderer Click-Header mit Asc/Desc-Indikator. Ohne onSortChange
|
|
603
|
-
* bleibt die Header-Click-Mechanik aus, columns.sortable ist dann nur
|
|
604
|
-
* semantischer Hinweis. */
|
|
605
|
-
readonly sort?: DataTableSort | null;
|
|
606
|
-
/** Wird gerufen mit dem nächsten Sort-State nach einem Header-Klick.
|
|
607
|
-
* 3-State-Toggle (Convention): asc → desc → null. Caller setzt damit
|
|
608
|
-
* seinen URL-State / Query-Param und triggert ein refetch. */
|
|
609
|
-
readonly onSortChange?: (next: DataTableSort | null) => void;
|
|
610
|
-
/** Pro-Row-Aktionen — eine Spalte am rechten Rand mit Inline-Buttons
|
|
611
|
-
* (≤2 Aktionen) oder Kebab-Dropdown (>2). Caller liefert Resolved-
|
|
612
|
-
* Form (Labels + onTrigger schon verdrahtet); DataTable kümmert
|
|
613
|
-
* sich nur um Render + Confirm-Dialog. */
|
|
614
|
-
readonly rowActions?: readonly DataTableRowAction[];
|
|
615
|
-
/** Wie die Row-Action-Spalte rendert:
|
|
616
|
-
* - `"adaptive"` (Default): ≤2 sichtbare Actions inline (rechtsbündig),
|
|
617
|
-
* >2 als Kebab-Dropdown. Passt die Optik automatisch an die Anzahl an.
|
|
618
|
-
* - `"inline"`: IMMER Inline-Buttons, linksbündig — auch bei >2 (kein
|
|
619
|
-
* Kebab). So stehen die Aktionen über alle Rows an derselben x-Position
|
|
620
|
-
* (kein Wandern durch unterschiedlich breite Labels) und alle Listen
|
|
621
|
-
* einer App sehen gleich aus. */
|
|
622
|
-
readonly rowActionMode?: DataTableRowActionMode;
|
|
623
|
-
/** Custom Empty-State-Inhalt (z. B. Icon + Heading + CTA-Button).
|
|
624
|
-
* Default-Renderer rahmt ihn in einer dashed-border Box. */
|
|
625
|
-
readonly emptyState?: ReactNode;
|
|
626
|
-
/** Optionaler Titel-Slot ganz links der Toolbar — Screen-Titel
|
|
627
|
-
* ("Items", "Bestellungen"). Web rendert als font-medium Heading. */
|
|
628
|
-
readonly toolbarTitle?: ReactNode;
|
|
629
|
-
/** Toolbar-Slot mittig (typisch Search-Input). Renderer entscheidet
|
|
630
|
-
* das Layout — Web spreizt das Element als flex-1 mit max-Breite,
|
|
631
|
-
* Native könnte es als Header-Suchleiste rendern. */
|
|
632
|
-
readonly toolbarStart?: ReactNode;
|
|
633
|
-
/** Toolbar-Slot rechts (typisch + Neu Button, Filter, View-Switch).
|
|
634
|
-
* Web zieht den Cluster mit ml-auto an die rechte Kante. */
|
|
635
|
-
readonly toolbarEnd?: ReactNode;
|
|
636
|
-
/** Faceted-Filter-Dropdowns (select/boolean-Felder) — der Renderer
|
|
637
|
-
* setzt sie neben das Search-Input. Leer/undefined → keine Filter. */
|
|
638
|
-
readonly filterFacets?: readonly DataTableFacet[];
|
|
639
|
-
/** Aktuell ausgewählte Werte je Facet-Feld (vom URL-State). */
|
|
640
|
-
readonly filterValues?: Readonly<Record<string, readonly string[]>>;
|
|
641
|
-
/** Toggle eines Facet-Werts → Caller schreibt URL-State + refetch. */
|
|
642
|
-
readonly onFilterChange?: (field: string, values: readonly string[]) => void;
|
|
643
|
-
/** Reset-Button: löscht alle aktiven Facets. Renderer zeigt ihn nur
|
|
644
|
-
* wenn mindestens ein Facet aktiv ist. */
|
|
645
|
-
readonly onFilterReset?: () => void;
|
|
646
|
-
/** Time-range filters beside the facet dropdowns (fw#3104). */
|
|
647
|
-
readonly dateRangeFacets?: readonly DataTableDateRangeFacet[];
|
|
648
|
-
/** One bound changed ("" clears it) → caller writes URL state + refetch. */
|
|
649
|
-
readonly onDateRangeChange?: (field: string, bound: "from" | "to", value: string) => void;
|
|
650
|
-
/** Pagination-State + Callback. Wenn gesetzt, rendert der Renderer
|
|
651
|
-
* einen Pager unter der Tabelle (Web: Footer-Bar mit ← 1 ... N →).
|
|
652
|
-
* total/limit/page sind 1-basiert für die UI; Server-Translation
|
|
653
|
-
* zu offset = (page-1)*limit liegt beim Caller. Wenn page > total/limit
|
|
654
|
-
* würde, soll der Caller das vorab clampen. */
|
|
655
|
-
readonly pager?: {
|
|
656
|
-
readonly page: number;
|
|
657
|
-
readonly limit: number;
|
|
658
|
-
readonly total: number;
|
|
659
|
-
readonly onPageChange: (next: number) => void;
|
|
660
|
-
};
|
|
661
|
-
/** Infinite-Scroll Callback. Wenn gesetzt, rendert der Renderer einen
|
|
662
|
-
* Bottom-Sentinel und ruft `onReachEnd` wenn der ins Viewport rückt
|
|
663
|
-
* (Web: IntersectionObserver). Caller verwaltet accumulation +
|
|
664
|
-
* cursor + hasMore. `loadingMore=true` zeigt einen Spinner unter
|
|
665
|
-
* der Tabelle solange die nächste Page lädt. Inkompatibel mit
|
|
666
|
-
* `pager` — entweder Pager ODER Infinite-Scroll, nicht beides. */
|
|
667
|
-
readonly onReachEnd?: () => void;
|
|
668
|
-
readonly loadingMore?: boolean;
|
|
669
|
-
/** Wenn false und onReachEnd gesetzt, rendert der Renderer einen
|
|
670
|
-
* "Ende der Liste"-Hinweis statt des Sentinels. Default true. */
|
|
671
|
-
readonly hasMore?: boolean;
|
|
672
|
-
readonly testId?: string;
|
|
673
|
-
/** Wires an edit path into a component column-renderer: `DataTableCell`
|
|
674
|
-
* passes this down as `ColumnRendererProps.onChange` for that cell,
|
|
675
|
-
* bound to `(rowId, column.field)`. Renderers without a component
|
|
676
|
-
* renderer (format-spec, type-default) ignore it — read-only stays
|
|
677
|
-
* read-only unless the column has a custom renderer that reads
|
|
678
|
-
* `onChange`. */
|
|
679
|
-
readonly onCellChange?: (rowId: string, field: string, value: unknown) => void;
|
|
680
|
-
/** Overrides the row `data-testid` (default `row-${row.id}`) — for a
|
|
681
|
-
* screen that already has its own DOM-test/e2e naming scheme. */
|
|
682
|
-
readonly getRowTestId?: (row: ListRowViewModel) => string;
|
|
683
|
-
/** Overrides the cell `data-testid` (default `cell-${row.id}-${field}`). */
|
|
684
|
-
readonly getCellTestId?: (row: ListRowViewModel, field: string) => string;
|
|
685
|
-
/** Drops the table's own rounded/border/card background — for a host
|
|
686
|
-
* that already provides the surrounding boundary (a tab panel, fw#2722)
|
|
687
|
-
* and would otherwise show a card nested inside that boundary. Default
|
|
688
|
-
* false: unchanged card-framed table. */
|
|
689
|
-
readonly chromeless?: boolean;
|
|
690
|
-
/** Fills the available height of its flex container and scrolls rows
|
|
691
|
-
* internally instead of growing with row count — for a host that would
|
|
692
|
-
* otherwise have a long table stretch the whole page, or a short table
|
|
693
|
-
* leave dead space below it (a tab panel, fw#2722). Requires the same
|
|
694
|
-
* flex-fill chain FormProps.fillHeight sets up above it; without that
|
|
695
|
-
* ancestor chain this collapses to zero height (Web: `flex-1 min-h-0`
|
|
696
|
-
* has no effect outside a sized flex-col ancestor). Default false:
|
|
697
|
-
* unchanged document-flow table that grows with its content. */
|
|
698
|
-
readonly scrollBody?: boolean;
|
|
699
|
-
/** Uses the shared screen padding (wider bottom inset) instead of the
|
|
700
|
-
* table's symmetric embedded inset — for a table that IS the screen body
|
|
701
|
-
* (entityList/projectionList), so a list screen ends at the same footer
|
|
702
|
-
* distance as a form or custom screen (fw#2640). Hosts set this or
|
|
703
|
-
* `scrollBody`, not both: the wider bottom inset competes with
|
|
704
|
-
* `scrollBody`'s flex-fill height budget.
|
|
705
|
-
* Default false: unchanged symmetric inset for an embedded table. */
|
|
706
|
-
readonly screenPadding?: boolean;
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
// ---- EmbeddedListInput (createEmbeddedListField widget) ----
|
|
710
|
-
|
|
711
|
-
/** Cell type for one column of an embedded-list field. Mirrors
|
|
712
|
-
* EmbeddedListCellViewModel["type"] from `@cosmicdrift/kumiko-headless`. */
|
|
713
|
-
export type EmbeddedListCellType =
|
|
714
|
-
| "text"
|
|
715
|
-
| "number"
|
|
716
|
-
| "boolean"
|
|
717
|
-
| "date"
|
|
718
|
-
| "money"
|
|
719
|
-
| "decimal"
|
|
720
|
-
| "select"
|
|
721
|
-
| "reference"
|
|
722
|
-
| "timestamp";
|
|
723
|
-
|
|
724
|
-
/** One column of an embedded-list table (one entry per key of the source
|
|
725
|
-
* EmbeddedFieldDef's `schema`). */
|
|
726
|
-
export type EmbeddedListColumn = {
|
|
727
|
-
readonly field: string;
|
|
728
|
-
readonly label: string;
|
|
729
|
-
readonly type: EmbeddedListCellType;
|
|
730
|
-
readonly required: boolean;
|
|
731
|
-
/** Read-only, value supplied by the caller (already computed from
|
|
732
|
-
* sibling cells) — not directly user-editable. */
|
|
733
|
-
readonly derived: boolean;
|
|
734
|
-
/** Only for `type: "select"`. */
|
|
735
|
-
readonly options?: readonly string[];
|
|
736
|
-
readonly optionLabels?: Readonly<Record<string, string>>;
|
|
737
|
-
/** Only for `type: "reference"` — pre-fetched by the caller, ONE query
|
|
738
|
-
* per column shared across every row (not one query per cell). */
|
|
739
|
-
readonly referenceOptions?: readonly { readonly value: string; readonly label: string }[];
|
|
740
|
-
readonly referenceLoading?: boolean;
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
/** One entry of an embedded-list totals row (e.g. an invoice's total
|
|
744
|
-
* amount, summed across all rows by the caller). */
|
|
745
|
-
export type EmbeddedListTotal = {
|
|
746
|
-
readonly field: string;
|
|
747
|
-
readonly label: string;
|
|
748
|
-
readonly value: number;
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
/** Row-array + totals table for a `createEmbeddedListField` (invoice-
|
|
752
|
-
* positions-style) field — structurally too different from `Input`'s
|
|
753
|
-
* value/onChange union to fit there, so it's its own primitive, same as
|
|
754
|
-
* `DataTable`. Controlled: the caller (the eventual form-field wrapper)
|
|
755
|
-
* computes rows/derived values/issue groups and wires every callback;
|
|
756
|
-
* this primitive only renders and reports interaction. */
|
|
757
|
-
export type EmbeddedListInputProps = {
|
|
758
|
-
readonly id: string;
|
|
759
|
-
readonly columns: readonly EmbeddedListColumn[];
|
|
760
|
-
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
761
|
-
readonly totals?: readonly EmbeddedListTotal[];
|
|
762
|
-
/** Currency for money cells and the totals row — one value for the whole
|
|
763
|
-
* list (currency lives on the head aggregate, not the row). Web impl
|
|
764
|
-
* falls back to "EUR" when absent. */
|
|
765
|
-
readonly currency?: string;
|
|
766
|
-
/** Whole-list read-only (e.g. a released invoice) — hides row-mutation
|
|
767
|
-
* affordances and disables every cell. */
|
|
768
|
-
readonly disabled?: boolean;
|
|
769
|
-
readonly minItems?: number;
|
|
770
|
-
readonly maxItems?: number;
|
|
771
|
-
/** Shown under the whole list (e.g. under the totals row). */
|
|
772
|
-
readonly listIssues?: readonly FieldIssue[];
|
|
773
|
-
/** Keyed by row index. */
|
|
774
|
-
readonly rowIssues?: Readonly<Record<number, readonly FieldIssue[]>>;
|
|
775
|
-
/** Keyed `${rowIndex}.${field}`. */
|
|
776
|
-
readonly cellIssues?: Readonly<Record<string, readonly FieldIssue[]>>;
|
|
777
|
-
readonly onCellChange: (rowIndex: number, field: string, value: unknown) => void;
|
|
778
|
-
readonly onAddRow: () => void;
|
|
779
|
-
readonly onRemoveRow: (rowIndex: number) => void;
|
|
780
|
-
readonly onDuplicateRow: (rowIndex: number) => void;
|
|
781
|
-
readonly onMoveRow: (fromIndex: number, toIndex: number) => void;
|
|
782
|
-
/** Tab/newline-delimited clipboard paste starting at (rowIndex,
|
|
783
|
-
* columnIndex) — parsed to a 2D string grid by the Web impl (paste
|
|
784
|
-
* events are a browser-only concept); undefined caller = no paste
|
|
785
|
-
* handling wired up. */
|
|
786
|
-
readonly onPasteCells?: (
|
|
787
|
-
rowIndex: number,
|
|
788
|
-
columnIndex: number,
|
|
789
|
-
grid: readonly (readonly string[])[],
|
|
790
|
-
) => void;
|
|
791
|
-
readonly addLabel: string;
|
|
792
|
-
readonly removeLabel: string;
|
|
793
|
-
readonly duplicateLabel: string;
|
|
794
|
-
readonly moveUpLabel: string;
|
|
795
|
-
readonly moveDownLabel: string;
|
|
796
|
-
readonly emptyLabel: string;
|
|
797
|
-
readonly emptyCtaLabel: string;
|
|
798
|
-
readonly testId?: string;
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
export type { FormWidth };
|
|
802
|
-
|
|
803
|
-
/** Submit wrapper. Web: `<form onSubmit>`, native: a View that triggers an
|
|
804
|
-
* onSubmit callback via button press. `onSubmit` gets an abstract
|
|
805
|
-
* signature (no FormEvent) so native impls can fill it meaningfully.
|
|
806
|
-
*
|
|
807
|
-
* `title`: slot in the card header. `actions`: optional slot for the
|
|
808
|
-
* primary form actions (Save, Cancel) — renders as a footer row at the
|
|
809
|
-
* end of the card (normal document flow). Native impls may render the
|
|
810
|
-
* same slot as a bottom bar instead. */
|
|
811
|
-
export type FormProps = {
|
|
812
|
-
readonly onSubmit: (e?: FormEvent) => void;
|
|
813
|
-
readonly children: ReactNode;
|
|
814
|
-
readonly title?: ReactNode;
|
|
815
|
-
/** Optionaler muted Untertitel unter dem Titel (z.B. "Add a new item to
|
|
816
|
-
* your catalog") — gibt dem Form-Header Kontext statt nur ein Label. */
|
|
817
|
-
readonly subtitle?: ReactNode;
|
|
818
|
-
readonly actions?: ReactNode;
|
|
819
|
-
/** Secondary, record-related actions — rendered on the left on desktop,
|
|
820
|
-
* on their own row below the primary action on a narrow viewport. */
|
|
821
|
-
readonly secondaryActions?: ReactNode;
|
|
822
|
-
readonly testId?: string;
|
|
823
|
-
/** Max width of the form container. Default "full" — see FormWidth
|
|
824
|
-
* (`packages/types/src/screen.ts`, EditLayout.width). Native impls may
|
|
825
|
-
* ignore this prop (no width constraint there). */
|
|
826
|
-
readonly width?: FormWidth;
|
|
827
|
-
/** Pins `actions` to the viewport bottom on narrow screens (`<640px`)
|
|
828
|
-
* instead of normal document flow, so it stays reachable when a virtual
|
|
829
|
-
* keyboard shrinks the visible viewport (fw#1918). Desktop/tablet
|
|
830
|
-
* unaffected. Native impls may ignore this prop (already bottom-bar by
|
|
831
|
-
* convention there). */
|
|
832
|
-
readonly stickyActions?: boolean;
|
|
833
|
-
/** Extra content rendered above the card, inside the same page padding
|
|
834
|
-
* the card itself sits in — for a host that needs its own header region
|
|
835
|
-
* (title/metrics/tabs) to share the form's left padding and width
|
|
836
|
-
* instead of rendering as unpadded siblings before it. Native impls may
|
|
837
|
-
* ignore this prop. */
|
|
838
|
-
readonly headerRegion?: ReactNode;
|
|
839
|
-
/** Compact content on the right of the form title, same row — status
|
|
840
|
-
* chips or allowance badges that belong to the screen, not to a field.
|
|
841
|
-
* Native impls may ignore this prop. */
|
|
842
|
-
readonly titleAction?: ReactNode;
|
|
843
|
-
/** Sizes the form to fill its container's height (instead of the page's
|
|
844
|
-
* natural content height) so a single scrolling child — a lone
|
|
845
|
-
* relatedList tab's table — can scroll internally instead of stretching
|
|
846
|
-
* the whole page (fw#2722). Only set by RenderEdit for a lone relatedList
|
|
847
|
-
* tab section; every other caller leaves it unset and keeps normal
|
|
848
|
-
* document-flow height. Native impls may ignore this prop (already a
|
|
849
|
-
* bounded viewport there). */
|
|
850
|
-
readonly fillHeight?: boolean;
|
|
851
|
-
/** Drops the surrounding card (bg/border/rounded, title/subtitle block,
|
|
852
|
-
* footer border) so sections+footer render as siblings of `headerRegion`
|
|
853
|
-
* on the page background instead of inside a nested card — a tabbed
|
|
854
|
-
* projectionDetail's tab content is a sibling of the head card, not its
|
|
855
|
-
* child (bedienkonzept A1), so it must look like a standalone screen
|
|
856
|
-
* instead of a form embedded in another card. Default false: unchanged
|
|
857
|
-
* card chrome. Native impls may ignore this prop (no card chrome there
|
|
858
|
-
* to begin with). */
|
|
859
|
-
readonly chromeless?: boolean;
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
/** Titled Gruppe von Feldern. Web: `<fieldset>` + `<legend>`, Native:
|
|
863
|
-
* View mit Header-Text. Native-Impls können den Title als Accordion
|
|
864
|
-
* oder Collapsible rendern. */
|
|
865
|
-
export type SectionProps = {
|
|
866
|
-
/** Optional — ohne Titel rendert die Section nur die Gruppierung
|
|
867
|
-
* (kein Header). RenderEdit lässt ihn weg, wenn er den Screen-Titel
|
|
868
|
-
* der Action-Bar 1:1 wiederholen würde. */
|
|
869
|
-
readonly title?: string;
|
|
870
|
-
/** Optionaler muted Untertitel unter dem Titel — gibt der Section
|
|
871
|
-
* Kontext statt nur ein Label (analog FormProps.subtitle). */
|
|
872
|
-
readonly subtitle?: ReactNode;
|
|
873
|
-
readonly children: ReactNode;
|
|
874
|
-
/** Rendered top-right in the title row — never a footer. A
|
|
875
|
-
* title-less/subtitle-less Section still draws the row when `actions`
|
|
876
|
-
* is set, so a hideTitle tabs-Section with actions isn't stranded. */
|
|
877
|
-
readonly actions?: ReactNode;
|
|
878
|
-
/** "destructive" marks the Section as a warning/danger area (e.g. account
|
|
879
|
-
* deletion, restrict processing) — border color only, no content change.
|
|
880
|
-
* Default "default" (normal card border). */
|
|
881
|
-
readonly variant?: "default" | "destructive";
|
|
882
|
-
readonly testId?: string;
|
|
883
|
-
/** Rendered left of the title, `text-muted-foreground`, same optical
|
|
884
|
-
* size as the title. No effect without a `title` — an icon alone would
|
|
885
|
-
* have nothing to sit next to. */
|
|
886
|
-
readonly icon?: IconKey;
|
|
887
|
-
};
|
|
888
|
-
|
|
889
|
-
/** Chromeless flex-fill layout host — no title, no card frame, no padding,
|
|
890
|
-
* just a container that sizes to its content and, once the ancestor chain
|
|
891
|
-
* is height-constrained, shrinks so one scrolling child can scroll
|
|
892
|
-
* internally instead of the page growing (fw#2722, height fw#2778). Web:
|
|
893
|
-
* `<div className="flex min-h-0 flex-col">`. Native: Views are
|
|
894
|
-
* already flex-column and the parent is already a bounded viewport there,
|
|
895
|
-
* so a native impl may render this as a bare Fragment. Used only as the
|
|
896
|
-
* terminal link in `RenderEdit`'s `fillHeight` chain (`RelatedListSection`'s
|
|
897
|
-
* `hideTitle` branch) — `Section`/`Card` were rejected here because both
|
|
898
|
-
* carry title/padding/border chrome this spot doesn't want and would double
|
|
899
|
-
* up with the table's own padding. */
|
|
900
|
-
export type FillContainerProps = {
|
|
901
|
-
readonly children: ReactNode;
|
|
902
|
-
readonly testId?: string;
|
|
903
|
-
};
|
|
904
|
-
|
|
905
|
-
/** Columns-basiertes Layout. Web: CSS grid, Native: Flex-Wrap mit
|
|
906
|
-
* Width-%, oder react-native-grid. Jedes direkte Child kann eine
|
|
907
|
-
* `GridCell`-Wrapping bekommen für span-Kontrolle.
|
|
908
|
-
*
|
|
909
|
-
* `columns: "auto"` opts out of the N-column stretch layout entirely —
|
|
910
|
-
* children keep their own content width and wrap onto new lines (e.g. a
|
|
911
|
-
* metrics band of self-sized tiles, or an inline label+badge row) instead
|
|
912
|
-
* of being stretched into equal-width tracks. `GridCell` span-wrapping is
|
|
913
|
-
* meaningless in this mode and should be skipped. */
|
|
914
|
-
export type GridProps = {
|
|
915
|
-
readonly columns: number | "auto";
|
|
916
|
-
readonly children: ReactNode;
|
|
917
|
-
readonly testId?: string;
|
|
918
|
-
/** Rows visible before the grid becomes vertically scrollable. Omitted =
|
|
919
|
-
* the grid grows with its content and never scrolls. Ignored when
|
|
920
|
-
* `columns` is "auto". */
|
|
921
|
-
readonly maxRows?: number;
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
/** Span-Wrapper für ein Kind innerhalb eines Grid. Web: `style={{gridColumn: span N}}`,
|
|
925
|
-
* Native: eigenes Width-Rechnen. */
|
|
926
|
-
export type GridCellProps = {
|
|
927
|
-
readonly span?: number;
|
|
928
|
-
readonly children: ReactNode;
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
/** Semantischer Text. Variants bilden Standard-Typografie-Rollen ab —
|
|
932
|
-
* `body` ist Default, `small` für sekundäre Labels, `code` für inline
|
|
933
|
-
* monospace (entityId, screen-id), `required-mark` für das Sternchen
|
|
934
|
-
* hinter Labels, `muted` für gedimmten Fließtext (text-sm muted-
|
|
935
|
-
* foreground). Custom-Impls mappen auf ihren TypeScale. */
|
|
936
|
-
export type TextProps = {
|
|
937
|
-
readonly variant?: "body" | "small" | "code" | "required-mark" | "muted";
|
|
938
|
-
readonly children: ReactNode;
|
|
939
|
-
readonly testId?: string;
|
|
940
|
-
};
|
|
941
|
-
|
|
942
|
-
/** Heading mit zwei Rollen — `page` als Page-Titel (Web: h1), `section`
|
|
943
|
-
* als Sub-Header über einer Group (Web: h2 mit uppercase + muted).
|
|
944
|
-
* Native-Impls mappen auf `<Text>` mit entsprechendem fontWeight/Size. */
|
|
945
|
-
export type HeadingProps = {
|
|
946
|
-
readonly variant?: "page" | "section";
|
|
947
|
-
readonly children: ReactNode;
|
|
948
|
-
readonly testId?: string;
|
|
949
|
-
};
|
|
950
|
-
|
|
951
|
-
/** Modal-Dialog für Bestätigungen oder kompakte Sub-Forms. Web rendert
|
|
952
|
-
* Radix-Dialog (Focus-Trap, Esc-Schließen, Overlay-Click); Native
|
|
953
|
-
* würde ein Native-Modal nutzen. Apps öffnen den Dialog über einen
|
|
954
|
-
* External-State (`open` + `onOpenChange`); Confirm-Action läuft
|
|
955
|
-
* durch `onConfirm`, Cancel klappt zu via `onOpenChange(false)`.
|
|
956
|
-
*
|
|
957
|
-
* Variant `danger` markiert destruktive Bestätigungen visuell
|
|
958
|
-
* (rote Confirm-Button-Klasse), `default` für neutrale Dialoge. */
|
|
959
|
-
export type DialogProps = {
|
|
960
|
-
readonly open: boolean;
|
|
961
|
-
readonly onOpenChange: (open: boolean) => void;
|
|
962
|
-
readonly title: string;
|
|
963
|
-
/** Optional Beschreibung — typisch ein Satz der die Konsequenz
|
|
964
|
-
* erklärt ("Diese Aktion lässt sich nicht rückgängig machen."). */
|
|
965
|
-
readonly description?: string;
|
|
966
|
-
/** Confirm-Button-Label. Default kommt aus i18n
|
|
967
|
-
* (`kumiko.dialog.confirm`). */
|
|
968
|
-
readonly confirmLabel?: string;
|
|
969
|
-
/** Cancel-Button-Label. Default `kumiko.dialog.cancel`. */
|
|
970
|
-
readonly cancelLabel?: string;
|
|
971
|
-
/** `default` = Confirm primary, `danger` = Confirm danger. */
|
|
972
|
-
readonly variant?: "default" | "danger";
|
|
973
|
-
/** Wird gefeuert wenn der User Confirm drückt. Async-Funktion ist
|
|
974
|
-
* ok — Dialog setzt automatisch loading-State, ruft danach
|
|
975
|
-
* onOpenChange(false). */
|
|
976
|
-
readonly onConfirm: () => void | Promise<void>;
|
|
977
|
-
/** Optional zusätzlicher Inhalt zwischen description und Buttons
|
|
978
|
-
* (z.B. ein Input wenn der Dialog auch Eingaben sammelt). */
|
|
979
|
-
readonly children?: ReactNode;
|
|
980
|
-
readonly testId?: string;
|
|
981
|
-
};
|
|
982
|
-
|
|
983
|
-
/** Bare content shell for hosting an existing self-contained form/widget
|
|
984
|
-
* (own submit/cancel buttons) inside a modal overlay — unlike `Dialog`,
|
|
985
|
-
* it renders no footer buttons of its own. Web renders the same
|
|
986
|
-
* focus-trapped Radix overlay as `Dialog`, just without the button row. */
|
|
987
|
-
export type ModalProps = {
|
|
988
|
-
readonly open: boolean;
|
|
989
|
-
readonly onOpenChange: (open: boolean) => void;
|
|
990
|
-
/** Screen-reader-only accessible title (required by the underlying
|
|
991
|
-
* dialog primitive) — not shown visually since the hosted content
|
|
992
|
-
* is expected to render its own heading. */
|
|
993
|
-
readonly title: string;
|
|
994
|
-
readonly children: ReactNode;
|
|
995
|
-
readonly testId?: string;
|
|
996
|
-
};
|
|
997
|
-
|
|
998
|
-
/** Side panel for hosting an existing self-contained form/widget (own
|
|
999
|
-
* submit/cancel buttons) without leaving the underlying screen — e.g. a
|
|
1000
|
-
* toolbar action's `kind: "drawer"` mounts an actionForm here instead of
|
|
1001
|
-
* navigating to a full page (fw#2225). Same "bare content shell, no
|
|
1002
|
-
* footer buttons of its own" contract as `Modal`, slide-in instead of
|
|
1003
|
-
* centered overlay. */
|
|
1004
|
-
export type DrawerProps = {
|
|
1005
|
-
readonly open: boolean;
|
|
1006
|
-
readonly onOpenChange: (open: boolean) => void;
|
|
1007
|
-
readonly title?: string;
|
|
1008
|
-
readonly children: ReactNode;
|
|
1009
|
-
readonly testId?: string;
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
/** Image lightbox — full-size preview on click. Web renders Radix overlay;
|
|
1013
|
-
* trigger (thumbnail) and open state live in the app. */
|
|
1014
|
-
export type LightboxProps = {
|
|
1015
|
-
readonly open: boolean;
|
|
1016
|
-
readonly onOpenChange: (open: boolean) => void;
|
|
1017
|
-
readonly src: string;
|
|
1018
|
-
readonly alt: string;
|
|
1019
|
-
readonly testId?: string;
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
|
-
/** Source-badge for one cascade step (User / Tenant / System / …).
|
|
1023
|
-
* Used inline next to a config value to indicate where it came from.
|
|
1024
|
-
* Requires a LocaleProvider above it (labels run through useTranslation)
|
|
1025
|
-
* — createKumikoApp wires one; standalone consumers must wrap. */
|
|
1026
|
-
export type ConfigSourceBadgeProps = {
|
|
1027
|
-
readonly source: ConfigValueSource;
|
|
1028
|
-
/** Scope of the hosting screen. Non-system screens collapse sources
|
|
1029
|
-
* ABOVE their scope (system-row/app-override/computed) into the neutral
|
|
1030
|
-
* default badge — operator internals stay invisible to tenants, same
|
|
1031
|
-
* rule as ConfigCascadeView's toDisplayLevels. */
|
|
1032
|
-
readonly screenScope?: ConfigScope;
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
/** Collapsible cascade-view for a config-edit field. Split into two slots
|
|
1036
|
-
* (#429): "trigger" is the collapsed ▶ + source + value line for the label
|
|
1037
|
-
* row; "panel" is the expanded cascade + reset shown below the input. In
|
|
1038
|
-
* split mode the screen owns the `expanded` state (both slots share it);
|
|
1039
|
-
* without `slot` the component renders both with its own state. */
|
|
1040
|
-
export type ConfigCascadeViewProps = {
|
|
1041
|
-
readonly cascade: ConfigCascade;
|
|
1042
|
-
readonly screenScope: ConfigScope;
|
|
1043
|
-
readonly onReset?: (key: string, scope: ConfigScope) => void;
|
|
1044
|
-
readonly qualifiedKey?: string;
|
|
1045
|
-
readonly slot?: "trigger" | "panel";
|
|
1046
|
-
readonly expanded?: boolean;
|
|
1047
|
-
readonly onToggle?: () => void;
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
/** Card-Slots — benannte Inhalts-Regionen, alle optional. Neue Slots später =
|
|
1051
|
-
* rein additiv → Consumer müssen NIE migriert werden. `header` ist der Escape-
|
|
1052
|
-
* Hatch: ein roher Knoten ersetzt den Default-Header (title/subtitle/
|
|
1053
|
-
* headerActions) komplett, wenn die Convenience-Slots nicht reichen. */
|
|
1054
|
-
export type CardSlots = {
|
|
1055
|
-
readonly header?: ReactNode;
|
|
1056
|
-
readonly title?: ReactNode;
|
|
1057
|
-
readonly subtitle?: ReactNode;
|
|
1058
|
-
/** Free-form block content left of `headerActions`, below title/subtitle —
|
|
1059
|
-
* for a host-supplied header (projectionDetail `slots.header`) that must
|
|
1060
|
-
* share the action row instead of pushing actions into a row of their own. */
|
|
1061
|
-
readonly headerContent?: ReactNode;
|
|
1062
|
-
/** Rechtsbündiger Header-Slot (Range-Umschalter, Filter, Badge …). */
|
|
1063
|
-
readonly headerActions?: ReactNode;
|
|
1064
|
-
/** Footer-Inhalt, abgehoben (border-t). Default rechtsbündig. */
|
|
1065
|
-
readonly footer?: ReactNode;
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
/** Card-Options — Verhalten/Optik, ebenfalls additiv erweiterbar. */
|
|
1069
|
-
export type CardOptions = {
|
|
1070
|
-
/** Body-Padding (px-6 py-6). false = randlos (Tabelle/eigene Tiles). Default true. */
|
|
1071
|
-
readonly padded?: boolean;
|
|
1072
|
-
/** Eckenradius. Default "xl" (= Entity-Card). */
|
|
1073
|
-
readonly radius?: "lg" | "xl";
|
|
1074
|
-
/** Footer abgehoben mit border-t + bg-muted/30. Default true. */
|
|
1075
|
-
readonly footerBordered?: boolean;
|
|
1076
|
-
/** Joins the ancestor fillHeight flex chain (fw#2722/#2778 — a tabs-mode
|
|
1077
|
-
* relatedList table sizing to the panel instead of the page) instead of
|
|
1078
|
-
* normal document-flow height. Default false. */
|
|
1079
|
-
readonly fillHeight?: boolean;
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
/** Generische Card-Chrome (border/bg/radius/shadow + Header/Body/Footer) als
|
|
1083
|
-
* Primitive — eine Quelle für alle Card-Flächen, statt pro App nachgebaut.
|
|
1084
|
-
* Slot- + Options-basiert, damit der Contract stabil bleibt. */
|
|
1085
|
-
export type CardProps = {
|
|
1086
|
-
readonly slots?: CardSlots;
|
|
1087
|
-
readonly options?: CardOptions;
|
|
1088
|
-
/** Body-Inhalt (Normalfall; Header/Footer kommen über slots). */
|
|
1089
|
-
readonly children?: ReactNode;
|
|
1090
|
-
readonly className?: string;
|
|
1091
|
-
readonly testId?: string;
|
|
1092
|
-
readonly dataAttributes?: DataAttributes;
|
|
1093
|
-
};
|
|
1094
|
-
|
|
1095
|
-
/** Determinate progress bar (e.g. wizard step progress). `value` is a
|
|
1096
|
-
* 0..1 fraction, not a percentage — implementations scale for display.
|
|
1097
|
-
* `tone` controls the fill color; omitted defaults to "default" (backward
|
|
1098
|
-
* compatible with callers predating this field). */
|
|
1099
|
-
export type ProgressTone = "default" | "success" | "warn" | "danger";
|
|
1100
|
-
|
|
1101
|
-
export type ProgressProps = {
|
|
1102
|
-
readonly value: number;
|
|
1103
|
-
readonly tone?: ProgressTone;
|
|
1104
|
-
readonly testId?: string;
|
|
1105
|
-
};
|
|
1106
|
-
|
|
1107
|
-
/** Wizard step overview — numbered chips, one per step label, with the
|
|
1108
|
-
* active step highlighted. With `onStepSelect`, completed steps (index
|
|
1109
|
-
* below `currentIndex`) become buttons for going back; forward jumps stay
|
|
1110
|
-
* impossible because step-jump validation is a separate concern
|
|
1111
|
-
* (kumiko-framework#1966). Implementations render a
|
|
1112
|
-
* narrow-viewport fallback showing `compactLabel` instead (caller
|
|
1113
|
-
* supplies it pre-translated, e.g. "Step 2 of 5 · Industry") — which of
|
|
1114
|
-
* the two is visible is a responsive layout choice owned by the
|
|
1115
|
-
* implementation, not this contract. */
|
|
1116
|
-
export type StepBarProps = {
|
|
1117
|
-
readonly steps: readonly string[];
|
|
1118
|
-
readonly currentIndex: number;
|
|
1119
|
-
readonly compactLabel: string;
|
|
1120
|
-
readonly onStepSelect?: (index: number) => void;
|
|
1121
|
-
/** "label" (default) swaps the step row for `compactLabel` on narrow
|
|
1122
|
-
* viewports; "steps" keeps the row there too — for short wizards whose
|
|
1123
|
-
* labels fit a phone and whose done steps must stay tappable. */
|
|
1124
|
-
readonly narrowLayout?: "label" | "steps";
|
|
1125
|
-
readonly testId?: string;
|
|
1126
|
-
readonly compactTestId?: string;
|
|
1127
|
-
};
|
|
1128
|
-
|
|
1129
|
-
/** Wraps a single wizard step so it stays mounted but inert while off-
|
|
1130
|
-
* screen — `hidden` visually hides it, and implementations that have a
|
|
1131
|
-
* native constraint-validation concept (web: `<fieldset disabled>`, spec-
|
|
1132
|
-
* guaranteed to also bar descendants from participating) must bar it too,
|
|
1133
|
-
* or a `type="submit"` step-advance button triggers the browser's
|
|
1134
|
-
* full-form check against fields on unvisited, unfocusable steps
|
|
1135
|
-
* (kumiko-framework#1976). Implementations without that concept can gate
|
|
1136
|
-
* on `hidden` alone. */
|
|
1137
|
-
export type WizardStepGroupProps = {
|
|
1138
|
-
readonly hidden: boolean;
|
|
1139
|
-
readonly children: ReactNode;
|
|
1140
|
-
};
|
|
1141
|
-
|
|
1142
|
-
/** Tab strip for a projectionDetail `layout.mode: "tabs"`. Renders only the
|
|
1143
|
-
* strip, not the content — the caller mounts the active section separately.
|
|
1144
|
-
* Keyboard/ARIA (role=tablist/tab, arrow-key navigation) is the
|
|
1145
|
-
* implementation's job. */
|
|
1146
|
-
export type TabsProps = {
|
|
1147
|
-
readonly items: readonly {
|
|
1148
|
-
readonly id: string;
|
|
1149
|
-
readonly label: string;
|
|
1150
|
-
/** Shown dimmed after the label (e.g. an open-items count). Omit for
|
|
1151
|
-
* tabs without a counter. */
|
|
1152
|
-
readonly count?: number;
|
|
1153
|
-
}[];
|
|
1154
|
-
readonly activeId: string;
|
|
1155
|
-
readonly onSelect: (id: string) => void;
|
|
1156
|
-
readonly testId?: string;
|
|
1157
|
-
};
|
|
1158
|
-
|
|
1159
|
-
export type StatusTone = "ok" | "warn" | "bad" | "critical" | "muted";
|
|
1160
|
-
|
|
1161
|
-
/** Status pill. `value` is the raw column value — tone-mapping is the app's job. */
|
|
1162
|
-
export type StatusBadgeProps = {
|
|
1163
|
-
readonly value: string;
|
|
1164
|
-
readonly tone?: StatusTone;
|
|
1165
|
-
readonly testId?: string;
|
|
1166
|
-
};
|
|
1167
|
-
|
|
1168
|
-
// Status value -> StatusTone heuristic, shared by every surface that shows a
|
|
1169
|
-
// raw status value without a tone of its own (projectionDetail header badge,
|
|
1170
|
-
// list cells). Covers the common status vocabularies; unknown values stay
|
|
1171
|
-
// undefined so the caller keeps its own neutral default.
|
|
1172
|
-
const STATUS_TONE_BY_VALUE: Readonly<Record<string, StatusTone>> = {
|
|
1173
|
-
ok: "ok",
|
|
1174
|
-
active: "ok",
|
|
1175
|
-
done: "ok",
|
|
1176
|
-
complete: "ok",
|
|
1177
|
-
completed: "ok",
|
|
1178
|
-
paid: "ok",
|
|
1179
|
-
approved: "ok",
|
|
1180
|
-
published: "ok",
|
|
1181
|
-
success: "ok",
|
|
1182
|
-
pending: "warn",
|
|
1183
|
-
processing: "warn",
|
|
1184
|
-
review: "warn",
|
|
1185
|
-
"in-review": "warn",
|
|
1186
|
-
waiting: "warn",
|
|
1187
|
-
open: "warn",
|
|
1188
|
-
draft: "warn",
|
|
1189
|
-
failed: "bad",
|
|
1190
|
-
error: "bad",
|
|
1191
|
-
overdue: "bad",
|
|
1192
|
-
rejected: "bad",
|
|
1193
|
-
blocked: "bad",
|
|
1194
|
-
critical: "bad",
|
|
1195
|
-
};
|
|
1196
|
-
|
|
1197
|
-
export function statusToneForValue(value: string): StatusTone | undefined {
|
|
1198
|
-
const slug = value
|
|
1199
|
-
.trim()
|
|
1200
|
-
.toLowerCase()
|
|
1201
|
-
.replace(/[\s_]+/g, "-");
|
|
1202
|
-
// Own-key check: the value comes from row data, and a plain object lookup
|
|
1203
|
-
// would hand back `Object.prototype.constructor` & friends as a "tone".
|
|
1204
|
-
return Object.hasOwn(STATUS_TONE_BY_VALUE, slug) ? STATUS_TONE_BY_VALUE[slug] : undefined;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
/** Compact label/value tile (record-detail metrics band). `testId` is the
|
|
1208
|
-
* tile's own id — the impl derives `${testId}-label`/`${testId}-value` for
|
|
1209
|
-
* the two rendered nodes, so a caller only ever needs to know the base id. */
|
|
1210
|
-
export type MetricProps = {
|
|
1211
|
-
readonly label: string;
|
|
1212
|
-
readonly value: string;
|
|
1213
|
-
readonly testId?: string;
|
|
1214
|
-
/** Click handler — set when the metric declares a `navigate` target
|
|
1215
|
-
* (see `MetricSpec`). Omitted for a plain, non-interactive metric. */
|
|
1216
|
-
readonly onPress?: () => void;
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
/** Structured JSON display (audit payload/metadata, job logs, unsupported
|
|
1220
|
-
* jsonb/embedded field fallback). Takes the raw value, not a pre-stringified
|
|
1221
|
-
* one — the implementation owns `JSON.stringify` (and must not throw on
|
|
1222
|
-
* circular refs / BigInt / other non-serializable input). `indent` mirrors
|
|
1223
|
-
* `JSON.stringify`'s space-count param, default 2. */
|
|
1224
|
-
export type JsonViewProps = {
|
|
1225
|
-
readonly value: unknown;
|
|
1226
|
-
readonly indent?: number;
|
|
1227
|
-
readonly testId?: string;
|
|
1228
|
-
};
|
|
1229
|
-
|
|
1230
|
-
/** One item in an `ActionOverflowMenu` (A7: header/row actions beyond the
|
|
1231
|
-
* primary one collapse into this menu instead of a wall of buttons). */
|
|
1232
|
-
export type ActionMenuItemSpec = {
|
|
1233
|
-
readonly id: string;
|
|
1234
|
-
readonly label: string;
|
|
1235
|
-
/** Icon name, resolved by the implementation (same names as row actions). */
|
|
1236
|
-
readonly icon?: IconKey;
|
|
1237
|
-
readonly disabled?: boolean;
|
|
1238
|
-
readonly variant?: "default" | "danger";
|
|
1239
|
-
readonly onSelect: () => void;
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
|
-
export type ActionOverflowMenuProps = {
|
|
1243
|
-
readonly items: readonly ActionMenuItemSpec[];
|
|
1244
|
-
/** aria-label for the three-dots trigger. */
|
|
1245
|
-
readonly label: string;
|
|
1246
|
-
readonly testId?: string;
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
|
-
/** One revealed value on a `secretMint` screen's confirm phase (fw#2548). */
|
|
1250
|
-
export type SecretRevealValue = {
|
|
1251
|
-
readonly label: string;
|
|
1252
|
-
readonly value: string;
|
|
1253
|
-
readonly copyable: boolean;
|
|
1254
|
-
readonly multiline: boolean;
|
|
1255
|
-
/** Render `value` as a scannable QR code instead of monospaced text — for
|
|
1256
|
-
* an otpauth://-style enrollment URI. Platforms without a QR-capable
|
|
1257
|
-
* SecretReveal primitive fall back to the monospaced display. */
|
|
1258
|
-
readonly qr?: boolean;
|
|
1259
|
-
};
|
|
1260
|
-
|
|
1261
|
-
/** One-time secret reveal — mint-form confirm phase. Renders each value
|
|
1262
|
-
* monospaced (multiline splits it one line per entry, for recovery codes),
|
|
1263
|
-
* with a per-value copy control when `copyable`. */
|
|
1264
|
-
export type SecretRevealProps = {
|
|
1265
|
-
readonly values: readonly SecretRevealValue[];
|
|
1266
|
-
readonly copyLabel: string;
|
|
1267
|
-
readonly copiedLabel: string;
|
|
1268
|
-
readonly testId?: string;
|
|
1269
|
-
};
|
|
1270
|
-
|
|
1271
|
-
/** Thumb-reach action row for custom screens: pinned to the bottom edge on
|
|
1272
|
-
* narrow viewports (above the device's safe-area inset), inline otherwise.
|
|
1273
|
-
* `children` are the forward actions and share the remaining width; `back`
|
|
1274
|
-
* renders an icon-only back control in front of them. */
|
|
1275
|
-
export type StickyActionBarProps = {
|
|
1276
|
-
readonly children: ReactNode;
|
|
1277
|
-
readonly back?: { readonly onBack: () => void; readonly label: string };
|
|
1278
|
-
readonly testId?: string;
|
|
1279
|
-
};
|
|
1280
|
-
|
|
1281
|
-
/** Copies `text` to the clipboard; the label flips to `copiedLabel` once
|
|
1282
|
-
* the platform confirmed the write. */
|
|
1283
|
-
export type CopyButtonProps = {
|
|
1284
|
-
readonly text: string;
|
|
1285
|
-
readonly label: string;
|
|
1286
|
-
readonly copiedLabel: string;
|
|
1287
|
-
readonly variant?: "primary" | "secondary";
|
|
1288
|
-
/** Layout extras — web merges via cn(), native impls ignore it. */
|
|
1289
|
-
readonly className?: string;
|
|
1290
|
-
readonly testId?: string;
|
|
1291
|
-
};
|
|
1292
|
-
|
|
1293
|
-
/** Hands `text` to another app. "whatsapp" opens a WhatsApp chat prefilled
|
|
1294
|
-
* with the text (to `phone` when given, otherwise the contact picker);
|
|
1295
|
-
* "system" opens the platform share sheet and renders nothing where the
|
|
1296
|
-
* platform has none. */
|
|
1297
|
-
export type ShareButtonProps = {
|
|
1298
|
-
readonly text: string;
|
|
1299
|
-
readonly label: string;
|
|
1300
|
-
readonly variant?: "primary" | "secondary";
|
|
1301
|
-
/** Layout extras — web merges via cn(), native impls ignore it. */
|
|
1302
|
-
readonly className?: string;
|
|
1303
|
-
readonly testId?: string;
|
|
1304
|
-
} & (
|
|
1305
|
-
| { readonly target: "whatsapp"; readonly phone?: string }
|
|
1306
|
-
| { readonly target: "system"; readonly title?: string; readonly url?: string }
|
|
1307
|
-
);
|
|
1308
|
-
|
|
1309
|
-
export type PromoPanelAction =
|
|
1310
|
-
| { readonly label: string; readonly href: string; readonly testId?: string }
|
|
1311
|
-
| { readonly label: string; readonly onClick: () => void; readonly testId?: string };
|
|
1312
|
-
|
|
1313
|
-
/** Offer surface for upsells (e.g. "create an account"): its own tinted
|
|
1314
|
-
* panel with eyebrow, headline, benefit content and one call to action, so
|
|
1315
|
-
* an offer never reads like an info or error banner. */
|
|
1316
|
-
export type PromoPanelProps = {
|
|
1317
|
-
readonly title: string;
|
|
1318
|
-
readonly eyebrow?: string;
|
|
1319
|
-
readonly children?: ReactNode;
|
|
1320
|
-
readonly action?: PromoPanelAction;
|
|
1321
|
-
readonly testId?: string;
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
// ---- Core-Registry (Kumiko-eigene Primitives) ----
|
|
1325
|
-
|
|
1326
|
-
export type CorePrimitives = {
|
|
1327
|
-
readonly Button: ComponentType<ButtonProps>;
|
|
1328
|
-
readonly Banner: ComponentType<BannerProps>;
|
|
1329
|
-
readonly Field: ComponentType<FieldProps>;
|
|
1330
|
-
readonly Input: ComponentType<InputProps>;
|
|
1331
|
-
readonly DataTable: ComponentType<DataTableProps>;
|
|
1332
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1333
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1334
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1335
|
-
readonly EmbeddedListInput?: ComponentType<EmbeddedListInputProps>;
|
|
1336
|
-
readonly Form: ComponentType<FormProps>;
|
|
1337
|
-
readonly Section: ComponentType<SectionProps>;
|
|
1338
|
-
readonly Card: ComponentType<CardProps>;
|
|
1339
|
-
readonly Grid: ComponentType<GridProps>;
|
|
1340
|
-
readonly GridCell: ComponentType<GridCellProps>;
|
|
1341
|
-
readonly Text: ComponentType<TextProps>;
|
|
1342
|
-
readonly Heading: ComponentType<HeadingProps>;
|
|
1343
|
-
readonly Dialog: ComponentType<DialogProps>;
|
|
1344
|
-
readonly Modal: ComponentType<ModalProps>;
|
|
1345
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1346
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1347
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1348
|
-
readonly Drawer?: ComponentType<DrawerProps>;
|
|
1349
|
-
readonly Lightbox: ComponentType<LightboxProps>;
|
|
1350
|
-
readonly ConfigSourceBadge: ComponentType<ConfigSourceBadgeProps>;
|
|
1351
|
-
readonly ConfigCascadeView: ComponentType<ConfigCascadeViewProps>;
|
|
1352
|
-
readonly Link: ComponentType<LinkProps>;
|
|
1353
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1354
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1355
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1356
|
-
readonly Progress?: ComponentType<ProgressProps>;
|
|
1357
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1358
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1359
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1360
|
-
readonly StepBar?: ComponentType<StepBarProps>;
|
|
1361
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1362
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1363
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1364
|
-
readonly WizardStepGroup?: ComponentType<WizardStepGroupProps>;
|
|
1365
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1366
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1367
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1368
|
-
readonly Tabs?: ComponentType<TabsProps>;
|
|
1369
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1370
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1371
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1372
|
-
readonly StatusBadge?: ComponentType<StatusBadgeProps>;
|
|
1373
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1374
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1375
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1376
|
-
readonly Metric?: ComponentType<MetricProps>;
|
|
1377
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1378
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1379
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1380
|
-
readonly JsonView?: ComponentType<JsonViewProps>;
|
|
1381
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1382
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1383
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1384
|
-
readonly FillContainer?: ComponentType<FillContainerProps>;
|
|
1385
|
-
/** Optional: without an implementation, callers with >2 header/row
|
|
1386
|
-
* actions fall back to today's all-buttons-inline rendering (A7). */
|
|
1387
|
-
readonly ActionOverflowMenu?: ComponentType<ActionOverflowMenuProps>;
|
|
1388
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1389
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1390
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1391
|
-
readonly SecretReveal?: ComponentType<SecretRevealProps>;
|
|
1392
|
-
/** Optional (unlike the other Core-Primitives) so existing partial
|
|
1393
|
-
* CorePrimitives mocks in tests keep compiling — additive rollout of
|
|
1394
|
-
* a new primitive shouldn't force every test double to grow a stub. */
|
|
1395
|
-
readonly StickyActionBar?: ComponentType<StickyActionBarProps>;
|
|
1396
|
-
/** Optional — additive rollout, see StickyActionBar. */
|
|
1397
|
-
readonly CopyButton?: ComponentType<CopyButtonProps>;
|
|
1398
|
-
/** Optional — additive rollout, see StickyActionBar. */
|
|
1399
|
-
readonly ShareButton?: ComponentType<ShareButtonProps>;
|
|
1400
|
-
/** Optional — additive rollout, see StickyActionBar. */
|
|
1401
|
-
readonly PromoPanel?: ComponentType<PromoPanelProps>;
|
|
1402
|
-
};
|
|
1403
|
-
|
|
1404
|
-
/** Offene Extension-Zone für App-eigene Primitives. Devs erweitern
|
|
1405
|
-
* dieses Interface via TypeScript Module-Augmentation:
|
|
1406
|
-
*
|
|
1407
|
-
* declare module "@cosmicdrift/kumiko-renderer" {
|
|
1408
|
-
* interface AppPrimitives {
|
|
1409
|
-
* Chip: ComponentType<ChipProps>;
|
|
1410
|
-
* Accordion: ComponentType<AccordionProps>;
|
|
1411
|
-
* }
|
|
1412
|
-
* }
|
|
1413
|
-
*
|
|
1414
|
-
* Nach der Augmentation tauchen die Keys in `PrimitivesRegistry`,
|
|
1415
|
-
* `createKumikoApp({ primitives: { Chip, Accordion } })`, und
|
|
1416
|
-
* `usePrimitives().Chip` auf — TypeScript-gestützt. Kumikos eigene
|
|
1417
|
-
* Components nutzen ausschließlich `CorePrimitives`; App-Primitives
|
|
1418
|
-
* sind ausschließlich für Dev-Code (Custom-Screens, Shell, eigene
|
|
1419
|
-
* Components). */
|
|
1420
|
-
// biome-ignore lint/suspicious/noEmptyInterface: extension point for module augmentation
|
|
1421
|
-
export interface AppPrimitives {}
|
|
1422
|
-
|
|
1423
|
-
export type PrimitivesRegistry = CorePrimitives & AppPrimitives;
|
|
1424
|
-
|
|
1425
|
-
// ---- Context + Provider + Hook ----
|
|
1426
|
-
|
|
1427
|
-
const PrimitivesContext = createContext<PrimitivesRegistry | undefined>(undefined);
|
|
1428
|
-
|
|
1429
|
-
export type PrimitivesProviderProps = {
|
|
1430
|
-
readonly children: ReactNode;
|
|
1431
|
-
readonly value: PrimitivesRegistry;
|
|
1432
|
-
};
|
|
1433
|
-
|
|
1434
|
-
export function PrimitivesProvider({ children, value }: PrimitivesProviderProps): ReactNode {
|
|
1435
|
-
return <PrimitivesContext.Provider value={value}>{children}</PrimitivesContext.Provider>;
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
export function usePrimitives(): PrimitivesRegistry {
|
|
1439
|
-
const registry = useContext(PrimitivesContext);
|
|
1440
|
-
if (registry === undefined) {
|
|
1441
|
-
throw new Error(
|
|
1442
|
-
"usePrimitives: no <PrimitivesProvider> mounted. Wrap your app in one (createKumikoApp does this for you with defaultPrimitives from @cosmicdrift/kumiko-renderer-web).",
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
return registry;
|
|
1446
|
-
}
|