@eifi1/ui-kit 0.32.2 → 0.33.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/README.md +16 -8
- package/dist/account/delete-account-setting.d.ts +10 -10
- package/dist/account/delete-account-setting.js +2 -1
- package/dist/account/delete-account-setting.js.map +1 -1
- package/dist/account/email-change-setting.d.ts +5 -0
- package/dist/account/email-change-setting.js +4 -3
- package/dist/account/email-change-setting.js.map +1 -1
- package/dist/account/sessions-setting.d.ts +4 -0
- package/dist/account/sessions-setting.js +3 -2
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.d.ts +10 -10
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +10 -10
- package/dist/admin/invitations-panel.js +4 -3
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/plan-change.d.ts +100 -0
- package/dist/admin/plan-change.js +208 -0
- package/dist/admin/plan-change.js.map +1 -0
- package/dist/admin/reviewer-scope-editor.d.ts +10 -10
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/admin/role-select.d.ts +7 -7
- package/dist/admin/role-select.js.map +1 -1
- package/dist/admin/transfer-ownership-dialog.d.ts +10 -10
- package/dist/admin/user-roster.d.ts +7 -7
- package/dist/admin/user-roster.js +2 -2
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/auth-errors.d.ts +13 -2
- package/dist/auth/auth-errors.js +5 -1
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/complete-name-dialog.d.ts +4 -1
- package/dist/auth/complete-name-dialog.js +2 -1
- package/dist/auth/complete-name-dialog.js.map +1 -1
- package/dist/auth/register-form.d.ts +10 -10
- package/dist/billing/billing-banner.d.ts +13 -11
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/billing-labels.d.ts +10 -10
- package/dist/billing/billing-labels.js +10 -1
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/billing-lock.d.ts +39 -12
- package/dist/billing/billing-lock.js +9 -3
- package/dist/billing/billing-lock.js.map +1 -1
- package/dist/billing/billing-overview.d.ts +154 -0
- package/dist/billing/billing-overview.js +2 -0
- package/dist/billing/billing-overview.js.map +1 -0
- package/dist/billing/billing-standing.d.ts +10 -10
- package/dist/billing/checkout-processing.d.ts +217 -0
- package/dist/billing/checkout-processing.js +198 -0
- package/dist/billing/checkout-processing.js.map +1 -0
- package/dist/billing/checkout-return.d.ts +51 -0
- package/dist/billing/checkout-return.js +43 -0
- package/dist/billing/checkout-return.js.map +1 -0
- package/dist/billing/pay-page.d.ts +210 -0
- package/dist/billing/pay-page.js +113 -0
- package/dist/billing/pay-page.js.map +1 -0
- package/dist/billing/plan-card.d.ts +10 -10
- package/dist/billing/plan-limit-notice.d.ts +10 -10
- package/dist/billing/plan-limit-toast.d.ts +143 -0
- package/dist/billing/plan-limit-toast.js +40 -0
- package/dist/billing/plan-limit-toast.js.map +1 -0
- package/dist/billing/subscription-actions.d.ts +38 -15
- package/dist/billing/subscription-actions.js +9 -2
- package/dist/billing/subscription-actions.js.map +1 -1
- package/dist/billing/subscription-status-chip.d.ts +9 -9
- package/dist/bin/eifi1-pay-page.mjs +166 -0
- package/dist/chart.d.ts +10 -10
- package/dist/components/account-chips.d.ts +6 -1
- package/dist/components/account-settings.d.ts +4 -1
- package/dist/components/account-settings.js +2 -1
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +10 -10
- package/dist/components/autocomplete.d.ts +2 -1
- package/dist/components/autocomplete.js +3 -1
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +10 -10
- package/dist/components/button-group.js +1 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +10 -10
- package/dist/components/calculator.js +3 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/checkbox-group.d.ts +2 -1
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/checkbox.d.ts +2 -1
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +19 -17
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +3 -1
- package/dist/components/choice-card.js +1 -1
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox.d.ts +4 -3
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +10 -10
- package/dist/components/confirm-dialog.js +6 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +11 -11
- package/dist/components/country-select.d.ts +2 -1
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +10 -10
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +12 -1
- package/dist/components/data-table-filter-popover.d.ts +12 -1
- package/dist/components/data-table-filters.d.ts +12 -1
- package/dist/components/data-table-labels.d.ts +12 -1
- package/dist/components/data-table-pagination.d.ts +12 -1
- package/dist/components/data-table.d.ts +12 -1
- package/dist/components/data-table.js +3 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +6 -4
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +3 -2
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +10 -10
- package/dist/components/field-parts.d.ts +32 -4
- package/dist/components/field-parts.js +54 -19
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/file-button.d.ts +10 -10
- package/dist/components/file-button.js +3 -2
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +11 -11
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.js +5 -5
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +9 -9
- package/dist/components/form-actions.js +3 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +10 -10
- package/dist/components/inline-edit-field.d.ts +2 -1
- package/dist/components/inline-edit-field.js +6 -6
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/language-select.d.ts +10 -10
- package/dist/components/legal-page.d.ts +10 -10
- package/dist/components/legal.d.ts +10 -10
- package/dist/components/list.d.ts +2 -1
- package/dist/components/list.js +4 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/money-field.d.ts +10 -10
- package/dist/components/month-picker.js +54 -44
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +3 -2
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +10 -10
- package/dist/components/number-input.d.ts +10 -10
- package/dist/components/numpad-sheet.d.ts +10 -10
- package/dist/components/numpad-sheet.js +2 -2
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/phone-input.d.ts +10 -10
- package/dist/components/row-actions.d.ts +5 -5
- package/dist/components/row-actions.js +130 -32
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart.d.ts +10 -10
- package/dist/components/settings-fields.d.ts +10 -10
- package/dist/components/share-card.d.ts +7 -7
- package/dist/components/share-card.js +26 -10
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/status-dot.d.ts +1 -0
- package/dist/components/status-dot.js +2 -2
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/swatch-picker.d.ts +2 -1
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +88 -17
- package/dist/components/swipeable-row.js +46 -9
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +2 -1
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.d.ts +2 -1
- package/dist/components/table.js +6 -0
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +9 -9
- package/dist/components/time-input.d.ts +10 -10
- package/dist/components/toggle-group.d.ts +2 -1
- package/dist/components/toggle-group.js +1 -1
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +4 -0
- package/dist/components/translation-review-editor.js +4 -3
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-parts.d.ts +1 -0
- package/dist/components/translation-review.d.ts +1 -0
- package/dist/components/translation-review.js +15 -18
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +9 -9
- package/dist/components/ui.js +47 -27
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +12 -1
- package/dist/components/user-avatar.d.ts +1 -0
- package/dist/components/write-lock.d.ts +101 -13
- package/dist/components/write-lock.js +37 -13
- package/dist/components/write-lock.js.map +1 -1
- package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
- package/dist/data-table.d.ts +12 -1
- package/dist/{delete-account-setting-Dy4kJgUm.d.ts → delete-account-setting-DiPX4Vue.d.ts} +355 -41
- package/dist/feedback/feedback-attachment.d.ts +10 -10
- package/dist/feedback/feedback-attachment.js +7 -7
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-context.d.ts +10 -10
- package/dist/feedback/feedback-crash.d.ts +10 -10
- package/dist/feedback/feedback-dialog.d.ts +10 -10
- package/dist/feedback/feedback-inbox.d.ts +9 -9
- package/dist/feedback/feedback-inbox.js +7 -3
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-labels.d.ts +10 -10
- package/dist/feedback/feedback-menu.d.ts +3 -3
- package/dist/feedback/feedback-record.d.ts +10 -10
- package/dist/feedback/feedback-row-detail.d.ts +10 -10
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +10 -10
- package/dist/feedback/feedback-submit.d.ts +10 -10
- package/dist/feedback/feedback-swipe.d.ts +10 -10
- package/dist/feedback/feedback-swipe.js +3 -6
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +10 -10
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +10 -10
- package/dist/feedback.d.ts +10 -10
- package/dist/hooks/use-file-drop.d.ts +10 -10
- package/dist/i18n/defaults.d.ts +10 -10
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +10 -10
- package/dist/i18n/german.js +34 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +4 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +10 -10
- package/dist/i18n/locales/de-CH.d.ts +10 -10
- package/dist/i18n/locales/en.d.ts +10 -10
- package/dist/i18n/locales/es.d.ts +10 -10
- package/dist/i18n/locales/es.js +34 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +10 -10
- package/dist/i18n/locales/fr.js +35 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +10 -10
- package/dist/i18n/locales/hu.js +36 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +10 -10
- package/dist/i18n/locales/it.js +34 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +10 -10
- package/dist/i18n/locales/zh.js +34 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +10 -10
- package/dist/i18n/review.js +8 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.d.ts +10 -10
- package/dist/landing/landing-sections.d.ts +10 -10
- package/dist/landing/public-header.d.ts +10 -10
- package/dist/landing/routing.d.ts +10 -10
- package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
- package/dist/pay/index.html +30 -0
- package/dist/pay/pay.css +126 -0
- package/dist/pay/pay.js +198 -0
- package/dist/rhf/fields.d.ts +10 -10
- package/dist/rhf/form.d.ts +10 -10
- package/dist/rhf.d.ts +10 -10
- package/dist/settings/settings-section.d.ts +10 -10
- package/dist/shell/app-shell.d.ts +9 -9
- package/dist/shell/top-bar-brand.d.ts +10 -10
- package/dist/shell/topbar-action-menu.d.ts +1 -0
- package/dist/shell.d.ts +10 -10
- package/dist/testing/css-variables.d.ts +74 -0
- package/dist/testing/css-variables.generated.d.ts +7 -0
- package/dist/testing/css-variables.generated.js +8 -0
- package/dist/testing/css-variables.generated.js.map +1 -0
- package/dist/testing/css-variables.js +93 -0
- package/dist/testing/css-variables.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/theme/contrast-tokens.d.ts +44 -1
- package/dist/theme/contrast-tokens.js +21 -0
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -2
- package/dist/theme/palette-derive.js +60 -11
- package/dist/theme/palette-derive.js.map +1 -1
- package/dist/theme/palette-presets.js +45 -43
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +11 -11
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +10 -10
- package/package.json +10 -2
- package/src/account/delete-account-setting.tsx +6 -1
- package/src/account/email-change-setting.tsx +9 -3
- package/src/account/sessions-setting.tsx +7 -2
- package/src/admin/admin-action-confirm.tsx +2 -1
- package/src/admin/invitations-panel.tsx +9 -3
- package/src/admin/plan-change.tsx +459 -0
- package/src/admin/reviewer-scope-editor.tsx +2 -1
- package/src/admin/role-select.tsx +2 -1
- package/src/admin/user-roster.tsx +5 -3
- package/src/auth/auth-errors.ts +25 -2
- package/src/auth/complete-name-dialog.tsx +6 -2
- package/src/billing/billing-banner.tsx +3 -1
- package/src/billing/billing-labels.ts +45 -0
- package/src/billing/billing-lock.ts +36 -3
- package/src/billing/billing-overview.ts +58 -0
- package/src/billing/checkout-processing.ts +391 -0
- package/src/billing/checkout-return.ts +90 -0
- package/src/billing/pay-page.ts +242 -0
- package/src/billing/plan-limit-toast.tsx +91 -0
- package/src/billing/subscription-actions.tsx +41 -8
- package/src/components/account-settings.tsx +6 -2
- package/src/components/autocomplete.tsx +5 -2
- package/src/components/button-group.tsx +4 -1
- package/src/components/calculator.tsx +10 -6
- package/src/components/checkbox-group.tsx +2 -2
- package/src/components/checkbox.tsx +2 -1
- package/src/components/chip.tsx +37 -32
- package/src/components/choice-card.tsx +4 -2
- package/src/components/combobox.tsx +4 -4
- package/src/components/confirm-dialog.tsx +13 -6
- package/src/components/country-select.tsx +2 -2
- package/src/components/danger-confirm.tsx +2 -2
- package/src/components/data-table.tsx +20 -9
- package/src/components/date-picker.tsx +10 -7
- package/src/components/entity-combobox.tsx +3 -3
- package/src/components/field-parts.tsx +61 -14
- package/src/components/file-button.tsx +5 -4
- package/src/components/file-dropzone.tsx +2 -2
- package/src/components/floating-panel.tsx +7 -5
- package/src/components/form-actions.tsx +12 -5
- package/src/components/inline-edit-field.tsx +13 -13
- package/src/components/list.tsx +10 -9
- package/src/components/month-picker.tsx +8 -2
- package/src/components/multi-entity-combobox.tsx +3 -3
- package/src/components/numpad-sheet.tsx +8 -4
- package/src/components/row-actions.tsx +208 -58
- package/src/components/share-card.tsx +67 -13
- package/src/components/status-dot.tsx +7 -7
- package/src/components/swatch-picker.tsx +2 -2
- package/src/components/swipeable-row.tsx +138 -27
- package/src/components/switch.tsx +2 -1
- package/src/components/table.tsx +8 -1
- package/src/components/toggle-group.tsx +5 -3
- package/src/components/translation-review-editor.tsx +8 -3
- package/src/components/translation-review.tsx +18 -22
- package/src/components/ui.tsx +92 -43
- package/src/components/write-lock.tsx +150 -24
- package/src/feedback/feedback-attachment.tsx +13 -9
- package/src/feedback/feedback-inbox.tsx +10 -6
- package/src/feedback/feedback-row-detail.tsx +3 -2
- package/src/feedback/feedback-swipe.tsx +6 -11
- package/src/feedback/feedback-table.tsx +2 -1
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +38 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/es.ts +34 -0
- package/src/i18n/locales/fr.ts +39 -0
- package/src/i18n/locales/hu.ts +37 -0
- package/src/i18n/locales/it.ts +35 -0
- package/src/i18n/locales/zh.ts +33 -0
- package/src/i18n/review.ts +7 -0
- package/src/index.ts +48 -4
- package/src/testing/css-variables.generated.ts +112 -0
- package/src/testing/css-variables.ts +191 -0
- package/src/testing/index.ts +13 -0
- package/src/theme/contrast-tokens.ts +53 -2
- package/src/theme/palette-derive.ts +87 -10
- package/src/theme/palette-presets.ts +45 -43
- package/src/wizard/stepper-nav.tsx +2 -1
- package/tokens.css +306 -46
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme/palette-presets.ts"],"sourcesContent":["// Appearance presets — the single source of truth for the \"palette switcher\"\n// (feedback #307). Each preset is a full set of design-token values for BOTH\n// light and dark, covering the page/surface backgrounds, the brand accent, the\n// semantic money trio and the 9-colour categorical chart palette, plus the\n// heatmap stops. A preset is applied at runtime by writing these as CSS custom\n// properties onto <html> (see palette-store.ts), which instantly re-skins every\n// token-driven surface and chart. The two heatmaps and the treemap interpolate\n// concrete hex in JS, so they read the same values from here via the hooks in\n// palette-store.\n//\n// The alternative presets below were designed + accessibility-vetted as a set\n// (each is CVD-safe across protan/deutan/tritan, money is never red/green, and\n// text/background contrast clears WCAG AA on both themes). To promote one to the\n// app-wide default, copy its values into the :root/.dark blocks in tokens.css\n// (the default preset here mirrors those exactly).\n\nimport type { HeatStops } from \"./chart-palette\";\nimport { contrastStep, rememberTokenSet } from \"./contrast-tokens\";\n\nexport interface TokenSet {\n // Backgrounds & chrome\n bgPage: string;\n bgSurface: string;\n bgSurface2: string;\n border: string;\n textPrimary: string;\n // The two quieter text roles. Derived per preset by blending `textPrimary` toward\n // that preset's own surfaces until the contrast against the WORST of bgPage /\n // bgSurface / bgSurface2 hits 7:1 and 4.8:1 — so both clear WCAG AA for normal text\n // (4.5:1) on every surface they can land on, and both inherit the preset's warmth\n // instead of importing a cool grey from Tailwind's palette.\n textSecondary: string;\n textMuted: string;\n // Interactive brand accent\n brand: string;\n brandHover: string;\n brandContrast: string;\n // Semantic money (never red/green — CVD-safe, paired with +/- cues elsewhere)\n moneyIncome: string;\n moneyExpense: string;\n moneyNet: string;\n moneyNeutral: string;\n // Categorical chart series (Paul Tol \"Muted\" by default — CVD-safe), exactly 9\n chart: string[];\n // Heatmap interpolation stops (variance diverging + spending sequential)\n heat: HeatStops;\n}\n\nexport interface PalettePreset {\n id: string;\n name: string;\n blurb: string;\n light: TokenSet;\n dark: TokenSet;\n}\n\n// The shipped default (feedback #328): SOLAR-DUSK warm light + INDIGO-NOIR deep\n// dark surfaces — the two curated presets the user picked per mode — but with a\n// SINGLE unified brand accent (indigo) across both modes rather than each\n// preset's own accent (the user asked for the brand accent to stay the same).\n// So the light block is Solar Dusk with its teal brand overridden to indigo, and\n// the dark block is Indigo Noir (already indigo). The money trio\n// (income/expense/net) drives the chart bars and stays palette-driven so those\n// semantic colours adapt to each mode's contrast (best practice — feedback #328).\nexport const DEFAULT_PRESET: PalettePreset = {\n id: \"default\",\n name: \"Default\",\n blurb: \"Solar-dusk light · indigo-noir dark\",\n light: {\n // Solar Dusk (light) surfaces, with the brand unified to indigo (see above).\n bgPage: \"#ede3cf\",\n bgSurface: \"#f6ecd9\",\n bgSurface2: \"#f3ead6\",\n border: \"#e2d6bd\",\n textPrimary: \"#463f33\",\n textSecondary: \"#50493c\",\n textMuted: \"#696153\",\n brand: \"#4f46e5\",\n brandHover: \"#4338ca\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0b6650\",\n moneyExpense: \"#905902\",\n moneyNet: \"#7a4fa3\",\n moneyNeutral: \"#6b6453\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#f1e7d2\",\n under: \"#0b6650\",\n over: \"#9c6418\",\n seqLow: \"#f6ecd0\",\n seqHigh: \"#9c6418\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n // Indigo Noir (dark)\n bgPage: \"#0e0f1c\",\n bgSurface: \"#181a2c\",\n bgSurface2: \"#21243a\",\n border: \"#2e3252\",\n textPrimary: \"#e6e8f4\",\n textSecondary: \"#adafbe\",\n textMuted: \"#8d90a0\",\n brand: \"#8b93f8\",\n brandHover: \"#aab1fb\",\n brandContrast: \"#16182b\",\n moneyIncome: \"#22c3b6\",\n // Amber-400 (#fbbf24) sat at OKLab L 0.84 while every other dark token lives\n // at 0.68–0.74, so outflows outshouted the inflows they are paired with. Same\n // hue and chroma, pulled to L 0.785 (dev#434). The heat stops below keep the\n // brighter amber on purpose: a gradient endpoint is meant to be the loudest\n // thing in its scale, and it is a fill, not a figure.\n moneyExpense: \"#e4b035\",\n moneyNet: \"#b39aef\",\n moneyNeutral: \"#9aa1bd\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#cc78be\"],\n heat: {\n neutral: \"#363a5a\",\n under: \"#22c3b6\",\n over: \"#fbbf24\",\n seqLow: \"#3a2c14\",\n seqHigh: \"#fbbf24\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n};\n\n// Vetted alternatives to preview (CVD-safe, WCAG-AA on both themes).\nexport const ALTERNATIVE_PRESETS: PalettePreset[] = [\n {\n id: \"warm-paper\",\n name: \"Warm Paper\",\n blurb: \"Cream paper · warm charcoal\",\n light: {\n bgPage: \"#ece5d6\",\n bgSurface: \"#f5efe3\",\n bgSurface2: \"#efe9dc\",\n border: \"#e2dcce\",\n textPrimary: \"#2b2622\",\n textSecondary: \"#4f4a44\",\n textMuted: \"#68625b\",\n brand: \"#9a5b34\",\n brandHover: \"#824827\",\n brandContrast: \"#fdf8f2\",\n moneyIncome: \"#0e7490\",\n moneyExpense: \"#8f3f0a\",\n moneyNet: \"#7c4dab\",\n moneyNeutral: \"#6b6258\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#efe9dc\",\n under: \"#0e7490\",\n over: \"#b45309\",\n seqLow: \"#fbeed2\",\n seqHigh: \"#b45309\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#211e1a\",\n bgSurface: \"#2a2622\",\n bgSurface2: \"#322d27\",\n border: \"#3d372f\",\n textPrimary: \"#e8e2d6\",\n textSecondary: \"#bfb9af\",\n textMuted: \"#9e998f\",\n brand: \"#e0986a\",\n brandHover: \"#ecae85\",\n brandContrast: \"#2a1c10\",\n moneyIncome: \"#4dd4c4\",\n moneyExpense: \"#fbbf3a\",\n moneyNet: \"#bfa0e0\",\n moneyNeutral: \"#a39a8c\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#cf6488\", \"#cc78be\"],\n heat: {\n neutral: \"#3a352e\",\n under: \"#4dd4c4\",\n over: \"#fbbf3a\",\n seqLow: \"#2c2014\",\n seqHigh: \"#fbbf3a\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"nord-frost\",\n name: \"Nord Frost\",\n blurb: \"Cool snow · polar night\",\n light: {\n bgPage: \"#e2e7ef\",\n bgSurface: \"#f0f3f8\",\n bgSurface2: \"#e1e6ee\",\n border: \"#d3dae6\",\n textPrimary: \"#2e3440\",\n textSecondary: \"#454b56\",\n textMuted: \"#5e636e\",\n brand: \"#3b6ea5\",\n brandHover: \"#305a86\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0f8060\",\n moneyExpense: \"#b8431a\",\n moneyNet: \"#6b4fa0\",\n moneyNeutral: \"#5b6577\",\n chart: [\"#3a6ea5\", \"#56b6c2\", \"#2f7d4f\", \"#cdb52f\", \"#d05f7a\", \"#9a3f93\", \"#4d4bbf\", \"#dd6a2c\", \"#7a8aa8\"],\n heat: {\n neutral: \"#e1e6ee\",\n under: \"#0f8060\",\n over: \"#b8431a\",\n seqLow: \"#f0f3f7\",\n seqHigh: \"#a14708\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#242933\",\n bgSurface: \"#2e3440\",\n bgSurface2: \"#3b4252\",\n border: \"#434c5e\",\n textPrimary: \"#eceff4\",\n textSecondary: \"#d4d7de\",\n textMuted: \"#afb3bc\",\n brand: \"#88c0d0\",\n brandHover: \"#a3d4e0\",\n brandContrast: \"#1c2027\",\n moneyIncome: \"#4ecf9c\",\n moneyExpense: \"#e8a24f\",\n moneyNet: \"#caa0e0\",\n moneyNeutral: \"#9aa5b8\",\n chart: [\"#7fb4e6\", \"#5ec9d6\", \"#5cc483\", \"#dccb6b\", \"#e87e96\", \"#bd6fce\", \"#9389f2\", \"#ef8f4a\", \"#8693ad\"],\n heat: {\n neutral: \"#3b4252\",\n under: \"#4ecf9c\",\n over: \"#e8a24f\",\n seqLow: \"#33373f\",\n seqHigh: \"#e8a24f\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"indigo-noir\",\n name: \"Indigo Noir\",\n blurb: \"Crisp light · deep indigo\",\n light: {\n bgPage: \"#e8eaf2\",\n bgSurface: \"#f6f7fb\",\n bgSurface2: \"#eef1f8\",\n border: \"#dadfeb\",\n textPrimary: \"#1b1f2e\",\n textSecondary: \"#494d5a\",\n textMuted: \"#626572\",\n brand: \"#4338ca\",\n brandHover: \"#3730a3\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0c6478\",\n moneyExpense: \"#b45309\",\n moneyNet: \"#7c3aed\",\n moneyNeutral: \"#5b6478\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#eef1f7\",\n under: \"#0c6478\",\n over: \"#b45309\",\n seqLow: \"#fdeed4\",\n seqHigh: \"#b45309\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#0e0f1c\",\n bgSurface: \"#181a2c\",\n bgSurface2: \"#21243a\",\n border: \"#2e3252\",\n textPrimary: \"#e6e8f4\",\n textSecondary: \"#adafbe\",\n textMuted: \"#8d90a0\",\n brand: \"#8b93f8\",\n brandHover: \"#aab1fb\",\n brandContrast: \"#16182b\",\n moneyIncome: \"#22c3b6\",\n // Kept byte-identical to DEFAULT_PRESET.dark, which IS this preset (dev#434).\n moneyExpense: \"#e4b035\",\n moneyNet: \"#b39aef\",\n moneyNeutral: \"#9aa1bd\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#cc78be\"],\n heat: {\n neutral: \"#363a5a\",\n under: \"#22c3b6\",\n over: \"#fbbf24\",\n seqLow: \"#3a2c14\",\n seqHigh: \"#fbbf24\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"solar-dusk\",\n name: \"Solar Dusk\",\n blurb: \"Solarized warmth, easy on the eyes\",\n light: {\n bgPage: \"#ede3cf\",\n bgSurface: \"#f6ecd9\",\n bgSurface2: \"#f3ead6\",\n border: \"#e2d6bd\",\n textPrimary: \"#463f33\",\n textSecondary: \"#50493c\",\n textMuted: \"#696153\",\n brand: \"#2c6979\",\n brandHover: \"#1f515f\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0b6650\",\n moneyExpense: \"#905902\",\n moneyNet: \"#7a4fa3\",\n moneyNeutral: \"#6b6453\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#f1e7d2\",\n under: \"#0b6650\",\n over: \"#9c6418\",\n seqLow: \"#f6ecd0\",\n seqHigh: \"#9c6418\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#0e1a1f\",\n bgSurface: \"#15272e\",\n bgSurface2: \"#1d333c\",\n border: \"#2a4750\",\n textPrimary: \"#d6cdb8\",\n textSecondary: \"#c2bdab\",\n textMuted: \"#9c9d91\",\n brand: \"#5fb8cc\",\n brandHover: \"#82cfe0\",\n brandContrast: \"#0a161a\",\n moneyIncome: \"#22b884\",\n moneyExpense: \"#f0b94e\",\n moneyNet: \"#bfa0e0\",\n moneyNeutral: \"#9aa3a0\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#c873c0\"],\n heat: {\n neutral: \"#26424b\",\n under: \"#22b884\",\n over: \"#f0b94e\",\n seqLow: \"#1f3138\",\n seqHigh: \"#f0b94e\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"zinc-mono\",\n name: \"Zinc Mono\",\n blurb: \"True-neutral chrome · data pops\",\n light: {\n bgPage: \"#ececec\",\n bgSurface: \"#f7f7f7\",\n bgSurface2: \"#f4f4f5\",\n border: \"#e4e4e7\",\n textPrimary: \"#18181b\",\n textSecondary: \"#4e4e50\",\n textMuted: \"#676769\",\n brand: \"#1d4ed8\",\n brandHover: \"#1e40af\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0e7490\",\n moneyExpense: \"#cf4310\",\n moneyNet: \"#7c3aed\",\n moneyNeutral: \"#52525b\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#f4f4f5\",\n under: \"#0e7490\",\n over: \"#cf4310\",\n seqLow: \"#fdeede\",\n seqHigh: \"#cf4310\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#09090b\",\n bgSurface: \"#18181b\",\n bgSurface2: \"#27272a\",\n border: \"#3f3f46\",\n textPrimary: \"#f4f4f5\",\n textSecondary: \"#b2b2b3\",\n textMuted: \"#929294\",\n brand: \"#60a5fa\",\n brandHover: \"#93c5fd\",\n brandContrast: \"#0b1220\",\n moneyIncome: \"#22d3ee\",\n moneyExpense: \"#f97316\",\n moneyNet: \"#c4b5fd\",\n moneyNeutral: \"#a1a1aa\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#cc78be\"],\n heat: {\n neutral: \"#3f3f46\",\n under: \"#22d3ee\",\n over: \"#f97316\",\n seqLow: \"#3a2410\",\n seqHigh: \"#f97316\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n];\n\n// ── \"Imprint\" chart hues (feedback #119) ──────────────────────────────────────\n// anyplot's categorical palette (https://anyplot.ai/palette), by a colleague of\n// Marcel's. 8 hues + an amber semantic anchor, derived from Wong 2011 by a\n// Petroff-style max-min ΔE search in CAM02-UCS with Machado 2009 CVD simulation —\n// the same lineage as our default Paul Tol \"Muted\", one generation on, and tuned\n// for exactly the warm cream paper our light theme uses.\n//\n// This preset is DEFAULT_PRESET with ONLY the 9 categorical chart hues swapped;\n// surfaces, brand and the semantic money trio stay put, because those were their\n// own deliberate decisions (#307/#328) and are not what the palette is for. The\n// heatmap stops likewise stay teal/amber: they encode under/over, and imprint's\n// own diverging ramp is red↔blue, which would fight the money semantics.\n//\n// Measured on our surfaces (CIEDE2000, Machado severity-100, 8 like-for-like\n// slots on the cream light surface #f6ecd9) — neither palette dominates:\n// Paul Tol Muted min-contrast 1.38 · worst-CVD ΔE00 11.8 · normal ΔE00 16.1\n// imprint min-contrast 1.95 · worst-CVD ΔE00 7.2 · normal ΔE00 22.2\n// i.e. imprint is punchier and has no near-invisible pale slot (Tol's #88ccee\n// sits at 1.5:1 on cream), while Tol separates better under colour-vision\n// deficiency. So this ships as a CHOICE, not as a new default.\n//\n// The dark set keeps imprint's hues verbatim except matte-red and rose, whose\n// lightness is lifted (oklch L 0.50→0.55) because verbatim they land at 2.67:1\n// and 2.76:1 on our deep-indigo dark surface — under WCAG 2.1 SC 1.4.11's 3:1\n// non-text minimum. Lifted they clear it at 3.30:1 / 3.28:1. anyplot documents\n// the same shortfall on their own dark ground and answers it with a stroke halo;\n// lifting keeps the fix in the token layer instead of every chart component.\nconst IMPRINT_HUES_LIGHT = [\n \"#009E73\", // brand green — first series\n \"#C475FD\", // lavender — creative\n \"#4467A3\", // blue — cool / info\n \"#BD8233\", // ochre — earth / commodity\n \"#AE3030\", // matte red — bad / loss / error\n \"#2ABCCD\", // cyan — sky / tech-cool\n \"#954477\", // rose — wellness / health\n \"#99B314\", // lime — growth / nature\n \"#DDCC77\", // amber — anyplot's semantic anchor, used here as slot 9\n];\nconst IMPRINT_HUES_DARK = [\n \"#009E73\",\n \"#C475FD\",\n \"#4467A3\",\n \"#BD8233\",\n \"#BF413E\", // matte red, L-lifted for our dark surface\n \"#2ABCCD\",\n \"#A25083\", // rose, L-lifted for our dark surface\n \"#99B314\",\n \"#DDCC77\",\n];\n\nexport const IMPRINT_PRESET: PalettePreset = {\n id: \"imprint\",\n name: \"Imprint\",\n blurb: \"Colourblind-safe chart hues by anyplot.ai\",\n light: { ...DEFAULT_PRESET.light, chart: IMPRINT_HUES_LIGHT },\n dark: { ...DEFAULT_PRESET.dark, chart: IMPRINT_HUES_DARK },\n};\n\n// The full-theme switcher from #307 stayed retired (feedback #328) — the default\n// is still the single source of truth for surfaces, brand and money, and\n// ALTERNATIVE_PRESETS above remains a reference bank only. What IS selectable is\n// the categorical chart ramp (feedback #119), so these two presets are the live\n// set; presetById falls back to the default for any stale persisted id.\n/**\n * The categorical chart ramp, shared by every derived preset below.\n *\n * Paul Tol's \"Muted\" set, unchanged, and deliberately NOT re-derived per preset. A\n * categorical ramp solves a different problem from a UI palette — separability among\n * nine unknown siblings, rather than contrast against one known surface — and a set\n * optimised against real dichromat confusion lines beats one spaced evenly by formula.\n * `auditChartRamp` in palette-derive.ts measures it: 0.059 minimum separation in the\n * light variant, which is the number MIN_SEPARATION was calibrated under.\n *\n * It pays for that in contrast: four of the nine light hues sit below the 3:1 WCAG\n * 1.4.11 asks of a filled shape. That is not fixable by darkening them — see\n * SINGLE_LIGHTNESS_MAX_SERIES — so a chart with more than four series must carry a\n * second channel (a direct label, a pattern, a legend adjacent to the mark).\n */\nconst PAUL_TOL = {\n light: DEFAULT_PRESET.light.chart,\n dark: DEFAULT_PRESET.dark.chart,\n} as const;\n\n/**\n * Presets DERIVED rather than picked, by `derivePalette` from one brand colour each.\n *\n * Every text role here was solved to a contrast target against the worst of the three\n * surfaces it can land on, every semantic colour against both the page and its own badge\n * tint, and the whole set is re-checked by `derived-presets.test.ts` — which re-runs the\n * deriver and fails if these committed values drift from what it now produces.\n *\n * They are committed rather than derived at import because deriving eight token sets\n * costs ~34ms, and charging every consumer that at startup for palettes most of them\n * will never select is the wrong trade. The test is what keeps the two in agreement.\n */\nexport const DERIVED_PRESETS: PalettePreset[] = [\n {\n id: \"ink\",\n name: \"Ink\",\n blurb: \"Cool blue-grey ground · solved for AAA body text\",\n light: {\n bgPage: \"#dce2ef\",\n bgSurface: \"#e6edfa\",\n bgSurface2: \"#e1e8f5\",\n border: \"#b0bace\",\n textPrimary: \"#2e3139\",\n textSecondary: \"#454950\",\n textMuted: \"#5f646c\",\n brand: \"#2f5fd0\",\n brandHover: \"#1f4cbc\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00735f\",\n moneyExpense: \"#8e5900\",\n moneyNet: \"#7356a2\",\n moneyNeutral: \"#656970\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#e1e8f5\",\n under: \"#00735f\",\n over: \"#8e5900\",\n seqLow: \"#fee3c5\",\n seqHigh: \"#8e5900\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#090d15\",\n bgSurface: \"#131720\",\n bgSurface2: \"#1d212b\",\n border: \"#363e4e\",\n textPrimary: \"#c7ccd5\",\n textSecondary: \"#a7abb4\",\n textMuted: \"#858992\",\n brand: \"#3364d5\",\n brandHover: \"#4477ea\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00997f\",\n moneyExpense: \"#b77b29\",\n moneyNet: \"#9679c9\",\n moneyNeutral: \"#a1a5ac\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#1d212b\",\n under: \"#00997f\",\n over: \"#b77b29\",\n seqLow: \"#331f05\",\n seqHigh: \"#b77b29\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n {\n id: \"moss\",\n name: \"Moss\",\n blurb: \"Green ground · solved for AAA body text\",\n light: {\n bgPage: \"#d8e6dd\",\n bgSurface: \"#e3f1e8\",\n bgSurface2: \"#deece3\",\n border: \"#abbfb2\",\n textPrimary: \"#2c342e\",\n textSecondary: \"#434b45\",\n textMuted: \"#5d6660\",\n brand: \"#2f7a52\",\n brandHover: \"#196842\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00735f\",\n moneyExpense: \"#8f5a00\",\n moneyNet: \"#7457a3\",\n moneyNeutral: \"#646b66\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#deece3\",\n under: \"#00735f\",\n over: \"#8f5a00\",\n seqLow: \"#fee3c4\",\n seqHigh: \"#8f5a00\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#060f0a\",\n bgSurface: \"#101a14\",\n bgSurface2: \"#1a241e\",\n border: \"#314237\",\n textPrimary: \"#c5cfc9\",\n textSecondary: \"#a4aea8\",\n textMuted: \"#838c86\",\n brand: \"#2f7a52\",\n brandHover: \"#428c63\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#009a80\",\n moneyExpense: \"#b87b2a\",\n moneyNet: \"#9779c9\",\n moneyNeutral: \"#9fa7a2\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#1a241e\",\n under: \"#009a80\",\n over: \"#b87b2a\",\n seqLow: \"#331f05\",\n seqHigh: \"#b87b2a\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n {\n id: \"plum\",\n name: \"Plum\",\n blurb: \"Violet ground · solved for AAA body text\",\n light: {\n bgPage: \"#e7dfea\",\n bgSurface: \"#f2e9f5\",\n bgSurface2: \"#ece4ef\",\n border: \"#c2b6c6\",\n textPrimary: \"#342f36\",\n textSecondary: \"#4c474e\",\n textMuted: \"#676169\",\n brand: \"#7b3f8f\",\n brandHover: \"#692d7d\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00725e\",\n moneyExpense: \"#8e5900\",\n moneyNet: \"#7356a2\",\n moneyNeutral: \"#6c676e\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#ece4ef\",\n under: \"#00725e\",\n over: \"#8e5900\",\n seqLow: \"#fee3c5\",\n seqHigh: \"#8e5900\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#100b12\",\n bgSurface: \"#1b151d\",\n bgSurface2: \"#251f27\",\n border: \"#453a48\",\n textPrimary: \"#d0cad3\",\n textSecondary: \"#afa9b1\",\n textMuted: \"#8d878f\",\n brand: \"#8f52a4\",\n brandHover: \"#a264b7\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00997f\",\n moneyExpense: \"#b77b29\",\n moneyNet: \"#9679c8\",\n moneyNeutral: \"#a8a2aa\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#251f27\",\n under: \"#00997f\",\n over: \"#b77b29\",\n seqLow: \"#331f05\",\n seqHigh: \"#b77b29\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n {\n id: \"contrast\",\n name: \"High contrast\",\n blurb: \"Near-neutral ground · text solved to 13:1 / 8.5:1 / 5.5:1\",\n light: {\n bgPage: \"#e0e2e5\",\n bgSurface: \"#ebedef\",\n bgSurface2: \"#e6e8ea\",\n border: \"#b7babe\",\n textPrimary: \"#191d24\",\n textSecondary: \"#373c44\",\n textMuted: \"#535860\",\n brand: \"#1f2937\",\n brandHover: \"#111b28\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00735f\",\n moneyExpense: \"#8e5900\",\n moneyNet: \"#7456a2\",\n moneyNeutral: \"#656970\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#e6e8ea\",\n under: \"#00735f\",\n over: \"#8e5900\",\n seqLow: \"#fee3c5\",\n seqHigh: \"#8e5900\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#0c0d0f\",\n bgSurface: \"#161719\",\n bgSurface2: \"#202123\",\n border: \"#3c3e41\",\n textPrimary: \"#e1e7f0\",\n textSecondary: \"#b7bdc5\",\n textMuted: \"#9197a0\",\n brand: \"#546c8d\",\n brandHover: \"#657e9f\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00997f\",\n moneyExpense: \"#b77b29\",\n moneyNet: \"#9679c8\",\n moneyNeutral: \"#a0a5ac\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#202123\",\n under: \"#00997f\",\n over: \"#b77b29\",\n seqLow: \"#331f05\",\n seqHigh: \"#b77b29\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n];\n\nexport const PALETTES: PalettePreset[] = [DEFAULT_PRESET, IMPRINT_PRESET, ...DERIVED_PRESETS];\n\nexport function presetById(id: string): PalettePreset {\n return PALETTES.find((p) => p.id === id) ?? DEFAULT_PRESET;\n}\n\n/**\n * Write a token set onto an element's inline style as CSS custom properties.\n *\n * This is the only writer, and there is no un-writer: `presetById` falls back to\n * `DEFAULT_PRESET` and this then writes the full set, so returning to the default\n * APPLIES it rather than clearing anything. A `clearTokenSet` and the hand-kept\n * 24-name `TOKEN_VARS` list it looped over used to live here, both exported, both\n * called by nobody in either consumer — a second copy of these property names with\n * nothing comparing the two, so adding a token here and forgetting it there would\n * have been silent and only a dead function would have noticed. The list is gone;\n * the test beside this asserts instead that every field of a `TokenSet` reaches the\n * element, which is what that list was standing in for.\n *\n * **More contrast (0.32, docs/text-size-harmonization.md §5, §10.5).** With `contrast`\n * the text and border tokens are written one step stronger, and `--text-placeholder` —\n * otherwise derived in tokens.css — is written inline too; without it that inline\n * placeholder is removed again, so the stylesheet's formula applies. The step is made\n * HERE because these tokens are inline: no `[data-contrast=more]` rule could beat them.\n *\n * `contrast` defaults to the element's own `data-contrast=\"more\"`, which the contrast\n * layer (`applyContrast`, `useApplyContrast`) sets first. That is how the palette and the\n * contrast cooperate without knowing each other: a palette change re-applies through\n * here and keeps the contrast, and a contrast change re-applies the set remembered here.\n */\nexport function applyTokenSet(\n el: HTMLElement,\n t: TokenSet,\n contrast: boolean = el.getAttribute(\"data-contrast\") === \"more\",\n): void {\n rememberTokenSet(el, t);\n const step = contrast ? contrastStep(t) : null;\n el.style.setProperty(\"--bg-page\", t.bgPage);\n el.style.setProperty(\"--bg-surface\", t.bgSurface);\n el.style.setProperty(\"--bg-surface-2\", t.bgSurface2);\n el.style.setProperty(\"--border\", step?.border ?? t.border);\n el.style.setProperty(\"--text-primary\", t.textPrimary);\n el.style.setProperty(\"--text-secondary\", step?.textSecondary ?? t.textSecondary);\n el.style.setProperty(\"--text-muted\", step?.textMuted ?? t.textMuted);\n if (step) el.style.setProperty(\"--text-placeholder\", step.textPlaceholder);\n else el.style.removeProperty(\"--text-placeholder\");\n el.style.setProperty(\"--brand\", t.brand);\n el.style.setProperty(\"--brand-hover\", t.brandHover);\n el.style.setProperty(\"--brand-contrast\", t.brandContrast);\n el.style.setProperty(\"--money-income\", t.moneyIncome);\n el.style.setProperty(\"--money-expense\", t.moneyExpense);\n el.style.setProperty(\"--money-net\", t.moneyNet);\n el.style.setProperty(\"--money-neutral\", t.moneyNeutral);\n t.chart.forEach((c, i) => el.style.setProperty(`--chart-${i + 1}`, c));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/theme/palette-presets.ts"],"sourcesContent":["// Appearance presets — the single source of truth for the \"palette switcher\"\n// (feedback #307). Each preset is a full set of design-token values for BOTH\n// light and dark, covering the page/surface backgrounds, the brand accent, the\n// semantic money trio and the 9-colour categorical chart palette, plus the\n// heatmap stops. A preset is applied at runtime by writing these as CSS custom\n// properties onto <html> (see palette-store.ts), which instantly re-skins every\n// token-driven surface and chart. The two heatmaps and the treemap interpolate\n// concrete hex in JS, so they read the same values from here via the hooks in\n// palette-store.\n//\n// The alternative presets below were designed + accessibility-vetted as a set\n// (each is CVD-safe across protan/deutan/tritan, money is never red/green, and\n// text/background contrast clears WCAG AA on both themes). To promote one to the\n// app-wide default, copy its values into the :root/.dark blocks in tokens.css\n// (the default preset here mirrors those exactly).\n\nimport type { HeatStops } from \"./chart-palette\";\nimport { contrastStep, rememberTokenSet } from \"./contrast-tokens\";\n\nexport interface TokenSet {\n // Backgrounds & chrome\n bgPage: string;\n bgSurface: string;\n bgSurface2: string;\n border: string;\n textPrimary: string;\n // The two quieter text roles. Derived per preset by blending `textPrimary` toward\n // that preset's own surfaces until the contrast against the WORST of bgPage /\n // bgSurface / bgSurface2 hits 7:1 and 4.8:1 — so both clear WCAG AA for normal text\n // (4.5:1) on every surface they can land on, and both inherit the preset's warmth\n // instead of importing a cool grey from Tailwind's palette.\n textSecondary: string;\n textMuted: string;\n // Interactive brand accent\n brand: string;\n brandHover: string;\n brandContrast: string;\n // Semantic money (never red/green — CVD-safe, paired with +/- cues elsewhere)\n moneyIncome: string;\n moneyExpense: string;\n moneyNet: string;\n moneyNeutral: string;\n // Categorical chart series (Paul Tol \"Muted\" by default — CVD-safe), exactly 9\n chart: string[];\n // Heatmap interpolation stops (variance diverging + spending sequential)\n heat: HeatStops;\n}\n\nexport interface PalettePreset {\n id: string;\n name: string;\n blurb: string;\n light: TokenSet;\n dark: TokenSet;\n}\n\n// The shipped default (feedback #328): SOLAR-DUSK warm light + INDIGO-NOIR deep\n// dark surfaces — the two curated presets the user picked per mode — but with a\n// SINGLE unified brand accent (indigo) across both modes rather than each\n// preset's own accent (the user asked for the brand accent to stay the same).\n// So the light block is Solar Dusk with its teal brand overridden to indigo, and\n// the dark block is Indigo Noir (already indigo). The money trio\n// (income/expense/net) drives the chart bars and stays palette-driven so those\n// semantic colours adapt to each mode's contrast (best practice — feedback #328).\nexport const DEFAULT_PRESET: PalettePreset = {\n id: \"default\",\n name: \"Default\",\n blurb: \"Solar-dusk light · indigo-noir dark\",\n light: {\n // Solar Dusk (light) surfaces, with the brand unified to indigo (see above).\n bgPage: \"#ede3cf\",\n bgSurface: \"#f6ecd9\",\n // The well is the page in light (0.33, k26): #f3ead6 stood 1.02:1 from the card it\n // is inset in. Every light preset does the same; see SURFACE_L in palette-derive.ts.\n bgSurface2: \"#ede3cf\",\n border: \"#e2d6bd\",\n textPrimary: \"#463f33\",\n textSecondary: \"#50493c\",\n textMuted: \"#696153\",\n brand: \"#4f46e5\",\n brandHover: \"#4338ca\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0b6650\",\n moneyExpense: \"#905902\",\n moneyNet: \"#7a4fa3\",\n moneyNeutral: \"#6b6453\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#f1e7d2\",\n under: \"#0b6650\",\n over: \"#9c6418\",\n seqLow: \"#f6ecd0\",\n seqHigh: \"#9c6418\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n // Indigo Noir (dark)\n bgPage: \"#0e0f1c\",\n bgSurface: \"#181a2c\",\n bgSurface2: \"#21243a\",\n border: \"#2e3252\",\n textPrimary: \"#e6e8f4\",\n textSecondary: \"#adafbe\",\n textMuted: \"#8d90a0\",\n brand: \"#8b93f8\",\n brandHover: \"#aab1fb\",\n brandContrast: \"#16182b\",\n moneyIncome: \"#22c3b6\",\n // Amber-400 (#fbbf24) sat at OKLab L 0.84 while every other dark token lives\n // at 0.68–0.74, so outflows outshouted the inflows they are paired with. Same\n // hue and chroma, pulled to L 0.785 (dev#434). The heat stops below keep the\n // brighter amber on purpose: a gradient endpoint is meant to be the loudest\n // thing in its scale, and it is a fill, not a figure.\n moneyExpense: \"#e4b035\",\n moneyNet: \"#b39aef\",\n moneyNeutral: \"#9aa1bd\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#cc78be\"],\n heat: {\n neutral: \"#363a5a\",\n under: \"#22c3b6\",\n over: \"#fbbf24\",\n seqLow: \"#3a2c14\",\n seqHigh: \"#fbbf24\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n};\n\n// Vetted alternatives to preview (CVD-safe, WCAG-AA on both themes).\nexport const ALTERNATIVE_PRESETS: PalettePreset[] = [\n {\n id: \"warm-paper\",\n name: \"Warm Paper\",\n blurb: \"Cream paper · warm charcoal\",\n light: {\n bgPage: \"#ece5d6\",\n bgSurface: \"#f5efe3\",\n bgSurface2: \"#ece5d6\",\n border: \"#e2dcce\",\n textPrimary: \"#2b2622\",\n textSecondary: \"#4f4a44\",\n textMuted: \"#68625b\",\n brand: \"#9a5b34\",\n brandHover: \"#824827\",\n brandContrast: \"#fdf8f2\",\n moneyIncome: \"#0e7490\",\n moneyExpense: \"#8f3f0a\",\n moneyNet: \"#7c4dab\",\n moneyNeutral: \"#6b6258\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#efe9dc\",\n under: \"#0e7490\",\n over: \"#b45309\",\n seqLow: \"#fbeed2\",\n seqHigh: \"#b45309\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#211e1a\",\n bgSurface: \"#2a2622\",\n bgSurface2: \"#322d27\",\n border: \"#3d372f\",\n textPrimary: \"#e8e2d6\",\n textSecondary: \"#bfb9af\",\n textMuted: \"#9e998f\",\n brand: \"#e0986a\",\n brandHover: \"#ecae85\",\n brandContrast: \"#2a1c10\",\n moneyIncome: \"#4dd4c4\",\n moneyExpense: \"#fbbf3a\",\n moneyNet: \"#bfa0e0\",\n moneyNeutral: \"#a39a8c\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#cf6488\", \"#cc78be\"],\n heat: {\n neutral: \"#3a352e\",\n under: \"#4dd4c4\",\n over: \"#fbbf3a\",\n seqLow: \"#2c2014\",\n seqHigh: \"#fbbf3a\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"nord-frost\",\n name: \"Nord Frost\",\n blurb: \"Cool snow · polar night\",\n light: {\n bgPage: \"#e2e7ef\",\n bgSurface: \"#f0f3f8\",\n bgSurface2: \"#e2e7ef\",\n border: \"#d3dae6\",\n textPrimary: \"#2e3440\",\n textSecondary: \"#454b56\",\n textMuted: \"#5e636e\",\n brand: \"#3b6ea5\",\n brandHover: \"#305a86\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0f8060\",\n moneyExpense: \"#b8431a\",\n moneyNet: \"#6b4fa0\",\n moneyNeutral: \"#5b6577\",\n chart: [\"#3a6ea5\", \"#56b6c2\", \"#2f7d4f\", \"#cdb52f\", \"#d05f7a\", \"#9a3f93\", \"#4d4bbf\", \"#dd6a2c\", \"#7a8aa8\"],\n heat: {\n neutral: \"#e1e6ee\",\n under: \"#0f8060\",\n over: \"#b8431a\",\n seqLow: \"#f0f3f7\",\n seqHigh: \"#a14708\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#242933\",\n bgSurface: \"#2e3440\",\n bgSurface2: \"#3b4252\",\n border: \"#434c5e\",\n textPrimary: \"#eceff4\",\n textSecondary: \"#d4d7de\",\n textMuted: \"#afb3bc\",\n brand: \"#88c0d0\",\n brandHover: \"#a3d4e0\",\n brandContrast: \"#1c2027\",\n moneyIncome: \"#4ecf9c\",\n moneyExpense: \"#e8a24f\",\n moneyNet: \"#caa0e0\",\n moneyNeutral: \"#9aa5b8\",\n chart: [\"#7fb4e6\", \"#5ec9d6\", \"#5cc483\", \"#dccb6b\", \"#e87e96\", \"#bd6fce\", \"#9389f2\", \"#ef8f4a\", \"#8693ad\"],\n heat: {\n neutral: \"#3b4252\",\n under: \"#4ecf9c\",\n over: \"#e8a24f\",\n seqLow: \"#33373f\",\n seqHigh: \"#e8a24f\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"indigo-noir\",\n name: \"Indigo Noir\",\n blurb: \"Crisp light · deep indigo\",\n light: {\n bgPage: \"#e8eaf2\",\n bgSurface: \"#f6f7fb\",\n bgSurface2: \"#e8eaf2\",\n border: \"#dadfeb\",\n textPrimary: \"#1b1f2e\",\n textSecondary: \"#494d5a\",\n textMuted: \"#626572\",\n brand: \"#4338ca\",\n brandHover: \"#3730a3\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0c6478\",\n moneyExpense: \"#b45309\",\n moneyNet: \"#7c3aed\",\n moneyNeutral: \"#5b6478\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#eef1f7\",\n under: \"#0c6478\",\n over: \"#b45309\",\n seqLow: \"#fdeed4\",\n seqHigh: \"#b45309\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#0e0f1c\",\n bgSurface: \"#181a2c\",\n bgSurface2: \"#21243a\",\n border: \"#2e3252\",\n textPrimary: \"#e6e8f4\",\n textSecondary: \"#adafbe\",\n textMuted: \"#8d90a0\",\n brand: \"#8b93f8\",\n brandHover: \"#aab1fb\",\n brandContrast: \"#16182b\",\n moneyIncome: \"#22c3b6\",\n // Kept byte-identical to DEFAULT_PRESET.dark, which IS this preset (dev#434).\n moneyExpense: \"#e4b035\",\n moneyNet: \"#b39aef\",\n moneyNeutral: \"#9aa1bd\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#cc78be\"],\n heat: {\n neutral: \"#363a5a\",\n under: \"#22c3b6\",\n over: \"#fbbf24\",\n seqLow: \"#3a2c14\",\n seqHigh: \"#fbbf24\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"solar-dusk\",\n name: \"Solar Dusk\",\n blurb: \"Solarized warmth, easy on the eyes\",\n light: {\n bgPage: \"#ede3cf\",\n bgSurface: \"#f6ecd9\",\n bgSurface2: \"#ede3cf\",\n border: \"#e2d6bd\",\n textPrimary: \"#463f33\",\n textSecondary: \"#50493c\",\n textMuted: \"#696153\",\n brand: \"#2c6979\",\n brandHover: \"#1f515f\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0b6650\",\n moneyExpense: \"#905902\",\n moneyNet: \"#7a4fa3\",\n moneyNeutral: \"#6b6453\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#f1e7d2\",\n under: \"#0b6650\",\n over: \"#9c6418\",\n seqLow: \"#f6ecd0\",\n seqHigh: \"#9c6418\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#0e1a1f\",\n bgSurface: \"#15272e\",\n bgSurface2: \"#1d333c\",\n border: \"#2a4750\",\n textPrimary: \"#d6cdb8\",\n textSecondary: \"#c2bdab\",\n textMuted: \"#9c9d91\",\n brand: \"#5fb8cc\",\n brandHover: \"#82cfe0\",\n brandContrast: \"#0a161a\",\n moneyIncome: \"#22b884\",\n moneyExpense: \"#f0b94e\",\n moneyNet: \"#bfa0e0\",\n moneyNeutral: \"#9aa3a0\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#c873c0\"],\n heat: {\n neutral: \"#26424b\",\n under: \"#22b884\",\n over: \"#f0b94e\",\n seqLow: \"#1f3138\",\n seqHigh: \"#f0b94e\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n {\n id: \"zinc-mono\",\n name: \"Zinc Mono\",\n blurb: \"True-neutral chrome · data pops\",\n light: {\n bgPage: \"#ececec\",\n bgSurface: \"#f7f7f7\",\n bgSurface2: \"#ececec\",\n border: \"#e4e4e7\",\n textPrimary: \"#18181b\",\n textSecondary: \"#4e4e50\",\n textMuted: \"#676769\",\n brand: \"#1d4ed8\",\n brandHover: \"#1e40af\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#0e7490\",\n moneyExpense: \"#cf4310\",\n moneyNet: \"#7c3aed\",\n moneyNeutral: \"#52525b\",\n chart: [\"#332288\", \"#88ccee\", \"#44aa99\", \"#117733\", \"#999933\", \"#ddcc77\", \"#cc6677\", \"#882255\", \"#aa4499\"],\n heat: {\n neutral: \"#f4f4f5\",\n under: \"#0e7490\",\n over: \"#cf4310\",\n seqLow: \"#fdeede\",\n seqHigh: \"#cf4310\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n },\n dark: {\n bgPage: \"#09090b\",\n bgSurface: \"#18181b\",\n bgSurface2: \"#27272a\",\n border: \"#3f3f46\",\n textPrimary: \"#f4f4f5\",\n textSecondary: \"#b2b2b3\",\n textMuted: \"#929294\",\n brand: \"#60a5fa\",\n brandHover: \"#93c5fd\",\n brandContrast: \"#0b1220\",\n moneyIncome: \"#22d3ee\",\n moneyExpense: \"#f97316\",\n moneyNet: \"#c4b5fd\",\n moneyNeutral: \"#a1a1aa\",\n chart: [\"#7e72d6\", \"#9fd8f2\", \"#5fc4b0\", \"#3fa45f\", \"#bfbf5e\", \"#e8dda0\", \"#e08c9a\", \"#c25a78\", \"#cc78be\"],\n heat: {\n neutral: \"#3f3f46\",\n under: \"#22d3ee\",\n over: \"#f97316\",\n seqLow: \"#3a2410\",\n seqHigh: \"#f97316\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n },\n },\n];\n\n// ── \"Imprint\" chart hues (feedback #119) ──────────────────────────────────────\n// anyplot's categorical palette (https://anyplot.ai/palette), by a colleague of\n// Marcel's. 8 hues + an amber semantic anchor, derived from Wong 2011 by a\n// Petroff-style max-min ΔE search in CAM02-UCS with Machado 2009 CVD simulation —\n// the same lineage as our default Paul Tol \"Muted\", one generation on, and tuned\n// for exactly the warm cream paper our light theme uses.\n//\n// This preset is DEFAULT_PRESET with ONLY the 9 categorical chart hues swapped;\n// surfaces, brand and the semantic money trio stay put, because those were their\n// own deliberate decisions (#307/#328) and are not what the palette is for. The\n// heatmap stops likewise stay teal/amber: they encode under/over, and imprint's\n// own diverging ramp is red↔blue, which would fight the money semantics.\n//\n// Measured on our surfaces (CIEDE2000, Machado severity-100, 8 like-for-like\n// slots on the cream light surface #f6ecd9) — neither palette dominates:\n// Paul Tol Muted min-contrast 1.38 · worst-CVD ΔE00 11.8 · normal ΔE00 16.1\n// imprint min-contrast 1.95 · worst-CVD ΔE00 7.2 · normal ΔE00 22.2\n// i.e. imprint is punchier and has no near-invisible pale slot (Tol's #88ccee\n// sits at 1.5:1 on cream), while Tol separates better under colour-vision\n// deficiency. So this ships as a CHOICE, not as a new default.\n//\n// The dark set keeps imprint's hues verbatim except matte-red and rose, whose\n// lightness is lifted (oklch L 0.50→0.55) because verbatim they land at 2.67:1\n// and 2.76:1 on our deep-indigo dark surface — under WCAG 2.1 SC 1.4.11's 3:1\n// non-text minimum. Lifted they clear it at 3.30:1 / 3.28:1. anyplot documents\n// the same shortfall on their own dark ground and answers it with a stroke halo;\n// lifting keeps the fix in the token layer instead of every chart component.\nconst IMPRINT_HUES_LIGHT = [\n \"#009E73\", // brand green — first series\n \"#C475FD\", // lavender — creative\n \"#4467A3\", // blue — cool / info\n \"#BD8233\", // ochre — earth / commodity\n \"#AE3030\", // matte red — bad / loss / error\n \"#2ABCCD\", // cyan — sky / tech-cool\n \"#954477\", // rose — wellness / health\n \"#99B314\", // lime — growth / nature\n \"#DDCC77\", // amber — anyplot's semantic anchor, used here as slot 9\n];\nconst IMPRINT_HUES_DARK = [\n \"#009E73\",\n \"#C475FD\",\n \"#4467A3\",\n \"#BD8233\",\n \"#BF413E\", // matte red, L-lifted for our dark surface\n \"#2ABCCD\",\n \"#A25083\", // rose, L-lifted for our dark surface\n \"#99B314\",\n \"#DDCC77\",\n];\n\nexport const IMPRINT_PRESET: PalettePreset = {\n id: \"imprint\",\n name: \"Imprint\",\n blurb: \"Colourblind-safe chart hues by anyplot.ai\",\n light: { ...DEFAULT_PRESET.light, chart: IMPRINT_HUES_LIGHT },\n dark: { ...DEFAULT_PRESET.dark, chart: IMPRINT_HUES_DARK },\n};\n\n// The full-theme switcher from #307 stayed retired (feedback #328) — the default\n// is still the single source of truth for surfaces, brand and money, and\n// ALTERNATIVE_PRESETS above remains a reference bank only. What IS selectable is\n// the categorical chart ramp (feedback #119), so these two presets are the live\n// set; presetById falls back to the default for any stale persisted id.\n/**\n * The categorical chart ramp, shared by every derived preset below.\n *\n * Paul Tol's \"Muted\" set, unchanged, and deliberately NOT re-derived per preset. A\n * categorical ramp solves a different problem from a UI palette — separability among\n * nine unknown siblings, rather than contrast against one known surface — and a set\n * optimised against real dichromat confusion lines beats one spaced evenly by formula.\n * `auditChartRamp` in palette-derive.ts measures it: 0.059 minimum separation in the\n * light variant, which is the number MIN_SEPARATION was calibrated under.\n *\n * It pays for that in contrast: four of the nine light hues sit below the 3:1 WCAG\n * 1.4.11 asks of a filled shape. That is not fixable by darkening them — see\n * SINGLE_LIGHTNESS_MAX_SERIES — so a chart with more than four series must carry a\n * second channel (a direct label, a pattern, a legend adjacent to the mark).\n */\nconst PAUL_TOL = {\n light: DEFAULT_PRESET.light.chart,\n dark: DEFAULT_PRESET.dark.chart,\n} as const;\n\n/**\n * Presets DERIVED rather than picked, by `derivePalette` from one brand colour each.\n *\n * Every text role here was solved to a contrast target against the worst of the three\n * surfaces it can land on, every semantic colour against both the page and its own badge\n * tint, and the whole set is re-checked by `derived-presets.test.ts` — which re-runs the\n * deriver and fails if these committed values drift from what it now produces.\n *\n * They are committed rather than derived at import because deriving eight token sets\n * costs ~34ms, and charging every consumer that at startup for palettes most of them\n * will never select is the wrong trade. The test is what keeps the two in agreement.\n */\nexport const DERIVED_PRESETS: PalettePreset[] = [\n {\n id: \"ink\",\n name: \"Ink\",\n blurb: \"Cool blue-grey ground · solved for AAA body text\",\n light: {\n bgPage: \"#dce2ef\",\n bgSurface: \"#e6edfa\",\n bgSurface2: \"#dce2ef\",\n border: \"#b0bace\",\n textPrimary: \"#2e3139\",\n textSecondary: \"#454950\",\n textMuted: \"#5f646c\",\n brand: \"#2f5fd0\",\n brandHover: \"#1f4cbc\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00725e\",\n moneyExpense: \"#8e5900\",\n moneyNet: \"#7356a2\",\n moneyNeutral: \"#61656c\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#dce2ef\",\n under: \"#00725e\",\n over: \"#8e5900\",\n seqLow: \"#fee3c5\",\n seqHigh: \"#8e5900\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#090d15\",\n bgSurface: \"#131720\",\n bgSurface2: \"#1d212b\",\n border: \"#363e4e\",\n textPrimary: \"#c7ccd5\",\n textSecondary: \"#a7abb4\",\n textMuted: \"#858992\",\n brand: \"#3466d7\",\n brandHover: \"#2353c3\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00997f\",\n moneyExpense: \"#b77b29\",\n moneyNet: \"#9679c9\",\n moneyNeutral: \"#a1a5ac\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#1d212b\",\n under: \"#00997f\",\n over: \"#b77b29\",\n seqLow: \"#331f05\",\n seqHigh: \"#b77b29\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n {\n id: \"moss\",\n name: \"Moss\",\n blurb: \"Green ground · solved for AAA body text\",\n light: {\n bgPage: \"#d8e6dd\",\n bgSurface: \"#e3f1e8\",\n bgSurface2: \"#d8e6dd\",\n border: \"#abbfb2\",\n textPrimary: \"#2c342e\",\n textSecondary: \"#434b45\",\n textMuted: \"#5d6660\",\n brand: \"#2f7a52\",\n brandHover: \"#196842\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00735f\",\n moneyExpense: \"#8e5900\",\n moneyNet: \"#7356a2\",\n moneyNeutral: \"#5f6762\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#d8e6dd\",\n under: \"#00735f\",\n over: \"#8e5900\",\n seqLow: \"#fee3c5\",\n seqHigh: \"#8e5900\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#060f0a\",\n bgSurface: \"#101a14\",\n bgSurface2: \"#1a241e\",\n border: \"#314237\",\n textPrimary: \"#c5cfc9\",\n textSecondary: \"#a4aea8\",\n textMuted: \"#838c86\",\n brand: \"#317b53\",\n brandHover: \"#1c6943\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#009a80\",\n moneyExpense: \"#b87b2a\",\n moneyNet: \"#9779c9\",\n moneyNeutral: \"#9fa7a2\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#1a241e\",\n under: \"#009a80\",\n over: \"#b87b2a\",\n seqLow: \"#331f05\",\n seqHigh: \"#b87b2a\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n {\n id: \"plum\",\n name: \"Plum\",\n blurb: \"Violet ground · solved for AAA body text\",\n light: {\n bgPage: \"#e7dfea\",\n bgSurface: \"#f2e9f5\",\n bgSurface2: \"#e7dfea\",\n border: \"#c2b6c6\",\n textPrimary: \"#342f36\",\n textSecondary: \"#4c474e\",\n textMuted: \"#676169\",\n brand: \"#7b3f8f\",\n brandHover: \"#692d7d\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00725e\",\n moneyExpense: \"#8d5900\",\n moneyNet: \"#7355a2\",\n moneyNeutral: \"#676369\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#e7dfea\",\n under: \"#00725e\",\n over: \"#8d5900\",\n seqLow: \"#fee3c4\",\n seqHigh: \"#8d5900\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#100b12\",\n bgSurface: \"#1b151d\",\n bgSurface2: \"#251f27\",\n border: \"#453a48\",\n textPrimary: \"#d0cad3\",\n textSecondary: \"#afa9b1\",\n textMuted: \"#8d878f\",\n brand: \"#9153a6\",\n brandHover: \"#7f4193\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00997f\",\n moneyExpense: \"#b77b29\",\n moneyNet: \"#9679c8\",\n moneyNeutral: \"#a8a2aa\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#251f27\",\n under: \"#00997f\",\n over: \"#b77b29\",\n seqLow: \"#331f05\",\n seqHigh: \"#b77b29\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n {\n id: \"contrast\",\n name: \"High contrast\",\n blurb: \"Near-neutral ground · text solved to 13:1 / 8.5:1 / 5.5:1\",\n light: {\n bgPage: \"#e0e2e5\",\n bgSurface: \"#ebedef\",\n bgSurface2: \"#e0e2e5\",\n border: \"#b7babe\",\n textPrimary: \"#191d24\",\n textSecondary: \"#373c44\",\n textMuted: \"#535860\",\n brand: \"#1f2937\",\n brandHover: \"#111b28\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00715d\",\n moneyExpense: \"#8d5800\",\n moneyNet: \"#7255a1\",\n moneyNeutral: \"#5f646b\",\n chart: PAUL_TOL.light,\n heat: {\n neutral: \"#e0e2e5\",\n under: \"#00715d\",\n over: \"#8d5800\",\n seqLow: \"#fee3c5\",\n seqHigh: \"#8d5800\",\n empty: \"rgba(120,110,95,0.14)\",\n },\n },\n dark: {\n bgPage: \"#0c0d0f\",\n bgSurface: \"#161719\",\n bgSurface2: \"#202123\",\n border: \"#3c3e41\",\n textPrimary: \"#e1e7f0\",\n textSecondary: \"#b7bdc5\",\n textMuted: \"#9197a0\",\n brand: \"#546c8d\",\n brandHover: \"#445b7b\",\n brandContrast: \"#ffffff\",\n moneyIncome: \"#00997f\",\n moneyExpense: \"#b77b29\",\n moneyNet: \"#9679c8\",\n moneyNeutral: \"#a0a5ac\",\n chart: PAUL_TOL.dark,\n heat: {\n neutral: \"#202123\",\n under: \"#00997f\",\n over: \"#b77b29\",\n seqLow: \"#331f05\",\n seqHigh: \"#b77b29\",\n empty: \"rgba(160,170,190,0.10)\",\n },\n },\n },\n];\n\nexport const PALETTES: PalettePreset[] = [DEFAULT_PRESET, IMPRINT_PRESET, ...DERIVED_PRESETS];\n\nexport function presetById(id: string): PalettePreset {\n return PALETTES.find((p) => p.id === id) ?? DEFAULT_PRESET;\n}\n\n/**\n * Write a token set onto an element's inline style as CSS custom properties.\n *\n * This is the only writer, and there is no un-writer: `presetById` falls back to\n * `DEFAULT_PRESET` and this then writes the full set, so returning to the default\n * APPLIES it rather than clearing anything. A `clearTokenSet` and the hand-kept\n * 24-name `TOKEN_VARS` list it looped over used to live here, both exported, both\n * called by nobody in either consumer — a second copy of these property names with\n * nothing comparing the two, so adding a token here and forgetting it there would\n * have been silent and only a dead function would have noticed. The list is gone;\n * the test beside this asserts instead that every field of a `TokenSet` reaches the\n * element, which is what that list was standing in for.\n *\n * **More contrast (0.32, docs/text-size-harmonization.md §5, §10.5).** With `contrast`\n * the text and border tokens are written one step stronger, and `--text-placeholder` —\n * otherwise derived in tokens.css — is written inline too; without it that inline\n * placeholder is removed again, so the stylesheet's formula applies. The step is made\n * HERE because these tokens are inline: no `[data-contrast=more]` rule could beat them.\n *\n * `contrast` defaults to the element's own `data-contrast=\"more\"`, which the contrast\n * layer (`applyContrast`, `useApplyContrast`) sets first. That is how the palette and the\n * contrast cooperate without knowing each other: a palette change re-applies through\n * here and keeps the contrast, and a contrast change re-applies the set remembered here.\n */\nexport function applyTokenSet(\n el: HTMLElement,\n t: TokenSet,\n contrast: boolean = el.getAttribute(\"data-contrast\") === \"more\",\n): void {\n rememberTokenSet(el, t);\n const step = contrast ? contrastStep(t) : null;\n el.style.setProperty(\"--bg-page\", t.bgPage);\n el.style.setProperty(\"--bg-surface\", t.bgSurface);\n el.style.setProperty(\"--bg-surface-2\", t.bgSurface2);\n el.style.setProperty(\"--border\", step?.border ?? t.border);\n el.style.setProperty(\"--text-primary\", t.textPrimary);\n el.style.setProperty(\"--text-secondary\", step?.textSecondary ?? t.textSecondary);\n el.style.setProperty(\"--text-muted\", step?.textMuted ?? t.textMuted);\n if (step) el.style.setProperty(\"--text-placeholder\", step.textPlaceholder);\n else el.style.removeProperty(\"--text-placeholder\");\n el.style.setProperty(\"--brand\", t.brand);\n el.style.setProperty(\"--brand-hover\", t.brandHover);\n el.style.setProperty(\"--brand-contrast\", t.brandContrast);\n el.style.setProperty(\"--money-income\", t.moneyIncome);\n el.style.setProperty(\"--money-expense\", t.moneyExpense);\n el.style.setProperty(\"--money-net\", t.moneyNet);\n el.style.setProperty(\"--money-neutral\", t.moneyNeutral);\n t.chart.forEach((c, i) => el.style.setProperty(`--chart-${i + 1}`, c));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import { bO as ButtonVariant } from '../delete-account-setting-
|
|
3
|
+
import { bO as ButtonVariant } from '../delete-account-setting-DiPX4Vue.js';
|
|
4
4
|
import { UseWizardReturn, WizardLabels } from './types.js';
|
|
5
|
+
import { CommitScope } from '../components/write-lock.js';
|
|
5
6
|
import '../components/tooltip.js';
|
|
6
7
|
import '../hooks/use-anchored-rect.js';
|
|
7
8
|
import '../lib/clipping.js';
|
|
8
|
-
import '../data-table-labels-
|
|
9
|
+
import '../data-table-labels-BJ5JE_X2.js';
|
|
9
10
|
import '../components/data-table-sort.js';
|
|
10
11
|
import '../components/swipeable-row.js';
|
|
12
|
+
import '../components/status-dot.js';
|
|
13
|
+
import '../components/progress-bar.js';
|
|
14
|
+
import '../components/signed-amount.js';
|
|
15
|
+
import '../components/stat-tile.js';
|
|
16
|
+
import '../components/chip.js';
|
|
17
|
+
import 'lucide-react';
|
|
18
|
+
import '../components/field-parts.js';
|
|
11
19
|
import '../components/mini-calendar.js';
|
|
12
20
|
import '../components/calendar-heatmap.js';
|
|
13
21
|
import '../components/popover.js';
|
|
14
22
|
import '../hooks/use-breakpoint.js';
|
|
15
|
-
import '../components/chip.js';
|
|
16
|
-
import 'lucide-react';
|
|
17
|
-
import '../components/field-parts.js';
|
|
18
23
|
import '../components/field-sync.js';
|
|
19
24
|
import '../tour/tour.js';
|
|
20
25
|
import '../search/command-palette.js';
|
|
@@ -25,7 +30,6 @@ import '../components/page-contents.js';
|
|
|
25
30
|
import '../components/series-chart-labels.js';
|
|
26
31
|
import '../components/pie-chart-labels.js';
|
|
27
32
|
import '../components/sparkline.js';
|
|
28
|
-
import '../components/stat-tile.js';
|
|
29
33
|
import '../components/signature-pad.js';
|
|
30
34
|
import '../components/password-strength.js';
|
|
31
35
|
import '../components/swatch-picker.js';
|
|
@@ -37,9 +41,6 @@ import '../hooks/use-search-param-state.js';
|
|
|
37
41
|
import '../components/measured-grid.js';
|
|
38
42
|
import '../components/user-avatar.js';
|
|
39
43
|
import '../lib/person-name.js';
|
|
40
|
-
import '../components/status-dot.js';
|
|
41
|
-
import '../components/progress-bar.js';
|
|
42
|
-
import '../components/signed-amount.js';
|
|
43
44
|
import '../shell/topbar-action-menu.js';
|
|
44
45
|
import '../components/menu-item.js';
|
|
45
46
|
import '../components/floating-panel.js';
|
|
@@ -55,7 +56,6 @@ import '../components/error-boundary.js';
|
|
|
55
56
|
import '../components/authed-image.js';
|
|
56
57
|
import '../components/image-grid.js';
|
|
57
58
|
import '../components/lightbox.js';
|
|
58
|
-
import '../components/write-lock.js';
|
|
59
59
|
import '../auth/sign-in-form.js';
|
|
60
60
|
import '../landing/access.js';
|
|
61
61
|
import '../landing/landing-labels.js';
|
|
@@ -150,7 +150,7 @@ declare function StepperNav<TData extends Record<string, unknown>>({ wizard, chi
|
|
|
150
150
|
* with five buttons would not say which one saves — and would read like `useWizard`'s
|
|
151
151
|
* `commits`, which is a step's policy, not a lock.
|
|
152
152
|
*/
|
|
153
|
-
finishCommit?:
|
|
153
|
+
finishCommit?: CommitScope;
|
|
154
154
|
/**
|
|
155
155
|
* Why Finish is not available — {@link Button}'s `disabledReason` on the Finish button:
|
|
156
156
|
* `aria-disabled` and focusable, the reason in the kit Tooltip and on the button's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/stepper-nav.tsx"],"sourcesContent":["import { useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Button, Spinner } from \"../components/ui\";\nimport type { ButtonVariant } from \"../components/ui\";\nimport { Modal } from \"../components/modal\";\nimport { alertFrameClass } from \"../components/alert-banner\";\nimport { WizardContextProvider } from \"./wizard-context\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { UseWizardReturn, WizardLabels } from \"./types\";\n\n/**\n * The chrome around a wizard: the step indicator, the step content, and the\n * Cancel / Back / Skip / Next / Finish bar — plus the cancel-confirmation\n * dialog, because \"are you sure you want to lose this\" is part of the wizard's\n * contract rather than something each app should re-decide.\n *\n * It is a component and not a set of parts for one reason: which button shows,\n * and whether it is enabled, is a function of `wizard` state that was getting\n * re-derived (and re-derived slightly differently) at every call site. Finish is\n * gated on `canFinish`, Next and Skip on `nextBlocked`, and Skip only appears on\n * an `optional` step that is not the last — that policy lives here now.\n *\n * The `<StepperNav>` is also what publishes {@link WizardContextProvider}, which\n * is how a mounted step registers its own validator. A step's\n * `useRhfWizardStep` / `useWizardStepValidate` therefore only works inside one.\n *\n * Which buttons show is read off `wizard` (`canCancel`, `canGoBack`, `canDone`,\n * `isCommitStep`), so the policy options — `cancellable`, `onExit`, `commits`,\n * `onDone` — are all set on `useWizard`. What is set HERE is how the Finish button\n * looks and what wraps it, because that is presentation and usually depends on\n * state the app holds (a \"replace everything\" toggle, a write lock — `finishCommit`).\n */\nexport function StepperNav<TData extends Record<string, unknown>>({\n wizard,\n children,\n title,\n description,\n className,\n labels,\n finishVariant = \"brand\",\n finishDisabled = false,\n finishCommit,\n finishDisabledReason,\n doneDisabled = false,\n renderFinish,\n}: {\n wizard: UseWizardReturn<TData>;\n children: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n className?: string;\n labels?: Partial<WizardLabels>;\n /** The Finish button's variant — `\"danger\"` for a destructive commit (an import\n * that replaces what is there). Default `\"brand\"`. Next keeps `\"brand\"`. */\n finishVariant?: ButtonVariant;\n /** Disable Finish for a reason of the app's own, on top of the kit's gates\n * (`canFinish`, submitting, validating). */\n finishDisabled?: boolean;\n /**\n * Finish COMMITS — {@link Button}'s `commit`, handed to the Finish button. Under a\n * locked {@link WriteLockProvider} it is `aria-disabled` but focusable, with the lock's\n * reason in the kit Tooltip, and a click (or Enter) does not run `wizard.finish`.\n * Every other button of the bar stays live: the steps before the commit write nothing,\n * so the whole wizard can still be walked through on a read-only page.\n *\n * keksdose G1: its YNAB import runs the mapping wizard on the read-only demo (dev#497)\n * and locks only the confirm step's Finish — by cloning `commit` into the element\n * `renderFinish` hands it, because StepperNav took no `commit` of its own. That clone\n * still works; this is the same thing without one. Named `finishCommit` beside\n * `finishVariant` / `finishDisabled` rather than a bare `commit`, which on a component\n * with five buttons would not say which one saves — and would read like `useWizard`'s\n * `commits`, which is a step's policy, not a lock.\n */\n finishCommit?: boolean;\n /**\n * Why Finish is not available — {@link Button}'s `disabledReason` on the Finish button:\n * `aria-disabled` and focusable, the reason in the kit Tooltip and on the button's\n * `aria-describedby`, `wizard.finish` not run. The explained form of `finishDisabled`\n * (a native `disabled` takes the button out of the tab order, so its reason never\n * reaches a keyboard). Under a lock with `finishCommit`, the lock's reason wins.\n */\n finishDisabledReason?: ReactNode;\n /**\n * Disable Done — the button of the last step after a commit — while the step still\n * has work of its own running. keksdose's YNAB import ends on a recurring step whose\n * convert loop writes one rule at a time (ynab-import-panel.tsx): leaving mid-loop\n * abandons the rest. Until now the panel dropped `onDone` for the duration, which\n * takes the button away instead of saying \"not yet\": disabled, it stays where the\n * eye left it and comes back to life when the loop ends.\n */\n doneDisabled?: boolean;\n /**\n * Wrap the Finish button: receives the kit's button element — already labelled,\n * gated and wired to `wizard.finish` — and returns what to render in its place.\n *\n * ```tsx\n * renderFinish={(button) => <SaveGuard lock={lock}>{button}</SaveGuard>}\n * ```\n *\n * A wrapper and not a replacement (`finishButton: (props) => …`) or a hook\n * (`onBeforeFinish`): the app's need is to put something AROUND the commit — a\n * write-lock tooltip, which has to sit on a wrapping element because a disabled\n * button receives no pointer events — while the kit keeps owning what the button\n * says and when it is enabled. A replacement would hand every app the gating to\n * re-derive (the problem this component exists to end); `onBeforeFinish` can veto\n * a click but cannot explain a disabled button. The element is a plain `<Button>`,\n * so a wrapper that clones it with `disabled` works as expected.\n *\n * For the write lock itself there is no need to wrap any more: `finishCommit` (and\n * `finishDisabledReason`) put the kit Button's own Tooltip on Finish. An element\n * cloned with `commit` here keeps working the same way.\n */\n renderFinish?: (button: ReactElement) => ReactNode;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n const cancelTitleId = useId();\n\n // The step area never shrinks below the tallest step shown so far, so the button\n // bar does not jump up and down as Next and Back move between a long step and a\n // short one. This replaces a fixed `min-h-[300px]` (carried over from Kastlan,\n // whose steps are all long forms), which did the same for steps under 300px but\n // left a short wizard — two fields a step — with a ~200px hole above its buttons.\n // Measured rather than guessed: the floor is whatever the app's own steps need.\n const contentRef = useRef<HTMLDivElement>(null);\n const [contentFloor, setContentFloor] = useState(0);\n useLayoutEffect(() => {\n const el = contentRef.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n const h = el.offsetHeight;\n setContentFloor((floor) => (h > floor ? h : floor));\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const contextValue = useMemo(\n () => ({\n registerStepValidate: wizard.registerStepValidate,\n setNextBlocked: wizard.setNextBlocked,\n }),\n [wizard.registerStepValidate, wizard.setNextBlocked],\n );\n\n // Finish is only valid once every step before the last has been completed or\n // skipped (derived once in useWizard). Prevents a restored last-step index\n // (empty data after a reload) from firing an empty submit.\n const canFinish = wizard.canFinish;\n\n // Pending while the step's validators run (an async server check): a spinner and\n // `aria-busy`, and disabled — the hook's own re-entry guard still refuses a second\n // press, this just stops it looking like the first one did nothing.\n const pending = wizard.isValidating;\n const stepNextLabel = wizard.currentStep.nextLabel;\n\n // Every branch below assigns it; no initial value to overwrite.\n let forwardButton: ReactNode;\n if (wizard.isCommitStep) {\n const finishButton = (\n <Button\n variant={finishVariant}\n data-tour=\"wizard-finish\"\n onClick={wizard.finish}\n // The kit Button's lock: a reason (its own or, with `commit`, the provider's)\n // wins over `disabled` and keeps the button reachable, saying why.\n commit={finishCommit}\n disabledReason={finishDisabledReason}\n disabled={wizard.isSubmitting || pending || !canFinish || finishDisabled}\n aria-busy={wizard.isSubmitting || pending || undefined}\n >\n {/* Decorative: the button's text is what a reader hears. */}\n {pending && <Spinner label={null} className=\"size-4\" />}\n {wizard.isSubmitting ? l.submitting : (stepNextLabel ?? l.finish)}\n </Button>\n );\n forwardButton = renderFinish ? renderFinish(finishButton) : finishButton;\n } else if (wizard.isLastStep) {\n // The last step after a commit. Nothing to finish; Done only when the app gave\n // an `onDone` — otherwise the step carries its own way out.\n forwardButton = wizard.canDone ? (\n <Button variant=\"brand\" data-tour=\"wizard-done\" onClick={wizard.done} disabled={doneDisabled}>\n {stepNextLabel ?? l.done ?? DEFAULT_WIZARD_LABELS.done}\n </Button>\n ) : null;\n } else {\n forwardButton = (\n <Button\n variant=\"brand\"\n data-tour=\"wizard-next\"\n onClick={wizard.goNext}\n disabled={wizard.nextBlocked || pending}\n aria-busy={pending || undefined}\n >\n {pending && <Spinner label={null} className=\"size-4\" />}\n {stepNextLabel ?? l.next}\n </Button>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-6\", className)}>\n {(title || description) && (\n <div>\n {title && (\n <h1 className=\"text-2xl font-bold tracking-tight text-[var(--text-primary)]\">\n {title}\n </h1>\n )}\n {description && (\n <p className=\"mt-1 text-sm text-[var(--text-muted)]\">{description}</p>\n )}\n </div>\n )}\n\n {/* Step indicator */}\n <nav aria-label={l.steps}>\n <ol className=\"flex w-full items-center\">\n {wizard.steps.map((step, index) => {\n const status = wizard.stepStatus(index);\n const isLast = index === wizard.steps.length - 1;\n\n return (\n <li key={step.id} className={cn(\"flex items-center\", !isLast && \"flex-1\")}>\n <button\n type=\"button\"\n onClick={() => wizard.goToStep(index)}\n // `canGoToStep` adds the commit lock: once past a committing step,\n // the steps up to it are done with and cannot be reopened.\n disabled={status === \"upcoming\" || !wizard.canGoToStep(index)}\n aria-current={status === \"active\" ? \"step\" : undefined}\n className={cn(\n \"group flex flex-col items-center gap-1.5\",\n status === \"upcoming\" ? \"cursor-default\" : \"cursor-pointer\",\n )}\n >\n {/* Circle */}\n <span\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors\",\n status === \"completed\" &&\n \"border-[var(--brand)] bg-[var(--brand)] text-[var(--brand-contrast)]\",\n status === \"active\" &&\n \"border-[var(--brand)] bg-[var(--bg-surface)] text-[var(--brand)]\",\n status === \"upcoming\" &&\n \"border-[var(--border)] bg-[var(--bg-surface)] text-[var(--text-placeholder)]\",\n status === \"skipped\" &&\n \"border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-muted)]\",\n )}\n >\n {status === \"completed\" ? <Check className=\"size-4\" /> : index + 1}\n </span>\n {/* Label — hidden on mobile, where the line below carries it */}\n <span\n className={cn(\n \"hidden text-xs font-medium md:block\",\n status === \"active\" || status === \"completed\"\n ? \"text-[var(--text-primary)]\"\n : \"text-[var(--text-muted)]\",\n )}\n >\n {step.label}\n </span>\n </button>\n\n {/* Connector line */}\n {!isLast && (\n <div\n className={cn(\n \"mx-2 h-0.5 flex-1\",\n status === \"completed\" ? \"bg-[var(--brand)]\" : \"bg-[var(--border)]\",\n )}\n />\n )}\n </li>\n );\n })}\n </ol>\n {/* Mobile: the labels above are hidden, so name the current step here */}\n <p className=\"mt-2 text-center text-sm font-medium text-[var(--text-primary)] md:hidden\">\n {l.step(wizard.currentStepIndex + 1, wizard.steps.length)}\n {\" — \"}\n {wizard.currentStep.label}\n </p>\n </nav>\n\n {/* Step content */}\n <WizardContextProvider value={contextValue}>\n <div style={contentFloor > 0 ? { minHeight: contentFloor } : undefined}>\n <div ref={contentRef}>{children}</div>\n </div>\n </WizardContextProvider>\n\n {/* Submit error */}\n {wizard.error && (\n <div\n role=\"alert\"\n className={cn(\n \"text-sm text-[var(--danger)]\",\n alertFrameClass(\"danger\"),\n )}\n >\n {wizard.error}\n </div>\n )}\n\n {/* Navigation bar */}\n <div className=\"flex items-center justify-between border-t border-[var(--border)] pt-4\">\n {/* The empty span keeps the right-hand group on the right when Cancel is off. */}\n {wizard.canCancel ? (\n <Button variant=\"secondary\" onClick={wizard.cancel}>\n {l.cancel}\n </Button>\n ) : (\n <span />\n )}\n <div className=\"flex items-center gap-2\">\n {wizard.canGoBack && (\n // Disabled, not hidden, while the commit runs: it comes back if the commit\n // fails, and a button that vanishes mid-click is its own kind of surprise.\n <Button variant=\"secondary\" onClick={wizard.goBack} disabled={wizard.isSubmitting}>\n {l.back}\n </Button>\n )}\n {wizard.currentStep.optional && !wizard.isLastStep && !wizard.isCommitStep && (\n <Button\n variant=\"ghost\"\n onClick={wizard.skip}\n disabled={wizard.nextBlocked || wizard.isValidating}\n >\n {l.skip}\n </Button>\n )}\n {forwardButton}\n </div>\n </div>\n\n {/* Cancel confirmation */}\n {wizard.showCancelDialog && (\n <Modal onClose={wizard.dismissCancel} labelledBy={cancelTitleId} className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <h2 id={cancelTitleId} className=\"text-lg font-semibold text-[var(--text-primary)]\">\n {l.cancelTitle}\n </h2>\n <p className=\"text-sm text-[var(--text-muted)]\">{l.confirmCancel}</p>\n </div>\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n <Button variant=\"secondary\" onClick={wizard.dismissCancel}>\n {l.cancelDismissLabel}\n </Button>\n <Button variant=\"danger\" onClick={wizard.confirmCancel}>\n {l.cancelConfirmLabel}\n </Button>\n </div>\n </Modal>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/stepper-nav.tsx"],"sourcesContent":["import { useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Button, Spinner } from \"../components/ui\";\nimport type { ButtonVariant } from \"../components/ui\";\nimport { Modal } from \"../components/modal\";\nimport { alertFrameClass } from \"../components/alert-banner\";\nimport { WizardContextProvider } from \"./wizard-context\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { UseWizardReturn, WizardLabels } from \"./types\";\nimport type { CommitScope } from \"../components/write-lock\";\n\n/**\n * The chrome around a wizard: the step indicator, the step content, and the\n * Cancel / Back / Skip / Next / Finish bar — plus the cancel-confirmation\n * dialog, because \"are you sure you want to lose this\" is part of the wizard's\n * contract rather than something each app should re-decide.\n *\n * It is a component and not a set of parts for one reason: which button shows,\n * and whether it is enabled, is a function of `wizard` state that was getting\n * re-derived (and re-derived slightly differently) at every call site. Finish is\n * gated on `canFinish`, Next and Skip on `nextBlocked`, and Skip only appears on\n * an `optional` step that is not the last — that policy lives here now.\n *\n * The `<StepperNav>` is also what publishes {@link WizardContextProvider}, which\n * is how a mounted step registers its own validator. A step's\n * `useRhfWizardStep` / `useWizardStepValidate` therefore only works inside one.\n *\n * Which buttons show is read off `wizard` (`canCancel`, `canGoBack`, `canDone`,\n * `isCommitStep`), so the policy options — `cancellable`, `onExit`, `commits`,\n * `onDone` — are all set on `useWizard`. What is set HERE is how the Finish button\n * looks and what wraps it, because that is presentation and usually depends on\n * state the app holds (a \"replace everything\" toggle, a write lock — `finishCommit`).\n */\nexport function StepperNav<TData extends Record<string, unknown>>({\n wizard,\n children,\n title,\n description,\n className,\n labels,\n finishVariant = \"brand\",\n finishDisabled = false,\n finishCommit,\n finishDisabledReason,\n doneDisabled = false,\n renderFinish,\n}: {\n wizard: UseWizardReturn<TData>;\n children: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n className?: string;\n labels?: Partial<WizardLabels>;\n /** The Finish button's variant — `\"danger\"` for a destructive commit (an import\n * that replaces what is there). Default `\"brand\"`. Next keeps `\"brand\"`. */\n finishVariant?: ButtonVariant;\n /** Disable Finish for a reason of the app's own, on top of the kit's gates\n * (`canFinish`, submitting, validating). */\n finishDisabled?: boolean;\n /**\n * Finish COMMITS — {@link Button}'s `commit`, handed to the Finish button. Under a\n * locked {@link WriteLockProvider} it is `aria-disabled` but focusable, with the lock's\n * reason in the kit Tooltip, and a click (or Enter) does not run `wizard.finish`.\n * Every other button of the bar stays live: the steps before the commit write nothing,\n * so the whole wizard can still be walked through on a read-only page.\n *\n * keksdose G1: its YNAB import runs the mapping wizard on the read-only demo (dev#497)\n * and locks only the confirm step's Finish — by cloning `commit` into the element\n * `renderFinish` hands it, because StepperNav took no `commit` of its own. That clone\n * still works; this is the same thing without one. Named `finishCommit` beside\n * `finishVariant` / `finishDisabled` rather than a bare `commit`, which on a component\n * with five buttons would not say which one saves — and would read like `useWizard`'s\n * `commits`, which is a step's policy, not a lock.\n */\n finishCommit?: CommitScope;\n /**\n * Why Finish is not available — {@link Button}'s `disabledReason` on the Finish button:\n * `aria-disabled` and focusable, the reason in the kit Tooltip and on the button's\n * `aria-describedby`, `wizard.finish` not run. The explained form of `finishDisabled`\n * (a native `disabled` takes the button out of the tab order, so its reason never\n * reaches a keyboard). Under a lock with `finishCommit`, the lock's reason wins.\n */\n finishDisabledReason?: ReactNode;\n /**\n * Disable Done — the button of the last step after a commit — while the step still\n * has work of its own running. keksdose's YNAB import ends on a recurring step whose\n * convert loop writes one rule at a time (ynab-import-panel.tsx): leaving mid-loop\n * abandons the rest. Until now the panel dropped `onDone` for the duration, which\n * takes the button away instead of saying \"not yet\": disabled, it stays where the\n * eye left it and comes back to life when the loop ends.\n */\n doneDisabled?: boolean;\n /**\n * Wrap the Finish button: receives the kit's button element — already labelled,\n * gated and wired to `wizard.finish` — and returns what to render in its place.\n *\n * ```tsx\n * renderFinish={(button) => <SaveGuard lock={lock}>{button}</SaveGuard>}\n * ```\n *\n * A wrapper and not a replacement (`finishButton: (props) => …`) or a hook\n * (`onBeforeFinish`): the app's need is to put something AROUND the commit — a\n * write-lock tooltip, which has to sit on a wrapping element because a disabled\n * button receives no pointer events — while the kit keeps owning what the button\n * says and when it is enabled. A replacement would hand every app the gating to\n * re-derive (the problem this component exists to end); `onBeforeFinish` can veto\n * a click but cannot explain a disabled button. The element is a plain `<Button>`,\n * so a wrapper that clones it with `disabled` works as expected.\n *\n * For the write lock itself there is no need to wrap any more: `finishCommit` (and\n * `finishDisabledReason`) put the kit Button's own Tooltip on Finish. An element\n * cloned with `commit` here keeps working the same way.\n */\n renderFinish?: (button: ReactElement) => ReactNode;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n const cancelTitleId = useId();\n\n // The step area never shrinks below the tallest step shown so far, so the button\n // bar does not jump up and down as Next and Back move between a long step and a\n // short one. This replaces a fixed `min-h-[300px]` (carried over from Kastlan,\n // whose steps are all long forms), which did the same for steps under 300px but\n // left a short wizard — two fields a step — with a ~200px hole above its buttons.\n // Measured rather than guessed: the floor is whatever the app's own steps need.\n const contentRef = useRef<HTMLDivElement>(null);\n const [contentFloor, setContentFloor] = useState(0);\n useLayoutEffect(() => {\n const el = contentRef.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n const h = el.offsetHeight;\n setContentFloor((floor) => (h > floor ? h : floor));\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const contextValue = useMemo(\n () => ({\n registerStepValidate: wizard.registerStepValidate,\n setNextBlocked: wizard.setNextBlocked,\n }),\n [wizard.registerStepValidate, wizard.setNextBlocked],\n );\n\n // Finish is only valid once every step before the last has been completed or\n // skipped (derived once in useWizard). Prevents a restored last-step index\n // (empty data after a reload) from firing an empty submit.\n const canFinish = wizard.canFinish;\n\n // Pending while the step's validators run (an async server check): a spinner and\n // `aria-busy`, and disabled — the hook's own re-entry guard still refuses a second\n // press, this just stops it looking like the first one did nothing.\n const pending = wizard.isValidating;\n const stepNextLabel = wizard.currentStep.nextLabel;\n\n // Every branch below assigns it; no initial value to overwrite.\n let forwardButton: ReactNode;\n if (wizard.isCommitStep) {\n const finishButton = (\n <Button\n variant={finishVariant}\n data-tour=\"wizard-finish\"\n onClick={wizard.finish}\n // The kit Button's lock: a reason (its own or, with `commit`, the provider's)\n // wins over `disabled` and keeps the button reachable, saying why.\n commit={finishCommit}\n disabledReason={finishDisabledReason}\n disabled={wizard.isSubmitting || pending || !canFinish || finishDisabled}\n aria-busy={wizard.isSubmitting || pending || undefined}\n >\n {/* Decorative: the button's text is what a reader hears. */}\n {pending && <Spinner label={null} className=\"size-4\" />}\n {wizard.isSubmitting ? l.submitting : (stepNextLabel ?? l.finish)}\n </Button>\n );\n forwardButton = renderFinish ? renderFinish(finishButton) : finishButton;\n } else if (wizard.isLastStep) {\n // The last step after a commit. Nothing to finish; Done only when the app gave\n // an `onDone` — otherwise the step carries its own way out.\n forwardButton = wizard.canDone ? (\n <Button variant=\"brand\" data-tour=\"wizard-done\" onClick={wizard.done} disabled={doneDisabled}>\n {stepNextLabel ?? l.done ?? DEFAULT_WIZARD_LABELS.done}\n </Button>\n ) : null;\n } else {\n forwardButton = (\n <Button\n variant=\"brand\"\n data-tour=\"wizard-next\"\n onClick={wizard.goNext}\n disabled={wizard.nextBlocked || pending}\n aria-busy={pending || undefined}\n >\n {pending && <Spinner label={null} className=\"size-4\" />}\n {stepNextLabel ?? l.next}\n </Button>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-6\", className)}>\n {(title || description) && (\n <div>\n {title && (\n <h1 className=\"text-2xl font-bold tracking-tight text-[var(--text-primary)]\">\n {title}\n </h1>\n )}\n {description && (\n <p className=\"mt-1 text-sm text-[var(--text-muted)]\">{description}</p>\n )}\n </div>\n )}\n\n {/* Step indicator */}\n <nav aria-label={l.steps}>\n <ol className=\"flex w-full items-center\">\n {wizard.steps.map((step, index) => {\n const status = wizard.stepStatus(index);\n const isLast = index === wizard.steps.length - 1;\n\n return (\n <li key={step.id} className={cn(\"flex items-center\", !isLast && \"flex-1\")}>\n <button\n type=\"button\"\n onClick={() => wizard.goToStep(index)}\n // `canGoToStep` adds the commit lock: once past a committing step,\n // the steps up to it are done with and cannot be reopened.\n disabled={status === \"upcoming\" || !wizard.canGoToStep(index)}\n aria-current={status === \"active\" ? \"step\" : undefined}\n className={cn(\n \"group flex flex-col items-center gap-1.5\",\n status === \"upcoming\" ? \"cursor-default\" : \"cursor-pointer\",\n )}\n >\n {/* Circle */}\n <span\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors\",\n status === \"completed\" &&\n \"border-[var(--brand)] bg-[var(--brand)] text-[var(--brand-contrast)]\",\n status === \"active\" &&\n \"border-[var(--brand)] bg-[var(--bg-surface)] text-[var(--brand)]\",\n status === \"upcoming\" &&\n \"border-[var(--border)] bg-[var(--bg-surface)] text-[var(--text-placeholder)]\",\n status === \"skipped\" &&\n \"border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-muted)]\",\n )}\n >\n {status === \"completed\" ? <Check className=\"size-4\" /> : index + 1}\n </span>\n {/* Label — hidden on mobile, where the line below carries it */}\n <span\n className={cn(\n \"hidden text-xs font-medium md:block\",\n status === \"active\" || status === \"completed\"\n ? \"text-[var(--text-primary)]\"\n : \"text-[var(--text-muted)]\",\n )}\n >\n {step.label}\n </span>\n </button>\n\n {/* Connector line */}\n {!isLast && (\n <div\n className={cn(\n \"mx-2 h-0.5 flex-1\",\n status === \"completed\" ? \"bg-[var(--brand)]\" : \"bg-[var(--border)]\",\n )}\n />\n )}\n </li>\n );\n })}\n </ol>\n {/* Mobile: the labels above are hidden, so name the current step here */}\n <p className=\"mt-2 text-center text-sm font-medium text-[var(--text-primary)] md:hidden\">\n {l.step(wizard.currentStepIndex + 1, wizard.steps.length)}\n {\" — \"}\n {wizard.currentStep.label}\n </p>\n </nav>\n\n {/* Step content */}\n <WizardContextProvider value={contextValue}>\n <div style={contentFloor > 0 ? { minHeight: contentFloor } : undefined}>\n <div ref={contentRef}>{children}</div>\n </div>\n </WizardContextProvider>\n\n {/* Submit error */}\n {wizard.error && (\n <div\n role=\"alert\"\n className={cn(\n \"text-sm text-[var(--danger)]\",\n alertFrameClass(\"danger\"),\n )}\n >\n {wizard.error}\n </div>\n )}\n\n {/* Navigation bar */}\n <div className=\"flex items-center justify-between border-t border-[var(--border)] pt-4\">\n {/* The empty span keeps the right-hand group on the right when Cancel is off. */}\n {wizard.canCancel ? (\n <Button variant=\"secondary\" onClick={wizard.cancel}>\n {l.cancel}\n </Button>\n ) : (\n <span />\n )}\n <div className=\"flex items-center gap-2\">\n {wizard.canGoBack && (\n // Disabled, not hidden, while the commit runs: it comes back if the commit\n // fails, and a button that vanishes mid-click is its own kind of surprise.\n <Button variant=\"secondary\" onClick={wizard.goBack} disabled={wizard.isSubmitting}>\n {l.back}\n </Button>\n )}\n {wizard.currentStep.optional && !wizard.isLastStep && !wizard.isCommitStep && (\n <Button\n variant=\"ghost\"\n onClick={wizard.skip}\n disabled={wizard.nextBlocked || wizard.isValidating}\n >\n {l.skip}\n </Button>\n )}\n {forwardButton}\n </div>\n </div>\n\n {/* Cancel confirmation */}\n {wizard.showCancelDialog && (\n <Modal onClose={wizard.dismissCancel} labelledBy={cancelTitleId} className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <h2 id={cancelTitleId} className=\"text-lg font-semibold text-[var(--text-primary)]\">\n {l.cancelTitle}\n </h2>\n <p className=\"text-sm text-[var(--text-muted)]\">{l.confirmCancel}</p>\n </div>\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n <Button variant=\"secondary\" onClick={wizard.dismissCancel}>\n {l.cancelDismissLabel}\n </Button>\n <Button variant=\"danger\" onClick={wizard.confirmCancel}>\n {l.cancelConfirmLabel}\n </Button>\n </div>\n </Modal>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/wizard.d.ts
CHANGED
|
@@ -6,20 +6,25 @@ export { WizardStep, WizardStepProps } from './wizard/wizard-step.js';
|
|
|
6
6
|
export { WizardSummary } from './wizard/wizard-summary.js';
|
|
7
7
|
export { StepperNav } from './wizard/stepper-nav.js';
|
|
8
8
|
import 'react';
|
|
9
|
-
import './delete-account-setting-
|
|
9
|
+
import './delete-account-setting-DiPX4Vue.js';
|
|
10
10
|
import './components/tooltip.js';
|
|
11
11
|
import './hooks/use-anchored-rect.js';
|
|
12
12
|
import './lib/clipping.js';
|
|
13
|
-
import './data-table-labels-
|
|
13
|
+
import './data-table-labels-BJ5JE_X2.js';
|
|
14
14
|
import './components/data-table-sort.js';
|
|
15
15
|
import './components/swipeable-row.js';
|
|
16
|
+
import './components/status-dot.js';
|
|
17
|
+
import './components/progress-bar.js';
|
|
18
|
+
import './components/signed-amount.js';
|
|
19
|
+
import './components/stat-tile.js';
|
|
20
|
+
import './components/chip.js';
|
|
21
|
+
import 'lucide-react';
|
|
22
|
+
import './components/field-parts.js';
|
|
23
|
+
import './components/write-lock.js';
|
|
16
24
|
import './components/mini-calendar.js';
|
|
17
25
|
import './components/calendar-heatmap.js';
|
|
18
26
|
import './components/popover.js';
|
|
19
27
|
import './hooks/use-breakpoint.js';
|
|
20
|
-
import './components/chip.js';
|
|
21
|
-
import 'lucide-react';
|
|
22
|
-
import './components/field-parts.js';
|
|
23
28
|
import './components/field-sync.js';
|
|
24
29
|
import './tour/tour.js';
|
|
25
30
|
import './search/command-palette.js';
|
|
@@ -30,7 +35,6 @@ import './components/page-contents.js';
|
|
|
30
35
|
import './components/series-chart-labels.js';
|
|
31
36
|
import './components/pie-chart-labels.js';
|
|
32
37
|
import './components/sparkline.js';
|
|
33
|
-
import './components/stat-tile.js';
|
|
34
38
|
import './components/signature-pad.js';
|
|
35
39
|
import './components/password-strength.js';
|
|
36
40
|
import './components/swatch-picker.js';
|
|
@@ -42,9 +46,6 @@ import './hooks/use-search-param-state.js';
|
|
|
42
46
|
import './components/measured-grid.js';
|
|
43
47
|
import './components/user-avatar.js';
|
|
44
48
|
import './lib/person-name.js';
|
|
45
|
-
import './components/status-dot.js';
|
|
46
|
-
import './components/progress-bar.js';
|
|
47
|
-
import './components/signed-amount.js';
|
|
48
49
|
import './shell/topbar-action-menu.js';
|
|
49
50
|
import './components/menu-item.js';
|
|
50
51
|
import './components/floating-panel.js';
|
|
@@ -60,7 +61,6 @@ import './components/error-boundary.js';
|
|
|
60
61
|
import './components/authed-image.js';
|
|
61
62
|
import './components/image-grid.js';
|
|
62
63
|
import './components/lightbox.js';
|
|
63
|
-
import './components/write-lock.js';
|
|
64
64
|
import './auth/sign-in-form.js';
|
|
65
65
|
import './landing/access.js';
|
|
66
66
|
import './landing/landing-labels.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"types": "./dist/table-text.d.ts",
|
|
49
49
|
"import": "./dist/table-text.js"
|
|
50
50
|
},
|
|
51
|
+
"./testing": {
|
|
52
|
+
"types": "./dist/testing/index.d.ts",
|
|
53
|
+
"import": "./dist/testing/index.js"
|
|
54
|
+
},
|
|
51
55
|
"./rhf": {
|
|
52
56
|
"types": "./dist/rhf.d.ts",
|
|
53
57
|
"import": "./dist/rhf.js"
|
|
@@ -85,7 +89,7 @@
|
|
|
85
89
|
"check": "node scripts/check.mjs",
|
|
86
90
|
"test": "vitest run",
|
|
87
91
|
"test:watch": "vitest",
|
|
88
|
-
"build": "node scripts/tsup-quiet.mjs && node scripts/fix-esm-extensions.mjs",
|
|
92
|
+
"build": "node scripts/tsup-quiet.mjs && node scripts/fix-esm-extensions.mjs && node scripts/build-pay-page.mjs",
|
|
89
93
|
"verify:package": "npm run build && node scripts/verify-package.mjs",
|
|
90
94
|
"dev": "tsup --watch",
|
|
91
95
|
"dev:showcase": "vite --config showcase/vite.config.ts",
|
|
@@ -171,6 +175,10 @@
|
|
|
171
175
|
"publishConfig": {
|
|
172
176
|
"access": "public"
|
|
173
177
|
},
|
|
178
|
+
"_bin": "`eifi1-pay-page` copies the kit's static pay page (dist/pay/, docs/billing-harmonization.md §14.4) into an app's web build and writes its pay-config.json. Built by scripts/build-pay-page.mjs from pay-page/; it ships inside `dist`, so `files` needs nothing more.",
|
|
179
|
+
"bin": {
|
|
180
|
+
"eifi1-pay-page": "dist/bin/eifi1-pay-page.mjs"
|
|
181
|
+
},
|
|
174
182
|
"main": "./dist/index.js",
|
|
175
183
|
"module": "./dist/index.js",
|
|
176
184
|
"types": "./dist/index.d.ts",
|
|
@@ -19,6 +19,7 @@ import { authErrorCode, englishRateLimited, isRateLimited, retryAfterSeconds } f
|
|
|
19
19
|
import { useFocusWhen } from "../auth/status-parts";
|
|
20
20
|
import { CARD_DESCRIPTION_CLASS, refusalCompanies, settle, useMounted } from "./account-parts";
|
|
21
21
|
import { SettingsCardTitle } from "../settings/settings-heading";
|
|
22
|
+
import { COMMIT_EXCEPT_BILLING } from "../components/write-lock";
|
|
22
23
|
|
|
23
24
|
/* ── Labels ──────────────────────────────────────────────────────────────── */
|
|
24
25
|
|
|
@@ -182,6 +183,10 @@ function isConsequence(item: DeleteAccountConsequence): item is DangerConsequenc
|
|
|
182
183
|
*
|
|
183
184
|
* **After success** the card says the account is deactivated and the person is being
|
|
184
185
|
* signed out — the app's to do, since the kit sends nothing (auth §8).
|
|
186
|
+
*
|
|
187
|
+
* **Under a write lock** the confirm is a `commit` exempt from billing
|
|
188
|
+
* (`COMMIT_EXCEPT_BILLING`, 0.33): leaving never depends on paying
|
|
189
|
+
* (docs/billing-harmonization.md decision 14, §12.36); a demo's lock still holds it.
|
|
185
190
|
*/
|
|
186
191
|
export function DeleteAccountSetting({
|
|
187
192
|
email,
|
|
@@ -318,7 +323,7 @@ export function DeleteAccountSetting({
|
|
|
318
323
|
) : (
|
|
319
324
|
<>
|
|
320
325
|
<DangerConfirm
|
|
321
|
-
commit
|
|
326
|
+
commit={COMMIT_EXCEPT_BILLING}
|
|
322
327
|
armLabel={labels.arm}
|
|
323
328
|
prompt={labels.prompt}
|
|
324
329
|
consequences={lines}
|
|
@@ -12,6 +12,7 @@ import { authErrorCode, englishRateLimited, isRateLimited, retryAfterSeconds } f
|
|
|
12
12
|
import { emailParts, taggedEmail } from "../auth/email-tag";
|
|
13
13
|
import { CARD_DESCRIPTION_CLASS, settle, useMounted } from "./account-parts";
|
|
14
14
|
import { SettingsCardTitle } from "../settings/settings-heading";
|
|
15
|
+
import { COMMIT_EXCEPT_BILLING } from "../components/write-lock";
|
|
15
16
|
|
|
16
17
|
/* ── Labels ──────────────────────────────────────────────────────────────── */
|
|
17
18
|
|
|
@@ -179,6 +180,11 @@ interface Failure {
|
|
|
179
180
|
* **Done** (`confirmed`): "Your email address is now …" and, unless `passkeyNote` is
|
|
180
181
|
* off, that a passkey keeps its old label in the device's list — the authenticator
|
|
181
182
|
* stored the name it was given, and only a new passkey carries the new one.
|
|
183
|
+
*
|
|
184
|
+
* **Under a write lock** the request, "Send the link again" and "Cancel the change" are
|
|
185
|
+
* `commit` controls exempt from billing (`COMMIT_EXCEPT_BILLING`, 0.33): the address is
|
|
186
|
+
* the account's own settings, which a lapsed plan never locks
|
|
187
|
+
* (docs/billing-harmonization.md §3.3, §12.36); a demo's lock still does.
|
|
182
188
|
*/
|
|
183
189
|
export function EmailChangeSetting({
|
|
184
190
|
currentEmail,
|
|
@@ -283,7 +289,7 @@ export function EmailChangeSetting({
|
|
|
283
289
|
type="button"
|
|
284
290
|
variant="secondary"
|
|
285
291
|
size="sm"
|
|
286
|
-
commit
|
|
292
|
+
commit={COMMIT_EXCEPT_BILLING}
|
|
287
293
|
pending={busy === "resend"}
|
|
288
294
|
disabled={busy === "cancel"}
|
|
289
295
|
onClick={() => run("resend", onResend)}
|
|
@@ -296,7 +302,7 @@ export function EmailChangeSetting({
|
|
|
296
302
|
type="button"
|
|
297
303
|
variant="ghost"
|
|
298
304
|
size="sm"
|
|
299
|
-
commit
|
|
305
|
+
commit={COMMIT_EXCEPT_BILLING}
|
|
300
306
|
pending={busy === "cancel"}
|
|
301
307
|
disabled={busy === "resend"}
|
|
302
308
|
onClick={() => run("cancel", onCancel)}
|
|
@@ -445,7 +451,7 @@ export function EmailChangeSetting({
|
|
|
445
451
|
{failure.message}
|
|
446
452
|
</AlertBanner>
|
|
447
453
|
)}
|
|
448
|
-
<Button type="submit" commit disabled={!working && !canSubmit} pending={working}>
|
|
454
|
+
<Button type="submit" commit={COMMIT_EXCEPT_BILLING} disabled={!working && !canSubmit} pending={working}>
|
|
449
455
|
{labels.submit}
|
|
450
456
|
</Button>
|
|
451
457
|
</form>
|
|
@@ -14,6 +14,7 @@ import { Skeleton } from "../components/skeleton";
|
|
|
14
14
|
import { Button, Card } from "../components/ui";
|
|
15
15
|
import { CARD_DESCRIPTION_CLASS, settle, useMounted } from "./account-parts";
|
|
16
16
|
import { SettingsCardTitle } from "../settings/settings-heading";
|
|
17
|
+
import { COMMIT_EXCEPT_BILLING } from "../components/write-lock";
|
|
17
18
|
|
|
18
19
|
/* ── Labels ──────────────────────────────────────────────────────────────── */
|
|
19
20
|
|
|
@@ -145,6 +146,10 @@ export interface SessionsSettingProps<Id extends SessionId = SessionId>
|
|
|
145
146
|
*
|
|
146
147
|
* Both requests are the app's; a failure is said under the card's action, in the app's
|
|
147
148
|
* words if `describeError` has them.
|
|
149
|
+
*
|
|
150
|
+
* **Under a write lock** both are `commit` controls exempt from billing
|
|
151
|
+
* (`COMMIT_EXCEPT_BILLING`, 0.33): a lapsed plan never keeps a person from ending their
|
|
152
|
+
* own sessions (docs/billing-harmonization.md §12.13, §12.36); a demo's lock still does.
|
|
148
153
|
*/
|
|
149
154
|
export function SessionsSetting<Id extends SessionId = SessionId>({
|
|
150
155
|
onSignOutEverywhere,
|
|
@@ -252,7 +257,7 @@ export function SessionsSetting<Id extends SessionId = SessionId>({
|
|
|
252
257
|
type="button"
|
|
253
258
|
size="sm"
|
|
254
259
|
variant="ghost"
|
|
255
|
-
commit
|
|
260
|
+
commit={COMMIT_EXCEPT_BILLING}
|
|
256
261
|
pending={busyId === session.id}
|
|
257
262
|
disabled={busyId !== null && busyId !== session.id}
|
|
258
263
|
aria-label={labels.revokeItem(device)}
|
|
@@ -268,7 +273,7 @@ export function SessionsSetting<Id extends SessionId = SessionId>({
|
|
|
268
273
|
))}
|
|
269
274
|
<DangerConfirm
|
|
270
275
|
tone="warning"
|
|
271
|
-
commit
|
|
276
|
+
commit={COMMIT_EXCEPT_BILLING}
|
|
272
277
|
armLabel={labels.signOutEverywhere}
|
|
273
278
|
prompt={labels.signOutEverywherePrompt}
|
|
274
279
|
confirmLabel={labels.confirmSignOutEverywhere}
|
|
@@ -12,6 +12,7 @@ import { authErrorCode } from "../auth/auth-errors";
|
|
|
12
12
|
import type { KitErrorCode } from "../auth/auth-errors";
|
|
13
13
|
import { hasMessage, usePersonLabel } from "./admin-parts";
|
|
14
14
|
import type { AdminPerson, MaybePromise } from "./admin-parts";
|
|
15
|
+
import type { CommitScope } from "../components/write-lock";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* The confirmation in front of an admin action, at the level the SERVER states
|
|
@@ -153,7 +154,7 @@ export interface AdminActionConfirmProps {
|
|
|
153
154
|
open?: boolean;
|
|
154
155
|
/** The confirm COMMITS: under a locked `WriteLockProvider` it is held with the lock's
|
|
155
156
|
* reason. Off by default — an admin action is not a write to the open record. */
|
|
156
|
-
commit?:
|
|
157
|
+
commit?: CommitScope;
|
|
157
158
|
labels?: Partial<AdminActionLabels>;
|
|
158
159
|
}
|
|
159
160
|
|