@cosmicdrift/kumiko-renderer 0.326.1 → 0.328.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,7 @@ import type {
40
40
  FormWidth,
41
41
  IconKey,
42
42
  NavIconKey,
43
+ SelectOptionTone,
43
44
  } from "@cosmicdrift/kumiko-framework/ui-types";
44
45
  import type {
45
46
  FieldIssue,
@@ -91,8 +92,9 @@ export type ButtonProps = {
91
92
  * becomes visually; the renderers use "primary" for Save, "danger" for
92
93
  * Delete, "secondary" for a Confirm state, "link" for inline actions in
93
94
  * 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";
95
+ * destructive action as red text instead of a red fill, "ghost" for a
96
+ * low-emphasis primary-colored text action beside a filled primary. */
97
+ readonly variant?: "primary" | "secondary" | "danger" | "link" | "danger-ghost" | "ghost";
96
98
  /** Größe — default="md". "sm" für kompakte Inline-Aktionen (Toolbar,
97
99
  * Listen-Zeilen), "icon" für quadratische Icon-only-Buttons. */
98
100
  readonly size?: "sm" | "md" | "icon";
@@ -130,8 +132,10 @@ export type ButtonProps = {
130
132
  export type LinkProps = {
131
133
  readonly href: string;
132
134
  readonly variant?: "default" | "button" | "muted";
133
- /** `_blank` setzt in der Web-Impl automatisch rel="noreferrer". */
134
- readonly target?: "_blank";
135
+ /** `_blank` makes the web impl set `rel="noopener noreferrer"` unless
136
+ * `rel` is set explicitly. */
137
+ readonly target?: "_blank" | "_self" | "_parent" | "_top";
138
+ readonly rel?: string;
135
139
  /** Layout-Zusätze (self-center, text-xs) — Web merged via cn(),
136
140
  * Native-Impls ignorieren es (Präzedenz: CardProps.className). */
137
141
  readonly className?: string;
@@ -184,6 +188,10 @@ export type FieldProps = {
184
188
  * `htmlFor` (no separate `aria-label` needed). For tables/grids whose
185
189
  * column header already carries the label for each row. */
186
190
  readonly hideLabel?: boolean;
191
+ /** Marks the field as edited relative to its saved value — the label row
192
+ * shows a "changed" marker. Set only in edit mode for fields present in
193
+ * the form's change set. */
194
+ readonly changed?: boolean;
187
195
  readonly testId?: string;
188
196
  };
189
197
 
@@ -350,10 +358,19 @@ export type InputProps =
350
358
  * {value,label}-Form für DB-getragene Refs (Tier 2.7e-3). */
351
359
  readonly options:
352
360
  | readonly string[]
353
- | readonly { readonly value: string; readonly label: string }[];
361
+ | readonly {
362
+ readonly value: string;
363
+ readonly label: string;
364
+ /** Second line under the label; shown by `radioVariant="card"`. */
365
+ readonly description?: string;
366
+ }[];
354
367
  readonly disabled?: boolean;
355
368
  readonly required?: boolean;
356
369
  readonly hasError?: boolean;
370
+ /** `"card"` renders the radio group as bordered cards with label plus
371
+ * optional description per option and always uses the radio group,
372
+ * regardless of option count or `display`. Default `"list"`. */
373
+ readonly radioVariant?: "list" | "card";
357
374
  /** Requested presentation. `"radio"` renders the options as a visible
358
375
  * radio group (WAI-ARIA radiogroup, one click per choice),
359
376
  * `"dropdown"` renders the combobox. Omitted = the implementation
@@ -552,6 +569,9 @@ export type DataTableRowAction = {
552
569
  readonly onTrigger: (row: ListRowViewModel) => Promise<void> | void;
553
570
  /** Conditional Visibility pro Row (z.B. "Start" nur wenn status==="scheduled"). */
554
571
  readonly isVisible?: (row: ListRowViewModel) => boolean;
572
+ /** This action is what a click on the row does. The card layout drops it
573
+ * from the row menu, since the whole card is already the tap target. */
574
+ readonly rowClick?: boolean;
555
575
  /** Resolved icon (author `RowAction.icon` or the id-derived default) —
556
576
  * drives both the icon-left-of-text render and the icon-only collapse
557
577
  * rule (see `shouldRenderActionsIconOnly`). */
@@ -612,13 +632,13 @@ export type DataTableProps = {
612
632
  * Form (Labels + onTrigger schon verdrahtet); DataTable kümmert
613
633
  * sich nur um Render + Confirm-Dialog. */
614
634
  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. */
635
+ /** How the row-action column renders:
636
+ * - `"adaptive"` (default): with `onRowClick` all actions go in the kebab;
637
+ * without it the primary action is a link button and the rest go in the
638
+ * kebab (a single action gets no kebab).
639
+ * - `"inline"`: ALWAYS inline buttons, left-aligned, even with >2 (no
640
+ * kebab). Actions then sit at the same x-position across all rows
641
+ * (no drifting with label width) and all lists in an app look alike. */
622
642
  readonly rowActionMode?: DataTableRowActionMode;
623
643
  /** Custom Empty-State-Inhalt (z. B. Icon + Heading + CTA-Button).
624
644
  * Default-Renderer rahmt ihn in einer dashed-border Box. */
@@ -657,7 +677,16 @@ export type DataTableProps = {
657
677
  readonly limit: number;
658
678
  readonly total: number;
659
679
  readonly onPageChange: (next: number) => void;
680
+ /** Offered page sizes; with `onPageSizeChange` the footer shows a
681
+ * "N pro Seite" select. */
682
+ readonly pageSizeOptions?: readonly number[];
683
+ readonly onPageSizeChange?: (next: number) => void;
660
684
  };
685
+ /** Hint at the left of the toolbar (relatedList tab description). */
686
+ readonly toolbarDescription?: string;
687
+ /** Noun for the footer count ("1–19 of 19 leases", "1 position"),
688
+ * resolved for the given count. Omitted = generic "entries" wording. */
689
+ readonly itemNoun?: (count: number) => string;
661
690
  /** Infinite-Scroll Callback. Wenn gesetzt, rendert der Renderer einen
662
691
  * Bottom-Sentinel und ruft `onReachEnd` wenn der ins Viewport rückt
663
692
  * (Web: IntersectionObserver). Caller verwaltet accumulation +
@@ -693,8 +722,12 @@ export type DataTableProps = {
693
722
  * leave dead space below it (a tab panel, fw#2722). Requires the same
694
723
  * flex-fill chain FormProps.fillHeight sets up above it; without that
695
724
  * 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. */
725
+ * has no effect outside a sized flex-col ancestor). When true the table
726
+ * also takes the full board layout: the wrapper fills its container
727
+ * (`h-full`), toolbar and footer (pager or entry count) stay pinned
728
+ * outside the scrolling body, no card frame and no outer padding.
729
+ * Default false: unchanged document-flow table that grows with its
730
+ * content, pager below the last row. */
698
731
  readonly scrollBody?: boolean;
699
732
  /** Uses the shared screen padding (wider bottom inset) instead of the
700
733
  * table's symmetric embedded inset — for a table that IS the screen body
@@ -800,6 +833,13 @@ export type EmbeddedListInputProps = {
800
833
 
801
834
  export type { FormWidth };
802
835
 
836
+ /** One entry of a form's "on this page" navigation: `id` matches the
837
+ * `SectionProps.id` of the section it scrolls to. */
838
+ export type FormSectionNavItem = {
839
+ readonly id: string;
840
+ readonly title: string;
841
+ };
842
+
803
843
  /** Submit wrapper. Web: `<form onSubmit>`, native: a View that triggers an
804
844
  * onSubmit callback via button press. `onSubmit` gets an abstract
805
845
  * signature (no FormEvent) so native impls can fill it meaningfully.
@@ -843,8 +883,10 @@ export type FormProps = {
843
883
  /** Sizes the form to fill its container's height (instead of the page's
844
884
  * natural content height) so a single scrolling child — a lone
845
885
  * 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
886
+ * the whole page (fw#2722). Set by RenderEdit for a lone relatedList tab
887
+ * section and for screens that fill the shell height (together with
888
+ * `stickyActions`: sections scroll, footer stays pinned without
889
+ * `position: fixed`); every other caller leaves it unset and keeps normal
848
890
  * document-flow height. Native impls may ignore this prop (already a
849
891
  * bounded viewport there). */
850
892
  readonly fillHeight?: boolean;
@@ -857,12 +899,31 @@ export type FormProps = {
857
899
  * card chrome. Native impls may ignore this prop (no card chrome there
858
900
  * to begin with). */
859
901
  readonly chromeless?: boolean;
902
+ /** Set by RenderEdit for entityEdit/actionForm screens that fill the shell
903
+ * height: the form renders without a surrounding card, in a padded scroll
904
+ * surface with a fixed-width form column. Requires `fillHeight`; projection
905
+ * detail screens (which also fill the height) do not set it. Native impls
906
+ * may ignore this prop. */
907
+ readonly screenForm?: boolean;
908
+ /** Number of unsaved changes, shown in the pinned footer. Omitted in create
909
+ * mode and when nothing changed — the footer then shows no status. */
910
+ readonly unsavedCount?: number;
911
+ /** Drawer-hosted forms: context box above the fields. */
912
+ readonly summary?: { readonly title: string; readonly subtitle?: string };
913
+ /** Titled sections for the "on this page" navigation next to a
914
+ * `screenForm`. The web impl shows it only with three or more entries. */
915
+ readonly sectionNav?: readonly FormSectionNavItem[];
916
+ /** Leading column next to the scroll surface of a `screenForm` (wizard step
917
+ * navigation). Native impls may ignore this prop. */
918
+ readonly sideRail?: ReactNode;
860
919
  };
861
920
 
862
921
  /** Titled Gruppe von Feldern. Web: `<fieldset>` + `<legend>`, Native:
863
922
  * View mit Header-Text. Native-Impls können den Title als Accordion
864
923
  * oder Collapsible rendern. */
865
924
  export type SectionProps = {
925
+ /** DOM id — the scroll target of a form's "on this page" navigation. */
926
+ readonly id?: string;
866
927
  /** Optional — ohne Titel rendert die Section nur die Gruppierung
867
928
  * (kein Header). RenderEdit lässt ihn weg, wenn er den Screen-Titel
868
929
  * der Action-Bar 1:1 wiederholen würde. */
@@ -900,6 +961,11 @@ export type SectionProps = {
900
961
  export type FillContainerProps = {
901
962
  readonly children: ReactNode;
902
963
  readonly testId?: string;
964
+ /** Claims the remaining height of the flex chain (`flex-1`) instead of
965
+ * sizing to its content, so a footer inside the child sits at the bottom
966
+ * edge even with few rows. Set only when the screen fills the shell
967
+ * height. */
968
+ readonly grow?: boolean;
903
969
  };
904
970
 
905
971
  /** Columns-basiertes Layout. Web: CSS grid, Native: Flex-Wrap mit
@@ -919,12 +985,20 @@ export type GridProps = {
919
985
  * the grid grows with its content and never scrolls. Ignored when
920
986
  * `columns` is "auto". */
921
987
  readonly maxRows?: number;
988
+ /** Fields flow left to right in wrapping rows sized by `GridCell.width`
989
+ * instead of equal-width tracks (screen forms and drawers). */
990
+ readonly flow?: boolean;
922
991
  };
923
992
 
993
+ /** Width class of a field cell inside a `flow` grid: sized by what the field holds. */
994
+ export type FieldCellWidth = "text" | "number" | "money" | "date" | "select" | "full" | "auto";
995
+
924
996
  /** Span-Wrapper für ein Kind innerhalb eines Grid. Web: `style={{gridColumn: span N}}`,
925
997
  * Native: eigenes Width-Rechnen. */
926
998
  export type GridCellProps = {
927
999
  readonly span?: number;
1000
+ /** Only read inside a `flow` grid. */
1001
+ readonly width?: FieldCellWidth;
928
1002
  readonly children: ReactNode;
929
1003
  };
930
1004
 
@@ -970,6 +1044,10 @@ export type DialogProps = {
970
1044
  readonly cancelLabel?: string;
971
1045
  /** `default` = Confirm primary, `danger` = Confirm danger. */
972
1046
  readonly variant?: "default" | "danger";
1047
+ /** Which button takes focus on open. Default: Confirm for a bare dialog,
1048
+ * the browser/Radix default when `children` are present. `"cancel"` for
1049
+ * dialogs where the safe choice must be the one Enter triggers. */
1050
+ readonly initialFocus?: "confirm" | "cancel";
973
1051
  /** Wird gefeuert wenn der User Confirm drückt. Async-Funktion ist
974
1052
  * ok — Dialog setzt automatisch loading-State, ruft danach
975
1053
  * onOpenChange(false). */
@@ -1101,6 +1179,7 @@ export type ProgressTone = "default" | "success" | "warn" | "danger";
1101
1179
  export type ProgressProps = {
1102
1180
  readonly value: number;
1103
1181
  readonly tone?: ProgressTone;
1182
+ readonly ariaLabel?: string;
1104
1183
  readonly testId?: string;
1105
1184
  };
1106
1185
 
@@ -1122,6 +1201,12 @@ export type StepBarProps = {
1122
1201
  * viewports; "steps" keeps the row there too — for short wizards whose
1123
1202
  * labels fit a phone and whose done steps must stay tappable. */
1124
1203
  readonly narrowLayout?: "label" | "steps";
1204
+ /** "vertical" renders a step rail (from `lg`, with `compactLabel` as the
1205
+ * single line below it) instead of the chip row. Default "horizontal". */
1206
+ readonly orientation?: "horizontal" | "vertical";
1207
+ /** Vertical only: rail heading ("Step 4 of 6") and optional description. */
1208
+ readonly heading?: string;
1209
+ readonly description?: string;
1125
1210
  readonly testId?: string;
1126
1211
  readonly compactTestId?: string;
1127
1212
  };
@@ -1136,6 +1221,8 @@ export type StepBarProps = {
1136
1221
  * on `hidden` alone. */
1137
1222
  export type WizardStepGroupProps = {
1138
1223
  readonly hidden: boolean;
1224
+ /** Pads the step content (tab panels sit full-bleed under the tab strip). */
1225
+ readonly inset?: boolean;
1139
1226
  readonly children: ReactNode;
1140
1227
  };
1141
1228
 
@@ -1194,6 +1281,17 @@ const STATUS_TONE_BY_VALUE: Readonly<Record<string, StatusTone>> = {
1194
1281
  critical: "bad",
1195
1282
  };
1196
1283
 
1284
+ const STATUS_TONE_BY_OPTION_TONE: Readonly<Record<SelectOptionTone, StatusTone>> = {
1285
+ ok: "ok",
1286
+ warn: "warn",
1287
+ bad: "bad",
1288
+ neutral: "muted",
1289
+ };
1290
+
1291
+ export function statusToneForOptionTone(tone: SelectOptionTone): StatusTone {
1292
+ return STATUS_TONE_BY_OPTION_TONE[tone];
1293
+ }
1294
+
1197
1295
  export function statusToneForValue(value: string): StatusTone | undefined {
1198
1296
  const slug = value
1199
1297
  .trim()
@@ -1227,6 +1325,24 @@ export type JsonViewProps = {
1227
1325
  readonly testId?: string;
1228
1326
  };
1229
1327
 
1328
+ /** Band above the tabs of a record detail: optional subtitle line plus the
1329
+ * metrics as a definition list. `children` are `Metric` tiles. */
1330
+ export type MetricBandProps = {
1331
+ readonly subtitle?: ReactNode;
1332
+ readonly children: ReactNode;
1333
+ readonly testId?: string;
1334
+ };
1335
+
1336
+ /** Page title area rendered by the app shell's header instead of inside the
1337
+ * screen: `title` replaces the last breadcrumb, `status` sits right after it,
1338
+ * `actions` at the right edge. Renders nothing itself; without a shell slot
1339
+ * (see `usePageHeaderSlotAvailable`) callers keep their previous placement. */
1340
+ export type PageHeaderProps = {
1341
+ readonly title?: string;
1342
+ readonly status?: ReactNode;
1343
+ readonly actions?: ReactNode;
1344
+ };
1345
+
1230
1346
  /** One item in an `ActionOverflowMenu` (A7: header/row actions beyond the
1231
1347
  * primary one collapse into this menu instead of a wall of buttons). */
1232
1348
  export type ActionMenuItemSpec = {
@@ -1382,6 +1498,11 @@ export type CorePrimitives = {
1382
1498
  * CorePrimitives mocks in tests keep compiling — additive rollout of
1383
1499
  * a new primitive shouldn't force every test double to grow a stub. */
1384
1500
  readonly FillContainer?: ComponentType<FillContainerProps>;
1501
+ /** Optional — additive rollout, see StickyActionBar. Without it, header
1502
+ * content stays where the screen always rendered it. */
1503
+ readonly PageHeader?: ComponentType<PageHeaderProps>;
1504
+ /** Optional — additive rollout, see StickyActionBar. */
1505
+ readonly MetricBand?: ComponentType<MetricBandProps>;
1385
1506
  /** Optional: without an implementation, callers with >2 header/row
1386
1507
  * actions fall back to today's all-buttons-inline rendering (A7). */
1387
1508
  readonly ActionOverflowMenu?: ComponentType<ActionOverflowMenuProps>;
@@ -0,0 +1,3 @@
1
+ export function screenFillsHeight(screen: { readonly fillHeight?: boolean }): boolean {
2
+ return screen.fillHeight !== false;
3
+ }