@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
|
@@ -1,171 +1,45 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
1
|
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 { CodeInput } from "./code-input.js";
|
|
9
|
-
import { ComboboxInput } from "./combobox.js";
|
|
10
|
-
import { DateInput } from "./date-input.js";
|
|
11
|
-
import { FileUploadInput } from "./file-upload.js";
|
|
12
|
-
import { FilesDropzoneInput } from "./files-dropzone.js";
|
|
13
|
-
import { LocatedTimestampInput } from "./located-timestamp-input.js";
|
|
14
|
-
import { MoneyInput } from "./money-input.js";
|
|
15
|
-
import { NumberInput } from "./number-input.js";
|
|
16
|
-
import { hasDescriptionOrGroup } from "./option-groups.js";
|
|
17
|
-
import { TimestampInput } from "./timestamp-input.js";
|
|
18
|
-
import { TzInput } from "./tz-input.js";
|
|
19
|
-
import { useFocusOnMount } from "./use-focus-on-mount.js";
|
|
20
2
|
import "../react-css-custom-properties.js";
|
|
21
3
|
import { FieldDescriptionContext, FieldLayoutContext } from "./contexts.js";
|
|
22
|
-
import {
|
|
4
|
+
import { renderBooleanKind, renderComboboxKind, renderSelectKind } from "./input-choice-kinds.js";
|
|
5
|
+
import { renderFileKind, renderFilesDropzoneKind } from "./input-file-kinds.js";
|
|
6
|
+
import { renderMoneyKind, renderNumberKind, renderRangeKind } from "./input-number-kinds.js";
|
|
7
|
+
import { commonInputAttrs, } from "./input-shared.js";
|
|
8
|
+
import { renderDateKind, renderLocatedTimestampKind, renderTimestampKind, renderTzKind, } from "./input-temporal-kinds.js";
|
|
9
|
+
import { renderCodeKind, renderEmailKind, renderMonthKind, renderPasswordKind, renderTextareaKind, renderTextKind, } from "./input-text-kinds.js";
|
|
10
|
+
import { useFocusOnMount } from "./use-focus-on-mount.js";
|
|
11
|
+
const KIND_RENDERERS = {
|
|
12
|
+
text: renderTextKind,
|
|
13
|
+
code: renderCodeKind,
|
|
14
|
+
email: renderEmailKind,
|
|
15
|
+
month: renderMonthKind,
|
|
16
|
+
password: renderPasswordKind,
|
|
17
|
+
number: renderNumberKind,
|
|
18
|
+
range: renderRangeKind,
|
|
19
|
+
boolean: renderBooleanKind,
|
|
20
|
+
file: renderFileKind,
|
|
21
|
+
image: renderFileKind,
|
|
22
|
+
"files-dropzone": renderFilesDropzoneKind,
|
|
23
|
+
date: renderDateKind,
|
|
24
|
+
select: renderSelectKind,
|
|
25
|
+
combobox: renderComboboxKind,
|
|
26
|
+
money: renderMoneyKind,
|
|
27
|
+
timestamp: renderTimestampKind,
|
|
28
|
+
locatedTimestamp: renderLocatedTimestampKind,
|
|
29
|
+
tz: renderTzKind,
|
|
30
|
+
textarea: renderTextareaKind,
|
|
31
|
+
};
|
|
32
|
+
function renderKind(kind, props, shared) {
|
|
33
|
+
return KIND_RENDERERS[kind](props, shared);
|
|
34
|
+
}
|
|
23
35
|
export function DefaultInput(props) {
|
|
24
36
|
// Vendored ui/input + ui/checkbox style errors via `aria-invalid`
|
|
25
37
|
// themselves, no manual border-destructive needed anymore.
|
|
26
38
|
const booleanLayout = useContext(FieldLayoutContext);
|
|
27
39
|
const describedBy = useContext(FieldDescriptionContext);
|
|
28
40
|
const textInputRef = useFocusOnMount(props.kind === "text" ? props.autoFocus : undefined);
|
|
29
|
-
const common =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"aria-describedby": describedBy,
|
|
34
|
-
"aria-required": props.required,
|
|
35
|
-
"aria-invalid": props.hasError === true ? true : undefined,
|
|
36
|
-
};
|
|
37
|
-
switch (props.kind) {
|
|
38
|
-
case "text":
|
|
39
|
-
return 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) })));
|
|
40
|
-
case "code":
|
|
41
|
-
return _jsx(CodeInput, { ...props });
|
|
42
|
-
case "email":
|
|
43
|
-
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" }));
|
|
44
|
-
case "month":
|
|
45
|
-
return (_jsx(UiInput, { type: "month", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value) }));
|
|
46
|
-
case "password":
|
|
47
|
-
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);
|
|
48
|
-
case "number": {
|
|
49
|
-
const NumberIcon = fieldIconFor(props.icon);
|
|
50
|
-
// Icon, input and unit share this one box, and the grid cell sizes the
|
|
51
|
-
// box rather than the bare input: anchored to a wider wrapper, the unit
|
|
52
|
-
// would sit next to a narrower input instead of inside it.
|
|
53
|
-
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 }))] }));
|
|
54
|
-
return withInputAddons(props, numberField);
|
|
55
|
-
}
|
|
56
|
-
case "range":
|
|
57
|
-
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" }));
|
|
58
|
-
case "boolean":
|
|
59
|
-
// layout="inline" (checkbox lists like MultiSelectCheckboxes, the
|
|
60
|
-
// BooleanField widget) keeps the checkbox optics; the standard form
|
|
61
|
-
// field (render-field.tsx, layout="stacked") gets the switch.
|
|
62
|
-
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" }));
|
|
63
|
-
case "file":
|
|
64
|
-
case "image":
|
|
65
|
-
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 && {
|
|
66
|
-
uploadActionForm: props.uploadActionForm,
|
|
67
|
-
}), ...(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 }) }));
|
|
68
|
-
case "files-dropzone":
|
|
69
|
-
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 && {
|
|
70
|
-
uploadActionForm: props.uploadActionForm,
|
|
71
|
-
}), ...(props.entityId !== undefined && { entityId: props.entityId }), ...(props.fieldName !== undefined && { fieldName: props.fieldName }), ...(props.testId !== undefined && { testId: props.testId }) }));
|
|
72
|
-
case "date":
|
|
73
|
-
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 }) }));
|
|
74
|
-
case "select": {
|
|
75
|
-
// Visual consolidation: all selects go through ComboboxInput
|
|
76
|
-
// (cmdk + Radix popover). Before, there were two paths: Radix
|
|
77
|
-
// Select for `kind:"select"` and cmdk for `kind:"combobox"`. Three
|
|
78
|
-
// visually different variants (single select, combobox single, combobox
|
|
79
|
-
// multi) became unmanageable. With the merge the combobox is the only
|
|
80
|
-
// implementation: the search input is present even for 4-item status
|
|
81
|
-
// selects, a deliberately accepted UX consequence for style consistency.
|
|
82
|
-
const comboOptions = props.options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
|
|
83
|
-
// The dropdown shows a disabled option's hint inline, so it stays readable in the trigger and search.
|
|
84
|
-
const dropdownOptions = comboOptions.map(withInlineDisabledHint);
|
|
85
|
-
// The radio group can't render the unselected placeholder as its own
|
|
86
|
-
// segment, so it counts and renders on the real options only — the
|
|
87
|
-
// dropdown below keeps comboOptions (placeholder included) since it
|
|
88
|
-
// has a genuine "nothing selected" row.
|
|
89
|
-
// An author who asks for `display: "radio"` explicitly declared the ""
|
|
90
|
-
// option as a real choice (e.g. "Any"), so it stays a segment.
|
|
91
|
-
const radioGroupOptions = props.display === "radio" ? comboOptions : withoutSegmentedSelectPlaceholder(comboOptions);
|
|
92
|
-
// An explicit `display` is an author decision and outranks the
|
|
93
|
-
// heuristic in both directions — a requested radio group renders as
|
|
94
|
-
// one even when the options outnumber the heuristic's threshold.
|
|
95
|
-
const requestedPresentation = props.radioVariant === "card"
|
|
96
|
-
? "radioList"
|
|
97
|
-
: props.display === "radio"
|
|
98
|
-
? "segmented"
|
|
99
|
-
: props.display === "dropdown"
|
|
100
|
-
? "dropdown"
|
|
101
|
-
: defaultSelectPresentation(radioGroupOptions);
|
|
102
|
-
// A segmented control has no room for a description line or group headings.
|
|
103
|
-
const presentation = requestedPresentation === "segmented" && hasDescriptionOrGroup(radioGroupOptions)
|
|
104
|
-
? "radioList"
|
|
105
|
-
: requestedPresentation;
|
|
106
|
-
if (presentation === "radioList" && radioGroupOptions.length > 0) {
|
|
107
|
-
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 }) }));
|
|
108
|
-
}
|
|
109
|
-
if (presentation === "segmented" && radioGroupOptions.length > 0) {
|
|
110
|
-
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 }) }));
|
|
111
|
-
}
|
|
112
|
-
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 }) }));
|
|
113
|
-
}
|
|
114
|
-
case "combobox": {
|
|
115
|
-
// Discriminated union per `multiple`; we split
|
|
116
|
-
// TS-side into two branches so ComboboxInput's single/multi variants
|
|
117
|
-
// render with the right types.
|
|
118
|
-
const baseProps = {
|
|
119
|
-
id: props.id,
|
|
120
|
-
name: props.name,
|
|
121
|
-
options: props.options,
|
|
122
|
-
...(props.disabled !== undefined && { disabled: props.disabled }),
|
|
123
|
-
...(props.required !== undefined && { required: props.required }),
|
|
124
|
-
...(props.hasError !== undefined && { hasError: props.hasError }),
|
|
125
|
-
...(props.placeholder !== undefined && { placeholder: props.placeholder }),
|
|
126
|
-
...(props.searchPlaceholder !== undefined && {
|
|
127
|
-
searchPlaceholder: props.searchPlaceholder,
|
|
128
|
-
}),
|
|
129
|
-
...(props.emptyText !== undefined && { emptyText: props.emptyText }),
|
|
130
|
-
...(props.onSearchChange !== undefined && { onSearchChange: props.onSearchChange }),
|
|
131
|
-
...(props.loading !== undefined && { loading: props.loading }),
|
|
132
|
-
...(props.onCreate !== undefined && { onCreate: props.onCreate }),
|
|
133
|
-
...(props.createLabel !== undefined && { createLabel: props.createLabel }),
|
|
134
|
-
};
|
|
135
|
-
if (props.multiple === true) {
|
|
136
|
-
return (_jsx(ComboboxInput, { ...baseProps, multiple: true, value: props.value, onChange: props.onChange }));
|
|
137
|
-
}
|
|
138
|
-
return _jsx(ComboboxInput, { ...baseProps, value: props.value, onChange: props.onChange });
|
|
139
|
-
}
|
|
140
|
-
case "money":
|
|
141
|
-
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 }) }));
|
|
142
|
-
case "timestamp":
|
|
143
|
-
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 }) }));
|
|
144
|
-
case "locatedTimestamp":
|
|
145
|
-
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 }) }));
|
|
146
|
-
case "textarea": {
|
|
147
|
-
const rows = props.rows;
|
|
148
|
-
const onSubmitShortcut = props.onSubmitShortcut;
|
|
149
|
-
const onKeyDown = props.onKeyDown;
|
|
150
|
-
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) && {
|
|
151
|
-
...(onSubmitShortcut !== undefined && {
|
|
152
|
-
"aria-keyshortcuts": "Control+Enter Meta+Enter",
|
|
153
|
-
}),
|
|
154
|
-
onKeyDown: (e) => {
|
|
155
|
-
onKeyDown?.(e);
|
|
156
|
-
// Lets a caller that already handles Ctrl/Cmd+Enter (e.g. an
|
|
157
|
-
// "Enter sends" handler that ignores only Shift) opt out of the
|
|
158
|
-
// shortcut instead of submitting twice.
|
|
159
|
-
if (e.defaultPrevented)
|
|
160
|
-
return;
|
|
161
|
-
if (onSubmitShortcut === undefined || e.key !== "Enter" || !(e.metaKey || e.ctrlKey))
|
|
162
|
-
return;
|
|
163
|
-
e.preventDefault();
|
|
164
|
-
onSubmitShortcut();
|
|
165
|
-
},
|
|
166
|
-
}) }));
|
|
167
|
-
}
|
|
168
|
-
case "tz":
|
|
169
|
-
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 }) }));
|
|
170
|
-
}
|
|
41
|
+
const common = commonInputAttrs(props, describedBy);
|
|
42
|
+
if (!Object.hasOwn(KIND_RENDERERS, props.kind))
|
|
43
|
+
return undefined;
|
|
44
|
+
return renderKind(props.kind, props, { common, textInputRef, booleanLayout });
|
|
171
45
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type TimeseriesPoint = {
|
|
2
|
+
readonly atMs: number;
|
|
3
|
+
/** null = outage/no value → falls to the baseline for a visible drop. */
|
|
4
|
+
readonly value: number | null;
|
|
5
|
+
};
|
|
6
|
+
export declare const SINGLE_POINT_RADIUS = 3;
|
|
7
|
+
export declare function clampDotY(y: number, height: number): number;
|
|
8
|
+
export type ChartTone = "positive" | "negative" | "active" | "neutral";
|
|
9
|
+
export declare function chartToneColor(tone: ChartTone): string;
|
|
10
|
+
export type ChartSeries = {
|
|
11
|
+
readonly key: string;
|
|
12
|
+
/** Translated by the caller. */
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly points: readonly TimeseriesPoint[];
|
|
15
|
+
};
|
|
16
|
+
export type ChartMarker = {
|
|
17
|
+
readonly atMs: number;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
/** Colors the pin and, on StackedAreaChart, draws a dashed guide line in the plot. */
|
|
20
|
+
readonly color?: string;
|
|
21
|
+
/** Translated by the caller. Groups markers into one legend entry when `markerLegend` is "legend". */
|
|
22
|
+
readonly legendLabel?: string;
|
|
23
|
+
};
|
|
24
|
+
/** Unstacked line drawn over a StackedAreaChart on the same y scale. */
|
|
25
|
+
export type ChartLine = ChartSeries & {
|
|
26
|
+
readonly dashed?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type ToneMap = Readonly<Record<string, ChartTone>>;
|
|
29
|
+
/** Key -> raw CSS color; wins over the tone map. */
|
|
30
|
+
export type ColorMap = Readonly<Record<string, string>>;
|
|
31
|
+
export declare function chartColor(key: string, index: number, tones: ToneMap | undefined, colors?: ColorMap): string;
|
|
32
|
+
export declare function bucketTimes(series: readonly ChartSeries[]): readonly number[];
|
|
33
|
+
export declare function valueAt(series: ChartSeries, atMs: number): number;
|
|
34
|
+
export declare function seriesTotal(series: ChartSeries): number;
|
|
35
|
+
export declare function percent(fraction: number): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const SINGLE_POINT_RADIUS = 3;
|
|
2
|
+
export function clampDotY(y, height) {
|
|
3
|
+
return Math.max(SINGLE_POINT_RADIUS, Math.min(height - SINGLE_POINT_RADIUS, y));
|
|
4
|
+
}
|
|
5
|
+
const CHART_TONE_VAR = {
|
|
6
|
+
positive: "var(--color-status-ok)",
|
|
7
|
+
negative: "var(--color-status-bad)",
|
|
8
|
+
active: "var(--color-status-active)",
|
|
9
|
+
neutral: "var(--color-status-neutral)",
|
|
10
|
+
};
|
|
11
|
+
export function chartToneColor(tone) {
|
|
12
|
+
return CHART_TONE_VAR[tone];
|
|
13
|
+
}
|
|
14
|
+
const CHART_FALLBACK_TONES = ["active", "positive", "negative", "neutral"];
|
|
15
|
+
export function chartColor(key, index, tones, colors) {
|
|
16
|
+
const color = colors?.[key];
|
|
17
|
+
if (color !== undefined)
|
|
18
|
+
return color;
|
|
19
|
+
const tone = tones?.[key] ?? CHART_FALLBACK_TONES[index % CHART_FALLBACK_TONES.length] ?? "active";
|
|
20
|
+
return CHART_TONE_VAR[tone];
|
|
21
|
+
}
|
|
22
|
+
export function bucketTimes(series) {
|
|
23
|
+
const times = new Set();
|
|
24
|
+
for (const s of series)
|
|
25
|
+
for (const p of s.points)
|
|
26
|
+
times.add(p.atMs);
|
|
27
|
+
return [...times].sort((a, b) => a - b);
|
|
28
|
+
}
|
|
29
|
+
export function valueAt(series, atMs) {
|
|
30
|
+
return series.points.find((p) => p.atMs === atMs)?.value ?? 0;
|
|
31
|
+
}
|
|
32
|
+
export function seriesTotal(series) {
|
|
33
|
+
return series.points.reduce((sum, p) => sum + (p.value ?? 0), 0);
|
|
34
|
+
}
|
|
35
|
+
export function percent(fraction) {
|
|
36
|
+
return `${(fraction * 100).toFixed(2)}%`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type ChartMarker } from "./chart-data.ts";
|
|
3
|
+
export declare function assignPinRows(fractions: readonly number[]): readonly number[];
|
|
4
|
+
export type KeyedMarker = ChartMarker & {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function sortMarkers(markers: readonly ChartMarker[]): readonly KeyedMarker[];
|
|
8
|
+
export declare function MarkerPins({ markers, windowStartMs, windowEndMs, numbered, formatMarkerTime, className, }: {
|
|
9
|
+
readonly className?: string;
|
|
10
|
+
readonly markers: readonly ChartMarker[];
|
|
11
|
+
readonly windowStartMs: number;
|
|
12
|
+
readonly windowEndMs: number;
|
|
13
|
+
/** false: plain colored dots whose tooltip carries label and time. */
|
|
14
|
+
readonly numbered?: boolean;
|
|
15
|
+
readonly formatMarkerTime?: (atMs: number) => string;
|
|
16
|
+
}): ReactNode;
|
|
17
|
+
export declare function markerLegendItems(markers: readonly ChartMarker[]): readonly {
|
|
18
|
+
readonly key: string;
|
|
19
|
+
readonly label: string;
|
|
20
|
+
readonly color: string;
|
|
21
|
+
}[];
|
|
22
|
+
export declare function MarkerLegend({ markers, formatMarkerTime, className, }: {
|
|
23
|
+
readonly className?: string;
|
|
24
|
+
readonly markers: readonly ChartMarker[];
|
|
25
|
+
readonly formatMarkerTime?: (atMs: number) => string;
|
|
26
|
+
}): ReactNode;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { percent } from "./chart-data.js";
|
|
4
|
+
// A 16px pin on a ~230px-wide plot (390px viewport) collides when markers sit
|
|
5
|
+
// within ~7% of each other, so close pins are staggered into extra rows.
|
|
6
|
+
const MARKER_PIN_MIN_GAP_FRACTION = 0.07;
|
|
7
|
+
const MARKER_PIN_MAX_ROWS = 3;
|
|
8
|
+
const MARKER_PIN_ROW_HEIGHT_PX = 18;
|
|
9
|
+
export function assignPinRows(fractions) {
|
|
10
|
+
const lastFractionPerRow = [];
|
|
11
|
+
return fractions.map((fraction) => {
|
|
12
|
+
let row = lastFractionPerRow.findIndex((last) => fraction - last >= MARKER_PIN_MIN_GAP_FRACTION);
|
|
13
|
+
if (row === -1) {
|
|
14
|
+
row =
|
|
15
|
+
lastFractionPerRow.length < MARKER_PIN_MAX_ROWS
|
|
16
|
+
? lastFractionPerRow.length
|
|
17
|
+
: MARKER_PIN_MAX_ROWS - 1;
|
|
18
|
+
}
|
|
19
|
+
lastFractionPerRow[row] = fraction;
|
|
20
|
+
return row;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
// Two markers may share atMs and label, so the occurrence count keeps React keys unique.
|
|
24
|
+
export function sortMarkers(markers) {
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
return [...markers]
|
|
27
|
+
.sort((a, b) => a.atMs - b.atMs)
|
|
28
|
+
.map((marker) => {
|
|
29
|
+
const base = `${marker.atMs}-${marker.label}`;
|
|
30
|
+
const occurrence = seen.get(base) ?? 0;
|
|
31
|
+
seen.set(base, occurrence + 1);
|
|
32
|
+
return { ...marker, key: `${base}-${occurrence}` };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function MarkerPins({ markers, windowStartMs, windowEndMs, numbered = true, formatMarkerTime, className, }) {
|
|
36
|
+
const span = Math.max(1, windowEndMs - windowStartMs);
|
|
37
|
+
const sorted = sortMarkers(markers);
|
|
38
|
+
const fractions = sorted.map((marker) => Math.max(0, Math.min(1, (marker.atMs - windowStartMs) / span)));
|
|
39
|
+
const rows = assignPinRows(fractions);
|
|
40
|
+
const rowCount = Math.max(1, ...rows.map((row) => row + 1));
|
|
41
|
+
return (_jsx("div", { "aria-hidden": "true", className: cn("relative mt-1", className), style: { height: rowCount * MARKER_PIN_ROW_HEIGHT_PX }, children: sorted.map((marker, i) => (_jsx("span", { "data-testid": "chart-marker-pin", title: numbered
|
|
42
|
+
? undefined
|
|
43
|
+
: formatMarkerTime === undefined
|
|
44
|
+
? marker.label
|
|
45
|
+
: `${marker.label} · ${formatMarkerTime(marker.atMs)}`, className: cn("absolute flex -translate-x-1/2 items-center justify-center rounded-full text-[10px] font-medium text-background", numbered ? "size-4" : "size-2", marker.color === undefined && "bg-foreground"), style: {
|
|
46
|
+
left: percent(fractions[i] ?? 0),
|
|
47
|
+
top: (rows[i] ?? 0) * MARKER_PIN_ROW_HEIGHT_PX + (numbered ? 0 : 4),
|
|
48
|
+
...(marker.color !== undefined && { backgroundColor: marker.color }),
|
|
49
|
+
}, children: numbered ? i + 1 : null }, marker.key))) }));
|
|
50
|
+
}
|
|
51
|
+
export function markerLegendItems(markers) {
|
|
52
|
+
const items = new Map();
|
|
53
|
+
for (const marker of markers) {
|
|
54
|
+
const label = marker.legendLabel ?? marker.label;
|
|
55
|
+
const color = marker.color ?? "var(--color-foreground)";
|
|
56
|
+
const key = `marker-${label}-${color}`;
|
|
57
|
+
if (!items.has(key))
|
|
58
|
+
items.set(key, { key, label, color });
|
|
59
|
+
}
|
|
60
|
+
return [...items.values()];
|
|
61
|
+
}
|
|
62
|
+
export function MarkerLegend({ markers, formatMarkerTime, className, }) {
|
|
63
|
+
return (_jsx("ol", { className: cn("flex flex-col gap-1 text-xs text-muted-foreground", className), children: sortMarkers(markers).map((marker, i) => (_jsxs("li", { "data-testid": "chart-marker-item", className: "flex gap-2", children: [_jsx("span", { className: "w-4 shrink-0 text-center tabular-nums text-foreground", style: marker.color !== undefined ? { color: marker.color } : undefined, children: i + 1 }), _jsx("span", { children: marker.label }), formatMarkerTime !== undefined && (_jsx("span", { className: "ml-auto tabular-nums", children: formatMarkerTime(marker.atMs) }))] }, marker.key))) }));
|
|
64
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export declare function ChartLegend({ items, formatValue, }: {
|
|
3
|
+
readonly items: readonly {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
/** Defaults to the key; set when the key is prefixed to avoid collisions. */
|
|
6
|
+
readonly testIdKey?: string;
|
|
7
|
+
readonly label: string;
|
|
8
|
+
readonly color: string;
|
|
9
|
+
/** Omitted: no sum next to the label. */
|
|
10
|
+
readonly total?: number;
|
|
11
|
+
readonly swatch?: "area" | "line" | "dashed-line";
|
|
12
|
+
}[];
|
|
13
|
+
readonly formatValue: (value: number) => string;
|
|
14
|
+
}): ReactNode;
|
|
15
|
+
export declare function evenYTickValues(max: number): readonly number[];
|
|
16
|
+
export declare function YTicks({ values, formatValue, className, }: {
|
|
17
|
+
readonly values: readonly number[];
|
|
18
|
+
readonly formatValue: (value: number) => string;
|
|
19
|
+
readonly className?: string;
|
|
20
|
+
}): ReactNode;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
export function ChartLegend({ items, formatValue, }) {
|
|
4
|
+
return (_jsx("ul", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => (_jsxs("li", { "data-testid": `chart-legend-${item.testIdKey ?? item.key}`, className: "flex items-center gap-1.5", children: [item.swatch === undefined || item.swatch === "area" ? (_jsx("span", { "aria-hidden": "true", className: "size-2 rounded-[2px]", style: { backgroundColor: item.color } })) : (_jsx("span", { "aria-hidden": "true", className: "w-3 border-t-2", style: {
|
|
5
|
+
borderColor: item.color,
|
|
6
|
+
borderTopStyle: item.swatch === "dashed-line" ? "dashed" : "solid",
|
|
7
|
+
} })), _jsx("span", { children: item.label }), item.total !== undefined && (_jsx("span", { className: "tabular-nums text-foreground", children: formatValue(item.total) }))] }, item.key))) }));
|
|
8
|
+
}
|
|
9
|
+
const Y_TICK_COUNT = 3;
|
|
10
|
+
export function evenYTickValues(max) {
|
|
11
|
+
return Array.from({ length: Y_TICK_COUNT }, (_, i) => (max / (Y_TICK_COUNT - 1)) * i);
|
|
12
|
+
}
|
|
13
|
+
export function YTicks({ values, formatValue, className, }) {
|
|
14
|
+
return (_jsx("div", { "aria-hidden": "true", className: cn("flex flex-col-reverse justify-between text-right text-[11px] tabular-nums text-muted-foreground", className), children: values.map((value, i) => (_jsx("span", { children: formatValue(value) }, i))) }));
|
|
15
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type
|
|
2
|
+
import { type ChartMarker, type ChartSeries, type ColorMap, type TimeseriesPoint, type ToneMap } from "./chart-data.ts";
|
|
3
3
|
import { type StatusTone } from "./status-badge.tsx";
|
|
4
4
|
export type StatusBarEntry = {
|
|
5
5
|
/** Stable key (z.B. ISO-Datum). */
|
|
@@ -31,11 +31,6 @@ export declare function smoothPath(pts: ReadonlyArray<{
|
|
|
31
31
|
readonly x: number;
|
|
32
32
|
readonly y: number;
|
|
33
33
|
}>): string;
|
|
34
|
-
export type TimeseriesPoint = {
|
|
35
|
-
readonly atMs: number;
|
|
36
|
-
/** null = outage/no value → falls to the baseline for a visible drop. */
|
|
37
|
-
readonly value: number | null;
|
|
38
|
-
};
|
|
39
34
|
export type TimeseriesYAxis = {
|
|
40
35
|
/** Number of gridlines including the zero baseline (min 2). Tick values are
|
|
41
36
|
* rounded to 1/2/5 x 10^n steps and the y-scale reaches the top tick. */
|
|
@@ -97,26 +92,6 @@ export declare function TimeseriesChart({ points, windowStartMs, windowEndMs, to
|
|
|
97
92
|
readonly emptyContent?: ReactNode;
|
|
98
93
|
readonly testId?: string;
|
|
99
94
|
}): ReactNode;
|
|
100
|
-
export type ChartTone = "positive" | "negative" | "active" | "neutral";
|
|
101
|
-
export declare function chartToneColor(tone: ChartTone): string;
|
|
102
|
-
export type ChartSeries = {
|
|
103
|
-
readonly key: string;
|
|
104
|
-
/** Translated by the caller. */
|
|
105
|
-
readonly label: string;
|
|
106
|
-
readonly points: readonly TimeseriesPoint[];
|
|
107
|
-
};
|
|
108
|
-
export type ChartMarker = {
|
|
109
|
-
readonly atMs: number;
|
|
110
|
-
readonly label: string;
|
|
111
|
-
/** Colors the pin and, on StackedAreaChart, draws a dashed guide line in the plot. */
|
|
112
|
-
readonly color?: string;
|
|
113
|
-
/** Translated by the caller. Groups markers into one legend entry when `markerLegend` is "legend". */
|
|
114
|
-
readonly legendLabel?: string;
|
|
115
|
-
};
|
|
116
|
-
/** Unstacked line drawn over a StackedAreaChart on the same y scale. */
|
|
117
|
-
export type ChartLine = ChartSeries & {
|
|
118
|
-
readonly dashed?: boolean;
|
|
119
|
-
};
|
|
120
95
|
export type SegmentBarRow = {
|
|
121
96
|
readonly key: string;
|
|
122
97
|
readonly label: string;
|
|
@@ -127,10 +102,6 @@ export type SegmentBarRow = {
|
|
|
127
102
|
readonly value: number;
|
|
128
103
|
}[];
|
|
129
104
|
};
|
|
130
|
-
type ToneMap = Readonly<Record<string, ChartTone>>;
|
|
131
|
-
/** Key -> raw CSS color; wins over the tone map. */
|
|
132
|
-
type ColorMap = Readonly<Record<string, string>>;
|
|
133
|
-
export declare function assignPinRows(fractions: readonly number[]): readonly number[];
|
|
134
105
|
/** Vertically stacked bars per bucket (x axis = bucket, not time).
|
|
135
106
|
* The last bucket carries `todayLabel` when it contains `windowEndMs` and
|
|
136
107
|
* has day granularity. */
|
|
@@ -158,51 +129,7 @@ export declare function SegmentBarChart({ rows, tones, colors, ariaLabel, format
|
|
|
158
129
|
readonly formatValue?: (value: number) => string;
|
|
159
130
|
readonly testId?: string;
|
|
160
131
|
}): ReactNode;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
export
|
|
164
|
-
|
|
165
|
-
readonly windowStartMs: number;
|
|
166
|
-
readonly windowEndMs: number;
|
|
167
|
-
readonly todayMs?: number;
|
|
168
|
-
readonly tones?: ToneMap;
|
|
169
|
-
readonly colors?: ColorMap;
|
|
170
|
-
readonly markers?: readonly ChartMarker[];
|
|
171
|
-
/** Drawn over the bands, not stacked. A `null` value breaks the line. */
|
|
172
|
-
readonly lines?: readonly ChartLine[];
|
|
173
|
-
/** Range presets anchored at today; uncontrolled, the widget renders the switch itself. */
|
|
174
|
-
readonly ranges?: StackedAreaRanges;
|
|
175
|
-
/** Uncontrolled start window; "from-today" ignores the default range and starts at today. */
|
|
176
|
-
readonly initialWindow?: StackedAreaInitialWindow;
|
|
177
|
-
/** Scrubber under the plot to drag the visible window; starts at today without `ranges`. */
|
|
178
|
-
readonly brush?: boolean;
|
|
179
|
-
readonly windowSelection?: StackedAreaWindowSelection;
|
|
180
|
-
/** Makes the window controlled: no own range switch, `windowSelection` is the only source. */
|
|
181
|
-
readonly onWindowSelectionChange?: (selection: StackedAreaWindowSelection) => void;
|
|
182
|
-
/** aria-labels of the two brush handles. */
|
|
183
|
-
readonly brushLabels?: {
|
|
184
|
-
readonly start: string;
|
|
185
|
-
readonly end: string;
|
|
186
|
-
};
|
|
187
|
-
readonly ariaLabel: string;
|
|
188
|
-
readonly todayLabel: string;
|
|
189
|
-
readonly formatBucketLabel: (atMs: number, format: StackedAreaDateFormat) => string;
|
|
190
|
-
readonly formatValue?: (value: number) => string;
|
|
191
|
-
/** y-axis tick labels only (legend and totals keep `formatValue`); defaults to `formatValue`. */
|
|
192
|
-
readonly formatAxisValue?: (value: number) => string;
|
|
193
|
-
readonly formatMarkerTime?: (atMs: number, format: StackedAreaDateFormat) => string;
|
|
194
|
-
/** Overrides the automatic format (month and year once the visible window spans 18 months). */
|
|
195
|
-
readonly dateFormat?: StackedAreaDateFormat;
|
|
196
|
-
/** Zone the x-axis ticks align to; must match the zone `formatBucketLabel` formats in. Default UTC. */
|
|
197
|
-
readonly timeZone?: string;
|
|
198
|
-
/** "legend": unnumbered pins with tooltip, one dashed legend entry per marker label and color. */
|
|
199
|
-
readonly markerLegend?: "list" | "legend";
|
|
200
|
-
/** Writes each marker label vertically next to its guide line on wide containers; narrow ones keep the pins. */
|
|
201
|
-
readonly markerLabels?: boolean;
|
|
202
|
-
/** Fixed width per bucket; the plot scrolls horizontally, y ticks stay put. */
|
|
203
|
-
readonly scrollable?: boolean;
|
|
204
|
-
readonly showLegendTotals?: boolean;
|
|
205
|
-
readonly emptyContent?: ReactNode;
|
|
206
|
-
readonly testId?: string;
|
|
207
|
-
}): ReactNode;
|
|
208
|
-
export {};
|
|
132
|
+
export type { ChartLine, ChartMarker, ChartSeries, ChartTone, TimeseriesPoint, } from "./chart-data.ts";
|
|
133
|
+
export { chartToneColor } from "./chart-data.ts";
|
|
134
|
+
export { assignPinRows } from "./chart-markers.tsx";
|
|
135
|
+
export { StackedAreaChart } from "./stacked-area-chart.tsx";
|