@eifi1/ui-kit 0.20.1 → 0.22.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/dist/chart.d.ts +10 -8
- package/dist/components/account-chips.d.ts +10 -3
- package/dist/components/account-chips.js +5 -2
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/account-settings.d.ts +33 -2
- package/dist/components/account-settings.js +43 -21
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +27 -8
- package/dist/components/amount-input.js +15 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +8 -0
- package/dist/components/autocomplete.js +9 -4
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +10 -8
- package/dist/components/calculator.d.ts +10 -8
- package/dist/components/checkbox-group.d.ts +104 -0
- package/dist/components/checkbox-group.js +111 -0
- package/dist/components/checkbox-group.js.map +1 -0
- package/dist/components/checkbox.d.ts +19 -0
- package/dist/components/checkbox.js +30 -7
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +18 -0
- package/dist/components/chip.js +47 -10
- package/dist/components/chip.js.map +1 -1
- package/dist/components/combobox.d.ts +13 -2
- package/dist/components/combobox.js +17 -8
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +66 -161
- package/dist/components/copy-button.d.ts +10 -8
- package/dist/components/country-select.d.ts +124 -0
- package/dist/components/country-select.js +237 -0
- package/dist/components/country-select.js.map +1 -0
- package/dist/components/currency-select.d.ts +17 -1
- package/dist/components/currency-select.js +56 -23
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +66 -166
- package/dist/components/danger-confirm.js +133 -49
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -2
- package/dist/components/data-table-filter-popover.d.ts +1 -2
- package/dist/components/data-table-filters.d.ts +1 -2
- package/dist/components/data-table-labels.d.ts +4 -97
- package/dist/components/data-table-labels.js +9 -1
- 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.d.ts +1 -2
- package/dist/components/data-table.js +98 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -5
- package/dist/components/date-picker.js +110 -59
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +8 -1
- package/dist/components/entity-combobox.js +9 -4
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +10 -8
- package/dist/components/field-anatomy.d.ts +95 -0
- package/dist/components/field-anatomy.js +84 -0
- package/dist/components/field-anatomy.js.map +1 -0
- package/dist/components/field-parts.d.ts +114 -0
- package/dist/components/field-parts.js +72 -0
- package/dist/components/field-parts.js.map +1 -0
- package/dist/components/file-button.d.ts +10 -8
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +32 -9
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +10 -8
- package/dist/components/form-actions.js +61 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.d.ts +3 -0
- package/dist/components/hover-menu.js +2 -2
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/iban-input.d.ts +66 -0
- package/dist/components/iban-input.js +125 -0
- package/dist/components/iban-input.js.map +1 -0
- package/dist/components/icon-picker.d.ts +12 -2
- package/dist/components/icon-picker.js +9 -2
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-edit-field.d.ts +167 -0
- package/dist/components/inline-edit-field.js +293 -0
- package/dist/components/inline-edit-field.js.map +1 -0
- package/dist/components/language-select.d.ts +127 -0
- package/dist/components/language-select.js +18 -0
- package/dist/components/language-select.js.map +1 -0
- package/dist/components/line-items.d.ts +25 -3
- package/dist/components/line-items.js +55 -46
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.d.ts +25 -0
- package/dist/components/list.js +16 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/money-field.d.ts +20 -8
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +290 -86
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +8 -1
- package/dist/components/multi-select.js +9 -4
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +25 -13
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +70 -13
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +10 -8
- package/dist/components/one-time-code-input.d.ts +100 -0
- package/dist/components/one-time-code-input.js +137 -0
- package/dist/components/one-time-code-input.js.map +1 -0
- package/dist/components/phone-input.d.ts +66 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- package/dist/components/series-chart.d.ts +10 -8
- package/dist/components/settings-fields.d.ts +10 -8
- package/dist/components/share-card.d.ts +8 -6
- package/dist/components/sign-chip.d.ts +66 -0
- package/dist/components/sign-chip.js +39 -0
- package/dist/components/sign-chip.js.map +1 -0
- package/dist/components/swatch-picker.d.ts +27 -2
- package/dist/components/swatch-picker.js +73 -31
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/switch.d.ts +19 -0
- package/dist/components/switch.js +24 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/text-link.d.ts +10 -8
- package/dist/components/tile-radio.d.ts +68 -13
- package/dist/components/tile-radio.js +3 -3
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +15 -9
- package/dist/components/time-input.js.map +1 -1
- package/dist/components/toggle-group.d.ts +47 -1
- package/dist/components/toggle-group.js +110 -69
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/ui.d.ts +10 -8
- package/dist/components/ui.js +280 -78
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -2
- package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +2 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-attachment.js +210 -3
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +1 -1
- package/dist/feedback/feedback-thread.d.ts +37 -3
- package/dist/feedback/feedback-thread.js +38 -9
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-NhUm7Zga.d.ts} +70 -5
- package/dist/feedback.d.ts +2 -2
- package/dist/hooks/use-file-drop.d.ts +10 -8
- package/dist/i18n/defaults.d.ts +10 -8
- package/dist/i18n/defaults.js +17 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +10 -8
- package/dist/i18n/german.js +50 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +10 -8
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +10 -8
- package/dist/i18n/locales/de-CH.d.ts +10 -8
- package/dist/i18n/locales/en.d.ts +10 -8
- package/dist/i18n/locales/en.js +9 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +10 -8
- package/dist/i18n/locales/es.js +48 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +10 -8
- package/dist/i18n/locales/fr.js +48 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +10 -8
- package/dist/i18n/locales/hu.js +49 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +10 -8
- package/dist/i18n/locales/it.js +49 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +10 -8
- package/dist/i18n/locales/zh.js +48 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +10 -8
- package/dist/i18n/review.js +8 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -10
- package/dist/index.js +57 -2
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → kit-labels-R8mIAc9N.d.ts} +811 -8
- package/dist/lib/countries.d.ts +43 -0
- package/dist/lib/countries.js +39 -0
- package/dist/lib/countries.js.map +1 -0
- package/dist/lib/decimal-marks.d.ts +46 -0
- package/dist/lib/decimal-marks.js +40 -0
- package/dist/lib/decimal-marks.js.map +1 -0
- package/dist/lib/iban.d.ts +98 -0
- package/dist/lib/iban.js +154 -0
- package/dist/lib/iban.js.map +1 -0
- package/dist/lib/isin.d.ts +30 -0
- package/dist/lib/isin.js +36 -0
- package/dist/lib/isin.js.map +1 -0
- package/dist/lib/phone.d.ts +69 -0
- package/dist/lib/phone.js +121 -0
- package/dist/lib/phone.js.map +1 -0
- package/dist/rhf/fields.d.ts +148 -11
- package/dist/rhf/fields.js +332 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +10 -8
- package/dist/rhf.d.ts +13 -9
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +10 -8
- package/dist/shell/top-bar-brand.d.ts +10 -8
- package/dist/shell/topbar-action-menu.d.ts +13 -1
- package/dist/shell/topbar-action-menu.js +31 -28
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +10 -8
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +10 -8
- package/dist/wizard.d.ts +10 -8
- package/package.json +1 -1
- package/src/components/account-chips.tsx +17 -3
- package/src/components/account-settings.tsx +92 -26
- package/src/components/amount-input.tsx +49 -56
- package/src/components/autocomplete.tsx +133 -110
- package/src/components/checkbox-group.tsx +229 -0
- package/src/components/checkbox.tsx +72 -8
- package/src/components/chip.tsx +80 -10
- package/src/components/combobox.tsx +292 -253
- package/src/components/country-select.tsx +443 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +345 -77
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +173 -62
- package/src/components/entity-combobox.tsx +107 -87
- package/src/components/field-anatomy.tsx +190 -0
- package/src/components/field-parts.tsx +184 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +115 -2
- package/src/components/hover-menu.tsx +7 -4
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +26 -4
- package/src/components/inline-edit-field.tsx +533 -0
- package/src/components/language-select.tsx +78 -0
- package/src/components/line-items.tsx +35 -2
- package/src/components/list.tsx +55 -6
- package/src/components/money-field.tsx +10 -0
- package/src/components/month-picker.tsx +399 -63
- package/src/components/multi-select.tsx +80 -58
- package/src/components/number-field.tsx +28 -46
- package/src/components/number-input.tsx +210 -43
- package/src/components/one-time-code-input.tsx +288 -0
- package/src/components/phone-input.tsx +299 -0
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +118 -30
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +71 -16
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/ui.tsx +472 -105
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +313 -9
- package/src/feedback/feedback-dialog.tsx +2 -0
- package/src/feedback/feedback-thread.tsx +66 -5
- package/src/i18n/defaults.ts +16 -1
- package/src/i18n/german.ts +47 -0
- package/src/i18n/kit-labels.tsx +78 -2
- package/src/i18n/locales/en.ts +10 -0
- package/src/i18n/locales/es.ts +46 -0
- package/src/i18n/locales/fr.ts +45 -0
- package/src/i18n/locales/hu.ts +46 -0
- package/src/i18n/locales/it.ts +47 -0
- package/src/i18n/locales/zh.ts +44 -0
- package/src/i18n/review.ts +7 -0
- package/src/index.ts +53 -3
- package/src/lib/countries.ts +91 -0
- package/src/lib/decimal-marks.ts +99 -0
- package/src/lib/iban.ts +171 -0
- package/src/lib/isin.ts +62 -0
- package/src/lib/phone.ts +211 -0
- package/src/rhf/fields.tsx +606 -9
- package/src/rhf.ts +2 -1
- package/src/shell/topbar-action-menu.tsx +53 -29
- package/src/tour/tour.tsx +66 -28
package/dist/wizard.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export { WizardStep, WizardStepProps } from './wizard/wizard-step.js';
|
|
|
6
6
|
export { WizardSummary } from './wizard/wizard-summary.js';
|
|
7
7
|
export { StepperNav } from './wizard/stepper-nav.js';
|
|
8
8
|
import 'react';
|
|
9
|
-
import './kit-labels-
|
|
10
|
-
import './
|
|
9
|
+
import './kit-labels-R8mIAc9N.js';
|
|
10
|
+
import './data-table-labels-B7OdnM0S.js';
|
|
11
|
+
import './components/data-table-sort.js';
|
|
12
|
+
import './components/swipeable-row.js';
|
|
11
13
|
import './components/mini-calendar.js';
|
|
12
14
|
import './components/calendar-heatmap.js';
|
|
13
15
|
import './components/popover.js';
|
|
@@ -29,7 +31,6 @@ import './components/sparkline.js';
|
|
|
29
31
|
import './components/stat-tile.js';
|
|
30
32
|
import './components/signature-pad.js';
|
|
31
33
|
import './components/password-strength.js';
|
|
32
|
-
import './components/danger-confirm.js';
|
|
33
34
|
import './components/swatch-picker.js';
|
|
34
35
|
import './components/tile-radio.js';
|
|
35
36
|
import './components/icon-picker.js';
|
|
@@ -37,11 +38,10 @@ import './components/dialog-frame.js';
|
|
|
37
38
|
import './components/modal.js';
|
|
38
39
|
import './hooks/use-search-param-state.js';
|
|
39
40
|
import './components/measured-grid.js';
|
|
40
|
-
import './feedback-attachment-
|
|
41
|
+
import './feedback-attachment-NhUm7Zga.js';
|
|
41
42
|
import './feedback/feedback-thread.js';
|
|
42
43
|
import './feedback/feedback-inbox.js';
|
|
43
44
|
import './components/account-settings-labels.js';
|
|
44
|
-
import './components/confirm-dialog.js';
|
|
45
45
|
import './components/floating-panel.js';
|
|
46
46
|
import './components/bulk-action-bar.js';
|
|
47
47
|
import './components/list.js';
|
|
@@ -60,11 +60,13 @@ import './components/write-lock.js';
|
|
|
60
60
|
import './components/legal.js';
|
|
61
61
|
import './components/account-chips.js';
|
|
62
62
|
import './lib/format.js';
|
|
63
|
-
import './data-table-3mi2R3so.js';
|
|
64
|
-
import './components/data-table-sort.js';
|
|
65
|
-
import './components/swipeable-row.js';
|
|
66
63
|
import './components/reauth-dialog.js';
|
|
67
64
|
import './components/server-wake.js';
|
|
68
65
|
import './lib/server-wake.js';
|
|
69
66
|
import './components/translation-review-labels.js';
|
|
70
67
|
import './lib/translation-review.js';
|
|
68
|
+
import './components/country-select.js';
|
|
69
|
+
import './components/inline-edit-field.js';
|
|
70
|
+
import './lib/iban.js';
|
|
71
|
+
import './lib/phone.js';
|
|
72
|
+
import './components/sign-chip.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -3,7 +3,8 @@ import type { LucideIcon } from "lucide-react";
|
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { formatDate, formatRelativeTime, toDate, EMPTY_FORMATTED_VALUE } from "../lib/format";
|
|
5
5
|
import type { DateInput, FormatDateStyle, FormatRelativeTimeOptions } from "../lib/format";
|
|
6
|
-
import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
6
|
+
import { useKitDateFormatter, useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
7
|
+
import { toLocalIso } from "../lib/dates";
|
|
7
8
|
import { Chip } from "./chip";
|
|
8
9
|
import type { ChipShape, ChipSize, ChipTone, ChipVariant } from "./chip";
|
|
9
10
|
import { Tooltip } from "./tooltip";
|
|
@@ -201,7 +202,8 @@ export interface DateMarkProps {
|
|
|
201
202
|
*/
|
|
202
203
|
display?: "date" | "relative";
|
|
203
204
|
/** Default `medium`. Under `"relative"`, the style of the tooltip's date (default
|
|
204
|
-
* `dateTime`) and of the date `relative.absoluteAfterDays` switches to.
|
|
205
|
+
* `dateTime`) and of the date `relative.absoluteAfterDays` switches to. Given, it wins
|
|
206
|
+
* over the provider's `formatDate`. */
|
|
205
207
|
dateStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;
|
|
206
208
|
/** `formatRelativeTime` options: `numeric`, `style`, `absoluteAfterDays`, `now`. */
|
|
207
209
|
relative?: Omit<FormatRelativeTimeOptions, "locale" | "empty" | "absoluteStyle">;
|
|
@@ -216,6 +218,13 @@ export interface DateMarkProps {
|
|
|
216
218
|
/**
|
|
217
219
|
* A date in a table cell: a `<time>` with a machine-readable `dateTime`, nowrap and
|
|
218
220
|
* tabular figures, in the provider's locale — or `empty` when there is none.
|
|
221
|
+
*
|
|
222
|
+
* `display="date"` without a `dateStyle` is written by `<UiKitProvider formatDate>` when
|
|
223
|
+
* the app set one (keksdose K12: its `DateCell` exists to put every column's date in the
|
|
224
|
+
* app's date-format preference, weekday and all — dev#546), called with the LOCAL
|
|
225
|
+
* calendar day of the value as `"YYYY-MM-DD"` and `weekday: true`. Without one, or for
|
|
226
|
+
* an empty answer, it is `Intl`'s `medium` date as before. The relative display is not
|
|
227
|
+
* a calendar day and keeps `formatRelativeTime`.
|
|
219
228
|
*/
|
|
220
229
|
export function DateMark({
|
|
221
230
|
value,
|
|
@@ -227,13 +236,18 @@ export function DateMark({
|
|
|
227
236
|
className,
|
|
228
237
|
}: DateMarkProps) {
|
|
229
238
|
const locale = useKitLocale(localeProp);
|
|
239
|
+
const fromProvider = useKitDateFormatter();
|
|
230
240
|
const date = toDate(value);
|
|
231
241
|
const cls = cn("whitespace-nowrap tabular-nums", className);
|
|
232
242
|
if (!date) return <span className={cn(cls, "text-[var(--text-muted)]")}>{empty}</span>;
|
|
233
243
|
if (display === "date") {
|
|
244
|
+
const own =
|
|
245
|
+
dateStyle === undefined && fromProvider
|
|
246
|
+
? fromProvider(toLocalIso(date), { unit: "day", source: "dateMark", locale, weekday: true })
|
|
247
|
+
: "";
|
|
234
248
|
return (
|
|
235
249
|
<time dateTime={date.toISOString()} className={cls}>
|
|
236
|
-
{formatDate(date, dateStyle ?? "medium", { locale })}
|
|
250
|
+
{own || formatDate(date, dateStyle ?? "medium", { locale })}
|
|
237
251
|
</time>
|
|
238
252
|
);
|
|
239
253
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { useId, useState } from "react";
|
|
2
2
|
import type { KeyboardEvent, ReactNode } from "react";
|
|
3
|
-
import { Button, Card,
|
|
3
|
+
import { Button, Card, Input } from "./ui";
|
|
4
4
|
import { UserAvatar } from "./user-avatar";
|
|
5
5
|
import { CopyButton } from "./copy-button";
|
|
6
6
|
import { QrCode } from "./qr-code";
|
|
7
|
+
import { OneTimeCodeInput } from "./one-time-code-input";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_PASSWORD_STRENGTH_LABELS,
|
|
10
|
+
passwordByteLength,
|
|
11
|
+
PasswordStrengthMeter,
|
|
12
|
+
} from "./password-strength";
|
|
13
|
+
import type { PasswordStrengthMeterProps } from "./password-strength";
|
|
7
14
|
import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
8
15
|
import { useAccountSettingsLabels } from "./account-settings-labels";
|
|
9
16
|
import type {
|
|
@@ -95,30 +102,73 @@ export function ProfileSetting({
|
|
|
95
102
|
);
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
/**
|
|
106
|
+
* What {@link PasswordSetting}'s `strength` takes besides `true`: the meter's own options,
|
|
107
|
+
* minus the ones the card owns — `value` (the new password), `minLength` (the card's, so
|
|
108
|
+
* the checklist's length rule and the submit's check are one number) and `maxBytes`
|
|
109
|
+
* (the card's own prop, which the submit enforces as well).
|
|
110
|
+
*/
|
|
111
|
+
export type PasswordSettingStrength = Pick<PasswordStrengthMeterProps, "score" | "showRequirements" | "labels">;
|
|
112
|
+
|
|
98
113
|
export function PasswordSetting({
|
|
99
114
|
onSubmit,
|
|
100
115
|
pending,
|
|
101
116
|
minLength = 8,
|
|
117
|
+
strength,
|
|
118
|
+
maxBytes,
|
|
102
119
|
labels: labelsProp,
|
|
103
120
|
}: {
|
|
104
121
|
/** Called with the validated (current, new) pair; return a promise to auto-clear on success. */
|
|
105
122
|
onSubmit: (currentPassword: string, newPassword: string) => void | Promise<unknown>;
|
|
106
123
|
pending?: boolean;
|
|
124
|
+
/** The shortest new password the card submits, in characters as a person counts them
|
|
125
|
+
* (an emoji is one). Keep it in step with the server's own minimum. Default 8. */
|
|
107
126
|
minLength?: number;
|
|
127
|
+
/**
|
|
128
|
+
* Show the {@link PasswordStrengthMeter} under the new-password field (0.22.0,
|
|
129
|
+
* kastlan's input audit): the bar, the level in words and the checklist, with the
|
|
130
|
+
* length rule drawn from `minLength`. `true` for the meter as it comes; an object for
|
|
131
|
+
* its `score` (zxcvbn's, say), `showRequirements` or `labels`.
|
|
132
|
+
*
|
|
133
|
+
* Off by default, so a card that never asked for it does not grow one. The meter is
|
|
134
|
+
* advisory, as everywhere in the kit: it blocks nothing — the card's own checks do
|
|
135
|
+
* (`minLength`, the confirmation, `maxBytes`).
|
|
136
|
+
*/
|
|
137
|
+
strength?: boolean | PasswordSettingStrength;
|
|
138
|
+
/**
|
|
139
|
+
* A byte ceiling on the new password — bcrypt reads at most 72 BYTES and ignores the
|
|
140
|
+
* rest, which is kastlan's case: `maxBytes={72}`. A new password over it is refused
|
|
141
|
+
* on submit with the meter's own "too long" sentence (the `passwordStrength`
|
|
142
|
+
* namespace's `tooLong`), and the meter, when shown, warns while it is typed.
|
|
143
|
+
*
|
|
144
|
+
* Unset by default, and leave it unset for a secret with no ceiling: an invented one
|
|
145
|
+
* talks people out of the long passphrase they chose.
|
|
146
|
+
*/
|
|
147
|
+
maxBytes?: number;
|
|
108
148
|
/** Prop > `<UiKitProvider labels={{ accountSettings: { password } }}>` > English. */
|
|
109
149
|
labels?: Partial<PasswordSettingLabels>;
|
|
110
150
|
}) {
|
|
111
151
|
const labels = useAccountSettingsLabels("password", labelsProp);
|
|
152
|
+
const strengthOptions = typeof strength === "object" ? strength : undefined;
|
|
153
|
+
// Only for `tooLong`: the sentence the meter shows is the one the refusal says.
|
|
154
|
+
const strengthLabels = useKitLabels("passwordStrength", DEFAULT_PASSWORD_STRENGTH_LABELS, strengthOptions?.labels);
|
|
112
155
|
const [current, setCurrent] = useState("");
|
|
113
156
|
const [next, setNext] = useState("");
|
|
114
157
|
const [confirm, setConfirm] = useState("");
|
|
115
158
|
const [error, setError] = useState<string | null>(null);
|
|
116
159
|
|
|
117
160
|
const submit = async () => {
|
|
118
|
-
|
|
161
|
+
// Code points, not UTF-16 units — the count the meter's checklist and a server's
|
|
162
|
+
// `len()` use. `next.length` counted an emoji as two, so four of them passed a
|
|
163
|
+
// minimum of eight that the meter beside the field said they missed.
|
|
164
|
+
if ([...next].length < minLength) {
|
|
119
165
|
setError(labels.tooShort);
|
|
120
166
|
return;
|
|
121
167
|
}
|
|
168
|
+
if (maxBytes !== undefined && passwordByteLength(next) > maxBytes) {
|
|
169
|
+
setError(strengthLabels.tooLong(maxBytes));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
122
172
|
if (next !== confirm) {
|
|
123
173
|
setError(labels.mismatch);
|
|
124
174
|
return;
|
|
@@ -134,11 +184,31 @@ export function PasswordSetting({
|
|
|
134
184
|
}
|
|
135
185
|
};
|
|
136
186
|
|
|
187
|
+
const nextField = (
|
|
188
|
+
<Input type="password" autoComplete="new-password" label={labels.next} value={next} onChange={(e) => setNext(e.target.value)} />
|
|
189
|
+
);
|
|
190
|
+
|
|
137
191
|
return (
|
|
138
192
|
<Card className="p-4 space-y-3">
|
|
139
193
|
<div className="text-sm font-medium">{labels.title}</div>
|
|
140
194
|
<Input type="password" autoComplete="current-password" label={labels.current} value={current} onChange={(e) => setCurrent(e.target.value)} />
|
|
141
|
-
|
|
195
|
+
{strength ? (
|
|
196
|
+
// One box for the field and its meter, so the card's `space-y-3` spaces the pair
|
|
197
|
+
// from its neighbours and the meter keeps its own small gap under the field.
|
|
198
|
+
<div>
|
|
199
|
+
{nextField}
|
|
200
|
+
<PasswordStrengthMeter
|
|
201
|
+
value={next}
|
|
202
|
+
minLength={minLength}
|
|
203
|
+
maxBytes={maxBytes}
|
|
204
|
+
score={strengthOptions?.score}
|
|
205
|
+
showRequirements={strengthOptions?.showRequirements}
|
|
206
|
+
labels={strengthOptions?.labels}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
) : (
|
|
210
|
+
nextField
|
|
211
|
+
)}
|
|
142
212
|
<Input type="password" autoComplete="new-password" label={labels.confirm} value={confirm} onChange={(e) => setConfirm(e.target.value)} />
|
|
143
213
|
{error && <p className="text-xs text-[var(--danger)]">{error}</p>}
|
|
144
214
|
<Button onClick={() => void submit()} disabled={!current || !next || !confirm || pending}>
|
|
@@ -204,13 +274,17 @@ export function TwoFactorSetting({
|
|
|
204
274
|
const labels = useAccountSettingsLabels("twoFactor", labelsProp);
|
|
205
275
|
const [code, setCode] = useState("");
|
|
206
276
|
const [password, setPassword] = useState("");
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
277
|
+
// Both code fields are a OneTimeCodeInput (0.22.0) — the field this card wrote by hand
|
|
278
|
+
// twice — and keep what they did:
|
|
279
|
+
// - `readOnlyUntilFocus`: browsers autofill a one-time-code field with the saved
|
|
280
|
+
// username (the disable form has a current-password field right above it), and a
|
|
281
|
+
// read-only field is never autofilled. The guard drops on focus, and the field
|
|
282
|
+
// keeps the look of an editable one meanwhile (FIELD_WRITABLE_LOOK) — see the prop.
|
|
283
|
+
// - `length={8}`: the `maxLength={8}` they had, so an authenticator issuing eight
|
|
284
|
+
// digits still fits. What changed is that they now take digits only: "123 456"
|
|
285
|
+
// pasted from an authenticator that groups its digits used to reach `onEnable`
|
|
286
|
+
// with the space in it.
|
|
287
|
+
// - one `code` state for both, as before.
|
|
214
288
|
// Only for the "Status: Enabled" composition — the punctuation between a field's
|
|
215
289
|
// name and its value is the language's (see `CommonLabels.fieldValue`).
|
|
216
290
|
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
@@ -272,16 +346,12 @@ export function TwoFactorSetting({
|
|
|
272
346
|
</div>
|
|
273
347
|
</div>
|
|
274
348
|
)}
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
readOnly={otpReadonly}
|
|
279
|
-
inputClassName={FIELD_WRITABLE_LOOK}
|
|
280
|
-
onFocus={onOtpFocus}
|
|
281
|
-
maxLength={8}
|
|
349
|
+
<OneTimeCodeInput
|
|
350
|
+
readOnlyUntilFocus
|
|
351
|
+
length={8}
|
|
282
352
|
label={labels.codeLabel}
|
|
283
353
|
value={code}
|
|
284
|
-
onChange={
|
|
354
|
+
onChange={setCode}
|
|
285
355
|
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
286
356
|
if (e.key === "Enter" && code && !busy) {
|
|
287
357
|
e.preventDefault();
|
|
@@ -299,16 +369,12 @@ export function TwoFactorSetting({
|
|
|
299
369
|
<div className="space-y-3 border-t border-[var(--border)] pt-3">
|
|
300
370
|
<div className="text-sm font-medium">{labels.disableSection}</div>
|
|
301
371
|
<Input type="password" autoComplete="current-password" label={labels.password} value={password} onChange={(e) => setPassword(e.target.value)} />
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
readOnly={otpReadonly}
|
|
306
|
-
inputClassName={FIELD_WRITABLE_LOOK}
|
|
307
|
-
onFocus={onOtpFocus}
|
|
308
|
-
maxLength={8}
|
|
372
|
+
<OneTimeCodeInput
|
|
373
|
+
readOnlyUntilFocus
|
|
374
|
+
length={8}
|
|
309
375
|
label={labels.codeLabel}
|
|
310
376
|
value={code}
|
|
311
|
-
onChange={
|
|
377
|
+
onChange={setCode}
|
|
312
378
|
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
313
379
|
if (e.key === "Enter" && password && code && !busy) {
|
|
314
380
|
e.preventDefault();
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
sanitizeLive,
|
|
26
26
|
splitLeadingSign,
|
|
27
27
|
} from "../lib/calc";
|
|
28
|
+
import { decimalMark, localizeMark, readTyped, showsTyped } from "../lib/decimal-marks";
|
|
29
|
+
import { hasMessage, mergeDescribedBy } from "./choice-parts";
|
|
28
30
|
|
|
29
31
|
interface AmountInputProps {
|
|
30
32
|
value: string;
|
|
@@ -36,6 +38,23 @@ interface AmountInputProps {
|
|
|
36
38
|
disabled?: boolean;
|
|
37
39
|
/** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
|
|
38
40
|
invalid?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* What is wrong with the amount, in the caller's own words — {@link Input}'s `error`
|
|
43
|
+
* (keksdose K4: "exceeds the outstanding balance" was a loose `<p>` beside the field
|
|
44
|
+
* that nothing pointed at). Rendered under the field, after a text `hint`; merged into
|
|
45
|
+
* `aria-describedby` after the caller's ids and the hint's; implies `invalid`, so the
|
|
46
|
+
* field paints as well as announces. `null`, `false` and `""` are no message.
|
|
47
|
+
* {@link MoneyField} takes it too.
|
|
48
|
+
*/
|
|
49
|
+
error?: ReactNode;
|
|
50
|
+
/**
|
|
51
|
+
* Show the desktop calculator trigger (default `true`). `false` for an amount in an
|
|
52
|
+
* ephemeral close-on-blur editor — a budget's assigned cell, a VAT cell (keksdose K6)
|
|
53
|
+
* — where a click on the icon blurs the field and tears the editor down before the
|
|
54
|
+
* popover can open. Typing a calculation ("200+50", then Enter) still evaluates, and
|
|
55
|
+
* a phone keeps its numpad, exactly as {@link NumberInput}'s `calculator={false}`.
|
|
56
|
+
*/
|
|
57
|
+
calculator?: boolean;
|
|
39
58
|
className?: string;
|
|
40
59
|
id?: string;
|
|
41
60
|
ariaLabel?: string;
|
|
@@ -224,51 +243,12 @@ function settleAmount(text: string, digits: number | undefined, min?: number, ma
|
|
|
224
243
|
return Number(out) === Number(t) ? text : out;
|
|
225
244
|
}
|
|
226
245
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
* The same answer `NumberField` gives, for the same reason: a mark outside those two
|
|
230
|
-
* (Arabic's "٫") is not one {@link sanitizeLive} lets through, so those locales keep
|
|
231
|
-
* the dot rather than get a field that eats the key it shows.
|
|
232
|
-
*/
|
|
233
|
-
function decimalMark(locale: string | undefined): "," | "." {
|
|
234
|
-
try {
|
|
235
|
-
const part = new Intl.NumberFormat(locale).formatToParts(1.5).find((p) => p.type === "decimal");
|
|
236
|
-
return part?.value === "," ? "," : ".";
|
|
237
|
-
} catch {
|
|
238
|
-
// An invalid tag throws a RangeError; a typo in a locale must not take the form down.
|
|
239
|
-
return ".";
|
|
240
|
-
}
|
|
241
|
-
}
|
|
246
|
+
// The locale's decimal mark and the grouping parse (kastlan 40, keksdose G1) live in
|
|
247
|
+
// `lib/decimal-marks.ts`, shared with NumberInput since 0.22 (keksdose K5).
|
|
242
248
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
* settle then made that 1.23, final. This decides per operand, from the typist's own
|
|
247
|
-
* text, before that fold:
|
|
248
|
-
*
|
|
249
|
-
* - both marks present: the one that is not the locale's is grouping, and goes;
|
|
250
|
-
* - in a "," locale, only "." present: grouping when it cannot be a decimal as a
|
|
251
|
-
* German reader writes it — twice ("1.234.567"), or in thousands shape ("1.234",
|
|
252
|
-
* "12.500") — otherwise a decimal ("1.5", typed by a dot-decimal habit);
|
|
253
|
-
* - in a "." locale, only "," present: grouping when it appears twice ("1,234,567");
|
|
254
|
-
* once it stays a decimal, because Swiss and German typists write "1,5" in a
|
|
255
|
-
* de-CH form (kastlan 40) and a unit price "1,789" is a decimal.
|
|
256
|
-
*
|
|
257
|
-
* Only a keystroke's text comes through here. A calculator result is dot-decimal
|
|
258
|
-
* already, and "1.234" from `100/81.03…` is never a thousand.
|
|
259
|
-
*/
|
|
260
|
-
function normalizeTypedMarks(raw: string, mark: "," | "."): string {
|
|
261
|
-
const other = mark === "," ? "." : ",";
|
|
262
|
-
return raw.replace(/[0-9.,]+/g, (operand) => {
|
|
263
|
-
const hasMark = operand.includes(mark);
|
|
264
|
-
const count = operand.split(other).length - 1;
|
|
265
|
-
if (count === 0) return operand;
|
|
266
|
-
const grouping = hasMark
|
|
267
|
-
? true
|
|
268
|
-
: count > 1 || (mark === "," && /^\d{1,3}(\.\d{3})+$/.test(operand));
|
|
269
|
-
return grouping ? operand.split(other).join("") : operand;
|
|
270
|
-
});
|
|
271
|
-
}
|
|
249
|
+
// The error line under a field — the type of `ui.tsx`'s (module-private) one, so an
|
|
250
|
+
// AmountInput's message is indistinguishable from an Input's.
|
|
251
|
+
const FIELD_ERROR_CLASS = "mt-1 text-[11px] leading-tight text-[var(--danger)]";
|
|
272
252
|
|
|
273
253
|
// The consuming app's ONE money palette (`--money-expense` / `--money-income`),
|
|
274
254
|
// not a bespoke rose/emerald pairing: a figure being typed has to wear the same
|
|
@@ -311,20 +291,23 @@ function isResultOf(previous: string, text: string, settle: (text: string) => st
|
|
|
311
291
|
}
|
|
312
292
|
|
|
313
293
|
export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
314
|
-
({ value, onChange, currency, onCurrencyChange, placeholder, label, disabled, invalid: invalidProp, className, id, ariaLabel, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, hint, autoFocus, tone = "neutral", negative = false, onNegativeChange, variant = "field", align = "start", labels, currencyNames, digits: digitsProp, min, max, onCommit }, ref) => {
|
|
294
|
+
({ value, onChange, currency, onCurrencyChange, placeholder, label, disabled, invalid: invalidProp, error, calculator = true, className, id, ariaLabel, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, hint, autoFocus, tone = "neutral", negative = false, onNegativeChange, variant = "field", align = "start", labels, currencyNames, digits: digitsProp, min, max, onCommit }, ref) => {
|
|
315
295
|
const generatedId = useId();
|
|
316
296
|
const fieldId = id ?? generatedId;
|
|
317
297
|
const hintId = useId();
|
|
298
|
+
const errorId = useId();
|
|
318
299
|
// Text is a caption under the field; a FieldHint rides the label line. See `hint`.
|
|
319
300
|
const textHint = (typeof hint === "string" && hint !== "") || typeof hint === "number";
|
|
320
|
-
const
|
|
321
|
-
|
|
301
|
+
const hasError = hasMessage(error);
|
|
302
|
+
// The standing advice first, the news second: the caller's ids, the caption, the error.
|
|
303
|
+
const describedBy = mergeDescribedBy(ariaDescribedBy, textHint && hintId, hasError && errorId);
|
|
304
|
+
const invalid = Boolean(invalidProp) || hasError || ariaInvalid === true || ariaInvalid === "true";
|
|
322
305
|
const editable = !!onCurrencyChange;
|
|
323
306
|
// On phones we suppress the OS keyboard (inputMode="none" below) and show our
|
|
324
307
|
// own calculator numpad, so the desktop popover trigger is hidden. The
|
|
325
308
|
// end padding keys off showCalc, so it tightens up automatically.
|
|
326
309
|
const isMobile = useMediaQuery(PHONE_QUERY, false);
|
|
327
|
-
const showCalc = !disabled && !isMobile;
|
|
310
|
+
const showCalc = calculator && !disabled && !isMobile;
|
|
328
311
|
const [focused, setFocused] = useState(false);
|
|
329
312
|
// On mobile, focusing the field opens the numpad bottom sheet in place of the
|
|
330
313
|
// native keyboard (feedback #334) — a full calculator keypad, not just the old
|
|
@@ -381,15 +364,17 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
381
364
|
// decimals (keksdose live #356). Display only — `value` is the host's, untouched.
|
|
382
365
|
const displayDigits = digitsProp ?? currencyMinorDigits(currency);
|
|
383
366
|
const resting = focused ? shown : settleAmount(shown, displayDigits);
|
|
384
|
-
const derived =
|
|
385
|
-
|
|
367
|
+
const derived = localizeMark(resting, mark);
|
|
368
|
+
// The draft only while it says what the field holds AND holds nothing the live
|
|
369
|
+
// sanitising would drop — a letter, a second decimal point — which would otherwise
|
|
370
|
+
// stay on screen while the value said something else (see `showsTyped`).
|
|
386
371
|
const display =
|
|
387
372
|
draft === null
|
|
388
373
|
? derived
|
|
389
|
-
:
|
|
374
|
+
: showsTyped(draft, mark, shown)
|
|
390
375
|
? draft
|
|
391
376
|
: // The caller owns the sign: the typed "12" is shown as the "-12" it is.
|
|
392
|
-
signOwned && negative &&
|
|
377
|
+
signOwned && negative && shown.startsWith("-") && showsTyped(draft, mark, shown.slice(1))
|
|
393
378
|
? `-${draft}`
|
|
394
379
|
: derived;
|
|
395
380
|
// Every route into the field — typing, the numpad sheet, the desktop
|
|
@@ -434,7 +419,7 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
434
419
|
};
|
|
435
420
|
const typed = (raw: string) => {
|
|
436
421
|
setDraft(raw);
|
|
437
|
-
handleText(
|
|
422
|
+
handleText(readTyped(raw, mark));
|
|
438
423
|
};
|
|
439
424
|
const { open, setOpen, wrapperRef, panelRef, query, setQuery, inputRef } = useDropdownSearch();
|
|
440
425
|
// The chip the currency list hangs off (Keksdose dev#548). It is portalled now, so
|
|
@@ -565,9 +550,10 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
565
550
|
>
|
|
566
551
|
{showCalc && (
|
|
567
552
|
<CalculatorButton
|
|
568
|
-
// The mark the field shows
|
|
569
|
-
//
|
|
570
|
-
|
|
553
|
+
// The mark the field shows but not the typist's grouping: the evaluator
|
|
554
|
+
// reads either mark, and "1.234,5" is two of them. The result comes back
|
|
555
|
+
// dot-decimal.
|
|
556
|
+
value={derived}
|
|
571
557
|
onChange={(result, expression) => {
|
|
572
558
|
setDraft(null);
|
|
573
559
|
const settled = settle(result);
|
|
@@ -660,6 +646,13 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
660
646
|
{hint}
|
|
661
647
|
</p>
|
|
662
648
|
)}
|
|
649
|
+
{/* The news after the standing advice, as Select orders them. The root is
|
|
650
|
+
already a steady box, so the message coming and going remounts nothing. */}
|
|
651
|
+
{hasError && (
|
|
652
|
+
<p id={errorId} className={FIELD_ERROR_CLASS}>
|
|
653
|
+
{error}
|
|
654
|
+
</p>
|
|
655
|
+
)}
|
|
663
656
|
{showNumpad && (
|
|
664
657
|
<NumberPadSheet
|
|
665
658
|
// Localised like the field it mirrors. Keys are typing, read by the
|