@eifi1/ui-kit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +32 -0
- package/README.md +498 -57
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +10 -0
- package/dist/chart.js.map +1 -0
- package/dist/components/account-settings.d.ts +11 -0
- package/dist/components/account-settings.js +19 -12
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +10 -6
- package/dist/components/alert-banner.js +20 -16
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +30 -4
- package/dist/components/amount-input.js +36 -14
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +24 -7
- package/dist/components/calculator.js +37 -20
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +156 -0
- package/dist/components/chart-zoom.js +262 -0
- package/dist/components/chart-zoom.js.map +1 -0
- package/dist/components/chart.d.ts +28 -3
- package/dist/components/chart.js +71 -32
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.d.ts +51 -0
- package/dist/components/checkbox.js +123 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +128 -0
- package/dist/components/chip.js +357 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/choice-parts.d.ts +24 -0
- package/dist/components/choice-parts.js +18 -0
- package/dist/components/choice-parts.js.map +1 -0
- package/dist/components/combobox-core.d.ts +52 -9
- package/dist/components/combobox-core.js +91 -20
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +43 -33
- package/dist/components/combobox.js +409 -340
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +48 -8
- package/dist/components/currency-select.js +107 -65
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +15 -3
- package/dist/components/data-table-filter-popover.js +23 -17
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +3 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +53 -2
- package/dist/components/data-table-labels.js +31 -2
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table-pagination.js +16 -10
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.js +1 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +201 -85
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -9
- package/dist/components/date-picker.js +232 -94
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.d.ts +96 -20
- package/dist/components/dropdown.js +77 -33
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +5 -4
- package/dist/components/entity-combobox.js +107 -76
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +36 -0
- package/dist/components/facing-pair.js +40 -0
- package/dist/components/facing-pair.js.map +1 -0
- package/dist/components/field-sync.d.ts +164 -0
- package/dist/components/field-sync.js +264 -0
- package/dist/components/field-sync.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +38 -6
- package/dist/components/file-dropzone.js +34 -9
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +37 -26
- package/dist/components/full-bleed-dialog.js +20 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/grouped-picker.d.ts +21 -10
- package/dist/components/grouped-picker.js +66 -52
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.d.ts +20 -4
- package/dist/components/hover-menu.js +15 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +102 -14
- package/dist/components/mini-calendar.js +256 -70
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +9 -3
- package/dist/components/modal.js +34 -45
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +79 -0
- package/dist/components/month-picker.js +342 -0
- package/dist/components/month-picker.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +7 -6
- package/dist/components/multi-entity-combobox.js +112 -72
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +13 -11
- package/dist/components/multi-select.js +161 -84
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +92 -0
- package/dist/components/number-field.js +111 -0
- package/dist/components/number-field.js.map +1 -0
- package/dist/components/number-input.d.ts +28 -2
- package/dist/components/number-input.js +12 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +32 -4
- package/dist/components/numpad-sheet.js +30 -13
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-contents.d.ts +90 -0
- package/dist/components/page-contents.js +170 -0
- package/dist/components/page-contents.js.map +1 -0
- package/dist/components/password-strength.d.ts +106 -0
- package/dist/components/password-strength.js +132 -0
- package/dist/components/password-strength.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +23 -15
- package/dist/components/picker-sheet.js +34 -14
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.d.ts +62 -4
- package/dist/components/popover.js +52 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/search-field.d.ts +25 -7
- package/dist/components/search-field.js +7 -5
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +14 -0
- package/dist/components/series-chart-labels.js +11 -0
- package/dist/components/series-chart-labels.js.map +1 -0
- package/dist/components/series-chart.d.ts +207 -0
- package/dist/components/series-chart.js +223 -0
- package/dist/components/series-chart.js.map +1 -0
- package/dist/components/settings-fields.d.ts +14 -4
- package/dist/components/settings-fields.js +19 -8
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/signature-pad.d.ts +119 -0
- package/dist/components/signature-pad.js +405 -0
- package/dist/components/signature-pad.js.map +1 -0
- package/dist/components/slider.d.ts +80 -0
- package/dist/components/slider.js +155 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sparkline.d.ts +93 -0
- package/dist/components/sparkline.js +205 -0
- package/dist/components/sparkline.js.map +1 -0
- package/dist/components/stat-tile.d.ts +151 -0
- package/dist/components/stat-tile.js +236 -0
- package/dist/components/stat-tile.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +25 -2
- package/dist/components/swipeable-row.js +31 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +60 -0
- package/dist/components/switch.js +81 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/time-input.d.ts +58 -0
- package/dist/components/time-input.js +50 -0
- package/dist/components/time-input.js.map +1 -0
- package/dist/components/toggle-group.d.ts +18 -3
- package/dist/components/toggle-group.js +10 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +115 -0
- package/dist/components/toggle-legend.js +114 -0
- package/dist/components/toggle-legend.js.map +1 -0
- package/dist/components/tooltip.d.ts +40 -11
- package/dist/components/tooltip.js +77 -20
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/treemap.d.ts +132 -0
- package/dist/components/treemap.js +179 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/ui.d.ts +159 -46
- package/dist/components/ui.js +220 -115
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-mobile-reveal.js +1 -0
- package/dist/components/use-mobile-reveal.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/use-table-state.js +38 -17
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +15 -4
- package/dist/components/user-avatar.js +5 -4
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +3 -2
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
- package/dist/data-table.d.ts +7 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/feedback/feedback-attachment.js +12 -11
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.js +2 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +33 -7
- package/dist/feedback/feedback-inbox.js +49 -37
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback.d.ts +5 -0
- package/dist/feedback.js +5 -0
- package/dist/feedback.js.map +1 -0
- package/dist/hooks/use-anchored-panel.js +1 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js +1 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-announce.d.ts +46 -0
- package/dist/hooks/use-announce.js +37 -0
- package/dist/hooks/use-announce.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.js +1 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -1
- package/dist/hooks/use-close-transition.js +1 -0
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-dismiss.js +1 -0
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +16 -0
- package/dist/hooks/use-focus-trap.js +67 -0
- package/dist/hooks/use-focus-trap.js.map +1 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +27 -1
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.js +1 -0
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.d.ts +31 -0
- package/dist/i18n/defaults.js +63 -0
- package/dist/i18n/defaults.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +250 -0
- package/dist/i18n/kit-labels.js +184 -0
- package/dist/i18n/kit-labels.js.map +1 -0
- package/dist/index.d.ts +47 -22
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calc.js +1 -0
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/dates.js +1 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/safe-storage.js +1 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/search/command-palette.d.ts +11 -1
- package/dist/search/command-palette.js +21 -15
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.js +3 -0
- package/dist/search.js.map +1 -0
- package/dist/shell/app-shell.d.ts +33 -4
- package/dist/shell/app-shell.js +275 -78
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +3 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +6 -0
- package/dist/shell/role-switcher.js +7 -4
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar.d.ts +15 -7
- package/dist/shell/top-bar.js +3 -5
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +3 -2
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +5 -2
- package/dist/shell/topbar-controls.js +20 -15
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +13 -0
- package/dist/shell.js +8 -0
- package/dist/shell.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +19 -12
- package/dist/theme/chart-palette.js +24 -30
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/color.d.ts +78 -0
- package/dist/theme/color.js +148 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/palette-derive.d.ts +140 -0
- package/dist/theme/palette-derive.js +376 -0
- package/dist/theme/palette-derive.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +27 -5
- package/dist/theme/palette-presets.js +231 -33
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +1 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.d.ts +2 -1
- package/dist/tour/tour.js +15 -9
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.js +10 -8
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +18 -2
- package/dist/wizard/types.js +4 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +12 -4
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js +1 -0
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js +1 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.js +1 -0
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js +5 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +9 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +67 -14
- package/src/chart.ts +22 -0
- package/src/components/account-settings.tsx +275 -0
- package/src/components/alert-banner.tsx +75 -0
- package/src/components/amount-input.tsx +471 -0
- package/src/components/calculator.tsx +252 -0
- package/src/components/chart-zoom.tsx +499 -0
- package/src/components/chart.tsx +363 -0
- package/src/components/checkbox.tsx +222 -0
- package/src/components/chip.tsx +570 -0
- package/src/components/choice-parts.ts +33 -0
- package/src/components/combobox-core.tsx +540 -0
- package/src/components/combobox.tsx +1026 -0
- package/src/components/currency-select.tsx +269 -0
- package/src/components/data-table-filter-popover.tsx +252 -0
- package/src/components/data-table-filters.ts +176 -0
- package/src/components/data-table-labels.ts +190 -0
- package/src/components/data-table-pagination.tsx +126 -0
- package/src/components/data-table-sort.ts +70 -0
- package/src/components/data-table.tsx +1740 -0
- package/src/components/date-picker.tsx +631 -0
- package/src/components/dropdown.tsx +341 -0
- package/src/components/entity-combobox.tsx +215 -0
- package/src/components/facing-pair.ts +86 -0
- package/src/components/field-sync.tsx +472 -0
- package/src/components/file-dropzone.tsx +181 -0
- package/src/components/full-bleed-dialog.tsx +179 -0
- package/src/components/grouped-picker.tsx +157 -0
- package/src/components/hover-menu.tsx +183 -0
- package/src/components/mini-calendar.tsx +554 -0
- package/src/components/modal.tsx +287 -0
- package/src/components/month-picker.tsx +549 -0
- package/src/components/multi-entity-combobox.tsx +226 -0
- package/src/components/multi-select.tsx +303 -0
- package/src/components/number-field.tsx +225 -0
- package/src/components/number-input.tsx +211 -0
- package/src/components/numpad-sheet.tsx +264 -0
- package/src/components/page-contents.tsx +289 -0
- package/src/components/password-strength.tsx +258 -0
- package/src/components/picker-sheet.tsx +251 -0
- package/src/components/popover.tsx +195 -0
- package/src/components/search-field.tsx +146 -0
- package/src/components/series-chart-labels.ts +26 -0
- package/src/components/series-chart.tsx +481 -0
- package/src/components/settings-fields.tsx +68 -0
- package/src/components/signature-pad.tsx +630 -0
- package/src/components/slider.tsx +313 -0
- package/src/components/sparkline.tsx +329 -0
- package/src/components/stat-tile.tsx +448 -0
- package/src/components/swipeable-row.tsx +271 -0
- package/src/components/switch.tsx +170 -0
- package/src/components/time-input.tsx +107 -0
- package/src/components/toggle-group.tsx +153 -0
- package/src/components/toggle-legend.tsx +229 -0
- package/src/components/tooltip.tsx +477 -0
- package/src/components/treemap.tsx +390 -0
- package/src/components/ui.tsx +1284 -0
- package/src/components/use-mobile-reveal.ts +54 -0
- package/src/components/use-table-state.ts +295 -0
- package/src/components/user-avatar.tsx +61 -0
- package/src/components/wizard-stepper.tsx +37 -0
- package/src/data-table.ts +19 -0
- package/src/feedback/feedback-attachment.tsx +246 -0
- package/src/feedback/feedback-dialog.tsx +209 -0
- package/src/feedback/feedback-inbox.tsx +615 -0
- package/src/feedback.ts +8 -0
- package/src/hooks/use-anchored-panel.ts +169 -0
- package/src/hooks/use-anchored-rect.ts +78 -0
- package/src/hooks/use-announce.ts +85 -0
- package/src/hooks/use-body-scroll-lock.ts +94 -0
- package/src/hooks/use-close-transition.ts +99 -0
- package/src/hooks/use-dismiss.ts +77 -0
- package/src/hooks/use-focus-trap.ts +160 -0
- package/src/hooks/use-media-query.ts +23 -0
- package/src/hooks/use-overlay-history.ts +345 -0
- package/src/hooks/use-row-swipe.ts +212 -0
- package/src/i18n/defaults.ts +68 -0
- package/src/i18n/kit-labels.tsx +447 -0
- package/src/index.ts +169 -0
- package/src/lib/calc.ts +309 -0
- package/src/lib/cn.ts +5 -0
- package/src/lib/dates.ts +187 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/safe-storage.ts +37 -0
- package/src/search/command-palette.tsx +342 -0
- package/src/search.ts +3 -0
- package/src/shell/app-shell.tsx +772 -0
- package/src/shell/option-switcher-menu.tsx +72 -0
- package/src/shell/role-switcher.tsx +46 -0
- package/src/shell/top-bar.tsx +39 -0
- package/src/shell/topbar-action-menu.tsx +91 -0
- package/src/shell/topbar-controls.tsx +229 -0
- package/src/shell.ts +10 -0
- package/src/theme/chart-palette.ts +145 -0
- package/src/theme/color.ts +266 -0
- package/src/theme/palette-derive.ts +681 -0
- package/src/theme/palette-presets.ts +760 -0
- package/src/theme/palette-store.ts +94 -0
- package/src/theme/theme-store.ts +145 -0
- package/src/tour/tour.tsx +586 -0
- package/src/tour.ts +3 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wizard/stepper-nav.tsx +221 -0
- package/src/wizard/types.ts +178 -0
- package/src/wizard/use-wizard.ts +345 -0
- package/src/wizard/validation.ts +37 -0
- package/src/wizard/wizard-context.tsx +29 -0
- package/src/wizard/wizard-step.tsx +14 -0
- package/src/wizard/wizard-summary.tsx +69 -0
- package/src/wizard.ts +14 -0
- package/tokens.css +222 -20
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { KeyboardEvent, ReactNode } from "react";
|
|
3
|
+
import { Button, Card, FIELD_WRITABLE_LOOK, Input } from "./ui";
|
|
4
|
+
import { UserAvatar } from "./user-avatar";
|
|
5
|
+
import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* App-agnostic account-settings SECTIONS (feedback #333). The presentation lives
|
|
9
|
+
* in @hb/ui; each app injects its own auth-API handlers + translated labels and
|
|
10
|
+
* arranges these next to its own app-specific cards (appearance, budgets, …).
|
|
11
|
+
* Every section is a self-contained <Card> owning its transient form state.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface ProfileSettingLabels {
|
|
15
|
+
title: string;
|
|
16
|
+
email: string;
|
|
17
|
+
role: string;
|
|
18
|
+
memberSince: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
save: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ProfileSetting({
|
|
24
|
+
name,
|
|
25
|
+
email,
|
|
26
|
+
role,
|
|
27
|
+
memberSince,
|
|
28
|
+
value,
|
|
29
|
+
onChange,
|
|
30
|
+
onSave,
|
|
31
|
+
saving,
|
|
32
|
+
labels,
|
|
33
|
+
}: {
|
|
34
|
+
name?: string | null;
|
|
35
|
+
email?: string | null;
|
|
36
|
+
role?: ReactNode;
|
|
37
|
+
memberSince?: ReactNode;
|
|
38
|
+
value: string;
|
|
39
|
+
onChange: (value: string) => void;
|
|
40
|
+
onSave: () => void;
|
|
41
|
+
saving?: boolean;
|
|
42
|
+
labels: ProfileSettingLabels;
|
|
43
|
+
}) {
|
|
44
|
+
const dirty = value.trim().length > 0 && value.trim() !== (name ?? "");
|
|
45
|
+
return (
|
|
46
|
+
<Card className="p-4 space-y-3">
|
|
47
|
+
<div className="flex items-center gap-3">
|
|
48
|
+
<UserAvatar name={name} email={email} size="lg" />
|
|
49
|
+
<div className="min-w-0">
|
|
50
|
+
<div className="text-sm font-medium">{labels.title}</div>
|
|
51
|
+
<div className="truncate font-mono text-xs text-[var(--text-muted)]">{email ?? "—"}</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1 text-sm">
|
|
55
|
+
<span className="text-[var(--text-muted)]">{labels.role}</span>
|
|
56
|
+
<span>{role ?? "—"}</span>
|
|
57
|
+
<span className="text-[var(--text-muted)]">{labels.memberSince}</span>
|
|
58
|
+
<span>{memberSince ?? "—"}</span>
|
|
59
|
+
</div>
|
|
60
|
+
<Input
|
|
61
|
+
id="display-name"
|
|
62
|
+
label={labels.displayName}
|
|
63
|
+
value={value}
|
|
64
|
+
onChange={(e) => onChange(e.target.value)}
|
|
65
|
+
maxLength={120}
|
|
66
|
+
/>
|
|
67
|
+
<Button onClick={onSave} disabled={!dirty || saving}>
|
|
68
|
+
{labels.save}
|
|
69
|
+
</Button>
|
|
70
|
+
</Card>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PasswordSettingLabels {
|
|
75
|
+
title: string;
|
|
76
|
+
current: string;
|
|
77
|
+
next: string;
|
|
78
|
+
confirm: string;
|
|
79
|
+
submit: string;
|
|
80
|
+
tooShort: string;
|
|
81
|
+
mismatch: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function PasswordSetting({
|
|
85
|
+
onSubmit,
|
|
86
|
+
pending,
|
|
87
|
+
minLength = 8,
|
|
88
|
+
labels,
|
|
89
|
+
}: {
|
|
90
|
+
/** Called with the validated (current, new) pair; return a promise to auto-clear on success. */
|
|
91
|
+
onSubmit: (currentPassword: string, newPassword: string) => void | Promise<unknown>;
|
|
92
|
+
pending?: boolean;
|
|
93
|
+
minLength?: number;
|
|
94
|
+
labels: PasswordSettingLabels;
|
|
95
|
+
}) {
|
|
96
|
+
const [current, setCurrent] = useState("");
|
|
97
|
+
const [next, setNext] = useState("");
|
|
98
|
+
const [confirm, setConfirm] = useState("");
|
|
99
|
+
const [error, setError] = useState<string | null>(null);
|
|
100
|
+
|
|
101
|
+
const submit = async () => {
|
|
102
|
+
if (next.length < minLength) {
|
|
103
|
+
setError(labels.tooShort);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (next !== confirm) {
|
|
107
|
+
setError(labels.mismatch);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
setError(null);
|
|
111
|
+
try {
|
|
112
|
+
await onSubmit(current, next);
|
|
113
|
+
setCurrent("");
|
|
114
|
+
setNext("");
|
|
115
|
+
setConfirm("");
|
|
116
|
+
} catch {
|
|
117
|
+
// The app surfaces the failure (e.g. a toast); keep the fields for a retry.
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Card className="p-4 space-y-3">
|
|
123
|
+
<div className="text-sm font-medium">{labels.title}</div>
|
|
124
|
+
<Input type="password" autoComplete="current-password" label={labels.current} value={current} onChange={(e) => setCurrent(e.target.value)} />
|
|
125
|
+
<Input type="password" autoComplete="new-password" label={labels.next} value={next} onChange={(e) => setNext(e.target.value)} />
|
|
126
|
+
<Input type="password" autoComplete="new-password" label={labels.confirm} value={confirm} onChange={(e) => setConfirm(e.target.value)} />
|
|
127
|
+
{error && <p className="text-xs text-[var(--danger)]">{error}</p>}
|
|
128
|
+
<Button onClick={() => void submit()} disabled={!current || !next || !confirm || pending}>
|
|
129
|
+
{labels.submit}
|
|
130
|
+
</Button>
|
|
131
|
+
</Card>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface TwoFactorSettingLabels {
|
|
136
|
+
status: string;
|
|
137
|
+
enabledText: string;
|
|
138
|
+
disabledText: string;
|
|
139
|
+
enable: string;
|
|
140
|
+
scanHint: string;
|
|
141
|
+
codeLabel: string;
|
|
142
|
+
verify: string;
|
|
143
|
+
disableSection: string;
|
|
144
|
+
password: string;
|
|
145
|
+
disable: string;
|
|
146
|
+
/**
|
|
147
|
+
* Alt text for the setup QR image, which shipped as a hardcoded `alt="QR"`.
|
|
148
|
+
*
|
|
149
|
+
* OPTIONAL, alone among these keys, and not because it matters less: this
|
|
150
|
+
* interface is annotated at consumer call sites (`const LABELS:
|
|
151
|
+
* TwoFactorSettingLabels = {…}`), so a new REQUIRED key is a compile error in
|
|
152
|
+
* every app on the next `npm update` — the additive-API rule in the README. It
|
|
153
|
+
* falls back to English here the way `AmountInput`'s label keys do. Make it
|
|
154
|
+
* required in the next major, when the three apps can be updated with it.
|
|
155
|
+
*/
|
|
156
|
+
qrAlt?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** `qrAlt`'s English fallback, kept beside the interface rather than inline so the
|
|
160
|
+
* one English word in this section is findable by the same `DEFAULT_*` grep as
|
|
161
|
+
* every other one. Module-private: the key goes required in the next major. */
|
|
162
|
+
const DEFAULT_QR_ALT = "QR code";
|
|
163
|
+
|
|
164
|
+
export function TwoFactorSetting({
|
|
165
|
+
enabled,
|
|
166
|
+
setup,
|
|
167
|
+
onStartSetup,
|
|
168
|
+
onEnable,
|
|
169
|
+
onDisable,
|
|
170
|
+
busy,
|
|
171
|
+
labels,
|
|
172
|
+
}: {
|
|
173
|
+
enabled: boolean;
|
|
174
|
+
/** QR + secret returned by the app's setup call; null before setup starts. */
|
|
175
|
+
setup: { qrSvg: string; secret: string } | null;
|
|
176
|
+
onStartSetup: () => void;
|
|
177
|
+
onEnable: (code: string) => void;
|
|
178
|
+
onDisable: (password: string, code: string) => void;
|
|
179
|
+
busy?: boolean;
|
|
180
|
+
labels: TwoFactorSettingLabels;
|
|
181
|
+
}) {
|
|
182
|
+
const [code, setCode] = useState("");
|
|
183
|
+
const [password, setPassword] = useState("");
|
|
184
|
+
// Browsers autofill one-time-code fields with the saved username; keep the
|
|
185
|
+
// field readOnly until focus to block that. It therefore has to keep the LOOK of
|
|
186
|
+
// an editable field (FIELD_WRITABLE_LOOK): this is the one place where readOnly
|
|
187
|
+
// does not mean "you may not type here", and without the opt-out the field would
|
|
188
|
+
// sit there greyed until the moment it is focused.
|
|
189
|
+
const [otpReadonly, setOtpReadonly] = useState(true);
|
|
190
|
+
const onOtpFocus = () => setOtpReadonly(false);
|
|
191
|
+
// Only for the "Status: Enabled" composition — the punctuation between a field's
|
|
192
|
+
// name and its value is the language's (see `CommonLabels.fieldValue`).
|
|
193
|
+
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Card className="p-4 space-y-3">
|
|
197
|
+
<div className="text-sm">
|
|
198
|
+
{common.fieldValue(labels.status, enabled ? labels.enabledText : labels.disabledText)}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{!enabled && !setup && (
|
|
202
|
+
<Button onClick={onStartSetup} disabled={busy}>
|
|
203
|
+
{labels.enable}
|
|
204
|
+
</Button>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{setup && (
|
|
208
|
+
<div className="space-y-2">
|
|
209
|
+
<div className="text-xs text-[var(--text-secondary)]">{labels.scanHint}</div>
|
|
210
|
+
{/* `bg-white`, NOT `--bg-surface`, and it must stay that way: a QR code is
|
|
211
|
+
read by a camera looking for maximum contrast between the modules and
|
|
212
|
+
their quiet zone. On the dark theme a surface-coloured backing makes it
|
|
213
|
+
slow to acquire, and on a warm light preset it lowers the contrast ratio
|
|
214
|
+
the spec is written against. This is the one place in the kit where a
|
|
215
|
+
literal colour is the correct answer. */}
|
|
216
|
+
<div className="inline-block rounded-md bg-white p-2">
|
|
217
|
+
<img
|
|
218
|
+
alt={labels.qrAlt ?? DEFAULT_QR_ALT}
|
|
219
|
+
src={`data:image/svg+xml;base64,${setup.qrSvg}`}
|
|
220
|
+
className="h-48 w-48"
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
<div className="break-all font-mono text-xs">{setup.secret}</div>
|
|
224
|
+
<Input
|
|
225
|
+
inputMode="numeric"
|
|
226
|
+
autoComplete="one-time-code"
|
|
227
|
+
readOnly={otpReadonly}
|
|
228
|
+
inputClassName={FIELD_WRITABLE_LOOK}
|
|
229
|
+
onFocus={onOtpFocus}
|
|
230
|
+
maxLength={8}
|
|
231
|
+
label={labels.codeLabel}
|
|
232
|
+
value={code}
|
|
233
|
+
onChange={(e) => setCode(e.target.value)}
|
|
234
|
+
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
235
|
+
if (e.key === "Enter" && code && !busy) {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
onEnable(code);
|
|
238
|
+
}
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
<Button onClick={() => onEnable(code)} disabled={!code || busy}>
|
|
242
|
+
{labels.verify}
|
|
243
|
+
</Button>
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{enabled && (
|
|
248
|
+
<div className="space-y-3 border-t border-[var(--border)] pt-3">
|
|
249
|
+
<div className="text-sm font-medium">{labels.disableSection}</div>
|
|
250
|
+
<Input type="password" autoComplete="current-password" label={labels.password} value={password} onChange={(e) => setPassword(e.target.value)} />
|
|
251
|
+
<Input
|
|
252
|
+
inputMode="numeric"
|
|
253
|
+
autoComplete="one-time-code"
|
|
254
|
+
readOnly={otpReadonly}
|
|
255
|
+
inputClassName={FIELD_WRITABLE_LOOK}
|
|
256
|
+
onFocus={onOtpFocus}
|
|
257
|
+
maxLength={8}
|
|
258
|
+
label={labels.codeLabel}
|
|
259
|
+
value={code}
|
|
260
|
+
onChange={(e) => setCode(e.target.value)}
|
|
261
|
+
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
262
|
+
if (e.key === "Enter" && password && code && !busy) {
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
onDisable(password, code);
|
|
265
|
+
}
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
<Button variant="danger" onClick={() => onDisable(password, code)} disabled={!password || !code || busy}>
|
|
269
|
+
{labels.disable}
|
|
270
|
+
</Button>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
</Card>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { AlertTriangle } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
|
|
5
|
+
export type AlertTone = "danger" | "warning" | "neutral";
|
|
6
|
+
|
|
7
|
+
/** Border + surface per tone, WITHOUT a radius or padding, so a caller that owns
|
|
8
|
+
* its own box (a Card, say) can take the tone alone. Single source for both
|
|
9
|
+
* {@link alertFrameClass} and {@link toneFrameClass}.
|
|
10
|
+
*
|
|
11
|
+
* The colored tones are 2px, not 1px, and that is load-bearing rather than
|
|
12
|
+
* decorative. A 1px border is 1.25 device pixels on a 125%-scaled display — the
|
|
13
|
+
* usual Windows setting — so whether it renders as a solid line or as two
|
|
14
|
+
* half-lit pixels depends on where the box happens to land in device-pixel space.
|
|
15
|
+
* The same card then shows a heavy left edge and a right edge that fades out,
|
|
16
|
+
* which is what feedback #264, #277, #491 and #498 all reported. A 2px border is
|
|
17
|
+
* 2.5 device pixels: it always covers at least two pixels fully, on both edges,
|
|
18
|
+
* at any offset. Callers compensate the extra pixel in their own padding so
|
|
19
|
+
* switching tones never shifts the layout. */
|
|
20
|
+
const TONE_FRAME: Record<AlertTone, string> = {
|
|
21
|
+
danger: "border-2 border-[var(--danger-border)] bg-[var(--danger-bg)]",
|
|
22
|
+
warning: "border-2 border-[var(--warning-border)] bg-[var(--warning-bg)]",
|
|
23
|
+
neutral: "border border-[var(--border)]",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** The tone's border + surface on their own — for a caller that already has a box
|
|
27
|
+
* with its own radius and padding (e.g. a Card). Remember to shave 1px off that
|
|
28
|
+
* padding for the colored tones, whose border is 2px wide. */
|
|
29
|
+
export function toneFrameClass(tone: AlertTone): string {
|
|
30
|
+
return TONE_FRAME[tone];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Single source for the warning-callout frame (feedback #277): the tone's border
|
|
34
|
+
* and surface plus this component's own radius and padding, the latter
|
|
35
|
+
* compensating the 2px colored border so toggling tones never shifts layout. */
|
|
36
|
+
export function alertFrameClass(tone: AlertTone): string {
|
|
37
|
+
const box = tone === "neutral" ? "rounded-md p-3" : "rounded-md p-[11px]";
|
|
38
|
+
return `${box} ${TONE_FRAME[tone]}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const TONE_TEXT: Record<Exclude<AlertTone, "neutral">, string> = {
|
|
42
|
+
danger: "text-[var(--danger)]",
|
|
43
|
+
warning: "text-[var(--warning)]",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const TONE_ICON: Record<Exclude<AlertTone, "neutral">, string> = {
|
|
47
|
+
danger: "text-[var(--danger)]",
|
|
48
|
+
warning: "text-[var(--warning)]",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export interface AlertBannerProps extends ComponentPropsWithoutRef<"div"> {
|
|
52
|
+
/** `neutral` is deliberately not offered: a callout with a warning triangle in it
|
|
53
|
+
* and no colour is a warning that looks like a note, which is the one thing this
|
|
54
|
+
* box must never be. The neutral frame is still available on its own through
|
|
55
|
+
* {@link toneFrameClass}. */
|
|
56
|
+
tone?: Exclude<AlertTone, "neutral">;
|
|
57
|
+
children: ReactNode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Static warning/danger callout box with the shared frame and an icon. */
|
|
61
|
+
export function AlertBanner({ tone = "danger", className, children, ...rest }: AlertBannerProps) {
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
// Arbitrary attributes first — a `data-tour` anchor, a test id, an
|
|
65
|
+
// `aria-describedby` — then the layout and tone classes, which are merged
|
|
66
|
+
// through `cn` rather than spread so a caller's `className` refines the box
|
|
67
|
+
// instead of replacing it.
|
|
68
|
+
{...rest}
|
|
69
|
+
className={cn("flex items-start gap-2 text-sm", alertFrameClass(tone), TONE_TEXT[tone], className)}
|
|
70
|
+
>
|
|
71
|
+
<AlertTriangle className={cn("mt-0.5 size-4 shrink-0", TONE_ICON[tone])} />
|
|
72
|
+
<span className="flex-1">{children}</span>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|