@cosmicdrift/kumiko-renderer 0.389.0 → 0.390.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/projection-detail-body.js +59 -394
- package/dist/app/projection-detail-header-model.d.ts +11 -0
- package/dist/app/projection-detail-header-model.js +33 -0
- package/dist/app/projection-detail-header-region.d.ts +28 -0
- package/dist/app/projection-detail-header-region.js +81 -0
- package/dist/app/projection-detail-header.d.ts +29 -0
- package/dist/app/projection-detail-header.js +84 -0
- package/dist/app/projection-detail-navigation.d.ts +13 -0
- package/dist/app/projection-detail-navigation.js +91 -0
- package/dist/app/projection-detail-states.d.ts +22 -0
- package/dist/app/projection-detail-states.js +26 -0
- package/dist/app/projection-drawer-host.d.ts +12 -0
- package/dist/app/projection-drawer-host.js +10 -0
- package/dist/app/projection-list-body.d.ts +1 -1
- package/dist/app/projection-list-body.js +69 -189
- package/dist/app/projection-list-render-props.d.ts +56 -0
- package/dist/app/projection-list-render-props.js +47 -0
- package/dist/app/projection-list-render.d.ts +26 -0
- package/dist/app/projection-list-render.js +20 -0
- package/dist/app/projection-list-row-click.d.ts +12 -0
- package/dist/app/projection-list-row-click.js +14 -0
- package/dist/app/use-projection-detail-actions.d.ts +35 -0
- package/dist/app/use-projection-detail-actions.js +156 -0
- package/dist/app/use-projection-detail-data.d.ts +19 -0
- package/dist/app/use-projection-detail-data.js +47 -0
- package/dist/app/use-projection-detail-layout.d.ts +17 -0
- package/dist/app/use-projection-detail-layout.js +23 -0
- package/dist/app/use-projection-list-actions.d.ts +31 -0
- package/dist/app/use-projection-list-actions.js +73 -0
- package/dist/app/use-projection-list-data.d.ts +20 -0
- package/dist/app/use-projection-list-data.js +26 -0
- package/dist/app/use-projection-list-definitions.d.ts +11 -0
- package/dist/app/use-projection-list-definitions.js +20 -0
- package/dist/app/use-projection-list-query.d.ts +28 -0
- package/dist/app/use-projection-list-query.js +83 -0
- package/dist/components/render-edit-banners.d.ts +12 -0
- package/dist/components/render-edit-banners.js +90 -0
- package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
- package/dist/components/render-edit-draft-save-helpers.js +42 -0
- package/dist/components/render-edit-field-grid.d.ts +4 -0
- package/dist/components/render-edit-field-grid.js +40 -0
- package/dist/components/render-edit-fields-section.d.ts +4 -0
- package/dist/components/render-edit-fields-section.js +76 -0
- package/dist/components/render-edit-form-actions.d.ts +4 -0
- package/dist/components/render-edit-form-actions.js +66 -0
- package/dist/components/render-edit-header-actions.d.ts +11 -0
- package/dist/components/render-edit-header-actions.js +120 -0
- package/dist/components/render-edit-layout-flags.d.ts +29 -0
- package/dist/components/render-edit-layout-flags.js +106 -0
- package/dist/components/render-edit-layout.d.ts +4 -0
- package/dist/components/render-edit-layout.js +79 -0
- package/dist/components/render-edit-render-model.d.ts +63 -0
- package/dist/components/render-edit-render-model.js +14 -0
- package/dist/components/render-edit-section-kinds.d.ts +4 -0
- package/dist/components/render-edit-section-kinds.js +115 -0
- package/dist/components/render-edit-submit-result.d.ts +8 -0
- package/dist/components/render-edit-submit-result.js +77 -0
- package/dist/components/render-edit-submit.d.ts +7 -0
- package/dist/components/render-edit-submit.js +113 -0
- package/dist/components/render-edit-titles.d.ts +17 -0
- package/dist/components/render-edit-titles.js +99 -0
- package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
- package/dist/components/render-edit-wizard-navigation.js +115 -0
- package/dist/components/render-edit.d.ts +2 -2
- package/dist/components/render-edit.js +63 -1128
- package/dist/components/render-field-choice-inputs.d.ts +7 -0
- package/dist/components/render-field-choice-inputs.js +52 -0
- package/dist/components/render-field-control.d.ts +12 -0
- package/dist/components/render-field-control.js +93 -0
- package/dist/components/render-field-display-text.d.ts +4 -0
- package/dist/components/render-field-display-text.js +48 -0
- package/dist/components/render-field-fill-action.d.ts +13 -0
- package/dist/components/render-field-fill-action.js +63 -0
- package/dist/components/render-field-input-shared.d.ts +39 -0
- package/dist/components/render-field-input-shared.js +20 -0
- package/dist/components/render-field-input.d.ts +3 -0
- package/dist/components/render-field-input.js +34 -0
- package/dist/components/render-field-meta.d.ts +6 -0
- package/dist/components/render-field-meta.js +51 -0
- package/dist/components/render-field-read-only.d.ts +11 -0
- package/dist/components/render-field-read-only.js +75 -0
- package/dist/components/render-field-reference.d.ts +15 -0
- package/dist/components/render-field-reference.js +103 -0
- package/dist/components/render-field-scalar-inputs.d.ts +10 -0
- package/dist/components/render-field-scalar-inputs.js +35 -0
- package/dist/components/render-field-text-inputs.d.ts +3 -0
- package/dist/components/render-field-text-inputs.js +34 -0
- package/dist/components/render-field-values.d.ts +18 -0
- package/dist/components/render-field-values.js +111 -0
- package/dist/components/render-field-write-only.d.ts +9 -0
- package/dist/components/render-field-write-only.js +29 -0
- package/dist/components/render-field.d.ts +2 -2
- package/dist/components/render-field.js +39 -670
- package/dist/components/render-list-header-slot.d.ts +5 -0
- package/dist/components/render-list-header-slot.js +30 -0
- package/dist/components/render-list-reference-bridge.d.ts +3 -0
- package/dist/components/render-list-reference-bridge.js +25 -0
- package/dist/components/render-list-reference-columns.d.ts +25 -0
- package/dist/components/render-list-reference-columns.js +137 -0
- package/dist/components/render-list-table-props.d.ts +52 -0
- package/dist/components/render-list-table-props.js +53 -0
- package/dist/components/render-list-toolbar-model.d.ts +42 -0
- package/dist/components/render-list-toolbar-model.js +82 -0
- package/dist/components/render-list-toolbar.d.ts +38 -0
- package/dist/components/render-list-toolbar.js +40 -0
- package/dist/components/render-list-types.d.ts +113 -0
- package/dist/components/render-list-types.js +1 -0
- package/dist/components/render-list.d.ts +4 -113
- package/dist/components/render-list.js +91 -307
- package/dist/components/use-reference-input-state.d.ts +20 -0
- package/dist/components/use-reference-input-state.js +85 -0
- package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
- package/dist/components/use-render-edit-controlled-sync.js +26 -0
- package/dist/components/use-render-edit-draft-save.d.ts +29 -0
- package/dist/components/use-render-edit-draft-save.js +141 -0
- package/dist/components/use-render-edit-form.d.ts +10 -0
- package/dist/components/use-render-edit-form.js +101 -0
- package/dist/components/use-render-edit-sections.d.ts +15 -0
- package/dist/components/use-render-edit-sections.js +100 -0
- package/dist/components/use-render-edit-state.d.ts +65 -0
- package/dist/components/use-render-edit-state.js +158 -0
- package/dist/components/use-render-edit-steps.d.ts +13 -0
- package/dist/components/use-render-edit-steps.js +52 -0
- package/dist/components/use-render-list-columns.d.ts +18 -0
- package/dist/components/use-render-list-columns.js +41 -0
- package/dist/components/use-render-list-search.d.ts +4 -0
- package/dist/components/use-render-list-search.js +50 -0
- package/dist/primitives.d.ts +1 -1
- package/dist/web/app/dashboard-body.d.ts +1 -1
- package/dist/web/app/dashboard-body.js +42 -246
- package/dist/web/app/dashboard-header.d.ts +34 -0
- package/dist/web/app/dashboard-header.js +58 -0
- package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
- package/dist/web/app/dashboard-screen-controls.js +42 -0
- package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
- package/dist/web/app/dashboard-screen-tiles.js +42 -0
- package/dist/web/app/dashboard-visibility.d.ts +42 -0
- package/dist/web/app/dashboard-visibility.js +102 -0
- package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
- package/dist/web/app/use-dashboard-screen-state.js +69 -0
- package/dist/web/primitives/data-table/actions-column.js +6 -5
- package/dist/web/primitives/data-table/card-body.d.ts +6 -0
- package/dist/web/primitives/data-table/card-body.js +32 -0
- package/dist/web/primitives/data-table/card-row.d.ts +5 -0
- package/dist/web/primitives/data-table/card-row.js +31 -0
- package/dist/web/primitives/data-table/card-view.d.ts +3 -0
- package/dist/web/primitives/data-table/card-view.js +13 -0
- package/dist/web/primitives/data-table/column-resize-handle.js +3 -1
- package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
- package/dist/web/primitives/data-table/data-cell-props.js +8 -0
- package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
- package/dist/web/primitives/data-table/row-render-parts.js +35 -0
- package/dist/web/primitives/data-table/table-body.d.ts +3 -0
- package/dist/web/primitives/data-table/table-body.js +32 -0
- package/dist/web/primitives/data-table/table-content.d.ts +3 -0
- package/dist/web/primitives/data-table/table-content.js +41 -0
- package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
- package/dist/web/primitives/data-table/table-data-cell.js +25 -0
- package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
- package/dist/web/primitives/data-table/table-header-row.js +21 -0
- package/dist/web/primitives/data-table/table-view.d.ts +3 -0
- package/dist/web/primitives/data-table/table-view.js +25 -0
- package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
- package/dist/web/primitives/data-table/toolbar.js +53 -0
- package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
- package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
- package/dist/web/primitives/data-table.d.ts +3 -4
- package/dist/web/primitives/data-table.js +32 -252
- package/dist/web/primitives/file-value-list.js +1 -1
- package/dist/web/primitives/form-footer-parts.d.ts +25 -0
- package/dist/web/primitives/form-footer-parts.js +55 -0
- package/dist/web/primitives/form.js +33 -43
- package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
- package/dist/web/primitives/input-choice-kinds.js +67 -0
- package/dist/web/primitives/input-file-kinds.d.ts +3 -0
- package/dist/web/primitives/input-file-kinds.js +6 -0
- package/dist/web/primitives/input-number-kinds.d.ts +4 -0
- package/dist/web/primitives/input-number-kinds.js +16 -0
- package/dist/web/primitives/input-optional-props.d.ts +20 -0
- package/dist/web/primitives/input-optional-props.js +24 -0
- package/dist/web/primitives/input-select-presentation.d.ts +13 -0
- package/dist/web/primitives/input-select-presentation.js +19 -0
- package/dist/web/primitives/input-shared.d.ts +22 -0
- package/dist/web/primitives/input-shared.js +10 -0
- package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
- package/dist/web/primitives/input-temporal-kinds.js +10 -0
- package/dist/web/primitives/input-text-kinds.d.ts +7 -0
- package/dist/web/primitives/input-text-kinds.js +33 -0
- package/dist/web/primitives/input.js +35 -161
- package/dist/web/widgets/chart-data.d.ts +35 -0
- package/dist/web/widgets/chart-data.js +37 -0
- package/dist/web/widgets/chart-markers.d.ts +26 -0
- package/dist/web/widgets/chart-markers.js +64 -0
- package/dist/web/widgets/chart-parts.d.ts +20 -0
- package/dist/web/widgets/chart-parts.js +15 -0
- package/dist/web/widgets/charts.d.ts +5 -78
- package/dist/web/widgets/charts.js +7 -485
- package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
- package/dist/web/widgets/drawer-panel-class.js +40 -0
- package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
- package/dist/web/widgets/drawer-resize-controls.js +12 -0
- package/dist/web/widgets/drawer-sizing.d.ts +19 -0
- package/dist/web/widgets/drawer-sizing.js +60 -0
- package/dist/web/widgets/drawer.js +17 -116
- package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
- package/dist/web/widgets/stacked-area-brush.js +91 -0
- package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
- package/dist/web/widgets/stacked-area-chart.js +45 -0
- package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
- package/dist/web/widgets/stacked-area-chrome.js +77 -0
- package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
- package/dist/web/widgets/stacked-area-geometry.js +83 -0
- package/dist/web/widgets/stacked-area-model.d.ts +11 -0
- package/dist/web/widgets/stacked-area-model.js +1 -0
- package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
- package/dist/web/widgets/stacked-area-plot.js +51 -0
- package/dist/web/widgets/stacked-area-props.d.ts +83 -0
- package/dist/web/widgets/stacked-area-props.js +16 -0
- package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
- package/dist/web/widgets/stacked-area-svg.js +38 -0
- package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
- package/dist/web/widgets/stacked-area-tooltip.js +86 -0
- package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
- package/dist/web/widgets/use-drawer-resize.js +52 -0
- package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
- package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
- package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
- package/dist/web/widgets/use-stacked-area-window.js +76 -0
- package/package.json +9 -9
- package/src/changes.json +54 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import type { useTranslation } from "../../index.ts";
|
|
3
|
+
type Translate = ReturnType<typeof useTranslation>;
|
|
4
|
+
export type FormFooterModel = {
|
|
5
|
+
readonly t: Translate;
|
|
6
|
+
readonly testId: string | undefined;
|
|
7
|
+
readonly chromeless: boolean;
|
|
8
|
+
readonly stickyActions: boolean | undefined;
|
|
9
|
+
readonly fillHeight: boolean | undefined;
|
|
10
|
+
readonly unsavedCount: number | undefined;
|
|
11
|
+
readonly railed: boolean;
|
|
12
|
+
readonly pinned: boolean;
|
|
13
|
+
readonly secondaryActions: ReactNode;
|
|
14
|
+
readonly renderedSecondary: ReactNode;
|
|
15
|
+
readonly renderedActions: ReactNode;
|
|
16
|
+
readonly hasNonPrimaryOverflow: boolean;
|
|
17
|
+
readonly fixedFooterStyle: CSSProperties | undefined;
|
|
18
|
+
};
|
|
19
|
+
export declare function unsavedStatusText(t: Translate, unsavedCount: number): string;
|
|
20
|
+
export declare function renderNarrowPinnedFooter(model: Pick<FormFooterModel, "t" | "testId" | "unsavedCount">, nodes: readonly ReactNode[], keyboardInset: number): ReactNode;
|
|
21
|
+
export declare function footerRowClassName(model: FormFooterModel): string;
|
|
22
|
+
export declare function renderUnsavedStatus(model: FormFooterModel, unsavedCount: number): ReactNode;
|
|
23
|
+
export declare function renderSecondaryActionsGroup(model: FormFooterModel): ReactNode;
|
|
24
|
+
export declare function renderPrimaryActionsGroup(model: FormFooterModel): ReactNode;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { cardFooterBorder } from "./card-styles.js";
|
|
4
|
+
import { NarrowPinnedFooter } from "./narrow-pinned-footer.js";
|
|
5
|
+
import { STICKY_FOOTER_SAFE_AREA_CLASS } from "./sticky-action-bar.js";
|
|
6
|
+
export function unsavedStatusText(t, unsavedCount) {
|
|
7
|
+
if (unsavedCount === 0)
|
|
8
|
+
return t("kumiko.form.noUnsavedChanges");
|
|
9
|
+
if (unsavedCount === 1)
|
|
10
|
+
return t("kumiko.form.unsaved.one");
|
|
11
|
+
return t("kumiko.form.unsaved.other", { count: unsavedCount });
|
|
12
|
+
}
|
|
13
|
+
export function renderNarrowPinnedFooter(model, nodes, keyboardInset) {
|
|
14
|
+
const { t, testId, unsavedCount } = model;
|
|
15
|
+
return (_jsx(NarrowPinnedFooter, { nodes: nodes, overflowLabel: unsavedCount !== undefined && unsavedCount > 0
|
|
16
|
+
? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
|
|
17
|
+
: t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
|
|
18
|
+
? unsavedStatusText(t, unsavedCount)
|
|
19
|
+
: undefined, testId: testId, keyboardInset: keyboardInset }));
|
|
20
|
+
}
|
|
21
|
+
export function footerRowClassName(model) {
|
|
22
|
+
const { pinned, chromeless, fillHeight, railed } = model;
|
|
23
|
+
return cn(pinned
|
|
24
|
+
? cn("flex h-14 shrink-0 items-center justify-end gap-2 border-t border-border bg-card px-6", !railed && "md:pl-10")
|
|
25
|
+
: "flex flex-col-reverse gap-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:py-4", !pinned && !chromeless && "px-[var(--card-padding)]", !pinned && !chromeless && cardFooterBorder, !pinned && fillHeight === true && "shrink-0");
|
|
26
|
+
}
|
|
27
|
+
export function renderUnsavedStatus(model, unsavedCount) {
|
|
28
|
+
const { t, testId, pinned } = model;
|
|
29
|
+
return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-unsaved` : undefined, className: cn("mr-auto flex min-w-0 items-center gap-2 text-[13px]",
|
|
30
|
+
// Only the stacked (flex-col-reverse) footer moves the status above the
|
|
31
|
+
// actions; the pinned single-row footer keeps it on the left.
|
|
32
|
+
!pinned && "max-sm:order-last", unsavedCount > 0 ? "text-foreground-secondary" : "text-muted-foreground"), children: [unsavedCount > 0 && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-status-active", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: unsavedStatusText(t, unsavedCount) })] }));
|
|
33
|
+
}
|
|
34
|
+
export function renderSecondaryActionsGroup(model) {
|
|
35
|
+
const { testId, pinned, hasNonPrimaryOverflow, renderedSecondary } = model;
|
|
36
|
+
return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions-secondary` : undefined, className: cn(
|
|
37
|
+
// min-h-11: link-variant buttons (Copy link) are h-auto and would fall
|
|
38
|
+
// below the 24px touch-target minimum on narrow viewports.
|
|
39
|
+
"flex flex-wrap items-center gap-2 max-sm:[&_button]:min-h-11 max-sm:[&_button]:px-2 max-sm:[&_button]:text-xs", pinned && hasNonPrimaryOverflow && "mr-auto"), children: renderedSecondary }));
|
|
40
|
+
}
|
|
41
|
+
export function renderPrimaryActionsGroup(model) {
|
|
42
|
+
const { testId, pinned, stickyActions, fixedFooterStyle, renderedActions } = model;
|
|
43
|
+
return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, style: fixedFooterStyle, className: cn("flex items-center gap-2", pinned
|
|
44
|
+
? "shrink-0 max-md:[&>button]:min-h-11"
|
|
45
|
+
: "flex-wrap max-sm:w-full max-sm:[&>button]:flex-1 max-sm:[&>button]:min-h-11 sm:ml-auto",
|
|
46
|
+
// Below sm (640px): pin only the primary action to the viewport
|
|
47
|
+
// bottom instead of normal flow, so a virtual keyboard shrinking
|
|
48
|
+
// the viewport can't push it out of reach. `fixed`
|
|
49
|
+
// escapes the card's `overflow-hidden` (only transform/filter/
|
|
50
|
+
// contain ancestors trap it, confirmed against
|
|
51
|
+
// AppLayout/SidebarInset — neither sets those).
|
|
52
|
+
stickyActions === true &&
|
|
53
|
+
!pinned &&
|
|
54
|
+
cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }));
|
|
55
|
+
}
|
|
@@ -2,13 +2,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Children, Fragment, isValidElement, useContext, useEffect, useRef, useState, } from "react";
|
|
3
3
|
import { STICKY_PRIMARY_ACTION_PROP, useTranslation, } from "../../index.js";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { footerRowClassName, renderNarrowPinnedFooter, renderPrimaryActionsGroup, renderSecondaryActionsGroup, renderUnsavedStatus, } from "./form-footer-parts.js";
|
|
5
6
|
import { screenPaddingClassName, screenWidthClassName } from "./layout.js";
|
|
6
|
-
import {
|
|
7
|
-
import { STICKY_FOOTER_SAFE_AREA_CLASS, STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
|
|
7
|
+
import { STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
|
|
8
8
|
import { useKeyboardInset } from "./use-keyboard-inset.js";
|
|
9
9
|
import { useIsBelowSmViewport } from "./use-narrow-viewport.js";
|
|
10
10
|
import "../react-css-custom-properties.js";
|
|
11
|
-
import {
|
|
11
|
+
import { cardHeaderBorder } from "./card-styles.js";
|
|
12
12
|
import { InsideFormContext, ScreenFormContext, ScreenWidthContext } from "./contexts.js";
|
|
13
13
|
// Extension sections that render their own <form> via BareFormProvider
|
|
14
14
|
// (legacy custom-form pattern, e.g. ChangeEmailSection) land
|
|
@@ -138,21 +138,7 @@ function flattenActionNodes(node) {
|
|
|
138
138
|
}
|
|
139
139
|
return Children.toArray(node);
|
|
140
140
|
}
|
|
141
|
-
function
|
|
142
|
-
if (unsavedCount === 0)
|
|
143
|
-
return t("kumiko.form.noUnsavedChanges");
|
|
144
|
-
if (unsavedCount === 1)
|
|
145
|
-
return t("kumiko.form.unsaved.one");
|
|
146
|
-
return t("kumiko.form.unsaved.other", { count: unsavedCount });
|
|
147
|
-
}
|
|
148
|
-
// Footer wrapper for DefaultForm's card and chromeless layouts alike — only
|
|
149
|
-
// the card-derived horizontal padding/border differs between them.
|
|
150
|
-
export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
|
|
151
|
-
const t = useTranslation();
|
|
152
|
-
const isBelowSm = useIsBelowSmViewport();
|
|
153
|
-
const keyboardInset = useKeyboardInset();
|
|
154
|
-
if (actions === undefined && secondaryActions === undefined)
|
|
155
|
-
return null;
|
|
141
|
+
function splitStickyActions(stickyActions, actions, secondaryActions) {
|
|
156
142
|
// Only split when sticky: the non-sticky (regular, non-wizard) footer must
|
|
157
143
|
// reproduce the previous DOM exactly (form-action-bar.test.tsx pins
|
|
158
144
|
// `-actions`/`-actions-secondary` content 1:1 to the `actions`/
|
|
@@ -170,6 +156,17 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
|
|
|
170
156
|
const renderedActions = stickyActions === true ? (hasPrimaryAction ? primaryActionNodes : actions) : actions;
|
|
171
157
|
const hasNonPrimaryOverflow = nonPrimaryActionNodes !== undefined && nonPrimaryActionNodes.length > 0;
|
|
172
158
|
const renderedSecondary = stickyActions === true && hasPrimaryAction ? (_jsxs(_Fragment, { children: [secondaryActions, nonPrimaryActionNodes] })) : (secondaryActions);
|
|
159
|
+
return { renderedActions, renderedSecondary, hasNonPrimaryOverflow };
|
|
160
|
+
}
|
|
161
|
+
// Footer wrapper for DefaultForm's card and chromeless layouts alike — only
|
|
162
|
+
// the card-derived horizontal padding/border differs between them.
|
|
163
|
+
export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
|
|
164
|
+
const t = useTranslation();
|
|
165
|
+
const isBelowSm = useIsBelowSmViewport();
|
|
166
|
+
const keyboardInset = useKeyboardInset();
|
|
167
|
+
if (actions === undefined && secondaryActions === undefined)
|
|
168
|
+
return null;
|
|
169
|
+
const { renderedActions, renderedSecondary, hasNonPrimaryOverflow } = splitStickyActions(stickyActions, actions, secondaryActions);
|
|
173
170
|
const pinned = isPinnedFooter(stickyActions, fillHeight);
|
|
174
171
|
const fixedFooterStyle = stickyActions === true && !pinned && isBelowSm && keyboardInset > 0
|
|
175
172
|
? { bottom: keyboardInset }
|
|
@@ -177,32 +174,25 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
|
|
|
177
174
|
// Below sm the pinned footer swaps to NarrowPinnedFooter; the subtree remounts, so a
|
|
178
175
|
// confirm dialog owned by an action button closes on a breakpoint change (rotate, resize).
|
|
179
176
|
if (pinned && isBelowSm) {
|
|
180
|
-
return (
|
|
181
|
-
? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
|
|
182
|
-
: t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
|
|
183
|
-
? unsavedStatusText(t, unsavedCount)
|
|
184
|
-
: undefined, testId: testId, keyboardInset: keyboardInset }));
|
|
177
|
+
return renderNarrowPinnedFooter({ t, testId, unsavedCount }, [...flattenActionNodes(secondaryActions), ...flattenActionNodes(actions)], keyboardInset);
|
|
185
178
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
stickyActions === true &&
|
|
204
|
-
!pinned &&
|
|
205
|
-
cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }))] }));
|
|
179
|
+
const model = {
|
|
180
|
+
t,
|
|
181
|
+
testId,
|
|
182
|
+
chromeless,
|
|
183
|
+
stickyActions,
|
|
184
|
+
fillHeight,
|
|
185
|
+
unsavedCount,
|
|
186
|
+
railed,
|
|
187
|
+
pinned,
|
|
188
|
+
secondaryActions,
|
|
189
|
+
renderedSecondary,
|
|
190
|
+
renderedActions,
|
|
191
|
+
hasNonPrimaryOverflow,
|
|
192
|
+
fixedFooterStyle,
|
|
193
|
+
};
|
|
194
|
+
return (_jsxs("div", { className: footerRowClassName(model), children: [unsavedCount !== undefined && renderUnsavedStatus(model, unsavedCount), (secondaryActions !== undefined || hasNonPrimaryOverflow) &&
|
|
195
|
+
renderSecondaryActionsGroup(model), renderedActions !== undefined && renderPrimaryActionsGroup(model)] }));
|
|
206
196
|
}
|
|
207
197
|
export const SECTION_NAV_MIN_ITEMS = 3;
|
|
208
198
|
// "On this page" navigation next to a screen form. The active entry follows
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Checkbox } from "../ui/checkbox.js";
|
|
3
|
+
import { Switch } from "../ui/switch.js";
|
|
4
|
+
import { ComboboxInput } from "./combobox.js";
|
|
5
|
+
import { RadioListSelect, SegmentedSelect, withInlineDisabledHint, withoutSegmentedSelectPlaceholder, } from "./field.js";
|
|
6
|
+
import { stateProps } from "./input-optional-props.js";
|
|
7
|
+
import { resolveSelectPresentation } from "./input-select-presentation.js";
|
|
8
|
+
// layout="inline" (checkbox lists like MultiSelectCheckboxes, the
|
|
9
|
+
// BooleanField widget) keeps the checkbox optics; the standard form
|
|
10
|
+
// field (render-field.tsx, layout="stacked") gets the switch.
|
|
11
|
+
export const renderBooleanKind = (props, { booleanLayout }) => 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" }));
|
|
12
|
+
function normalizeSelectOptions(options) {
|
|
13
|
+
return options.map((o) => (typeof o === "string" ? { value: o, label: o } : o));
|
|
14
|
+
}
|
|
15
|
+
// Visual consolidation: all selects go through ComboboxInput
|
|
16
|
+
// (cmdk + Radix popover). Before, there were two paths: Radix
|
|
17
|
+
// Select for `kind:"select"` and cmdk for `kind:"combobox"`. Three
|
|
18
|
+
// visually different variants (single select, combobox single, combobox
|
|
19
|
+
// multi) became unmanageable. With the merge the combobox is the only
|
|
20
|
+
// implementation: the search input is present even for 4-item status
|
|
21
|
+
// selects, a deliberately accepted UX consequence for style consistency.
|
|
22
|
+
export const renderSelectKind = (props) => {
|
|
23
|
+
const comboOptions = normalizeSelectOptions(props.options);
|
|
24
|
+
// The radio group can't render the unselected placeholder as its own
|
|
25
|
+
// segment, so it counts and renders on the real options only — the
|
|
26
|
+
// dropdown below keeps comboOptions (placeholder included) since it
|
|
27
|
+
// has a genuine "nothing selected" row.
|
|
28
|
+
// An author who asks for `display: "radio"` explicitly declared the ""
|
|
29
|
+
// option as a real choice (e.g. "Any"), so it stays a segment.
|
|
30
|
+
const radioGroupOptions = props.display === "radio" ? comboOptions : withoutSegmentedSelectPlaceholder(comboOptions);
|
|
31
|
+
const presentation = resolveSelectPresentation(props, radioGroupOptions);
|
|
32
|
+
if (presentation === "radioList" && radioGroupOptions.length > 0) {
|
|
33
|
+
return (_jsx(RadioListSelect, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: radioGroupOptions, ...(props.radioVariant !== undefined && { variant: props.radioVariant }), ...stateProps(props) }));
|
|
34
|
+
}
|
|
35
|
+
if (presentation === "segmented" && radioGroupOptions.length > 0) {
|
|
36
|
+
return (_jsx(SegmentedSelect, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: radioGroupOptions, ...stateProps(props) }));
|
|
37
|
+
}
|
|
38
|
+
// The dropdown shows a disabled option's hint inline, so it stays readable in the trigger and search.
|
|
39
|
+
return (_jsx(ComboboxInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: comboOptions.map(withInlineDisabledHint), ...stateProps(props) }));
|
|
40
|
+
};
|
|
41
|
+
function comboboxBaseProps(props) {
|
|
42
|
+
return {
|
|
43
|
+
id: props.id,
|
|
44
|
+
name: props.name,
|
|
45
|
+
options: props.options,
|
|
46
|
+
...stateProps(props),
|
|
47
|
+
...(props.placeholder !== undefined && { placeholder: props.placeholder }),
|
|
48
|
+
...(props.searchPlaceholder !== undefined && {
|
|
49
|
+
searchPlaceholder: props.searchPlaceholder,
|
|
50
|
+
}),
|
|
51
|
+
...(props.emptyText !== undefined && { emptyText: props.emptyText }),
|
|
52
|
+
...(props.onSearchChange !== undefined && { onSearchChange: props.onSearchChange }),
|
|
53
|
+
...(props.loading !== undefined && { loading: props.loading }),
|
|
54
|
+
...(props.onCreate !== undefined && { onCreate: props.onCreate }),
|
|
55
|
+
...(props.createLabel !== undefined && { createLabel: props.createLabel }),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Discriminated union per `multiple`; we split
|
|
59
|
+
// TS-side into two branches so ComboboxInput's single/multi variants
|
|
60
|
+
// render with the right types.
|
|
61
|
+
export const renderComboboxKind = (props) => {
|
|
62
|
+
const baseProps = comboboxBaseProps(props);
|
|
63
|
+
if (props.multiple === true) {
|
|
64
|
+
return _jsx(ComboboxInput, { ...baseProps, multiple: true, value: props.value, onChange: props.onChange });
|
|
65
|
+
}
|
|
66
|
+
return _jsx(ComboboxInput, { ...baseProps, value: props.value, onChange: props.onChange });
|
|
67
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { FileUploadInput } from "./file-upload.js";
|
|
3
|
+
import { FilesDropzoneInput } from "./files-dropzone.js";
|
|
4
|
+
import { uploadTargetProps } from "./input-optional-props.js";
|
|
5
|
+
export const renderFileKind = (props) => (_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 }), ...uploadTargetProps(props), ...(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 }) }));
|
|
6
|
+
export const renderFilesDropzoneKind = (props) => (_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 }), ...uploadTargetProps(props), ...(props.testId !== undefined && { testId: props.testId }) }));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { FIELD_ICON_CLASS, fieldIconFor, unitSuffixId, withInputAddons } from "./field.js";
|
|
4
|
+
import { stateProps } from "./input-optional-props.js";
|
|
5
|
+
import { MoneyInput } from "./money-input.js";
|
|
6
|
+
import { NumberInput } from "./number-input.js";
|
|
7
|
+
export const renderNumberKind = (props) => {
|
|
8
|
+
const NumberIcon = fieldIconFor(props.icon);
|
|
9
|
+
// Icon, input and unit share this one box, and the grid cell sizes the
|
|
10
|
+
// box rather than the bare input: anchored to a wider wrapper, the unit
|
|
11
|
+
// would sit next to a narrower input instead of inside it.
|
|
12
|
+
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 }))] }));
|
|
13
|
+
return withInputAddons(props, numberField);
|
|
14
|
+
};
|
|
15
|
+
export const renderRangeKind = (props, { common }) => (_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" }));
|
|
16
|
+
export const renderMoneyKind = (props) => (_jsx(MoneyInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, currency: props.currency ?? "EUR", ...(props.locale !== undefined && { locale: props.locale }), ...stateProps(props) }));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type StateInput = {
|
|
2
|
+
readonly disabled?: boolean;
|
|
3
|
+
readonly required?: boolean;
|
|
4
|
+
readonly hasError?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type LocaleBoundsInput = {
|
|
7
|
+
readonly locale?: string;
|
|
8
|
+
readonly min?: string;
|
|
9
|
+
readonly max?: string;
|
|
10
|
+
};
|
|
11
|
+
type UploadTargetInput = {
|
|
12
|
+
readonly entityType?: string;
|
|
13
|
+
readonly uploadActionForm?: string;
|
|
14
|
+
readonly entityId?: string;
|
|
15
|
+
readonly fieldName?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function stateProps({ disabled, required, hasError }: StateInput): StateInput;
|
|
18
|
+
export declare function localeBoundsProps({ locale, min, max }: LocaleBoundsInput): LocaleBoundsInput;
|
|
19
|
+
export declare function uploadTargetProps({ entityType, uploadActionForm, entityId, fieldName, }: UploadTargetInput): UploadTargetInput;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The inputs below take optional props that must be absent, not `undefined`,
|
|
2
|
+
// so a primitive's own defaults still apply.
|
|
3
|
+
export function stateProps({ disabled, required, hasError }) {
|
|
4
|
+
return {
|
|
5
|
+
...(disabled !== undefined && { disabled }),
|
|
6
|
+
...(required !== undefined && { required }),
|
|
7
|
+
...(hasError !== undefined && { hasError }),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function localeBoundsProps({ locale, min, max }) {
|
|
11
|
+
return {
|
|
12
|
+
...(locale !== undefined && { locale }),
|
|
13
|
+
...(min !== undefined && { min }),
|
|
14
|
+
...(max !== undefined && { max }),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function uploadTargetProps({ entityType, uploadActionForm, entityId, fieldName, }) {
|
|
18
|
+
return {
|
|
19
|
+
...(entityType !== undefined && { entityType }),
|
|
20
|
+
...(uploadActionForm !== undefined && { uploadActionForm }),
|
|
21
|
+
...(entityId !== undefined && { entityId }),
|
|
22
|
+
...(fieldName !== undefined && { fieldName }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defaultSelectPresentation } from "./field.tsx";
|
|
2
|
+
type SelectPresentation = ReturnType<typeof defaultSelectPresentation>;
|
|
3
|
+
type PresentationOption = {
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly description?: string;
|
|
6
|
+
readonly group?: string;
|
|
7
|
+
};
|
|
8
|
+
type PresentationRequest = {
|
|
9
|
+
readonly radioVariant?: "list" | "card";
|
|
10
|
+
readonly display?: "radio" | "dropdown";
|
|
11
|
+
};
|
|
12
|
+
export declare function resolveSelectPresentation(request: PresentationRequest, options: readonly PresentationOption[]): SelectPresentation;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defaultSelectPresentation } from "./field.js";
|
|
2
|
+
import { hasDescriptionOrGroup } from "./option-groups.js";
|
|
3
|
+
// An explicit `display` is an author decision and outranks the heuristic in
|
|
4
|
+
// both directions — a requested radio group renders as one even when the
|
|
5
|
+
// options outnumber the heuristic's threshold.
|
|
6
|
+
function requestedSelectPresentation({ radioVariant, display }, options) {
|
|
7
|
+
if (radioVariant === "card")
|
|
8
|
+
return "radioList";
|
|
9
|
+
if (display === "radio")
|
|
10
|
+
return "segmented";
|
|
11
|
+
if (display === "dropdown")
|
|
12
|
+
return "dropdown";
|
|
13
|
+
return defaultSelectPresentation(options);
|
|
14
|
+
}
|
|
15
|
+
// A segmented control has no room for a description line or group headings.
|
|
16
|
+
export function resolveSelectPresentation(request, options) {
|
|
17
|
+
const requested = requestedSelectPresentation(request, options);
|
|
18
|
+
return requested === "segmented" && hasDescriptionOrGroup(options) ? "radioList" : requested;
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContextType, ReactNode } from "react";
|
|
2
|
+
import type { InputProps } from "../../index.ts";
|
|
3
|
+
import type { FieldDescriptionContext, FieldLayoutContext } from "./contexts.tsx";
|
|
4
|
+
import type { useFocusOnMount } from "./use-focus-on-mount.ts";
|
|
5
|
+
export type InputKind = InputProps["kind"];
|
|
6
|
+
export type PropsOfKind<K extends InputKind> = InputProps extends infer P ? P extends {
|
|
7
|
+
readonly kind: infer PK;
|
|
8
|
+
} ? K extends PK ? P : never : never : never;
|
|
9
|
+
export declare function commonInputAttrs(props: InputProps, describedBy: ContextType<typeof FieldDescriptionContext>): {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly disabled: boolean | undefined;
|
|
13
|
+
readonly "aria-describedby": string | undefined;
|
|
14
|
+
readonly "aria-required": boolean | undefined;
|
|
15
|
+
readonly "aria-invalid": true | undefined;
|
|
16
|
+
};
|
|
17
|
+
export type DefaultInputShared = {
|
|
18
|
+
readonly common: ReturnType<typeof commonInputAttrs>;
|
|
19
|
+
readonly textInputRef: ReturnType<typeof useFocusOnMount>;
|
|
20
|
+
readonly booleanLayout: ContextType<typeof FieldLayoutContext>;
|
|
21
|
+
};
|
|
22
|
+
export type KindRenderer<K extends InputKind> = (props: PropsOfKind<K>, shared: DefaultInputShared) => ReactNode;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function commonInputAttrs(props, describedBy) {
|
|
2
|
+
return {
|
|
3
|
+
id: props.id,
|
|
4
|
+
name: props.name,
|
|
5
|
+
disabled: props.disabled,
|
|
6
|
+
"aria-describedby": describedBy,
|
|
7
|
+
"aria-required": props.required,
|
|
8
|
+
"aria-invalid": props.hasError === true ? true : undefined,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { KindRenderer } from "./input-shared.ts";
|
|
2
|
+
export declare const renderDateKind: KindRenderer<"date">;
|
|
3
|
+
export declare const renderTimestampKind: KindRenderer<"timestamp">;
|
|
4
|
+
export declare const renderLocatedTimestampKind: KindRenderer<"locatedTimestamp">;
|
|
5
|
+
export declare const renderTzKind: KindRenderer<"tz">;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DateInput } from "./date-input.js";
|
|
3
|
+
import { localeBoundsProps, stateProps } from "./input-optional-props.js";
|
|
4
|
+
import { LocatedTimestampInput } from "./located-timestamp-input.js";
|
|
5
|
+
import { TimestampInput } from "./timestamp-input.js";
|
|
6
|
+
import { TzInput } from "./tz-input.js";
|
|
7
|
+
export const renderDateKind = (props) => (_jsx(DateInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...localeBoundsProps(props), ...stateProps(props) }));
|
|
8
|
+
export const renderTimestampKind = (props) => (_jsx(TimestampInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...(props.wallClock !== undefined && { wallClock: props.wallClock }), ...localeBoundsProps(props), ...stateProps(props) }));
|
|
9
|
+
export const renderLocatedTimestampKind = (props) => (_jsx(LocatedTimestampInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...localeBoundsProps(props), ...stateProps(props) }));
|
|
10
|
+
export const renderTzKind = (props) => (_jsx(TzInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...stateProps(props) }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { KindRenderer } from "./input-shared.ts";
|
|
2
|
+
export declare const renderTextKind: KindRenderer<"text">;
|
|
3
|
+
export declare const renderCodeKind: KindRenderer<"code">;
|
|
4
|
+
export declare const renderEmailKind: KindRenderer<"email">;
|
|
5
|
+
export declare const renderMonthKind: KindRenderer<"month">;
|
|
6
|
+
export declare const renderPasswordKind: KindRenderer<"password">;
|
|
7
|
+
export declare const renderTextareaKind: KindRenderer<"textarea">;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { Input as UiInput } from "../ui/input.js";
|
|
4
|
+
import { Textarea } from "../ui/textarea.js";
|
|
5
|
+
import { CodeInput } from "./code-input.js";
|
|
6
|
+
import { fieldIconFor, textareaMinHeight, withFieldIcon, withInputAddons, withTrailingAction, } from "./field.js";
|
|
7
|
+
export const renderTextKind = (props, { common, textInputRef }) => withInputAddons(props, withFieldIcon(props.icon, _jsx(UiInput, { type: "text", ref: textInputRef, ...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) })));
|
|
8
|
+
export const renderCodeKind = (props) => _jsx(CodeInput, { ...props });
|
|
9
|
+
export const renderEmailKind = (props, { common }) => (_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" }));
|
|
10
|
+
export const renderMonthKind = (props, { common }) => (_jsx(UiInput, { type: "month", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value) }));
|
|
11
|
+
export const renderPasswordKind = (props, { common }) => 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);
|
|
12
|
+
function textareaShortcutProps({ onKeyDown, onSubmitShortcut }) {
|
|
13
|
+
if (onSubmitShortcut === undefined && onKeyDown === undefined)
|
|
14
|
+
return {};
|
|
15
|
+
return {
|
|
16
|
+
...(onSubmitShortcut !== undefined && {
|
|
17
|
+
"aria-keyshortcuts": "Control+Enter Meta+Enter",
|
|
18
|
+
}),
|
|
19
|
+
onKeyDown: (e) => {
|
|
20
|
+
onKeyDown?.(e);
|
|
21
|
+
// Lets a caller that already handles Ctrl/Cmd+Enter (e.g. an
|
|
22
|
+
// "Enter sends" handler that ignores only Shift) opt out of the
|
|
23
|
+
// shortcut instead of submitting twice.
|
|
24
|
+
if (e.defaultPrevented)
|
|
25
|
+
return;
|
|
26
|
+
if (onSubmitShortcut === undefined || e.key !== "Enter" || !(e.metaKey || e.ctrlKey))
|
|
27
|
+
return;
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
onSubmitShortcut();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export const renderTextareaKind = (props, { common }) => (_jsx(Textarea, { ...common, ...props.dataAttributes, readOnly: props.readOnly, value: props.value, onChange: (e) => props.onChange(e.target.value), rows: props.rows ?? 4, className: "resize-y", ...(props.placeholder !== undefined && { placeholder: props.placeholder }), ...(props.rows !== undefined && { style: textareaMinHeight(props.rows) }), ...textareaShortcutProps(props) }));
|