@lt-three/love3 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -1
- package/dist/native/analytics/animation.d.ts +4 -0
- package/dist/native/analytics/animation.js +6 -0
- package/dist/native/analytics/chart.types.d.ts +25 -0
- package/dist/native/analytics/chart.types.js +0 -0
- package/dist/native/analytics/line-chart.d.ts +4 -0
- package/dist/native/analytics/line-chart.ios.d.ts +4 -0
- package/dist/native/analytics/line-chart.ios.js +24 -0
- package/dist/native/analytics/line-chart.js +33 -0
- package/dist/native/analytics/metric-card.d.ts +10 -0
- package/dist/native/analytics/metric-card.js +29 -0
- package/dist/native/analytics/multi-line-chart.d.ts +18 -0
- package/dist/native/analytics/multi-line-chart.js +216 -0
- package/dist/native/analytics/pie-chart.d.ts +15 -0
- package/dist/native/analytics/pie-chart.ios.d.ts +4 -0
- package/dist/native/analytics/pie-chart.ios.js +24 -0
- package/dist/native/analytics/pie-chart.js +92 -0
- package/dist/native/analytics/single-number.d.ts +19 -0
- package/dist/native/analytics/single-number.js +101 -0
- package/dist/native/atom/avatar.d.ts +21 -0
- package/dist/native/atom/avatar.js +46 -0
- package/dist/native/atom/badge.d.ts +28 -0
- package/dist/native/atom/badge.js +51 -0
- package/dist/native/atom/button.d.ts +46 -0
- package/dist/native/atom/button.js +109 -0
- package/dist/native/atom/checkbox.d.ts +21 -0
- package/dist/native/atom/checkbox.js +47 -0
- package/dist/native/atom/colored-value.d.ts +12 -0
- package/dist/native/atom/colored-value.js +24 -0
- package/dist/native/atom/image.d.ts +21 -0
- package/dist/native/atom/image.js +72 -0
- package/dist/native/atom/label.d.ts +18 -0
- package/dist/native/atom/label.js +33 -0
- package/dist/native/atom/link.d.ts +18 -0
- package/dist/native/atom/link.js +25 -0
- package/dist/native/atom/readout.d.ts +22 -0
- package/dist/native/atom/readout.js +39 -0
- package/dist/native/atom/search-input.d.ts +12 -0
- package/dist/native/atom/search-input.js +36 -0
- package/dist/native/atom/select.d.ts +54 -0
- package/dist/native/atom/select.js +200 -0
- package/dist/native/atom/separator.d.ts +12 -0
- package/dist/native/atom/separator.js +16 -0
- package/dist/native/atom/skeleton.d.ts +12 -0
- package/dist/native/atom/skeleton.js +39 -0
- package/dist/native/atom/slider.d.ts +32 -0
- package/dist/native/atom/slider.js +75 -0
- package/dist/native/atom/spinner.d.ts +15 -0
- package/dist/native/atom/spinner.js +25 -0
- package/dist/native/atom/switch.d.ts +18 -0
- package/dist/native/atom/switch.js +52 -0
- package/dist/native/atom/symbol.d.ts +16 -0
- package/dist/native/atom/symbol.js +38 -0
- package/dist/native/atom/symbol.types.d.ts +25 -0
- package/dist/native/atom/symbol.types.js +49 -0
- package/dist/native/atom/text-field.d.ts +49 -0
- package/dist/native/atom/text-field.js +56 -0
- package/dist/native/atom/text.d.ts +2 -0
- package/dist/native/atom/text.js +3 -0
- package/dist/native/atom/theme-toggle.d.ts +21 -0
- package/dist/native/atom/theme-toggle.js +36 -0
- package/dist/native/atom/timezone-select.d.ts +16 -0
- package/dist/native/atom/timezone-select.js +28 -0
- package/dist/native/atom/typography.d.ts +13 -0
- package/dist/native/atom/typography.js +28 -0
- package/dist/native/base/button.d.ts +61 -0
- package/dist/native/base/button.js +92 -0
- package/dist/native/base/form.d.ts +72 -0
- package/dist/native/base/form.js +113 -0
- package/dist/native/base/glass-card.d.ts +15 -0
- package/dist/native/base/glass-card.js +38 -0
- package/dist/native/base/input.d.ts +29 -0
- package/dist/native/base/input.js +27 -0
- package/dist/native/base/sheet.d.ts +71 -0
- package/dist/native/base/sheet.js +187 -0
- package/dist/native/base/text.d.ts +14 -0
- package/dist/native/base/text.js +53 -0
- package/dist/native/form/checkbox.d.ts +13 -0
- package/dist/native/form/checkbox.js +55 -0
- package/dist/native/form/date-picker.d.ts +23 -0
- package/dist/native/form/date-picker.js +119 -0
- package/dist/native/form/form.d.ts +22 -0
- package/dist/native/form/form.js +44 -0
- package/dist/native/form/radio-group.d.ts +23 -0
- package/dist/native/form/radio-group.js +88 -0
- package/dist/native/form/select.d.ts +13 -0
- package/dist/native/form/select.js +45 -0
- package/dist/native/form/slider.d.ts +14 -0
- package/dist/native/form/slider.js +42 -0
- package/dist/native/form/switch.d.ts +9 -0
- package/dist/native/form/switch.js +59 -0
- package/dist/native/form/text-area.d.ts +11 -0
- package/dist/native/form/text-area.js +50 -0
- package/dist/native/form/text-input.d.ts +18 -0
- package/dist/native/form/text-input.js +90 -0
- package/dist/native/index.d.ts +1 -0
- package/dist/native/index.js +0 -0
- package/dist/native/layout/accordion.d.ts +25 -0
- package/dist/native/layout/accordion.js +54 -0
- package/dist/native/layout/card.d.ts +47 -0
- package/dist/native/layout/card.js +57 -0
- package/dist/native/layout/description.d.ts +16 -0
- package/dist/native/layout/description.js +32 -0
- package/dist/native/layout/error.d.ts +21 -0
- package/dist/native/layout/error.js +44 -0
- package/dist/native/layout/information.d.ts +25 -0
- package/dist/native/layout/information.js +35 -0
- package/dist/native/layout/locked.d.ts +38 -0
- package/dist/native/layout/locked.js +94 -0
- package/dist/native/layout/modal.d.ts +25 -0
- package/dist/native/layout/modal.js +93 -0
- package/dist/native/layout/not-found.d.ts +17 -0
- package/dist/native/layout/not-found.js +30 -0
- package/dist/native/layout/page-frame.d.ts +23 -0
- package/dist/native/layout/page-frame.js +46 -0
- package/dist/native/layout/panel.d.ts +18 -0
- package/dist/native/layout/panel.js +23 -0
- package/dist/native/layout/reorderable-list.d.ts +42 -0
- package/dist/native/layout/reorderable-list.js +52 -0
- package/dist/native/layout/sheet.d.ts +30 -0
- package/dist/native/layout/sheet.js +46 -0
- package/dist/native/layout/simple-dialog.d.ts +16 -0
- package/dist/native/layout/simple-dialog.js +20 -0
- package/dist/native/layout/simple-table.d.ts +50 -0
- package/dist/native/layout/simple-table.js +75 -0
- package/dist/native/layout/tab-bar.d.ts +33 -0
- package/dist/native/layout/tab-bar.js +48 -0
- package/dist/native/layout/tabs.d.ts +28 -0
- package/dist/native/layout/tabs.js +54 -0
- package/dist/native/layout/tuning-panel.d.ts +24 -0
- package/dist/native/layout/tuning-panel.js +246 -0
- package/dist/native/lib/autocomplete.d.ts +7 -0
- package/dist/native/lib/autocomplete.js +34 -0
- package/dist/native/lib/breakpoints.d.ts +14 -0
- package/dist/native/lib/breakpoints.js +17 -0
- package/dist/native/lib/chart-palette.d.ts +8 -0
- package/dist/native/lib/chart-palette.js +20 -0
- package/dist/native/lib/confirm.d.ts +48 -0
- package/dist/native/lib/confirm.js +69 -0
- package/dist/native/lib/control.d.ts +24 -0
- package/dist/native/lib/control.js +0 -0
- package/dist/native/lib/external-store.d.ts +16 -0
- package/dist/native/lib/external-store.js +30 -0
- package/dist/native/lib/keyboard.d.ts +15 -0
- package/dist/native/lib/keyboard.js +24 -0
- package/dist/native/lib/labels.d.ts +15 -0
- package/dist/native/lib/labels.js +17 -0
- package/dist/native/lib/loading.d.ts +10 -0
- package/dist/native/lib/loading.js +15 -0
- package/dist/native/lib/network.d.ts +11 -0
- package/dist/native/lib/network.js +20 -0
- package/dist/native/lib/scheme.d.ts +18 -0
- package/dist/native/lib/scheme.js +22 -0
- package/dist/native/lib/snackbar.d.ts +45 -0
- package/dist/native/lib/snackbar.js +59 -0
- package/dist/native/lib/theme.d.ts +34 -0
- package/dist/native/lib/theme.js +53 -0
- package/dist/native/lib/timezone.d.ts +26 -0
- package/dist/native/lib/timezone.js +95 -0
- package/dist/native/lib/token-color.d.ts +5 -0
- package/dist/native/lib/token-color.js +13 -0
- package/dist/native/lib/token-color.web.d.ts +4 -0
- package/dist/native/lib/token-color.web.js +7 -0
- package/dist/native/lib/tuning-context.d.ts +9 -0
- package/dist/native/lib/tuning-context.js +7 -0
- package/dist/native/lib/tuning.d.ts +24 -0
- package/dist/native/lib/tuning.js +42 -0
- package/dist/native/lib/typography.d.ts +10 -0
- package/dist/native/lib/typography.js +11 -0
- package/dist/native/lib/utils.d.ts +11 -0
- package/dist/native/lib/utils.js +21 -0
- package/dist/native/once/confirm.d.ts +7 -0
- package/dist/native/once/confirm.js +60 -0
- package/dist/native/once/error-boundary.d.ts +28 -0
- package/dist/native/once/error-boundary.js +55 -0
- package/dist/native/once/loading-lock.d.ts +8 -0
- package/dist/native/once/loading-lock.js +28 -0
- package/dist/native/once/offline-banner.d.ts +15 -0
- package/dist/native/once/offline-banner.js +35 -0
- package/dist/native/once/snackbar.d.ts +17 -0
- package/dist/native/once/snackbar.js +106 -0
- package/dist/native/styles.css +46 -0
- package/dist/shared/analytics.d.ts +71 -0
- package/dist/shared/analytics.js +0 -0
- package/dist/shared/button.d.ts +7 -0
- package/dist/shared/button.js +23 -0
- package/dist/shared/form.d.ts +45 -0
- package/dist/shared/form.js +46 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/tokens/contrast-pairs.d.ts +30 -0
- package/dist/tokens/contrast-pairs.js +404 -0
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/styles.gen.css +250 -0
- package/dist/tokens/tokens.gen.d.ts +141 -0
- package/dist/tokens/tokens.gen.js +206 -0
- package/dist/tuning/catalog.d.ts +27 -0
- package/dist/tuning/catalog.gen.d.ts +4 -0
- package/dist/tuning/catalog.gen.js +397 -0
- package/dist/tuning/catalog.js +128 -0
- package/dist/tuning/color.d.ts +20 -0
- package/dist/tuning/color.js +80 -0
- package/dist/tuning/css.d.ts +35 -0
- package/dist/tuning/css.js +147 -0
- package/dist/tuning/index.d.ts +8 -0
- package/dist/tuning/index.js +9 -0
- package/dist/tuning/model.d.ts +33 -0
- package/dist/tuning/model.js +154 -0
- package/dist/tuning/native.d.ts +35 -0
- package/dist/tuning/native.js +80 -0
- package/dist/tuning/schema.d.ts +69 -0
- package/dist/tuning/schema.js +247 -0
- package/dist/tuning/types.d.ts +75 -0
- package/dist/tuning/types.js +39 -0
- package/dist/web/analytics/line-chart.d.ts +20 -0
- package/dist/web/analytics/line-chart.js +28 -0
- package/dist/web/analytics/metric-card.d.ts +18 -0
- package/dist/web/analytics/metric-card.js +40 -0
- package/dist/web/analytics/multi-line-chart.d.ts +39 -0
- package/dist/web/analytics/multi-line-chart.js +345 -0
- package/dist/web/atom/avatar.d.ts +11 -0
- package/dist/web/atom/avatar.js +24 -0
- package/dist/web/atom/badge.d.ts +2 -0
- package/dist/web/atom/badge.js +3 -0
- package/dist/web/atom/button.d.ts +23 -0
- package/dist/web/atom/button.js +35 -0
- package/dist/web/atom/checkbox.d.ts +19 -0
- package/dist/web/atom/checkbox.js +33 -0
- package/dist/web/atom/colored-value.d.ts +13 -0
- package/dist/web/atom/colored-value.js +24 -0
- package/dist/web/atom/help.d.ts +10 -0
- package/dist/web/atom/help.js +18 -0
- package/dist/web/atom/label.d.ts +2 -0
- package/dist/web/atom/label.js +3 -0
- package/dist/web/atom/segmented-control.d.ts +39 -0
- package/dist/web/atom/segmented-control.js +42 -0
- package/dist/web/atom/select.d.ts +43 -0
- package/dist/web/atom/select.js +93 -0
- package/dist/web/atom/separator.d.ts +2 -0
- package/dist/web/atom/separator.js +3 -0
- package/dist/web/atom/skeleton.d.ts +2 -0
- package/dist/web/atom/skeleton.js +3 -0
- package/dist/web/atom/slider.d.ts +33 -0
- package/dist/web/atom/slider.js +70 -0
- package/dist/web/atom/spinner.d.ts +8 -0
- package/dist/web/atom/spinner.js +14 -0
- package/dist/web/atom/switch.d.ts +11 -0
- package/dist/web/atom/switch.js +10 -0
- package/dist/web/atom/text-area.d.ts +23 -0
- package/dist/web/atom/text-area.js +27 -0
- package/dist/web/atom/text-field.d.ts +18 -0
- package/dist/web/atom/text-field.js +22 -0
- package/dist/web/atom/toggle-chip.d.ts +22 -0
- package/dist/web/atom/toggle-chip.js +28 -0
- package/dist/web/atom/tooltip.d.ts +23 -0
- package/dist/web/atom/tooltip.js +26 -0
- package/dist/web/atom/typography.d.ts +6 -0
- package/dist/web/atom/typography.js +23 -0
- package/dist/web/base/accordion.d.ts +9 -0
- package/dist/web/base/accordion.js +44 -0
- package/dist/web/base/alert.d.ts +11 -0
- package/dist/web/base/alert.js +37 -0
- package/dist/web/base/avatar.d.ts +8 -0
- package/dist/web/base/avatar.js +29 -0
- package/dist/web/base/badge.d.ts +12 -0
- package/dist/web/base/badge.js +26 -0
- package/dist/web/base/breadcrumb.d.ts +16 -0
- package/dist/web/base/breadcrumb.js +72 -0
- package/dist/web/base/button.d.ts +12 -0
- package/dist/web/base/button.js +47 -0
- package/dist/web/base/card.d.ts +19 -0
- package/dist/web/base/card.js +74 -0
- package/dist/web/base/checkbox.d.ts +6 -0
- package/dist/web/base/checkbox.js +21 -0
- package/dist/web/base/dialog.d.ts +20 -0
- package/dist/web/base/dialog.js +92 -0
- package/dist/web/base/dropdown-menu.d.ts +29 -0
- package/dist/web/base/dropdown-menu.js +125 -0
- package/dist/web/base/field.d.ts +27 -0
- package/dist/web/base/field.js +118 -0
- package/dist/web/base/form.d.ts +38 -0
- package/dist/web/base/form.js +110 -0
- package/dist/web/base/input.d.ts +9 -0
- package/dist/web/base/input.js +23 -0
- package/dist/web/base/label.d.ts +6 -0
- package/dist/web/base/label.js +15 -0
- package/dist/web/base/pagination.d.ts +34 -0
- package/dist/web/base/pagination.js +90 -0
- package/dist/web/base/popover.d.ts +11 -0
- package/dist/web/base/popover.js +37 -0
- package/dist/web/base/radio-group.d.ts +7 -0
- package/dist/web/base/radio-group.js +27 -0
- package/dist/web/base/scroll-area.d.ts +10 -0
- package/dist/web/base/scroll-area.js +41 -0
- package/dist/web/base/select.d.ts +17 -0
- package/dist/web/base/select.js +96 -0
- package/dist/web/base/separator.d.ts +6 -0
- package/dist/web/base/separator.js +17 -0
- package/dist/web/base/sheet.d.ts +19 -0
- package/dist/web/base/sheet.js +88 -0
- package/dist/web/base/skeleton.d.ts +5 -0
- package/dist/web/base/skeleton.js +14 -0
- package/dist/web/base/slider.d.ts +9 -0
- package/dist/web/base/slider.js +39 -0
- package/dist/web/base/sonner.d.ts +9 -0
- package/dist/web/base/sonner.js +25 -0
- package/dist/web/base/spinner.d.ts +5 -0
- package/dist/web/base/spinner.js +17 -0
- package/dist/web/base/switch.d.ts +6 -0
- package/dist/web/base/switch.js +19 -0
- package/dist/web/base/table.d.ts +14 -0
- package/dist/web/base/table.js +68 -0
- package/dist/web/base/tabs.d.ts +9 -0
- package/dist/web/base/tabs.js +36 -0
- package/dist/web/base/textarea.d.ts +9 -0
- package/dist/web/base/textarea.js +23 -0
- package/dist/web/base/tooltip.d.ts +9 -0
- package/dist/web/base/tooltip.js +36 -0
- package/dist/web/form/checkbox.d.ts +35 -0
- package/dist/web/form/checkbox.js +91 -0
- package/dist/web/form/radio-group.d.ts +24 -0
- package/dist/web/form/radio-group.js +75 -0
- package/dist/web/form/select.d.ts +16 -0
- package/dist/web/form/select.js +44 -0
- package/dist/web/form/slider.d.ts +19 -0
- package/dist/web/form/slider.js +51 -0
- package/dist/web/form/switch.d.ts +11 -0
- package/dist/web/form/switch.js +50 -0
- package/dist/web/form/text-area.d.ts +10 -0
- package/dist/web/form/text-area.js +31 -0
- package/dist/web/form/text-input.d.ts +10 -0
- package/dist/web/form/text-input.js +42 -0
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +0 -0
- package/dist/web/layout/accordion.d.ts +26 -0
- package/dist/web/layout/accordion.js +29 -0
- package/dist/web/layout/alert-modal.d.ts +41 -0
- package/dist/web/layout/alert-modal.js +71 -0
- package/dist/web/layout/card.d.ts +17 -0
- package/dist/web/layout/card.js +35 -0
- package/dist/web/layout/description.d.ts +12 -0
- package/dist/web/layout/description.js +20 -0
- package/dist/web/layout/information.d.ts +36 -0
- package/dist/web/layout/information.js +53 -0
- package/dist/web/layout/modal.d.ts +16 -0
- package/dist/web/layout/modal.js +37 -0
- package/dist/web/layout/page-frame.d.ts +37 -0
- package/dist/web/layout/page-frame.js +79 -0
- package/dist/web/layout/scroll-area.d.ts +10 -0
- package/dist/web/layout/scroll-area.js +13 -0
- package/dist/web/layout/sheet.d.ts +32 -0
- package/dist/web/layout/sheet.js +68 -0
- package/dist/web/layout/simple-dialog.d.ts +11 -0
- package/dist/web/layout/simple-dialog.js +24 -0
- package/dist/web/layout/simple-table.d.ts +49 -0
- package/dist/web/layout/simple-table.js +58 -0
- package/dist/web/layout/tabs.d.ts +26 -0
- package/dist/web/layout/tabs.js +42 -0
- package/dist/web/layout/titled-modal.d.ts +32 -0
- package/dist/web/layout/titled-modal.js +58 -0
- package/dist/web/lib/css-vars.d.ts +14 -0
- package/dist/web/lib/css-vars.js +18 -0
- package/dist/web/lib/dialog.d.ts +13 -0
- package/dist/web/lib/dialog.js +0 -0
- package/dist/web/lib/form.d.ts +2 -0
- package/dist/web/lib/form.js +3 -0
- package/dist/web/lib/labels.d.ts +27 -0
- package/dist/web/lib/labels.js +29 -0
- package/dist/web/lib/modal-open-focus.d.ts +17 -0
- package/dist/web/lib/modal-open-focus.js +44 -0
- package/dist/web/lib/restore-focus.d.ts +12 -0
- package/dist/web/lib/restore-focus.js +32 -0
- package/dist/web/lib/utils.d.ts +4 -0
- package/dist/web/lib/utils.js +10 -0
- package/dist/web/styles.css +162 -0
- package/package.json +203 -4
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { LINE_CHART_DEFAULT_LABEL, LINE_CHART_EMPTY_TEXT, LINE_CHART_TABLE_POINT_LABEL, LINE_CHART_TABLE_SERIES_LABEL, LINE_CHART_TABLE_VALUE_LABEL, lineChartTableDropped, lineChartTableThinned } from "../lib/labels.js";
|
|
3
|
+
import { cssVars } from "../lib/css-vars.js";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { defineChart, dot, lineY } from "@tanstack/charts";
|
|
7
|
+
import { Chart } from "@tanstack/charts/react";
|
|
8
|
+
import { scaleLinear } from "@tanstack/charts/scales/linear";
|
|
9
|
+
import { tooltip } from "@tanstack/charts/tooltip";
|
|
10
|
+
|
|
11
|
+
//#region src/web/analytics/multi-line-chart.tsx
|
|
12
|
+
const DEFAULT_HEIGHT = 220;
|
|
13
|
+
/** The maximum number of rows per series in the table for screen readers. */
|
|
14
|
+
const MAX_TABLE_ROWS_PER_SERIES = 60;
|
|
15
|
+
const THEME = {
|
|
16
|
+
foreground: "var(--foreground)",
|
|
17
|
+
muted: "var(--muted-foreground)",
|
|
18
|
+
grid: "var(--line)",
|
|
19
|
+
background: "transparent",
|
|
20
|
+
palette: [
|
|
21
|
+
"var(--chart-1)",
|
|
22
|
+
"var(--chart-2)",
|
|
23
|
+
"var(--chart-3)",
|
|
24
|
+
"var(--chart-4)",
|
|
25
|
+
"var(--chart-5)"
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
/** Grid lines are thin 1px lines (`--line`). The library's default opacity of 0.11 is not applied, and the token colour is used as is. */
|
|
29
|
+
const GRID_LINE = {
|
|
30
|
+
stroke: "var(--line)",
|
|
31
|
+
strokeOpacity: 1,
|
|
32
|
+
strokeWidth: 1
|
|
33
|
+
};
|
|
34
|
+
/** The axis baseline is one step darker than the grid lines (`--line-strong`). */
|
|
35
|
+
const AXIS_LINE = {
|
|
36
|
+
stroke: "var(--line-strong)",
|
|
37
|
+
strokeOpacity: 1,
|
|
38
|
+
strokeWidth: 1
|
|
39
|
+
};
|
|
40
|
+
/** Do not fade tick label text (the default is 0.68). */
|
|
41
|
+
const TICK_LABELS = { opacity: 1 };
|
|
42
|
+
/** Focus when entering the figure (`svg tabindex=0`) by keyboard is the same opaque ring as other components (no low alpha; 2px offset). */
|
|
43
|
+
const FOCUS_RING = "[&_svg:focus-visible]:rounded-control [&_svg:focus-visible]:ring-2 [&_svg:focus-visible]:ring-ring [&_svg:focus-visible]:ring-offset-2 [&_svg:focus-visible]:ring-offset-background [&_svg:focus-visible]:outline-none";
|
|
44
|
+
const TOOLTIP_VARS = cssVars({
|
|
45
|
+
"--ts-chart-tooltip-background": "var(--overlay)",
|
|
46
|
+
"--ts-chart-tooltip-color": "var(--popover-foreground)",
|
|
47
|
+
"--ts-chart-tooltip-border": "1px solid var(--line)",
|
|
48
|
+
"--ts-chart-tooltip-border-radius": "var(--radius-control)",
|
|
49
|
+
"--ts-chart-tooltip-shadow": "var(--shadow-md)",
|
|
50
|
+
"--ts-chart-tooltip-padding": "8px 10px",
|
|
51
|
+
"--ts-chart-tooltip-font": "400 12px/16px var(--font-sans)"
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* A multi-series line chart. Same API as native's `analytics/multi-line-chart` (`shared/analytics.ts`).
|
|
55
|
+
* It is drawn with `@tanstack/charts` (SVG). Grid lines are thin 1px lines, the colours are `--chart-1` to `--chart-5`, and the tooltip is an overlay surface + a shadow.
|
|
56
|
+
*
|
|
57
|
+
* - **Non-numeric points are dropped**: points whose `y` (or `t`) is not finite (NaN, Infinity) are removed before building
|
|
58
|
+
* (they are not in the table for screen readers either, and the number removed is noted with the table). If everything is removed, `emptyText`
|
|
59
|
+
* - When every series has `t`, **the horizontal position is decided by time**. If even one lacks it, each series is placed at even intervals in its own order
|
|
60
|
+
* (that is fine for a single series; when overlaying series with different steps, provide `t`). A series with a single point is drawn as a dot
|
|
61
|
+
* - There are at most three x-axis ticks, at the first, middle and last positions (a number that does not overlap when there are few points). **The label comes from the nearest point of the first series**
|
|
62
|
+
* (when placing by time, a time that exists only in the second or later series does not become a label). It is placed at the point's position
|
|
63
|
+
* - There are three y-axis ticks, top, middle and bottom. So the line does not collapse even when all points have the same value, it is moved up and down only when the range is zero
|
|
64
|
+
* - The default of `formatValue` has **a number of digits that depends on the data's range** (ticks can be told apart even for values from 0 to 1). Ticks, the tooltip and the table for screen readers
|
|
65
|
+
* go through the same formatting. When you pass one, **keep the function reference stable** (a new function every time rebuilds the chart on every render and closes the tooltip).
|
|
66
|
+
* The same goes for `series` (pass a reference the parent does not recreate)
|
|
67
|
+
* - On hover and focus, the tooltip shows the value of each series' point at that position (arrow keys also move it by keyboard), with the series name and colour.
|
|
68
|
+
* **Outside the range where a series has points (up to half the point spacing at the ends), no row is shown for that series** (so a far-away point's value does not look like the value at that position).
|
|
69
|
+
* When the point's label differs from the title, it is added to the row
|
|
70
|
+
* - Reading: the figure is `role="img"` named by `accessibilityLabel` (a summary). The exact values are also held in a hidden table (`table`)
|
|
71
|
+
* - **Series are not told apart by colour alone**: the legend has the series names, and secondary series can be dashed
|
|
72
|
+
*/
|
|
73
|
+
function MultiLineChart({ series, height = DEFAULT_HEIGHT, formatValue, emptyText = LINE_CHART_EMPTY_TEXT, accessibilityLabel, style, className, legend = true, table = true }) {
|
|
74
|
+
const label = accessibilityLabel ?? defaultLabel(series);
|
|
75
|
+
const model = useMemo(() => buildModel(series, formatValue), [series, formatValue]);
|
|
76
|
+
if (model === null) return /* @__PURE__ */ jsx("div", {
|
|
77
|
+
"data-slot": "line-chart",
|
|
78
|
+
"data-empty": "",
|
|
79
|
+
style: {
|
|
80
|
+
minHeight: height,
|
|
81
|
+
...style
|
|
82
|
+
},
|
|
83
|
+
className: cn("flex items-center justify-center", className),
|
|
84
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
85
|
+
className: "text-muted-foreground text-sm",
|
|
86
|
+
children: emptyText
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
90
|
+
"data-slot": "line-chart",
|
|
91
|
+
style,
|
|
92
|
+
className: cn("flex min-w-0 flex-col gap-2", FOCUS_RING, className),
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ jsx(Chart, {
|
|
95
|
+
definition: model.definition,
|
|
96
|
+
height,
|
|
97
|
+
ariaLabel: label,
|
|
98
|
+
style: TOOLTIP_VARS
|
|
99
|
+
}),
|
|
100
|
+
legend ? /* @__PURE__ */ jsx("ul", {
|
|
101
|
+
"data-slot": "line-chart-legend",
|
|
102
|
+
className: "flex flex-wrap gap-x-4 gap-y-1 px-1",
|
|
103
|
+
children: series.map((entry) => /* @__PURE__ */ jsxs("li", {
|
|
104
|
+
className: "text-muted-foreground flex items-center gap-1.5 text-[11px]",
|
|
105
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
106
|
+
"aria-hidden": true,
|
|
107
|
+
className: "inline-block h-0.5 w-2.5",
|
|
108
|
+
style: {
|
|
109
|
+
background: entry.dashed === true ? `repeating-linear-gradient(90deg, ${entry.color} 0 4px, transparent 4px 6px)` : entry.color,
|
|
110
|
+
opacity: entry.points.length > 0 ? 1 : .4
|
|
111
|
+
}
|
|
112
|
+
}), entry.label]
|
|
113
|
+
}, entry.key))
|
|
114
|
+
}) : null,
|
|
115
|
+
table ? /* @__PURE__ */ jsx(DataTable, {
|
|
116
|
+
caption: label,
|
|
117
|
+
model
|
|
118
|
+
}) : null
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** The name when there is no summary. It lists the series names. */
|
|
123
|
+
function defaultLabel(series) {
|
|
124
|
+
const names = series.map((entry) => entry.label).filter((name) => name !== "");
|
|
125
|
+
return names.length === 0 ? LINE_CHART_DEFAULT_LABEL : `${names.join("・")}の${LINE_CHART_DEFAULT_LABEL}`;
|
|
126
|
+
}
|
|
127
|
+
/** Samples at even intervals down to at most `limit` items (the first and the last are always kept). */
|
|
128
|
+
function thin(items, limit) {
|
|
129
|
+
if (items.length <= limit) return [...items];
|
|
130
|
+
const picked = /* @__PURE__ */ new Set();
|
|
131
|
+
for (let i = 0; i < limit; i += 1) picked.add(Math.round(i * (items.length - 1) / (limit - 1)));
|
|
132
|
+
return [...picked].sort((a, b) => a - b).flatMap((index) => items[index] === void 0 ? [] : [items[index]]);
|
|
133
|
+
}
|
|
134
|
+
/** The table hidden for screen readers. It is not shown on screen (sr-only). Values go through the same formatting as the figure's ticks. */
|
|
135
|
+
function DataTable({ caption, model }) {
|
|
136
|
+
const total = model.series.reduce((sum, entry) => sum + entry.points.length, 0);
|
|
137
|
+
const shownSeries = model.series.map((entry) => ({
|
|
138
|
+
entry,
|
|
139
|
+
points: thin(entry.points, MAX_TABLE_ROWS_PER_SERIES)
|
|
140
|
+
}));
|
|
141
|
+
const shown = shownSeries.reduce((sum, item) => sum + item.points.length, 0);
|
|
142
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
143
|
+
"data-slot": "line-chart-table",
|
|
144
|
+
className: "sr-only",
|
|
145
|
+
children: [
|
|
146
|
+
model.dropped > 0 ? /* @__PURE__ */ jsx("p", { children: lineChartTableDropped(model.dropped) }) : null,
|
|
147
|
+
shown < total ? /* @__PURE__ */ jsx("p", { children: lineChartTableThinned(shown, total) }) : null,
|
|
148
|
+
/* @__PURE__ */ jsxs("table", { children: [
|
|
149
|
+
/* @__PURE__ */ jsx("caption", { children: caption }),
|
|
150
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
151
|
+
/* @__PURE__ */ jsx("th", {
|
|
152
|
+
scope: "col",
|
|
153
|
+
children: LINE_CHART_TABLE_SERIES_LABEL
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ jsx("th", {
|
|
156
|
+
scope: "col",
|
|
157
|
+
children: LINE_CHART_TABLE_POINT_LABEL
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ jsx("th", {
|
|
160
|
+
scope: "col",
|
|
161
|
+
children: "値"
|
|
162
|
+
})
|
|
163
|
+
] }) }),
|
|
164
|
+
/* @__PURE__ */ jsx("tbody", { children: shownSeries.flatMap(({ entry, points }) => points.map((point, index) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
165
|
+
/* @__PURE__ */ jsx("th", {
|
|
166
|
+
scope: "row",
|
|
167
|
+
children: entry.label
|
|
168
|
+
}),
|
|
169
|
+
/* @__PURE__ */ jsx("td", { children: String(point.x) }),
|
|
170
|
+
/* @__PURE__ */ jsx("td", { children: model.format(point.y) })
|
|
171
|
+
] }, `${entry.key}-${index.toString()}`))) })
|
|
172
|
+
] })
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** Draws only points whose `y` (and `t`) are finite. NaN and Infinity break the scale, so they are dropped before building. */
|
|
177
|
+
function isUsable(point) {
|
|
178
|
+
return Number.isFinite(point.y) && (point.t === void 0 || Number.isFinite(point.t));
|
|
179
|
+
}
|
|
180
|
+
function buildModel(series, formatValue) {
|
|
181
|
+
const cleaned = series.map((entry) => ({
|
|
182
|
+
...entry,
|
|
183
|
+
points: entry.points.filter(isUsable)
|
|
184
|
+
}));
|
|
185
|
+
const total = series.reduce((sum, entry) => sum + entry.points.length, 0);
|
|
186
|
+
const clean = cleaned.filter((entry) => entry.points.length > 0);
|
|
187
|
+
if (clean.length === 0) return null;
|
|
188
|
+
const kept = clean.reduce((sum, entry) => sum + entry.points.length, 0);
|
|
189
|
+
const built = buildDefinition(clean, formatValue);
|
|
190
|
+
return {
|
|
191
|
+
definition: built.definition,
|
|
192
|
+
series: clean,
|
|
193
|
+
dropped: total - kept,
|
|
194
|
+
format: built.format
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** The point nearest to a position. */
|
|
198
|
+
function nearestRow(entry, position) {
|
|
199
|
+
let best;
|
|
200
|
+
for (const row of entry) if (best === void 0 || Math.abs(row.x - position) < Math.abs(best.x - position)) best = row;
|
|
201
|
+
return best;
|
|
202
|
+
}
|
|
203
|
+
/** When every series has `t`, place by time. */
|
|
204
|
+
function isTimed(series) {
|
|
205
|
+
const times = series.flatMap((entry) => entry.points.map((point) => point.t));
|
|
206
|
+
return times.length > 0 && times.every((time) => time !== void 0);
|
|
207
|
+
}
|
|
208
|
+
/** The horizontal position of a point (by time when there is time, otherwise its order within the series, mapped to 0 to 1). */
|
|
209
|
+
function positionOf(timed, index, count, point) {
|
|
210
|
+
if (timed && point.t !== void 0) return point.t;
|
|
211
|
+
return count === 1 ? .5 : index / (count - 1);
|
|
212
|
+
}
|
|
213
|
+
function extent(values) {
|
|
214
|
+
let min = Infinity;
|
|
215
|
+
let max = -Infinity;
|
|
216
|
+
for (const value of values) {
|
|
217
|
+
if (value < min) min = value;
|
|
218
|
+
if (value > max) max = value;
|
|
219
|
+
}
|
|
220
|
+
return [min, max];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The default formatting. It takes the number of digits that lets the tick spacing (half the range, since there are three ticks, top, middle and bottom) be told apart, and adds one more digit (0 to 6 digits).
|
|
224
|
+
* Values from 0 to 1 get 2 digits, and a range of 100 or more gives integers. The tooltip and the table for screen readers use the same formatting.
|
|
225
|
+
*/
|
|
226
|
+
function autoFormat(low, high) {
|
|
227
|
+
const step = (high - low) / 2;
|
|
228
|
+
const digits = step > 0 && Number.isFinite(step) ? Math.min(6, Math.max(0, Math.ceil(-Math.log10(step)) + 1)) : 0;
|
|
229
|
+
return (value) => (value === 0 ? 0 : value).toFixed(digits);
|
|
230
|
+
}
|
|
231
|
+
/** How far outside a series' point range its row is still shown (half the point spacing; for a single point, 2% of the x-axis width). */
|
|
232
|
+
function reachOf(entry, domainWidth) {
|
|
233
|
+
if (entry.length < 2) return domainWidth * .02;
|
|
234
|
+
const [min, max] = extent(entry.map((row) => row.x));
|
|
235
|
+
return (max - min) / (entry.length - 1) / 2;
|
|
236
|
+
}
|
|
237
|
+
function buildDefinition(series, formatValue) {
|
|
238
|
+
const timed = isTimed(series);
|
|
239
|
+
const rows = series.map((entry) => entry.points.map((point, index) => ({
|
|
240
|
+
x: positionOf(timed, index, entry.points.length, point),
|
|
241
|
+
y: point.y,
|
|
242
|
+
label: String(point.x),
|
|
243
|
+
series: entry.label,
|
|
244
|
+
color: entry.color
|
|
245
|
+
})));
|
|
246
|
+
const [min, max] = extent(series.flatMap((entry) => entry.points.map((point) => point.y)));
|
|
247
|
+
const span = max - min || Math.max(Math.abs(max), 1);
|
|
248
|
+
const low = max === min ? min - span / 2 : min;
|
|
249
|
+
const high = max === min ? max + span / 2 : max;
|
|
250
|
+
const mid = low / 2 + high / 2;
|
|
251
|
+
const format = formatValue ?? autoFormat(low, high);
|
|
252
|
+
const [xMin, xMax] = extent(rows.flatMap((entry) => entry.map((row) => row.x)));
|
|
253
|
+
const xDomain = timed ? xMax === xMin ? [xMin - .5, xMax + .5] : [xMin, xMax] : [0, 1];
|
|
254
|
+
const domainWidth = xDomain[1] - xDomain[0];
|
|
255
|
+
const reference = rows[0] ?? [];
|
|
256
|
+
const tickRows = [
|
|
257
|
+
xDomain[0],
|
|
258
|
+
(xDomain[0] + xDomain[1]) / 2,
|
|
259
|
+
xDomain[1]
|
|
260
|
+
].map((position) => nearestRow(reference, position)).filter((row, index, all) => row !== void 0 && all.indexOf(row) === index);
|
|
261
|
+
const xTicks = tickRows.map((row) => row.x);
|
|
262
|
+
const labelAt = (position) => tickRows.find((row) => row.x === position)?.label ?? "";
|
|
263
|
+
const ranges = rows.map((entry) => extent(entry.map((row) => row.x)));
|
|
264
|
+
const reaches = rows.map((entry) => reachOf(entry, domainWidth));
|
|
265
|
+
return {
|
|
266
|
+
definition: defineChart({
|
|
267
|
+
marks: series.flatMap((entry, index) => {
|
|
268
|
+
const own = rows[index] ?? [];
|
|
269
|
+
const line = lineY(own, {
|
|
270
|
+
id: entry.key,
|
|
271
|
+
x: "x",
|
|
272
|
+
y: "y",
|
|
273
|
+
stroke: entry.color,
|
|
274
|
+
strokeWidth: entry.dashed === true ? 1.5 : 2,
|
|
275
|
+
strokeDasharray: entry.dashed === true ? "4 3" : void 0
|
|
276
|
+
});
|
|
277
|
+
return own.length === 1 ? [line, dot(own, {
|
|
278
|
+
id: `${entry.key}-dot`,
|
|
279
|
+
x: "x",
|
|
280
|
+
y: "y",
|
|
281
|
+
fill: entry.color,
|
|
282
|
+
r: 3
|
|
283
|
+
})] : [line];
|
|
284
|
+
}),
|
|
285
|
+
scales: {
|
|
286
|
+
x: {
|
|
287
|
+
scale: scaleLinear().domain(xDomain),
|
|
288
|
+
axis: {
|
|
289
|
+
line: AXIS_LINE,
|
|
290
|
+
ticks: {
|
|
291
|
+
values: xTicks,
|
|
292
|
+
format: labelAt
|
|
293
|
+
},
|
|
294
|
+
tickLabels: TICK_LABELS
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
y: {
|
|
298
|
+
scale: scaleLinear().domain([low, high]),
|
|
299
|
+
grid: GRID_LINE,
|
|
300
|
+
axis: {
|
|
301
|
+
line: AXIS_LINE,
|
|
302
|
+
ticks: {
|
|
303
|
+
values: [
|
|
304
|
+
low,
|
|
305
|
+
mid,
|
|
306
|
+
high
|
|
307
|
+
],
|
|
308
|
+
format
|
|
309
|
+
},
|
|
310
|
+
tickLabels: TICK_LABELS
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
theme: THEME,
|
|
315
|
+
focus: "nearest-x",
|
|
316
|
+
tooltip: {
|
|
317
|
+
use: tooltip,
|
|
318
|
+
content: (points) => {
|
|
319
|
+
const primary = points[0];
|
|
320
|
+
if (primary === void 0) return { rows: [] };
|
|
321
|
+
const position = primary.datum.x;
|
|
322
|
+
return {
|
|
323
|
+
title: primary.datum.label,
|
|
324
|
+
rows: rows.flatMap((entry, index) => {
|
|
325
|
+
const range = ranges[index] ?? [0, 0];
|
|
326
|
+
const reach = reaches[index] ?? 0;
|
|
327
|
+
if (position < range[0] - reach || position > range[1] + reach) return [];
|
|
328
|
+
const hit = nearestRow(entry, position);
|
|
329
|
+
if (hit === void 0) return [];
|
|
330
|
+
return [{
|
|
331
|
+
label: hit.label === primary.datum.label ? hit.series : `${hit.series}(${hit.label})`,
|
|
332
|
+
value: format(hit.y),
|
|
333
|
+
color: hit.color
|
|
334
|
+
}];
|
|
335
|
+
})
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}),
|
|
340
|
+
format
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
//#endregion
|
|
345
|
+
export { MultiLineChart };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
//#region src/web/atom/avatar.d.ts
|
|
3
|
+
export type AvatarProps = {
|
|
4
|
+
src?: string;
|
|
5
|
+
/** Alt text for the image. Empty by default (decorative; assumes the name is shown next to it). For a standalone face with no name, pass the person's name */
|
|
6
|
+
alt?: string;
|
|
7
|
+
fallback?: React.ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function Avatar({ src, alt, fallback, className }: AvatarProps): React.JSX.Element;
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Avatar as Avatar$1, AvatarFallback, AvatarImage } from "../base/avatar.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { User } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
//#region src/web/atom/avatar.tsx
|
|
7
|
+
function Avatar({ src, alt = "", fallback, className }) {
|
|
8
|
+
return /* @__PURE__ */ jsxs(Avatar$1, {
|
|
9
|
+
className: cn("size-6", className),
|
|
10
|
+
children: [/* @__PURE__ */ jsx(AvatarImage, {
|
|
11
|
+
src,
|
|
12
|
+
alt
|
|
13
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
14
|
+
className: "bg-muted-foreground text-background",
|
|
15
|
+
children: fallback ?? /* @__PURE__ */ jsx(User, {
|
|
16
|
+
className: "size-4",
|
|
17
|
+
"aria-hidden": true
|
|
18
|
+
})
|
|
19
|
+
})]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { Avatar };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ButtonSize, ButtonVariant } from "../../shared/button.js";
|
|
2
|
+
import { Button as Button$1 } from "../base/button.js";
|
|
3
|
+
import React, { ComponentType, SVGProps } from "react";
|
|
4
|
+
//#region src/web/atom/button.d.ts
|
|
5
|
+
export type ButtonIcon = ComponentType<SVGProps<SVGSVGElement>>;
|
|
6
|
+
type ButtonIconProps = {
|
|
7
|
+
iconLeft?: never;
|
|
8
|
+
iconRight?: never;
|
|
9
|
+
} | {
|
|
10
|
+
iconLeft: ButtonIcon;
|
|
11
|
+
iconRight?: never;
|
|
12
|
+
} | {
|
|
13
|
+
iconLeft?: never;
|
|
14
|
+
iconRight: ButtonIcon;
|
|
15
|
+
};
|
|
16
|
+
export type ButtonProps = {
|
|
17
|
+
action?: "previous-page";
|
|
18
|
+
variant?: ButtonVariant;
|
|
19
|
+
size?: ButtonSize;
|
|
20
|
+
} & ButtonIconProps;
|
|
21
|
+
export declare function Button(props: ButtonProps & React.ComponentProps<typeof Button$1>): React.JSX.Element;
|
|
22
|
+
//#endregion
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Button as Button$1 } from "../base/button.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/web/atom/button.tsx
|
|
6
|
+
function Button(props) {
|
|
7
|
+
const { type = "button", onClick, iconLeft: IconLeft, iconRight: IconRight, className, children, action, asChild, ...remain } = props;
|
|
8
|
+
const innerOnClick = (event) => {
|
|
9
|
+
onClick?.(event);
|
|
10
|
+
switch (action) {
|
|
11
|
+
case "previous-page": window.history.back();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
if (asChild) return /* @__PURE__ */ jsx(Button$1, {
|
|
15
|
+
asChild: true,
|
|
16
|
+
className: cn("cursor-pointer", className),
|
|
17
|
+
onClick: innerOnClick,
|
|
18
|
+
...remain,
|
|
19
|
+
children
|
|
20
|
+
});
|
|
21
|
+
return /* @__PURE__ */ jsxs(Button$1, {
|
|
22
|
+
type,
|
|
23
|
+
className: cn("cursor-pointer", className),
|
|
24
|
+
onClick: innerOnClick,
|
|
25
|
+
...remain,
|
|
26
|
+
children: [
|
|
27
|
+
IconLeft && /* @__PURE__ */ jsx(IconLeft, { className: "!w-3 !h-3" }),
|
|
28
|
+
children,
|
|
29
|
+
IconRight && /* @__PURE__ */ jsx(IconRight, { className: "!w-3 !h-3" })
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { Button };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Checkbox as Checkbox$1 } from "../base/checkbox.js";
|
|
2
|
+
import { ComponentProps, ReactNode } from "react";
|
|
3
|
+
//#region src/web/atom/checkbox.d.ts
|
|
4
|
+
export type CheckboxProps = Omit<ComponentProps<typeof Checkbox$1>, "checked" | "onCheckedChange"> & {
|
|
5
|
+
/** Checked state (controlled mode) */
|
|
6
|
+
checked: boolean;
|
|
7
|
+
/** Called when the value changes */
|
|
8
|
+
onCheckedChange: (checked: boolean) => void;
|
|
9
|
+
/** Label shown to the right of the checkbox */
|
|
10
|
+
label?: ReactNode;
|
|
11
|
+
/** Extra class for the wrapper */
|
|
12
|
+
wrapperClassName?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A simple Checkbox that does not depend on a form context.
|
|
16
|
+
* Passing `label` adds a clickable label on the right.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Checkbox({ checked, onCheckedChange, label, wrapperClassName, id, ...rest }: CheckboxProps): import("react").JSX.Element;
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Checkbox as Checkbox$1 } from "../base/checkbox.js";
|
|
3
|
+
import { Label } from "../base/label.js";
|
|
4
|
+
import { useId } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
|
|
7
|
+
//#region src/web/atom/checkbox.tsx
|
|
8
|
+
/**
|
|
9
|
+
* A simple Checkbox that does not depend on a form context.
|
|
10
|
+
* Passing `label` adds a clickable label on the right.
|
|
11
|
+
*/
|
|
12
|
+
function Checkbox({ checked, onCheckedChange, label, wrapperClassName, id, ...rest }) {
|
|
13
|
+
const generated = useId();
|
|
14
|
+
const inputId = id ?? generated;
|
|
15
|
+
const box = /* @__PURE__ */ jsx(Checkbox$1, {
|
|
16
|
+
id: inputId,
|
|
17
|
+
checked,
|
|
18
|
+
onCheckedChange: (c) => onCheckedChange(c === true),
|
|
19
|
+
...rest
|
|
20
|
+
});
|
|
21
|
+
if (label === void 0) return box;
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
23
|
+
className: cn("inline-flex items-center gap-1.5", wrapperClassName),
|
|
24
|
+
children: [box, /* @__PURE__ */ jsx(Label, {
|
|
25
|
+
htmlFor: inputId,
|
|
26
|
+
className: "cursor-pointer",
|
|
27
|
+
children: label
|
|
28
|
+
})]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { Checkbox };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ColoredValueSharedProps } from "../../shared/analytics.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/web/atom/colored-value.d.ts
|
|
4
|
+
export type ColoredValueProps<TValue> = ColoredValueSharedProps<TValue>;
|
|
5
|
+
/**
|
|
6
|
+
* A number whose colour changes with its sign. Same API as native's `atom/colored-value` (`shared/analytics.ts`).
|
|
7
|
+
* The default colours are the tokens `state-success` / `state-error` (4.5:1 or more in light and dark), not a fixed Tailwind palette.
|
|
8
|
+
* It is a number, so it is drawn monospaced (tabular).
|
|
9
|
+
*
|
|
10
|
+
* **Do not convey meaning by colour alone.** `format` puts the sign (`+` / `−`) into the value, so the direction is clear even to people who cannot see the colour.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ColoredValue<TValue>({ value, format, isPositive, isNegative, positiveColor, negativeColor, className }: ColoredValueProps<TValue>): ReactNode;
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/web/atom/colored-value.tsx
|
|
5
|
+
/**
|
|
6
|
+
* A number whose colour changes with its sign. Same API as native's `atom/colored-value` (`shared/analytics.ts`).
|
|
7
|
+
* The default colours are the tokens `state-success` / `state-error` (4.5:1 or more in light and dark), not a fixed Tailwind palette.
|
|
8
|
+
* It is a number, so it is drawn monospaced (tabular).
|
|
9
|
+
*
|
|
10
|
+
* **Do not convey meaning by colour alone.** `format` puts the sign (`+` / `−`) into the value, so the direction is clear even to people who cannot see the colour.
|
|
11
|
+
*/
|
|
12
|
+
function ColoredValue({ value, format, isPositive, isNegative, positiveColor = "text-state-success", negativeColor = "text-state-error", className }) {
|
|
13
|
+
const formatted = format ? format(value) : String(value);
|
|
14
|
+
const tone = isPositive?.(value) ? "positive" : isNegative?.(value) ? "negative" : void 0;
|
|
15
|
+
return /* @__PURE__ */ jsx("span", {
|
|
16
|
+
"data-slot": "colored-value",
|
|
17
|
+
"data-tone": tone,
|
|
18
|
+
className: cn("font-mono tabular-nums", tone === "positive" && positiveColor, tone === "negative" && negativeColor, className),
|
|
19
|
+
children: formatted
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { ColoredValue };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
//#region src/web/atom/help.d.ts
|
|
3
|
+
type Props = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
/** Accessible name of the trigger (it is shown as an icon only, so the name is given here) */
|
|
6
|
+
label?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function Help({ children, label }: Props): React.JSX.Element;
|
|
9
|
+
//#endregion
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HELP_LABEL } from "../lib/labels.js";
|
|
2
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../base/tooltip.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { CircleQuestionMark } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
//#region src/web/atom/help.tsx
|
|
7
|
+
function Help({ children, label = HELP_LABEL }) {
|
|
8
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
9
|
+
"aria-label": label,
|
|
10
|
+
children: /* @__PURE__ */ jsx(CircleQuestionMark, {
|
|
11
|
+
className: "h-4 w-4",
|
|
12
|
+
"aria-hidden": true
|
|
13
|
+
})
|
|
14
|
+
}), /* @__PURE__ */ jsx(TooltipContent, { children })] });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { Help };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/web/atom/segmented-control.d.ts
|
|
3
|
+
export type SegmentedOption<T extends string | number> = {
|
|
4
|
+
value: T;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** DEV-E2E: ID used by e2e to identify individual segments */
|
|
8
|
+
testId?: string;
|
|
9
|
+
};
|
|
10
|
+
export type SegmentedControlProps<T extends string | number> = {
|
|
11
|
+
value: T;
|
|
12
|
+
onValueChange: (value: T) => void;
|
|
13
|
+
options: SegmentedOption<T>[];
|
|
14
|
+
/**
|
|
15
|
+
* - `joined` (default) ... share a single border, with a 1px divider between segments
|
|
16
|
+
* - `separate` ... each segment has its own independent border
|
|
17
|
+
* - `underline` ... no border; tabs where only the selected one gets an underline
|
|
18
|
+
* - `solid` ... each segment has a 1px border and the borders overlap with neighbours. The selected one is filled with indigo
|
|
19
|
+
*/
|
|
20
|
+
variant?: "joined" | "separate" | "underline" | "solid";
|
|
21
|
+
/** `md` is the body size (16/26, pad 9/16). */
|
|
22
|
+
size?: "xs" | "sm" | "md";
|
|
23
|
+
/** Divide the full width equally among the segments */
|
|
24
|
+
fill?: boolean;
|
|
25
|
+
className?: string;
|
|
26
|
+
"aria-label"?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Horizontal exclusive selection (tabs, mode picker, interval picker, filter kinds).
|
|
30
|
+
*
|
|
31
|
+
* The selected segment gets the indigo (action) fill with white text (corner radius is the control step).
|
|
32
|
+
* It sets `role="radiogroup"` and `aria-checked` on each segment, so callers
|
|
33
|
+
* do not have to assemble aria themselves.
|
|
34
|
+
*
|
|
35
|
+
* Hand-rolled versions (`.map` over `<button>` with a ternary className) tend to drift in
|
|
36
|
+
* dividers, selected-state styling and keyboard handling, so they are consolidated here.
|
|
37
|
+
*/
|
|
38
|
+
export declare function SegmentedControl<T extends string | number>({ value, onValueChange, options, variant, size, fill, className, "aria-label": ariaLabel }: SegmentedControlProps<T>): import("react").JSX.Element;
|
|
39
|
+
//#endregion
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/web/atom/segmented-control.tsx
|
|
5
|
+
const SIZE = {
|
|
6
|
+
xs: "px-2 py-1 text-xs",
|
|
7
|
+
sm: "px-3 py-1.5 text-sm",
|
|
8
|
+
md: "px-4 py-[9px] text-base leading-[26px]"
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Horizontal exclusive selection (tabs, mode picker, interval picker, filter kinds).
|
|
12
|
+
*
|
|
13
|
+
* The selected segment gets the indigo (action) fill with white text (corner radius is the control step).
|
|
14
|
+
* It sets `role="radiogroup"` and `aria-checked` on each segment, so callers
|
|
15
|
+
* do not have to assemble aria themselves.
|
|
16
|
+
*
|
|
17
|
+
* Hand-rolled versions (`.map` over `<button>` with a ternary className) tend to drift in
|
|
18
|
+
* dividers, selected-state styling and keyboard handling, so they are consolidated here.
|
|
19
|
+
*/
|
|
20
|
+
function SegmentedControl({ value, onValueChange, options, variant = "joined", size = "sm", fill, className, "aria-label": ariaLabel }) {
|
|
21
|
+
return /* @__PURE__ */ jsx("div", {
|
|
22
|
+
role: "radiogroup",
|
|
23
|
+
"aria-label": ariaLabel,
|
|
24
|
+
className: cn("inline-flex", fill && "flex w-full", variant === "joined" && "border-input bg-card rounded-control border", variant === "separate" && "gap-1", className),
|
|
25
|
+
children: options.map((option) => {
|
|
26
|
+
const active = option.value === value;
|
|
27
|
+
return /* @__PURE__ */ jsx("button", {
|
|
28
|
+
type: "button",
|
|
29
|
+
role: "radio",
|
|
30
|
+
"aria-checked": active,
|
|
31
|
+
disabled: option.disabled,
|
|
32
|
+
"data-testid": option.testId,
|
|
33
|
+
onClick: () => onValueChange(option.value),
|
|
34
|
+
className: cn("cursor-pointer whitespace-nowrap transition-colors disabled:cursor-not-allowed disabled:opacity-50", SIZE[size], fill && "flex-1", variant === "joined" && "not-first:border-input not-first:border-l first:rounded-l-control last:rounded-r-control", variant === "separate" && "border-input bg-card rounded-control border", variant === "underline" && "border-b-2 border-transparent", variant === "solid" && "border-input bg-card not-first:-ml-px first:rounded-l-control last:rounded-r-control border", active ? variant === "underline" ? "border-action text-foreground font-medium" : cn("bg-action text-action-foreground", variant === "solid" && "border-action relative") : "text-muted-foreground hover:bg-accent hover:text-foreground"),
|
|
35
|
+
children: option.label
|
|
36
|
+
}, String(option.value));
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { SegmentedControl };
|