@eifi1/ui-kit 0.31.1 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -28
- package/dist/account/delete-account-setting.d.ts +8 -3
- package/dist/admin/admin-action-confirm.d.ts +8 -3
- package/dist/admin/admin-action-log.d.ts +1 -1
- package/dist/admin/admin-action-log.js +4 -2
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +8 -3
- package/dist/admin/invitations-panel.js +29 -26
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.d.ts +8 -3
- package/dist/admin/role-select.d.ts +10 -5
- package/dist/admin/transfer-ownership-dialog.d.ts +8 -3
- package/dist/admin/user-roster.d.ts +10 -5
- package/dist/admin/user-roster.js +8 -47
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/auth-errors.d.ts +43 -8
- package/dist/auth/auth-errors.js +24 -1
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/register-form.d.ts +8 -3
- package/dist/billing/billing-banner.d.ts +184 -0
- package/dist/billing/billing-banner.js +98 -0
- package/dist/billing/billing-banner.js.map +1 -0
- package/dist/billing/billing-labels.d.ts +100 -0
- package/dist/billing/billing-labels.js +76 -0
- package/dist/billing/billing-labels.js.map +1 -0
- package/dist/billing/billing-lock.d.ts +158 -0
- package/dist/billing/billing-lock.js +17 -0
- package/dist/billing/billing-lock.js.map +1 -0
- package/dist/billing/billing-standing.d.ts +149 -0
- package/dist/billing/billing-standing.js +28 -0
- package/dist/billing/billing-standing.js.map +1 -0
- package/dist/billing/plan-card.d.ts +248 -0
- package/dist/billing/plan-card.js +248 -0
- package/dist/billing/plan-card.js.map +1 -0
- package/dist/billing/plan-limit-notice.d.ts +156 -0
- package/dist/billing/plan-limit-notice.js +76 -0
- package/dist/billing/plan-limit-notice.js.map +1 -0
- package/dist/billing/plan-limit.d.ts +34 -0
- package/dist/billing/plan-limit.js +20 -0
- package/dist/billing/plan-limit.js.map +1 -0
- package/dist/billing/plan-price.d.ts +61 -0
- package/dist/billing/plan-price.js +45 -0
- package/dist/billing/plan-price.js.map +1 -0
- package/dist/billing/subscription-actions.d.ts +133 -0
- package/dist/billing/subscription-actions.js +28 -0
- package/dist/billing/subscription-actions.js.map +1 -0
- package/dist/billing/subscription-status-chip.d.ts +149 -0
- package/dist/billing/subscription-status-chip.js +48 -0
- package/dist/billing/subscription-status-chip.js.map +1 -0
- package/dist/chart.d.ts +9 -3
- package/dist/chart.js.map +1 -1
- package/dist/components/account-chips.d.ts +16 -4
- package/dist/components/account-chips.js +21 -3
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/alert-banner.d.ts +6 -1
- package/dist/components/alert-banner.js +5 -4
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +8 -3
- package/dist/components/amount-input.js +5 -5
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/appearance-labels.d.ts +38 -0
- package/dist/components/appearance-labels.js +19 -0
- package/dist/components/appearance-labels.js.map +1 -0
- package/dist/components/autocomplete.js +2 -2
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/breadcrumbs.js +3 -2
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/bulk-action-bar.d.ts +2 -2
- package/dist/components/bulk-action-bar.js +7 -12
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-group.d.ts +8 -3
- package/dist/components/button-group.js +7 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +8 -3
- package/dist/components/calculator.js +4 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/calendar-heatmap.js +5 -5
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chart-height.d.ts +23 -0
- package/dist/components/chart-height.js +15 -0
- package/dist/components/chart-height.js.map +1 -0
- package/dist/components/chart-zoom.js +6 -1
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/checkbox-group.js +2 -2
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/checkbox.d.ts +10 -0
- package/dist/components/checkbox.js +25 -10
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +19 -3
- package/dist/components/chip.js +54 -23
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +1 -1
- package/dist/components/choice-card.js +9 -8
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/column-mapper.js +1 -1
- package/dist/components/column-mapper.js.map +1 -1
- package/dist/components/combobox-core.js +8 -7
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.js +8 -8
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +8 -3
- package/dist/components/copy-button.d.ts +10 -5
- package/dist/components/country-select.js +1 -1
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-select.js +3 -3
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +8 -3
- package/dist/components/data-table-cells.d.ts +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filter-popover.js +4 -1
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-labels.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -2
- package/dist/components/data-table-pagination.js +15 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +123 -52
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +1 -1
- package/dist/components/date-picker.js +18 -6
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.js +2 -2
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.js +2 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/disclosure.js +3 -2
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -1
- package/dist/components/dropdown.js +7 -1
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/facing-pair.d.ts +19 -6
- package/dist/components/facing-pair.js +3 -2
- package/dist/components/facing-pair.js.map +1 -1
- package/dist/components/field-parts.d.ts +102 -6
- package/dist/components/field-parts.js +81 -6
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-sync.d.ts +4 -1
- package/dist/components/field-sync.js +12 -2
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/field.js +2 -2
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +8 -3
- package/dist/components/file-dropzone.d.ts +8 -3
- package/dist/components/floating-panel.js +7 -4
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/focus-ring.d.ts +12 -0
- package/dist/components/focus-ring.js +8 -0
- package/dist/components/focus-ring.js.map +1 -0
- package/dist/components/form-actions.d.ts +8 -3
- package/dist/components/form-actions.js +33 -19
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +11 -1
- package/dist/components/full-bleed-dialog.js +3 -1
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/iban-input.d.ts +8 -3
- package/dist/components/image-grid.js +1 -1
- package/dist/components/image-grid.js.map +1 -1
- package/dist/components/inline-edit-field.js +10 -4
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/language-select.d.ts +8 -3
- package/dist/components/legal-page.d.ts +26 -5
- package/dist/components/legal-page.js +3 -1
- package/dist/components/legal-page.js.map +1 -1
- package/dist/components/legal.d.ts +8 -3
- package/dist/components/legal.js +2 -1
- package/dist/components/legal.js.map +1 -1
- package/dist/components/lightbox.js +24 -2
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/line-items.js +2 -1
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.d.ts +5 -0
- package/dist/components/list.js +13 -9
- package/dist/components/list.js.map +1 -1
- package/dist/components/measured-grid.d.ts +3 -1
- package/dist/components/measured-grid.js +38 -20
- package/dist/components/measured-grid.js.map +1 -1
- package/dist/components/menu-item.js +2 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.js +8 -4
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/money-field.d.ts +8 -3
- package/dist/components/month-picker.js +50 -39
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/nav-pills.js +1 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-field.d.ts +8 -3
- package/dist/components/number-input.d.ts +11 -4
- package/dist/components/number-input.js +8 -6
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +8 -3
- package/dist/components/numpad-sheet.js +2 -1
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/one-time-code-input.js +2 -2
- package/dist/components/one-time-code-input.js.map +1 -1
- package/dist/components/page-contents.js +2 -2
- package/dist/components/page-contents.js.map +1 -1
- package/dist/components/phone-input.d.ts +8 -3
- package/dist/components/phone-input.js +4 -4
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/pie-chart.d.ts +8 -2
- package/dist/components/pie-chart.js +11 -7
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.d.ts +9 -1
- package/dist/components/popover.js +8 -4
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.d.ts +100 -0
- package/dist/components/row-actions.js +231 -0
- package/dist/components/row-actions.js.map +1 -0
- package/dist/components/scroll-area.js +1 -1
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/search-field.js +3 -2
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-budget.d.ts +30 -7
- package/dist/components/series-chart-budget.js +12 -4
- package/dist/components/series-chart-budget.js.map +1 -1
- package/dist/components/series-chart-ticks.d.ts +7 -1
- package/dist/components/series-chart-ticks.js +6 -0
- package/dist/components/series-chart-ticks.js.map +1 -1
- package/dist/components/series-chart.d.ts +58 -28
- package/dist/components/series-chart.js +42 -32
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/settings-fields.d.ts +57 -8
- package/dist/components/settings-fields.js +39 -1
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.d.ts +11 -6
- package/dist/components/share-card.js +8 -2
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/sign-chip.d.ts +4 -0
- package/dist/components/sign-chip.js +17 -4
- package/dist/components/sign-chip.js.map +1 -1
- package/dist/components/signature-pad.d.ts +3 -1
- package/dist/components/signature-pad.js +8 -5
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/slider.js +7 -6
- package/dist/components/slider.js.map +1 -1
- package/dist/components/sparkline.d.ts +5 -1
- package/dist/components/sparkline.js +4 -2
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.d.ts +17 -1
- package/dist/components/stat-tile.js +48 -20
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +4 -0
- package/dist/components/status-dot.js +9 -2
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/swipeable-row.js +10 -2
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +21 -7
- package/dist/components/switch.js +24 -11
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.js +2 -2
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +8 -3
- package/dist/components/text-link.js +1 -1
- package/dist/components/text-link.js.map +1 -1
- package/dist/components/text.d.ts +7 -2
- package/dist/components/text.js +5 -5
- package/dist/components/text.js.map +1 -1
- package/dist/components/tile-radio.js +2 -1
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +8 -3
- package/dist/components/toast.d.ts +18 -2
- package/dist/components/toast.js +8 -5
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle-group.d.ts +25 -3
- package/dist/components/toggle-group.js +40 -11
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +10 -1
- package/dist/components/toggle-legend.js +21 -7
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -1
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-parts.d.ts +4 -0
- package/dist/components/translation-review.d.ts +4 -0
- package/dist/components/translation-review.js +3 -3
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/tree-view.js +3 -2
- package/dist/components/tree-view.js.map +1 -1
- package/dist/components/treemap.d.ts +5 -2
- package/dist/components/treemap.js +33 -16
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/ui.d.ts +8 -3
- package/dist/components/ui.js +207 -119
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/user-avatar.d.ts +5 -1
- package/dist/components/user-avatar.js +1 -1
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +20 -14
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-labels-DUELeXpa.d.ts → data-table-labels-kpAAVi6g.d.ts} +26 -2
- package/dist/data-table.d.ts +1 -1
- package/dist/{delete-account-setting-CpIhzxxZ.d.ts → delete-account-setting-FLyJf-kZ.d.ts} +642 -60
- package/dist/demo/demo-banner.js +1 -0
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +8 -3
- package/dist/feedback/feedback-context.d.ts +8 -3
- package/dist/feedback/feedback-crash.d.ts +8 -3
- package/dist/feedback/feedback-dialog.d.ts +8 -3
- package/dist/feedback/feedback-dialog.js +3 -3
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +8 -3
- package/dist/feedback/feedback-inbox.js +2 -2
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-labels.d.ts +8 -3
- package/dist/feedback/feedback-menu.d.ts +9 -4
- package/dist/feedback/feedback-record.d.ts +8 -3
- package/dist/feedback/feedback-row-detail.d.ts +8 -3
- package/dist/feedback/feedback-status-undo.d.ts +8 -3
- package/dist/feedback/feedback-submit.d.ts +8 -3
- package/dist/feedback/feedback-swipe.d.ts +8 -3
- package/dist/feedback/feedback-table.d.ts +8 -3
- package/dist/feedback/feedback-thread.d.ts +8 -3
- package/dist/feedback/feedback-thread.js +3 -3
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +8 -3
- package/dist/hooks/use-anchored-panel.d.ts +32 -3
- package/dist/hooks/use-anchored-panel.js +21 -3
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-breakpoint.d.ts +88 -0
- package/dist/hooks/use-breakpoint.js +50 -0
- package/dist/hooks/use-breakpoint.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +8 -3
- package/dist/hooks/use-large-text.d.ts +29 -0
- package/dist/hooks/use-large-text.js +20 -0
- package/dist/hooks/use-large-text.js.map +1 -0
- package/dist/hooks/use-windowed-rows.d.ts +52 -8
- package/dist/hooks/use-windowed-rows.js +94 -5
- package/dist/hooks/use-windowed-rows.js.map +1 -1
- package/dist/i18n/defaults.d.ts +8 -3
- package/dist/i18n/defaults.js +8 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +8 -3
- package/dist/i18n/german.js +94 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +8 -3
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +10 -5
- package/dist/i18n/locales/de-CH.d.ts +8 -3
- package/dist/i18n/locales/en.d.ts +8 -3
- package/dist/i18n/locales/es.d.ts +8 -3
- package/dist/i18n/locales/es.js +89 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +8 -3
- package/dist/i18n/locales/fr.js +91 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +8 -3
- package/dist/i18n/locales/hu.js +91 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +8 -3
- package/dist/i18n/locales/it.js +92 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +8 -3
- package/dist/i18n/locales/zh.js +89 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +8 -3
- package/dist/i18n/review.js +15 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +27 -11
- package/dist/index.js +77 -4
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.d.ts +8 -3
- package/dist/landing/landing-sections.d.ts +8 -3
- package/dist/landing/landing-sections.js +7 -3
- package/dist/landing/landing-sections.js.map +1 -1
- package/dist/landing/public-header.d.ts +9 -4
- package/dist/landing/public-header.js +3 -3
- package/dist/landing/public-header.js.map +1 -1
- package/dist/landing/routing.d.ts +8 -3
- package/dist/lib/cn.js +3 -2
- package/dist/lib/cn.js.map +1 -1
- package/dist/rhf/fields.d.ts +8 -3
- package/dist/rhf/fields.js +1 -1
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +8 -3
- package/dist/rhf/form.js +2 -2
- package/dist/rhf/form.js.map +1 -1
- package/dist/rhf.d.ts +8 -3
- package/dist/search/command-palette.d.ts +1 -1
- package/dist/search/command-palette.js +9 -8
- package/dist/search/command-palette.js.map +1 -1
- package/dist/settings/settings-layout.js +2 -1
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/settings/settings-section.d.ts +8 -3
- package/dist/settings/use-account-appearance.d.ts +92 -0
- package/dist/settings/use-account-appearance.js +63 -0
- package/dist/settings/use-account-appearance.js.map +1 -0
- package/dist/settings/use-settings-route.d.ts +1 -1
- package/dist/settings/use-settings-route.js +2 -3
- package/dist/settings/use-settings-route.js.map +1 -1
- package/dist/shell/app-shell.d.ts +12 -171
- package/dist/shell/app-shell.js +144 -28
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.js +1 -1
- package/dist/shell/auth-layout.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +1 -1
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/top-bar-brand.d.ts +8 -3
- package/dist/shell/top-bar-brand.js +3 -1
- package/dist/shell/top-bar-brand.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +4 -0
- package/dist/shell/topbar-action-menu.js +3 -3
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.js +4 -4
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +9 -5
- package/dist/theme/contrast-tokens.d.ts +41 -0
- package/dist/theme/contrast-tokens.js +59 -0
- package/dist/theme/contrast-tokens.js.map +1 -0
- package/dist/theme/contrast.d.ts +110 -0
- package/dist/theme/contrast.js +128 -0
- package/dist/theme/contrast.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +12 -1
- package/dist/theme/palette-presets.js +9 -4
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.d.ts +173 -0
- package/dist/theme/text-size.js +121 -0
- package/dist/theme/text-size.js.map +1 -0
- package/dist/wizard/stepper-nav.d.ts +8 -3
- package/dist/wizard/wizard-summary.js +1 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -3
- package/package.json +1 -1
- package/src/admin/admin-action-log.tsx +6 -1
- package/src/admin/invitations-panel.tsx +36 -29
- package/src/admin/user-roster.tsx +17 -72
- package/src/auth/auth-errors.ts +68 -7
- package/src/billing/billing-banner.tsx +205 -0
- package/src/billing/billing-labels.ts +240 -0
- package/src/billing/billing-lock.ts +75 -0
- package/src/billing/billing-standing.ts +72 -0
- package/src/billing/plan-card.tsx +458 -0
- package/src/billing/plan-limit-notice.tsx +145 -0
- package/src/billing/plan-limit.ts +53 -0
- package/src/billing/plan-price.ts +101 -0
- package/src/billing/subscription-actions.tsx +76 -0
- package/src/billing/subscription-status-chip.tsx +95 -0
- package/src/chart.ts +2 -0
- package/src/components/account-chips.tsx +36 -10
- package/src/components/alert-banner.tsx +16 -7
- package/src/components/amount-input.tsx +5 -5
- package/src/components/appearance-labels.ts +47 -0
- package/src/components/autocomplete.tsx +2 -2
- package/src/components/breadcrumbs.tsx +3 -3
- package/src/components/bulk-action-bar.tsx +11 -14
- package/src/components/button-group.tsx +21 -0
- package/src/components/calculator.tsx +4 -3
- package/src/components/calendar-heatmap.tsx +5 -5
- package/src/components/chart-height.ts +30 -0
- package/src/components/chart-zoom.tsx +6 -1
- package/src/components/checkbox-group.tsx +2 -2
- package/src/components/checkbox.tsx +62 -20
- package/src/components/chip.tsx +139 -30
- package/src/components/choice-card.tsx +16 -11
- package/src/components/column-mapper.tsx +1 -1
- package/src/components/combobox-core.tsx +8 -7
- package/src/components/combobox.tsx +8 -8
- package/src/components/country-select.tsx +1 -1
- package/src/components/currency-select.tsx +3 -3
- package/src/components/data-table-filter-popover.tsx +7 -1
- package/src/components/data-table-pagination.tsx +60 -31
- package/src/components/data-table.tsx +164 -43
- package/src/components/date-picker.tsx +44 -10
- package/src/components/description-list.tsx +2 -2
- package/src/components/dialog-frame.tsx +2 -1
- package/src/components/disclosure.tsx +3 -2
- package/src/components/dropdown.tsx +10 -2
- package/src/components/facing-pair.ts +12 -5
- package/src/components/field-parts.tsx +190 -7
- package/src/components/field-sync.tsx +17 -3
- package/src/components/field.tsx +2 -2
- package/src/components/floating-panel.tsx +7 -4
- package/src/components/focus-ring.ts +12 -0
- package/src/components/form-actions.tsx +55 -18
- package/src/components/full-bleed-dialog.tsx +18 -4
- package/src/components/image-grid.tsx +1 -1
- package/src/components/inline-edit-field.tsx +26 -4
- package/src/components/legal-page.tsx +28 -2
- package/src/components/legal.tsx +19 -1
- package/src/components/lightbox.tsx +24 -2
- package/src/components/line-items.tsx +7 -1
- package/src/components/list.tsx +36 -10
- package/src/components/measured-grid.tsx +49 -24
- package/src/components/menu-item.tsx +2 -2
- package/src/components/mini-calendar.tsx +8 -4
- package/src/components/month-picker.tsx +15 -6
- package/src/components/nav-pills.tsx +1 -1
- package/src/components/number-input.tsx +14 -7
- package/src/components/numpad-sheet.tsx +2 -2
- package/src/components/one-time-code-input.tsx +2 -2
- package/src/components/page-contents.tsx +2 -2
- package/src/components/phone-input.tsx +9 -4
- package/src/components/pie-chart.tsx +19 -9
- package/src/components/popover.tsx +22 -5
- package/src/components/row-actions.tsx +450 -0
- package/src/components/scroll-area.tsx +1 -1
- package/src/components/search-field.tsx +4 -2
- package/src/components/series-chart-budget.ts +40 -8
- package/src/components/series-chart-ticks.ts +10 -0
- package/src/components/series-chart.tsx +123 -72
- package/src/components/settings-fields.tsx +89 -5
- package/src/components/share-card.tsx +31 -2
- package/src/components/sign-chip.tsx +28 -4
- package/src/components/signature-pad.tsx +13 -7
- package/src/components/slider.tsx +22 -9
- package/src/components/sparkline.tsx +11 -3
- package/src/components/stat-tile.tsx +70 -14
- package/src/components/status-dot.tsx +25 -2
- package/src/components/swipeable-row.tsx +10 -2
- package/src/components/switch.tsx +57 -23
- package/src/components/table.tsx +2 -2
- package/src/components/text-link.tsx +1 -1
- package/src/components/text.tsx +11 -6
- package/src/components/tile-radio.tsx +4 -1
- package/src/components/toast.tsx +27 -7
- package/src/components/toggle-group.tsx +115 -14
- package/src/components/toggle-legend.tsx +53 -8
- package/src/components/translation-review-editor.tsx +1 -1
- package/src/components/translation-review.tsx +11 -5
- package/src/components/tree-view.tsx +3 -2
- package/src/components/treemap.tsx +27 -10
- package/src/components/ui.tsx +353 -153
- package/src/components/user-avatar.tsx +1 -1
- package/src/components/wizard-stepper.tsx +5 -1
- package/src/demo/demo-banner.tsx +5 -0
- package/src/feedback/feedback-dialog.tsx +3 -3
- package/src/feedback/feedback-inbox.tsx +2 -2
- package/src/feedback/feedback-thread.tsx +3 -3
- package/src/hooks/use-anchored-panel.ts +67 -5
- package/src/hooks/use-breakpoint.ts +132 -0
- package/src/hooks/use-large-text.ts +41 -0
- package/src/hooks/use-windowed-rows.ts +168 -12
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/german.ts +107 -0
- package/src/i18n/kit-labels.tsx +12 -0
- package/src/i18n/locales/es.ts +98 -0
- package/src/i18n/locales/fr.ts +102 -0
- package/src/i18n/locales/hu.ts +100 -0
- package/src/i18n/locales/it.ts +103 -0
- package/src/i18n/locales/zh.ts +94 -0
- package/src/i18n/review.ts +14 -0
- package/src/index.ts +76 -6
- package/src/landing/landing-sections.tsx +7 -3
- package/src/landing/public-header.tsx +4 -4
- package/src/lib/cn.ts +13 -2
- package/src/rhf/fields.tsx +1 -1
- package/src/rhf/form.tsx +2 -2
- package/src/search/command-palette.tsx +10 -9
- package/src/settings/settings-layout.tsx +2 -1
- package/src/settings/use-account-appearance.ts +148 -0
- package/src/settings/use-settings-route.ts +3 -4
- package/src/shell/app-shell.tsx +278 -54
- package/src/shell/auth-layout.tsx +1 -1
- package/src/shell/option-switcher-menu.tsx +1 -1
- package/src/shell/top-bar-brand.tsx +3 -1
- package/src/shell/topbar-action-menu.tsx +3 -3
- package/src/shell/topbar-controls.tsx +4 -4
- package/src/theme/contrast-tokens.ts +112 -0
- package/src/theme/contrast.ts +225 -0
- package/src/theme/palette-presets.ts +24 -4
- package/src/theme/palette-store.ts +6 -1
- package/src/theme/text-size.ts +283 -0
- package/src/wizard/wizard-summary.tsx +4 -2
- package/tokens.css +158 -8
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { c4 as CardTone } from '../delete-account-setting-FLyJf-kZ.js';
|
|
4
4
|
import '../components/tooltip.js';
|
|
5
5
|
import '../hooks/use-anchored-rect.js';
|
|
6
6
|
import '../lib/clipping.js';
|
|
7
|
-
import '../data-table-labels-
|
|
7
|
+
import '../data-table-labels-kpAAVi6g.js';
|
|
8
8
|
import '../components/data-table-sort.js';
|
|
9
9
|
import '../components/swipeable-row.js';
|
|
10
10
|
import '../components/mini-calendar.js';
|
|
11
11
|
import '../components/calendar-heatmap.js';
|
|
12
12
|
import '../components/popover.js';
|
|
13
|
+
import '../hooks/use-breakpoint.js';
|
|
13
14
|
import '../components/chip.js';
|
|
14
15
|
import 'lucide-react';
|
|
16
|
+
import '../components/field-parts.js';
|
|
15
17
|
import '../components/field-sync.js';
|
|
16
18
|
import '../wizard/types.js';
|
|
17
19
|
import '../tour/tour.js';
|
|
@@ -80,6 +82,9 @@ import '../admin/admin-parts.js';
|
|
|
80
82
|
import '../admin/admin-action-log.js';
|
|
81
83
|
import './settings-labels.js';
|
|
82
84
|
import '../demo/demo-labels.js';
|
|
85
|
+
import '../components/appearance-labels.js';
|
|
86
|
+
import '../theme/contrast.js';
|
|
87
|
+
import '../theme/text-size.js';
|
|
83
88
|
import '../components/reauth-dialog.js';
|
|
84
89
|
import '../components/server-wake.js';
|
|
85
90
|
import '../lib/server-wake.js';
|
|
@@ -93,7 +98,7 @@ import '../components/sign-chip.js';
|
|
|
93
98
|
import '../components/column-mapper.js';
|
|
94
99
|
import '../lib/column-mapping.js';
|
|
95
100
|
import '../lib/table-text.js';
|
|
96
|
-
import '../components/
|
|
101
|
+
import '../components/focus-ring.js';
|
|
97
102
|
|
|
98
103
|
interface SettingsSectionProps extends Omit<ComponentPropsWithoutRef<"div">, "title" | "id"> {
|
|
99
104
|
/** The card's DOM id — the catalogue entry's `anchor`, the `?focus=` target. */
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { TextSize } from '../theme/text-size.js';
|
|
2
|
+
import { ContrastMode } from '../theme/contrast.js';
|
|
3
|
+
import 'zustand/middleware';
|
|
4
|
+
import 'zustand';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The text size and contrast follow the account like the language does
|
|
8
|
+
* (docs/text-size-harmonization.md §2.2, §2.6, §6; settings contract §6.2) — the same
|
|
9
|
+
* rule as {@link useAccountLanguage}, for two values at once.
|
|
10
|
+
*/
|
|
11
|
+
/** Where a value in force came from, in the order they are asked. */
|
|
12
|
+
type AppearanceSource = "device" | "account" | "default";
|
|
13
|
+
/** The two fields of an `/auth/me` answer this reads — server-kit 0.6's `UserResponse`,
|
|
14
|
+
* both nullable (null = never chosen). */
|
|
15
|
+
interface AccountAppearanceFields {
|
|
16
|
+
text_size?: string | null;
|
|
17
|
+
contrast?: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** This device's own choices — the text-size and contrast stores' values, `null` where
|
|
20
|
+
* this device never chose. */
|
|
21
|
+
interface DeviceAppearance {
|
|
22
|
+
textSize?: string | null;
|
|
23
|
+
contrast?: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** The body of `PATCH /auth/me` for a pick: one field, as server-kit's `ProfileUpdate`
|
|
26
|
+
* spells it. */
|
|
27
|
+
type AccountAppearancePatch = {
|
|
28
|
+
text_size: TextSize;
|
|
29
|
+
} | {
|
|
30
|
+
contrast: ContrastMode;
|
|
31
|
+
};
|
|
32
|
+
interface ResolvedAccountAppearance {
|
|
33
|
+
textSize: TextSize;
|
|
34
|
+
/** The stored MODE — "system" included — not whether more contrast is on now. */
|
|
35
|
+
contrast: ContrastMode;
|
|
36
|
+
sources: {
|
|
37
|
+
textSize: AppearanceSource;
|
|
38
|
+
contrast: AppearanceSource;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The values on a device (§2.6): **its own choice → the account's → the default**
|
|
43
|
+
* (Normal; "system" for contrast), each taken only when it is a known value. Pure.
|
|
44
|
+
*/
|
|
45
|
+
declare function resolveAccountAppearance(device: DeviceAppearance, account: AccountAppearanceFields | null | undefined): ResolvedAccountAppearance;
|
|
46
|
+
interface UseAccountAppearanceOptions {
|
|
47
|
+
/** The signed-in account (`/auth/me`), or `null` / `undefined` while signed out or not
|
|
48
|
+
* loaded yet — then nothing is saved to it. */
|
|
49
|
+
account: AccountAppearanceFields | null | undefined;
|
|
50
|
+
/** This device's own choices: `useTextSizeStore((s) => s.size)` and
|
|
51
|
+
* `useContrastStore((s) => s.contrast)`. */
|
|
52
|
+
device: DeviceAppearance;
|
|
53
|
+
/** Store a device choice: `(patch) => { if (patch.textSize) setSize(patch.textSize);
|
|
54
|
+
* if (patch.contrast) setContrast(patch.contrast); }`. */
|
|
55
|
+
setDevice: (patch: {
|
|
56
|
+
textSize?: TextSize;
|
|
57
|
+
contrast?: ContrastMode;
|
|
58
|
+
}) => void;
|
|
59
|
+
/** Write the account: `PATCH /auth/me` with the patch as its body. Return its promise. */
|
|
60
|
+
save: (patch: AccountAppearancePatch) => unknown;
|
|
61
|
+
/** A demo session: a pick stays on the device, the account is never written (§6). */
|
|
62
|
+
isDemo?: boolean;
|
|
63
|
+
}
|
|
64
|
+
interface AccountAppearance extends ResolvedAccountAppearance {
|
|
65
|
+
/** The user picked a size: it becomes the device's choice and — signed in, and not a
|
|
66
|
+
* demo — the account's. The promise is `save`'s; the device keeps the pick if it
|
|
67
|
+
* fails, and the app says so (`appearance.saveFailed`, 0.32.1). */
|
|
68
|
+
pickTextSize: (size: TextSize) => Promise<unknown>;
|
|
69
|
+
/** As {@link pickTextSize}, for the contrast mode. */
|
|
70
|
+
pickContrast: (mode: ContrastMode) => Promise<unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The account's text size and contrast on this device (§6): the order of
|
|
74
|
+
* {@link resolveAccountAppearance}, and the writes.
|
|
75
|
+
*
|
|
76
|
+
* - **Nothing writes the account on its own.** Rendering, signing in and adopting the
|
|
77
|
+
* account's values call neither `save` nor `setDevice`, so a new device adopts the
|
|
78
|
+
* account instead of overwriting it.
|
|
79
|
+
* - **A pick** writes the device's choice, then `save` — except in a demo session or
|
|
80
|
+
* signed out.
|
|
81
|
+
*
|
|
82
|
+
* The hook only answers. The values are put in force by the stores' apply hooks, which
|
|
83
|
+
* resolve the same order from the same inputs:
|
|
84
|
+
*
|
|
85
|
+
* ```tsx
|
|
86
|
+
* useApplyTextSize({ account: me?.text_size });
|
|
87
|
+
* useApplyContrast({ account: me?.contrast });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
declare function useAccountAppearance(options: UseAccountAppearanceOptions): AccountAppearance;
|
|
91
|
+
|
|
92
|
+
export { type AccountAppearance, type AccountAppearanceFields, type AccountAppearancePatch, type AppearanceSource, type DeviceAppearance, type ResolvedAccountAppearance, type UseAccountAppearanceOptions, resolveAccountAppearance, useAccountAppearance };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { isTextSize, resolveTextSize } from "../theme/text-size.js";
|
|
4
|
+
import { isContrastMode, resolveContrastMode } from "../theme/contrast.js";
|
|
5
|
+
function source(device, account) {
|
|
6
|
+
return device ? "device" : account ? "account" : "default";
|
|
7
|
+
}
|
|
8
|
+
function resolveAccountAppearance(device, account) {
|
|
9
|
+
return {
|
|
10
|
+
textSize: resolveTextSize(device.textSize, account?.text_size),
|
|
11
|
+
contrast: resolveContrastMode(device.contrast, account?.contrast),
|
|
12
|
+
sources: {
|
|
13
|
+
textSize: source(isTextSize(device.textSize), isTextSize(account?.text_size)),
|
|
14
|
+
contrast: source(isContrastMode(device.contrast), isContrastMode(account?.contrast))
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function useAccountAppearance(options) {
|
|
19
|
+
const { account, device, setDevice, save, isDemo = false } = options;
|
|
20
|
+
const deviceTextSize = device.textSize;
|
|
21
|
+
const deviceContrast = device.contrast;
|
|
22
|
+
const accountTextSize = account?.text_size;
|
|
23
|
+
const accountContrast = account?.contrast;
|
|
24
|
+
const signedIn = account !== null && account !== void 0;
|
|
25
|
+
const resolved = useMemo(
|
|
26
|
+
() => resolveAccountAppearance(
|
|
27
|
+
{ textSize: deviceTextSize, contrast: deviceContrast },
|
|
28
|
+
signedIn ? { text_size: accountTextSize, contrast: accountContrast } : void 0
|
|
29
|
+
),
|
|
30
|
+
[deviceTextSize, deviceContrast, signedIn, accountTextSize, accountContrast]
|
|
31
|
+
);
|
|
32
|
+
const write = useCallback(
|
|
33
|
+
(patch) => {
|
|
34
|
+
if (isDemo || !signedIn) return Promise.resolve();
|
|
35
|
+
try {
|
|
36
|
+
return Promise.resolve(save(patch));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return Promise.reject(error);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[save, isDemo, signedIn]
|
|
42
|
+
);
|
|
43
|
+
const pickTextSize = useCallback(
|
|
44
|
+
(size) => {
|
|
45
|
+
setDevice({ textSize: size });
|
|
46
|
+
return write({ text_size: size });
|
|
47
|
+
},
|
|
48
|
+
[setDevice, write]
|
|
49
|
+
);
|
|
50
|
+
const pickContrast = useCallback(
|
|
51
|
+
(mode) => {
|
|
52
|
+
setDevice({ contrast: mode });
|
|
53
|
+
return write({ contrast: mode });
|
|
54
|
+
},
|
|
55
|
+
[setDevice, write]
|
|
56
|
+
);
|
|
57
|
+
return { ...resolved, pickTextSize, pickContrast };
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
resolveAccountAppearance,
|
|
61
|
+
useAccountAppearance
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=use-account-appearance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/settings/use-account-appearance.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport { isTextSize, resolveTextSize } from \"../theme/text-size\";\nimport type { TextSize } from \"../theme/text-size\";\nimport { isContrastMode, resolveContrastMode } from \"../theme/contrast\";\nimport type { ContrastMode } from \"../theme/contrast\";\n\n/**\n * The text size and contrast follow the account like the language does\n * (docs/text-size-harmonization.md §2.2, §2.6, §6; settings contract §6.2) — the same\n * rule as {@link useAccountLanguage}, for two values at once.\n */\n\n/** Where a value in force came from, in the order they are asked. */\nexport type AppearanceSource = \"device\" | \"account\" | \"default\";\n\n/** The two fields of an `/auth/me` answer this reads — server-kit 0.6's `UserResponse`,\n * both nullable (null = never chosen). */\nexport interface AccountAppearanceFields {\n text_size?: string | null;\n contrast?: string | null;\n}\n\n/** This device's own choices — the text-size and contrast stores' values, `null` where\n * this device never chose. */\nexport interface DeviceAppearance {\n textSize?: string | null;\n contrast?: string | null;\n}\n\n/** The body of `PATCH /auth/me` for a pick: one field, as server-kit's `ProfileUpdate`\n * spells it. */\nexport type AccountAppearancePatch = { text_size: TextSize } | { contrast: ContrastMode };\n\nexport interface ResolvedAccountAppearance {\n textSize: TextSize;\n /** The stored MODE — \"system\" included — not whether more contrast is on now. */\n contrast: ContrastMode;\n sources: { textSize: AppearanceSource; contrast: AppearanceSource };\n}\n\nfunction source(device: boolean, account: boolean): AppearanceSource {\n return device ? \"device\" : account ? \"account\" : \"default\";\n}\n\n/**\n * The values on a device (§2.6): **its own choice → the account's → the default**\n * (Normal; \"system\" for contrast), each taken only when it is a known value. Pure.\n */\nexport function resolveAccountAppearance(\n device: DeviceAppearance,\n account: AccountAppearanceFields | null | undefined,\n): ResolvedAccountAppearance {\n return {\n textSize: resolveTextSize(device.textSize, account?.text_size),\n contrast: resolveContrastMode(device.contrast, account?.contrast),\n sources: {\n textSize: source(isTextSize(device.textSize), isTextSize(account?.text_size)),\n contrast: source(isContrastMode(device.contrast), isContrastMode(account?.contrast)),\n },\n };\n}\n\nexport interface UseAccountAppearanceOptions {\n /** The signed-in account (`/auth/me`), or `null` / `undefined` while signed out or not\n * loaded yet — then nothing is saved to it. */\n account: AccountAppearanceFields | null | undefined;\n /** This device's own choices: `useTextSizeStore((s) => s.size)` and\n * `useContrastStore((s) => s.contrast)`. */\n device: DeviceAppearance;\n /** Store a device choice: `(patch) => { if (patch.textSize) setSize(patch.textSize);\n * if (patch.contrast) setContrast(patch.contrast); }`. */\n setDevice: (patch: { textSize?: TextSize; contrast?: ContrastMode }) => void;\n /** Write the account: `PATCH /auth/me` with the patch as its body. Return its promise. */\n save: (patch: AccountAppearancePatch) => unknown;\n /** A demo session: a pick stays on the device, the account is never written (§6). */\n isDemo?: boolean;\n}\n\nexport interface AccountAppearance extends ResolvedAccountAppearance {\n /** The user picked a size: it becomes the device's choice and — signed in, and not a\n * demo — the account's. The promise is `save`'s; the device keeps the pick if it\n * fails, and the app says so (`appearance.saveFailed`, 0.32.1). */\n pickTextSize: (size: TextSize) => Promise<unknown>;\n /** As {@link pickTextSize}, for the contrast mode. */\n pickContrast: (mode: ContrastMode) => Promise<unknown>;\n}\n\n/**\n * The account's text size and contrast on this device (§6): the order of\n * {@link resolveAccountAppearance}, and the writes.\n *\n * - **Nothing writes the account on its own.** Rendering, signing in and adopting the\n * account's values call neither `save` nor `setDevice`, so a new device adopts the\n * account instead of overwriting it.\n * - **A pick** writes the device's choice, then `save` — except in a demo session or\n * signed out.\n *\n * The hook only answers. The values are put in force by the stores' apply hooks, which\n * resolve the same order from the same inputs:\n *\n * ```tsx\n * useApplyTextSize({ account: me?.text_size });\n * useApplyContrast({ account: me?.contrast });\n * ```\n */\nexport function useAccountAppearance(options: UseAccountAppearanceOptions): AccountAppearance {\n const { account, device, setDevice, save, isDemo = false } = options;\n const deviceTextSize = device.textSize;\n const deviceContrast = device.contrast;\n const accountTextSize = account?.text_size;\n const accountContrast = account?.contrast;\n const signedIn = account !== null && account !== undefined;\n // By value, so a fresh `/auth/me` object with the same two fields changes nothing.\n const resolved = useMemo(\n () =>\n resolveAccountAppearance(\n { textSize: deviceTextSize, contrast: deviceContrast },\n signedIn ? { text_size: accountTextSize, contrast: accountContrast } : undefined,\n ),\n [deviceTextSize, deviceContrast, signedIn, accountTextSize, accountContrast],\n );\n const write = useCallback(\n (patch: AccountAppearancePatch) => {\n if (isDemo || !signedIn) return Promise.resolve();\n try {\n return Promise.resolve(save(patch));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n [save, isDemo, signedIn],\n );\n const pickTextSize = useCallback(\n (size: TextSize) => {\n setDevice({ textSize: size });\n return write({ text_size: size });\n },\n [setDevice, write],\n );\n const pickContrast = useCallback(\n (mode: ContrastMode) => {\n setDevice({ contrast: mode });\n return write({ contrast: mode });\n },\n [setDevice, write],\n );\n return { ...resolved, pickTextSize, pickContrast };\n}\n"],"mappings":";AAAA,SAAS,aAAa,eAAe;AACrC,SAAS,YAAY,uBAAuB;AAE5C,SAAS,gBAAgB,2BAA2B;AAqCpD,SAAS,OAAO,QAAiB,SAAoC;AACnE,SAAO,SAAS,WAAW,UAAU,YAAY;AACnD;AAMO,SAAS,yBACd,QACA,SAC2B;AAC3B,SAAO;AAAA,IACL,UAAU,gBAAgB,OAAO,UAAU,SAAS,SAAS;AAAA,IAC7D,UAAU,oBAAoB,OAAO,UAAU,SAAS,QAAQ;AAAA,IAChE,SAAS;AAAA,MACP,UAAU,OAAO,WAAW,OAAO,QAAQ,GAAG,WAAW,SAAS,SAAS,CAAC;AAAA,MAC5E,UAAU,OAAO,eAAe,OAAO,QAAQ,GAAG,eAAe,SAAS,QAAQ,CAAC;AAAA,IACrF;AAAA,EACF;AACF;AA6CO,SAAS,qBAAqB,SAAyD;AAC5F,QAAM,EAAE,SAAS,QAAQ,WAAW,MAAM,SAAS,MAAM,IAAI;AAC7D,QAAM,iBAAiB,OAAO;AAC9B,QAAM,iBAAiB,OAAO;AAC9B,QAAM,kBAAkB,SAAS;AACjC,QAAM,kBAAkB,SAAS;AACjC,QAAM,WAAW,YAAY,QAAQ,YAAY;AAEjD,QAAM,WAAW;AAAA,IACf,MACE;AAAA,MACE,EAAE,UAAU,gBAAgB,UAAU,eAAe;AAAA,MACrD,WAAW,EAAE,WAAW,iBAAiB,UAAU,gBAAgB,IAAI;AAAA,IACzE;AAAA,IACF,CAAC,gBAAgB,gBAAgB,UAAU,iBAAiB,eAAe;AAAA,EAC7E;AACA,QAAM,QAAQ;AAAA,IACZ,CAAC,UAAkC;AACjC,UAAI,UAAU,CAAC,SAAU,QAAO,QAAQ,QAAQ;AAChD,UAAI;AACF,eAAO,QAAQ,QAAQ,KAAK,KAAK,CAAC;AAAA,MACpC,SAAS,OAAO;AACd,eAAO,QAAQ,OAAO,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,IACA,CAAC,MAAM,QAAQ,QAAQ;AAAA,EACzB;AACA,QAAM,eAAe;AAAA,IACnB,CAAC,SAAmB;AAClB,gBAAU,EAAE,UAAU,KAAK,CAAC;AAC5B,aAAO,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,IAClC;AAAA,IACA,CAAC,WAAW,KAAK;AAAA,EACnB;AACA,QAAM,eAAe;AAAA,IACnB,CAAC,SAAuB;AACtB,gBAAU,EAAE,UAAU,KAAK,CAAC;AAC5B,aAAO,MAAM,EAAE,UAAU,KAAK,CAAC;AAAA,IACjC;AAAA,IACA,CAAC,WAAW,KAAK;AAAA,EACnB;AACA,SAAO,EAAE,GAAG,UAAU,cAAc,aAAa;AACnD;","names":[]}
|
|
@@ -3,7 +3,7 @@ import 'react';
|
|
|
3
3
|
import '../search/search-index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Which layout the settings page is in. `"auto"` follows the screen (
|
|
6
|
+
* Which layout the settings page is in. `"auto"` follows the screen (`usePhoneLayout()`,
|
|
7
7
|
* as `Tabs` does); `"desktop"` / `"phone"` pin one — for a preview of both side by side,
|
|
8
8
|
* where the window is one width.
|
|
9
9
|
*/
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useEffect, useMemo } from "react";
|
|
3
3
|
import { useLocation, useNavigate } from "react-router";
|
|
4
|
-
import {
|
|
5
|
-
import { PHONE_QUERY } from "../components/ui.js";
|
|
4
|
+
import { usePhoneLayout } from "../hooks/use-breakpoint.js";
|
|
6
5
|
import { normalizeSettingsBase, visibleSettingsGroups } from "./settings-catalogue.js";
|
|
7
6
|
const SETTINGS_FROM_LIST_STATE = "eifi1.settings.fromList";
|
|
8
7
|
function settingsFromListState() {
|
|
@@ -55,7 +54,7 @@ function useSettingsRoute(options) {
|
|
|
55
54
|
const { groups, entries, basePath, defaultGroup, aliases, layout = "auto" } = options;
|
|
56
55
|
const location = useLocation();
|
|
57
56
|
const navigate = useNavigate();
|
|
58
|
-
const narrow =
|
|
57
|
+
const narrow = usePhoneLayout();
|
|
59
58
|
const phone = layout === "phone" || layout === "auto" && narrow;
|
|
60
59
|
const base = normalizeSettingsBase(basePath);
|
|
61
60
|
const visible = useMemo(() => visibleSettingsGroups(groups, entries), [groups, entries]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/settings/use-settings-route.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { useMediaQuery } from \"../hooks/use-media-query\";\nimport { PHONE_QUERY } from \"../components/ui\";\nimport { normalizeSettingsBase, visibleSettingsGroups } from \"./settings-catalogue\";\nimport type { SettingsEntry, SettingsGroup } from \"./settings-catalogue\";\n\n/**\n * Which layout the settings page is in. `\"auto\"` follows the screen ({@link PHONE_QUERY},\n * as `Tabs` does); `\"desktop\"` / `\"phone\"` pin one — for a preview of both side by side,\n * where the window is one width.\n */\nexport type SettingsLayoutMode = \"auto\" | \"desktop\" | \"phone\";\n\nexport interface UseSettingsRouteOptions<G extends string> {\n groups: readonly SettingsGroup<G>[];\n /** The catalogue's entries. Given, a group with no visible entry is hidden (§4.1), and\n * `/settings?focus=<anchor>` finds the anchor's group. */\n entries?: readonly SettingsEntry<G>[];\n /** `/settings` or `/admin`. */\n basePath: string;\n /** The group `/settings` opens on a desktop. Default: the first visible group (§4.1). */\n defaultGroup?: G;\n /** Retired group ids and their successors — `{ overview: \"metrics\" }` — applied with a\n * replace (§3.1, keksdose's admin). */\n aliases?: Readonly<Partial<Record<string, G>>>;\n /** See {@link SettingsLayoutMode}. Default `\"auto\"`. */\n layout?: SettingsLayoutMode;\n}\n\nexport interface SettingsRoute<G extends string> {\n /** The group on screen. `null` on a phone's list (and while there is no visible group). */\n group: G | null;\n /** The segment after the group, owned by a card; `null` without one. */\n sub: string | null;\n /** `?focus=<anchor>`, or `null`. */\n focus: string | null;\n /** Open a group: a PUSH on a phone, so the system back gesture returns to the list; a\n * REPLACE on a desktop, so back leaves settings instead of walking every group looked\n * at (§3.1, keksdose's rule). */\n select: (group: G) => void;\n /** See {@link SettingsLayoutContextValue.selectSub}. */\n selectSub: (sub: string | null) => void;\n /** To the list: `navigate(-1)` when the list is the previous entry, else a replace to\n * the base path — so a deep link from a mail never sends \"back\" out of the app. */\n back: () => void;\n /** The visible groups, in order. */\n groups: SettingsGroup<G>[];\n /** Whether the phone layout is in force. */\n phone: boolean;\n /** Whether this location is about to be replaced (a legacy hash, an alias, an unknown\n * group): what renders meanwhile is already the destination's, but nothing should\n * act on the URL until it is. */\n redirecting: boolean;\n /** Whether the list is the entry before this one (the marker {@link select} and the\n * list's links leave in `location.state`). */\n fromList: boolean;\n}\n\n/**\n * The `location.state` key that says \"the list is the entry before this one\". Written by\n * the phone list's links and {@link SettingsRoute.select}; carried over by every replace\n * the page makes, since a replace does not change what came before.\n */\nexport const SETTINGS_FROM_LIST_STATE = \"eifi1.settings.fromList\";\n\n/** `location.state` with the marker set — for an app's own link to a group page that is\n * opened FROM the list it renders. */\nexport function settingsFromListState(): Record<string, true> {\n return { [SETTINGS_FROM_LIST_STATE]: true };\n}\n\nfunction isFromList(state: unknown): boolean {\n return typeof state === \"object\" && state !== null && (state as Record<string, unknown>)[SETTINGS_FROM_LIST_STATE] === true;\n}\n\nexport interface SettingsLocation {\n pathname: string;\n search: string;\n hash: string;\n}\n\nexport interface SettingsLocationRules<G extends string> {\n basePath: string;\n /** The visible group ids. */\n groups: readonly G[];\n /** The resolved default group, `null` with no visible group. */\n defaultGroup: G | null;\n aliases?: Readonly<Partial<Record<string, G>>>;\n /** Anchor → group, for `/settings?focus=<anchor>` with no group named. */\n anchorGroup?: ReadonlyMap<string, G>;\n phone: boolean;\n}\n\nexport interface SettingsLocationResult<G extends string> {\n group: G | null;\n sub: string | null;\n /** The path (with query) to replace the location with, or `null` when it is already\n * canonical. */\n redirect: string | null;\n}\n\n/**\n * Where a location lands on the settings page — pure, so the rules of §3.1 are testable\n * without a router:\n *\n * - `/settings` → the default group on a desktop (a replace), the list on a phone;\n * - `/settings/<group>[/<sub>]` → that group, the rest of the path as `sub`;\n * - an alias → its successor, with a replace, `sub`, query and hash kept;\n * - an unknown or hidden group → the default group (desktop) or the list (phone), with\n * a replace and the query kept — never a 404, so an old bookmark still lands;\n * - keksdose's legacy `/settings?<query>#<group>[/<sub>]` →\n * `/settings/<group>[/<sub>]?<query>`, the WHOLE query carried (a tour's\n * `tour`/`tourStep` as well as `focus`), then resolved by the rules above;\n * - `/settings?focus=<anchor>` with no group → the anchor's group, where it can ring.\n */\nexport function resolveSettingsLocation<G extends string>(\n location: SettingsLocation,\n rules: SettingsLocationRules<G>,\n): SettingsLocationResult<G> {\n const base = normalizeSettingsBase(rules.basePath);\n const { pathname, search, hash } = location;\n const known = new Set<string>(rules.groups);\n const atBase = pathname === base || pathname === `${base}/` || (base === \"\" && pathname === \"/\");\n\n if (atBase) {\n const legacy = hash.replace(/^#/, \"\").replace(/^\\/+/, \"\");\n if (legacy) {\n // The hash becomes the path; whatever the path then says (an alias, an unknown\n // group) is resolved in the same step, so the history sees one replace.\n const converted = `${base}/${legacy}`;\n const next = resolveSettingsLocation({ pathname: converted, search, hash: \"\" }, rules);\n return { ...next, redirect: next.redirect ?? `${converted}${search}` };\n }\n const anchor = new URLSearchParams(search).get(\"focus\");\n const anchorGroup = anchor ? rules.anchorGroup?.get(anchor) : undefined;\n if (anchorGroup && known.has(anchorGroup)) {\n return { group: anchorGroup, sub: null, redirect: `${base}/${anchorGroup}${search}` };\n }\n if (rules.phone || rules.defaultGroup === null) return { group: null, sub: null, redirect: null };\n return { group: rules.defaultGroup, sub: null, redirect: `${base}/${rules.defaultGroup}${search}` };\n }\n\n if (!pathname.startsWith(`${base}/`)) {\n // Mounted somewhere `basePath` does not name: a misconfiguration, not a link to\n // correct. Show what the base path would, and leave the address alone.\n return { group: rules.phone ? null : rules.defaultGroup, sub: null, redirect: null };\n }\n const rest = pathname.slice(base.length + 1);\n const [segment = \"\", ...subParts] = rest.split(\"/\");\n const sub = subParts.filter(Boolean).join(\"/\") || null;\n if (known.has(segment)) return { group: segment as G, sub, redirect: null };\n\n const successor = rules.aliases?.[segment];\n if (successor !== undefined && known.has(successor)) {\n return {\n group: successor,\n sub,\n redirect: `${base}/${successor}${sub ? `/${sub}` : \"\"}${search}${hash}`,\n };\n }\n\n // Unknown or hidden. The hash is dropped: on the list it would read as a legacy group\n // and send the page round again.\n if (rules.phone || rules.defaultGroup === null) {\n return { group: null, sub: null, redirect: `${base || \"/\"}${search}` };\n }\n return { group: rules.defaultGroup, sub: null, redirect: `${base}/${rules.defaultGroup}${search}` };\n}\n\n/**\n * The settings page's route (docs/settings-harmonization.md §3.1): which group the URL\n * names, and the history rules for moving between groups.\n *\n * The URL IS the selection, as it was in keksdose (where it was the hash): there is no\n * state to keep in step with it, so a deep link, a reload, back and forward all land\n * where the address says. Everything that is not canonical — `/settings` on a desktop, a\n * legacy hash, an alias, an unknown or hidden group — is replaced by its canonical path.\n *\n * **The replace runs on every navigation**, keyed on `location.key`, not once on mount:\n * the router keeps the page mounted across links followed inside the app — keksdose's\n * inbox renders admin-typed URLs, and a push click routes an open window without a\n * reload — so a legacy `/settings#security` arriving while the page is open converts too\n * (§10.1).\n */\nexport function useSettingsRoute<G extends string>(options: UseSettingsRouteOptions<G>): SettingsRoute<G> {\n const { groups, entries, basePath, defaultGroup, aliases, layout = \"auto\" } = options;\n const location = useLocation();\n const navigate = useNavigate();\n const narrow = useMediaQuery(PHONE_QUERY, false);\n const phone = layout === \"phone\" || (layout === \"auto\" && narrow);\n const base = normalizeSettingsBase(basePath);\n\n const visible = useMemo(() => visibleSettingsGroups(groups, entries), [groups, entries]);\n const anchorGroup = useMemo(() => {\n const map = new Map<string, G>();\n for (const e of entries ?? []) if (e.visible !== false && !map.has(e.anchor)) map.set(e.anchor, e.group);\n return map;\n }, [entries]);\n const ids = visible.map((g) => g.id);\n const fallback =\n defaultGroup !== undefined && ids.includes(defaultGroup) ? defaultGroup : (ids[0] ?? null);\n\n const { group, sub, redirect } = resolveSettingsLocation(location, {\n basePath: base,\n groups: ids,\n defaultGroup: fallback,\n aliases,\n anchorGroup,\n phone,\n });\n\n const { key, state, search } = location;\n useEffect(() => {\n // `state` rides along: the marker describes the entry BEFORE this one, which a\n // replace does not change.\n if (redirect) navigate(redirect, { replace: true, state });\n }, [key, redirect, state, navigate]);\n\n const select = useCallback(\n (next: G) => {\n const to = `${base}/${next}`;\n // From the list, the push marks it, so the group page's back is a real back.\n if (phone) navigate(to, group === null ? { state: settingsFromListState() } : undefined);\n // A new group is a new page: the old one's query (a focus, a roster filter) stays\n // behind, as keksdose's `selectGroup` left it.\n else navigate(to, { replace: true, state });\n },\n [base, phone, group, state, navigate],\n );\n\n const selectSub = useCallback(\n (next: string | null) => {\n if (group === null) return;\n navigate(`${base}/${group}${next ? `/${next}` : \"\"}${search}`, { replace: true, state });\n },\n [base, group, search, state, navigate],\n );\n\n const fromList = isFromList(state);\n const back = useCallback(() => {\n if (fromList) navigate(-1);\n else navigate(base || \"/\", { replace: true });\n }, [fromList, base, navigate]);\n\n return {\n group,\n sub,\n focus: new URLSearchParams(search).get(\"focus\"),\n select,\n selectSub,\n back,\n groups: visible,\n phone,\n redirecting: redirect !== null,\n fromList,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/settings/use-settings-route.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { usePhoneLayout } from \"../hooks/use-breakpoint\";\nimport { normalizeSettingsBase, visibleSettingsGroups } from \"./settings-catalogue\";\nimport type { SettingsEntry, SettingsGroup } from \"./settings-catalogue\";\n\n/**\n * Which layout the settings page is in. `\"auto\"` follows the screen (`usePhoneLayout()`,\n * as `Tabs` does); `\"desktop\"` / `\"phone\"` pin one — for a preview of both side by side,\n * where the window is one width.\n */\nexport type SettingsLayoutMode = \"auto\" | \"desktop\" | \"phone\";\n\nexport interface UseSettingsRouteOptions<G extends string> {\n groups: readonly SettingsGroup<G>[];\n /** The catalogue's entries. Given, a group with no visible entry is hidden (§4.1), and\n * `/settings?focus=<anchor>` finds the anchor's group. */\n entries?: readonly SettingsEntry<G>[];\n /** `/settings` or `/admin`. */\n basePath: string;\n /** The group `/settings` opens on a desktop. Default: the first visible group (§4.1). */\n defaultGroup?: G;\n /** Retired group ids and their successors — `{ overview: \"metrics\" }` — applied with a\n * replace (§3.1, keksdose's admin). */\n aliases?: Readonly<Partial<Record<string, G>>>;\n /** See {@link SettingsLayoutMode}. Default `\"auto\"`. */\n layout?: SettingsLayoutMode;\n}\n\nexport interface SettingsRoute<G extends string> {\n /** The group on screen. `null` on a phone's list (and while there is no visible group). */\n group: G | null;\n /** The segment after the group, owned by a card; `null` without one. */\n sub: string | null;\n /** `?focus=<anchor>`, or `null`. */\n focus: string | null;\n /** Open a group: a PUSH on a phone, so the system back gesture returns to the list; a\n * REPLACE on a desktop, so back leaves settings instead of walking every group looked\n * at (§3.1, keksdose's rule). */\n select: (group: G) => void;\n /** See {@link SettingsLayoutContextValue.selectSub}. */\n selectSub: (sub: string | null) => void;\n /** To the list: `navigate(-1)` when the list is the previous entry, else a replace to\n * the base path — so a deep link from a mail never sends \"back\" out of the app. */\n back: () => void;\n /** The visible groups, in order. */\n groups: SettingsGroup<G>[];\n /** Whether the phone layout is in force. */\n phone: boolean;\n /** Whether this location is about to be replaced (a legacy hash, an alias, an unknown\n * group): what renders meanwhile is already the destination's, but nothing should\n * act on the URL until it is. */\n redirecting: boolean;\n /** Whether the list is the entry before this one (the marker {@link select} and the\n * list's links leave in `location.state`). */\n fromList: boolean;\n}\n\n/**\n * The `location.state` key that says \"the list is the entry before this one\". Written by\n * the phone list's links and {@link SettingsRoute.select}; carried over by every replace\n * the page makes, since a replace does not change what came before.\n */\nexport const SETTINGS_FROM_LIST_STATE = \"eifi1.settings.fromList\";\n\n/** `location.state` with the marker set — for an app's own link to a group page that is\n * opened FROM the list it renders. */\nexport function settingsFromListState(): Record<string, true> {\n return { [SETTINGS_FROM_LIST_STATE]: true };\n}\n\nfunction isFromList(state: unknown): boolean {\n return typeof state === \"object\" && state !== null && (state as Record<string, unknown>)[SETTINGS_FROM_LIST_STATE] === true;\n}\n\nexport interface SettingsLocation {\n pathname: string;\n search: string;\n hash: string;\n}\n\nexport interface SettingsLocationRules<G extends string> {\n basePath: string;\n /** The visible group ids. */\n groups: readonly G[];\n /** The resolved default group, `null` with no visible group. */\n defaultGroup: G | null;\n aliases?: Readonly<Partial<Record<string, G>>>;\n /** Anchor → group, for `/settings?focus=<anchor>` with no group named. */\n anchorGroup?: ReadonlyMap<string, G>;\n phone: boolean;\n}\n\nexport interface SettingsLocationResult<G extends string> {\n group: G | null;\n sub: string | null;\n /** The path (with query) to replace the location with, or `null` when it is already\n * canonical. */\n redirect: string | null;\n}\n\n/**\n * Where a location lands on the settings page — pure, so the rules of §3.1 are testable\n * without a router:\n *\n * - `/settings` → the default group on a desktop (a replace), the list on a phone;\n * - `/settings/<group>[/<sub>]` → that group, the rest of the path as `sub`;\n * - an alias → its successor, with a replace, `sub`, query and hash kept;\n * - an unknown or hidden group → the default group (desktop) or the list (phone), with\n * a replace and the query kept — never a 404, so an old bookmark still lands;\n * - keksdose's legacy `/settings?<query>#<group>[/<sub>]` →\n * `/settings/<group>[/<sub>]?<query>`, the WHOLE query carried (a tour's\n * `tour`/`tourStep` as well as `focus`), then resolved by the rules above;\n * - `/settings?focus=<anchor>` with no group → the anchor's group, where it can ring.\n */\nexport function resolveSettingsLocation<G extends string>(\n location: SettingsLocation,\n rules: SettingsLocationRules<G>,\n): SettingsLocationResult<G> {\n const base = normalizeSettingsBase(rules.basePath);\n const { pathname, search, hash } = location;\n const known = new Set<string>(rules.groups);\n const atBase = pathname === base || pathname === `${base}/` || (base === \"\" && pathname === \"/\");\n\n if (atBase) {\n const legacy = hash.replace(/^#/, \"\").replace(/^\\/+/, \"\");\n if (legacy) {\n // The hash becomes the path; whatever the path then says (an alias, an unknown\n // group) is resolved in the same step, so the history sees one replace.\n const converted = `${base}/${legacy}`;\n const next = resolveSettingsLocation({ pathname: converted, search, hash: \"\" }, rules);\n return { ...next, redirect: next.redirect ?? `${converted}${search}` };\n }\n const anchor = new URLSearchParams(search).get(\"focus\");\n const anchorGroup = anchor ? rules.anchorGroup?.get(anchor) : undefined;\n if (anchorGroup && known.has(anchorGroup)) {\n return { group: anchorGroup, sub: null, redirect: `${base}/${anchorGroup}${search}` };\n }\n if (rules.phone || rules.defaultGroup === null) return { group: null, sub: null, redirect: null };\n return { group: rules.defaultGroup, sub: null, redirect: `${base}/${rules.defaultGroup}${search}` };\n }\n\n if (!pathname.startsWith(`${base}/`)) {\n // Mounted somewhere `basePath` does not name: a misconfiguration, not a link to\n // correct. Show what the base path would, and leave the address alone.\n return { group: rules.phone ? null : rules.defaultGroup, sub: null, redirect: null };\n }\n const rest = pathname.slice(base.length + 1);\n const [segment = \"\", ...subParts] = rest.split(\"/\");\n const sub = subParts.filter(Boolean).join(\"/\") || null;\n if (known.has(segment)) return { group: segment as G, sub, redirect: null };\n\n const successor = rules.aliases?.[segment];\n if (successor !== undefined && known.has(successor)) {\n return {\n group: successor,\n sub,\n redirect: `${base}/${successor}${sub ? `/${sub}` : \"\"}${search}${hash}`,\n };\n }\n\n // Unknown or hidden. The hash is dropped: on the list it would read as a legacy group\n // and send the page round again.\n if (rules.phone || rules.defaultGroup === null) {\n return { group: null, sub: null, redirect: `${base || \"/\"}${search}` };\n }\n return { group: rules.defaultGroup, sub: null, redirect: `${base}/${rules.defaultGroup}${search}` };\n}\n\n/**\n * The settings page's route (docs/settings-harmonization.md §3.1): which group the URL\n * names, and the history rules for moving between groups.\n *\n * The URL IS the selection, as it was in keksdose (where it was the hash): there is no\n * state to keep in step with it, so a deep link, a reload, back and forward all land\n * where the address says. Everything that is not canonical — `/settings` on a desktop, a\n * legacy hash, an alias, an unknown or hidden group — is replaced by its canonical path.\n *\n * **The replace runs on every navigation**, keyed on `location.key`, not once on mount:\n * the router keeps the page mounted across links followed inside the app — keksdose's\n * inbox renders admin-typed URLs, and a push click routes an open window without a\n * reload — so a legacy `/settings#security` arriving while the page is open converts too\n * (§10.1).\n */\nexport function useSettingsRoute<G extends string>(options: UseSettingsRouteOptions<G>): SettingsRoute<G> {\n const { groups, entries, basePath, defaultGroup, aliases, layout = \"auto\" } = options;\n const location = useLocation();\n const navigate = useNavigate();\n const narrow = usePhoneLayout();\n const phone = layout === \"phone\" || (layout === \"auto\" && narrow);\n const base = normalizeSettingsBase(basePath);\n\n const visible = useMemo(() => visibleSettingsGroups(groups, entries), [groups, entries]);\n const anchorGroup = useMemo(() => {\n const map = new Map<string, G>();\n for (const e of entries ?? []) if (e.visible !== false && !map.has(e.anchor)) map.set(e.anchor, e.group);\n return map;\n }, [entries]);\n const ids = visible.map((g) => g.id);\n const fallback =\n defaultGroup !== undefined && ids.includes(defaultGroup) ? defaultGroup : (ids[0] ?? null);\n\n const { group, sub, redirect } = resolveSettingsLocation(location, {\n basePath: base,\n groups: ids,\n defaultGroup: fallback,\n aliases,\n anchorGroup,\n phone,\n });\n\n const { key, state, search } = location;\n useEffect(() => {\n // `state` rides along: the marker describes the entry BEFORE this one, which a\n // replace does not change.\n if (redirect) navigate(redirect, { replace: true, state });\n }, [key, redirect, state, navigate]);\n\n const select = useCallback(\n (next: G) => {\n const to = `${base}/${next}`;\n // From the list, the push marks it, so the group page's back is a real back.\n if (phone) navigate(to, group === null ? { state: settingsFromListState() } : undefined);\n // A new group is a new page: the old one's query (a focus, a roster filter) stays\n // behind, as keksdose's `selectGroup` left it.\n else navigate(to, { replace: true, state });\n },\n [base, phone, group, state, navigate],\n );\n\n const selectSub = useCallback(\n (next: string | null) => {\n if (group === null) return;\n navigate(`${base}/${group}${next ? `/${next}` : \"\"}${search}`, { replace: true, state });\n },\n [base, group, search, state, navigate],\n );\n\n const fromList = isFromList(state);\n const back = useCallback(() => {\n if (fromList) navigate(-1);\n else navigate(base || \"/\", { replace: true });\n }, [fromList, base, navigate]);\n\n return {\n group,\n sub,\n focus: new URLSearchParams(search).get(\"focus\"),\n select,\n selectSub,\n back,\n groups: visible,\n phone,\n redirecting: redirect !== null,\n fromList,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
import 'react';
|
|
2
|
+
import 'lucide-react';
|
|
3
|
+
import '../theme/text-size.js';
|
|
4
|
+
export { bA as AppShell, bC as AppShellMoreLabels, bD as AppShellNavItem, bE as AppShellProps, bF as AppShellSidebarFooterItem, bG as AppShellSubItem, co as DEFAULT_APP_SHELL_MORE_LABELS, cG as DEFAULT_MOBILE_BAR_MAX, fZ as splitMobileBar } from '../delete-account-setting-FLyJf-kZ.js';
|
|
5
|
+
import 'zustand/middleware';
|
|
6
|
+
import 'zustand';
|
|
5
7
|
import '../components/tooltip.js';
|
|
6
8
|
import '../hooks/use-anchored-rect.js';
|
|
7
9
|
import '../lib/clipping.js';
|
|
8
|
-
import '../data-table-labels-
|
|
10
|
+
import '../data-table-labels-kpAAVi6g.js';
|
|
9
11
|
import '../components/data-table-sort.js';
|
|
10
12
|
import '../components/swipeable-row.js';
|
|
11
13
|
import '../components/mini-calendar.js';
|
|
12
14
|
import '../components/calendar-heatmap.js';
|
|
13
15
|
import '../components/popover.js';
|
|
16
|
+
import '../hooks/use-breakpoint.js';
|
|
14
17
|
import '../components/chip.js';
|
|
18
|
+
import '../components/field-parts.js';
|
|
15
19
|
import '../components/field-sync.js';
|
|
16
20
|
import '../wizard/types.js';
|
|
17
21
|
import '../tour/tour.js';
|
|
@@ -61,8 +65,6 @@ import './topbar-controls.js';
|
|
|
61
65
|
import '../theme/palette-presets.js';
|
|
62
66
|
import '../theme/chart-palette.js';
|
|
63
67
|
import '../theme/theme-store.js';
|
|
64
|
-
import 'zustand/middleware';
|
|
65
|
-
import 'zustand';
|
|
66
68
|
import '../auth/complete-name-dialog.js';
|
|
67
69
|
import '../auth/forgot-password-form.js';
|
|
68
70
|
import '../auth/status-parts.js';
|
|
@@ -80,6 +82,8 @@ import '../admin/admin-parts.js';
|
|
|
80
82
|
import '../admin/admin-action-log.js';
|
|
81
83
|
import '../settings/settings-labels.js';
|
|
82
84
|
import '../demo/demo-labels.js';
|
|
85
|
+
import '../components/appearance-labels.js';
|
|
86
|
+
import '../theme/contrast.js';
|
|
83
87
|
import '../components/reauth-dialog.js';
|
|
84
88
|
import '../components/server-wake.js';
|
|
85
89
|
import '../lib/server-wake.js';
|
|
@@ -93,167 +97,4 @@ import '../components/sign-chip.js';
|
|
|
93
97
|
import '../components/column-mapper.js';
|
|
94
98
|
import '../lib/column-mapping.js';
|
|
95
99
|
import '../lib/table-text.js';
|
|
96
|
-
import '../components/
|
|
97
|
-
|
|
98
|
-
interface AppShellSubItem {
|
|
99
|
-
to: string;
|
|
100
|
-
label: string;
|
|
101
|
-
icon: LucideIcon;
|
|
102
|
-
/** Passed to NavLink's `end` (exact match). Defaults to true. */
|
|
103
|
-
end?: boolean;
|
|
104
|
-
}
|
|
105
|
-
interface AppShellNavItem {
|
|
106
|
-
to: string;
|
|
107
|
-
label: string;
|
|
108
|
-
/** Optional shorter label for the MOBILE BOTTOM BAR only (the sidebar, its
|
|
109
|
-
* tooltips and any palette built on this list keep `label`).
|
|
110
|
-
*
|
|
111
|
-
* The bar divides the viewport into `nav.length` equal cells, so a label's room
|
|
112
|
-
* shrinks with every entry added — at six entries on a 406 px phone that is ~67 px,
|
|
113
|
-
* and "Import & Export" wrapped to two lines and pushed the row taller than its
|
|
114
|
-
* neighbours (keksdose feedback live #210). A per-item override is better than
|
|
115
|
-
* shortening the real label, which the sidebar has ample room for and which is the
|
|
116
|
-
* name the rest of the product uses. */
|
|
117
|
-
shortLabel?: string;
|
|
118
|
-
/** 0.31.0: leave this entry out of the MOBILE BOTTOM BAR; the sidebar keeps it.
|
|
119
|
-
* For an entry a phone reaches another way — the brand link to the start page —
|
|
120
|
-
* when one cell fewer is what keeps the others' labels whole (the bar divides its
|
|
121
|
-
* width evenly, see `shortLabel`). Its group's sub-row still shows while one of its
|
|
122
|
-
* pages is open. Named like `DataTable`'s column `mobileHidden`. */
|
|
123
|
-
mobileHidden?: boolean;
|
|
124
|
-
icon: LucideIcon;
|
|
125
|
-
/** Passed to NavLink's `end` (exact match). Defaults to true. */
|
|
126
|
-
end?: boolean;
|
|
127
|
-
/** Optional `data-tour` value rendered on this item's link (both the desktop
|
|
128
|
-
* sidebar and the mobile bottom bar), so a guided tour can spotlight ONE nav
|
|
129
|
-
* entry precisely instead of matching by href (feedback #338). */
|
|
130
|
-
dataTour?: string;
|
|
131
|
-
/** Optional sub-items. When present, hovering (or focusing) the item opens a
|
|
132
|
-
* flyout to the right of the sidebar with a header (this item's label) and the
|
|
133
|
-
* sub-items as icon + title links. Works collapsed or expanded; the mobile
|
|
134
|
-
* bottom bar ignores sub-items and links to `to`. */
|
|
135
|
-
items?: AppShellSubItem[];
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* One entry of the sidebar's bottom area ({@link AppShellProps.sidebarFooterItems}) —
|
|
139
|
-
* the version link and the legal links kastlan hand-built in `sidebarFooter`.
|
|
140
|
-
*
|
|
141
|
-
* - `link` (default): one link. Expanded it shows `icon` + `text` (default `label`),
|
|
142
|
-
* with `hint` as a tooltip if given; collapsed it is the icon alone, named and
|
|
143
|
-
* tooltipped by `label`.
|
|
144
|
-
* - `links`: a small group — the legal links. Expanded, a `<nav>` named `label` holding
|
|
145
|
-
* the links as a wrapping row of small text; collapsed, ONE icon link to `to` (default
|
|
146
|
-
* the first link's), named and tooltipped by `label`, since a column of icons for
|
|
147
|
-
* "Imprint", "Privacy" and "Terms" would say nothing an icon can.
|
|
148
|
-
*/
|
|
149
|
-
type AppShellSidebarFooterItem = {
|
|
150
|
-
kind?: "link";
|
|
151
|
-
key: string;
|
|
152
|
-
to: string;
|
|
153
|
-
icon: LucideIcon;
|
|
154
|
-
/** The link's name, and the tooltip when collapsed ("What's new · v1.4.0"). */
|
|
155
|
-
label: string;
|
|
156
|
-
/** Visible text when expanded ("Kastlan v1.4.0"). Default `label`. */
|
|
157
|
-
text?: ReactNode;
|
|
158
|
-
/** A tooltip on the expanded link ("What's new"). */
|
|
159
|
-
hint?: string;
|
|
160
|
-
/** Leaves the app: a plain `<a target="_blank" rel="noopener noreferrer">`. */
|
|
161
|
-
external?: boolean;
|
|
162
|
-
} | {
|
|
163
|
-
kind: "links";
|
|
164
|
-
key: string;
|
|
165
|
-
icon: LucideIcon;
|
|
166
|
-
/** The group's name — its `<nav>` label, and the collapsed icon's name ("Legal"). */
|
|
167
|
-
label: string;
|
|
168
|
-
/** The collapsed icon's target. Default the first link's `to`. */
|
|
169
|
-
to?: string;
|
|
170
|
-
links: {
|
|
171
|
-
to: string;
|
|
172
|
-
label: string;
|
|
173
|
-
external?: boolean;
|
|
174
|
-
}[];
|
|
175
|
-
};
|
|
176
|
-
/**
|
|
177
|
-
* Exported and `<div>`-shaped. This is the outermost element of every consuming app, so
|
|
178
|
-
* there is nothing above it to hang an id, a landmark label or a `data-tour` anchor on —
|
|
179
|
-
* and nothing a consumer can wrap it in either, since the root owns the `h-dvh` and
|
|
180
|
-
* `overflow-hidden` the whole layout is built on (audit §api-design).
|
|
181
|
-
*/
|
|
182
|
-
interface AppShellProps extends ComponentPropsWithoutRef<"div"> {
|
|
183
|
-
/** Navigation entries — shared by the desktop sidebar and mobile bottom bar. */
|
|
184
|
-
nav: AppShellNavItem[];
|
|
185
|
-
/** The composed top bar (e.g. the shared TopBar with app-owned actions). */
|
|
186
|
-
topBar: ReactNode;
|
|
187
|
-
/** Main content — typically the router `<Outlet />`. */
|
|
188
|
-
children: ReactNode;
|
|
189
|
-
/** Desktop-only footer rendered below the content (hidden on mobile). */
|
|
190
|
-
footer?: ReactNode;
|
|
191
|
-
/** Extra sidebar content above the collapse toggle (e.g. a version link).
|
|
192
|
-
* Receives the collapsed state so it can render compact vs. full. */
|
|
193
|
-
sidebarFooter?: (collapsed: boolean) => ReactNode;
|
|
194
|
-
/** The sidebar's bottom area as data — a version link, the legal links — drawn full
|
|
195
|
-
* when expanded and as icon + tooltip when collapsed. Rendered above
|
|
196
|
-
* `sidebarFooter`, which stays for anything the data cannot describe. */
|
|
197
|
-
sidebarFooterItems?: AppShellSidebarFooterItem[];
|
|
198
|
-
/** The router link for `sidebarFooterItems`. Default: the provider's
|
|
199
|
-
* `linkComponent`, else react-router's `Link`. */
|
|
200
|
-
renderLink?: KitLinkComponent;
|
|
201
|
-
/** localStorage key for the persisted collapse state. */
|
|
202
|
-
collapseStorageKey?: string;
|
|
203
|
-
/** Default: `appShell.collapse` from the {@link UiKitProvider}, else English. */
|
|
204
|
-
collapseLabel?: string;
|
|
205
|
-
/** Default: `appShell.expand` from the {@link UiKitProvider}, else English. */
|
|
206
|
-
expandLabel?: string;
|
|
207
|
-
/** How an entry's `items` are revealed in the EXPANDED desktop sidebar.
|
|
208
|
-
*
|
|
209
|
-
* - `"flyout"` (default) — a panel beside the sidebar on hover/focus. Compact: the
|
|
210
|
-
* sidebar stays one row per group however many pages a group holds.
|
|
211
|
-
* - `"inline"` — a disclosure under the entry, the pattern of MUI's nested List and
|
|
212
|
-
* most documentation sites. Every page is visible at once, and the group holding
|
|
213
|
-
* the current page opens on its own, so the reader can see where they are without
|
|
214
|
-
* hovering anything.
|
|
215
|
-
*
|
|
216
|
-
* The icon-only (collapsed) sidebar always uses the flyout: an indented list has no
|
|
217
|
-
* room there. The mobile bottom bar ignores `items` in both modes. */
|
|
218
|
-
subNav?: "flyout" | "inline";
|
|
219
|
-
/** Accessible name of the inline disclosure button, given the group's label.
|
|
220
|
-
* Only used with `subNav="inline"`. Its expanded state is carried by
|
|
221
|
-
* `aria-expanded`, so one wording serves both directions. Default:
|
|
222
|
-
* `appShell.toggleGroup` from the {@link UiKitProvider}, else English. */
|
|
223
|
-
toggleGroupLabel?: (groupLabel: string) => string;
|
|
224
|
-
/** Below `md`: show the current group's `items` as a scrollable row above the
|
|
225
|
-
* bottom bar — the phone's counterpart of the sidebar's second level. Default true;
|
|
226
|
-
* `false` keeps the single bar, where a group entry only links to its own `to`. */
|
|
227
|
-
mobileSubNav?: boolean;
|
|
228
|
-
/**
|
|
229
|
-
* How the phone sub-nav lays out a group with more pages than fit on one row.
|
|
230
|
-
* `"wrap"` (default): every page in view, on as many rows as it takes — the bar
|
|
231
|
-
* grows. `"scroll"`: one row that scrolls sideways, faded at a cut edge, with the
|
|
232
|
-
* current page scrolled into view. A long group took four rows (~130px) of a 390px
|
|
233
|
-
* screen above the bottom bar (showcase audit); scrolling gives that back at the
|
|
234
|
-
* price of pages hidden past the edge, so it is the app's call, per shell.
|
|
235
|
-
*/
|
|
236
|
-
mobileSubNavLayout?: "wrap" | "scroll";
|
|
237
|
-
/**
|
|
238
|
-
* Lay the shell out inside its PARENT'S box instead of the viewport: `h-full` rather
|
|
239
|
-
* than `h-dvh`, its own scroll container at every width, a bottom bar pinned to the
|
|
240
|
-
* shell rather than the window, `--app-nav-h` published on the shell alone, and no
|
|
241
|
-
* persisted collapse state unless `collapseStorageKey` is passed. For an app preview,
|
|
242
|
-
* a device frame, a docs example.
|
|
243
|
-
*
|
|
244
|
-
* Implied for a shell rendered inside another AppShell, which is the case this
|
|
245
|
-
* exists for: the inner one used to publish its own nav height on `<html>`, fight
|
|
246
|
-
* the outer one over it, and remove it on unmount from under the outer app.
|
|
247
|
-
*/
|
|
248
|
-
embedded?: boolean;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* The responsive application shell: a top bar, a collapsible desktop sidebar
|
|
252
|
-
* (icon-only when collapsed, with tooltips), a mobile bottom nav bar, the main
|
|
253
|
-
* content area, and an optional desktop footer. Router-aware via react-router
|
|
254
|
-
* `NavLink`; domain-free — nav items, brand, footer and the top bar's actions
|
|
255
|
-
* are all supplied by the app.
|
|
256
|
-
*/
|
|
257
|
-
declare function AppShell({ nav, topBar, children, footer, sidebarFooter, sidebarFooterItems, renderLink, collapseStorageKey: collapseStorageKeyProp, collapseLabel, expandLabel, subNav, mobileSubNav, mobileSubNavLayout, toggleGroupLabel, embedded: embeddedProp, className, ...rest }: AppShellProps): react.JSX.Element;
|
|
258
|
-
|
|
259
|
-
export { AppShell, type AppShellNavItem, type AppShellProps, type AppShellSidebarFooterItem, type AppShellSubItem };
|
|
100
|
+
import '../components/focus-ring.js';
|