@cosmicdrift/kumiko-renderer 0.384.0 → 0.386.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.
Files changed (98) hide show
  1. package/dist/app/action-form-body.d.ts +50 -0
  2. package/dist/app/action-form-body.js +275 -0
  3. package/dist/app/client-actions.js +2 -2
  4. package/dist/app/config-edit-body.d.ts +9 -0
  5. package/dist/app/config-edit-body.js +340 -0
  6. package/dist/app/entity-edit-prefill.d.ts +27 -0
  7. package/dist/app/entity-edit-prefill.js +377 -0
  8. package/dist/app/entity-edit-screen.d.ts +11 -0
  9. package/dist/app/entity-edit-screen.js +395 -0
  10. package/dist/app/entity-list-screen.d.ts +10 -0
  11. package/dist/app/entity-list-screen.js +547 -0
  12. package/dist/app/kumiko-screen.d.ts +1 -36
  13. package/dist/app/kumiko-screen.js +16 -2855
  14. package/dist/app/projection-detail-body.d.ts +12 -0
  15. package/dist/app/projection-detail-body.js +464 -0
  16. package/dist/app/projection-detail-shim.d.ts +1 -1
  17. package/dist/app/projection-detail-shim.js +5 -1
  18. package/dist/app/projection-list-body.d.ts +10 -0
  19. package/dist/app/projection-list-body.js +245 -0
  20. package/dist/app/projection-list-shim.d.ts +2 -1
  21. package/dist/app/projection-list-shim.js +4 -0
  22. package/dist/app/reference-facet-bridge.js +2 -2
  23. package/dist/app/row-actions.js +3 -4
  24. package/dist/app/screen-shared.d.ts +72 -0
  25. package/dist/app/screen-shared.js +325 -0
  26. package/dist/app/secret-mint-body.js +3 -3
  27. package/dist/components/edit-mounts.d.ts +33 -0
  28. package/dist/components/edit-mounts.js +53 -0
  29. package/dist/components/error-boundary.js +2 -2
  30. package/dist/components/reference-create-dialog.js +1 -1
  31. package/dist/components/related-list-section.js +29 -6
  32. package/dist/components/render-edit-helpers.d.ts +29 -0
  33. package/dist/components/render-edit-helpers.js +121 -0
  34. package/dist/components/render-edit.d.ts +1 -4
  35. package/dist/components/render-edit.js +41 -443
  36. package/dist/components/render-field.js +3 -4
  37. package/dist/components/render-list.d.ts +1 -1
  38. package/dist/components/render-list.js +2 -2
  39. package/dist/components/use-wizard-form-draft.d.ts +51 -0
  40. package/dist/components/use-wizard-form-draft.js +291 -0
  41. package/dist/dev-console.d.ts +3 -0
  42. package/dist/dev-console.js +15 -0
  43. package/dist/headless/contracts/primitives.js +14 -17
  44. package/dist/headless/format/index.js +3 -3
  45. package/dist/headless/view-model/edit.d.ts +1 -1
  46. package/dist/headless/view-model/edit.js +9 -16
  47. package/dist/headless/view-model/list.d.ts +1 -1
  48. package/dist/headless/view-model/list.js +3 -8
  49. package/dist/index.d.ts +2 -1
  50. package/dist/index.js +5 -5
  51. package/dist/live/error-mapping.js +3 -4
  52. package/dist/web/app/create-app.js +5 -8
  53. package/dist/web/app/dashboard-body.js +4 -599
  54. package/dist/web/app/dashboard-panels.d.ts +53 -0
  55. package/dist/web/app/dashboard-panels.js +373 -0
  56. package/dist/web/app/dashboard-shared.d.ts +90 -0
  57. package/dist/web/app/dashboard-shared.js +171 -0
  58. package/dist/web/app/dashboard-text.d.ts +6 -0
  59. package/dist/web/app/dashboard-text.js +65 -0
  60. package/dist/web/layout/nav-tree.js +3 -4
  61. package/dist/web/layout/target-resolver-stub.js +2 -2
  62. package/dist/web/primitives/banner.d.ts +4 -0
  63. package/dist/web/primitives/banner.js +28 -0
  64. package/dist/web/primitives/button.d.ts +7 -0
  65. package/dist/web/primitives/button.js +48 -0
  66. package/dist/web/primitives/card-styles.d.ts +8 -0
  67. package/dist/web/primitives/card-styles.js +18 -0
  68. package/dist/web/primitives/card.d.ts +4 -0
  69. package/dist/web/primitives/card.js +41 -0
  70. package/dist/web/primitives/contexts.d.ts +22 -0
  71. package/dist/web/primitives/contexts.js +50 -0
  72. package/dist/web/primitives/data-table-cell.d.ts +76 -0
  73. package/dist/web/primitives/data-table-cell.js +403 -0
  74. package/dist/web/primitives/data-table.d.ts +4 -0
  75. package/dist/web/primitives/data-table.js +279 -0
  76. package/dist/web/primitives/default-form.d.ts +4 -0
  77. package/dist/web/primitives/default-form.js +79 -0
  78. package/dist/web/primitives/facet-filter.d.ts +14 -0
  79. package/dist/web/primitives/facet-filter.js +39 -0
  80. package/dist/web/primitives/field.d.ts +63 -0
  81. package/dist/web/primitives/field.js +181 -0
  82. package/dist/web/primitives/form.d.ts +53 -0
  83. package/dist/web/primitives/form.js +252 -0
  84. package/dist/web/primitives/index.d.ts +7 -30
  85. package/dist/web/primitives/index.js +27 -2006
  86. package/dist/web/primitives/input.d.ts +4 -0
  87. package/dist/web/primitives/input.js +166 -0
  88. package/dist/web/primitives/layout-elements.d.ts +14 -0
  89. package/dist/web/primitives/layout-elements.js +194 -0
  90. package/dist/web/primitives/modal.js +1 -1
  91. package/dist/web/primitives/pager.d.ts +22 -0
  92. package/dist/web/primitives/pager.js +89 -0
  93. package/dist/web/primitives/row-actions.d.ts +13 -0
  94. package/dist/web/primitives/row-actions.js +178 -0
  95. package/dist/web/primitives/secret-reveal.d.ts +4 -0
  96. package/dist/web/primitives/secret-reveal.js +67 -0
  97. package/package.json +5 -5
  98. package/src/changes.json +6 -0
@@ -0,0 +1,4 @@
1
+ import { type ReactNode } from "react";
2
+ import type { InputProps } from "../../index.ts";
3
+ import "../react-css-custom-properties.ts";
4
+ export declare function DefaultInput(props: InputProps): ReactNode;
@@ -0,0 +1,166 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useContext } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { Checkbox } from "../ui/checkbox.js";
5
+ import { Input as UiInput } from "../ui/input.js";
6
+ import { Switch } from "../ui/switch.js";
7
+ import { Textarea } from "../ui/textarea.js";
8
+ import { ComboboxInput } from "./combobox.js";
9
+ import { DateInput } from "./date-input.js";
10
+ import { FileUploadInput } from "./file-upload.js";
11
+ import { FilesDropzoneInput } from "./files-dropzone.js";
12
+ import { LocatedTimestampInput } from "./located-timestamp-input.js";
13
+ import { MoneyInput } from "./money-input.js";
14
+ import { NumberInput } from "./number-input.js";
15
+ import { hasDescriptionOrGroup } from "./option-groups.js";
16
+ import { TimestampInput } from "./timestamp-input.js";
17
+ import { TzInput } from "./tz-input.js";
18
+ import "../react-css-custom-properties.js";
19
+ import { FieldDescriptionContext, FieldLayoutContext } from "./contexts.js";
20
+ import { defaultSelectPresentation, FIELD_ICON_CLASS, fieldIconFor, RadioListSelect, SegmentedSelect, textareaMinHeight, unitSuffixId, withFieldIcon, withInlineDisabledHint, withInputAddons, withoutSegmentedSelectPlaceholder, withTrailingAction, } from "./field.js";
21
+ export function DefaultInput(props) {
22
+ // Vendored ui/input + ui/checkbox style errors via `aria-invalid`
23
+ // themselves, no manual border-destructive needed anymore.
24
+ const booleanLayout = useContext(FieldLayoutContext);
25
+ const describedBy = useContext(FieldDescriptionContext);
26
+ const common = {
27
+ id: props.id,
28
+ name: props.name,
29
+ disabled: props.disabled,
30
+ "aria-describedby": describedBy,
31
+ "aria-required": props.required,
32
+ "aria-invalid": props.hasError === true ? true : undefined,
33
+ };
34
+ switch (props.kind) {
35
+ case "text":
36
+ return withInputAddons(props, withFieldIcon(props.icon, _jsx(UiInput, { type: "text", ...common, ...props.dataAttributes, "data-testid": props.testId, readOnly: props.readOnly, value: props.value, onChange: (e) => props.onChange(e.target.value), onKeyDown: props.onKeyDown, ...(props.placeholder !== undefined && { placeholder: props.placeholder }), ...(props.autoComplete !== undefined && { autoComplete: props.autoComplete }), className: cn(fieldIconFor(props.icon) !== undefined ? "pl-8" : undefined) })));
37
+ case "email":
38
+ return (_jsx(UiInput, { type: "email", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value), ...(props.placeholder !== undefined && { placeholder: props.placeholder }), autoComplete: props.autoComplete ?? "email" }));
39
+ case "month":
40
+ return (_jsx(UiInput, { type: "month", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value) }));
41
+ case "password":
42
+ return withTrailingAction(props.trailingAction, _jsx(UiInput, { type: "password", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value), ...(props.placeholder !== undefined && { placeholder: props.placeholder }), autoComplete: props.autoComplete ?? "current-password", className: props.trailingAction !== undefined ? "pr-9" : undefined }), true);
43
+ case "number": {
44
+ const NumberIcon = fieldIconFor(props.icon);
45
+ // Icon, input and unit share this one box, and the grid cell sizes the
46
+ // box rather than the bare input: anchored to a wider wrapper, the unit
47
+ // would sit next to a narrower input instead of inside it.
48
+ const numberField = (_jsxs("div", { "data-slot": "number-field", className: "relative", children: [NumberIcon !== undefined && (_jsx(NumberIcon, { "aria-hidden": "true", className: FIELD_ICON_CLASS })), _jsx(NumberInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, disabled: props.disabled, required: props.required, hasError: props.hasError, testId: props.testId, ...(props.unit !== undefined && { ariaDescribedBy: unitSuffixId(props.id) }), ...(props.locale !== undefined && { locale: props.locale }), ...(props.grouping !== undefined && { grouping: props.grouping }), ...(props.integer !== undefined && { integer: props.integer }), ...(props.placeholder !== undefined && { placeholder: props.placeholder }), className: cn(NumberIcon !== undefined ? "pl-8" : undefined, props.unit !== undefined ? "pr-8" : undefined) }), props.unit !== undefined && (_jsx("span", { id: unitSuffixId(props.id), className: "pointer-events-none absolute right-2.5 top-1/2 max-w-[40%] -translate-y-1/2 truncate text-sm text-muted-foreground", children: props.unit }))] }));
49
+ return withInputAddons(props, numberField);
50
+ }
51
+ case "range":
52
+ return (_jsx("input", { type: "range", ...common, min: props.min, max: props.max, step: props.step ?? 1, value: props.value, onChange: (e) => props.onChange(Number(e.target.value)), className: "w-full accent-primary" }));
53
+ case "boolean":
54
+ // layout="inline" (checkbox lists like MultiSelectCheckboxes, the
55
+ // BooleanField widget) keeps the checkbox optics; the standard form
56
+ // field (render-field.tsx, layout="stacked") gets the switch.
57
+ return booleanLayout === "inline" ? (_jsx(Checkbox, { id: props.id, name: props.name, disabled: props.disabled, "aria-required": props.required, "aria-invalid": props.hasError === true ? true : undefined, checked: props.value, onCheckedChange: (checked) => props.onChange(checked === true) })) : (_jsx(Switch, { id: props.id, name: props.name, disabled: props.disabled, "aria-required": props.required, "aria-invalid": props.hasError === true ? true : undefined, checked: props.value, onCheckedChange: (checked) => props.onChange(checked === true), className: "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" }));
58
+ case "file":
59
+ case "image":
60
+ return (_jsx(FileUploadInput, { kind: props.kind, id: props.id, value: props.value, onChange: props.onChange, ...(props.accept !== undefined && { accept: props.accept }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.entityType !== undefined && { entityType: props.entityType }), ...(props.uploadActionForm !== undefined && {
61
+ uploadActionForm: props.uploadActionForm,
62
+ }), ...(props.entityId !== undefined && { entityId: props.entityId }), ...(props.fieldName !== undefined && { fieldName: props.fieldName }), ...(props.imageVariant !== undefined && { imageVariant: props.imageVariant }), ...(props.capture !== undefined && { capture: props.capture }), ...(props.directory !== undefined && { directory: props.directory }), ...(props.onFilesPicked !== undefined && { onFilesPicked: props.onFilesPicked }), ...(props.pickLabel !== undefined && { pickLabel: props.pickLabel }), ...(props.testId !== undefined && { testId: props.testId }) }));
63
+ case "files-dropzone":
64
+ return (_jsx(FilesDropzoneInput, { id: props.id, value: props.value, onChange: props.onChange, ...(props.accept !== undefined && { accept: props.accept }), ...(props.maxSize !== undefined && { maxSize: props.maxSize }), ...(props.maxCount !== undefined && { maxCount: props.maxCount }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.entityType !== undefined && { entityType: props.entityType }), ...(props.uploadActionForm !== undefined && {
65
+ uploadActionForm: props.uploadActionForm,
66
+ }), ...(props.entityId !== undefined && { entityId: props.entityId }), ...(props.fieldName !== undefined && { fieldName: props.fieldName }), ...(props.testId !== undefined && { testId: props.testId }) }));
67
+ case "date":
68
+ return (_jsx(DateInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...(props.locale !== undefined && { locale: props.locale }), ...(props.min !== undefined && { min: props.min }), ...(props.max !== undefined && { max: props.max }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
69
+ case "select": {
70
+ // Visual consolidation: all selects go through ComboboxInput
71
+ // (cmdk + Radix popover). Before, there were two paths: Radix
72
+ // Select for `kind:"select"` and cmdk for `kind:"combobox"`. Three
73
+ // visually different variants (single select, combobox single, combobox
74
+ // multi) became unmanageable. With the merge the combobox is the only
75
+ // implementation: the search input is present even for 4-item status
76
+ // selects, a deliberately accepted UX consequence for style consistency.
77
+ const comboOptions = props.options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
78
+ // The dropdown shows a disabled option's hint inline, so it stays readable in the trigger and search.
79
+ const dropdownOptions = comboOptions.map(withInlineDisabledHint);
80
+ // The radio group can't render the unselected placeholder as its own
81
+ // segment, so it counts and renders on the real options only — the
82
+ // dropdown below keeps comboOptions (placeholder included) since it
83
+ // has a genuine "nothing selected" row.
84
+ // An author who asks for `display: "radio"` explicitly declared the ""
85
+ // option as a real choice (e.g. "Any"), so it stays a segment.
86
+ const radioGroupOptions = props.display === "radio" ? comboOptions : withoutSegmentedSelectPlaceholder(comboOptions);
87
+ // An explicit `display` is an author decision and outranks the
88
+ // heuristic in both directions — a requested radio group renders as
89
+ // one even when the options outnumber the heuristic's threshold (#2711).
90
+ const requestedPresentation = props.radioVariant === "card"
91
+ ? "radioList"
92
+ : props.display === "radio"
93
+ ? "segmented"
94
+ : props.display === "dropdown"
95
+ ? "dropdown"
96
+ : defaultSelectPresentation(radioGroupOptions);
97
+ // A segmented control has no room for a description line or group headings.
98
+ const presentation = requestedPresentation === "segmented" && hasDescriptionOrGroup(radioGroupOptions)
99
+ ? "radioList"
100
+ : requestedPresentation;
101
+ if (presentation === "radioList" && radioGroupOptions.length > 0) {
102
+ return (_jsx(RadioListSelect, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: radioGroupOptions, ...(props.radioVariant !== undefined && { variant: props.radioVariant }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
103
+ }
104
+ if (presentation === "segmented" && radioGroupOptions.length > 0) {
105
+ return (_jsx(SegmentedSelect, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: radioGroupOptions, ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
106
+ }
107
+ return (_jsx(ComboboxInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: dropdownOptions, ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
108
+ }
109
+ case "combobox": {
110
+ // Tier 2.1c + tier 2.7e: discriminated union per `multiple`; we split
111
+ // TS-side into two branches so ComboboxInput's single/multi variants
112
+ // render with the right types.
113
+ const baseProps = {
114
+ id: props.id,
115
+ name: props.name,
116
+ options: props.options,
117
+ ...(props.disabled !== undefined && { disabled: props.disabled }),
118
+ ...(props.required !== undefined && { required: props.required }),
119
+ ...(props.hasError !== undefined && { hasError: props.hasError }),
120
+ ...(props.placeholder !== undefined && { placeholder: props.placeholder }),
121
+ ...(props.searchPlaceholder !== undefined && {
122
+ searchPlaceholder: props.searchPlaceholder,
123
+ }),
124
+ ...(props.emptyText !== undefined && { emptyText: props.emptyText }),
125
+ ...(props.onSearchChange !== undefined && { onSearchChange: props.onSearchChange }),
126
+ ...(props.loading !== undefined && { loading: props.loading }),
127
+ ...(props.onCreate !== undefined && { onCreate: props.onCreate }),
128
+ ...(props.createLabel !== undefined && { createLabel: props.createLabel }),
129
+ };
130
+ if (props.multiple === true) {
131
+ return (_jsx(ComboboxInput, { ...baseProps, multiple: true, value: props.value, onChange: props.onChange }));
132
+ }
133
+ return _jsx(ComboboxInput, { ...baseProps, value: props.value, onChange: props.onChange });
134
+ }
135
+ case "money":
136
+ return (_jsx(MoneyInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, currency: props.currency ?? "EUR", ...(props.locale !== undefined && { locale: props.locale }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
137
+ case "timestamp":
138
+ return (_jsx(TimestampInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...(props.wallClock !== undefined && { wallClock: props.wallClock }), ...(props.locale !== undefined && { locale: props.locale }), ...(props.min !== undefined && { min: props.min }), ...(props.max !== undefined && { max: props.max }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
139
+ case "locatedTimestamp":
140
+ return (_jsx(LocatedTimestampInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...(props.locale !== undefined && { locale: props.locale }), ...(props.min !== undefined && { min: props.min }), ...(props.max !== undefined && { max: props.max }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
141
+ case "textarea": {
142
+ const rows = props.rows;
143
+ const onSubmitShortcut = props.onSubmitShortcut;
144
+ const onKeyDown = props.onKeyDown;
145
+ return (_jsx(Textarea, { ...common, ...props.dataAttributes, readOnly: props.readOnly, value: props.value, onChange: (e) => props.onChange(e.target.value), rows: rows ?? 4, className: "resize-y", ...(props.placeholder !== undefined && { placeholder: props.placeholder }), ...(rows !== undefined && { style: textareaMinHeight(rows) }), ...((onSubmitShortcut !== undefined || onKeyDown !== undefined) && {
146
+ ...(onSubmitShortcut !== undefined && {
147
+ "aria-keyshortcuts": "Control+Enter Meta+Enter",
148
+ }),
149
+ onKeyDown: (e) => {
150
+ onKeyDown?.(e);
151
+ // Lets a caller that already handles Ctrl/Cmd+Enter (e.g. an
152
+ // "Enter sends" handler that ignores only Shift) opt out of the
153
+ // shortcut instead of submitting twice.
154
+ if (e.defaultPrevented)
155
+ return;
156
+ if (onSubmitShortcut === undefined || e.key !== "Enter" || !(e.metaKey || e.ctrlKey))
157
+ return;
158
+ e.preventDefault();
159
+ onSubmitShortcut();
160
+ },
161
+ }) }));
162
+ }
163
+ case "tz":
164
+ return (_jsx(TzInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...(props.disabled !== undefined && { disabled: props.disabled }), ...(props.required !== undefined && { required: props.required }), ...(props.hasError !== undefined && { hasError: props.hasError }) }));
165
+ }
166
+ }
@@ -0,0 +1,14 @@
1
+ import { type MouseEvent, type ReactNode } from "react";
2
+ import type { FillContainerProps, GridCellProps, GridProps, HeadingProps, LinkProps, ProgressProps, SectionProps, StepBarProps, TextProps, WizardStepGroupProps } from "../../index.ts";
3
+ import "../react-css-custom-properties.ts";
4
+ export declare function DefaultSection({ id, title, subtitle, children, actions, variant, layout, testId, icon, }: SectionProps): ReactNode;
5
+ export declare function DefaultFillContainer({ children, testId, grow }: FillContainerProps): ReactNode;
6
+ export declare function DefaultGrid({ columns, children, testId, maxRows, flow, list, ariaLabelledBy, }: GridProps): ReactNode;
7
+ export declare function DefaultGridCell({ span, width, children }: GridCellProps): ReactNode;
8
+ export declare function DefaultText({ variant, decorative, children, testId, }: TextProps): ReactNode;
9
+ export declare function isPlainPrimaryClick(event: MouseEvent): boolean;
10
+ export declare function DefaultLink({ href, variant, target, rel, onPress, className, children, testId, dataAttributes, }: LinkProps): ReactNode;
11
+ export declare function DefaultProgress({ value, tone, ariaLabel, testId }: ProgressProps): ReactNode;
12
+ export declare function DefaultStepBar({ steps, currentIndex, compactLabel, onStepSelect, doneSteps, selectableSteps, narrowLayout, orientation, heading, description, subtitles, upNext, testId, compactTestId, }: StepBarProps): ReactNode;
13
+ export declare function DefaultWizardStepGroup({ hidden, inset, children, }: WizardStepGroupProps): ReactNode;
14
+ export declare function DefaultHeading({ variant, children, testId }: HeadingProps): ReactNode;
@@ -0,0 +1,194 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Children, useContext } from "react";
3
+ import { isSafeHref } from "../../headless/index.js";
4
+ import { Icon } from "../icons.js";
5
+ import { cn } from "../lib/cn.js";
6
+ import { buttonVariants } from "../ui/button.js";
7
+ import { ProgressBar } from "../widgets/progress-bar.js";
8
+ import { StepBar } from "../widgets/step-bar.js";
9
+ import "../react-css-custom-properties.js";
10
+ import { cardSurface } from "./card-styles.js";
11
+ import { DrawerBodyContext, InsideFormContext, ScreenFormContext } from "./contexts.js";
12
+ export function DefaultSection({ id, title, subtitle, children, actions, variant = "default", layout = "stacked", testId, icon, }) {
13
+ const insideForm = useContext(InsideFormContext);
14
+ const insideScreenForm = useContext(ScreenFormContext);
15
+ // h3 instead of CardTitle (= div): keeps the heading semantics for
16
+ // screen reader navigation. The subtitle flows below (no divider,
17
+ // shadcn CardTitle+CardDescription pattern).
18
+ // icon only renders alongside a title — a title-less section has nothing
19
+ // for a lone icon to sit next to, so it stays as-is (no heading grows).
20
+ // actions render top-right in this same title row, never a
21
+ // footer — a title-less section still draws the row when actions are
22
+ // present, so a hideTitle tabs-Section with `actions` isn't stranded.
23
+ const TitleTag = insideScreenForm ? "h2" : "h3";
24
+ const titleBlock = title !== undefined || subtitle !== undefined ? (_jsxs("div", { className: "flex flex-col gap-1", children: [title !== undefined && (_jsxs(TitleTag, { "data-testid": testId !== undefined ? `${testId}-title` : undefined, className: "flex items-center gap-2 text-base font-semibold leading-none tracking-tight", children: [icon !== undefined && _jsx(Icon, { name: icon, className: "size-4 text-muted-foreground" }), title] })), subtitle !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-subtitle` : undefined, className: cn("text-sm text-muted-foreground", insideScreenForm && "text-[13px] text-foreground-secondary"), children: subtitle }))] })) : null;
25
+ const header = titleBlock !== null || actions !== undefined ? (_jsxs("div", { className: "flex items-start justify-between gap-4", children: [titleBlock, actions !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, className: "ml-auto flex shrink-0 items-center gap-2", children: actions }))] })) : null;
26
+ // Inside a form: divider-less section WITHOUT a card surface of its own.
27
+ // The separator lines BETWEEN sections come from the divide-y wrapper in
28
+ // the form.
29
+ if (insideForm) {
30
+ return (_jsxs("section", { id: id, "data-testid": testId, className: cn(
31
+ // py-4 (not the standalone card's py-6): the border-t between
32
+ // sections already carries the section break, so the vertical
33
+ // gap only needs to read as roughly double the gap-4 field row
34
+ // spacing, not triple it.
35
+ insideScreenForm
36
+ ? "flex scroll-mt-4 flex-col gap-4 [&:not(:first-of-type)]:border-t [&:not(:first-of-type)]:border-border [&:not(:first-of-type)]:pt-7"
37
+ : "flex flex-col gap-4 px-6 py-4", variant === "destructive" && "border-l-2 border-destructive/40", layout === "settings-list" && "@container max-w-4xl"), children: [header, children] }));
38
+ }
39
+ // Standalone: own card, header flows into the body (no divider).
40
+ // overflow-hidden clips the card's own corner radius correctly for
41
+ // portaled overlays (Combobox/Select/Tooltip escape to document.body,
42
+ // unaffected). A non-portaled overlay (e.g. a custom dropdown built
43
+ // directly into `children`) WOULD get silently clipped — verify this
44
+ // against any new standalone-section content that renders its own
45
+ // non-portaled overlay.
46
+ return (_jsx("div", { "data-slot": "card", "data-testid": testId, className: cn(cardSurface(), "overflow-hidden", variant === "destructive" && "border-destructive/40"), children: _jsxs("div", { className: "flex flex-col gap-4 px-6 py-6", children: [header, children] }) }));
47
+ }
48
+ export function DefaultFillContainer({ children, testId, grow }) {
49
+ // flex-1 only on request (`grow`, set under fillScreenHeight): without it the
50
+ // container sizes to its content and only shrinks (min-h-0), so a two-row
51
+ // table in a non-fixed-height screen does not stretch (fw#2778).
52
+ return (_jsx("div", { "data-testid": testId, className: cn("flex min-h-0 flex-col", grow === true && "flex-1"), children: children }));
53
+ }
54
+ const DRAWER_FIELD_CELL_WIDTH_CLASS = {
55
+ money: "w-full sm:w-[200px] [&_label]:whitespace-nowrap",
56
+ select: "w-full",
57
+ };
58
+ const FIELD_CELL_WIDTH_CLASS = {
59
+ text: "w-full sm:w-60",
60
+ number: "w-fit min-w-24 shrink-0 [&_label]:whitespace-nowrap [&_[data-slot=number-field]]:w-32",
61
+ money: "w-40 shrink-0 [&_label]:whitespace-nowrap",
62
+ date: "w-full sm:w-[200px]",
63
+ timestamp: "w-full sm:w-[328px]",
64
+ select: "w-full sm:w-auto sm:min-w-[200px] sm:has-[[data-radio-list]]:w-full",
65
+ full: "w-full",
66
+ // self-start: the label shares the top line with neighbouring labels. The
67
+ // switch (1.15rem) gets (h-9 input − switch) / 2 margin to sit on the input line.
68
+ toggle: "w-auto min-w-40 shrink-0 self-start [&_[data-slot=switch]]:my-[0.55rem]",
69
+ };
70
+ export function DefaultGrid({ columns, children, testId, maxRows, flow, list, ariaLabelledBy, }) {
71
+ const insideDrawerBody = useContext(DrawerBodyContext);
72
+ const groupProps = ariaLabelledBy !== undefined
73
+ ? { role: "group", "aria-labelledby": ariaLabelledBy }
74
+ : {};
75
+ if (list === true) {
76
+ return (_jsx("div", { ...groupProps, "data-testid": testId, className: "flex flex-col divide-y divide-border", children: children }));
77
+ }
78
+ if (flow === true) {
79
+ return (_jsx("div", { ...groupProps, "data-testid": testId, className: cn("flex gap-4", insideDrawerBody
80
+ ? "flex-col items-start"
81
+ : // items-end aligns inputs when labels wrap to different heights; a
82
+ // below-control appendix grows only the one cell and would push its
83
+ // neighbours down, so such rows align to the top instead. An error
84
+ // line does the same: bottom-aligned, it would lift that field's
85
+ // label above its neighbours'.
86
+ "flex-wrap items-end has-[[data-appendix-below]]:items-start has-[[role=alert]]:items-start"), children: children }));
87
+ }
88
+ // "auto": content-sized items in a wrapping row (e.g. a metrics band of
89
+ // self-sized tiles) instead of N equal-width, container-stretched tracks.
90
+ // maxRows/scrolling don't apply — the row just wraps.
91
+ if (columns === "auto") {
92
+ return (_jsx("div", { ...groupProps, "data-testid": testId, className: "flex flex-wrap items-center gap-4", children: children }));
93
+ }
94
+ // Responsive: mobile (< sm = 640px) stays single-column, from sm on the
95
+ // author-declared column count applies. The inline style writes a CSS
96
+ // variable; the Tailwind class reads it with
97
+ // `grid-template-columns: var(--grid-cols)`. A clean solution because
98
+ // Tailwind JIT cannot resolve a dynamic `grid-cols-${N}`.
99
+ const rowCount = Math.ceil(Children.toArray(children).length / columns);
100
+ const clipped = maxRows !== undefined && rowCount > maxRows;
101
+ // gridAutoRows is a minmax MIN, not a fixed height, so wrapped labels grow
102
+ // their row instead of being clipped mid-row. maxHeight is therefore only
103
+ // an approximation of "maxRows rows tall" — a row taller than the minimum
104
+ // means fewer than maxRows rows end up visible before scrolling kicks in.
105
+ // Themes may override --kumiko-grid-row-h (default 2.5rem, set in
106
+ // styles.css) to change that minimum.
107
+ // Mobile note: the grid collapses to 1 column below `sm` (see className),
108
+ // but rowCount above is always computed against the desktop `columns` —
109
+ // inline styles can't express a media query, so the mobile row count is
110
+ // an under-count. Known, accepted.
111
+ const style = {
112
+ "--grid-cols": `repeat(${columns}, minmax(0, 1fr))`,
113
+ ...(clipped && {
114
+ gridAutoRows: "minmax(var(--kumiko-grid-row-h, 2.5rem), auto)",
115
+ maxHeight: `calc(${maxRows} * var(--kumiko-grid-row-h, 2.5rem) + ${maxRows - 1} * var(--kumiko-grid-gap, 1rem))`,
116
+ overflowY: "auto",
117
+ }),
118
+ };
119
+ return (_jsx("div", { ...groupProps, "data-testid": testId, className: "grid gap-[var(--kumiko-grid-gap,1rem)] grid-cols-1 sm:[grid-template-columns:var(--grid-cols)]", style: style, children: children }));
120
+ }
121
+ export function DefaultGridCell({ span, width, children }) {
122
+ const insideDrawerBody = useContext(DrawerBodyContext);
123
+ if (width !== undefined) {
124
+ return (_jsx("div", { className: cn("min-w-0 max-w-full", insideDrawerBody
125
+ ? (DRAWER_FIELD_CELL_WIDTH_CLASS[width] ?? FIELD_CELL_WIDTH_CLASS[width])
126
+ : FIELD_CELL_WIDTH_CLASS[width]), children: children }));
127
+ }
128
+ const s = span !== undefined ? Math.min(span, 12) : 1;
129
+ // Below `sm` the grid has one explicit column (see DefaultGrid); an unconditional
130
+ // `span N` there would add implicit auto columns that squeeze the fr column.
131
+ const style = { "--grid-span": s };
132
+ return (_jsx("div", { className: "min-w-0 sm:[grid-column:span_var(--grid-span)]", style: style, children: children }));
133
+ }
134
+ export function DefaultText({ variant = "body", decorative, children, testId, }) {
135
+ switch (variant) {
136
+ case "code":
137
+ return (_jsx("code", { "data-testid": testId, className: "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm", children: children }));
138
+ case "small":
139
+ return (_jsx("small", { "data-testid": testId, className: "text-xs text-muted-foreground", children: children }));
140
+ case "required-mark":
141
+ return (_jsx("span", { "data-testid": testId, "data-required": true, className: "text-destructive", children: children }));
142
+ case "muted":
143
+ return (_jsx("span", { "data-testid": testId, ...(decorative === true && { "aria-hidden": true }), className: "text-sm text-muted-foreground", children: children }));
144
+ default:
145
+ return _jsx("span", { "data-testid": testId, children: children });
146
+ }
147
+ }
148
+ // ---- Link (anchor with button/muted look) ----
149
+ // isSafeHref lives in @cosmicdrift/kumiko-renderer/headless — shared with
150
+ // page-render's server-side markdown renderer, both take untrusted
151
+ // tenant-authored hrefs.
152
+ // `button` uses the primary button surface on a semantic <a>, the
153
+ // standard for "continue to" navigations after success states (formerly
154
+ // authButtonClass), `muted` is the subtle secondary link (formerly
155
+ // authMutedLinkClass).
156
+ // Modified and middle clicks keep the browser default (new tab/window).
157
+ export function isPlainPrimaryClick(event) {
158
+ return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
159
+ }
160
+ export function DefaultLink({ href, variant = "default", target, rel, onPress, className, children, testId, dataAttributes, }) {
161
+ const variantClass = variant === "button"
162
+ ? buttonVariants({ variant: "default" })
163
+ : variant === "muted"
164
+ ? "text-sm text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"
165
+ : "text-primary underline-offset-4 hover:underline";
166
+ return (_jsx("a", { href: isSafeHref(href) ? href : "#", target: target, rel: rel ?? (target === "_blank" ? "noopener noreferrer" : undefined), ...dataAttributes, "data-testid": testId, className: cn(variantClass, className), onClick: onPress === undefined
167
+ ? undefined
168
+ : (event) => {
169
+ if (!isPlainPrimaryClick(event))
170
+ return;
171
+ event.preventDefault();
172
+ onPress();
173
+ }, children: children }));
174
+ }
175
+ export function DefaultProgress({ value, tone, ariaLabel, testId }) {
176
+ return _jsx(ProgressBar, { value: value, tone: tone, ariaLabel: ariaLabel, testId: testId });
177
+ }
178
+ export function DefaultStepBar({ steps, currentIndex, compactLabel, onStepSelect, doneSteps, selectableSteps, narrowLayout, orientation, heading, description, subtitles, upNext, testId, compactTestId, }) {
179
+ return (_jsx(StepBar, { steps: steps, currentIndex: currentIndex, compactLabel: compactLabel, onStepSelect: onStepSelect, doneSteps: doneSteps, selectableSteps: selectableSteps, narrowLayout: narrowLayout, orientation: orientation, heading: heading, description: description, subtitles: subtitles, upNext: upNext, testId: testId, compactTestId: compactTestId }));
180
+ }
181
+ export function DefaultWizardStepGroup({ hidden, inset, children, }) {
182
+ const visibleClass = inset === true ? "flex min-w-0 flex-col gap-4 p-6" : "contents";
183
+ return (_jsx("fieldset", { disabled: hidden, hidden: hidden, className: hidden ? undefined : visibleClass, children: children }));
184
+ }
185
+ export function DefaultHeading({ variant = "page", children, testId }) {
186
+ // Page heading = h1, very rare in an app (max 1 per screen).
187
+ // Section heading = h2 with uppercase + muted-foreground, the same look as
188
+ // the section header in forms, but usable as a standalone component for
189
+ // demo pages and custom screens.
190
+ if (variant === "section") {
191
+ return (_jsx("h2", { "data-testid": testId, className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: children }));
192
+ }
193
+ return (_jsx("h1", { "data-testid": testId, className: "text-2xl font-semibold tracking-tight", children: children }));
194
+ }
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import * as DialogPrimitive from "@radix-ui/react-dialog";
6
6
  import { useTranslation } from "../../index.js";
7
7
  import { cn } from "../lib/cn.js";
8
- import { BareFormProvider } from "./index.js";
8
+ import { BareFormProvider } from "./contexts.js";
9
9
  import { ModalShell } from "./modal-shell.js";
10
10
  export function DefaultModal({ open, onOpenChange, title, children, testId, }) {
11
11
  const t = useTranslation();
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from "react";
2
+ import "../react-css-custom-properties.ts";
3
+ export declare const TABLE_FOOTER_BAR_CLASS: string;
4
+ export declare const END_LABEL_MIN_ROWS = 50;
5
+ export declare function InfiniteSentinel({ onReachEnd, loadingMore, hasMore, rowsCount, testId, }: {
6
+ readonly onReachEnd: () => void;
7
+ readonly loadingMore: boolean;
8
+ readonly hasMore: boolean;
9
+ readonly rowsCount: number;
10
+ readonly testId?: string;
11
+ }): ReactNode;
12
+ export declare function Pager({ page, limit, total, onPageChange, pageSizeOptions, onPageSizeChange, itemNoun, testId, pinned, }: {
13
+ readonly page: number;
14
+ readonly limit: number;
15
+ readonly total: number;
16
+ readonly onPageChange: (next: number) => void;
17
+ readonly pageSizeOptions?: readonly number[];
18
+ readonly onPageSizeChange?: (next: number) => void;
19
+ readonly itemNoun?: (count: number) => string;
20
+ readonly testId?: string;
21
+ readonly pinned?: boolean;
22
+ }): ReactNode;
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
3
+ import { useEffect, useRef } from "react";
4
+ import { useTranslation } from "../../index.js";
5
+ import { cn } from "../lib/cn.js";
6
+ import "../react-css-custom-properties.js";
7
+ export const TABLE_FOOTER_BAR_CLASS = cn("flex h-15 shrink-0 items-center gap-4 border-t border-border bg-card pl-4 pr-2", "text-[13px] tabular-nums text-foreground-secondary md:h-11 md:px-6");
8
+ // InfiniteSentinel — empty div at the end of the table that uses
9
+ // IntersectionObserver to detect when the user scrolls near the list end.
10
+ // onReachEnd fires exactly once per "becomes visible" transition; the
11
+ // caller debounces via loadingMore (further visibility events are ignored
12
+ // while a page is loading). No observer during server-side render, no
13
+ // observer when hasMore=false — then the sentinel only shows the
14
+ // end-of-list hint via t("kumiko.list.end-of-list"), which requires a
15
+ // LocaleProvider in the tree (same requirement as Field/useRowActionTrigger).
16
+ // Without one, useTranslation() throws; with one that's missing the framework
17
+ // defaults (fallbackBundles not including kumikoDefaultTranslations), the
18
+ // user sees the raw key instead of translated text.
19
+ // ponytail: threshold mirrors the framework default pageSize (kumiko-screen.tsx
20
+ // `screen.pageSize ?? 50`) rather than the real per-screen pageSize, which isn't
21
+ // threaded down to this component. A screen with a custom pageSize can still
22
+ // under- or over-show the marker; thread pageSize through KumikoScreen →
23
+ // RenderList → DataTable → InfiniteSentinel if that's needed (fw#1713).
24
+ export const END_LABEL_MIN_ROWS = 50;
25
+ export function InfiniteSentinel({ onReachEnd, loadingMore, hasMore, rowsCount, testId, }) {
26
+ const t = useTranslation();
27
+ const ref = useRef(null);
28
+ useEffect(() => {
29
+ if (!hasMore)
30
+ return;
31
+ if (loadingMore)
32
+ return;
33
+ if (typeof IntersectionObserver === "undefined")
34
+ return;
35
+ const node = ref.current;
36
+ if (node === null)
37
+ return;
38
+ const observer = new IntersectionObserver((entries) => {
39
+ // Only fire the first visible event per mount: if the user keeps
40
+ // scrolling while loading is still in progress, loadingMore=true keeps the
41
+ // useEffect from remounting the observer at all.
42
+ for (const entry of entries) {
43
+ if (entry.isIntersecting) {
44
+ onReachEnd();
45
+ break;
46
+ }
47
+ }
48
+ }, { rootMargin: "200px" });
49
+ observer.observe(node);
50
+ return () => observer.disconnect();
51
+ }, [onReachEnd, loadingMore, hasMore]);
52
+ const showEndLabel = !hasMore && rowsCount >= END_LABEL_MIN_ROWS;
53
+ return (_jsx("div", { ref: ref, "data-testid": testId, className: cn("flex items-center justify-center text-sm text-muted-foreground", showEndLabel || loadingMore ? "py-4" : ""), children: showEndLabel ? (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-end` : undefined, children: t("kumiko.list.end-of-list") })) : loadingMore ? (_jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" })) : hasMore ? (_jsx("span", { "aria-hidden": "true", children: "\u00A0" })) : null }));
54
+ }
55
+ // Pager — status text on the left ("X – Y of Z"), "Page X of Y" and the
56
+ // prev/next arrows on the right. No numbered page list: the board shows none.
57
+ // `pinned` renders the footer bar of a scrollBody table (fixed height, top
58
+ // border); unpinned it sits as a plain row below the table.
59
+ //
60
+ // Status text and aria-labels go through t(...), which requires a
61
+ // LocaleProvider in the tree — same requirement as InfiniteSentinel above,
62
+ // already established for DataTable consumers.
63
+ export function Pager({ page, limit, total, onPageChange, pageSizeOptions, onPageSizeChange, itemNoun, testId, pinned, }) {
64
+ const t = useTranslation();
65
+ const totalPages = Math.max(1, Math.ceil(total / limit));
66
+ const safePage = Math.max(1, Math.min(page, totalPages));
67
+ const from = (safePage - 1) * limit + 1;
68
+ const to = Math.min(safePage * limit, total);
69
+ return (_jsxs("div", { "data-testid": testId, className: pinned === true
70
+ ? TABLE_FOOTER_BAR_CLASS
71
+ : "mt-3 flex items-center gap-4 text-[13px] tabular-nums text-foreground-secondary", children: [_jsx("div", { "data-testid": testId !== undefined ? `${testId}-status` : undefined, className: "flex-1", children: itemNoun !== undefined
72
+ ? t("kumiko.pager.status.noun", {
73
+ from: from.toLocaleString(),
74
+ to: to.toLocaleString(),
75
+ total: total.toLocaleString(),
76
+ noun: itemNoun(total),
77
+ })
78
+ : t("kumiko.pager.status", {
79
+ from: from.toLocaleString(),
80
+ to: to.toLocaleString(),
81
+ total: total.toLocaleString(),
82
+ }) }), pageSizeOptions !== undefined && onPageSizeChange !== undefined && (_jsxs("div", { className: "relative hidden md:block", children: [_jsx("select", { "aria-label": t("kumiko.pager.pageSizeLabel"), "data-testid": testId !== undefined ? `${testId}-page-size` : undefined, value: limit, onChange: (e) => onPageSizeChange(Number(e.target.value)), className: "h-7 appearance-none rounded-md border border-input bg-card pl-2 pr-7 text-[13px] text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: t("kumiko.pager.pageSize", { size: size.toLocaleString() }) }, size))) }), _jsx(ChevronDown, { className: "pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 opacity-50", "aria-hidden": "true" })] })), _jsx("div", { className: "hidden md:block", children: t("kumiko.pager.pageOf", {
83
+ page: safePage.toLocaleString(),
84
+ pages: totalPages.toLocaleString(),
85
+ }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(PagerButton, { ariaLabel: t("kumiko.pager.previousPage"), disabled: safePage <= 1, onClick: () => onPageChange(safePage - 1), testId: testId !== undefined ? `${testId}-prev` : undefined, children: _jsx(ChevronLeft, { className: "size-3.5", "aria-hidden": "true" }) }), _jsx(PagerButton, { ariaLabel: t("kumiko.pager.nextPage"), disabled: safePage >= totalPages, onClick: () => onPageChange(safePage + 1), testId: testId !== undefined ? `${testId}-next` : undefined, children: _jsx(ChevronRight, { className: "size-3.5", "aria-hidden": "true" }) })] })] }));
86
+ }
87
+ function PagerButton({ children, onClick, ariaLabel, disabled, testId, }) {
88
+ return (_jsx("button", { type: "button", onClick: onClick, disabled: disabled, "aria-label": ariaLabel, "data-testid": testId, className: cn("inline-flex size-11 items-center justify-center rounded-md border border-border md:size-7", "hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:pointer-events-none disabled:text-foreground-disabled"), children: children }));
89
+ }
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ListRowViewModel } from "../../headless/index.ts";
3
+ import type { DataTableRowAction, DataTableRowActionMode } from "../../index.ts";
4
+ import { type ActionOverflowMenuProps } from "../../index.ts";
5
+ import "../react-css-custom-properties.ts";
6
+ export declare function RowActionsCell({ row, actions, mode, rowIsLink, compact, }: {
7
+ readonly row: ListRowViewModel;
8
+ readonly actions: readonly DataTableRowAction[];
9
+ readonly mode?: DataTableRowActionMode;
10
+ readonly rowIsLink?: boolean;
11
+ readonly compact?: boolean;
12
+ }): ReactNode;
13
+ export declare function ActionOverflowMenu({ items, label, testId }: ActionOverflowMenuProps): ReactNode;