@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,94 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { create } from "zustand";
|
|
3
|
+
import { persist } from "zustand/middleware";
|
|
4
|
+
import type { StoreApi, UseBoundStore } from "zustand";
|
|
5
|
+
import { logger } from "../lib/logger";
|
|
6
|
+
import type { HeatStops } from "./chart-palette";
|
|
7
|
+
import { applyTokenSet, DEFAULT_PRESET, presetById, type TokenSet } from "./palette-presets";
|
|
8
|
+
import type { ThemeState, ThemeMode } from "./theme-store";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pre-hydration counterpart to `applyPersistedTheme`: read the palette id
|
|
12
|
+
* persisted under `storageKey` straight from localStorage and apply that preset's
|
|
13
|
+
* tokens for the given resolved `mode` to <html>, BEFORE React mounts (no flash of
|
|
14
|
+
* the default palette). Falls back to the default preset for missing/malformed
|
|
15
|
+
* state, so an app entrypoint never re-types the persist envelope.
|
|
16
|
+
*/
|
|
17
|
+
export function applyPersistedPalette(storageKey: string, mode: ThemeMode): void {
|
|
18
|
+
let id = DEFAULT_PRESET.id;
|
|
19
|
+
try {
|
|
20
|
+
const blob = JSON.parse(localStorage.getItem(storageKey) ?? "{}") as {
|
|
21
|
+
state?: { id?: string };
|
|
22
|
+
};
|
|
23
|
+
if (blob.state?.id) id = blob.state.id;
|
|
24
|
+
} catch {
|
|
25
|
+
/* malformed storage → default preset */
|
|
26
|
+
}
|
|
27
|
+
applyTokenSet(document.documentElement, presetById(id)[mode]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The palette layer flips the whole app between candidate appearance presets at
|
|
31
|
+
// runtime (feedback #307), re-skinning every token-driven surface and chart.
|
|
32
|
+
// Persisted like the theme so a reload keeps the selection.
|
|
33
|
+
interface PaletteState {
|
|
34
|
+
id: string;
|
|
35
|
+
setId: (id: string) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build a palette store bound to a consumer-supplied localStorage key, wired to
|
|
40
|
+
* that app's theme store (for the active light/dark mode). Returns the palette
|
|
41
|
+
* hook plus the effect + selector hooks that read the active preset's tokens.
|
|
42
|
+
*/
|
|
43
|
+
export function createPaletteStore(
|
|
44
|
+
storageKey: string,
|
|
45
|
+
useTheme: UseBoundStore<StoreApi<ThemeState>>,
|
|
46
|
+
) {
|
|
47
|
+
const usePalette = create<PaletteState>()(
|
|
48
|
+
logger(
|
|
49
|
+
persist(
|
|
50
|
+
(set) => ({
|
|
51
|
+
id: DEFAULT_PRESET.id,
|
|
52
|
+
setId: (id) => set({ id }),
|
|
53
|
+
}),
|
|
54
|
+
{ name: storageKey, version: 1 },
|
|
55
|
+
),
|
|
56
|
+
"palette",
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/** Apply the active preset's tokens to <html> as inline CSS custom properties,
|
|
61
|
+
* re-applying when the preset or the light/dark theme changes. Tailwind v4
|
|
62
|
+
* strips a bare root token block from the bundle, so this inline write from
|
|
63
|
+
* the TS source of truth is what actually drives the colours. */
|
|
64
|
+
function useApplyPalette(): void {
|
|
65
|
+
const id = usePalette((s) => s.id);
|
|
66
|
+
const mode = useTheme((s) => s.mode);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
applyTokenSet(document.documentElement, presetById(id)[mode]);
|
|
69
|
+
}, [id, mode]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The active preset's token set for the current theme — the source for the
|
|
73
|
+
* JS-interpolated charts (treemap fills, heatmap stops) that can't read CSS
|
|
74
|
+
* vars. */
|
|
75
|
+
function useActiveTokenSet(): TokenSet {
|
|
76
|
+
const id = usePalette((s) => s.id);
|
|
77
|
+
const mode = useTheme((s) => s.mode);
|
|
78
|
+
return presetById(id)[mode];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The 9 categorical chart hues for the active preset/theme (treemap cells). */
|
|
82
|
+
function useChartHex(): string[] {
|
|
83
|
+
return useActiveTokenSet().chart;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The active preset/theme heatmap interpolation stops. */
|
|
87
|
+
function useHeatStops(): HeatStops {
|
|
88
|
+
return useActiveTokenSet().heat;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { usePalette, useApplyPalette, useActiveTokenSet, useChartHex, useHeatStops };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type PaletteStore = ReturnType<typeof createPaletteStore>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { create } from "zustand";
|
|
3
|
+
import { persist } from "zustand/middleware";
|
|
4
|
+
import { logger } from "../lib/logger";
|
|
5
|
+
|
|
6
|
+
export type ThemeMode = "light" | "dark";
|
|
7
|
+
/** What the user chose; "system" tracks the OS `prefers-color-scheme`. */
|
|
8
|
+
export type ThemePreference = "light" | "dark" | "system";
|
|
9
|
+
|
|
10
|
+
export interface ThemeState {
|
|
11
|
+
/** The user's choice (persisted). */
|
|
12
|
+
preference: ThemePreference;
|
|
13
|
+
/** The RESOLVED light/dark actually in effect (system → OS). Read this for
|
|
14
|
+
* anything that needs the concrete theme (e.g. token selection, toasts). */
|
|
15
|
+
mode: ThemeMode;
|
|
16
|
+
setPreference: (preference: ThemePreference) => void;
|
|
17
|
+
/** Set an explicit light/dark choice (preference follows). */
|
|
18
|
+
setMode: (mode: ThemeMode) => void;
|
|
19
|
+
/** Flip the effective theme to the opposite explicit choice. */
|
|
20
|
+
toggle: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function systemPreference(): ThemeMode {
|
|
24
|
+
try {
|
|
25
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
26
|
+
} catch {
|
|
27
|
+
return "light";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolve(preference: ThemePreference): ThemeMode {
|
|
32
|
+
return preference === "system" ? systemPreference() : preference;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Normalize a persisted theme-state blob to a valid preference. Older blobs
|
|
36
|
+
* persisted the concrete `mode` (light/dark); carry that over as an explicit
|
|
37
|
+
* preference so the choice survives. Shared by the store's `migrate` and the
|
|
38
|
+
* pre-hydration {@link applyPersistedTheme}. */
|
|
39
|
+
function preferenceFromState(
|
|
40
|
+
s: { mode?: string; preference?: string } | null | undefined,
|
|
41
|
+
): ThemePreference {
|
|
42
|
+
if (s?.preference === "light" || s?.preference === "dark" || s?.preference === "system") {
|
|
43
|
+
return s.preference;
|
|
44
|
+
}
|
|
45
|
+
if (s?.mode === "light" || s?.mode === "dark") return s.mode;
|
|
46
|
+
return "system";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Toggle the `.dark` class on <html> for a resolved light/dark mode. */
|
|
50
|
+
function applyThemeClass(mode: ThemeMode): void {
|
|
51
|
+
const root = document.documentElement;
|
|
52
|
+
if (mode === "dark") root.classList.add("dark");
|
|
53
|
+
else root.classList.remove("dark");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Pre-hydration: read the theme PREFERENCE persisted under `storageKey` straight
|
|
58
|
+
* from localStorage, resolve it to a concrete light/dark mode, and toggle the
|
|
59
|
+
* `.dark` class on <html> — BEFORE React and the store mount, so the first paint
|
|
60
|
+
* already has the right theme (no flash). Returns the resolved mode so the caller
|
|
61
|
+
* can chain {@link applyPersistedPalette}. Encapsulates the zustand-persist
|
|
62
|
+
* envelope + the legacy `mode`→`preference` fallback so apps never re-type the
|
|
63
|
+
* storage shape in their entrypoint.
|
|
64
|
+
*/
|
|
65
|
+
export function applyPersistedTheme(storageKey: string): ThemeMode {
|
|
66
|
+
let preference: ThemePreference = "system";
|
|
67
|
+
try {
|
|
68
|
+
const blob = JSON.parse(localStorage.getItem(storageKey) ?? "{}") as {
|
|
69
|
+
state?: { mode?: string; preference?: string };
|
|
70
|
+
};
|
|
71
|
+
preference = preferenceFromState(blob.state);
|
|
72
|
+
} catch {
|
|
73
|
+
/* malformed storage → system */
|
|
74
|
+
}
|
|
75
|
+
const mode = resolve(preference);
|
|
76
|
+
applyThemeClass(mode);
|
|
77
|
+
return mode;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build a theme store bound to a consumer-supplied localStorage key. Tracks a
|
|
82
|
+
* light/dark/system PREFERENCE (feedback #330) and exposes the resolved `mode`.
|
|
83
|
+
* Returns the `useTheme` hook plus a `useApplyTheme` effect that toggles the
|
|
84
|
+
* `.dark` class on <html> and, while on "system", follows OS theme changes live.
|
|
85
|
+
*/
|
|
86
|
+
export function createThemeStore(storageKey: string) {
|
|
87
|
+
const useTheme = create<ThemeState>()(
|
|
88
|
+
logger(
|
|
89
|
+
persist(
|
|
90
|
+
(set, get) => ({
|
|
91
|
+
preference: "system" as ThemePreference,
|
|
92
|
+
mode: systemPreference(),
|
|
93
|
+
setPreference: (preference) => set({ preference, mode: resolve(preference) }),
|
|
94
|
+
setMode: (mode) => set({ preference: mode, mode }),
|
|
95
|
+
toggle: () => {
|
|
96
|
+
const next: ThemeMode = get().mode === "dark" ? "light" : "dark";
|
|
97
|
+
set({ preference: next, mode: next });
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
{
|
|
101
|
+
name: storageKey,
|
|
102
|
+
version: 2,
|
|
103
|
+
// Only the preference is persisted; `mode` is derived on load.
|
|
104
|
+
partialize: (s) => ({ preference: s.preference }),
|
|
105
|
+
migrate: (persistedState): { preference: ThemePreference } => ({
|
|
106
|
+
preference: preferenceFromState(
|
|
107
|
+
persistedState as { mode?: string; preference?: string } | null,
|
|
108
|
+
),
|
|
109
|
+
}),
|
|
110
|
+
onRehydrateStorage: () => (state) => {
|
|
111
|
+
// The persisted preference decides the concrete mode at load.
|
|
112
|
+
if (state) state.mode = resolve(state.preference);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
),
|
|
116
|
+
"theme",
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
function useApplyTheme(): void {
|
|
121
|
+
const mode = useTheme((s) => s.mode);
|
|
122
|
+
const preference = useTheme((s) => s.preference);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
applyThemeClass(mode);
|
|
125
|
+
}, [mode]);
|
|
126
|
+
// While tracking the system, follow OS theme changes live.
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (preference !== "system") return;
|
|
129
|
+
let mq: MediaQueryList;
|
|
130
|
+
try {
|
|
131
|
+
mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
132
|
+
} catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const onChange = () => useTheme.setState({ mode: mq.matches ? "dark" : "light" });
|
|
136
|
+
onChange();
|
|
137
|
+
mq.addEventListener?.("change", onChange);
|
|
138
|
+
return () => mq.removeEventListener?.("change", onChange);
|
|
139
|
+
}, [preference]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { useTheme, useApplyTheme };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type ThemeStore = ReturnType<typeof createThemeStore>;
|