@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,153 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface ToggleOption<T extends string> {
|
|
5
|
+
value: T;
|
|
6
|
+
label: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `onChange` is the group's own — the chosen VALUE, not a DOM event — so the div's
|
|
12
|
+
* `onChange` is omitted rather than shadowed: leaving both in scope would give the
|
|
13
|
+
* prop two incompatible meanings depending on which overload TypeScript picked.
|
|
14
|
+
*
|
|
15
|
+
* `children` is omitted too: this renders its `options` and nothing else, so a `children` the type
|
|
16
|
+
* accepted and the component ignored would be a prop that silently does nothing —
|
|
17
|
+
* worse than one that does not compile.
|
|
18
|
+
*/
|
|
19
|
+
export interface ToggleGroupProps<T extends string>
|
|
20
|
+
extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
|
|
21
|
+
value: T;
|
|
22
|
+
onChange: (value: T) => void;
|
|
23
|
+
options: ToggleOption<T>[];
|
|
24
|
+
className?: string;
|
|
25
|
+
/** Applied to every option button (e.g. to tune height/rounding to match
|
|
26
|
+
* adjacent fields). Per-option `className` still wins over this. */
|
|
27
|
+
optionClassName?: string;
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Pass `aria-label` instead — the DOM spelling, which every other
|
|
30
|
+
* control in this kit now answers to. Kept working because three applications ship
|
|
31
|
+
* this one today; it names the group only when `aria-label` is absent.
|
|
32
|
+
*/
|
|
33
|
+
ariaLabel?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Show, refuse the change (Keksdose live #288: a payment dated in the future has no
|
|
36
|
+
* state to set).
|
|
37
|
+
*
|
|
38
|
+
* Whatever `value` says stays pressed and keeps its own fill rather than going grey
|
|
39
|
+
* with the rest — a reader who cannot see WHICH option is chosen has been told less
|
|
40
|
+
* than before it was disabled. A caller with nothing to show passes no value, and
|
|
41
|
+
* the group renders dimmed with nothing pressed, which is the shape Keksdose's
|
|
42
|
+
* status picker uses for a row whose status does not exist yet.
|
|
43
|
+
*
|
|
44
|
+
* On the whole GROUP, not per option: a segmented control where some segments are
|
|
45
|
+
* live and others are not is a menu with holes in it, and no caller here wants one.
|
|
46
|
+
*/
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function ToggleGroup<T extends string>({
|
|
51
|
+
value,
|
|
52
|
+
onChange,
|
|
53
|
+
options,
|
|
54
|
+
className,
|
|
55
|
+
optionClassName,
|
|
56
|
+
ariaLabel,
|
|
57
|
+
disabled = false,
|
|
58
|
+
"aria-label": ariaLabelAttr,
|
|
59
|
+
...rest
|
|
60
|
+
}: ToggleGroupProps<T>) {
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
// The audit's named example of a closed prop list (§"Public API design"): the
|
|
64
|
+
// tour locates a step by CSS SELECTOR, so a component that drops every attribute
|
|
65
|
+
// it was not expecting cannot be spotlighted at all — and Keksdose's rule editor
|
|
66
|
+
// carries a comment explaining that it wraps this group in a bare <div> for
|
|
67
|
+
// exactly that reason.
|
|
68
|
+
//
|
|
69
|
+
// `...rest` first, then the attributes the group cannot do without: a caller
|
|
70
|
+
// hanging an anchor or a test id on the group must not be able to overwrite the
|
|
71
|
+
// radiogroup role or the disabled state by accident. `className` is destructured
|
|
72
|
+
// out entirely and merged through `cn`, so it is never in here.
|
|
73
|
+
{...rest}
|
|
74
|
+
role="radiogroup"
|
|
75
|
+
// The DOM spelling wins; `ariaLabel` is the fallback for the call sites that
|
|
76
|
+
// have not moved yet.
|
|
77
|
+
aria-label={ariaLabelAttr ?? ariaLabel}
|
|
78
|
+
// `aria-disabled` on the group as well as `disabled` on each button: a radio
|
|
79
|
+
// group is what the user is being refused, and a screen reader announcing
|
|
80
|
+
// three separately-disabled radios does not say that.
|
|
81
|
+
aria-disabled={disabled || undefined}
|
|
82
|
+
className={cn(
|
|
83
|
+
// `gap-0.5` — the same 2px as the container's own padding, so EVERY segment
|
|
84
|
+
// sits in a uniform 2px moat and no two fills ever touch. Flush segments were
|
|
85
|
+
// Keksdose live #268's rework: the pressed segment wears a saturated fill and
|
|
86
|
+
// an unpressed neighbour wears a pale hover fill, and with a shared edge the
|
|
87
|
+
// two rectangles read as one smeared shape — *"the boundary of the selected
|
|
88
|
+
// option and hovering next to it overlays the boundary of the selected
|
|
89
|
+
// button"*. A gap is what makes each segment its own chip; it cannot be
|
|
90
|
+
// undone by a caller's per-option colour, which a hover-only fix could.
|
|
91
|
+
//
|
|
92
|
+
// (The hover fill is the DESKTOP half of that report: Tailwind v4 wraps every
|
|
93
|
+
// `hover:` in `@media (hover: hover)`, so a phone never paints it. The half a
|
|
94
|
+
// phone does see is the focus ring — see the segment's own note below.)
|
|
95
|
+
"inline-flex w-full gap-0.5 rounded-md border border-[var(--border-strong)] bg-[var(--bg-surface)] p-0.5 shadow-sm",
|
|
96
|
+
// The whole group fades, the way every other disabled control in this
|
|
97
|
+
// package does; `cursor-not-allowed` is on the buttons, which is what a
|
|
98
|
+
// pointer is actually over.
|
|
99
|
+
disabled && "opacity-60",
|
|
100
|
+
className,
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{options.map((opt) => {
|
|
104
|
+
const active = opt.value === value;
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
key={opt.value}
|
|
108
|
+
type="button"
|
|
109
|
+
role="radio"
|
|
110
|
+
aria-checked={active}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
onClick={() => onChange(opt.value)}
|
|
113
|
+
className={cn(
|
|
114
|
+
// `truncate` (which carries whitespace-nowrap) rather than letting a
|
|
115
|
+
// label wrap: a segmented control sizes its whole row to the tallest
|
|
116
|
+
// option, so one two-word option — Keksdose feedback #147's "Where I
|
|
117
|
+
// am" — silently doubles the height of every segment beside it.
|
|
118
|
+
//
|
|
119
|
+
// `basis-auto` is what keeps that ellipsis a LAST resort rather than
|
|
120
|
+
// the normal state (Keksdose dev#475). With flex-1's `basis-0`, a
|
|
121
|
+
// shrink-to-fit group (`w-auto`) still resolves to the sum of the
|
|
122
|
+
// labels' widths — and then splits it EQUALLY, so the short option
|
|
123
|
+
// got 66px it did not need and "Where I am" got 66 of the 81 it did:
|
|
124
|
+
// truncated at 1778px of free screen. Basing each segment on its own
|
|
125
|
+
// content and sharing only the LEFTOVER space keeps a full-width
|
|
126
|
+
// group's segments near-equal and an auto-width group's exact.
|
|
127
|
+
//
|
|
128
|
+
// `focus-visible` + `ring-inset`, not `focus` + an outset ring. A ring
|
|
129
|
+
// is a box-shadow that spreads OUTWARD, so on a flush group it painted
|
|
130
|
+
// 2px of ring over both neighbours and over the container's own border
|
|
131
|
+
// — and on a phone it appeared on every TAP, because a tap focuses the
|
|
132
|
+
// button. That is the other half of what live #268's rework saw
|
|
133
|
+
// overlaying the selected segment's boundary. Inset keeps the ring
|
|
134
|
+
// inside the segment it belongs to; focus-visible keeps it for the
|
|
135
|
+
// keyboard, which is the only input that needs it.
|
|
136
|
+
"min-w-0 flex-1 basis-auto truncate rounded px-3 py-1.5 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--border-strong)]",
|
|
137
|
+
active
|
|
138
|
+
? "bg-[var(--bg-inverse)] text-[var(--text-inverse)]"
|
|
139
|
+
: "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]",
|
|
140
|
+
// No hover fill on a group that cannot be changed — a segment that
|
|
141
|
+
// lights up under the pointer is an offer, and there is none here.
|
|
142
|
+
disabled && "cursor-not-allowed hover:bg-transparent dark:hover:bg-transparent",
|
|
143
|
+
optionClassName,
|
|
144
|
+
opt.className,
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
{opt.label}
|
|
148
|
+
</button>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// A legend whose entries are SWITCHES — and the stroke table it shares with
|
|
2
|
+
// `SeriesChart`. Lifted out of lenkbank's measurement plots, where ten legends over
|
|
3
|
+
// thirteen charts took channels on and off the picture.
|
|
4
|
+
//
|
|
5
|
+
// No recharts in this module: a legend of switches is plain buttons, and it is as
|
|
6
|
+
// useful under a chart the consumer drew by hand as under `SeriesChart`.
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import { cn } from "../lib/cn";
|
|
9
|
+
import { useKitLabels } from "../i18n/kit-labels";
|
|
10
|
+
import { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from "./series-chart-labels";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The strokes a reader can tell apart, in the order they are handed out.
|
|
14
|
+
*
|
|
15
|
+
* For the charts where the colour is already spoken for: a comparison draws each
|
|
16
|
+
* measurement in its own colour, so a chart carrying several CHANNELS of each has
|
|
17
|
+
* nothing left but the stroke — and "solid versus dashed" only gets you two.
|
|
18
|
+
*
|
|
19
|
+
* **Five, and the fifth is the last one.** They are the draughtsman's line types, in
|
|
20
|
+
* the draughtsman's order — continuous, dashed, dotted, dash-dot, dash-dot-dot — the
|
|
21
|
+
* one family of five a century of drawings has already proved legible at a hair's
|
|
22
|
+
* width. A sixth is not distinguishable from one of these at two pixels wide, so a
|
|
23
|
+
* chart needing one is a chart that should be split.
|
|
24
|
+
*
|
|
25
|
+
* Kept here, beside the legend's own mark, and read by `SeriesChart` from here — so
|
|
26
|
+
* the key under a chart and the lines on it cannot disagree.
|
|
27
|
+
*/
|
|
28
|
+
export const STROKE_PATTERNS: readonly (string | undefined)[] = [
|
|
29
|
+
undefined,
|
|
30
|
+
"5 4",
|
|
31
|
+
"1 3",
|
|
32
|
+
"9 3 2 3",
|
|
33
|
+
"9 3 2 3 2 3",
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/** The dash array for the nth pattern, wrapping past the fifth. */
|
|
37
|
+
export function strokeDash(order: number): string | undefined {
|
|
38
|
+
const n = STROKE_PATTERNS.length;
|
|
39
|
+
return STROKE_PATTERNS[((Math.trunc(order) % n) + n) % n];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Which of the patterns a `step` series is drawn in.
|
|
43
|
+
*
|
|
44
|
+
* A whole-number channel that jumps rather than travels is drawn dashed, and it has
|
|
45
|
+
* to be one of {@link STROKE_PATTERNS} rather than a dash array written out at the
|
|
46
|
+
* call site: a legend entry says its pattern by index, so a step line drawn in
|
|
47
|
+
* anything else would be a legend promising a solid line for a dashed one. */
|
|
48
|
+
export const STEP_DASH = 1;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One key switched, as a new hidden set.
|
|
52
|
+
*
|
|
53
|
+
* Every caller writes `onToggle={(key) => setHidden(toggleHidden(hidden, key))}`. A
|
|
54
|
+
* new set rather than a mutated one, because the state is read during render and a
|
|
55
|
+
* mutation of something a component already holds is invisible to React.
|
|
56
|
+
*/
|
|
57
|
+
export function toggleHidden(hidden: ReadonlySet<string>, key: string): ReadonlySet<string> {
|
|
58
|
+
const next = new Set(hidden);
|
|
59
|
+
if (!next.delete(key)) next.add(key);
|
|
60
|
+
return next;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface LegendEntry {
|
|
64
|
+
key: string;
|
|
65
|
+
label: ReactNode;
|
|
66
|
+
/** Any CSS colour — `paletteFor(i)` for the kit's own ramp. */
|
|
67
|
+
color: string;
|
|
68
|
+
/**
|
|
69
|
+
* What the entry's mark looks like. `swatch` — a filled square, the default — says
|
|
70
|
+
* WHICH MEASUREMENT. `stroke` says WHICH QUANTITY, for the charts that carry both at
|
|
71
|
+
* once and tell them apart by colour and by dash.
|
|
72
|
+
*/
|
|
73
|
+
marker?: "swatch" | "stroke";
|
|
74
|
+
/** Which of {@link STROKE_PATTERNS}, when the marker is a stroke — the same index
|
|
75
|
+
* `SeriesChartSeries.dash` takes, so a legend cannot promise a dot-dash the plot
|
|
76
|
+
* draws dashed. */
|
|
77
|
+
dash?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ToggleLegendProps {
|
|
81
|
+
entries: LegendEntry[];
|
|
82
|
+
/** The keys currently off the chart. */
|
|
83
|
+
hidden: ReadonlySet<string>;
|
|
84
|
+
onToggle: (key: string) => void;
|
|
85
|
+
/** `vertical` is for a legend standing BESIDE the charts rather than under them —
|
|
86
|
+
* what a row of charts sharing one legend wants, since under two charts there is
|
|
87
|
+
* no "under", and putting it under one says it belongs to that one. */
|
|
88
|
+
orientation?: "horizontal" | "vertical";
|
|
89
|
+
/**
|
|
90
|
+
* Draw a legend of ONE entry rather than nothing.
|
|
91
|
+
*
|
|
92
|
+
* A single line's legend is usually noise. It is not where the legend is the only
|
|
93
|
+
* SWITCH that line has — a series that starts hidden and has no entry to bring it
|
|
94
|
+
* back is gone for good.
|
|
95
|
+
*/
|
|
96
|
+
showSingle?: boolean;
|
|
97
|
+
className?: string;
|
|
98
|
+
/** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */
|
|
99
|
+
labels?: Partial<SeriesChartLabels>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A legend whose entries are switches.
|
|
104
|
+
*
|
|
105
|
+
* `ChartLegendContent` with `onItemClick`/`activeKey` is the other kind: clicking an
|
|
106
|
+
* entry HIGHLIGHTS it and dims the rest, which is right when every series belongs on
|
|
107
|
+
* the chart and one of them is momentarily interesting. This is for the opposite —
|
|
108
|
+
* three coordinates on one axis where y runs to 800 and z barely moves, and the only
|
|
109
|
+
* way to see z is to take y off the chart entirely. Any number may be off at once.
|
|
110
|
+
*
|
|
111
|
+
* A hidden entry keeps its mark and its place. Removing it would reflow the legend on
|
|
112
|
+
* every click and lose the one thing it is for: saying what COULD be shown.
|
|
113
|
+
*
|
|
114
|
+
* Each entry is a real `<button>` with `aria-pressed` — pressed means "on the chart" —
|
|
115
|
+
* so Tab reaches every switch and Space/Enter flips it.
|
|
116
|
+
*/
|
|
117
|
+
export function ToggleLegend({
|
|
118
|
+
entries,
|
|
119
|
+
hidden,
|
|
120
|
+
onToggle,
|
|
121
|
+
orientation = "horizontal",
|
|
122
|
+
showSingle = false,
|
|
123
|
+
className,
|
|
124
|
+
labels: labelsProp,
|
|
125
|
+
}: ToggleLegendProps) {
|
|
126
|
+
const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
|
|
127
|
+
if (entries.length < (showSingle ? 1 : 2)) return null;
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
role="group"
|
|
131
|
+
aria-label={labels.legend}
|
|
132
|
+
className={cn(
|
|
133
|
+
"flex gap-x-3 gap-y-1",
|
|
134
|
+
orientation === "vertical"
|
|
135
|
+
? "flex-col items-start justify-center"
|
|
136
|
+
: "mt-2 flex-wrap items-center",
|
|
137
|
+
className,
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
{entries.map((entry) => {
|
|
141
|
+
const off = hidden.has(entry.key);
|
|
142
|
+
return (
|
|
143
|
+
<button
|
|
144
|
+
key={entry.key}
|
|
145
|
+
type="button"
|
|
146
|
+
// Pressed rather than a checkbox: it is a control over what the chart
|
|
147
|
+
// draws, and "not pressed" is the same sentence the dimmed mark says.
|
|
148
|
+
aria-pressed={!off}
|
|
149
|
+
onClick={() => onToggle(entry.key)}
|
|
150
|
+
className={cn(
|
|
151
|
+
"flex items-center gap-1.5 rounded text-start text-[11px] text-[var(--text-secondary)] transition-opacity hover:opacity-80",
|
|
152
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]",
|
|
153
|
+
off && "opacity-35",
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
<LegendMark entry={entry} off={off} />
|
|
157
|
+
{entry.label}
|
|
158
|
+
</button>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The column a vertical legend stands in: beside the charts, on their horizontal
|
|
167
|
+
* centre line. `h-full` + `justify-center`, so it centres in a grid cell or a flex row
|
|
168
|
+
* that stretches it — and stacks whatever it is given, which is what a panel showing
|
|
169
|
+
* two legends at once needs. The width is the caller's: how wide a legend column is
|
|
170
|
+
* is a fact about that screen's layout.
|
|
171
|
+
*/
|
|
172
|
+
export function LegendColumn({ children, className }: { children: ReactNode; className?: string }) {
|
|
173
|
+
return (
|
|
174
|
+
<div className={cn("flex h-full flex-col justify-center gap-3", className)}>{children}</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A legend and what it is a legend OF, as a headed column.
|
|
180
|
+
*
|
|
181
|
+
* For legends that are several vocabularies at once — which measurement and which
|
|
182
|
+
* channel — so the entries are read down a group and the groups across. Wrapped rows
|
|
183
|
+
* would put the second half of one group on a line under the first half of the next.
|
|
184
|
+
*/
|
|
185
|
+
export function LegendGroup({ title, children }: { title: ReactNode; children: ReactNode }) {
|
|
186
|
+
return (
|
|
187
|
+
<div className="flex min-w-0 flex-col items-start gap-1">
|
|
188
|
+
<span className="text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]">
|
|
189
|
+
{title}
|
|
190
|
+
</span>
|
|
191
|
+
<div className="flex flex-col items-start gap-y-0.5">{children}</div>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The entry's mark: a square for a measurement, a stroke for a quantity. A hidden one
|
|
197
|
+
* keeps its outline — a mark that vanished would leave a line of text with nothing in
|
|
198
|
+
* front of it.
|
|
199
|
+
*
|
|
200
|
+
* A stroke is an SVG line with the chart's own `strokeDasharray`, not a CSS gradient
|
|
201
|
+
* approximating it: a gradient can say "solid or dashed" and nothing more. */
|
|
202
|
+
function LegendMark({ entry, off }: { entry: LegendEntry; off: boolean }) {
|
|
203
|
+
if (entry.marker === "stroke") {
|
|
204
|
+
return (
|
|
205
|
+
<svg aria-hidden viewBox="0 0 20 2" className="h-0.5 w-5 shrink-0 overflow-visible">
|
|
206
|
+
<line
|
|
207
|
+
x1={0}
|
|
208
|
+
y1={1}
|
|
209
|
+
x2={20}
|
|
210
|
+
y2={1}
|
|
211
|
+
stroke={entry.color}
|
|
212
|
+
strokeWidth={2}
|
|
213
|
+
strokeDasharray={strokeDash(entry.dash ?? 0)}
|
|
214
|
+
opacity={off ? 0.35 : 1}
|
|
215
|
+
/>
|
|
216
|
+
</svg>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return (
|
|
220
|
+
<span
|
|
221
|
+
aria-hidden
|
|
222
|
+
className="h-2.5 w-2.5 shrink-0 rounded-[3px]"
|
|
223
|
+
style={{
|
|
224
|
+
backgroundColor: off ? "transparent" : entry.color,
|
|
225
|
+
boxShadow: `inset 0 0 0 1.5px ${entry.color}`,
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|