@eifi1/ui-kit 0.35.0 → 0.36.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 +35 -5
- package/dist/admin/role-select.d.ts +3 -2
- package/dist/admin/role-select.js +2 -0
- package/dist/admin/role-select.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +2 -2
- package/dist/components/autocomplete.js +2 -1
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-look.d.ts +1 -1
- package/dist/components/button-look.js +9 -4
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/checkbox-group.d.ts +5 -2
- package/dist/components/checkbox-group.js +15 -4
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/column-mapper.d.ts +5 -3
- package/dist/components/column-mapper.js +2 -0
- package/dist/components/column-mapper.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +2 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/country-select.d.ts +3 -3
- package/dist/components/country-select.js +3 -0
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-select.d.ts +3 -2
- package/dist/components/currency-select.js +10 -1
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/date-field.d.ts +2 -1
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.js +3 -0
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js +3 -0
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +2 -2
- package/dist/components/entity-combobox.js +2 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-parts.d.ts +66 -11
- package/dist/components/field-parts.js +32 -10
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +3 -2
- package/dist/components/field-strip.js +2 -1
- package/dist/components/field-strip.js.map +1 -1
- package/dist/components/field.d.ts +3 -2
- package/dist/components/field.js +24 -15
- package/dist/components/field.js.map +1 -1
- package/dist/components/help-popover.d.ts +9 -7
- package/dist/components/help-popover.js.map +1 -1
- package/dist/components/icon-picker.d.ts +3 -2
- package/dist/components/icon-picker.js +3 -0
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-entity-combobox.d.ts +2 -2
- package/dist/components/inline-entity-combobox.js +2 -1
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.d.ts +2 -2
- package/dist/components/input.js +2 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/month-picker.d.ts +2 -1
- package/dist/components/month-picker.js +8 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +2 -2
- package/dist/components/multi-entity-combobox.js +2 -1
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +2 -2
- package/dist/components/multi-select.js +3 -0
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numeric-field.d.ts +5 -2
- package/dist/components/numeric-field.js +4 -1
- package/dist/components/numeric-field.js.map +1 -1
- package/dist/components/one-time-code-input.d.ts +2 -1
- package/dist/components/one-time-code-input.js +2 -1
- package/dist/components/one-time-code-input.js.map +1 -1
- package/dist/components/phone-input.d.ts +1 -1
- package/dist/components/phone-input.js +4 -0
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/popover.js +1 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/select.d.ts +4 -3
- package/dist/components/select.js +2 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/slider.d.ts +3 -2
- package/dist/components/slider.js +7 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/stat-tile.js +2 -11
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swatch-picker.d.ts +3 -2
- package/dist/components/swatch-picker.js +3 -0
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/text-link.js +3 -1
- package/dist/components/text-link.js.map +1 -1
- package/dist/components/textarea.d.ts +2 -1
- package/dist/components/textarea.js +9 -3
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/toggle-group.d.ts +2 -2
- package/dist/components/toggle-group.js +7 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/i18n/defaults.js +5 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +97 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -0
- package/dist/i18n/kit-labels.js +21 -1
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +103 -2
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +95 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +96 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +95 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +96 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +95 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +13 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +3 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/device-settings.d.ts +72 -0
- package/dist/lib/device-settings.js +64 -0
- package/dist/lib/device-settings.js.map +1 -0
- package/dist/lib/plain-text.d.ts +11 -0
- package/dist/lib/plain-text.js +16 -0
- package/dist/lib/plain-text.js.map +1 -0
- package/dist/lib/safe-storage.d.ts +6 -0
- package/dist/lib/safe-storage.js +11 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +39 -0
- package/dist/notifications/notification-parts.js +47 -0
- package/dist/notifications/notification-parts.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +62 -0
- package/dist/notifications/notification-types-setting.js +192 -0
- package/dist/notifications/notification-types-setting.js.map +1 -0
- package/dist/notifications/notification-types.d.ts +75 -0
- package/dist/notifications/notification-types.js +2 -0
- package/dist/notifications/notification-types.js.map +1 -0
- package/dist/notifications/notifications-labels.d.ts +131 -0
- package/dist/notifications/notifications-labels.js +83 -0
- package/dist/notifications/notifications-labels.js.map +1 -0
- package/dist/notifications/push-device-setting.d.ts +37 -0
- package/dist/notifications/push-device-setting.js +164 -0
- package/dist/notifications/push-device-setting.js.map +1 -0
- package/dist/notifications/push-devices-setting.d.ts +44 -0
- package/dist/notifications/push-devices-setting.js +203 -0
- package/dist/notifications/push-devices-setting.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +47 -0
- package/dist/notifications/quiet-hours-setting.js +131 -0
- package/dist/notifications/quiet-hours-setting.js.map +1 -0
- package/dist/notifications/time-zone-select.d.ts +50 -0
- package/dist/notifications/time-zone-select.js +76 -0
- package/dist/notifications/time-zone-select.js.map +1 -0
- package/dist/notifications/unsubscribe-panel.d.ts +53 -0
- package/dist/notifications/unsubscribe-panel.js +139 -0
- package/dist/notifications/unsubscribe-panel.js.map +1 -0
- package/dist/notifications/use-push-device.d.ts +122 -0
- package/dist/notifications/use-push-device.js +230 -0
- package/dist/notifications/use-push-device.js.map +1 -0
- package/dist/rhf/fields.d.ts +26 -19
- package/dist/rhf/fields.js +51 -22
- package/dist/rhf/fields.js.map +1 -1
- package/dist/settings/device-reset-setting-labels.d.ts +3 -0
- package/dist/settings/device-reset-setting-labels.js +15 -0
- package/dist/settings/device-reset-setting-labels.js.map +1 -0
- package/dist/settings/device-reset-setting.d.ts +58 -0
- package/dist/settings/device-reset-setting.js +51 -0
- package/dist/settings/device-reset-setting.js.map +1 -0
- package/dist/shell/app-shell.js +5 -0
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/theme/contrast-tokens.d.ts +42 -4
- package/dist/theme/contrast-tokens.js +56 -9
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +4 -2
- package/dist/theme/contrast.js +13 -0
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-derive.d.ts +15 -1
- package/dist/theme/palette-derive.js +37 -8
- package/dist/theme/palette-derive.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +2 -1
- package/dist/theme/palette-presets.js +5 -5
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +6 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +6 -0
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +6 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/role-select.tsx +4 -1
- package/src/components/amount-input.tsx +2 -1
- package/src/components/autocomplete.tsx +5 -2
- package/src/components/button-look.ts +15 -5
- package/src/components/checkbox-group.tsx +22 -4
- package/src/components/column-mapper.tsx +6 -2
- package/src/components/combobox.tsx +5 -2
- package/src/components/country-select.tsx +6 -1
- package/src/components/currency-select.tsx +12 -3
- package/src/components/date-field.tsx +2 -2
- package/src/components/date-picker.tsx +3 -0
- package/src/components/date-range-picker.tsx +3 -0
- package/src/components/entity-combobox.tsx +5 -2
- package/src/components/field-parts.tsx +107 -19
- package/src/components/field-strip.tsx +4 -3
- package/src/components/field.tsx +28 -15
- package/src/components/help-popover.tsx +9 -7
- package/src/components/icon-picker.tsx +8 -3
- package/src/components/inline-entity-combobox.tsx +5 -2
- package/src/components/input.tsx +4 -2
- package/src/components/month-picker.tsx +10 -3
- package/src/components/multi-entity-combobox.tsx +5 -2
- package/src/components/multi-select.tsx +6 -1
- package/src/components/number-input.tsx +2 -2
- package/src/components/numeric-field.ts +10 -2
- package/src/components/one-time-code-input.tsx +5 -3
- package/src/components/phone-input.tsx +4 -0
- package/src/components/popover.tsx +3 -1
- package/src/components/select.tsx +6 -3
- package/src/components/slider.tsx +10 -3
- package/src/components/stat-tile.tsx +3 -18
- package/src/components/swatch-picker.tsx +9 -4
- package/src/components/text-link.tsx +3 -1
- package/src/components/textarea.tsx +11 -4
- package/src/components/toggle-group.tsx +21 -5
- package/src/i18n/defaults.ts +4 -0
- package/src/i18n/german.ts +113 -0
- package/src/i18n/kit-labels.tsx +51 -1
- package/src/i18n/locales/en.ts +6 -0
- package/src/i18n/locales/es.ts +106 -0
- package/src/i18n/locales/fr.ts +106 -0
- package/src/i18n/locales/hu.ts +104 -0
- package/src/i18n/locales/it.ts +105 -0
- package/src/i18n/locales/zh.ts +96 -0
- package/src/i18n/review.ts +13 -0
- package/src/i18n/shared-labels.ts +2 -0
- package/src/index.ts +50 -0
- package/src/lib/device-settings.ts +146 -0
- package/src/lib/plain-text.ts +22 -0
- package/src/lib/safe-storage.ts +16 -0
- package/src/notifications/notification-parts.tsx +83 -0
- package/src/notifications/notification-types-setting.tsx +309 -0
- package/src/notifications/notification-types.ts +80 -0
- package/src/notifications/notifications-labels.ts +238 -0
- package/src/notifications/push-device-setting.tsx +224 -0
- package/src/notifications/push-devices-setting.tsx +306 -0
- package/src/notifications/quiet-hours-setting.tsx +185 -0
- package/src/notifications/time-zone-select.tsx +126 -0
- package/src/notifications/unsubscribe-panel.tsx +203 -0
- package/src/notifications/use-push-device.ts +392 -0
- package/src/rhf/fields.tsx +77 -29
- package/src/settings/device-reset-setting-labels.ts +20 -0
- package/src/settings/device-reset-setting.tsx +119 -0
- package/src/shell/app-shell.tsx +8 -0
- package/src/theme/contrast-tokens.ts +106 -12
- package/src/theme/contrast.ts +30 -4
- package/src/theme/palette-derive.ts +68 -7
- package/src/theme/palette-presets.ts +9 -6
- package/src/theme/palette-store.ts +12 -0
- package/src/theme/text-size.ts +8 -0
- package/src/theme/theme-store.ts +10 -0
- package/tokens.css +38 -4
package/src/rhf/fields.tsx
CHANGED
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
type InlineEntityComboboxProps,
|
|
92
92
|
} from "../components/combobox";
|
|
93
93
|
import type { ComboClearValue } from "../components/combobox-core";
|
|
94
|
-
import { mergeRefs, useInlineHint } from "../components/field-parts";
|
|
94
|
+
import { fieldHelpMark, mergeRefs, useInlineHint } from "../components/field-parts";
|
|
95
95
|
import { useKitLocale } from "../i18n/kit-labels";
|
|
96
96
|
import { cn } from "../lib/cn";
|
|
97
97
|
import { decimalMark, readTyped } from "../lib/decimal-marks";
|
|
@@ -115,8 +115,14 @@ export interface RhfFieldBaseProps<
|
|
|
115
115
|
label?: ReactNode;
|
|
116
116
|
/** Standing advice under the field ("As printed on the card"), attached to the
|
|
117
117
|
* control with `aria-describedby`. A `FieldHint` is its words at Large and on a
|
|
118
|
-
* touch screen (§4, DUP-2), not a lone "?"; a `HelpPopover` stays a "?".
|
|
118
|
+
* touch screen (§4, DUP-2), not a lone "?"; a `HelpPopover` stays a "?". Beside a
|
|
119
|
+
* `help` it is that caption at every size. */
|
|
119
120
|
hint?: ReactNode;
|
|
121
|
+
/** Help the field is usable without — why, how to choose — behind the field's one
|
|
122
|
+
* "?" after the label: a `HelpPopover` named "About <label>" (round 0.36,
|
|
123
|
+
* docs/text-size-harmonization.md §4); pass a `HelpPopover` to name it yourself.
|
|
124
|
+
* `hint` stays the visible fact under the field. */
|
|
125
|
+
help?: ReactNode;
|
|
120
126
|
/** Draws the label's required mark and sets `aria-required` where the control
|
|
121
127
|
* takes it. Whether the field IS required is `rules`' or the resolver's to say. */
|
|
122
128
|
required?: boolean;
|
|
@@ -229,6 +235,7 @@ export function RhfField<
|
|
|
229
235
|
rules,
|
|
230
236
|
label,
|
|
231
237
|
hint,
|
|
238
|
+
help,
|
|
232
239
|
required,
|
|
233
240
|
disabled,
|
|
234
241
|
excludeWhenDisabled,
|
|
@@ -240,8 +247,11 @@ export function RhfField<
|
|
|
240
247
|
}: RhfFieldProps<TFieldValues, TName, TTransformed>) {
|
|
241
248
|
// §4: a FieldHint is a fact, so at Large and on a touch screen it is its words under
|
|
242
249
|
// the field, not a lone "?" there (DUP-2, refactor audit 2026-10). A HelpPopover stays
|
|
243
|
-
// a "?" at every size.
|
|
244
|
-
|
|
250
|
+
// a "?" at every size. With a `help` (0.36) its "?" follows the label and is the
|
|
251
|
+
// field's only one, so a FieldHint is its words at every size.
|
|
252
|
+
const helpMark = fieldHelpMark(help, label);
|
|
253
|
+
const shownHint = useInlineHint(hint, helpMark !== undefined);
|
|
254
|
+
const labelEl = showLabel && hasContent(label) && <RhfLabel required={required}>{label}</RhfLabel>;
|
|
245
255
|
return (
|
|
246
256
|
<FormField
|
|
247
257
|
control={control}
|
|
@@ -250,7 +260,14 @@ export function RhfField<
|
|
|
250
260
|
disabled={excludeWhenDisabled ? disabled : undefined}
|
|
251
261
|
render={({ field, fieldState }) => (
|
|
252
262
|
<FormItem className={className}>
|
|
253
|
-
{
|
|
263
|
+
{helpMark === undefined ? (
|
|
264
|
+
labelEl
|
|
265
|
+
) : (
|
|
266
|
+
<div className="flex items-center gap-1">
|
|
267
|
+
{labelEl}
|
|
268
|
+
<span className="flex shrink-0 items-center">{helpMark}</span>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
254
271
|
<ControlSlot
|
|
255
272
|
field={disabled && !field.disabled ? { ...field, disabled: true } : field}
|
|
256
273
|
fieldState={fieldState}
|
|
@@ -298,6 +315,7 @@ export function RhfTextField<
|
|
|
298
315
|
rules,
|
|
299
316
|
label,
|
|
300
317
|
hint,
|
|
318
|
+
help,
|
|
301
319
|
required,
|
|
302
320
|
disabled,
|
|
303
321
|
excludeWhenDisabled,
|
|
@@ -306,7 +324,7 @@ export function RhfTextField<
|
|
|
306
324
|
}: RhfTextFieldProps<TFieldValues, TName, TTransformed>) {
|
|
307
325
|
return (
|
|
308
326
|
<RhfField
|
|
309
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
327
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
310
328
|
render={({ field, invalid }) => (
|
|
311
329
|
<Input
|
|
312
330
|
aria-required={required || undefined}
|
|
@@ -349,6 +367,7 @@ export function RhfTextarea<
|
|
|
349
367
|
rules,
|
|
350
368
|
label,
|
|
351
369
|
hint,
|
|
370
|
+
help,
|
|
352
371
|
required,
|
|
353
372
|
disabled,
|
|
354
373
|
excludeWhenDisabled,
|
|
@@ -358,7 +377,7 @@ export function RhfTextarea<
|
|
|
358
377
|
}: RhfTextareaProps<TFieldValues, TName, TTransformed>) {
|
|
359
378
|
return (
|
|
360
379
|
<RhfField
|
|
361
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
380
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
362
381
|
render={({ field, invalid }) => (
|
|
363
382
|
<Textarea
|
|
364
383
|
aria-required={required || undefined}
|
|
@@ -435,6 +454,7 @@ export function RhfNumberField<
|
|
|
435
454
|
rules,
|
|
436
455
|
label,
|
|
437
456
|
hint,
|
|
457
|
+
help,
|
|
438
458
|
required,
|
|
439
459
|
disabled,
|
|
440
460
|
excludeWhenDisabled,
|
|
@@ -445,7 +465,7 @@ export function RhfNumberField<
|
|
|
445
465
|
}: RhfNumberFieldProps<TFieldValues, TName, TTransformed>) {
|
|
446
466
|
return (
|
|
447
467
|
<RhfField
|
|
448
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
468
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
449
469
|
render={({ field, invalid, id }) => (
|
|
450
470
|
// FormControl clones this element with `id` / `aria-describedby` /
|
|
451
471
|
// `aria-invalid`, which NumberControl hands on to the field.
|
|
@@ -647,6 +667,7 @@ export function RhfMoneyField<
|
|
|
647
667
|
rules,
|
|
648
668
|
label,
|
|
649
669
|
hint,
|
|
670
|
+
help,
|
|
650
671
|
required,
|
|
651
672
|
disabled,
|
|
652
673
|
excludeWhenDisabled,
|
|
@@ -655,7 +676,7 @@ export function RhfMoneyField<
|
|
|
655
676
|
}: RhfMoneyFieldProps<TFieldValues, TName, TTransformed>) {
|
|
656
677
|
return (
|
|
657
678
|
<RhfField
|
|
658
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
679
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
659
680
|
asControl={false}
|
|
660
681
|
render={({ field, invalid, id, describedBy }) => (
|
|
661
682
|
<MoneyControl
|
|
@@ -743,6 +764,7 @@ export function RhfDateField<
|
|
|
743
764
|
rules,
|
|
744
765
|
label,
|
|
745
766
|
hint,
|
|
767
|
+
help,
|
|
746
768
|
required,
|
|
747
769
|
disabled,
|
|
748
770
|
excludeWhenDisabled,
|
|
@@ -753,7 +775,7 @@ export function RhfDateField<
|
|
|
753
775
|
}: RhfDateFieldProps<TFieldValues, TName, TTransformed>) {
|
|
754
776
|
return (
|
|
755
777
|
<RhfField
|
|
756
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
778
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
757
779
|
render={({ field, invalid, id }) => (
|
|
758
780
|
// FormControl clones this element with `aria-describedby` / `aria-invalid`,
|
|
759
781
|
// which DateControl hands on to the picker's trigger.
|
|
@@ -853,6 +875,7 @@ export function RhfDateRangePicker<
|
|
|
853
875
|
toRules,
|
|
854
876
|
label,
|
|
855
877
|
hint,
|
|
878
|
+
help,
|
|
856
879
|
required,
|
|
857
880
|
disabled,
|
|
858
881
|
excludeWhenDisabled,
|
|
@@ -886,6 +909,7 @@ export function RhfDateRangePicker<
|
|
|
886
909
|
toState={to.fieldState}
|
|
887
910
|
label={label}
|
|
888
911
|
hint={hint}
|
|
912
|
+
help={help}
|
|
889
913
|
required={required}
|
|
890
914
|
disabled={Boolean(disabled)}
|
|
891
915
|
emptyValue={emptyValue}
|
|
@@ -905,6 +929,7 @@ function RangeBody({
|
|
|
905
929
|
toState,
|
|
906
930
|
label,
|
|
907
931
|
hint,
|
|
932
|
+
help,
|
|
908
933
|
required,
|
|
909
934
|
disabled,
|
|
910
935
|
emptyValue,
|
|
@@ -917,6 +942,7 @@ function RangeBody({
|
|
|
917
942
|
toState: ControllerFieldState;
|
|
918
943
|
label: ReactNode;
|
|
919
944
|
hint: ReactNode;
|
|
945
|
+
help: ReactNode;
|
|
920
946
|
required?: boolean;
|
|
921
947
|
disabled: boolean;
|
|
922
948
|
emptyValue: "" | null;
|
|
@@ -924,8 +950,10 @@ function RangeBody({
|
|
|
924
950
|
pickerProps: OwnDateRangeProps;
|
|
925
951
|
}) {
|
|
926
952
|
const { id, formItemId, formMessageId, describedBy } = useFormField();
|
|
927
|
-
// RhfField's rule: a FieldHint is its words at Large and on touch (DUP-2)
|
|
928
|
-
|
|
953
|
+
// RhfField's rule: a FieldHint is its words at Large and on touch (DUP-2) — and at
|
|
954
|
+
// every size beside a `help`, whose "?" follows the label (0.36).
|
|
955
|
+
const helpMark = fieldHelpMark(help, label);
|
|
956
|
+
const shownHint = useInlineHint(hint, helpMark !== undefined);
|
|
929
957
|
// Both fields' refs go to the one trigger (API-3, refactor audit 2026-10), so
|
|
930
958
|
// `shouldFocusError` lands there whichever of the two is first in error.
|
|
931
959
|
const triggerRef = useMemo(() => mergeRefs(from.ref, to.ref), [from.ref, to.ref]);
|
|
@@ -936,14 +964,22 @@ function RangeBody({
|
|
|
936
964
|
const messageIds = messages.map((_, i) => `${formMessageId}-${i}`);
|
|
937
965
|
const fromValue: unknown = from.value;
|
|
938
966
|
const toValue: unknown = to.value;
|
|
967
|
+
const labelEl = hasContent(label) && (
|
|
968
|
+
// FormLabel colours from the START's error alone (its context is that field); the
|
|
969
|
+
// pair's state says it here.
|
|
970
|
+
<FormLabel id={`${id}-label`} required={required} data-error={invalid || undefined}>
|
|
971
|
+
{label}
|
|
972
|
+
</FormLabel>
|
|
973
|
+
);
|
|
939
974
|
return (
|
|
940
975
|
<>
|
|
941
|
-
{
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
<
|
|
945
|
-
{
|
|
946
|
-
|
|
976
|
+
{helpMark === undefined ? (
|
|
977
|
+
labelEl
|
|
978
|
+
) : (
|
|
979
|
+
<div className="flex items-center gap-1">
|
|
980
|
+
{labelEl}
|
|
981
|
+
<span className="flex shrink-0 items-center">{helpMark}</span>
|
|
982
|
+
</div>
|
|
947
983
|
)}
|
|
948
984
|
<DateRangePicker
|
|
949
985
|
{...pickerProps}
|
|
@@ -1034,6 +1070,7 @@ export function RhfTimeInput<
|
|
|
1034
1070
|
rules,
|
|
1035
1071
|
label,
|
|
1036
1072
|
hint,
|
|
1073
|
+
help,
|
|
1037
1074
|
required,
|
|
1038
1075
|
disabled,
|
|
1039
1076
|
excludeWhenDisabled,
|
|
@@ -1043,7 +1080,7 @@ export function RhfTimeInput<
|
|
|
1043
1080
|
}: RhfTimeInputProps<TFieldValues, TName, TTransformed>) {
|
|
1044
1081
|
return (
|
|
1045
1082
|
<RhfField
|
|
1046
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1083
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1047
1084
|
render={({ field, invalid }) => {
|
|
1048
1085
|
const value: unknown = field.value;
|
|
1049
1086
|
return (
|
|
@@ -1105,6 +1142,7 @@ export function RhfIbanInput<
|
|
|
1105
1142
|
rules,
|
|
1106
1143
|
label,
|
|
1107
1144
|
hint,
|
|
1145
|
+
help,
|
|
1108
1146
|
required,
|
|
1109
1147
|
disabled,
|
|
1110
1148
|
excludeWhenDisabled,
|
|
@@ -1113,7 +1151,7 @@ export function RhfIbanInput<
|
|
|
1113
1151
|
}: RhfIbanInputProps<TFieldValues, TName, TTransformed>) {
|
|
1114
1152
|
return (
|
|
1115
1153
|
<RhfField
|
|
1116
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1154
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1117
1155
|
render={({ field, invalid }) => {
|
|
1118
1156
|
const value: unknown = field.value;
|
|
1119
1157
|
return (
|
|
@@ -1156,6 +1194,7 @@ export function RhfPhoneInput<
|
|
|
1156
1194
|
rules,
|
|
1157
1195
|
label,
|
|
1158
1196
|
hint,
|
|
1197
|
+
help,
|
|
1159
1198
|
required,
|
|
1160
1199
|
disabled,
|
|
1161
1200
|
excludeWhenDisabled,
|
|
@@ -1164,7 +1203,7 @@ export function RhfPhoneInput<
|
|
|
1164
1203
|
}: RhfPhoneInputProps<TFieldValues, TName, TTransformed>) {
|
|
1165
1204
|
return (
|
|
1166
1205
|
<RhfField
|
|
1167
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1206
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1168
1207
|
render={({ field, invalid }) => {
|
|
1169
1208
|
const value: unknown = field.value;
|
|
1170
1209
|
return (
|
|
@@ -1251,6 +1290,7 @@ export function RhfCountrySelect<
|
|
|
1251
1290
|
rules,
|
|
1252
1291
|
label,
|
|
1253
1292
|
hint,
|
|
1293
|
+
help,
|
|
1254
1294
|
required,
|
|
1255
1295
|
disabled,
|
|
1256
1296
|
excludeWhenDisabled,
|
|
@@ -1267,7 +1307,7 @@ export function RhfCountrySelect<
|
|
|
1267
1307
|
const ownLabels = typeof label === "string" ? { country: label, ...labels } : labels;
|
|
1268
1308
|
return (
|
|
1269
1309
|
<RhfField
|
|
1270
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1310
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1271
1311
|
render={({ field, invalid, labelId }) => {
|
|
1272
1312
|
const value: unknown = field.value;
|
|
1273
1313
|
return (
|
|
@@ -1403,6 +1443,7 @@ export function RhfMonthPicker<
|
|
|
1403
1443
|
rules,
|
|
1404
1444
|
label,
|
|
1405
1445
|
hint,
|
|
1446
|
+
help,
|
|
1406
1447
|
required,
|
|
1407
1448
|
disabled,
|
|
1408
1449
|
excludeWhenDisabled,
|
|
@@ -1414,7 +1455,7 @@ export function RhfMonthPicker<
|
|
|
1414
1455
|
}: RhfMonthPickerProps<TFieldValues, TName, TTransformed>) {
|
|
1415
1456
|
return (
|
|
1416
1457
|
<RhfField
|
|
1417
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1458
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1418
1459
|
render={({ field, invalid, id }) => (
|
|
1419
1460
|
// FormControl clones this element with `aria-describedby` / `aria-invalid`,
|
|
1420
1461
|
// which MonthControl hands on to the picker's trigger.
|
|
@@ -1473,6 +1514,7 @@ export function RhfSelect<
|
|
|
1473
1514
|
rules,
|
|
1474
1515
|
label,
|
|
1475
1516
|
hint,
|
|
1517
|
+
help,
|
|
1476
1518
|
required,
|
|
1477
1519
|
disabled,
|
|
1478
1520
|
excludeWhenDisabled,
|
|
@@ -1486,7 +1528,7 @@ export function RhfSelect<
|
|
|
1486
1528
|
}: RhfSelectProps<TFieldValues, TName, TTransformed>) {
|
|
1487
1529
|
return (
|
|
1488
1530
|
<RhfField
|
|
1489
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1531
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1490
1532
|
render={({ field, invalid }) => (
|
|
1491
1533
|
<Select
|
|
1492
1534
|
aria-required={required || undefined}
|
|
@@ -1523,7 +1565,9 @@ export type RhfCheckboxProps<
|
|
|
1523
1565
|
TFieldValues extends FieldValues = FieldValues,
|
|
1524
1566
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
1525
1567
|
TTransformed = TFieldValues,
|
|
1526
|
-
> =
|
|
1568
|
+
> = // No `help` (0.36): a box has no label line — its label sits beside it, inside the
|
|
1569
|
+
// `<label>` a "?" cannot join — and Checkbox takes none.
|
|
1570
|
+
Omit<RhfFieldBaseProps<TFieldValues, TName, TTransformed>, "help"> &
|
|
1527
1571
|
// Checkbox's own — its lock (`commit`, `disabledReason`) among them, which a `Pick`
|
|
1528
1572
|
// list dropped until 0.34 (DUP-9) — less the box's state the form holds, and its
|
|
1529
1573
|
// `description`, which is the twin's `hint`.
|
|
@@ -1646,6 +1690,7 @@ export function RhfToggleGroup<
|
|
|
1646
1690
|
rules,
|
|
1647
1691
|
label,
|
|
1648
1692
|
hint,
|
|
1693
|
+
help,
|
|
1649
1694
|
required,
|
|
1650
1695
|
disabled,
|
|
1651
1696
|
excludeWhenDisabled,
|
|
@@ -1655,7 +1700,7 @@ export function RhfToggleGroup<
|
|
|
1655
1700
|
const labelled = hasContent(label);
|
|
1656
1701
|
return (
|
|
1657
1702
|
<RhfField
|
|
1658
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1703
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1659
1704
|
asControl={false}
|
|
1660
1705
|
render={({ field, invalid, id, labelId, describedBy }) => (
|
|
1661
1706
|
<ToggleControl<T>
|
|
@@ -1846,6 +1891,7 @@ export function RhfCombobox<
|
|
|
1846
1891
|
rules,
|
|
1847
1892
|
label,
|
|
1848
1893
|
hint,
|
|
1894
|
+
help,
|
|
1849
1895
|
required,
|
|
1850
1896
|
disabled,
|
|
1851
1897
|
excludeWhenDisabled,
|
|
@@ -1856,7 +1902,7 @@ export function RhfCombobox<
|
|
|
1856
1902
|
}: RhfComboboxProps<TFieldValues, TName, V, TTransformed>) {
|
|
1857
1903
|
return (
|
|
1858
1904
|
<RhfField
|
|
1859
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
1905
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1860
1906
|
asControl={false}
|
|
1861
1907
|
message={false}
|
|
1862
1908
|
render={({ field, invalid, error, id, describedBy }) => (
|
|
@@ -1956,6 +2002,7 @@ export function RhfInlineEntityCombobox<
|
|
|
1956
2002
|
rules,
|
|
1957
2003
|
label,
|
|
1958
2004
|
hint,
|
|
2005
|
+
help,
|
|
1959
2006
|
required,
|
|
1960
2007
|
disabled,
|
|
1961
2008
|
excludeWhenDisabled,
|
|
@@ -1966,7 +2013,7 @@ export function RhfInlineEntityCombobox<
|
|
|
1966
2013
|
}: RhfInlineEntityComboboxProps<TFieldValues, TName, V, TTransformed>) {
|
|
1967
2014
|
return (
|
|
1968
2015
|
<RhfField
|
|
1969
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
2016
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
1970
2017
|
render={({ field, invalid }) => {
|
|
1971
2018
|
const value: unknown = field.value;
|
|
1972
2019
|
return (
|
|
@@ -2021,6 +2068,7 @@ export function RhfTextCombobox<
|
|
|
2021
2068
|
rules,
|
|
2022
2069
|
label,
|
|
2023
2070
|
hint,
|
|
2071
|
+
help,
|
|
2024
2072
|
required,
|
|
2025
2073
|
disabled,
|
|
2026
2074
|
excludeWhenDisabled,
|
|
@@ -2030,7 +2078,7 @@ export function RhfTextCombobox<
|
|
|
2030
2078
|
}: RhfTextComboboxProps<TFieldValues, TName, TTransformed>) {
|
|
2031
2079
|
return (
|
|
2032
2080
|
<RhfField
|
|
2033
|
-
{...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
|
|
2081
|
+
{...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
|
|
2034
2082
|
asControl={false}
|
|
2035
2083
|
message={false}
|
|
2036
2084
|
render={({ field, invalid, error, id, describedBy }) => (
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The English words of ./device-reset-setting, in a module with no component code: the
|
|
2
|
+
// English catalogue (src/i18n/defaults.ts) imports them without the component, so `en`
|
|
3
|
+
// loads as one chunk like the other languages (0.34, KS U8). ./device-reset-setting
|
|
4
|
+
// re-exports them.
|
|
5
|
+
|
|
6
|
+
import type { DeviceResetLabels } from "./device-reset-setting";
|
|
7
|
+
|
|
8
|
+
/** docs/settings-harmonization.md §11.3, word for word. */
|
|
9
|
+
export const DEFAULT_DEVICE_RESET_LABELS: DeviceResetLabels = {
|
|
10
|
+
title: "Reset this device",
|
|
11
|
+
description:
|
|
12
|
+
"Puts this device's appearance, and the way its tables and panels were left, back to the defaults. Your account, your data and your sign-in are not touched.",
|
|
13
|
+
action: "Reset this device's settings",
|
|
14
|
+
confirmTitle: "Reset this device's settings?",
|
|
15
|
+
confirmBody:
|
|
16
|
+
"Theme, colours, text size, contrast and language go back to their defaults here, and tables and panels open as they first did. Where your account has a language, a text size or a contrast, this device follows it again. Your account and your other devices stay as they are.",
|
|
17
|
+
confirmAction: "Reset",
|
|
18
|
+
done: "This device's settings are back to their defaults.",
|
|
19
|
+
keywords: "reset defaults device restore appearance tables layout",
|
|
20
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
|
|
4
|
+
import { resetDeviceSettings } from "../lib/device-settings";
|
|
5
|
+
import { Button } from "../components/button";
|
|
6
|
+
import { useConfirm } from "../components/confirm-dialog";
|
|
7
|
+
import { toast } from "../components/toast";
|
|
8
|
+
import { SettingsSection, type SettingsSectionProps } from "./settings-section";
|
|
9
|
+
import { DEFAULT_DEVICE_RESET_LABELS } from "./device-reset-setting-labels";
|
|
10
|
+
|
|
11
|
+
/* ── Labels ──────────────────────────────────────────────────────────────── */
|
|
12
|
+
|
|
13
|
+
/** Every string {@link DeviceResetSetting} renders — the `deviceReset` namespace of
|
|
14
|
+
* `<UiKitProvider labels>`, overridable per instance through `labels`
|
|
15
|
+
* (docs/settings-harmonization.md §11.3). The confirm's Cancel is `confirmDialog.cancel`. */
|
|
16
|
+
export interface DeviceResetLabels {
|
|
17
|
+
/** The card's title, and the app's catalogue entry's. */
|
|
18
|
+
title: string;
|
|
19
|
+
/** The line under it: what goes back, and that the account is not touched. */
|
|
20
|
+
description: string;
|
|
21
|
+
/** The button that asks first. */
|
|
22
|
+
action: string;
|
|
23
|
+
/** The question it asks. */
|
|
24
|
+
confirmTitle: string;
|
|
25
|
+
/** What goes, what follows the account again, and what stays. */
|
|
26
|
+
confirmBody: string;
|
|
27
|
+
/** The button that answers it. */
|
|
28
|
+
confirmAction: string;
|
|
29
|
+
/** The toast once it is done. */
|
|
30
|
+
done: string;
|
|
31
|
+
/** For the app's catalogue entry (`keywords`); the card never reads it. */
|
|
32
|
+
keywords: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Defined in ./device-reset-setting-labels, which holds no component code.
|
|
36
|
+
export { DEFAULT_DEVICE_RESET_LABELS };
|
|
37
|
+
|
|
38
|
+
/* ── The card ────────────────────────────────────────────────────────────── */
|
|
39
|
+
|
|
40
|
+
/** The card's DOM id: its catalogue entry's `anchor`, the `?focus=` target. */
|
|
41
|
+
export const DEVICE_RESET_ANCHOR = "settings-device-reset";
|
|
42
|
+
|
|
43
|
+
// `onReset` is the card's own: the DOM's form-reset handler never fires on a div.
|
|
44
|
+
export interface DeviceResetSettingProps
|
|
45
|
+
extends Omit<SettingsSectionProps, "anchor" | "title" | "description" | "children" | "onReset"> {
|
|
46
|
+
/** The app's sentence after the kit's description: what of its own goes back, and what
|
|
47
|
+
* stays (keksdose: the date format, the feature set and the gestures go; presentation
|
|
48
|
+
* mode and the home position stay). */
|
|
49
|
+
description?: ReactNode;
|
|
50
|
+
labels?: LabelOverride<DeviceResetLabels>;
|
|
51
|
+
/** Runs after the reset and before the toast, with the keys reset or removed. */
|
|
52
|
+
onReset?: (keys: string[]) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* "Reset this device's settings" (docs/settings-harmonization.md §11): the LAST card of
|
|
57
|
+
* the appearance group. It puts this device's appearance — theme, palette, language, text
|
|
58
|
+
* size, contrast and the app's own appearance choices — and its views — table views, the
|
|
59
|
+
* sidebar, folded groups, saved filters — back to what a new device gets, through
|
|
60
|
+
* {@link resetDeviceSettings}. The account, the sign-in, the session and anything waiting
|
|
61
|
+
* to be sent are never touched: they never join the registry.
|
|
62
|
+
*
|
|
63
|
+
* - **A confirm, not an undo** (§11.2 decision 9): `useConfirm` in the neutral tone,
|
|
64
|
+
* then a toast. An app mounts a `ConfirmProvider` and a `Toaster` already.
|
|
65
|
+
* - **Not a commit**: nothing is written to the account, so a demo or a lapsed plan's
|
|
66
|
+
* write lock does not disable it (decision 3).
|
|
67
|
+
* - **Live, no reload** (decision 10): each registered store resets through its own
|
|
68
|
+
* setter; the card stays where it is.
|
|
69
|
+
*
|
|
70
|
+
* The app adds the catalogue entry, `{ id: "device_reset", group: "appearance", anchor:
|
|
71
|
+
* DEVICE_RESET_ANCHOR, title, keywords }` (`title` and `keywords` from these labels), last
|
|
72
|
+
* in its appearance group: inside a `SettingsLayout`, `SettingsSection` throws in
|
|
73
|
+
* development without it.
|
|
74
|
+
*/
|
|
75
|
+
export function DeviceResetSetting({
|
|
76
|
+
description,
|
|
77
|
+
labels: labelsProp,
|
|
78
|
+
onReset,
|
|
79
|
+
...rest
|
|
80
|
+
}: DeviceResetSettingProps) {
|
|
81
|
+
const labels = useKitLabels("deviceReset", DEFAULT_DEVICE_RESET_LABELS, labelsProp);
|
|
82
|
+
const confirm = useConfirm();
|
|
83
|
+
|
|
84
|
+
const reset = async () => {
|
|
85
|
+
const ok = await confirm({
|
|
86
|
+
title: labels.confirmTitle,
|
|
87
|
+
body: labels.confirmBody,
|
|
88
|
+
confirmLabel: labels.confirmAction,
|
|
89
|
+
tone: "neutral",
|
|
90
|
+
});
|
|
91
|
+
if (!ok) return;
|
|
92
|
+
const keys = resetDeviceSettings();
|
|
93
|
+
onReset?.(keys);
|
|
94
|
+
toast.success(labels.done);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<SettingsSection
|
|
99
|
+
{...rest}
|
|
100
|
+
anchor={DEVICE_RESET_ANCHOR}
|
|
101
|
+
title={labels.title}
|
|
102
|
+
description={
|
|
103
|
+
description != null ? (
|
|
104
|
+
<>
|
|
105
|
+
{labels.description} {description}
|
|
106
|
+
</>
|
|
107
|
+
) : (
|
|
108
|
+
labels.description
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<div>
|
|
113
|
+
<Button variant="secondary" onClick={() => void reset()}>
|
|
114
|
+
{labels.action}
|
|
115
|
+
</Button>
|
|
116
|
+
</div>
|
|
117
|
+
</SettingsSection>
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/shell/app-shell.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { cn } from "../lib/cn";
|
|
|
8
8
|
import { scrollIntoStrip, useStripFade } from "../lib/strip-fade";
|
|
9
9
|
import { dirOf, type Direction } from "../lib/direction";
|
|
10
10
|
import { readStored, writeStored } from "../lib/safe-storage";
|
|
11
|
+
import { registerDeviceSetting } from "../lib/device-settings";
|
|
11
12
|
import { Tooltip } from "../components/tooltip";
|
|
12
13
|
import { pickLinkRenderer } from "../components/text-link";
|
|
13
14
|
import { DialogFrame } from "../components/dialog-frame";
|
|
@@ -394,6 +395,13 @@ export function AppShell({
|
|
|
394
395
|
useEffect(() => {
|
|
395
396
|
if (collapseStorageKey !== null) writeStored(collapseStorageKey, collapsed ? "1" : "0");
|
|
396
397
|
}, [collapsed, collapseStorageKey]);
|
|
398
|
+
// Reset this device's settings (docs/settings-harmonization.md §11.4): the sidebar
|
|
399
|
+
// unfolds, live, and the effect above writes "0". An embedded shell without a key of
|
|
400
|
+
// its own stores nothing and joins nothing.
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (collapseStorageKey === null) return;
|
|
403
|
+
return registerDeviceSetting({ key: collapseStorageKey, kind: "view", reset: () => setCollapsed(false) });
|
|
404
|
+
}, [collapseStorageKey]);
|
|
397
405
|
// §4: at Large and Extra large the sidebar never collapses to icons only — a glyph
|
|
398
406
|
// with its name in a tooltip is the very thing a reader who asked for bigger type
|
|
399
407
|
// cannot use. The persisted preference is left alone, so Normal gets it back, and the
|