@eifi1/ui-kit 0.48.2 → 0.50.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 +34 -16
- package/dist/admin/admin-action-confirm.js +1 -1
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.js +5 -1
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/user-roster.d.ts +24 -12
- package/dist/admin/user-roster.js +21 -6
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/accept-invitation.js +2 -2
- package/dist/auth/accept-invitation.js.map +1 -1
- package/dist/auth/not-found-page.js +1 -1
- package/dist/auth/not-found-page.js.map +1 -1
- package/dist/auth/reset-password-form.js +2 -2
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/verify-email.d.ts +9 -1
- package/dist/auth/verify-email.js +5 -3
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/plan-card.js +2 -2
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -1
- package/dist/components/amount-input.js +3 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +1 -0
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/breadcrumbs.d.ts +19 -1
- package/dist/components/breadcrumbs.js +11 -4
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-look.d.ts +37 -7
- package/dist/components/button-look.js +33 -6
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +4 -1
- package/dist/components/button.js +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -1
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chart-edge.d.ts +10 -0
- package/dist/components/chart-edge.js +57 -0
- package/dist/components/chart-edge.js.map +1 -0
- package/dist/components/chart.js +2 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/chip.d.ts +8 -1
- package/dist/components/chip.js +4 -1
- package/dist/components/chip.js.map +1 -1
- package/dist/components/combobox-core.js +1 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox-suggestions.js +1 -0
- package/dist/components/combobox-suggestions.js.map +1 -1
- package/dist/components/confirm-context.d.ts +5 -0
- package/dist/components/confirm-context.js +7 -0
- package/dist/components/confirm-context.js.map +1 -0
- package/dist/components/confirm-dialog.js +3 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/currency-select.d.ts +28 -2
- package/dist/components/currency-select.js +56 -10
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +2 -2
- package/dist/components/danger-confirm.js +1 -1
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-fit.js +21 -3
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +58 -3
- package/dist/components/data-table-mobile.js +147 -16
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -1
- package/dist/components/data-table-row-actions.js +7 -0
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +1 -1
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table.d.ts +34 -1
- package/dist/components/data-table.js +37 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dropdown.js +1 -0
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/floating-panel.d.ts +36 -13
- package/dist/components/floating-panel.js +27 -7
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/form-actions.js +1 -1
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +1 -0
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/icon-button.d.ts +11 -0
- package/dist/components/icon-button.js +7 -5
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/modal.js +1 -0
- package/dist/components/modal.js.map +1 -1
- package/dist/components/nav-pills.d.ts +8 -1
- package/dist/components/nav-pills.js +61 -38
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -1
- package/dist/components/number-input.js +6 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.js +2 -0
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-header-context.d.ts +8 -0
- package/dist/components/page-header-context.js +7 -0
- package/dist/components/page-header-context.js.map +1 -0
- package/dist/components/page-header.d.ts +14 -2
- package/dist/components/page-header.js +10 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -0
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart.js +2 -1
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.js +1 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress-bar.js +5 -4
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.js +1 -1
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/row-actions-labels.js +3 -1
- package/dist/components/row-actions-labels.js.map +1 -1
- package/dist/components/row-actions.d.ts +43 -9
- package/dist/components/row-actions.js +78 -15
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +37 -0
- package/dist/components/section-nav.js +33 -0
- package/dist/components/section-nav.js.map +1 -0
- package/dist/components/series-chart.js +8 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +1 -1
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/signature-pad.js +1 -1
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/sparkline.js +2 -1
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.js +14 -7
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +5 -1
- package/dist/components/status-dot.js +4 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review-editor.js +2 -2
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap.d.ts +5 -2
- package/dist/components/treemap.js +26 -6
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.js +37 -6
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-banner.js +1 -1
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/demo/demo-ended.js +1 -1
- package/dist/demo/demo-ended.js.map +1 -1
- package/dist/demo/demo-start.js +1 -1
- package/dist/demo/demo-start.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-inbox.js +1 -1
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +9 -1
- package/dist/hooks/use-dismiss.js +25 -5
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-document-title.d.ts +14 -0
- package/dist/hooks/use-document-title.js +22 -0
- package/dist/hooks/use-document-title.js.map +1 -0
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +20 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +34 -1
- package/dist/i18n/kit-labels.js +43 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +22 -3
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +20 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +20 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +20 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +20 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +3 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +1 -0
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.js +2 -2
- package/dist/landing/landing-actions.js.map +1 -1
- package/dist/landing/public-header.js +2 -2
- package/dist/landing/public-header.js.map +1 -1
- package/dist/lib/clipboard-minus.d.ts +27 -0
- package/dist/lib/clipboard-minus.js +63 -0
- package/dist/lib/clipboard-minus.js.map +1 -0
- package/dist/lib/data-attributes.d.ts +11 -0
- package/dist/lib/data-attributes.js +10 -0
- package/dist/lib/data-attributes.js.map +1 -0
- package/dist/lib/document-title.d.ts +17 -0
- package/dist/lib/document-title.js +29 -0
- package/dist/lib/document-title.js.map +1 -0
- package/dist/lib/float-clearance.js +8 -2
- package/dist/lib/float-clearance.js.map +1 -1
- package/dist/lib/format.d.ts +36 -10
- package/dist/lib/format.js +57 -11
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/minus.d.ts +32 -0
- package/dist/lib/minus.js +14 -0
- package/dist/lib/minus.js.map +1 -0
- package/dist/lib/status-tone.d.ts +30 -0
- package/dist/lib/status-tone.js +17 -0
- package/dist/lib/status-tone.js.map +1 -0
- package/dist/settings/settings-layout.js +3 -2
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +1 -1
- package/dist/shell/account-menu-language.js +9 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/app-shell-labels.js +2 -1
- package/dist/shell/app-shell-labels.js.map +1 -1
- package/dist/shell/app-shell.d.ts +58 -16
- package/dist/shell/app-shell.js +151 -43
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/language-code.d.ts +3 -0
- package/dist/shell/language-code.js +8 -0
- package/dist/shell/language-code.js.map +1 -0
- package/dist/shell/topbar-controls.d.ts +14 -6
- package/dist/shell/topbar-controls.js +14 -17
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +1 -1
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/testing/react.d.ts +25 -0
- package/dist/testing/react.js +19 -0
- package/dist/testing/react.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +82 -15
- package/dist/theme/chart-palette.js +51 -24
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +9 -0
- package/dist/theme/palette-presets.js +77 -62
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +42 -0
- package/dist/tour/tour-dock.js +80 -0
- package/dist/tour/tour-dock.js.map +1 -0
- package/dist/tour/tour-gates.d.ts +71 -0
- package/dist/tour/tour-gates.js +114 -0
- package/dist/tour/tour-gates.js.map +1 -0
- package/dist/tour/tour-guide-button.d.ts +27 -0
- package/dist/tour/tour-guide-button.js +45 -0
- package/dist/tour/tour-guide-button.js.map +1 -0
- package/dist/tour/tour-guide-labels.d.ts +8 -0
- package/dist/tour/tour-guide-labels.js +9 -0
- package/dist/tour/tour-guide-labels.js.map +1 -0
- package/dist/tour/tour-labels.js +9 -1
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +133 -9
- package/dist/tour/tour.js +594 -70
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-url-sync.d.ts +5 -2
- package/dist/tour/use-tour-url-sync.js +7 -1
- package/dist/tour/use-tour-url-sync.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +18 -5
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +11 -0
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +11 -5
- package/dist/wizard/use-wizard.js.map +1 -1
- package/package.json +9 -1
- package/src/admin/admin-action-confirm.tsx +1 -1
- package/src/admin/invitations-panel.tsx +7 -3
- package/src/admin/user-roster.tsx +41 -14
- package/src/auth/accept-invitation.tsx +3 -3
- package/src/auth/not-found-page.tsx +1 -1
- package/src/auth/reset-password-form.tsx +3 -3
- package/src/auth/verify-email.tsx +13 -3
- package/src/billing/plan-card.tsx +2 -2
- package/src/components/amount-input.tsx +5 -4
- package/src/components/autocomplete.tsx +2 -0
- package/src/components/breadcrumbs.tsx +32 -4
- package/src/components/button-look.ts +86 -15
- package/src/components/button.tsx +6 -3
- package/src/components/calendar-heatmap.tsx +2 -1
- package/src/components/chart-edge.ts +73 -0
- package/src/components/chart.tsx +3 -1
- package/src/components/chip.tsx +10 -1
- package/src/components/combobox-core.tsx +2 -0
- package/src/components/combobox-suggestions.tsx +2 -0
- package/src/components/confirm-context.ts +7 -0
- package/src/components/confirm-dialog.tsx +3 -3
- package/src/components/currency-select.tsx +95 -6
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-fit.ts +41 -6
- package/src/components/data-table-mobile.tsx +259 -17
- package/src/components/data-table-row-actions.tsx +9 -1
- package/src/components/data-table-row.tsx +3 -0
- package/src/components/data-table.tsx +91 -19
- package/src/components/dropdown.tsx +2 -0
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/floating-panel.tsx +67 -16
- package/src/components/form-actions.tsx +2 -2
- package/src/components/full-bleed-dialog.tsx +3 -0
- package/src/components/icon-button.tsx +21 -8
- package/src/components/modal.tsx +3 -0
- package/src/components/nav-pills.tsx +29 -2
- package/src/components/number-input.tsx +8 -3
- package/src/components/numpad-sheet.tsx +4 -0
- package/src/components/page-header-context.ts +8 -0
- package/src/components/page-header.tsx +34 -2
- package/src/components/picker-sheet.tsx +4 -0
- package/src/components/pie-chart.tsx +2 -1
- package/src/components/popover.tsx +2 -0
- package/src/components/progress-bar.tsx +6 -3
- package/src/components/reauth-dialog.tsx +1 -1
- package/src/components/row-actions-labels.ts +2 -0
- package/src/components/row-actions.tsx +154 -33
- package/src/components/section-nav.tsx +65 -0
- package/src/components/series-chart.tsx +12 -3
- package/src/components/share-card.tsx +1 -1
- package/src/components/signature-pad.tsx +1 -1
- package/src/components/sparkline.tsx +2 -1
- package/src/components/stat-tile.tsx +19 -7
- package/src/components/status-dot.tsx +7 -1
- package/src/components/tooltip.tsx +3 -1
- package/src/components/translation-review-editor.tsx +2 -2
- package/src/components/translation-review.tsx +2 -2
- package/src/components/treemap.tsx +34 -8
- package/src/components/use-table-state.ts +63 -8
- package/src/data-table.ts +3 -3
- package/src/demo/demo-banner.tsx +1 -1
- package/src/demo/demo-ended.tsx +1 -1
- package/src/demo/demo-start.tsx +1 -1
- package/src/feedback/feedback-inbox.tsx +1 -1
- package/src/hooks/use-dismiss.ts +43 -4
- package/src/hooks/use-document-title.ts +31 -0
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +17 -0
- package/src/i18n/kit-labels.tsx +79 -1
- package/src/i18n/locales/es.ts +17 -0
- package/src/i18n/locales/fr.ts +17 -0
- package/src/i18n/locales/hu.ts +17 -0
- package/src/i18n/locales/it.ts +17 -0
- package/src/i18n/locales/zh.ts +17 -0
- package/src/i18n/review.ts +3 -0
- package/src/i18n/shared-labels.ts +1 -0
- package/src/index.ts +13 -0
- package/src/landing/landing-actions.tsx +2 -2
- package/src/landing/public-header.tsx +2 -2
- package/src/lib/clipboard-minus.ts +94 -0
- package/src/lib/data-attributes.ts +14 -0
- package/src/lib/document-title.ts +41 -0
- package/src/lib/float-clearance.ts +19 -3
- package/src/lib/format.ts +117 -30
- package/src/lib/minus.ts +50 -0
- package/src/lib/status-tone.ts +43 -0
- package/src/settings/settings-layout.tsx +3 -2
- package/src/shell/account-menu-language.tsx +14 -2
- package/src/shell/app-shell-labels.ts +1 -0
- package/src/shell/app-shell.tsx +291 -68
- package/src/shell/language-code.ts +5 -0
- package/src/shell/topbar-controls.tsx +35 -15
- package/src/testing/css-variables.generated.ts +6 -6
- package/src/testing/react.ts +59 -0
- package/src/theme/chart-palette.ts +102 -20
- package/src/theme/palette-presets.ts +121 -84
- package/src/tour/tour-dock.ts +132 -0
- package/src/tour/tour-gates.ts +195 -0
- package/src/tour/tour-guide-button.tsx +64 -0
- package/src/tour/tour-guide-labels.ts +15 -0
- package/src/tour/tour-labels.ts +8 -0
- package/src/tour/tour.tsx +949 -86
- package/src/tour/use-tour-url-sync.ts +14 -4
- package/src/tour.ts +3 -0
- package/src/wizard/stepper-nav.tsx +27 -7
- package/src/wizard/types.ts +12 -0
- package/src/wizard/use-wizard.ts +16 -9
- package/tokens.css +28 -18
package/src/lib/format.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useKitLocale, useKitTimeZone } from "../i18n/kit-labels";
|
|
3
3
|
import { parseIsoDate } from "./dates";
|
|
4
|
+
import { formatWithMinus, type MinusStyle } from "./minus";
|
|
4
5
|
import { roundHalfAway } from "./round";
|
|
5
6
|
|
|
7
|
+
export type { MinusStyle } from "./minus";
|
|
8
|
+
// 0.50 (KIT-1 follow-up): the kit's minus for an app's OWN `Intl.NumberFormat` wrapper.
|
|
9
|
+
export { formatWithMinus } from "./minus";
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Number, money, percentage, date and relative-time formatting — `Intl`, in one place,
|
|
8
13
|
* for the figures an app prints OUTSIDE a kit component.
|
|
@@ -19,9 +24,18 @@ import { roundHalfAway } from "./round";
|
|
|
19
24
|
* functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the
|
|
20
25
|
* kit components on it.
|
|
21
26
|
*
|
|
22
|
-
* A missing value — `null`, `undefined`,
|
|
23
|
-
*
|
|
24
|
-
* "
|
|
27
|
+
* A missing value — `null`, `undefined`, an unparseable date — formats as `empty`
|
|
28
|
+
* (default "—", the dash `StatTile` shows for no value), never as "NaN" or "Invalid
|
|
29
|
+
* Date". A number may also arrive as a numeric STRING (0.49, KIT-2) — "1850.00", "-12.5",
|
|
30
|
+
* "0": a Decimal field off a Python API — and is formatted like the number. Anything
|
|
31
|
+
* else (a non-numeric string, an object, `NaN`, ±`Infinity`) prints `empty` too, but is
|
|
32
|
+
* a bug upstream rather than a gap in the data, so a development build says so on the
|
|
33
|
+
* console, once per call site. Zero is a figure: `0` and `"0"` print "CHF 0.00", never
|
|
34
|
+
* the dash.
|
|
35
|
+
*
|
|
36
|
+
* A negative figure carries the typographic minus, U+2212 (0.49, KIT-1), in every
|
|
37
|
+
* locale — `Intl` writes a hyphen in most. `minus: "ascii"` for text a machine reads
|
|
38
|
+
* back (a CSV, the clipboard).
|
|
25
39
|
*/
|
|
26
40
|
|
|
27
41
|
/** What every formatter here prints for a missing value, unless told otherwise. */
|
|
@@ -47,6 +61,14 @@ export interface FormatNumberBaseOptions extends BaseOptions {
|
|
|
47
61
|
signDisplay?: Intl.NumberFormatOptions["signDisplay"];
|
|
48
62
|
/** Any further `Intl.NumberFormat` options, merged over the ones above. */
|
|
49
63
|
options?: Intl.NumberFormatOptions;
|
|
64
|
+
/**
|
|
65
|
+
* The minus sign of a negative figure (0.49, KIT-1). `"typographic"` (default):
|
|
66
|
+
* U+2212, as wide as "+" and the digits, as `SignedAmount` prints it — in every
|
|
67
|
+
* locale, also where `Intl` writes a hyphen. `"ascii"`: U+002D, for an export a
|
|
68
|
+
* machine parses back (a CSV download, the clipboard) — there, also pass
|
|
69
|
+
* `options: { useGrouping: false }` or write the raw number.
|
|
70
|
+
*/
|
|
71
|
+
minus?: MinusStyle;
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */
|
|
@@ -115,44 +137,113 @@ function numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {
|
|
|
115
137
|
};
|
|
116
138
|
}
|
|
117
139
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
140
|
+
/**
|
|
141
|
+
* What the number formatters take (0.49, KIT-2): a number, a numeric string ("1850.00",
|
|
142
|
+
* "-12.5", "1e3" — what a Decimal arrives as in JSON), or `null` / `undefined` for no
|
|
143
|
+
* value. A string is read with `Number()`, so a figure with more significant digits than
|
|
144
|
+
* a double holds (about 15–17) is printed as its nearest double.
|
|
145
|
+
*/
|
|
146
|
+
export type FormattableNumber = number | string | null | undefined;
|
|
147
|
+
|
|
148
|
+
/** A plain decimal: optional sign, digits with an optional point, an optional exponent.
|
|
149
|
+
* No grouping marks, no decimal comma, no currency — those are formatted text, and
|
|
150
|
+
* reading them back is a locale's guess. */
|
|
151
|
+
const NUMERIC_STRING = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
152
|
+
|
|
153
|
+
/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a
|
|
154
|
+
* production one (the consumer's bundler makes `import.meta.env.DEV` false) and never
|
|
155
|
+
* under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */
|
|
156
|
+
function devWarnings(): boolean {
|
|
157
|
+
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const warnedSites = new Set<string>();
|
|
161
|
+
/** This module's own frames, skipped to find the caller ("Object.formatMoney" is a
|
|
162
|
+
* `useKitFormat` wrapper). */
|
|
163
|
+
const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|toFormattable|warnInvalid|callSite)\b/;
|
|
164
|
+
|
|
165
|
+
/** The first stack frame outside the formatters: where the bad value was passed. */
|
|
166
|
+
function callSite(): string {
|
|
167
|
+
const frames = (new Error().stack ?? "").split("\n").slice(1);
|
|
168
|
+
return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function describeValue(value: unknown): string {
|
|
172
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") return String(value);
|
|
173
|
+
try {
|
|
174
|
+
return JSON.stringify(value) ?? String(value);
|
|
175
|
+
} catch {
|
|
176
|
+
return Object.prototype.toString.call(value);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A development build's console note about an invalid value — once per call site. */
|
|
181
|
+
function warnInvalid(fn: string, value: unknown): void {
|
|
182
|
+
if (!devWarnings()) return;
|
|
183
|
+
const site = callSite();
|
|
184
|
+
const key = `${fn}|${site || typeof value}`;
|
|
185
|
+
if (warnedSites.has(key)) return;
|
|
186
|
+
warnedSites.add(key);
|
|
187
|
+
const type = Array.isArray(value) ? "array" : typeof value;
|
|
188
|
+
console.warn(
|
|
189
|
+
`[ui-kit] ${fn} got ${describeValue(value)} (${type}), which is not a number: printed the empty value. ` +
|
|
190
|
+
"Pass a number, a numeric string, or null/undefined for a missing value." +
|
|
191
|
+
(site ? `\n at ${site.replace(/^at /, "")}` : ""),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* `value` as a finite number to format, or `undefined` to print `empty`. `null` and
|
|
197
|
+
* `undefined` are a missing value and pass quietly; anything that is neither a finite
|
|
198
|
+
* number nor a numeric string is an invalid one — ±Infinity included (lenkbank, adopting
|
|
199
|
+
* 0.13: a division by zero upstream; "∞" in a money column reads like data) — and a
|
|
200
|
+
* development build warns. `-0` is folded into `0`: zero carries no sign.
|
|
201
|
+
*/
|
|
202
|
+
function toFormattable(fn: string, value: unknown): number | undefined {
|
|
203
|
+
if (value === null || value === undefined) return undefined;
|
|
204
|
+
let n: number | undefined;
|
|
205
|
+
if (typeof value === "number") n = value;
|
|
206
|
+
else if (typeof value === "string" && NUMERIC_STRING.test(value.trim())) n = Number(value.trim());
|
|
207
|
+
if (n === undefined || !Number.isFinite(n)) {
|
|
208
|
+
warnInvalid(fn, value);
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
return n + 0;
|
|
122
212
|
}
|
|
123
213
|
|
|
124
214
|
/** A number in `locale`: "1,234.5", "1.234,5", "1’234.5" — with `unit`, "3.400 N". */
|
|
125
|
-
export function formatNumber(value:
|
|
126
|
-
|
|
127
|
-
|
|
215
|
+
export function formatNumber(value: FormattableNumber, options: FormatNumberOptions = {}): string {
|
|
216
|
+
const n = toFormattable("formatNumber", value);
|
|
217
|
+
if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
218
|
+
const figure = formatWithMinus(numberFormatter(options.locale, numberOptions(options)), n, options.minus);
|
|
128
219
|
return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;
|
|
129
220
|
}
|
|
130
221
|
|
|
131
222
|
/** An amount of `currency` (ISO 4217) in `locale`: "CHF 1’234.50", "1.234,50 €". */
|
|
132
|
-
export function formatMoney(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
): string {
|
|
137
|
-
if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
138
|
-
return numberFormatter(options.locale, {
|
|
223
|
+
export function formatMoney(value: FormattableNumber, currency: string, options: FormatMoneyOptions = {}): string {
|
|
224
|
+
const n = toFormattable("formatMoney", value);
|
|
225
|
+
if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
226
|
+
const formatter = numberFormatter(options.locale, {
|
|
139
227
|
style: "currency",
|
|
140
228
|
currency,
|
|
141
229
|
...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),
|
|
142
230
|
...numberOptions(options),
|
|
143
|
-
})
|
|
231
|
+
});
|
|
232
|
+
return formatWithMinus(formatter, n, options.minus);
|
|
144
233
|
}
|
|
145
234
|
|
|
146
235
|
/** A percentage in `locale` ("12%", "12 %", "+1.25%"). See {@link FormatPercentOptions.ratio}
|
|
147
236
|
* for what `value` means. Default digits: at most one. */
|
|
148
|
-
export function formatPercent(value:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
237
|
+
export function formatPercent(value: FormattableNumber, options: FormatPercentOptions = {}): string {
|
|
238
|
+
const n = toFormattable("formatPercent", value);
|
|
239
|
+
if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
240
|
+
const ratio = options.ratio === false ? n / 100 : n;
|
|
241
|
+
const formatter = numberFormatter(options.locale, {
|
|
152
242
|
style: "percent",
|
|
153
243
|
...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),
|
|
154
244
|
...numberOptions(options),
|
|
155
|
-
})
|
|
245
|
+
});
|
|
246
|
+
return formatWithMinus(formatter, ratio, options.minus);
|
|
156
247
|
}
|
|
157
248
|
|
|
158
249
|
/* ── Dates ─────────────────────────────────────────────────────────────────── */
|
|
@@ -327,13 +418,9 @@ type WithoutLocale<T> = Omit<T, "locale"> & { locale?: string };
|
|
|
327
418
|
export interface KitFormat {
|
|
328
419
|
/** The locale they format in — the provider's, or `undefined` (runtime default). */
|
|
329
420
|
locale: string | undefined;
|
|
330
|
-
formatNumber: (value:
|
|
331
|
-
formatMoney: (
|
|
332
|
-
|
|
333
|
-
currency: string,
|
|
334
|
-
options?: WithoutLocale<FormatMoneyOptions>,
|
|
335
|
-
) => string;
|
|
336
|
-
formatPercent: (value: number | null | undefined, options?: WithoutLocale<FormatPercentOptions>) => string;
|
|
421
|
+
formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;
|
|
422
|
+
formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;
|
|
423
|
+
formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;
|
|
337
424
|
formatDate: (
|
|
338
425
|
value: DateInput,
|
|
339
426
|
style?: FormatDateStyle | Intl.DateTimeFormatOptions,
|
package/src/lib/minus.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one minus sign (0.49, KIT-1). `Intl.NumberFormat` writes a negative with the
|
|
3
|
+
* HYPHEN-MINUS (U+002D) in nearly every locale — en, de-CH, fr, it, es, hu, zh — and
|
|
4
|
+
* with the typographic MINUS SIGN (U+2212) in a few (sv, fi). The kit prints U+2212
|
|
5
|
+
* everywhere, as `SignedAmount` always has: it is as wide as "+" and the tabular
|
|
6
|
+
* digits, so a column of signed figures lines up, and it does not break a line the way
|
|
7
|
+
* a hyphen may.
|
|
8
|
+
*
|
|
9
|
+
* `"ascii"` is the other way round, for text a machine reads back — a CSV, the
|
|
10
|
+
* clipboard: every minus becomes U+002D, whatever the locale wrote, so `Number()` and a
|
|
11
|
+
* spreadsheet parse it.
|
|
12
|
+
*/
|
|
13
|
+
export type MinusStyle = "typographic" | "ascii";
|
|
14
|
+
|
|
15
|
+
/** U+2212 MINUS SIGN. */
|
|
16
|
+
export const TYPOGRAPHIC_MINUS = "\u2212";
|
|
17
|
+
|
|
18
|
+
/** The parts `formatToParts` writes a minus in (the exponent's, under `notation:
|
|
19
|
+
* "scientific"`, too). */
|
|
20
|
+
const MINUS_PARTS: ReadonlySet<string> = new Set(["minusSign", "exponentMinusSign"]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `formatter.format(value)` with every minus sign in `minus` style. Only the parts
|
|
24
|
+
* `Intl` marks as a minus are touched — a hyphen inside a currency's name
|
|
25
|
+
* ("Bosnia-Herzegovina convertible marks") stays — and only when the text holds the
|
|
26
|
+
* other sign at all, so a positive figure costs no `formatToParts`.
|
|
27
|
+
*
|
|
28
|
+
* Public since 0.50 (the KIT-1 follow-up): an app that keeps its OWN `Intl.NumberFormat`
|
|
29
|
+
* wrapper (a currency with app-specific digits, a quantity) calls it in place of
|
|
30
|
+
* `formatter.format(value)`, so its negatives match the kit's "−". Where the wrapper only
|
|
31
|
+
* re-states `formatMoney` / `formatNumber`, route it through those instead.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const eur = new Intl.NumberFormat("en", { style: "currency", currency: "EUR" });
|
|
35
|
+
* formatWithMinus(eur, -150); // "−€150.00"
|
|
36
|
+
* formatWithMinus(eur, -150, "ascii"); // "-€150.00", for a CSV
|
|
37
|
+
*/
|
|
38
|
+
export function formatWithMinus(
|
|
39
|
+
formatter: Intl.NumberFormat,
|
|
40
|
+
value: number,
|
|
41
|
+
minus: MinusStyle = "typographic",
|
|
42
|
+
): string {
|
|
43
|
+
const text = formatter.format(value);
|
|
44
|
+
const [wanted, other] = minus === "ascii" ? ["-", TYPOGRAPHIC_MINUS] : [TYPOGRAPHIC_MINUS, "-"];
|
|
45
|
+
if (!text.includes(other)) return text;
|
|
46
|
+
return formatter
|
|
47
|
+
.formatToParts(value)
|
|
48
|
+
.map((part) => (MINUS_PARTS.has(part.type) ? wanted : part.value))
|
|
49
|
+
.join("");
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a status means, in five domain-free words, and the tone that says it (0.49 KIT-8).
|
|
3
|
+
*
|
|
4
|
+
* Every app paints its statuses by hand: kastlan's ~15 enums each carry a Chip tone
|
|
5
|
+
* (`shared/enums/enum-meta.ts`), keksdose's job and transaction states theirs, and the
|
|
6
|
+
* same meaning drifted into different colours — "unpaid" amber in one table, brand in
|
|
7
|
+
* the next. The kind is the decision ("does the reader have to act?"), the tone only
|
|
8
|
+
* its colour, so an app maps each of its states to a kind once and the colours agree
|
|
9
|
+
* everywhere:
|
|
10
|
+
*
|
|
11
|
+
* | kind | tone | for |
|
|
12
|
+
* |---|---|---|
|
|
13
|
+
* | `good` | `success` | done, settled, healthy: nothing to do (Paid, Verified) |
|
|
14
|
+
* | `attention` | `warning` | needs a look or an action soon, nothing broken yet (Unpaid, Due soon) |
|
|
15
|
+
* | `problem` | `danger` | something is wrong or late and needs action now (Overdue, Failed) |
|
|
16
|
+
* | `active` | `info` | in progress or current, neither good nor bad (Running, Sent) |
|
|
17
|
+
* | `inactive` | `neutral` | over, paused or not started: out of the way (Draft, Archived) |
|
|
18
|
+
*
|
|
19
|
+
* `brand` is not a status tone: it is the app's own colour, and a status in it reads as
|
|
20
|
+
* "this is the app" rather than as a state. Money (`income` / `expense`) is not one
|
|
21
|
+
* either: it says which way an amount flows, not how a record stands.
|
|
22
|
+
*/
|
|
23
|
+
export type StatusKind = "good" | "attention" | "problem" | "active" | "inactive";
|
|
24
|
+
|
|
25
|
+
/** The tones a {@link StatusKind} maps to — a subset of `ChipTone` and `StatusDotTone`. */
|
|
26
|
+
export type StatusTone = "success" | "warning" | "danger" | "info" | "neutral";
|
|
27
|
+
|
|
28
|
+
/** The five kinds, in the order the table above lists them. */
|
|
29
|
+
export const STATUS_KINDS: readonly StatusKind[] = ["good", "attention", "problem", "active", "inactive"];
|
|
30
|
+
|
|
31
|
+
const TONES: Readonly<Record<StatusKind, StatusTone>> = {
|
|
32
|
+
good: "success",
|
|
33
|
+
attention: "warning",
|
|
34
|
+
problem: "danger",
|
|
35
|
+
active: "info",
|
|
36
|
+
inactive: "neutral",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** The tone for a status kind: good → success, attention → warning, problem → danger,
|
|
40
|
+
* active → info, inactive → neutral. See {@link StatusKind}. */
|
|
41
|
+
export function statusTone(kind: StatusKind): StatusTone {
|
|
42
|
+
return TONES[kind];
|
|
43
|
+
}
|
|
@@ -323,6 +323,7 @@ export function SettingsLayout<G extends string>({
|
|
|
323
323
|
<div className={pageClass} data-settings-view="group">
|
|
324
324
|
<PageHeader
|
|
325
325
|
size="compact"
|
|
326
|
+
documentTitle={false}
|
|
326
327
|
title={open.title}
|
|
327
328
|
description={open.help}
|
|
328
329
|
breadcrumbs={
|
|
@@ -347,7 +348,7 @@ export function SettingsLayout<G extends string>({
|
|
|
347
348
|
</div>
|
|
348
349
|
) : (
|
|
349
350
|
<div className={pageClass} data-settings-view="list">
|
|
350
|
-
<PageHeader size="compact" title={pageTitle} />
|
|
351
|
+
<PageHeader size="compact" documentTitle={false} title={pageTitle} />
|
|
351
352
|
{searchField}
|
|
352
353
|
{searching ? (
|
|
353
354
|
results
|
|
@@ -409,7 +410,7 @@ export function SettingsLayout<G extends string>({
|
|
|
409
410
|
);
|
|
410
411
|
body = (
|
|
411
412
|
<div className={pageClass} data-settings-view="desktop">
|
|
412
|
-
<PageHeader size="compact" title={pageTitle} />
|
|
413
|
+
<PageHeader size="compact" documentTitle={false} title={pageTitle} />
|
|
413
414
|
{searchField}
|
|
414
415
|
<div className="md:grid md:grid-cols-[14rem_minmax(0,1fr)] md:gap-6">
|
|
415
416
|
{withSidebarFooter(
|
|
@@ -4,6 +4,7 @@ import { Globe } from "lucide-react";
|
|
|
4
4
|
import { Disclosure } from "../components/disclosure";
|
|
5
5
|
import { MenuItem } from "../components/menu-item";
|
|
6
6
|
import { DEFAULT_TOP_BAR_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
7
|
+
import { languageCodeLabel } from "./language-code";
|
|
7
8
|
|
|
8
9
|
/** One offered language in the account menu's sub-list. */
|
|
9
10
|
export interface AccountMenuLanguageOption {
|
|
@@ -56,7 +57,7 @@ export interface AccountMenuLanguageProps {
|
|
|
56
57
|
/**
|
|
57
58
|
* The language choice INSIDE the account menu (docs/top-bar-harmonization.md §2.3),
|
|
58
59
|
* keksdose's sub-list: a `Disclosure variant="menu"` row — the globe, "Language", the
|
|
59
|
-
* active language's flag and the chevron — that opens in place onto one checked
|
|
60
|
+
* active language's flag and code ("DE") and the chevron — that opens in place onto one checked
|
|
60
61
|
* `MenuItem` per language. Inline rather than a nested popover: a popover inside a
|
|
61
62
|
* popover fights the outside click and has nowhere to go on a phone (keksdose #140).
|
|
62
63
|
*
|
|
@@ -87,7 +88,18 @@ export function AccountMenuLanguage({
|
|
|
87
88
|
<Globe aria-hidden className="size-4" />
|
|
88
89
|
{label ?? words.language}
|
|
89
90
|
</span>
|
|
90
|
-
{active &&
|
|
91
|
+
{active && (
|
|
92
|
+
// The flag alone could not tell de-CH, fr-CH and it-CH apart (one Swiss
|
|
93
|
+
// flag each), so the code sits beside it (0.49 KIT-10); the row's name
|
|
94
|
+
// carries the language's own name.
|
|
95
|
+
<span className="flex items-center gap-1.5">
|
|
96
|
+
{flagOf(active)}
|
|
97
|
+
<span aria-hidden className="text-xs font-semibold tabular-nums tracking-wide text-secondary">
|
|
98
|
+
{languageCodeLabel(active.code)}
|
|
99
|
+
</span>
|
|
100
|
+
<span className="sr-only">{active.label}</span>
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
91
103
|
</span>
|
|
92
104
|
}
|
|
93
105
|
>
|