@eifi1/ui-kit 0.31.1 → 0.32.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 +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/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 +74 -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/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 +4 -3
- 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 +32 -0
- package/dist/components/appearance-labels.js +18 -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/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 +47 -21
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +1 -1
- package/dist/components/choice-card.js +8 -7
- 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 +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 +7 -5
- 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 +6 -3
- 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 +9 -5
- 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 +2 -2
- 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 +165 -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 +51 -6
- package/dist/components/settings-fields.js +37 -0
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.d.ts +11 -6
- package/dist/components/share-card.js +2 -0
- 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/swipeable-row.js +2 -1
- 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 +16 -1
- 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-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 +202 -114
- 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/{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-BvdQdbv3.d.ts} +605 -58
- 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 +77 -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 +89 -2
- 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 +84 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +8 -3
- package/dist/i18n/locales/fr.js +86 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +8 -3
- package/dist/i18n/locales/hu.js +86 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +8 -3
- package/dist/i18n/locales/it.js +87 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +8 -3
- package/dist/i18n/locales/zh.js +84 -2
- 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 +75 -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/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 +109 -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 +157 -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.d.ts +8 -3
- package/package.json +1 -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 +224 -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/components/account-chips.tsx +36 -10
- package/src/components/alert-banner.tsx +11 -6
- package/src/components/amount-input.tsx +5 -5
- package/src/components/appearance-labels.ts +40 -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/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 +105 -24
- package/src/components/choice-card.tsx +15 -10
- 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.tsx +160 -43
- package/src/components/date-picker.tsx +12 -9
- 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 +6 -3
- 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 +9 -5
- 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 +2 -2
- package/src/components/pie-chart.tsx +19 -9
- package/src/components/popover.tsx +22 -5
- package/src/components/row-actions.tsx +344 -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 +81 -2
- package/src/components/share-card.tsx +10 -0
- 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/swipeable-row.tsx +2 -1
- 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 +99 -12
- package/src/components/toggle-legend.tsx +53 -8
- package/src/components/translation-review.tsx +10 -5
- package/src/components/tree-view.tsx +3 -2
- package/src/components/treemap.tsx +27 -10
- package/src/components/ui.tsx +331 -145
- package/src/components/user-avatar.tsx +1 -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 +121 -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 +103 -0
- package/src/i18n/kit-labels.tsx +12 -0
- package/src/i18n/locales/es.ts +94 -0
- package/src/i18n/locales/fr.ts +98 -0
- package/src/i18n/locales/hu.ts +96 -0
- package/src/i18n/locales/it.ts +99 -0
- package/src/i18n/locales/zh.ts +90 -0
- package/src/i18n/review.ts +14 -0
- package/src/index.ts +74 -6
- 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 +224 -0
- package/src/theme/palette-presets.ts +24 -4
- package/src/theme/palette-store.ts +6 -1
- package/src/theme/text-size.ts +267 -0
- package/tokens.css +143 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/theme/text-size.ts"],"sourcesContent":["import { useEffect, useMemo, useSyncExternalStore } from \"react\";\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { logger } from \"../lib/logger\";\nimport { readStored } from \"../lib/safe-storage\";\n\n/**\n * The text size (docs/text-size-harmonization.md §2.1, §3.1, §10.3, §10.6).\n *\n * ONE scale on `<html>`: `data-text-size=\"normal|large|xlarge\"`, and `tokens.css` turns\n * it into `font-size: 100% / 125% / 150%` — percent, never px, so \"Normal\" keeps\n * whatever size the person set in the browser. Tailwind's spacing and type are rem, so\n * about 90 % of every screen grows with that one declaration; the breakpoints follow too\n * (tokens.css redefines `sm:` … `3xl:` per size, {@link useBreakpoint} answers the same\n * in JS).\n *\n * The names below are the mechanism's public vocabulary and are FROZEN: an app's\n * storage, its `/auth/me` column (server-kit's `TEXT_SIZES`) and the inline boot snippet\n * all spell them this way.\n */\n\n/** The three steps, in order (§2.1, §10.6): server-kit's `TEXT_SIZES`, value for value. */\nexport const TEXT_SIZES = [\"normal\", \"large\", \"xlarge\"] as const;\n\nexport type TextSize = (typeof TEXT_SIZES)[number];\n\n/** The root font size each step sets, as a factor of the browser's own (tokens.css). For\n * what CSS cannot reach: a canvas or SVG chart's px constants, a windowed list's row\n * estimate, a popover width in px (§3.2). */\nexport const TEXT_SCALE: Readonly<Record<TextSize, number>> = { normal: 1, large: 1.25, xlarge: 1.5 };\n\n/** The attribute on `<html>` that carries the size. */\nconst TEXT_SIZE_ATTRIBUTE = \"data-text-size\";\n\nexport function isTextSize(value: unknown): value is TextSize {\n return value === \"normal\" || value === \"large\" || value === \"xlarge\";\n}\n\n/**\n * The size in force: **this device's own choice → the account's → Normal** (§2.2, §2.6).\n * Each is taken only when it is one of {@link TEXT_SIZES}; anything else (a stale value,\n * `null` = never chosen) falls through to the next. The one order the pre-paint call, the\n * apply hook and `useAccountAppearance` all use, so the three can never disagree.\n */\nexport function resolveTextSize(device: unknown, account?: unknown): TextSize {\n if (isTextSize(device)) return device;\n if (isTextSize(account)) return account;\n return \"normal\";\n}\n\n/* ── <html data-text-size> and the subscribers that read it ─────────────── */\n\nconst listeners = new Set<() => void>();\nlet observer: MutationObserver | null = null;\n\nfunction notify(): void {\n for (const listener of listeners) listener();\n}\n\nfunction subscribe(listener: () => void): () => void {\n listeners.add(listener);\n // Also catches writers that are not this module — the inline boot snippet, an app\n // setting the attribute by hand, a test.\n if (!observer && typeof MutationObserver !== \"undefined\" && typeof document !== \"undefined\") {\n observer = new MutationObserver(notify);\n observer.observe(document.documentElement, { attributes: true, attributeFilter: [TEXT_SIZE_ATTRIBUTE] });\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) {\n observer?.disconnect();\n observer = null;\n }\n };\n}\n\n/** The size `<html>` carries now; Normal without one (or outside a browser). */\nexport function readTextSize(): TextSize {\n if (typeof document === \"undefined\") return \"normal\";\n const value = document.documentElement.getAttribute(TEXT_SIZE_ATTRIBUTE);\n return isTextSize(value) ? value : \"normal\";\n}\n\n/** Put `size` on `<html>`. Every {@link useTextSize} re-renders with it. */\nexport function applyTextSize(size: TextSize): void {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n if (root.getAttribute(TEXT_SIZE_ATTRIBUTE) === size) return;\n root.setAttribute(TEXT_SIZE_ATTRIBUTE, size);\n notify();\n}\n\nexport interface TextSizeInfo {\n size: TextSize;\n /** {@link TEXT_SCALE} for `size`: 1, 1.25 or 1.5. */\n scale: number;\n}\n\n/**\n * The size in force on this page, and its scale (§3.1) — read from `<html>`, so it needs\n * no store and works in every kit component: whoever applied the size (the app's store,\n * the pre-paint call, the inline snippet), this follows it.\n *\n * Only for what CSS cannot do. A class that should differ at Large is a `large:`\n * variant; a layout switch is a breakpoint, which already scales.\n */\nexport function useTextSize(): TextSizeInfo {\n const size = useSyncExternalStore(subscribe, readTextSize, () => \"normal\" as const);\n return useMemo(() => ({ size, scale: TEXT_SCALE[size] }), [size]);\n}\n\n/* ── Storage: the frozen format, the pre-paint call ─────────────────────── */\n\n/**\n * What a text-size store persists under its key — zustand's persist envelope, and FROZEN\n * (§10.3) because an app's inline `<head>` snippet reads it before any module loads:\n *\n * {\"state\":{\"size\":\"large\"},\"version\":1}\n *\n * `size` is one of {@link TEXT_SIZES}, or `null` / absent when this device never chose.\n * A future change adds a key; it never renames `state.size` or changes its values.\n */\nexport interface PersistedTextSize {\n state?: { size?: TextSize | null };\n version?: number;\n}\n\n/** This device's own choice under `storageKey`, or `null` when it never chose (or the\n * storage is blocked, malformed, or holds a value that is not a size). */\nexport function readPersistedTextSize(storageKey: string): TextSize | null {\n try {\n const blob = JSON.parse(readStored(storageKey) ?? \"null\") as PersistedTextSize | null;\n const size = blob?.state?.size;\n return isTextSize(size) ? size : null;\n } catch {\n return null;\n }\n}\n\nexport interface PersistedAppearanceOptions {\n /**\n * The account's last known value, for a device without a choice of its own (§10.3):\n * what the app persisted with its user (keksdose keeps `/auth/me` in localStorage), so\n * a cold boot paints the account's size instead of Normal and then jumping — offline\n * too. Called once, guarded: a throw counts as \"no account value\".\n */\n account?: () => string | null | undefined;\n}\n\nfunction readAccount(account: PersistedAppearanceOptions[\"account\"]): unknown {\n if (!account) return undefined;\n try {\n return account();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Pre-paint (§3.1, §10.3): read this device's choice under `storageKey` — else the\n * account's last known value — and put it on `<html>` BEFORE `createRoot`, next to\n * `applyPersistedTheme`, so the first paint already has the right size. Returns the size\n * applied.\n *\n * ```ts\n * applyPersistedTextSize(\"keksdose-text-size\", {\n * account: () => readPersistedUser()?.text_size,\n * });\n * ```\n */\nexport function applyPersistedTextSize(storageKey: string, options: PersistedAppearanceOptions = {}): TextSize {\n const size = resolveTextSize(readPersistedTextSize(storageKey), readAccount(options.account));\n applyTextSize(size);\n return size;\n}\n\n/**\n * The same pre-paint as {@link applyPersistedTextSize}, as an inline `<head>` script for\n * a boot splash painted from `index.html` before any module runs (§10.3). A function\n * EXPRESSION: call it with the storage key and, optionally, a function returning the\n * account's last known value.\n *\n * ```html\n * <script>\n * (function(k,a){…})(\"keksdose-text-size\", function () {\n * return JSON.parse(localStorage.getItem(\"keksdose-auth\")).state.user.text_size;\n * });\n * </script>\n * ```\n *\n * Paste the constant's value in place of `(function(k,a){…})`. It reads the frozen\n * {@link PersistedTextSize} format, never throws, and sets only `data-text-size`; the\n * splash itself sizes in rem so the attribute reaches it (tokens.css, or the splash's own\n * `html[data-text-size=large]{font-size:125%}` if it paints before the stylesheet).\n */\nexport const TEXT_SIZE_INLINE_SCRIPT =\n \"(function(k,a){function ok(v){return v==='normal'||v==='large'||v==='xlarge'}\" +\n \"var v=null;try{var s=JSON.parse(localStorage.getItem(k)||'null');v=s&&s.state&&s.state.size}catch(e){}\" +\n \"if(!ok(v)){v=null;try{v=a?a():null}catch(e){}}\" +\n \"try{document.documentElement.setAttribute('data-text-size',ok(v)?v:'normal')}catch(e){}})\";\n\n/* ── The store ──────────────────────────────────────────────────────────── */\n\nexport interface TextSizeState {\n /** This device's own choice (persisted), or `null` when it never chose — then the\n * account's value applies (§2.2). */\n size: TextSize | null;\n /** Store this device's choice; `null` forgets it, so the account's applies again. */\n setSize: (size: TextSize | null) => void;\n}\n\nexport interface ApplyTextSizeOptions {\n /** The account's value (`/auth/me`'s `text_size`): it applies while this device has no\n * choice of its own. Leave it out where there is no account. */\n account?: string | null;\n}\n\n/**\n * Build a text-size store bound to a consumer-supplied localStorage key, shaped like\n * `createThemeStore` (§6). It holds the DEVICE's choice only; the account's value comes\n * in through `useApplyTextSize({ account })` and `useAccountAppearance`, and the size in\n * force is always {@link resolveTextSize} of the two.\n *\n * - `useTextSizeStore` — the zustand hook: `size` (device choice or null) and `setSize`.\n * - `useApplyTextSize({ account })` — the effect that puts the size in force on `<html>`.\n * Mount it once near the root, beside `useApplyTheme`.\n *\n * The persisted format is {@link PersistedTextSize}, and frozen.\n */\nexport function createTextSizeStore(storageKey: string) {\n const useTextSizeStore = create<TextSizeState>()(\n logger(\n persist(\n (set) => ({\n size: null,\n setSize: (size) => set({ size: isTextSize(size) ? size : null }),\n }),\n {\n name: storageKey,\n version: 1,\n partialize: (s) => ({ size: s.size }),\n // A hand-edited or future value must not reach <html> as an unknown size.\n merge: (persisted, current) => ({\n ...current,\n size: isTextSize((persisted as { size?: unknown } | null)?.size)\n ? ((persisted as { size: TextSize }).size)\n : null,\n }),\n },\n ),\n \"text-size\",\n ),\n );\n\n function useApplyTextSize({ account }: ApplyTextSizeOptions = {}): TextSize {\n const device = useTextSizeStore((s) => s.size);\n const size = resolveTextSize(device, account);\n useEffect(() => {\n applyTextSize(size);\n }, [size]);\n return size;\n }\n\n return { useTextSizeStore, useApplyTextSize };\n}\n\nexport type TextSizeStore = ReturnType<typeof createTextSizeStore>;\n"],"mappings":";AAAA,SAAS,WAAW,SAAS,4BAA4B;AACzD,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAkBpB,MAAM,aAAa,CAAC,UAAU,SAAS,QAAQ;AAO/C,MAAM,aAAiD,EAAE,QAAQ,GAAG,OAAO,MAAM,QAAQ,IAAI;AAGpG,MAAM,sBAAsB;AAErB,SAAS,WAAW,OAAmC;AAC5D,SAAO,UAAU,YAAY,UAAU,WAAW,UAAU;AAC9D;AAQO,SAAS,gBAAgB,QAAiB,SAA6B;AAC5E,MAAI,WAAW,MAAM,EAAG,QAAO;AAC/B,MAAI,WAAW,OAAO,EAAG,QAAO;AAChC,SAAO;AACT;AAIA,MAAM,YAAY,oBAAI,IAAgB;AACtC,IAAI,WAAoC;AAExC,SAAS,SAAe;AACtB,aAAW,YAAY,UAAW,UAAS;AAC7C;AAEA,SAAS,UAAU,UAAkC;AACnD,YAAU,IAAI,QAAQ;AAGtB,MAAI,CAAC,YAAY,OAAO,qBAAqB,eAAe,OAAO,aAAa,aAAa;AAC3F,eAAW,IAAI,iBAAiB,MAAM;AACtC,aAAS,QAAQ,SAAS,iBAAiB,EAAE,YAAY,MAAM,iBAAiB,CAAC,mBAAmB,EAAE,CAAC;AAAA,EACzG;AACA,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AACzB,QAAI,UAAU,SAAS,GAAG;AACxB,gBAAU,WAAW;AACrB,iBAAW;AAAA,IACb;AAAA,EACF;AACF;AAGO,SAAS,eAAyB;AACvC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,QAAQ,SAAS,gBAAgB,aAAa,mBAAmB;AACvE,SAAO,WAAW,KAAK,IAAI,QAAQ;AACrC;AAGO,SAAS,cAAc,MAAsB;AAClD,MAAI,OAAO,aAAa,YAAa;AACrC,QAAM,OAAO,SAAS;AACtB,MAAI,KAAK,aAAa,mBAAmB,MAAM,KAAM;AACrD,OAAK,aAAa,qBAAqB,IAAI;AAC3C,SAAO;AACT;AAgBO,SAAS,cAA4B;AAC1C,QAAM,OAAO,qBAAqB,WAAW,cAAc,MAAM,QAAiB;AAClF,SAAO,QAAQ,OAAO,EAAE,MAAM,OAAO,WAAW,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;AAClE;AAoBO,SAAS,sBAAsB,YAAqC;AACzE,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,WAAW,UAAU,KAAK,MAAM;AACxD,UAAM,OAAO,MAAM,OAAO;AAC1B,WAAO,WAAW,IAAI,IAAI,OAAO;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYA,SAAS,YAAY,SAAyD;AAC5E,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,QAAQ;AAAA,EACjB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcO,SAAS,uBAAuB,YAAoB,UAAsC,CAAC,GAAa;AAC7G,QAAM,OAAO,gBAAgB,sBAAsB,UAAU,GAAG,YAAY,QAAQ,OAAO,CAAC;AAC5F,gBAAc,IAAI;AAClB,SAAO;AACT;AAqBO,MAAM,0BACX;AAiCK,SAAS,oBAAoB,YAAoB;AACtD,QAAM,mBAAmB,OAAsB;AAAA,IAC7C;AAAA,MACE;AAAA,QACE,CAAC,SAAS;AAAA,UACR,MAAM;AAAA,UACN,SAAS,CAAC,SAAS,IAAI,EAAE,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,CAAC;AAAA,QACjE;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,UACT,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK;AAAA;AAAA,UAEnC,OAAO,CAAC,WAAW,aAAa;AAAA,YAC9B,GAAG;AAAA,YACH,MAAM,WAAY,WAAyC,IAAI,IACzD,UAAiC,OACnC;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,WAAS,iBAAiB,EAAE,QAAQ,IAA0B,CAAC,GAAa;AAC1E,UAAM,SAAS,iBAAiB,CAAC,MAAM,EAAE,IAAI;AAC7C,UAAM,OAAO,gBAAgB,QAAQ,OAAO;AAC5C,cAAU,MAAM;AACd,oBAAc,IAAI;AAAA,IACpB,GAAG,CAAC,IAAI,CAAC;AACT,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,kBAAkB,iBAAiB;AAC9C;","names":[]}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { bO as ButtonVariant } from '../delete-account-setting-BvdQdbv3.js';
|
|
4
4
|
import { UseWizardReturn, WizardLabels } from './types.js';
|
|
5
5
|
import '../components/tooltip.js';
|
|
6
6
|
import '../hooks/use-anchored-rect.js';
|
|
7
7
|
import '../lib/clipping.js';
|
|
8
|
-
import '../data-table-labels-
|
|
8
|
+
import '../data-table-labels-kpAAVi6g.js';
|
|
9
9
|
import '../components/data-table-sort.js';
|
|
10
10
|
import '../components/swipeable-row.js';
|
|
11
11
|
import '../components/mini-calendar.js';
|
|
12
12
|
import '../components/calendar-heatmap.js';
|
|
13
13
|
import '../components/popover.js';
|
|
14
|
+
import '../hooks/use-breakpoint.js';
|
|
14
15
|
import '../components/chip.js';
|
|
15
16
|
import 'lucide-react';
|
|
17
|
+
import '../components/field-parts.js';
|
|
16
18
|
import '../components/field-sync.js';
|
|
17
19
|
import '../tour/tour.js';
|
|
18
20
|
import '../search/command-palette.js';
|
|
@@ -80,6 +82,9 @@ 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';
|
|
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
|
/**
|
|
99
104
|
* The chrome around a wizard: the step indicator, the step content, and the
|
package/dist/wizard.d.ts
CHANGED
|
@@ -6,18 +6,20 @@ 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-BvdQdbv3.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-kpAAVi6g.js';
|
|
14
14
|
import './components/data-table-sort.js';
|
|
15
15
|
import './components/swipeable-row.js';
|
|
16
16
|
import './components/mini-calendar.js';
|
|
17
17
|
import './components/calendar-heatmap.js';
|
|
18
18
|
import './components/popover.js';
|
|
19
|
+
import './hooks/use-breakpoint.js';
|
|
19
20
|
import './components/chip.js';
|
|
20
21
|
import 'lucide-react';
|
|
22
|
+
import './components/field-parts.js';
|
|
21
23
|
import './components/field-sync.js';
|
|
22
24
|
import './tour/tour.js';
|
|
23
25
|
import './search/command-palette.js';
|
|
@@ -85,6 +87,9 @@ import './admin/admin-parts.js';
|
|
|
85
87
|
import './admin/admin-action-log.js';
|
|
86
88
|
import './settings/settings-labels.js';
|
|
87
89
|
import './demo/demo-labels.js';
|
|
90
|
+
import './components/appearance-labels.js';
|
|
91
|
+
import './theme/contrast.js';
|
|
92
|
+
import './theme/text-size.js';
|
|
88
93
|
import './components/reauth-dialog.js';
|
|
89
94
|
import './components/server-wake.js';
|
|
90
95
|
import './lib/server-wake.js';
|
|
@@ -98,4 +103,4 @@ import './components/sign-chip.js';
|
|
|
98
103
|
import './components/column-mapper.js';
|
|
99
104
|
import './lib/column-mapping.js';
|
|
100
105
|
import './lib/table-text.js';
|
|
101
|
-
import './components/
|
|
106
|
+
import './components/focus-ring.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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.",
|
|
@@ -14,7 +14,8 @@ import type { ChipTone } from "../components/chip";
|
|
|
14
14
|
import { CopyButton } from "../components/copy-button";
|
|
15
15
|
import { LanguageSelect } from "../components/language-select";
|
|
16
16
|
import { SectionLabel, Caption } from "../components/text";
|
|
17
|
-
import {
|
|
17
|
+
import { RowActions } from "../components/row-actions";
|
|
18
|
+
import { Button, Input, Select, Spinner } from "../components/ui";
|
|
18
19
|
import { useWriteLock } from "../components/write-lock";
|
|
19
20
|
import { hasMessage, settle, useDayText, usePersonLabel } from "./admin-parts";
|
|
20
21
|
import type { AdminPerson, MaybePromise } from "./admin-parts";
|
|
@@ -480,35 +481,41 @@ export function InvitationsPanel<R extends string = string>({
|
|
|
480
481
|
</div>
|
|
481
482
|
{(link || resendable || revocable) && (
|
|
482
483
|
<div className="ms-auto flex shrink-0 items-center gap-1">
|
|
484
|
+
{/* The copy stays its own icon: it says "Copied" where it was
|
|
485
|
+
pressed, which a menu entry that closes cannot. */}
|
|
483
486
|
{link && open && <CopyButton size="xs" tone="muted" text={link} label={labels.copyLink} />}
|
|
484
|
-
{
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
487
|
+
{/* Resend and revoke are the row's actions (docs/text-size-
|
|
488
|
+
harmonization.md §10.8): two icons at Normal, one "⋯" menu at
|
|
489
|
+
Large, where both names as text ("Send … a new link") would
|
|
490
|
+
not fit a phone's row beside the copy icon. */}
|
|
491
|
+
<RowActions
|
|
492
|
+
name={invitation.email}
|
|
493
|
+
actions={[
|
|
494
|
+
resendable && {
|
|
495
|
+
key: "resend",
|
|
496
|
+
label: labels.resend(invitation.email),
|
|
497
|
+
icon: RefreshCw,
|
|
498
|
+
commit: true,
|
|
499
|
+
pending: busy === `resend:${key}`,
|
|
500
|
+
disabled: busy !== null,
|
|
501
|
+
onSelect: () =>
|
|
502
|
+
void (async () => {
|
|
503
|
+
const answer = await run(`resend:${key}`, () => onResend(invitation));
|
|
504
|
+
if (answer !== FAILED) took(answer, invitation.email, invitation.id);
|
|
505
|
+
})(),
|
|
506
|
+
},
|
|
507
|
+
revocable && {
|
|
508
|
+
key: "revoke",
|
|
509
|
+
label: labels.revoke(invitation.email),
|
|
510
|
+
icon: Trash2,
|
|
511
|
+
tone: "danger",
|
|
512
|
+
commit: true,
|
|
513
|
+
pending: busy === `revoke:${key}`,
|
|
514
|
+
disabled: busy !== null,
|
|
515
|
+
onSelect: () => void run(`revoke:${key}`, () => onRevoke(invitation)),
|
|
516
|
+
},
|
|
517
|
+
]}
|
|
518
|
+
/>
|
|
512
519
|
</div>
|
|
513
520
|
)}
|
|
514
521
|
</div>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import { MoreHorizontal } from "lucide-react";
|
|
4
|
-
import type { LucideIcon } from "lucide-react";
|
|
5
3
|
import { cn } from "../lib/cn";
|
|
6
4
|
import type { DateInput } from "../lib/format";
|
|
7
5
|
import type { PersonName } from "../lib/person-name";
|
|
@@ -12,8 +10,8 @@ import type { ChipTone } from "../components/chip";
|
|
|
12
10
|
import type { DataTableColumn } from "../components/data-table";
|
|
13
11
|
import { encodeSorts } from "../components/data-table-sort";
|
|
14
12
|
import type { SortState } from "../components/data-table-sort";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { RowActions } from "../components/row-actions";
|
|
14
|
+
import type { RowAction, RowActionList } from "../components/row-actions";
|
|
17
15
|
import { UserAvatar } from "../components/user-avatar";
|
|
18
16
|
import { personLabel } from "./admin-parts";
|
|
19
17
|
import { RoleSelect } from "./role-select";
|
|
@@ -213,27 +211,13 @@ export function UserIdentityCell({
|
|
|
213
211
|
|
|
214
212
|
/* ── The row's actions ──────────────────────────────────────────────────────── */
|
|
215
213
|
|
|
216
|
-
/** One entry of a row's action menu (§4).
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
key?: string;
|
|
220
|
-
label: string;
|
|
221
|
-
/** Runs it — usually opens `AdminActionConfirm` with the level the server states. */
|
|
222
|
-
onSelect: () => void;
|
|
223
|
-
icon?: LucideIcon;
|
|
224
|
-
/** `danger` for deactivate, transfer, erase. */
|
|
225
|
-
tone?: "default" | "danger";
|
|
226
|
-
/** Why it is not available for this row ("You can’t deactivate yourself"). The entry
|
|
227
|
-
* stays in the menu, focusable, and says so — an action that silently vanished
|
|
228
|
-
* would leave the admin looking for it. */
|
|
229
|
-
disabledReason?: ReactNode;
|
|
230
|
-
/** Leave it out for this row. */
|
|
231
|
-
hidden?: boolean;
|
|
232
|
-
}
|
|
214
|
+
/** One entry of a row's action menu (§4) — the kit's {@link RowAction} since 0.32,
|
|
215
|
+
* which generalised it for every dense row (docs/text-size-harmonization.md §10.8). */
|
|
216
|
+
export type UserRowAction = RowAction;
|
|
233
217
|
|
|
234
218
|
/** What `actions(row)` may return: entries, and `false` / `null` for the ones a
|
|
235
219
|
* condition left out (`row.is_active && {…}`). */
|
|
236
|
-
export type UserRowActionList =
|
|
220
|
+
export type UserRowActionList = RowActionList;
|
|
237
221
|
|
|
238
222
|
export interface UserRowActionsProps {
|
|
239
223
|
actions: UserRowActionList;
|
|
@@ -248,59 +232,19 @@ export interface UserRowActionsProps {
|
|
|
248
232
|
* a phone card, and both grow with every action §2.4 adds. The menu is portalled, so a
|
|
249
233
|
* table's scroller cannot clip it; Escape and an outside press close it, and focus goes
|
|
250
234
|
* back to the button, which is where a confirmation dialog then returns it.
|
|
235
|
+
*
|
|
236
|
+
* Since 0.32 this is {@link RowActions} with `collapse="menu"` — the menu at every size —
|
|
237
|
+
* named by the roster's own `actionsFor`.
|
|
251
238
|
*/
|
|
252
239
|
export function UserRowActions({ actions, name, labels: labelsProp }: UserRowActionsProps) {
|
|
253
240
|
const labels = useKitLabels("userRoster", DEFAULT_USER_ROSTER_LABELS, labelsProp);
|
|
254
|
-
const entries = actions.filter((a): a is UserRowAction => Boolean(a) && !(a as UserRowAction).hidden);
|
|
255
|
-
if (entries.length === 0) return null;
|
|
256
|
-
const title = labels.actionsFor(name);
|
|
257
241
|
return (
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
ref={ref}
|
|
265
|
-
size="xs"
|
|
266
|
-
label={title}
|
|
267
|
-
aria-haspopup="dialog"
|
|
268
|
-
aria-expanded={open}
|
|
269
|
-
stopPropagation
|
|
270
|
-
onClick={toggle}
|
|
271
|
-
>
|
|
272
|
-
<MoreHorizontal />
|
|
273
|
-
</IconButton>
|
|
274
|
-
)}
|
|
275
|
-
>
|
|
276
|
-
{(close) => (
|
|
277
|
-
<ul className="space-y-0.5">
|
|
278
|
-
{entries.map((action) => {
|
|
279
|
-
const Icon = action.icon;
|
|
280
|
-
return (
|
|
281
|
-
<li key={action.key ?? action.label}>
|
|
282
|
-
<Button
|
|
283
|
-
type="button"
|
|
284
|
-
variant="ghost"
|
|
285
|
-
size="sm"
|
|
286
|
-
stretch
|
|
287
|
-
tone={action.tone === "danger" ? "danger" : undefined}
|
|
288
|
-
disabledReason={action.disabledReason}
|
|
289
|
-
className="justify-start"
|
|
290
|
-
onClick={() => {
|
|
291
|
-
close();
|
|
292
|
-
action.onSelect();
|
|
293
|
-
}}
|
|
294
|
-
>
|
|
295
|
-
{Icon && <Icon className="size-4 shrink-0" aria-hidden />}
|
|
296
|
-
{action.label}
|
|
297
|
-
</Button>
|
|
298
|
-
</li>
|
|
299
|
-
);
|
|
300
|
-
})}
|
|
301
|
-
</ul>
|
|
302
|
-
)}
|
|
303
|
-
</Popover>
|
|
242
|
+
<RowActions
|
|
243
|
+
actions={actions}
|
|
244
|
+
name={name}
|
|
245
|
+
collapse="menu"
|
|
246
|
+
labels={{ actionsFor: labels.actionsFor }}
|
|
247
|
+
/>
|
|
304
248
|
);
|
|
305
249
|
}
|
|
306
250
|
|
|
@@ -507,7 +451,8 @@ export function userRosterColumns<T extends UserRosterRow, R extends string = st
|
|
|
507
451
|
column: { mobileHidden: true },
|
|
508
452
|
}),
|
|
509
453
|
// How long ago rather than a date — the question is "recently?" — with the exact
|
|
510
|
-
// time
|
|
454
|
+
// time beside it (keksdose's last-seen column, Kurvenschmiede's; 0.32 §4: never only
|
|
455
|
+
// in a tooltip).
|
|
511
456
|
dateColumn<T>({
|
|
512
457
|
key: "last_login",
|
|
513
458
|
header: labels.lastLogin,
|
package/src/auth/auth-errors.ts
CHANGED
|
@@ -86,9 +86,35 @@ export type DemoErrorCode =
|
|
|
86
86
|
| "demo_read_only"
|
|
87
87
|
| "demo_refused";
|
|
88
88
|
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
91
|
-
|
|
89
|
+
/**
|
|
90
|
+
* 0.32.0: billing's coded refusals (docs/billing-harmonization.md §3.3, §3.4, §4, §12.3) —
|
|
91
|
+
* server-kit 0.6's `BillingError` and `PlanLimitError`, value for value, answered as
|
|
92
|
+
* `{detail, code}` like the others and read by the same {@link authErrorCode}.
|
|
93
|
+
*
|
|
94
|
+
* - `billing_disabled` — `404`: the billing switch is off (§2.9), so every billing route
|
|
95
|
+
* but `GET /billing/status` is "not there", as a switched-off demo is.
|
|
96
|
+
* - `billing_read_only` — `402`: a write by (or into the data of) a payer out of good
|
|
97
|
+
* standing (§3.3, §12.1). Inside a sync reply it marks the changes that were refused
|
|
98
|
+
* and not applied (§12.5). The app's write lock and banner already say why, so — like
|
|
99
|
+
* `demo_read_only` — it needs no toast of its own.
|
|
100
|
+
* - `plan_limit` — `402`: a create past the plan's limit (§3.4), with the extra fields
|
|
101
|
+
* `dimension`, `plan`, `limit` and `used` — read them with `isPlanLimit`. A lapsed payer
|
|
102
|
+
* creating gets `billing_read_only` instead, never this (§12.3).
|
|
103
|
+
* - `billing_not_configured` — billing is switched on but the provider's settings are
|
|
104
|
+
* missing (the secrets, a price id): the operator's mistake, not the payer's.
|
|
105
|
+
*/
|
|
106
|
+
export type BillingErrorCode = "billing_disabled" | "billing_read_only" | "plan_limit" | "billing_not_configured";
|
|
107
|
+
|
|
108
|
+
/** Every code the kit reads: the sign-in refusals, the account ones (0.30.0), the demo's
|
|
109
|
+
* (0.31.0) and billing's (0.32.0). */
|
|
110
|
+
export type KitErrorCode = AuthErrorCode | AccountErrorCode | DemoErrorCode | BillingErrorCode;
|
|
111
|
+
|
|
112
|
+
const BILLING_CODES: ReadonlySet<BillingErrorCode> = new Set<BillingErrorCode>([
|
|
113
|
+
"billing_disabled",
|
|
114
|
+
"billing_read_only",
|
|
115
|
+
"plan_limit",
|
|
116
|
+
"billing_not_configured",
|
|
117
|
+
]);
|
|
92
118
|
|
|
93
119
|
const CODES: ReadonlySet<string> = new Set<KitErrorCode>([
|
|
94
120
|
"invalid_credentials",
|
|
@@ -112,6 +138,7 @@ const CODES: ReadonlySet<string> = new Set<KitErrorCode>([
|
|
|
112
138
|
"demo_not_ready",
|
|
113
139
|
"demo_read_only",
|
|
114
140
|
"demo_refused",
|
|
141
|
+
...BILLING_CODES,
|
|
115
142
|
]);
|
|
116
143
|
|
|
117
144
|
type Bag = Record<string, unknown>;
|
|
@@ -132,8 +159,8 @@ function codeOfBody(body: unknown): KitErrorCode | undefined {
|
|
|
132
159
|
|
|
133
160
|
/**
|
|
134
161
|
* The code an error carries — an {@link AuthErrorCode}, since 0.30.0 an
|
|
135
|
-
* {@link AccountErrorCode}, since 0.31.0 a {@link DemoErrorCode}
|
|
136
|
-
* carries none.
|
|
162
|
+
* {@link AccountErrorCode}, since 0.31.0 a {@link DemoErrorCode}, since 0.32.0 a
|
|
163
|
+
* {@link BillingErrorCode} — or `undefined` when it carries none.
|
|
137
164
|
*
|
|
138
165
|
* Reads the parsed response body wherever the apps' HTTP clients put it, without
|
|
139
166
|
* depending on any of them:
|
|
@@ -148,7 +175,7 @@ function codeOfBody(body: unknown): KitErrorCode | undefined {
|
|
|
148
175
|
* it is one of the codes above: axios writes its OWN codes there (`ERR_NETWORK`,
|
|
149
176
|
* `ECONNABORTED`, `ERR_BAD_REQUEST`), and so does Node, so an unknown value is never
|
|
150
177
|
* read as a refusal. The HTTP status is not checked: the code is the contract, and the
|
|
151
|
-
* status differs per refusal (401, 403, 409, 410).
|
|
178
|
+
* status differs per refusal (401, 402, 403, 404, 409, 410).
|
|
152
179
|
*/
|
|
153
180
|
export function authErrorCode(err: unknown): KitErrorCode | undefined {
|
|
154
181
|
if (!isBag(err)) return undefined;
|
|
@@ -173,13 +200,47 @@ export function authErrorCode(err: unknown): KitErrorCode | undefined {
|
|
|
173
200
|
* with, so an app's `onSubmit` can simply let its client's error through. See
|
|
174
201
|
* {@link authErrorCode} for the shapes it reads. Since 0.30.0 it knows the account codes
|
|
175
202
|
* too: `isAuthError(err, "last_admin")`; since 0.31.0 the demo's:
|
|
176
|
-
* `isAuthError(err, "demo_read_only")
|
|
203
|
+
* `isAuthError(err, "demo_read_only")`; since 0.32.0 billing's:
|
|
204
|
+
* `isAuthError(err, "billing_read_only")` (or {@link isBillingError}).
|
|
177
205
|
*/
|
|
178
206
|
export function isAuthError(err: unknown, code?: KitErrorCode): boolean {
|
|
179
207
|
const found = authErrorCode(err);
|
|
180
208
|
return found !== undefined && (code === undefined || found === code);
|
|
181
209
|
}
|
|
182
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Whether `err` is one of billing's coded refusals (0.32.0, docs/billing-harmonization.md
|
|
213
|
+
* §10) — any of the four, or the one named. {@link isAuthError} answers the same for a
|
|
214
|
+
* named code; this one also answers "is it billing's at all", which an app's error
|
|
215
|
+
* handler asks before it decides between the write lock, a `PlanLimitNotice` and a toast.
|
|
216
|
+
*/
|
|
217
|
+
export function isBillingError(err: unknown, code?: BillingErrorCode): boolean {
|
|
218
|
+
const found = authErrorCode(err);
|
|
219
|
+
return found !== undefined && BILLING_CODES.has(found as BillingErrorCode) && (code === undefined || found === code);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The parsed body that carries a code, wherever {@link authErrorCode} would find it (in
|
|
224
|
+
* the same order) — the object with the `code`, so FastAPI's nested `detail` when the code
|
|
225
|
+
* is there. For a reader of a refusal's EXTRA fields (`isPlanLimit`: `dimension`, `limit`,
|
|
226
|
+
* `used` sit beside the code). `accept` decides which codes count; default, the known ones.
|
|
227
|
+
*
|
|
228
|
+
* @internal Not part of the barrel.
|
|
229
|
+
*/
|
|
230
|
+
export function errorBodyWithCode(
|
|
231
|
+
err: unknown,
|
|
232
|
+
accept: (code: unknown) => boolean = (code) => known(code) !== undefined,
|
|
233
|
+
): Record<string, unknown> | undefined {
|
|
234
|
+
if (!isBag(err)) return undefined;
|
|
235
|
+
const response = err.response;
|
|
236
|
+
for (const body of [isBag(response) ? response.data : undefined, err.data, err.body, err]) {
|
|
237
|
+
if (!isBag(body)) continue;
|
|
238
|
+
if (accept(body.code)) return body;
|
|
239
|
+
if (isBag(body.detail) && accept(body.detail.code)) return body.detail;
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
183
244
|
/** An HTTP status as a number, or undefined. */
|
|
184
245
|
function statusOf(value: unknown): number | undefined {
|
|
185
246
|
return typeof value === "number" && Number.isInteger(value) ? value : undefined;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { CreditCard, Hourglass, PencilOff } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import type { LabelOverride } from "../i18n/kit-labels";
|
|
5
|
+
import { AlertBanner } from "../components/alert-banner";
|
|
6
|
+
import type { AlertSize, AlertTone } from "../components/alert-banner";
|
|
7
|
+
import { Button, Spinner } from "../components/ui";
|
|
8
|
+
import { useBillingLabels } from "./billing-labels";
|
|
9
|
+
import type { BillingLabels } from "./billing-labels";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The billing banners (docs/billing-harmonization.md §3.3, §7, §12.6, §12.21):
|
|
13
|
+
*
|
|
14
|
+
* - `trial-ending` — from 7 days before `trial_ends_at` (§3.3);
|
|
15
|
+
* - `grant-ending` — from 7 days before `comped_until`: a beta user's 12 months, an
|
|
16
|
+
* operator's grant;
|
|
17
|
+
* - `payment-failed` — from the first failed payment (`past_due`), while the provider
|
|
18
|
+
* retries;
|
|
19
|
+
* - `plan-ended` — out of good standing: read-only, never locked (§2.3);
|
|
20
|
+
* - `guest` — a guest in someone else's read-only item (§12.6);
|
|
21
|
+
* - `processing` — back from checkout before the webhook landed (§12.21).
|
|
22
|
+
*/
|
|
23
|
+
export type BillingBannerKind =
|
|
24
|
+
| "trial-ending"
|
|
25
|
+
| "grant-ending"
|
|
26
|
+
| "payment-failed"
|
|
27
|
+
| "plan-ended"
|
|
28
|
+
| "guest"
|
|
29
|
+
| "processing";
|
|
30
|
+
|
|
31
|
+
interface BillingBannerBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "onClick"> {
|
|
32
|
+
/**
|
|
33
|
+
* Over the kind's tone: `trial-ending` and `grant-ending` are `info` until the last
|
|
34
|
+
* three days and `warning` in them; `payment-failed` is `danger` (money did not
|
|
35
|
+
* arrive — kastlan's past-due strip was red); `plan-ended` is `warning`, a state with
|
|
36
|
+
* everything still there rather than an error; `guest` and `processing` are `info`.
|
|
37
|
+
*/
|
|
38
|
+
tone?: AlertTone;
|
|
39
|
+
/** `strip` (default): the band under the top bar, on every page. `box`: the same
|
|
40
|
+
* message on the subscription page itself. */
|
|
41
|
+
variant?: "strip" | "box";
|
|
42
|
+
/** Default `sm` for a strip (the demo's banner), `md` for a box. */
|
|
43
|
+
size?: AlertSize;
|
|
44
|
+
/**
|
|
45
|
+
* Where the action goes — the subscription page, say. The app's action, as an `href`
|
|
46
|
+
* or as {@link onAction}; without either there is no button. Never a write-locked
|
|
47
|
+
* commit: billing itself always works (§3.3).
|
|
48
|
+
*/
|
|
49
|
+
actionHref?: string;
|
|
50
|
+
/** What the action does — the app's portal call (`POST /billing/portal`, then off to
|
|
51
|
+
* its `url`). The kit sends no request. */
|
|
52
|
+
onAction?: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Over the kind's action words: "Choose a plan" (trial, grant, plan ended), "Update
|
|
55
|
+
* payment method" (payment failed), "Check again" (processing). The guest banner has
|
|
56
|
+
* none of its own: an action there needs this.
|
|
57
|
+
*/
|
|
58
|
+
actionLabel?: string;
|
|
59
|
+
/** The action is under way: the button's spinner, no second press. */
|
|
60
|
+
actionPending?: boolean;
|
|
61
|
+
/** An × that calls this; the app removes the banner and remembers that it did. */
|
|
62
|
+
onDismiss?: () => void;
|
|
63
|
+
dismissLabel?: string;
|
|
64
|
+
/** Prop > `<UiKitProvider labels={{ billing }}>` > English. */
|
|
65
|
+
labels?: LabelOverride<BillingLabels>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type BillingBannerProps = BillingBannerBaseProps &
|
|
69
|
+
(
|
|
70
|
+
| {
|
|
71
|
+
kind: "trial-ending" | "grant-ending";
|
|
72
|
+
/** Calendar days left — `billingDaysLeft(trial_ends_at)`. 0 is today. */
|
|
73
|
+
daysLeft: number;
|
|
74
|
+
item?: never;
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
kind: "guest";
|
|
78
|
+
/** The item's own name ("Household 2026"), or nothing. NEVER the owner's status:
|
|
79
|
+
* the banner says "for now" and nothing more (§12.6). */
|
|
80
|
+
item?: string;
|
|
81
|
+
daysLeft?: never;
|
|
82
|
+
}
|
|
83
|
+
| {
|
|
84
|
+
kind: "payment-failed" | "plan-ended" | "processing";
|
|
85
|
+
daysLeft?: never;
|
|
86
|
+
item?: never;
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
/** The last days, in which an ending trial or grant turns from news into a warning. */
|
|
91
|
+
const LAST_DAYS = 3;
|
|
92
|
+
|
|
93
|
+
function defaultTone(kind: BillingBannerKind, daysLeft: number | undefined): AlertTone {
|
|
94
|
+
switch (kind) {
|
|
95
|
+
case "trial-ending":
|
|
96
|
+
case "grant-ending":
|
|
97
|
+
return (daysLeft ?? 0) > LAST_DAYS ? "info" : "warning";
|
|
98
|
+
case "payment-failed":
|
|
99
|
+
return "danger";
|
|
100
|
+
case "plan-ended":
|
|
101
|
+
return "warning";
|
|
102
|
+
default:
|
|
103
|
+
return "info";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* One of the billing banners, as a kit `AlertBanner` strip (§7): the kind's sentence, its
|
|
109
|
+
* glyph and tone, and the app's action. Carries `data-billing-banner` with the kind.
|
|
110
|
+
*
|
|
111
|
+
* The app decides WHICH banner shows, from `GET /billing/overview` (and hides all of them
|
|
112
|
+
* while billing is off, §4); the kit only says it. A guest's banner takes no status at
|
|
113
|
+
* all — the type does not let one in — so the owner's payment state cannot leak into it
|
|
114
|
+
* by a wrong prop (§12.6).
|
|
115
|
+
*
|
|
116
|
+
* `processing` is a live region (`role="status"`): it appears when the person comes back
|
|
117
|
+
* from checkout and goes when the overview catches up (§12.21), and a screen reader
|
|
118
|
+
* should hear both. The others are page furniture, role-less as the strip is.
|
|
119
|
+
*/
|
|
120
|
+
export function BillingBanner(props: BillingBannerProps) {
|
|
121
|
+
const {
|
|
122
|
+
kind,
|
|
123
|
+
daysLeft,
|
|
124
|
+
item,
|
|
125
|
+
tone,
|
|
126
|
+
variant = "strip",
|
|
127
|
+
size,
|
|
128
|
+
actionHref,
|
|
129
|
+
onAction,
|
|
130
|
+
actionLabel,
|
|
131
|
+
actionPending,
|
|
132
|
+
onDismiss,
|
|
133
|
+
dismissLabel,
|
|
134
|
+
labels: labelsProp,
|
|
135
|
+
role,
|
|
136
|
+
...rest
|
|
137
|
+
} = props;
|
|
138
|
+
const labels = useBillingLabels(labelsProp);
|
|
139
|
+
const days = Math.max(0, Math.floor(daysLeft ?? 0));
|
|
140
|
+
|
|
141
|
+
let text: string;
|
|
142
|
+
let icon: ReactNode;
|
|
143
|
+
let ownAction: string | undefined;
|
|
144
|
+
switch (kind) {
|
|
145
|
+
case "trial-ending":
|
|
146
|
+
text = labels.trialEnding(days);
|
|
147
|
+
icon = <Hourglass />;
|
|
148
|
+
ownAction = labels.choosePlan;
|
|
149
|
+
break;
|
|
150
|
+
case "grant-ending":
|
|
151
|
+
text = labels.grantEnding(days);
|
|
152
|
+
icon = <Hourglass />;
|
|
153
|
+
ownAction = labels.choosePlan;
|
|
154
|
+
break;
|
|
155
|
+
case "payment-failed":
|
|
156
|
+
text = labels.paymentFailed;
|
|
157
|
+
icon = <CreditCard />;
|
|
158
|
+
ownAction = labels.updatePayment;
|
|
159
|
+
break;
|
|
160
|
+
case "plan-ended":
|
|
161
|
+
text = labels.planEnded;
|
|
162
|
+
icon = <PencilOff />;
|
|
163
|
+
ownAction = labels.choosePlan;
|
|
164
|
+
break;
|
|
165
|
+
case "guest":
|
|
166
|
+
text = labels.guestReadOnly(item);
|
|
167
|
+
icon = <PencilOff />;
|
|
168
|
+
ownAction = undefined;
|
|
169
|
+
break;
|
|
170
|
+
case "processing":
|
|
171
|
+
text = labels.processing;
|
|
172
|
+
icon = <Spinner label={null} />;
|
|
173
|
+
ownAction = labels.checkAgain;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const words = actionLabel ?? ownAction;
|
|
178
|
+
const action =
|
|
179
|
+
words === undefined || (actionHref === undefined && onAction === undefined) ? undefined : actionHref !== undefined ? (
|
|
180
|
+
<Button href={actionHref} variant="secondary" size="sm">
|
|
181
|
+
{words}
|
|
182
|
+
</Button>
|
|
183
|
+
) : (
|
|
184
|
+
<Button type="button" variant="secondary" size="sm" onClick={onAction} pending={actionPending}>
|
|
185
|
+
{words}
|
|
186
|
+
</Button>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<AlertBanner
|
|
191
|
+
{...rest}
|
|
192
|
+
role={role ?? (kind === "processing" ? "status" : undefined)}
|
|
193
|
+
variant={variant}
|
|
194
|
+
size={size ?? (variant === "strip" ? "sm" : "md")}
|
|
195
|
+
tone={tone ?? defaultTone(kind, days)}
|
|
196
|
+
icon={icon}
|
|
197
|
+
action={action}
|
|
198
|
+
onDismiss={onDismiss}
|
|
199
|
+
dismissLabel={dismissLabel}
|
|
200
|
+
data-billing-banner={kind}
|
|
201
|
+
>
|
|
202
|
+
{text}
|
|
203
|
+
</AlertBanner>
|
|
204
|
+
);
|
|
205
|
+
}
|