@eifi1/ui-kit 0.4.1 → 0.5.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/LICENSE +32 -0
- package/README.md +498 -57
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +10 -0
- package/dist/chart.js.map +1 -0
- package/dist/components/account-settings.d.ts +11 -0
- package/dist/components/account-settings.js +19 -12
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +10 -6
- package/dist/components/alert-banner.js +20 -16
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +30 -4
- package/dist/components/amount-input.js +36 -14
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +24 -7
- package/dist/components/calculator.js +37 -20
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +156 -0
- package/dist/components/chart-zoom.js +262 -0
- package/dist/components/chart-zoom.js.map +1 -0
- package/dist/components/chart.d.ts +28 -3
- package/dist/components/chart.js +71 -32
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.d.ts +51 -0
- package/dist/components/checkbox.js +123 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +128 -0
- package/dist/components/chip.js +357 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/choice-parts.d.ts +24 -0
- package/dist/components/choice-parts.js +18 -0
- package/dist/components/choice-parts.js.map +1 -0
- package/dist/components/combobox-core.d.ts +52 -9
- package/dist/components/combobox-core.js +89 -19
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +43 -33
- package/dist/components/combobox.js +409 -340
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +48 -8
- package/dist/components/currency-select.js +107 -65
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +15 -3
- package/dist/components/data-table-filter-popover.js +23 -17
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +3 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +53 -2
- package/dist/components/data-table-labels.js +31 -2
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table-pagination.js +16 -10
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.js +1 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +201 -85
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -9
- package/dist/components/date-picker.js +232 -94
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.d.ts +74 -19
- package/dist/components/dropdown.js +72 -22
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +5 -4
- package/dist/components/entity-combobox.js +107 -76
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +36 -0
- package/dist/components/facing-pair.js +40 -0
- package/dist/components/facing-pair.js.map +1 -0
- package/dist/components/field-sync.d.ts +164 -0
- package/dist/components/field-sync.js +264 -0
- package/dist/components/field-sync.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +21 -7
- package/dist/components/file-dropzone.js +28 -7
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +37 -26
- package/dist/components/full-bleed-dialog.js +20 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/grouped-picker.d.ts +21 -10
- package/dist/components/grouped-picker.js +66 -52
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.d.ts +20 -4
- package/dist/components/hover-menu.js +15 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +102 -14
- package/dist/components/mini-calendar.js +256 -70
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +9 -3
- package/dist/components/modal.js +34 -45
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +79 -0
- package/dist/components/month-picker.js +342 -0
- package/dist/components/month-picker.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +7 -6
- package/dist/components/multi-entity-combobox.js +112 -72
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +13 -11
- package/dist/components/multi-select.js +161 -84
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +92 -0
- package/dist/components/number-field.js +111 -0
- package/dist/components/number-field.js.map +1 -0
- package/dist/components/number-input.d.ts +21 -2
- package/dist/components/number-input.js +2 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +25 -4
- package/dist/components/numpad-sheet.js +30 -13
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-contents.d.ts +90 -0
- package/dist/components/page-contents.js +170 -0
- package/dist/components/page-contents.js.map +1 -0
- package/dist/components/password-strength.d.ts +106 -0
- package/dist/components/password-strength.js +132 -0
- package/dist/components/password-strength.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +23 -15
- package/dist/components/picker-sheet.js +34 -14
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.d.ts +62 -4
- package/dist/components/popover.js +52 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/search-field.d.ts +25 -7
- package/dist/components/search-field.js +7 -5
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +14 -0
- package/dist/components/series-chart-labels.js +11 -0
- package/dist/components/series-chart-labels.js.map +1 -0
- package/dist/components/series-chart.d.ts +207 -0
- package/dist/components/series-chart.js +223 -0
- package/dist/components/series-chart.js.map +1 -0
- package/dist/components/settings-fields.d.ts +14 -4
- package/dist/components/settings-fields.js +19 -8
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/signature-pad.d.ts +119 -0
- package/dist/components/signature-pad.js +405 -0
- package/dist/components/signature-pad.js.map +1 -0
- package/dist/components/slider.d.ts +80 -0
- package/dist/components/slider.js +155 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sparkline.d.ts +93 -0
- package/dist/components/sparkline.js +205 -0
- package/dist/components/sparkline.js.map +1 -0
- package/dist/components/stat-tile.d.ts +151 -0
- package/dist/components/stat-tile.js +236 -0
- package/dist/components/stat-tile.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +25 -2
- package/dist/components/swipeable-row.js +31 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +60 -0
- package/dist/components/switch.js +81 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/time-input.d.ts +58 -0
- package/dist/components/time-input.js +50 -0
- package/dist/components/time-input.js.map +1 -0
- package/dist/components/toggle-group.d.ts +18 -3
- package/dist/components/toggle-group.js +10 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +115 -0
- package/dist/components/toggle-legend.js +114 -0
- package/dist/components/toggle-legend.js.map +1 -0
- package/dist/components/tooltip.d.ts +40 -11
- package/dist/components/tooltip.js +77 -20
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/treemap.d.ts +132 -0
- package/dist/components/treemap.js +179 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/ui.d.ts +159 -46
- package/dist/components/ui.js +220 -115
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-mobile-reveal.js +1 -0
- package/dist/components/use-mobile-reveal.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/use-table-state.js +38 -17
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +5 -3
- package/dist/components/user-avatar.js +4 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +3 -2
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
- package/dist/data-table.d.ts +7 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/feedback/feedback-attachment.js +12 -11
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.js +2 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +33 -7
- package/dist/feedback/feedback-inbox.js +49 -37
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback.d.ts +5 -0
- package/dist/feedback.js +5 -0
- package/dist/feedback.js.map +1 -0
- package/dist/hooks/use-anchored-panel.js +1 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js +1 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-announce.d.ts +46 -0
- package/dist/hooks/use-announce.js +37 -0
- package/dist/hooks/use-announce.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.js +1 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -1
- package/dist/hooks/use-close-transition.js +1 -0
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-dismiss.js +1 -0
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +16 -0
- package/dist/hooks/use-focus-trap.js +67 -0
- package/dist/hooks/use-focus-trap.js.map +1 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +27 -1
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.js +1 -0
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.d.ts +31 -0
- package/dist/i18n/defaults.js +63 -0
- package/dist/i18n/defaults.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +250 -0
- package/dist/i18n/kit-labels.js +184 -0
- package/dist/i18n/kit-labels.js.map +1 -0
- package/dist/index.d.ts +47 -22
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calc.js +1 -0
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/dates.js +1 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/safe-storage.js +1 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/search/command-palette.d.ts +11 -1
- package/dist/search/command-palette.js +21 -15
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.js +3 -0
- package/dist/search.js.map +1 -0
- package/dist/shell/app-shell.d.ts +33 -4
- package/dist/shell/app-shell.js +275 -78
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +3 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +6 -0
- package/dist/shell/role-switcher.js +7 -4
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar.d.ts +15 -7
- package/dist/shell/top-bar.js +3 -5
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +3 -2
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +5 -2
- package/dist/shell/topbar-controls.js +20 -15
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +13 -0
- package/dist/shell.js +8 -0
- package/dist/shell.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +19 -12
- package/dist/theme/chart-palette.js +24 -30
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/color.d.ts +78 -0
- package/dist/theme/color.js +148 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/palette-derive.d.ts +140 -0
- package/dist/theme/palette-derive.js +376 -0
- package/dist/theme/palette-derive.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +14 -1
- package/dist/theme/palette-presets.js +231 -3
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +1 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.d.ts +2 -1
- package/dist/tour/tour.js +15 -9
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.js +10 -8
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +18 -2
- package/dist/wizard/types.js +4 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +12 -4
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js +1 -0
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js +1 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.js +1 -0
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js +5 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +9 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +66 -13
- package/src/chart.ts +22 -0
- package/src/components/account-settings.tsx +275 -0
- package/src/components/alert-banner.tsx +75 -0
- package/src/components/amount-input.tsx +471 -0
- package/src/components/calculator.tsx +252 -0
- package/src/components/chart-zoom.tsx +499 -0
- package/src/components/chart.tsx +363 -0
- package/src/components/checkbox.tsx +222 -0
- package/src/components/chip.tsx +570 -0
- package/src/components/choice-parts.ts +33 -0
- package/src/components/combobox-core.tsx +540 -0
- package/src/components/combobox.tsx +1026 -0
- package/src/components/currency-select.tsx +269 -0
- package/src/components/data-table-filter-popover.tsx +252 -0
- package/src/components/data-table-filters.ts +176 -0
- package/src/components/data-table-labels.ts +190 -0
- package/src/components/data-table-pagination.tsx +126 -0
- package/src/components/data-table-sort.ts +70 -0
- package/src/components/data-table.tsx +1740 -0
- package/src/components/date-picker.tsx +631 -0
- package/src/components/dropdown.tsx +341 -0
- package/src/components/entity-combobox.tsx +215 -0
- package/src/components/facing-pair.ts +86 -0
- package/src/components/field-sync.tsx +472 -0
- package/src/components/file-dropzone.tsx +181 -0
- package/src/components/full-bleed-dialog.tsx +179 -0
- package/src/components/grouped-picker.tsx +157 -0
- package/src/components/hover-menu.tsx +183 -0
- package/src/components/mini-calendar.tsx +554 -0
- package/src/components/modal.tsx +287 -0
- package/src/components/month-picker.tsx +549 -0
- package/src/components/multi-entity-combobox.tsx +226 -0
- package/src/components/multi-select.tsx +303 -0
- package/src/components/number-field.tsx +225 -0
- package/src/components/number-input.tsx +211 -0
- package/src/components/numpad-sheet.tsx +264 -0
- package/src/components/page-contents.tsx +289 -0
- package/src/components/password-strength.tsx +258 -0
- package/src/components/picker-sheet.tsx +251 -0
- package/src/components/popover.tsx +195 -0
- package/src/components/search-field.tsx +146 -0
- package/src/components/series-chart-labels.ts +26 -0
- package/src/components/series-chart.tsx +481 -0
- package/src/components/settings-fields.tsx +68 -0
- package/src/components/signature-pad.tsx +630 -0
- package/src/components/slider.tsx +313 -0
- package/src/components/sparkline.tsx +329 -0
- package/src/components/stat-tile.tsx +448 -0
- package/src/components/swipeable-row.tsx +271 -0
- package/src/components/switch.tsx +170 -0
- package/src/components/time-input.tsx +107 -0
- package/src/components/toggle-group.tsx +153 -0
- package/src/components/toggle-legend.tsx +229 -0
- package/src/components/tooltip.tsx +477 -0
- package/src/components/treemap.tsx +390 -0
- package/src/components/ui.tsx +1284 -0
- package/src/components/use-mobile-reveal.ts +54 -0
- package/src/components/use-table-state.ts +295 -0
- package/src/components/user-avatar.tsx +61 -0
- package/src/components/wizard-stepper.tsx +37 -0
- package/src/data-table.ts +19 -0
- package/src/feedback/feedback-attachment.tsx +246 -0
- package/src/feedback/feedback-dialog.tsx +209 -0
- package/src/feedback/feedback-inbox.tsx +615 -0
- package/src/feedback.ts +8 -0
- package/src/hooks/use-anchored-panel.ts +169 -0
- package/src/hooks/use-anchored-rect.ts +78 -0
- package/src/hooks/use-announce.ts +85 -0
- package/src/hooks/use-body-scroll-lock.ts +94 -0
- package/src/hooks/use-close-transition.ts +99 -0
- package/src/hooks/use-dismiss.ts +77 -0
- package/src/hooks/use-focus-trap.ts +160 -0
- package/src/hooks/use-media-query.ts +23 -0
- package/src/hooks/use-overlay-history.ts +345 -0
- package/src/hooks/use-row-swipe.ts +212 -0
- package/src/i18n/defaults.ts +68 -0
- package/src/i18n/kit-labels.tsx +447 -0
- package/src/index.ts +169 -0
- package/src/lib/calc.ts +309 -0
- package/src/lib/cn.ts +5 -0
- package/src/lib/dates.ts +187 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/safe-storage.ts +37 -0
- package/src/search/command-palette.tsx +342 -0
- package/src/search.ts +3 -0
- package/src/shell/app-shell.tsx +772 -0
- package/src/shell/option-switcher-menu.tsx +72 -0
- package/src/shell/role-switcher.tsx +46 -0
- package/src/shell/top-bar.tsx +39 -0
- package/src/shell/topbar-action-menu.tsx +91 -0
- package/src/shell/topbar-controls.tsx +229 -0
- package/src/shell.ts +10 -0
- package/src/theme/chart-palette.ts +145 -0
- package/src/theme/color.ts +266 -0
- package/src/theme/palette-derive.ts +681 -0
- package/src/theme/palette-presets.ts +760 -0
- package/src/theme/palette-store.ts +94 -0
- package/src/theme/theme-store.ts +145 -0
- package/src/tour/tour.tsx +586 -0
- package/src/tour.ts +3 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wizard/stepper-nav.tsx +221 -0
- package/src/wizard/types.ts +178 -0
- package/src/wizard/use-wizard.ts +345 -0
- package/src/wizard/validation.ts +37 -0
- package/src/wizard/wizard-context.tsx +29 -0
- package/src/wizard/wizard-step.tsx +14 -0
- package/src/wizard/wizard-summary.tsx +69 -0
- package/src/wizard.ts +14 -0
- package/tokens.css +222 -20
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ZoomBinding } from './chart-zoom.js';
|
|
4
|
+
import { SeriesChartLabels } from './series-chart-labels.js';
|
|
5
|
+
import 'recharts';
|
|
6
|
+
|
|
7
|
+
interface SeriesChartSeries {
|
|
8
|
+
/** The row key this line reads — and the `--color-<key>` it is painted with, so it
|
|
9
|
+
* must be a CSS identifier. Build keys out of data with {@link seriesKey}. */
|
|
10
|
+
key: string;
|
|
11
|
+
label: ReactNode;
|
|
12
|
+
/** Any CSS colour the chart shell accepts (hex, rgb/hsl, `var(--token)`). Default:
|
|
13
|
+
* the kit's categorical ramp by position, `paletteFor(index)`. */
|
|
14
|
+
color?: string;
|
|
15
|
+
/** Marks the PAIRED line of a pair — the return stroke against the out-stroke, the
|
|
16
|
+
* reference against the measurement. Shorthand for `dash: 1`. */
|
|
17
|
+
dashed?: boolean;
|
|
18
|
+
/** Which of {@link STROKE_PATTERNS} this line takes, for charts where the stroke says
|
|
19
|
+
* WHICH QUANTITY and the colour says WHICH MEASUREMENT. */
|
|
20
|
+
dash?: number;
|
|
21
|
+
/** `stepAfter`, for a whole-number channel that jumps rather than travels — a
|
|
22
|
+
* straight line between index 0 and index 1 draws an index of 0.5, which does not
|
|
23
|
+
* exist. Drawn in the {@link STEP_DASH} pattern, whatever `dash` says. */
|
|
24
|
+
step?: boolean;
|
|
25
|
+
/** Which of `axes` this line is measured on. Default: `"y"`, the single one. */
|
|
26
|
+
axis?: string;
|
|
27
|
+
}
|
|
28
|
+
interface SeriesChartAxisShape {
|
|
29
|
+
id: string;
|
|
30
|
+
/**
|
|
31
|
+
* Which side of the plot the ticks stand on. Left unless said otherwise — and said
|
|
32
|
+
* otherwise for the right-hand chart of a FACING pair (see `facingAxes`), whose
|
|
33
|
+
* scales go on the outside so neither column of numbers stands between two pictures
|
|
34
|
+
* being compared.
|
|
35
|
+
*/
|
|
36
|
+
orientation?: "left" | "right";
|
|
37
|
+
/** Tints the ticks — and the title too, when exactly one series is measured on this
|
|
38
|
+
* axis (see {@link soleSeriesColor}). */
|
|
39
|
+
color?: string;
|
|
40
|
+
/** What the TICKS need, in px. The title's strip is added on top. Default 48. */
|
|
41
|
+
width?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Pin the scale instead of fitting it to this chart's data. For a ROW of charts
|
|
44
|
+
* read against each other: auto-fitted, a 200 N loop and a 2000 N one draw the
|
|
45
|
+
* identical picture, and the comparison the layout promises is the one it fails.
|
|
46
|
+
*/
|
|
47
|
+
domain?: [number, number];
|
|
48
|
+
/** A tick, formatted — as a BARE number. The unit is said once, in the title; the
|
|
49
|
+
* tooltip gets it from `valueFormat`. Default: `Intl.NumberFormat` in the kit's
|
|
50
|
+
* locale, up to two fraction digits. */
|
|
51
|
+
format?: (value: number) => string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A y axis — and a VISIBLE one says what it measures.
|
|
55
|
+
*
|
|
56
|
+
* A union rather than an optional field, so the rule is the type's: a column of numbers
|
|
57
|
+
* whose quantity and unit are not written down is a column somebody reads as whatever
|
|
58
|
+
* they expected. `title: ""` is the explicit opt-out (the right-hand chart of a facing
|
|
59
|
+
* pair, whose neighbour already said it). A HIDDEN axis scales its lines and draws
|
|
60
|
+
* nothing, so there is nowhere for a title to go.
|
|
61
|
+
*/
|
|
62
|
+
type SeriesChartAxis = SeriesChartAxisShape & ({
|
|
63
|
+
title: string;
|
|
64
|
+
hide?: false;
|
|
65
|
+
} | {
|
|
66
|
+
title?: string;
|
|
67
|
+
hide: true;
|
|
68
|
+
});
|
|
69
|
+
interface SeriesChartX {
|
|
70
|
+
/** Row key of the abscissa. Default `"x"`, which is what {@link mergeSeries} writes. */
|
|
71
|
+
key?: string;
|
|
72
|
+
/** What the abscissa is, with its unit. Drawn under the axis. */
|
|
73
|
+
title?: string;
|
|
74
|
+
/** A tick, formatted — bare, like the y axes'. */
|
|
75
|
+
format?: (value: number) => string;
|
|
76
|
+
/**
|
|
77
|
+
* Whether this chart prints the abscissa's ticks. On by default; off for every chart
|
|
78
|
+
* of a stack sharing one x window but the bottom one, which prints them for all. It
|
|
79
|
+
* costs no alignment: the x mapping is decided by the margins and the y bands.
|
|
80
|
+
*/
|
|
81
|
+
ticks?: boolean;
|
|
82
|
+
/** The tooltip's heading. Default: the abscissa value through `format`. */
|
|
83
|
+
label?: (value: number) => ReactNode;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A vertical segment in data coordinates — two y at one x, which a row-per-abscissa
|
|
87
|
+
* chart structurally cannot draw. For CLOSING a shape the series already drew: the
|
|
88
|
+
* turnarounds of a hysteresis loop, where the abscissa stands still while the value
|
|
89
|
+
* falls from one branch to the other.
|
|
90
|
+
*/
|
|
91
|
+
interface SeriesChartSpan {
|
|
92
|
+
/** Distinct within the chart. */
|
|
93
|
+
key: string;
|
|
94
|
+
/** Which y axis `from`/`to` are measured on. Default `"y"`. */
|
|
95
|
+
axis?: string;
|
|
96
|
+
x: number;
|
|
97
|
+
from: number;
|
|
98
|
+
to: number;
|
|
99
|
+
/** Default: `var(--text-muted)`. */
|
|
100
|
+
color?: string;
|
|
101
|
+
}
|
|
102
|
+
interface SeriesChartProps {
|
|
103
|
+
/** One row per abscissa value. A missing key is a hole (see `connectNulls`). */
|
|
104
|
+
rows: Record<string, number>[];
|
|
105
|
+
series: SeriesChartSeries[];
|
|
106
|
+
/** Default: one untitled axis, `"y"`. */
|
|
107
|
+
axes?: SeriesChartAxis[];
|
|
108
|
+
x?: SeriesChartX;
|
|
109
|
+
/** A value in the tooltip, where a number stands on its own and needs its unit.
|
|
110
|
+
* Default: `Intl.NumberFormat` in the kit's locale. */
|
|
111
|
+
valueFormat?: (value: number) => string;
|
|
112
|
+
/** Tailwind height class. Default `h-72`. The empty state takes it too, so a chart
|
|
113
|
+
* losing its last line does not relayout the page under it. */
|
|
114
|
+
height?: string;
|
|
115
|
+
/** Bridge holes left by sources sampled on different grids. See {@link mergeSeries}. */
|
|
116
|
+
connectNulls?: boolean;
|
|
117
|
+
/** Shown, centred at the chart's height, instead of an empty chart. Default: the
|
|
118
|
+
* `seriesChart.empty` label. `null` shows nothing. */
|
|
119
|
+
empty?: ReactNode;
|
|
120
|
+
/**
|
|
121
|
+
* Milliseconds for a line to draw itself in. Off by default: a chart beside a live
|
|
122
|
+
* editor re-renders on every keystroke, and an animation restarted mid-flight reads
|
|
123
|
+
* as the curve twitching. Turn it on where series come and go by choice.
|
|
124
|
+
*/
|
|
125
|
+
animationMs?: number;
|
|
126
|
+
spans?: SeriesChartSpan[];
|
|
127
|
+
/** Supplied by `withChartZoom` and by nothing else. */
|
|
128
|
+
zoom?: ZoomBinding;
|
|
129
|
+
/** Per-chart strings over `<UiKitProvider labels={{ seriesChart }}>`. */
|
|
130
|
+
labels?: Partial<SeriesChartLabels>;
|
|
131
|
+
/** BCP 47 tag for the default number formats. Default: the provider's. */
|
|
132
|
+
locale?: string;
|
|
133
|
+
/** Onto the chart's root (`ChartContainer`) — e.g. an app's own grid ink. */
|
|
134
|
+
className?: string;
|
|
135
|
+
}
|
|
136
|
+
/** How much of an axis band the rotated title takes, in px. Reserved rather than
|
|
137
|
+
* shared with the ticks: a title merely offset inside the band prints on top of a
|
|
138
|
+
* five-figure tick, and which gives way depends on the data. */
|
|
139
|
+
declare const AXIS_TITLE_STRIP = 16;
|
|
140
|
+
/** The width the ticks of a y axis get when a caller does not say. */
|
|
141
|
+
declare const AXIS_TICK_WIDTH = 48;
|
|
142
|
+
/** The band a fitted axis draws, padded — and a real band for constant data, which is
|
|
143
|
+
* a zero-height domain recharts cannot scale into. `undefined` when nothing finite. */
|
|
144
|
+
declare function paddedDomain(values: Iterable<number>): [number, number] | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* {@link paddedDomain} for a caller that has already walked its own data — so a row
|
|
147
|
+
* of charts can share one scale without building an array to find two numbers in, and
|
|
148
|
+
* without a second padding rule. `low > high` means "nothing was seen": `undefined`.
|
|
149
|
+
*/
|
|
150
|
+
declare function padBand(low: number, high: number): [number, number] | undefined;
|
|
151
|
+
/** The whole band a y axis occupies: its ticks, and its title when it draws one. For
|
|
152
|
+
* lining a heading up over a PLOT rather than over its column. */
|
|
153
|
+
declare function axisBandWidth(tickWidth: number | undefined, titled: boolean): number;
|
|
154
|
+
/**
|
|
155
|
+
* The colour an axis TITLE takes, or `undefined` for the neutral one.
|
|
156
|
+
*
|
|
157
|
+
* An axis measuring exactly one line is that line's, and saying so in the title lets a
|
|
158
|
+
* reader find a channel's scale without counting axes. An axis measuring several is
|
|
159
|
+
* nobody's — a title in the first one's colour would be claiming it. Counted off the
|
|
160
|
+
* series, so toggling a second line onto an axis gives the colour up in the same render.
|
|
161
|
+
*/
|
|
162
|
+
declare function soleSeriesColor(axis: Pick<SeriesChartAxisShape, "id" | "color">, series: readonly Pick<SeriesChartSeries, "axis">[]): string | undefined;
|
|
163
|
+
/** The single-axis case, which is most charts. `width` is what the TICKS need;
|
|
164
|
+
* `orientation` is which side they stand on. */
|
|
165
|
+
declare function oneAxis(format: ((value: number) => string) | undefined, title: string, width?: number, orientation?: "left" | "right"): SeriesChartAxis[];
|
|
166
|
+
/** One source's samples: an abscissa and the channels measured against it. */
|
|
167
|
+
interface SeriesSource {
|
|
168
|
+
x: number[];
|
|
169
|
+
channels: Record<string, number[]>;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Merge sources sampled on different grids into rows the chart can draw.
|
|
173
|
+
*
|
|
174
|
+
* Merged ON THE ABSCISSA VALUE ITSELF, never interpolated onto a common grid: a line
|
|
175
|
+
* through invented points looks exactly like one through measured ones. Where a source
|
|
176
|
+
* has no sample the key is absent, and `connectNulls` bridges the hole with the same
|
|
177
|
+
* straight segment an interpolation would have drawn — without claiming a measurement.
|
|
178
|
+
*/
|
|
179
|
+
declare function mergeSeries(sources: SeriesSource[]): Record<string, number>[];
|
|
180
|
+
/**
|
|
181
|
+
* A series key built out of parts — as a CSS identifier.
|
|
182
|
+
*
|
|
183
|
+
* The chart shell paints each series through `--color-<key>`, and skips (with a
|
|
184
|
+
* console warning) any key that is not `[A-Za-z_][A-Za-z0-9_-]*`. A line whose colour
|
|
185
|
+
* was skipped has no stroke at all: an axis, a grid and nothing between them. So a key
|
|
186
|
+
* assembled out of DATA — an id, an enum, a side — goes through here: every other
|
|
187
|
+
* character becomes `_`, the parts are joined with `_`, and a key that would start with
|
|
188
|
+
* a digit or a hyphen gets a leading `_`.
|
|
189
|
+
*/
|
|
190
|
+
declare function seriesKey(...parts: (string | number)[]): string;
|
|
191
|
+
/**
|
|
192
|
+
* {@link SeriesChart} without the zoom: the same picture, no drag layer, no reset
|
|
193
|
+
* button. For a thumbnail, a print view, or a chart the consumer wraps in its own
|
|
194
|
+
* interaction.
|
|
195
|
+
*/
|
|
196
|
+
declare function StaticSeriesChart({ rows, x, series, axes, valueFormat, height, connectNulls, empty, animationMs, spans, zoom, labels: labelsProp, locale, className, }: SeriesChartProps): react.JSX.Element;
|
|
197
|
+
/**
|
|
198
|
+
* The series chart, zoomable. Drag across the plot to zoom (the drag's shape picks the
|
|
199
|
+
* axes), double-click or press the reset button to go back; wrap several in
|
|
200
|
+
* `SharedXZoom` to move their x windows together.
|
|
201
|
+
*/
|
|
202
|
+
declare const SeriesChart: {
|
|
203
|
+
(props: SeriesChartProps): react.JSX.Element;
|
|
204
|
+
displayName: string;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, type SeriesChartAxis, type SeriesChartProps, type SeriesChartSeries, type SeriesChartSpan, type SeriesChartX, type SeriesSource, StaticSeriesChart, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, soleSeriesColor };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { CartesianGrid, Label, Line, LineChart, ReferenceLine, XAxis, YAxis } from "recharts";
|
|
5
|
+
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "./chart.js";
|
|
6
|
+
import { DEFAULT_Y_AXIS, withChartZoom } from "./chart-zoom.js";
|
|
7
|
+
import { STEP_DASH, strokeDash } from "./toggle-legend.js";
|
|
8
|
+
import { DEFAULT_SERIES_CHART_LABELS } from "./series-chart-labels.js";
|
|
9
|
+
import { paletteFor } from "../theme/chart-palette.js";
|
|
10
|
+
import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
|
|
11
|
+
import { cn } from "../lib/cn.js";
|
|
12
|
+
const AXIS_TITLE_STRIP = 16;
|
|
13
|
+
const AXIS_TITLE_OFFSET = 10;
|
|
14
|
+
const AXIS_TICK_WIDTH = 48;
|
|
15
|
+
const AUTO_PAD = 0.04;
|
|
16
|
+
function paddedDomain(values) {
|
|
17
|
+
let low = Infinity;
|
|
18
|
+
let high = -Infinity;
|
|
19
|
+
for (const value of values) {
|
|
20
|
+
if (!Number.isFinite(value)) continue;
|
|
21
|
+
if (value < low) low = value;
|
|
22
|
+
if (value > high) high = value;
|
|
23
|
+
}
|
|
24
|
+
return padBand(low, high);
|
|
25
|
+
}
|
|
26
|
+
function padBand(low, high) {
|
|
27
|
+
if (!(low <= high)) return void 0;
|
|
28
|
+
const pad = (high - low) * AUTO_PAD || Math.abs(high) * AUTO_PAD || 1;
|
|
29
|
+
return [low - pad, high + pad];
|
|
30
|
+
}
|
|
31
|
+
function axisBandWidth(tickWidth, titled) {
|
|
32
|
+
return (tickWidth ?? AXIS_TICK_WIDTH) + (titled ? AXIS_TITLE_STRIP : 0);
|
|
33
|
+
}
|
|
34
|
+
function soleSeriesColor(axis, series) {
|
|
35
|
+
const mine = series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axis.id);
|
|
36
|
+
return mine.length === 1 ? axis.color : void 0;
|
|
37
|
+
}
|
|
38
|
+
function oneAxis(format, title, width, orientation) {
|
|
39
|
+
return [{ id: DEFAULT_Y_AXIS, format, title, width, orientation }];
|
|
40
|
+
}
|
|
41
|
+
function mergeSeries(sources) {
|
|
42
|
+
const byAbscissa = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const source of sources) {
|
|
44
|
+
source.x.forEach((value, index) => {
|
|
45
|
+
const row = byAbscissa.get(value) ?? { x: value };
|
|
46
|
+
for (const [key, channel] of Object.entries(source.channels)) row[key] = channel[index];
|
|
47
|
+
byAbscissa.set(value, row);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return [...byAbscissa.values()].sort((a, b) => a.x - b.x);
|
|
51
|
+
}
|
|
52
|
+
function seriesKey(...parts) {
|
|
53
|
+
const key = parts.map((part) => String(part).replace(/[^A-Za-z0-9_-]/g, "_")).join("_");
|
|
54
|
+
return /^[A-Za-z_]/.test(key) ? key : `_${key}`;
|
|
55
|
+
}
|
|
56
|
+
function useDefaultFormat(localeProp) {
|
|
57
|
+
const locale = useKitLocale(localeProp);
|
|
58
|
+
return useMemo(() => {
|
|
59
|
+
const format = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
|
|
60
|
+
return (value) => format.format(value);
|
|
61
|
+
}, [locale]);
|
|
62
|
+
}
|
|
63
|
+
const ONE_UNTITLED_AXIS = [{ id: DEFAULT_Y_AXIS, title: "" }];
|
|
64
|
+
function StaticSeriesChart({
|
|
65
|
+
rows,
|
|
66
|
+
x = {},
|
|
67
|
+
series,
|
|
68
|
+
axes = ONE_UNTITLED_AXIS,
|
|
69
|
+
valueFormat,
|
|
70
|
+
height = "h-72",
|
|
71
|
+
connectNulls,
|
|
72
|
+
empty,
|
|
73
|
+
animationMs = 0,
|
|
74
|
+
spans,
|
|
75
|
+
zoom,
|
|
76
|
+
labels: labelsProp,
|
|
77
|
+
locale,
|
|
78
|
+
className
|
|
79
|
+
}) {
|
|
80
|
+
const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
|
|
81
|
+
const number = useDefaultFormat(locale);
|
|
82
|
+
if (!rows.length || !series.length) {
|
|
83
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex w-full items-center justify-center px-2 text-center", height), children: empty !== void 0 ? empty : /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: labels.empty }) });
|
|
84
|
+
}
|
|
85
|
+
const xKey = x.key ?? "x";
|
|
86
|
+
const xFormat = x.format ?? number;
|
|
87
|
+
const config = Object.fromEntries(
|
|
88
|
+
series.map((entry, index) => [
|
|
89
|
+
entry.key,
|
|
90
|
+
{ label: entry.label, color: entry.color ?? paletteFor(index) }
|
|
91
|
+
])
|
|
92
|
+
);
|
|
93
|
+
const visible = axes.filter((axis) => !axis.hide);
|
|
94
|
+
const gridAxis = (visible[0] ?? axes[0])?.id ?? DEFAULT_Y_AXIS;
|
|
95
|
+
const onLeft = visible.some((axis) => (axis.orientation ?? "left") === "left");
|
|
96
|
+
const onRight = visible.some((axis) => axis.orientation === "right");
|
|
97
|
+
const margin = {
|
|
98
|
+
top: 8,
|
|
99
|
+
right: onRight ? 0 : 10,
|
|
100
|
+
left: onLeft ? 0 : 10,
|
|
101
|
+
// Under the ticks, not under the whole axis: whatever comes next owns the gap.
|
|
102
|
+
bottom: x.title ? 18 : 2
|
|
103
|
+
};
|
|
104
|
+
const fittedX = zoom?.xDomain ?? paddedDomain(rows.map((row) => row[xKey]));
|
|
105
|
+
const fittedY = (axis) => zoom?.yDomains[axis.id] ?? axis.domain ?? paddedDomain(
|
|
106
|
+
series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axis.id).flatMap((entry) => rows.map((row) => row[entry.key]))
|
|
107
|
+
);
|
|
108
|
+
return /* @__PURE__ */ jsx(ChartContainer, { config, className: cn("w-full", height, className), children: /* @__PURE__ */ jsxs(LineChart, { data: rows, margin, children: [
|
|
109
|
+
/* @__PURE__ */ jsx(CartesianGrid, { yAxisId: gridAxis }),
|
|
110
|
+
/* @__PURE__ */ jsx(
|
|
111
|
+
XAxis,
|
|
112
|
+
{
|
|
113
|
+
dataKey: xKey,
|
|
114
|
+
type: "number",
|
|
115
|
+
domain: fittedX ?? ["dataMin", "dataMax"],
|
|
116
|
+
allowDataOverflow: zoom?.xDomain !== void 0,
|
|
117
|
+
tickLine: false,
|
|
118
|
+
axisLine: false,
|
|
119
|
+
minTickGap: 32,
|
|
120
|
+
tick: x.ticks === false ? false : void 0,
|
|
121
|
+
height: x.ticks === false && !x.title ? 4 : void 0,
|
|
122
|
+
tickFormatter: xFormat,
|
|
123
|
+
children: x.title && /* @__PURE__ */ jsx(
|
|
124
|
+
Label,
|
|
125
|
+
{
|
|
126
|
+
value: x.title,
|
|
127
|
+
position: "insideBottom",
|
|
128
|
+
offset: -12,
|
|
129
|
+
className: "fill-[var(--text-muted)] text-[11px]"
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
axes.map((axis) => /* @__PURE__ */ jsx(
|
|
135
|
+
YAxis,
|
|
136
|
+
{
|
|
137
|
+
yAxisId: axis.id,
|
|
138
|
+
hide: axis.hide,
|
|
139
|
+
domain: fittedY(axis),
|
|
140
|
+
allowDataOverflow: zoom?.yDomains[axis.id] !== void 0,
|
|
141
|
+
orientation: axis.orientation ?? "left",
|
|
142
|
+
width: axisBandWidth(axis.width, Boolean(axis.title) && !axis.hide),
|
|
143
|
+
tickLine: false,
|
|
144
|
+
axisLine: false,
|
|
145
|
+
tickFormatter: axis.format ?? number,
|
|
146
|
+
tick: axis.color ? { fill: axis.color } : void 0,
|
|
147
|
+
children: axis.title && !axis.hide && /* @__PURE__ */ jsx(
|
|
148
|
+
Label,
|
|
149
|
+
{
|
|
150
|
+
value: axis.title,
|
|
151
|
+
angle: axis.orientation === "right" ? 90 : -90,
|
|
152
|
+
position: axis.orientation === "right" ? "insideRight" : "insideLeft",
|
|
153
|
+
style: { textAnchor: "middle", fill: soleSeriesColor(axis, series) },
|
|
154
|
+
offset: AXIS_TITLE_OFFSET,
|
|
155
|
+
className: "fill-[var(--text-muted)] text-[11px]"
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
},
|
|
159
|
+
axis.id
|
|
160
|
+
)),
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
ChartTooltip,
|
|
163
|
+
{
|
|
164
|
+
content: /* @__PURE__ */ jsx(
|
|
165
|
+
ChartTooltipContent,
|
|
166
|
+
{
|
|
167
|
+
labelFormatter: (value) => (x.label ?? xFormat)(Number(value)),
|
|
168
|
+
valueFormatter: valueFormat ?? number
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
),
|
|
173
|
+
series.map((entry) => /* @__PURE__ */ jsx(
|
|
174
|
+
Line,
|
|
175
|
+
{
|
|
176
|
+
yAxisId: entry.axis ?? DEFAULT_Y_AXIS,
|
|
177
|
+
type: entry.step ? "stepAfter" : "monotone",
|
|
178
|
+
dataKey: entry.key,
|
|
179
|
+
stroke: `var(--color-${entry.key})`,
|
|
180
|
+
strokeWidth: entry.step ? 1.5 : 2,
|
|
181
|
+
strokeDasharray: strokeDash(
|
|
182
|
+
entry.step ? STEP_DASH : entry.dash ?? (entry.dashed ? 1 : 0)
|
|
183
|
+
),
|
|
184
|
+
dot: false,
|
|
185
|
+
connectNulls,
|
|
186
|
+
isAnimationActive: animationMs > 0,
|
|
187
|
+
animationDuration: animationMs,
|
|
188
|
+
animationEasing: "ease-out"
|
|
189
|
+
},
|
|
190
|
+
entry.key
|
|
191
|
+
)),
|
|
192
|
+
spans?.map((span) => /* @__PURE__ */ jsx(
|
|
193
|
+
ReferenceLine,
|
|
194
|
+
{
|
|
195
|
+
yAxisId: span.axis ?? DEFAULT_Y_AXIS,
|
|
196
|
+
segment: [
|
|
197
|
+
{ x: span.x, y: span.from },
|
|
198
|
+
{ x: span.x, y: span.to }
|
|
199
|
+
],
|
|
200
|
+
stroke: span.color ?? "var(--text-muted)",
|
|
201
|
+
strokeWidth: 2,
|
|
202
|
+
ifOverflow: "discard"
|
|
203
|
+
},
|
|
204
|
+
span.key
|
|
205
|
+
)),
|
|
206
|
+
zoom?.layer
|
|
207
|
+
] }) });
|
|
208
|
+
}
|
|
209
|
+
const SeriesChart = withChartZoom(StaticSeriesChart);
|
|
210
|
+
export {
|
|
211
|
+
AXIS_TICK_WIDTH,
|
|
212
|
+
AXIS_TITLE_STRIP,
|
|
213
|
+
SeriesChart,
|
|
214
|
+
StaticSeriesChart,
|
|
215
|
+
axisBandWidth,
|
|
216
|
+
mergeSeries,
|
|
217
|
+
oneAxis,
|
|
218
|
+
padBand,
|
|
219
|
+
paddedDomain,
|
|
220
|
+
seriesKey,
|
|
221
|
+
soleSeriesColor
|
|
222
|
+
};
|
|
223
|
+
//# sourceMappingURL=series-chart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/series-chart.tsx"],"sourcesContent":["// The measurement plot: series against a swept numeric abscissa, any number of y axes,\n// dash patterns, vertical spans, and drag-to-zoom. Lifted out of lenkbank, where every\n// chart on every screen is this one arrangement of the kit's shell — so the arrangement\n// lives here once instead of as thirteen hand-assembled copies drifting apart.\n//\n// Built on `ChartContainer` + `ChartTooltipContent`, so it themes with the rest of the\n// chart kit: series default to the `--chart-N` ramp, axis text and grid take the\n// shell's tokens. It stays domain-free — every unit, title and number format is the\n// caller's, and the few words it can say itself are the `seriesChart` label namespace.\n//\n// **Every series may keep its own scale.** Position runs in millimetres and jerk in\n// hundreds of thousands of them per second cubed; on one axis the position line is\n// flat against the bottom. Normalising each series to its peak makes the y axis mean\n// nothing, so a caller declares an axis per incomparable channel instead, and may hide\n// all but the ones worth a column of numbers.\n//\n// Charts are drawn left-to-right in every writing direction — an abscissa is a number\n// line, not text — so `orientation` below is physical, and only the HTML chrome (the\n// reset button) is placed by logical side.\nimport { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { CartesianGrid, Label, Line, LineChart, ReferenceLine, XAxis, YAxis } from \"recharts\";\nimport { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from \"./chart\";\nimport { DEFAULT_Y_AXIS, withChartZoom, type ZoomBinding } from \"./chart-zoom\";\nimport { STEP_DASH, strokeDash } from \"./toggle-legend\";\nimport { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from \"./series-chart-labels\";\nimport { paletteFor } from \"../theme/chart-palette\";\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\n\nexport interface SeriesChartSeries {\n /** The row key this line reads — and the `--color-<key>` it is painted with, so it\n * must be a CSS identifier. Build keys out of data with {@link seriesKey}. */\n key: string;\n label: ReactNode;\n /** Any CSS colour the chart shell accepts (hex, rgb/hsl, `var(--token)`). Default:\n * the kit's categorical ramp by position, `paletteFor(index)`. */\n color?: string;\n /** Marks the PAIRED line of a pair — the return stroke against the out-stroke, the\n * reference against the measurement. Shorthand for `dash: 1`. */\n dashed?: boolean;\n /** Which of {@link STROKE_PATTERNS} this line takes, for charts where the stroke says\n * WHICH QUANTITY and the colour says WHICH MEASUREMENT. */\n dash?: number;\n /** `stepAfter`, for a whole-number channel that jumps rather than travels — a\n * straight line between index 0 and index 1 draws an index of 0.5, which does not\n * exist. Drawn in the {@link STEP_DASH} pattern, whatever `dash` says. */\n step?: boolean;\n /** Which of `axes` this line is measured on. Default: `\"y\"`, the single one. */\n axis?: string;\n}\n\ninterface SeriesChartAxisShape {\n id: string;\n /**\n * Which side of the plot the ticks stand on. Left unless said otherwise — and said\n * otherwise for the right-hand chart of a FACING pair (see `facingAxes`), whose\n * scales go on the outside so neither column of numbers stands between two pictures\n * being compared.\n */\n orientation?: \"left\" | \"right\";\n /** Tints the ticks — and the title too, when exactly one series is measured on this\n * axis (see {@link soleSeriesColor}). */\n color?: string;\n /** What the TICKS need, in px. The title's strip is added on top. Default 48. */\n width?: number;\n /**\n * Pin the scale instead of fitting it to this chart's data. For a ROW of charts\n * read against each other: auto-fitted, a 200 N loop and a 2000 N one draw the\n * identical picture, and the comparison the layout promises is the one it fails.\n */\n domain?: [number, number];\n /** A tick, formatted — as a BARE number. The unit is said once, in the title; the\n * tooltip gets it from `valueFormat`. Default: `Intl.NumberFormat` in the kit's\n * locale, up to two fraction digits. */\n format?: (value: number) => string;\n}\n\n/**\n * A y axis — and a VISIBLE one says what it measures.\n *\n * A union rather than an optional field, so the rule is the type's: a column of numbers\n * whose quantity and unit are not written down is a column somebody reads as whatever\n * they expected. `title: \"\"` is the explicit opt-out (the right-hand chart of a facing\n * pair, whose neighbour already said it). A HIDDEN axis scales its lines and draws\n * nothing, so there is nowhere for a title to go.\n */\nexport type SeriesChartAxis = SeriesChartAxisShape &\n ({ title: string; hide?: false } | { title?: string; hide: true });\n\nexport interface SeriesChartX {\n /** Row key of the abscissa. Default `\"x\"`, which is what {@link mergeSeries} writes. */\n key?: string;\n /** What the abscissa is, with its unit. Drawn under the axis. */\n title?: string;\n /** A tick, formatted — bare, like the y axes'. */\n format?: (value: number) => string;\n /**\n * Whether this chart prints the abscissa's ticks. On by default; off for every chart\n * of a stack sharing one x window but the bottom one, which prints them for all. It\n * costs no alignment: the x mapping is decided by the margins and the y bands.\n */\n ticks?: boolean;\n /** The tooltip's heading. Default: the abscissa value through `format`. */\n label?: (value: number) => ReactNode;\n}\n\n/**\n * A vertical segment in data coordinates — two y at one x, which a row-per-abscissa\n * chart structurally cannot draw. For CLOSING a shape the series already drew: the\n * turnarounds of a hysteresis loop, where the abscissa stands still while the value\n * falls from one branch to the other.\n */\nexport interface SeriesChartSpan {\n /** Distinct within the chart. */\n key: string;\n /** Which y axis `from`/`to` are measured on. Default `\"y\"`. */\n axis?: string;\n x: number;\n from: number;\n to: number;\n /** Default: `var(--text-muted)`. */\n color?: string;\n}\n\nexport interface SeriesChartProps {\n /** One row per abscissa value. A missing key is a hole (see `connectNulls`). */\n rows: Record<string, number>[];\n series: SeriesChartSeries[];\n /** Default: one untitled axis, `\"y\"`. */\n axes?: SeriesChartAxis[];\n x?: SeriesChartX;\n /** A value in the tooltip, where a number stands on its own and needs its unit.\n * Default: `Intl.NumberFormat` in the kit's locale. */\n valueFormat?: (value: number) => string;\n /** Tailwind height class. Default `h-72`. The empty state takes it too, so a chart\n * losing its last line does not relayout the page under it. */\n height?: string;\n /** Bridge holes left by sources sampled on different grids. See {@link mergeSeries}. */\n connectNulls?: boolean;\n /** Shown, centred at the chart's height, instead of an empty chart. Default: the\n * `seriesChart.empty` label. `null` shows nothing. */\n empty?: ReactNode;\n /**\n * Milliseconds for a line to draw itself in. Off by default: a chart beside a live\n * editor re-renders on every keystroke, and an animation restarted mid-flight reads\n * as the curve twitching. Turn it on where series come and go by choice.\n */\n animationMs?: number;\n spans?: SeriesChartSpan[];\n /** Supplied by `withChartZoom` and by nothing else. */\n zoom?: ZoomBinding;\n /** Per-chart strings over `<UiKitProvider labels={{ seriesChart }}>`. */\n labels?: Partial<SeriesChartLabels>;\n /** BCP 47 tag for the default number formats. Default: the provider's. */\n locale?: string;\n /** Onto the chart's root (`ChartContainer`) — e.g. an app's own grid ink. */\n className?: string;\n}\n\n/** How much of an axis band the rotated title takes, in px. Reserved rather than\n * shared with the ticks: a title merely offset inside the band prints on top of a\n * five-figure tick, and which gives way depends on the data. */\nexport const AXIS_TITLE_STRIP = 16;\n\n/** Where inside its strip the rotated title sits. */\nconst AXIS_TITLE_OFFSET = 10;\n\n/** The width the ticks of a y axis get when a caller does not say. */\nexport const AXIS_TICK_WIDTH = 48;\n\n/**\n * How much air a FITTED axis leaves between the data and its frame, as a fraction of\n * the range. Recharts fits `[dataMin, dataMax]`, which draws a peak half a stroke\n * outside the plot and a curve running to its limit indistinguishable from the frame.\n */\nconst AUTO_PAD = 0.04;\n\n/** The band a fitted axis draws, padded — and a real band for constant data, which is\n * a zero-height domain recharts cannot scale into. `undefined` when nothing finite. */\nexport function paddedDomain(values: Iterable<number>): [number, number] | undefined {\n let low = Infinity;\n let high = -Infinity;\n for (const value of values) {\n if (!Number.isFinite(value)) continue;\n if (value < low) low = value;\n if (value > high) high = value;\n }\n return padBand(low, high);\n}\n\n/**\n * {@link paddedDomain} for a caller that has already walked its own data — so a row\n * of charts can share one scale without building an array to find two numbers in, and\n * without a second padding rule. `low > high` means \"nothing was seen\": `undefined`.\n */\nexport function padBand(low: number, high: number): [number, number] | undefined {\n if (!(low <= high)) return undefined;\n const pad = (high - low) * AUTO_PAD || Math.abs(high) * AUTO_PAD || 1;\n return [low - pad, high + pad];\n}\n\n/** The whole band a y axis occupies: its ticks, and its title when it draws one. For\n * lining a heading up over a PLOT rather than over its column. */\nexport function axisBandWidth(tickWidth: number | undefined, titled: boolean): number {\n return (tickWidth ?? AXIS_TICK_WIDTH) + (titled ? AXIS_TITLE_STRIP : 0);\n}\n\n/**\n * The colour an axis TITLE takes, or `undefined` for the neutral one.\n *\n * An axis measuring exactly one line is that line's, and saying so in the title lets a\n * reader find a channel's scale without counting axes. An axis measuring several is\n * nobody's — a title in the first one's colour would be claiming it. Counted off the\n * series, so toggling a second line onto an axis gives the colour up in the same render.\n */\nexport function soleSeriesColor(\n axis: Pick<SeriesChartAxisShape, \"id\" | \"color\">,\n series: readonly Pick<SeriesChartSeries, \"axis\">[],\n): string | undefined {\n const mine = series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axis.id);\n return mine.length === 1 ? axis.color : undefined;\n}\n\n/** The single-axis case, which is most charts. `width` is what the TICKS need;\n * `orientation` is which side they stand on. */\nexport function oneAxis(\n format: ((value: number) => string) | undefined,\n title: string,\n width?: number,\n orientation?: \"left\" | \"right\",\n): SeriesChartAxis[] {\n return [{ id: DEFAULT_Y_AXIS, format, title, width, orientation }];\n}\n\n/** One source's samples: an abscissa and the channels measured against it. */\nexport interface SeriesSource {\n x: number[];\n channels: Record<string, number[]>;\n}\n\n/**\n * Merge sources sampled on different grids into rows the chart can draw.\n *\n * Merged ON THE ABSCISSA VALUE ITSELF, never interpolated onto a common grid: a line\n * through invented points looks exactly like one through measured ones. Where a source\n * has no sample the key is absent, and `connectNulls` bridges the hole with the same\n * straight segment an interpolation would have drawn — without claiming a measurement.\n */\nexport function mergeSeries(sources: SeriesSource[]): Record<string, number>[] {\n const byAbscissa = new Map<number, Record<string, number>>();\n for (const source of sources) {\n source.x.forEach((value, index) => {\n const row = byAbscissa.get(value) ?? { x: value };\n for (const [key, channel] of Object.entries(source.channels)) row[key] = channel[index];\n byAbscissa.set(value, row);\n });\n }\n return [...byAbscissa.values()].sort((a, b) => a.x - b.x);\n}\n\n/**\n * A series key built out of parts — as a CSS identifier.\n *\n * The chart shell paints each series through `--color-<key>`, and skips (with a\n * console warning) any key that is not `[A-Za-z_][A-Za-z0-9_-]*`. A line whose colour\n * was skipped has no stroke at all: an axis, a grid and nothing between them. So a key\n * assembled out of DATA — an id, an enum, a side — goes through here: every other\n * character becomes `_`, the parts are joined with `_`, and a key that would start with\n * a digit or a hyphen gets a leading `_`.\n */\nexport function seriesKey(...parts: (string | number)[]): string {\n const key = parts.map((part) => String(part).replace(/[^A-Za-z0-9_-]/g, \"_\")).join(\"_\");\n return /^[A-Za-z_]/.test(key) ? key : `_${key}`;\n}\n\n/** The number format every default falls back to. */\nfunction useDefaultFormat(localeProp: string | undefined): (value: number) => string {\n const locale = useKitLocale(localeProp);\n return useMemo(() => {\n const format = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });\n return (value: number) => format.format(value);\n }, [locale]);\n}\n\n/** The Y-axis list a chart draws when the caller gave none. */\nconst ONE_UNTITLED_AXIS: SeriesChartAxis[] = [{ id: DEFAULT_Y_AXIS, title: \"\" }];\n\n/**\n * {@link SeriesChart} without the zoom: the same picture, no drag layer, no reset\n * button. For a thumbnail, a print view, or a chart the consumer wraps in its own\n * interaction.\n */\nexport function StaticSeriesChart({\n rows,\n x = {},\n series,\n axes = ONE_UNTITLED_AXIS,\n valueFormat,\n height = \"h-72\",\n connectNulls,\n empty,\n animationMs = 0,\n spans,\n zoom,\n labels: labelsProp,\n locale,\n className,\n}: SeriesChartProps) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, labelsProp);\n const number = useDefaultFormat(locale);\n\n // The empty state takes the chart's own height and is centred in it: a chart that\n // shrank to its \"nothing to draw\" sentence moved everything under it up the page\n // the moment a legend entry was switched off.\n if (!rows.length || !series.length) {\n return (\n <div className={cn(\"flex w-full items-center justify-center px-2 text-center\", height)}>\n {empty !== undefined ? (\n empty\n ) : (\n <p className=\"text-xs text-[var(--text-muted)]\">{labels.empty}</p>\n )}\n </div>\n );\n }\n\n const xKey = x.key ?? \"x\";\n const xFormat = x.format ?? number;\n const config: ChartConfig = Object.fromEntries(\n series.map((entry, index) => [\n entry.key,\n { label: entry.label, color: entry.color ?? paletteFor(index) },\n ]),\n );\n const visible = axes.filter((axis) => !axis.hide);\n // The grid hangs its horizontal rules off ONE y axis, and recharts looks for the one\n // whose id is its own default (`0`). Every axis here is named, so without saying\n // which, it finds none and draws no horizontal rules at all. The first visible axis,\n // because those are the ticks a reader puts a ruler on.\n const gridAxis = (visible[0] ?? axes[0])?.id ?? DEFAULT_Y_AXIS;\n const onLeft = visible.some((axis) => (axis.orientation ?? \"left\") === \"left\");\n const onRight = visible.some((axis) => axis.orientation === \"right\");\n // The margins are what is left once the axes have their bands: an axis band IS the\n // gutter on its side, so a margin beside one is a second gutter. What remains is the\n // overhang of the first/last x tick where no y axis covers it.\n const margin = {\n top: 8,\n right: onRight ? 0 : 10,\n left: onLeft ? 0 : 10,\n // Under the ticks, not under the whole axis: whatever comes next owns the gap.\n bottom: x.title ? 18 : 2,\n };\n\n // Fitted with air around it, unless the reader has zoomed or the caller pinned it.\n const fittedX = zoom?.xDomain ?? paddedDomain(rows.map((row) => row[xKey]));\n const fittedY = (axis: SeriesChartAxis) =>\n zoom?.yDomains[axis.id] ??\n axis.domain ??\n paddedDomain(\n series\n .filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axis.id)\n .flatMap((entry) => rows.map((row) => row[entry.key])),\n );\n\n return (\n <ChartContainer config={config} className={cn(\"w-full\", height, className)}>\n <LineChart data={rows} margin={margin}>\n {/* Both ways: a measurement plot is read by putting a ruler on it, and a\n horizontal-only grid answers half of those questions. */}\n <CartesianGrid yAxisId={gridAxis} />\n <XAxis\n dataKey={xKey}\n // Numeric, not categorical: a measured sweep is unevenly spaced, and a\n // category axis would straighten exactly the curvature the chart is for.\n type=\"number\"\n domain={fittedX ?? [\"dataMin\", \"dataMax\"]}\n // Clip the lines to a zoom window instead of recharts widening it back out.\n allowDataOverflow={zoom?.xDomain !== undefined}\n tickLine={false}\n axisLine={false}\n minTickGap={32}\n tick={x.ticks === false ? false : undefined}\n // With neither ticks nor title there is nothing to reserve the band for, and\n // recharts' own 30 px would leave a gap under every chart of a stack.\n height={x.ticks === false && !x.title ? 4 : undefined}\n tickFormatter={xFormat}\n >\n {x.title && (\n <Label\n value={x.title}\n position=\"insideBottom\"\n offset={-12}\n className=\"fill-[var(--text-muted)] text-[11px]\"\n />\n )}\n </XAxis>\n {axes.map((axis) => (\n <YAxis\n key={axis.id}\n yAxisId={axis.id}\n hide={axis.hide}\n domain={fittedY(axis)}\n allowDataOverflow={zoom?.yDomains[axis.id] !== undefined}\n orientation={axis.orientation ?? \"left\"}\n width={axisBandWidth(axis.width, Boolean(axis.title) && !axis.hide)}\n tickLine={false}\n axisLine={false}\n tickFormatter={axis.format ?? number}\n // With an axis per channel the tint is the only thing saying which\n // numbers belong to which line.\n tick={axis.color ? { fill: axis.color } : undefined}\n >\n {axis.title && !axis.hide && (\n <Label\n value={axis.title}\n // Rotated so both sides read upward from the bottom of their axis.\n angle={axis.orientation === \"right\" ? 90 : -90}\n position={axis.orientation === \"right\" ? \"insideRight\" : \"insideLeft\"}\n // Inline, so a sole series' colour beats the class's neutral fill —\n // and absent for a shared axis, so the class draws it.\n style={{ textAnchor: \"middle\", fill: soleSeriesColor(axis, series) }}\n offset={AXIS_TITLE_OFFSET}\n className=\"fill-[var(--text-muted)] text-[11px]\"\n />\n )}\n </YAxis>\n ))}\n <ChartTooltip\n content={\n <ChartTooltipContent\n labelFormatter={(value) => (x.label ?? xFormat)(Number(value))}\n valueFormatter={valueFormat ?? number}\n />\n }\n />\n {series.map((entry) => (\n <Line\n key={entry.key}\n yAxisId={entry.axis ?? DEFAULT_Y_AXIS}\n type={entry.step ? \"stepAfter\" : \"monotone\"}\n dataKey={entry.key}\n stroke={`var(--color-${entry.key})`}\n strokeWidth={entry.step ? 1.5 : 2}\n strokeDasharray={strokeDash(\n entry.step ? STEP_DASH : (entry.dash ?? (entry.dashed ? 1 : 0)),\n )}\n dot={false}\n connectNulls={connectNulls}\n isAnimationActive={animationMs > 0}\n animationDuration={animationMs}\n animationEasing=\"ease-out\"\n />\n ))}\n {spans?.map((span) => (\n <ReferenceLine\n key={span.key}\n yAxisId={span.axis ?? DEFAULT_Y_AXIS}\n segment={[\n { x: span.x, y: span.from },\n { x: span.x, y: span.to },\n ]}\n stroke={span.color ?? \"var(--text-muted)\"}\n strokeWidth={2}\n // Discarded rather than clamped when zoomed past: a span clamped to the\n // window's edge draws a vertical line at an abscissa it never stood at.\n ifOverflow=\"discard\"\n />\n ))}\n {zoom?.layer}\n </LineChart>\n </ChartContainer>\n );\n}\n\n/**\n * The series chart, zoomable. Drag across the plot to zoom (the drag's shape picks the\n * axes), double-click or press the reset button to go back; wrap several in\n * `SharedXZoom` to move their x windows together.\n */\nexport const SeriesChart = withChartZoom(StaticSeriesChart);\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { SelectProps } from './ui.js';
|
|
3
4
|
import { ThemePreference } from '../theme/theme-store.js';
|
|
4
5
|
import 'zustand/middleware';
|
|
5
6
|
import 'zustand';
|
|
@@ -11,7 +12,14 @@ import 'zustand';
|
|
|
11
12
|
* The app owns WHERE these render (its own settings page) and any app-specific
|
|
12
13
|
* fields around them.
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Everything a {@link Select} takes, minus the two this component owns: the value is a
|
|
17
|
+
* {@link ThemePreference} rather than a string, and `onChange` hands over the preference
|
|
18
|
+
* itself because no caller of this wants the event. The rest — `disabled`, `required`,
|
|
19
|
+
* `invalid`, `error`, a `data-tour` anchor — passes straight through, which is what makes
|
|
20
|
+
* this a settings FIELD rather than a fixed widget.
|
|
21
|
+
*/
|
|
22
|
+
interface ThemeSettingProps extends Omit<SelectProps, "value" | "onChange" | "children"> {
|
|
15
23
|
value: ThemePreference;
|
|
16
24
|
onChange: (value: ThemePreference) => void;
|
|
17
25
|
label: ReactNode;
|
|
@@ -23,8 +31,10 @@ interface ThemeSettingProps {
|
|
|
23
31
|
};
|
|
24
32
|
id?: string;
|
|
25
33
|
}
|
|
26
|
-
declare function ThemeSetting({ value, onChange, label, optionLabels, id }: ThemeSettingProps): react.JSX.Element;
|
|
27
|
-
|
|
34
|
+
declare function ThemeSetting({ value, onChange, label, optionLabels, id, ...rest }: ThemeSettingProps): react.JSX.Element;
|
|
35
|
+
/** See {@link ThemeSettingProps}: a `Select`'s props with the value and the change
|
|
36
|
+
* handler re-typed to the language CODE the caller stores. */
|
|
37
|
+
interface LanguageSettingProps extends Omit<SelectProps, "value" | "onChange" | "children"> {
|
|
28
38
|
value: string;
|
|
29
39
|
onChange: (code: string) => void;
|
|
30
40
|
label: ReactNode;
|
|
@@ -34,6 +44,6 @@ interface LanguageSettingProps {
|
|
|
34
44
|
}[];
|
|
35
45
|
id?: string;
|
|
36
46
|
}
|
|
37
|
-
declare function LanguageSetting({ value, onChange, label, options, id }: LanguageSettingProps): react.JSX.Element;
|
|
47
|
+
declare function LanguageSetting({ value, onChange, label, options, id, ...rest }: LanguageSettingProps): react.JSX.Element;
|
|
38
48
|
|
|
39
49
|
export { LanguageSetting, type LanguageSettingProps, ThemeSetting, type ThemeSettingProps };
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { Select } from "./ui.js";
|
|
3
|
-
function ThemeSetting({ value, onChange, label, optionLabels, id }) {
|
|
4
|
-
return /* @__PURE__ */ jsxs(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
function ThemeSetting({ value, onChange, label, optionLabels, id, ...rest }) {
|
|
5
|
+
return /* @__PURE__ */ jsxs(
|
|
6
|
+
Select,
|
|
7
|
+
{
|
|
8
|
+
...rest,
|
|
9
|
+
id,
|
|
10
|
+
label,
|
|
11
|
+
value,
|
|
12
|
+
onChange: (e) => onChange(e.target.value),
|
|
13
|
+
children: [
|
|
14
|
+
/* @__PURE__ */ jsx("option", { value: "system", children: optionLabels.system }),
|
|
15
|
+
/* @__PURE__ */ jsx("option", { value: "light", children: optionLabels.light }),
|
|
16
|
+
/* @__PURE__ */ jsx("option", { value: "dark", children: optionLabels.dark })
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
);
|
|
9
20
|
}
|
|
10
|
-
function LanguageSetting({ value, onChange, label, options, id }) {
|
|
11
|
-
return /* @__PURE__ */ jsx(Select, { id, label, value, onChange: (e) => onChange(e.target.value), children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o.code, children: o.label }, o.code)) });
|
|
21
|
+
function LanguageSetting({ value, onChange, label, options, id, ...rest }) {
|
|
22
|
+
return /* @__PURE__ */ jsx(Select, { ...rest, id, label, value, onChange: (e) => onChange(e.target.value), children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o.code, children: o.label }, o.code)) });
|
|
12
23
|
}
|
|
13
24
|
export {
|
|
14
25
|
LanguageSetting,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/settings-fields.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { Select } from \"./ui\";\nimport type { ThemePreference } from \"../theme/theme-store\";\n\n/**\n * App-agnostic settings-field components (feedback #333): the language + theme\n * controls that every app's settings page needs, so they live in @hb/ui and the\n * app just supplies the current value, a change handler and translated labels.\n * The app owns WHERE these render (its own settings page) and any app-specific\n * fields around them.\n */\n\nexport interface ThemeSettingProps {\n value: ThemePreference;\n onChange: (value: ThemePreference) => void;\n label: ReactNode;\n /** Translated option labels. */\n optionLabels: { system: string; light: string; dark: string };\n id?: string;\n}\n\nexport function ThemeSetting({ value, onChange, label, optionLabels, id }: ThemeSettingProps) {\n return (\n <Select
|
|
1
|
+
{"version":3,"sources":["../../src/components/settings-fields.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { Select } from \"./ui\";\nimport type { SelectProps } from \"./ui\";\nimport type { ThemePreference } from \"../theme/theme-store\";\n\n/**\n * App-agnostic settings-field components (feedback #333): the language + theme\n * controls that every app's settings page needs, so they live in @hb/ui and the\n * app just supplies the current value, a change handler and translated labels.\n * The app owns WHERE these render (its own settings page) and any app-specific\n * fields around them.\n */\n\n/**\n * Everything a {@link Select} takes, minus the two this component owns: the value is a\n * {@link ThemePreference} rather than a string, and `onChange` hands over the preference\n * itself because no caller of this wants the event. The rest — `disabled`, `required`,\n * `invalid`, `error`, a `data-tour` anchor — passes straight through, which is what makes\n * this a settings FIELD rather than a fixed widget.\n */\nexport interface ThemeSettingProps\n extends Omit<SelectProps, \"value\" | \"onChange\" | \"children\"> {\n value: ThemePreference;\n onChange: (value: ThemePreference) => void;\n label: ReactNode;\n /** Translated option labels. */\n optionLabels: { system: string; light: string; dark: string };\n id?: string;\n}\n\nexport function ThemeSetting({ value, onChange, label, optionLabels, id, ...rest }: ThemeSettingProps) {\n return (\n <Select\n {...rest}\n id={id}\n label={label}\n value={value}\n onChange={(e) => onChange(e.target.value as ThemePreference)}\n >\n <option value=\"system\">{optionLabels.system}</option>\n <option value=\"light\">{optionLabels.light}</option>\n <option value=\"dark\">{optionLabels.dark}</option>\n </Select>\n );\n}\n\n/** See {@link ThemeSettingProps}: a `Select`'s props with the value and the change\n * handler re-typed to the language CODE the caller stores. */\nexport interface LanguageSettingProps\n extends Omit<SelectProps, \"value\" | \"onChange\" | \"children\"> {\n value: string;\n onChange: (code: string) => void;\n label: ReactNode;\n options: { code: string; label: string }[];\n id?: string;\n}\n\nexport function LanguageSetting({ value, onChange, label, options, id, ...rest }: LanguageSettingProps) {\n return (\n <Select {...rest} id={id} label={label} value={value} onChange={(e) => onChange(e.target.value)}>\n {options.map((o) => (\n <option key={o.code} value={o.code}>\n {o.label}\n </option>\n ))}\n </Select>\n );\n}\n"],"mappings":";AAgCI,SAOE,KAPF;AA/BJ,SAAS,cAAc;AA6BhB,SAAS,aAAa,EAAE,OAAO,UAAU,OAAO,cAAc,IAAI,GAAG,KAAK,GAAsB;AACrG,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAwB;AAAA,MAE3D;AAAA,4BAAC,YAAO,OAAM,UAAU,uBAAa,QAAO;AAAA,QAC5C,oBAAC,YAAO,OAAM,SAAS,uBAAa,OAAM;AAAA,QAC1C,oBAAC,YAAO,OAAM,QAAQ,uBAAa,MAAK;AAAA;AAAA;AAAA,EAC1C;AAEJ;AAaO,SAAS,gBAAgB,EAAE,OAAO,UAAU,OAAO,SAAS,IAAI,GAAG,KAAK,GAAyB;AACtG,SACE,oBAAC,UAAQ,GAAG,MAAM,IAAQ,OAAc,OAAc,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK,GAC3F,kBAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,EAAE,MAC3B,YAAE,SADQ,EAAE,IAEf,CACD,GACH;AAEJ;","names":[]}
|