@eifi1/ui-kit 0.21.0 → 0.23.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 +42 -25
- package/dist/chart.d.ts +19 -14
- 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 +53 -14
- package/dist/components/amount-input.js +17 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +29 -0
- package/dist/components/autocomplete.js +134 -104
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +19 -14
- package/dist/components/calculator.d.ts +19 -14
- 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/column-mapper.d.ts +182 -0
- package/dist/components/column-mapper.js +375 -0
- package/dist/components/column-mapper.js.map +1 -0
- package/dist/components/combobox-core.d.ts +39 -2
- package/dist/components/combobox-core.js +29 -6
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +91 -2
- package/dist/components/combobox.js +454 -308
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +69 -161
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +16 -11
- package/dist/components/country-select.d.ts +167 -0
- package/dist/components/country-select.js +293 -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 +69 -166
- package/dist/components/danger-confirm.js +144 -50
- 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 +66 -5
- package/dist/components/date-picker.js +378 -66
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +42 -1
- package/dist/components/entity-combobox.js +154 -113
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +19 -14
- package/dist/components/field-parts.d.ts +224 -0
- package/dist/components/field-parts.js +156 -0
- package/dist/components/field-parts.js.map +1 -0
- package/dist/components/field-strip.d.ts +105 -0
- package/dist/components/field-strip.js +46 -0
- package/dist/components/field-strip.js.map +1 -0
- package/dist/components/file-button.d.ts +19 -14
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +41 -15
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +18 -13
- package/dist/components/form-actions.js +69 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +69 -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 +21 -2
- package/dist/components/icon-picker.js +19 -4
- 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 +130 -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 +32 -14
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +291 -86
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +26 -1
- package/dist/components/multi-entity-combobox.js +143 -102
- package/dist/components/multi-entity-combobox.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 +34 -19
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +79 -19
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +19 -14
- 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 +69 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- package/dist/components/reauth-dialog.d.ts +2 -2
- package/dist/components/reauth-dialog.js +6 -8
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/series-chart.d.ts +19 -14
- package/dist/components/settings-fields.d.ts +19 -14
- package/dist/components/share-card.d.ts +16 -11
- 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 +43 -2
- package/dist/components/swatch-picker.js +80 -32
- 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 +18 -13
- package/dist/components/tile-radio.d.ts +88 -13
- package/dist/components/tile-radio.js +28 -11
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +24 -15
- 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/trigger-aria.d.ts +3 -0
- package/dist/components/trigger-aria.js +3 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/ui.d.ts +16 -11
- package/dist/components/ui.js +284 -88
- 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 +348 -41
- 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 +3 -2
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +68 -253
- package/dist/feedback/feedback-thread.js +46 -10
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
- package/dist/feedback.d.ts +66 -2
- package/dist/hooks/use-file-drop.d.ts +19 -14
- package/dist/i18n/defaults.d.ts +19 -14
- package/dist/i18n/defaults.js +19 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +19 -14
- package/dist/i18n/german.js +84 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -10
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +19 -14
- package/dist/i18n/locales/de-CH.d.ts +19 -14
- package/dist/i18n/locales/en.d.ts +19 -14
- package/dist/i18n/locales/en.js +16 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +19 -14
- package/dist/i18n/locales/es.js +83 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +19 -14
- package/dist/i18n/locales/fr.js +82 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +19 -14
- package/dist/i18n/locales/hu.js +85 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +19 -14
- package/dist/i18n/locales/it.js +84 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +19 -14
- package/dist/i18n/locales/zh.js +80 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +19 -14
- package/dist/i18n/review.js +27 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.js +73 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/column-mapping.d.ts +81 -0
- package/dist/lib/column-mapping.js +108 -0
- package/dist/lib/column-mapping.js.map +1 -0
- 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/lib/table-text.d.ts +109 -1
- package/dist/lib/table-text.js +122 -1
- package/dist/lib/table-text.js.map +1 -1
- package/dist/rhf/fields.d.ts +233 -18
- package/dist/rhf/fields.js +456 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +19 -14
- package/dist/rhf.d.ts +22 -15
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +18 -13
- package/dist/shell/top-bar-brand.d.ts +19 -14
- package/dist/shell/topbar-action-menu.d.ts +36 -3
- package/dist/shell/topbar-action-menu.js +74 -33
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +18 -13
- package/dist/table-text.d.ts +1 -1
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +49 -16
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +19 -14
- 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 +68 -56
- package/src/components/autocomplete.tsx +198 -114
- 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/column-mapper.tsx +666 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +661 -340
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +556 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +429 -82
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +595 -71
- package/src/components/entity-combobox.tsx +235 -124
- package/src/components/field-parts.tsx +403 -0
- package/src/components/field-strip.tsx +149 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +147 -6
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +47 -6
- 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 +13 -0
- package/src/components/month-picker.tsx +400 -63
- package/src/components/multi-entity-combobox.tsx +207 -120
- 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/reauth-dialog.tsx +17 -18
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +143 -31
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +145 -29
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +479 -138
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +597 -46
- package/src/feedback/feedback-dialog.tsx +9 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +113 -9
- package/src/i18n/defaults.ts +18 -1
- package/src/i18n/german.ts +89 -0
- package/src/i18n/kit-labels.tsx +82 -2
- package/src/i18n/locales/en.ts +36 -4
- package/src/i18n/locales/es.ts +87 -0
- package/src/i18n/locales/fr.ts +87 -0
- package/src/i18n/locales/hu.ts +83 -0
- package/src/i18n/locales/it.ts +88 -0
- package/src/i18n/locales/zh.ts +76 -0
- package/src/i18n/review.ts +26 -0
- package/src/index.ts +71 -3
- package/src/lib/column-mapping.ts +234 -0
- 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/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +860 -9
- package/src/rhf.ts +3 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/tour/tour.tsx +66 -28
- package/src/wizard/stepper-nav.tsx +35 -1
|
@@ -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,7 +38,41 @@ 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;
|
|
58
|
+
/** The field's WRAPPER — width, a margin, a flex share. The `<input>` is
|
|
59
|
+
* `inputClassName`'s. */
|
|
39
60
|
className?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Classes for the `<input>` itself — {@link NumberInput}'s and {@link Input}'s prop of
|
|
63
|
+
* the same name, merged the same way: after the field's own base classes, so it can
|
|
64
|
+
* restyle the figure (`text-end` for a money column whose decimal points line up,
|
|
65
|
+
* `font-medium`, a tighter `py-1` in a table cell), and before the end padding the
|
|
66
|
+
* calculator and the currency chip reserve and before the invalid border, so neither
|
|
67
|
+
* can be taken away by accident.
|
|
68
|
+
*
|
|
69
|
+
* keksdose G7: the invoice's VAT cell right-aligns its figure, and with only
|
|
70
|
+
* `className` (the wrapper) it reached the element through `[&_input]:text-end` — a
|
|
71
|
+
* descendant selector that depends on the field's inner markup, which the kit is free
|
|
72
|
+
* to change, and that a reader of the call site has to decode. {@link MoneyField}
|
|
73
|
+
* passes it through, and `RhfMoneyField` takes it too.
|
|
74
|
+
*/
|
|
75
|
+
inputClassName?: string;
|
|
40
76
|
id?: string;
|
|
41
77
|
ariaLabel?: string;
|
|
42
78
|
/**
|
|
@@ -224,51 +260,12 @@ function settleAmount(text: string, digits: number | undefined, min?: number, ma
|
|
|
224
260
|
return Number(out) === Number(t) ? text : out;
|
|
225
261
|
}
|
|
226
262
|
|
|
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
|
-
}
|
|
263
|
+
// The locale's decimal mark and the grouping parse (kastlan 40, keksdose G1) live in
|
|
264
|
+
// `lib/decimal-marks.ts`, shared with NumberInput since 0.22 (keksdose K5).
|
|
242
265
|
|
|
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
|
-
}
|
|
266
|
+
// The error line under a field — the type of `ui.tsx`'s (module-private) one, so an
|
|
267
|
+
// AmountInput's message is indistinguishable from an Input's.
|
|
268
|
+
const FIELD_ERROR_CLASS = "mt-1 text-[11px] leading-tight text-[var(--danger)]";
|
|
272
269
|
|
|
273
270
|
// The consuming app's ONE money palette (`--money-expense` / `--money-income`),
|
|
274
271
|
// not a bespoke rose/emerald pairing: a figure being typed has to wear the same
|
|
@@ -311,20 +308,23 @@ function isResultOf(previous: string, text: string, settle: (text: string) => st
|
|
|
311
308
|
}
|
|
312
309
|
|
|
313
310
|
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) => {
|
|
311
|
+
({ value, onChange, currency, onCurrencyChange, placeholder, label, disabled, invalid: invalidProp, error, calculator = true, className, inputClassName, 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
312
|
const generatedId = useId();
|
|
316
313
|
const fieldId = id ?? generatedId;
|
|
317
314
|
const hintId = useId();
|
|
315
|
+
const errorId = useId();
|
|
318
316
|
// Text is a caption under the field; a FieldHint rides the label line. See `hint`.
|
|
319
317
|
const textHint = (typeof hint === "string" && hint !== "") || typeof hint === "number";
|
|
320
|
-
const
|
|
321
|
-
|
|
318
|
+
const hasError = hasMessage(error);
|
|
319
|
+
// The standing advice first, the news second: the caller's ids, the caption, the error.
|
|
320
|
+
const describedBy = mergeDescribedBy(ariaDescribedBy, textHint && hintId, hasError && errorId);
|
|
321
|
+
const invalid = Boolean(invalidProp) || hasError || ariaInvalid === true || ariaInvalid === "true";
|
|
322
322
|
const editable = !!onCurrencyChange;
|
|
323
323
|
// On phones we suppress the OS keyboard (inputMode="none" below) and show our
|
|
324
324
|
// own calculator numpad, so the desktop popover trigger is hidden. The
|
|
325
325
|
// end padding keys off showCalc, so it tightens up automatically.
|
|
326
326
|
const isMobile = useMediaQuery(PHONE_QUERY, false);
|
|
327
|
-
const showCalc = !disabled && !isMobile;
|
|
327
|
+
const showCalc = calculator && !disabled && !isMobile;
|
|
328
328
|
const [focused, setFocused] = useState(false);
|
|
329
329
|
// On mobile, focusing the field opens the numpad bottom sheet in place of the
|
|
330
330
|
// native keyboard (feedback #334) — a full calculator keypad, not just the old
|
|
@@ -381,15 +381,17 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
381
381
|
// decimals (keksdose live #356). Display only — `value` is the host's, untouched.
|
|
382
382
|
const displayDigits = digitsProp ?? currencyMinorDigits(currency);
|
|
383
383
|
const resting = focused ? shown : settleAmount(shown, displayDigits);
|
|
384
|
-
const derived =
|
|
385
|
-
|
|
384
|
+
const derived = localizeMark(resting, mark);
|
|
385
|
+
// The draft only while it says what the field holds AND holds nothing the live
|
|
386
|
+
// sanitising would drop — a letter, a second decimal point — which would otherwise
|
|
387
|
+
// stay on screen while the value said something else (see `showsTyped`).
|
|
386
388
|
const display =
|
|
387
389
|
draft === null
|
|
388
390
|
? derived
|
|
389
|
-
:
|
|
391
|
+
: showsTyped(draft, mark, shown)
|
|
390
392
|
? draft
|
|
391
393
|
: // The caller owns the sign: the typed "12" is shown as the "-12" it is.
|
|
392
|
-
signOwned && negative &&
|
|
394
|
+
signOwned && negative && shown.startsWith("-") && showsTyped(draft, mark, shown.slice(1))
|
|
393
395
|
? `-${draft}`
|
|
394
396
|
: derived;
|
|
395
397
|
// Every route into the field — typing, the numpad sheet, the desktop
|
|
@@ -434,7 +436,7 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
434
436
|
};
|
|
435
437
|
const typed = (raw: string) => {
|
|
436
438
|
setDraft(raw);
|
|
437
|
-
handleText(
|
|
439
|
+
handleText(readTyped(raw, mark));
|
|
438
440
|
};
|
|
439
441
|
const { open, setOpen, wrapperRef, panelRef, query, setQuery, inputRef } = useDropdownSearch();
|
|
440
442
|
// The chip the currency list hangs off (Keksdose dev#548). It is portalled now, so
|
|
@@ -519,6 +521,8 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
519
521
|
// (feedback #314). FLOATING_INPUT_CLASS already bundles the peer +
|
|
520
522
|
// transparent-placeholder bits.
|
|
521
523
|
asDisplay ? DISPLAY_INPUT_CLASS : label !== undefined ? FLOATING_INPUT_CLASS : FIELD_BASE,
|
|
524
|
+
// The caller's, over the base and under everything below (see `inputClassName`).
|
|
525
|
+
inputClassName,
|
|
522
526
|
// Room for the trailing controls. showCalc is always false on a phone,
|
|
523
527
|
// so the display shape only ever has to clear the currency chip — and
|
|
524
528
|
// it clears it by the chip's actual width (a text-sm code plus a
|
|
@@ -565,9 +569,10 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
565
569
|
>
|
|
566
570
|
{showCalc && (
|
|
567
571
|
<CalculatorButton
|
|
568
|
-
// The mark the field shows
|
|
569
|
-
//
|
|
570
|
-
|
|
572
|
+
// The mark the field shows but not the typist's grouping: the evaluator
|
|
573
|
+
// reads either mark, and "1.234,5" is two of them. The result comes back
|
|
574
|
+
// dot-decimal.
|
|
575
|
+
value={derived}
|
|
571
576
|
onChange={(result, expression) => {
|
|
572
577
|
setDraft(null);
|
|
573
578
|
const settled = settle(result);
|
|
@@ -660,6 +665,13 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
660
665
|
{hint}
|
|
661
666
|
</p>
|
|
662
667
|
)}
|
|
668
|
+
{/* The news after the standing advice, as Select orders them. The root is
|
|
669
|
+
already a steady box, so the message coming and going remounts nothing. */}
|
|
670
|
+
{hasError && (
|
|
671
|
+
<p id={errorId} className={FIELD_ERROR_CLASS}>
|
|
672
|
+
{error}
|
|
673
|
+
</p>
|
|
674
|
+
)}
|
|
663
675
|
{showNumpad && (
|
|
664
676
|
<NumberPadSheet
|
|
665
677
|
// Localised like the field it mirrors. Keys are typing, read by the
|