@eifi1/ui-kit 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +32 -0
- package/README.md +498 -57
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +10 -0
- package/dist/chart.js.map +1 -0
- package/dist/components/account-settings.d.ts +11 -0
- package/dist/components/account-settings.js +19 -12
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +10 -6
- package/dist/components/alert-banner.js +20 -16
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +30 -4
- package/dist/components/amount-input.js +36 -14
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +24 -7
- package/dist/components/calculator.js +37 -20
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +156 -0
- package/dist/components/chart-zoom.js +262 -0
- package/dist/components/chart-zoom.js.map +1 -0
- package/dist/components/chart.d.ts +28 -3
- package/dist/components/chart.js +71 -32
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.d.ts +51 -0
- package/dist/components/checkbox.js +123 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +128 -0
- package/dist/components/chip.js +357 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/choice-parts.d.ts +24 -0
- package/dist/components/choice-parts.js +18 -0
- package/dist/components/choice-parts.js.map +1 -0
- package/dist/components/combobox-core.d.ts +52 -9
- package/dist/components/combobox-core.js +89 -19
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +43 -33
- package/dist/components/combobox.js +409 -340
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +48 -8
- package/dist/components/currency-select.js +107 -65
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +15 -3
- package/dist/components/data-table-filter-popover.js +23 -17
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +3 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +53 -2
- package/dist/components/data-table-labels.js +31 -2
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table-pagination.js +16 -10
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.js +1 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +201 -85
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -9
- package/dist/components/date-picker.js +232 -94
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.d.ts +74 -19
- package/dist/components/dropdown.js +72 -22
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +5 -4
- package/dist/components/entity-combobox.js +107 -76
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +36 -0
- package/dist/components/facing-pair.js +40 -0
- package/dist/components/facing-pair.js.map +1 -0
- package/dist/components/field-sync.d.ts +164 -0
- package/dist/components/field-sync.js +264 -0
- package/dist/components/field-sync.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +21 -7
- package/dist/components/file-dropzone.js +28 -7
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +37 -26
- package/dist/components/full-bleed-dialog.js +20 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/grouped-picker.d.ts +21 -10
- package/dist/components/grouped-picker.js +66 -52
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.d.ts +20 -4
- package/dist/components/hover-menu.js +15 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +102 -14
- package/dist/components/mini-calendar.js +256 -70
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +9 -3
- package/dist/components/modal.js +34 -45
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +79 -0
- package/dist/components/month-picker.js +342 -0
- package/dist/components/month-picker.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +7 -6
- package/dist/components/multi-entity-combobox.js +112 -72
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +13 -11
- package/dist/components/multi-select.js +161 -84
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +92 -0
- package/dist/components/number-field.js +111 -0
- package/dist/components/number-field.js.map +1 -0
- package/dist/components/number-input.d.ts +21 -2
- package/dist/components/number-input.js +2 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +25 -4
- package/dist/components/numpad-sheet.js +30 -13
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-contents.d.ts +90 -0
- package/dist/components/page-contents.js +170 -0
- package/dist/components/page-contents.js.map +1 -0
- package/dist/components/password-strength.d.ts +106 -0
- package/dist/components/password-strength.js +132 -0
- package/dist/components/password-strength.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +23 -15
- package/dist/components/picker-sheet.js +34 -14
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.d.ts +62 -4
- package/dist/components/popover.js +52 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/search-field.d.ts +25 -7
- package/dist/components/search-field.js +7 -5
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +14 -0
- package/dist/components/series-chart-labels.js +11 -0
- package/dist/components/series-chart-labels.js.map +1 -0
- package/dist/components/series-chart.d.ts +207 -0
- package/dist/components/series-chart.js +223 -0
- package/dist/components/series-chart.js.map +1 -0
- package/dist/components/settings-fields.d.ts +14 -4
- package/dist/components/settings-fields.js +19 -8
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/signature-pad.d.ts +119 -0
- package/dist/components/signature-pad.js +405 -0
- package/dist/components/signature-pad.js.map +1 -0
- package/dist/components/slider.d.ts +80 -0
- package/dist/components/slider.js +155 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sparkline.d.ts +93 -0
- package/dist/components/sparkline.js +205 -0
- package/dist/components/sparkline.js.map +1 -0
- package/dist/components/stat-tile.d.ts +151 -0
- package/dist/components/stat-tile.js +236 -0
- package/dist/components/stat-tile.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +25 -2
- package/dist/components/swipeable-row.js +31 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +60 -0
- package/dist/components/switch.js +81 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/time-input.d.ts +58 -0
- package/dist/components/time-input.js +50 -0
- package/dist/components/time-input.js.map +1 -0
- package/dist/components/toggle-group.d.ts +18 -3
- package/dist/components/toggle-group.js +10 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +115 -0
- package/dist/components/toggle-legend.js +114 -0
- package/dist/components/toggle-legend.js.map +1 -0
- package/dist/components/tooltip.d.ts +40 -11
- package/dist/components/tooltip.js +77 -20
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/treemap.d.ts +132 -0
- package/dist/components/treemap.js +179 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/ui.d.ts +159 -46
- package/dist/components/ui.js +220 -115
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-mobile-reveal.js +1 -0
- package/dist/components/use-mobile-reveal.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/use-table-state.js +38 -17
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +5 -3
- package/dist/components/user-avatar.js +4 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +3 -2
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
- package/dist/data-table.d.ts +7 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/feedback/feedback-attachment.js +12 -11
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.js +2 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +33 -7
- package/dist/feedback/feedback-inbox.js +49 -37
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback.d.ts +5 -0
- package/dist/feedback.js +5 -0
- package/dist/feedback.js.map +1 -0
- package/dist/hooks/use-anchored-panel.js +1 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js +1 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-announce.d.ts +46 -0
- package/dist/hooks/use-announce.js +37 -0
- package/dist/hooks/use-announce.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.js +1 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -1
- package/dist/hooks/use-close-transition.js +1 -0
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-dismiss.js +1 -0
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +16 -0
- package/dist/hooks/use-focus-trap.js +67 -0
- package/dist/hooks/use-focus-trap.js.map +1 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +27 -1
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.js +1 -0
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.d.ts +31 -0
- package/dist/i18n/defaults.js +63 -0
- package/dist/i18n/defaults.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +250 -0
- package/dist/i18n/kit-labels.js +184 -0
- package/dist/i18n/kit-labels.js.map +1 -0
- package/dist/index.d.ts +47 -22
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calc.js +1 -0
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/dates.js +1 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/safe-storage.js +1 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/search/command-palette.d.ts +11 -1
- package/dist/search/command-palette.js +21 -15
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.js +3 -0
- package/dist/search.js.map +1 -0
- package/dist/shell/app-shell.d.ts +33 -4
- package/dist/shell/app-shell.js +275 -78
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +3 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +6 -0
- package/dist/shell/role-switcher.js +7 -4
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar.d.ts +15 -7
- package/dist/shell/top-bar.js +3 -5
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +3 -2
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +5 -2
- package/dist/shell/topbar-controls.js +20 -15
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +13 -0
- package/dist/shell.js +8 -0
- package/dist/shell.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +19 -12
- package/dist/theme/chart-palette.js +24 -30
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/color.d.ts +78 -0
- package/dist/theme/color.js +148 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/palette-derive.d.ts +140 -0
- package/dist/theme/palette-derive.js +376 -0
- package/dist/theme/palette-derive.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +14 -1
- package/dist/theme/palette-presets.js +231 -3
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +1 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.d.ts +2 -1
- package/dist/tour/tour.js +15 -9
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.js +10 -8
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +18 -2
- package/dist/wizard/types.js +4 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +12 -4
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js +1 -0
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js +1 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.js +1 -0
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js +5 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +9 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +66 -13
- package/src/chart.ts +22 -0
- package/src/components/account-settings.tsx +275 -0
- package/src/components/alert-banner.tsx +75 -0
- package/src/components/amount-input.tsx +471 -0
- package/src/components/calculator.tsx +252 -0
- package/src/components/chart-zoom.tsx +499 -0
- package/src/components/chart.tsx +363 -0
- package/src/components/checkbox.tsx +222 -0
- package/src/components/chip.tsx +570 -0
- package/src/components/choice-parts.ts +33 -0
- package/src/components/combobox-core.tsx +540 -0
- package/src/components/combobox.tsx +1026 -0
- package/src/components/currency-select.tsx +269 -0
- package/src/components/data-table-filter-popover.tsx +252 -0
- package/src/components/data-table-filters.ts +176 -0
- package/src/components/data-table-labels.ts +190 -0
- package/src/components/data-table-pagination.tsx +126 -0
- package/src/components/data-table-sort.ts +70 -0
- package/src/components/data-table.tsx +1740 -0
- package/src/components/date-picker.tsx +631 -0
- package/src/components/dropdown.tsx +341 -0
- package/src/components/entity-combobox.tsx +215 -0
- package/src/components/facing-pair.ts +86 -0
- package/src/components/field-sync.tsx +472 -0
- package/src/components/file-dropzone.tsx +181 -0
- package/src/components/full-bleed-dialog.tsx +179 -0
- package/src/components/grouped-picker.tsx +157 -0
- package/src/components/hover-menu.tsx +183 -0
- package/src/components/mini-calendar.tsx +554 -0
- package/src/components/modal.tsx +287 -0
- package/src/components/month-picker.tsx +549 -0
- package/src/components/multi-entity-combobox.tsx +226 -0
- package/src/components/multi-select.tsx +303 -0
- package/src/components/number-field.tsx +225 -0
- package/src/components/number-input.tsx +211 -0
- package/src/components/numpad-sheet.tsx +264 -0
- package/src/components/page-contents.tsx +289 -0
- package/src/components/password-strength.tsx +258 -0
- package/src/components/picker-sheet.tsx +251 -0
- package/src/components/popover.tsx +195 -0
- package/src/components/search-field.tsx +146 -0
- package/src/components/series-chart-labels.ts +26 -0
- package/src/components/series-chart.tsx +481 -0
- package/src/components/settings-fields.tsx +68 -0
- package/src/components/signature-pad.tsx +630 -0
- package/src/components/slider.tsx +313 -0
- package/src/components/sparkline.tsx +329 -0
- package/src/components/stat-tile.tsx +448 -0
- package/src/components/swipeable-row.tsx +271 -0
- package/src/components/switch.tsx +170 -0
- package/src/components/time-input.tsx +107 -0
- package/src/components/toggle-group.tsx +153 -0
- package/src/components/toggle-legend.tsx +229 -0
- package/src/components/tooltip.tsx +477 -0
- package/src/components/treemap.tsx +390 -0
- package/src/components/ui.tsx +1284 -0
- package/src/components/use-mobile-reveal.ts +54 -0
- package/src/components/use-table-state.ts +295 -0
- package/src/components/user-avatar.tsx +61 -0
- package/src/components/wizard-stepper.tsx +37 -0
- package/src/data-table.ts +19 -0
- package/src/feedback/feedback-attachment.tsx +246 -0
- package/src/feedback/feedback-dialog.tsx +209 -0
- package/src/feedback/feedback-inbox.tsx +615 -0
- package/src/feedback.ts +8 -0
- package/src/hooks/use-anchored-panel.ts +169 -0
- package/src/hooks/use-anchored-rect.ts +78 -0
- package/src/hooks/use-announce.ts +85 -0
- package/src/hooks/use-body-scroll-lock.ts +94 -0
- package/src/hooks/use-close-transition.ts +99 -0
- package/src/hooks/use-dismiss.ts +77 -0
- package/src/hooks/use-focus-trap.ts +160 -0
- package/src/hooks/use-media-query.ts +23 -0
- package/src/hooks/use-overlay-history.ts +345 -0
- package/src/hooks/use-row-swipe.ts +212 -0
- package/src/i18n/defaults.ts +68 -0
- package/src/i18n/kit-labels.tsx +447 -0
- package/src/index.ts +169 -0
- package/src/lib/calc.ts +309 -0
- package/src/lib/cn.ts +5 -0
- package/src/lib/dates.ts +187 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/safe-storage.ts +37 -0
- package/src/search/command-palette.tsx +342 -0
- package/src/search.ts +3 -0
- package/src/shell/app-shell.tsx +772 -0
- package/src/shell/option-switcher-menu.tsx +72 -0
- package/src/shell/role-switcher.tsx +46 -0
- package/src/shell/top-bar.tsx +39 -0
- package/src/shell/topbar-action-menu.tsx +91 -0
- package/src/shell/topbar-controls.tsx +229 -0
- package/src/shell.ts +10 -0
- package/src/theme/chart-palette.ts +145 -0
- package/src/theme/color.ts +266 -0
- package/src/theme/palette-derive.ts +681 -0
- package/src/theme/palette-presets.ts +760 -0
- package/src/theme/palette-store.ts +94 -0
- package/src/theme/theme-store.ts +145 -0
- package/src/tour/tour.tsx +586 -0
- package/src/tour.ts +3 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wizard/stepper-nav.tsx +221 -0
- package/src/wizard/types.ts +178 -0
- package/src/wizard/use-wizard.ts +345 -0
- package/src/wizard/validation.ts +37 -0
- package/src/wizard/wizard-context.tsx +29 -0
- package/src/wizard/wizard-step.tsx +14 -0
- package/src/wizard/wizard-summary.tsx +69 -0
- package/src/wizard.ts +14 -0
- package/tokens.css +222 -20
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { Pencil } from "lucide-react";
|
|
3
4
|
import { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from "../components/ui.js";
|
|
4
|
-
import {
|
|
5
|
+
import { DEFAULT_WIZARD_LABELS } from "./types.js";
|
|
6
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
7
|
function WizardSummary({
|
|
6
8
|
sections,
|
|
7
9
|
onEditStep,
|
|
8
10
|
labels
|
|
9
11
|
}) {
|
|
10
|
-
const l =
|
|
12
|
+
const l = useKitLabels("wizard", DEFAULT_WIZARD_LABELS, labels);
|
|
11
13
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
12
14
|
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-[var(--text-primary)]", children: l.reviewTitle }),
|
|
13
15
|
sections.map((section, sIdx) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
@@ -28,7 +30,7 @@ function WizardSummary({
|
|
|
28
30
|
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("dl", { className: "grid gap-2 text-sm", children: section.items.map((item, iIdx) => /* @__PURE__ */ jsxs("div", { children: [
|
|
29
31
|
iIdx > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 border-t border-[var(--border)]", role: "presentation" }),
|
|
30
32
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
|
|
31
|
-
/* @__PURE__ */ jsx("dt", { className: "text-
|
|
33
|
+
/* @__PURE__ */ jsx("dt", { className: "text-[var(--text-muted)]", children: item.label }),
|
|
32
34
|
/* @__PURE__ */ jsx("dd", { className: "text-right font-medium text-[var(--text-primary)]", children: item.value })
|
|
33
35
|
] })
|
|
34
36
|
] }, iIdx)) }) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/wizard-summary.tsx"],"sourcesContent":["import { Pencil } from \"lucide-react\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from \"../components/ui\";\nimport type { SummarySection, WizardLabels } from \"./types\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/wizard-summary.tsx"],"sourcesContent":["import { Pencil } from \"lucide-react\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from \"../components/ui\";\nimport type { SummarySection, WizardLabels } from \"./types\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The review step: one card per section, each row a label/value pair, each card\n * carrying an edit button back to the step it came from.\n *\n * Sections are data, not children, because \"jump back to step N\" is the whole\n * point of the component and a caller assembling its own cards invariably drops\n * it. Labels and values are `ReactNode`, so a value can be a badge or a link.\n */\nexport function WizardSummary({\n sections,\n onEditStep,\n labels,\n}: {\n sections: SummarySection[];\n onEditStep: (stepIndex: number) => void;\n labels?: Partial<WizardLabels>;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold text-[var(--text-primary)]\">{l.reviewTitle}</h2>\n {sections.map((section, sIdx) => (\n <Card key={sIdx}>\n {/* CardHeader is a grid, and CardAction is the top-right column it\n reserves — the edit button has to go through it or it stacks under\n the title instead of sitting beside it. */}\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-base\">{section.label}</CardTitle>\n <CardAction>\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n aria-label={l.edit}\n title={l.edit}\n onClick={() => onEditStep(section.stepIndex)}\n >\n <Pencil />\n </IconButton>\n </CardAction>\n </CardHeader>\n <CardContent>\n <dl className=\"grid gap-2 text-sm\">\n {section.items.map((item, iIdx) => (\n <div key={iIdx}>\n {iIdx > 0 && (\n <div className=\"mb-2 border-t border-[var(--border)]\" role=\"presentation\" />\n )}\n <div className=\"flex items-start justify-between gap-4\">\n <dt className=\"text-[var(--text-muted)]\">{item.label}</dt>\n <dd className=\"text-right font-medium text-[var(--text-primary)]\">\n {item.value}\n </dd>\n </div>\n </div>\n ))}\n </dl>\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n"],"mappings":";AA2BM,cAMI,YANJ;AA3BN,SAAS,cAAc;AACvB,SAAS,MAAM,YAAY,aAAa,YAAY,WAAW,kBAAkB;AAEjF,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAUtB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,IAAI,aAAa,UAAU,uBAAuB,MAAM;AAE9D,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,QAAG,WAAU,oDAAoD,YAAE,aAAY;AAAA,IAC/E,SAAS,IAAI,CAAC,SAAS,SACtB,qBAAC,QAIC;AAAA,2BAAC,cAAW,WAAU,QACpB;AAAA,4BAAC,aAAU,WAAU,aAAa,kBAAQ,OAAM;AAAA,QAChD,oBAAC,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,cAAY,EAAE;AAAA,YACd,OAAO,EAAE;AAAA,YACT,SAAS,MAAM,WAAW,QAAQ,SAAS;AAAA,YAE3C,8BAAC,UAAO;AAAA;AAAA,QACV,GACF;AAAA,SACF;AAAA,MACA,oBAAC,eACC,8BAAC,QAAG,WAAU,sBACX,kBAAQ,MAAM,IAAI,CAAC,MAAM,SACxB,qBAAC,SACE;AAAA,eAAO,KACN,oBAAC,SAAI,WAAU,wCAAuC,MAAK,gBAAe;AAAA,QAE5E,qBAAC,SAAI,WAAU,0CACb;AAAA,8BAAC,QAAG,WAAU,4BAA4B,eAAK,OAAM;AAAA,UACrD,oBAAC,QAAG,WAAU,qDACX,eAAK,OACR;AAAA,WACF;AAAA,WATQ,IAUV,CACD,GACH,GACF;AAAA,SAlCS,IAmCX,CACD;AAAA,KACH;AAEJ;","names":[]}
|
package/dist/wizard.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { DEFAULT_WIZARD_LABELS, FieldErrors, StepStatus, SummaryItem, SummarySection, UseWizardOptions, UseWizardReturn, ValidateResult, WizardLabels, WizardStepConfig, resolveWizardLabels } from './wizard/types.js';
|
|
2
|
+
export { useWizard } from './wizard/use-wizard.js';
|
|
3
|
+
export { WizardContextProvider, WizardContextValue, useWizardContext } from './wizard/wizard-context.js';
|
|
4
|
+
export { RequiredFieldSpec, requiredFieldsValidator } from './wizard/validation.js';
|
|
5
|
+
export { WizardStep } from './wizard/wizard-step.js';
|
|
6
|
+
export { WizardSummary } from './wizard/wizard-summary.js';
|
|
7
|
+
export { StepperNav } from './wizard/stepper-nav.js';
|
|
8
|
+
import 'react';
|
package/dist/wizard.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
export * from "./wizard/types.js";
|
|
3
|
+
export * from "./wizard/use-wizard.js";
|
|
4
|
+
export * from "./wizard/wizard-context.js";
|
|
5
|
+
export * from "./wizard/validation.js";
|
|
6
|
+
export * from "./wizard/wizard-step.js";
|
|
7
|
+
export * from "./wizard/wizard-summary.js";
|
|
8
|
+
export * from "./wizard/stepper-nav.js";
|
|
9
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wizard.ts"],"sourcesContent":["// `@eifi1/ui-kit/wizard` — the multi-step engine, its chrome and its review step.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: useWizard syncs the active step to `?step=` via useSearchParams.\n//\n// NOT the same thing as `WizardStepper` in components/wizard-stepper, which is a bare\n// two-step indicator with no engine and stays on the main barrel.\nexport * from \"./wizard/types\";\nexport * from \"./wizard/use-wizard\";\nexport * from \"./wizard/wizard-context\";\nexport * from \"./wizard/validation\";\nexport * from \"./wizard/wizard-step\";\nexport * from \"./wizard/wizard-summary\";\nexport * from \"./wizard/stepper-nav\";\n"],"mappings":";AAOA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
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.",
|
|
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.",
|
|
7
7
|
"sideEffects": [
|
|
8
8
|
"**/*.css"
|
|
9
9
|
],
|
|
@@ -12,6 +12,34 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"import": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./chart": {
|
|
16
|
+
"types": "./dist/chart.d.ts",
|
|
17
|
+
"import": "./dist/chart.js"
|
|
18
|
+
},
|
|
19
|
+
"./shell": {
|
|
20
|
+
"types": "./dist/shell.d.ts",
|
|
21
|
+
"import": "./dist/shell.js"
|
|
22
|
+
},
|
|
23
|
+
"./data-table": {
|
|
24
|
+
"types": "./dist/data-table.d.ts",
|
|
25
|
+
"import": "./dist/data-table.js"
|
|
26
|
+
},
|
|
27
|
+
"./wizard": {
|
|
28
|
+
"types": "./dist/wizard.d.ts",
|
|
29
|
+
"import": "./dist/wizard.js"
|
|
30
|
+
},
|
|
31
|
+
"./tour": {
|
|
32
|
+
"types": "./dist/tour.d.ts",
|
|
33
|
+
"import": "./dist/tour.js"
|
|
34
|
+
},
|
|
35
|
+
"./feedback": {
|
|
36
|
+
"types": "./dist/feedback.d.ts",
|
|
37
|
+
"import": "./dist/feedback.js"
|
|
38
|
+
},
|
|
39
|
+
"./search": {
|
|
40
|
+
"types": "./dist/search.d.ts",
|
|
41
|
+
"import": "./dist/search.js"
|
|
42
|
+
},
|
|
15
43
|
"./dates": {
|
|
16
44
|
"types": "./dist/lib/dates.d.ts",
|
|
17
45
|
"import": "./dist/lib/dates.js"
|
|
@@ -21,52 +49,74 @@
|
|
|
21
49
|
},
|
|
22
50
|
"files": [
|
|
23
51
|
"dist",
|
|
24
|
-
"
|
|
52
|
+
"src",
|
|
53
|
+
"!src/**/__tests__",
|
|
54
|
+
"!src/test",
|
|
55
|
+
"tokens.css",
|
|
56
|
+
"LICENSE"
|
|
25
57
|
],
|
|
26
58
|
"scripts": {
|
|
27
59
|
"typecheck": "tsc --noEmit",
|
|
60
|
+
"lint": "eslint .",
|
|
61
|
+
"check:tokens": "node scripts/check-token-discipline.mjs",
|
|
62
|
+
"check": "npm run typecheck && npm run lint && npm run check:tokens && npm test && npm run build && node scripts/verify-package.mjs && npm run build:showcase",
|
|
28
63
|
"test": "vitest run",
|
|
29
64
|
"test:watch": "vitest",
|
|
30
65
|
"build": "tsup && node scripts/fix-esm-extensions.mjs",
|
|
66
|
+
"verify:package": "npm run build && node scripts/verify-package.mjs",
|
|
67
|
+
"dev": "tsup --watch",
|
|
68
|
+
"dev:showcase": "vite --config showcase/vite.config.ts",
|
|
69
|
+
"build:showcase": "vite build --config showcase/vite.config.ts",
|
|
70
|
+
"preview:showcase": "vite preview --config showcase/vite.config.ts",
|
|
31
71
|
"prepublishOnly": "npm run build"
|
|
32
72
|
},
|
|
33
73
|
"peerDependencies": {
|
|
74
|
+
"lucide-react": ">=0.400.0 <2",
|
|
34
75
|
"react": ">=19",
|
|
35
76
|
"react-dom": ">=19",
|
|
36
77
|
"react-router": ">=7",
|
|
37
|
-
"recharts": ">=3",
|
|
78
|
+
"recharts": ">=3.8",
|
|
38
79
|
"sonner": ">=2"
|
|
39
80
|
},
|
|
40
81
|
"peerDependenciesMeta": {
|
|
41
|
-
"react-router": {
|
|
42
|
-
"optional": true
|
|
43
|
-
},
|
|
44
|
-
"recharts": {
|
|
45
|
-
"optional": true
|
|
46
|
-
},
|
|
47
82
|
"sonner": {
|
|
48
83
|
"optional": true
|
|
49
84
|
}
|
|
50
85
|
},
|
|
86
|
+
"_dependencies": "Only what the package imports AND owns the version of. `lucide-react` is a PEER: every consumer already declares `lucide-react: ^0.511.0` itself, so a hard dependency here bought each of them a second copy of the icon set in node_modules — and pinned to 0.511.x it held all three behind a release line that is now at 1.x. The range is deliberately wide (`>=0.400.0 <2`): the kit imports named icon components and nothing else, which has been stable across that whole span, so the app picks the version. `flag-icons` was never imported at all — 5.6 MB of stylesheet and SVG installed into every consumer to support class NAMES (`fi fi-xx`) that CurrencyFlag, CurrencySelect and LanguageMenu render as strings. The host supplies that stylesheet; see the dependency section of README.md. It stays a devDependency because the showcase imports it.",
|
|
51
87
|
"dependencies": {
|
|
52
88
|
"clsx": "^2.1.1",
|
|
53
|
-
"flag-icons": "^7.5.0",
|
|
54
|
-
"lucide-react": "^0.511.0",
|
|
55
89
|
"tailwind-merge": "^3.3.0",
|
|
56
90
|
"zustand": "^5.0.5"
|
|
57
91
|
},
|
|
58
92
|
"devDependencies": {
|
|
93
|
+
"@eslint/js": "^9.39.5",
|
|
94
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
59
95
|
"@testing-library/jest-dom": "^6.6.3",
|
|
60
96
|
"@testing-library/react": "^16.3.0",
|
|
97
|
+
"@testing-library/user-event": "^14.6.7",
|
|
98
|
+
"@types/node": "^24.13.6",
|
|
61
99
|
"@types/react": "^19.2.14",
|
|
62
100
|
"@types/react-dom": "^19.2.3",
|
|
63
101
|
"@vitejs/plugin-react": "^4.5.2",
|
|
102
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
103
|
+
"eslint": "^9.39.5",
|
|
104
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
105
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
106
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
107
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
108
|
+
"flag-icons": "^7.5.0",
|
|
109
|
+
"globals": "^17.12.0",
|
|
64
110
|
"jsdom": "^30.0.1",
|
|
111
|
+
"lucide-react": "^0.511.0",
|
|
65
112
|
"react-router": "^8.3.0",
|
|
66
113
|
"recharts": "^3.8.1",
|
|
67
114
|
"sonner": "^2.0.7",
|
|
115
|
+
"tailwindcss": "^4.3.3",
|
|
68
116
|
"tsup": "^8.5.0",
|
|
69
117
|
"typescript": "~5.9.3",
|
|
118
|
+
"typescript-eslint": "^8.70.1",
|
|
119
|
+
"vite": "^7.3.6",
|
|
70
120
|
"vitest": "^4.1.11"
|
|
71
121
|
},
|
|
72
122
|
"license": "MIT",
|
|
@@ -83,5 +133,8 @@
|
|
|
83
133
|
},
|
|
84
134
|
"main": "./dist/index.js",
|
|
85
135
|
"module": "./dist/index.js",
|
|
86
|
-
"types": "./dist/index.d.ts"
|
|
136
|
+
"types": "./dist/index.d.ts",
|
|
137
|
+
"engines": {
|
|
138
|
+
"node": ">=20.18"
|
|
139
|
+
}
|
|
87
140
|
}
|
package/src/chart.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// `@eifi1/ui-kit/chart` — the chart shell and its colour system.
|
|
2
|
+
//
|
|
3
|
+
// A re-slicing of what the main barrel already exports, not a new API. It exists so a
|
|
4
|
+
// consumer can reach the chart kit WITHOUT the barrel: `import("@eifi1/ui-kit/chart")`
|
|
5
|
+
// is a 31KB chunk, where the same lazy import through the barrel pulls 221KB, because
|
|
6
|
+
// a dynamic import of the barrel cannot be tree-shaken down to one of its members.
|
|
7
|
+
//
|
|
8
|
+
// This is also the only entry point in the package that requires `recharts`.
|
|
9
|
+
export * from "./components/chart";
|
|
10
|
+
export * from "./theme/chart-palette";
|
|
11
|
+
|
|
12
|
+
// The tile chart (treemap) the apps share — moved here from keksdose so every consumer
|
|
13
|
+
// derives it from the package rather than from a copy.
|
|
14
|
+
export * from "./components/treemap";
|
|
15
|
+
|
|
16
|
+
// lenkbank's zoomable series chart, its synced x-zoom, the toggle legend and the
|
|
17
|
+
// mirrored-pair axis geometry — moved here so the apps derive them from the package.
|
|
18
|
+
export * from "./components/series-chart";
|
|
19
|
+
export * from "./components/chart-zoom";
|
|
20
|
+
export * from "./components/toggle-legend";
|
|
21
|
+
export * from "./components/facing-pair";
|
|
22
|
+
export * from "./components/series-chart-labels";
|
|
@@ -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
|
+
}
|