@eifi1/ui-kit 0.35.0 → 0.37.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.
Files changed (269) hide show
  1. package/README.md +35 -5
  2. package/dist/admin/role-select.d.ts +3 -2
  3. package/dist/admin/role-select.js +2 -0
  4. package/dist/admin/role-select.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +2 -2
  6. package/dist/components/amount-input.js.map +1 -1
  7. package/dist/components/autocomplete.d.ts +2 -2
  8. package/dist/components/autocomplete.js +2 -1
  9. package/dist/components/autocomplete.js.map +1 -1
  10. package/dist/components/button-look.d.ts +1 -1
  11. package/dist/components/button-look.js +9 -4
  12. package/dist/components/button-look.js.map +1 -1
  13. package/dist/components/checkbox-group.d.ts +5 -2
  14. package/dist/components/checkbox-group.js +15 -4
  15. package/dist/components/checkbox-group.js.map +1 -1
  16. package/dist/components/column-mapper.d.ts +5 -3
  17. package/dist/components/column-mapper.js +2 -0
  18. package/dist/components/column-mapper.js.map +1 -1
  19. package/dist/components/combobox.d.ts +2 -2
  20. package/dist/components/combobox.js +2 -1
  21. package/dist/components/combobox.js.map +1 -1
  22. package/dist/components/country-select.d.ts +3 -3
  23. package/dist/components/country-select.js +3 -0
  24. package/dist/components/country-select.js.map +1 -1
  25. package/dist/components/currency-select.d.ts +3 -2
  26. package/dist/components/currency-select.js +10 -1
  27. package/dist/components/currency-select.js.map +1 -1
  28. package/dist/components/date-field.d.ts +2 -1
  29. package/dist/components/date-field.js.map +1 -1
  30. package/dist/components/date-picker.js +3 -0
  31. package/dist/components/date-picker.js.map +1 -1
  32. package/dist/components/date-range-picker.js +3 -0
  33. package/dist/components/date-range-picker.js.map +1 -1
  34. package/dist/components/entity-combobox.d.ts +2 -2
  35. package/dist/components/entity-combobox.js +2 -1
  36. package/dist/components/entity-combobox.js.map +1 -1
  37. package/dist/components/field-parts.d.ts +66 -11
  38. package/dist/components/field-parts.js +32 -10
  39. package/dist/components/field-parts.js.map +1 -1
  40. package/dist/components/field-strip.d.ts +3 -2
  41. package/dist/components/field-strip.js +2 -1
  42. package/dist/components/field-strip.js.map +1 -1
  43. package/dist/components/field.d.ts +3 -2
  44. package/dist/components/field.js +24 -15
  45. package/dist/components/field.js.map +1 -1
  46. package/dist/components/help-popover.d.ts +9 -7
  47. package/dist/components/help-popover.js.map +1 -1
  48. package/dist/components/icon-picker.d.ts +3 -2
  49. package/dist/components/icon-picker.js +3 -0
  50. package/dist/components/icon-picker.js.map +1 -1
  51. package/dist/components/inline-entity-combobox.d.ts +2 -2
  52. package/dist/components/inline-entity-combobox.js +2 -1
  53. package/dist/components/inline-entity-combobox.js.map +1 -1
  54. package/dist/components/input.d.ts +2 -2
  55. package/dist/components/input.js +2 -1
  56. package/dist/components/input.js.map +1 -1
  57. package/dist/components/month-picker.d.ts +2 -1
  58. package/dist/components/month-picker.js +8 -1
  59. package/dist/components/month-picker.js.map +1 -1
  60. package/dist/components/multi-entity-combobox.d.ts +2 -2
  61. package/dist/components/multi-entity-combobox.js +2 -1
  62. package/dist/components/multi-entity-combobox.js.map +1 -1
  63. package/dist/components/multi-select.d.ts +2 -2
  64. package/dist/components/multi-select.js +3 -0
  65. package/dist/components/multi-select.js.map +1 -1
  66. package/dist/components/number-input.d.ts +2 -2
  67. package/dist/components/number-input.js.map +1 -1
  68. package/dist/components/numeric-field.d.ts +5 -2
  69. package/dist/components/numeric-field.js +4 -1
  70. package/dist/components/numeric-field.js.map +1 -1
  71. package/dist/components/one-time-code-input.d.ts +2 -1
  72. package/dist/components/one-time-code-input.js +2 -1
  73. package/dist/components/one-time-code-input.js.map +1 -1
  74. package/dist/components/phone-input.d.ts +1 -1
  75. package/dist/components/phone-input.js +4 -0
  76. package/dist/components/phone-input.js.map +1 -1
  77. package/dist/components/popover.js +1 -1
  78. package/dist/components/popover.js.map +1 -1
  79. package/dist/components/select.d.ts +4 -3
  80. package/dist/components/select.js +2 -1
  81. package/dist/components/select.js.map +1 -1
  82. package/dist/components/slider.d.ts +3 -2
  83. package/dist/components/slider.js +7 -1
  84. package/dist/components/slider.js.map +1 -1
  85. package/dist/components/stat-tile.js +2 -11
  86. package/dist/components/stat-tile.js.map +1 -1
  87. package/dist/components/swatch-picker.d.ts +3 -2
  88. package/dist/components/swatch-picker.js +3 -0
  89. package/dist/components/swatch-picker.js.map +1 -1
  90. package/dist/components/text-link.js +3 -1
  91. package/dist/components/text-link.js.map +1 -1
  92. package/dist/components/textarea.d.ts +2 -1
  93. package/dist/components/textarea.js +9 -3
  94. package/dist/components/textarea.js.map +1 -1
  95. package/dist/components/toggle-group.d.ts +2 -2
  96. package/dist/components/toggle-group.js +7 -3
  97. package/dist/components/toggle-group.js.map +1 -1
  98. package/dist/eslint/index.js +1 -1
  99. package/dist/eslint/role-utilities.d.ts +34 -2
  100. package/dist/eslint/role-utilities.js +30 -9
  101. package/dist/eslint/role-utilities.js.map +1 -1
  102. package/dist/i18n/defaults.js +5 -1
  103. package/dist/i18n/defaults.js.map +1 -1
  104. package/dist/i18n/german.js +97 -1
  105. package/dist/i18n/german.js.map +1 -1
  106. package/dist/i18n/kit-labels.d.ts +16 -0
  107. package/dist/i18n/kit-labels.js +21 -1
  108. package/dist/i18n/kit-labels.js.map +1 -1
  109. package/dist/i18n/locales/en.js +103 -2
  110. package/dist/i18n/locales/en.js.map +3 -3
  111. package/dist/i18n/locales/es.js +95 -1
  112. package/dist/i18n/locales/es.js.map +1 -1
  113. package/dist/i18n/locales/fr.js +96 -1
  114. package/dist/i18n/locales/fr.js.map +1 -1
  115. package/dist/i18n/locales/hu.js +95 -1
  116. package/dist/i18n/locales/hu.js.map +1 -1
  117. package/dist/i18n/locales/it.js +96 -1
  118. package/dist/i18n/locales/it.js.map +1 -1
  119. package/dist/i18n/locales/zh.js +95 -1
  120. package/dist/i18n/locales/zh.js.map +1 -1
  121. package/dist/i18n/review.js +13 -0
  122. package/dist/i18n/review.js.map +1 -1
  123. package/dist/i18n/shared-labels.js +3 -1
  124. package/dist/i18n/shared-labels.js.map +1 -1
  125. package/dist/index.d.ts +21 -0
  126. package/dist/index.js +38 -0
  127. package/dist/index.js.map +1 -1
  128. package/dist/lib/device-settings.d.ts +72 -0
  129. package/dist/lib/device-settings.js +64 -0
  130. package/dist/lib/device-settings.js.map +1 -0
  131. package/dist/lib/plain-text.d.ts +11 -0
  132. package/dist/lib/plain-text.js +16 -0
  133. package/dist/lib/plain-text.js.map +1 -0
  134. package/dist/lib/safe-storage.d.ts +6 -0
  135. package/dist/lib/safe-storage.js +11 -0
  136. package/dist/lib/safe-storage.js.map +1 -1
  137. package/dist/notifications/notification-parts.d.ts +39 -0
  138. package/dist/notifications/notification-parts.js +47 -0
  139. package/dist/notifications/notification-parts.js.map +1 -0
  140. package/dist/notifications/notification-types-setting.d.ts +62 -0
  141. package/dist/notifications/notification-types-setting.js +192 -0
  142. package/dist/notifications/notification-types-setting.js.map +1 -0
  143. package/dist/notifications/notification-types.d.ts +75 -0
  144. package/dist/notifications/notification-types.js +2 -0
  145. package/dist/notifications/notification-types.js.map +1 -0
  146. package/dist/notifications/notifications-labels.d.ts +131 -0
  147. package/dist/notifications/notifications-labels.js +83 -0
  148. package/dist/notifications/notifications-labels.js.map +1 -0
  149. package/dist/notifications/push-device-setting.d.ts +37 -0
  150. package/dist/notifications/push-device-setting.js +164 -0
  151. package/dist/notifications/push-device-setting.js.map +1 -0
  152. package/dist/notifications/push-devices-setting.d.ts +44 -0
  153. package/dist/notifications/push-devices-setting.js +203 -0
  154. package/dist/notifications/push-devices-setting.js.map +1 -0
  155. package/dist/notifications/quiet-hours-setting.d.ts +47 -0
  156. package/dist/notifications/quiet-hours-setting.js +131 -0
  157. package/dist/notifications/quiet-hours-setting.js.map +1 -0
  158. package/dist/notifications/time-zone-select.d.ts +50 -0
  159. package/dist/notifications/time-zone-select.js +76 -0
  160. package/dist/notifications/time-zone-select.js.map +1 -0
  161. package/dist/notifications/unsubscribe-panel.d.ts +53 -0
  162. package/dist/notifications/unsubscribe-panel.js +139 -0
  163. package/dist/notifications/unsubscribe-panel.js.map +1 -0
  164. package/dist/notifications/use-push-device.d.ts +122 -0
  165. package/dist/notifications/use-push-device.js +230 -0
  166. package/dist/notifications/use-push-device.js.map +1 -0
  167. package/dist/rhf/fields.d.ts +26 -19
  168. package/dist/rhf/fields.js +51 -22
  169. package/dist/rhf/fields.js.map +1 -1
  170. package/dist/settings/device-reset-setting-labels.d.ts +3 -0
  171. package/dist/settings/device-reset-setting-labels.js +15 -0
  172. package/dist/settings/device-reset-setting-labels.js.map +1 -0
  173. package/dist/settings/device-reset-setting.d.ts +58 -0
  174. package/dist/settings/device-reset-setting.js +51 -0
  175. package/dist/settings/device-reset-setting.js.map +1 -0
  176. package/dist/shell/app-shell.js +5 -0
  177. package/dist/shell/app-shell.js.map +1 -1
  178. package/dist/theme/contrast-tokens.d.ts +42 -4
  179. package/dist/theme/contrast-tokens.js +56 -9
  180. package/dist/theme/contrast-tokens.js.map +1 -1
  181. package/dist/theme/contrast.d.ts +4 -2
  182. package/dist/theme/contrast.js +13 -0
  183. package/dist/theme/contrast.js.map +1 -1
  184. package/dist/theme/palette-derive.d.ts +15 -1
  185. package/dist/theme/palette-derive.js +37 -8
  186. package/dist/theme/palette-derive.js.map +1 -1
  187. package/dist/theme/palette-presets.d.ts +2 -1
  188. package/dist/theme/palette-presets.js +5 -5
  189. package/dist/theme/palette-presets.js.map +1 -1
  190. package/dist/theme/palette-store.js +6 -0
  191. package/dist/theme/palette-store.js.map +1 -1
  192. package/dist/theme/text-size.js +6 -0
  193. package/dist/theme/text-size.js.map +1 -1
  194. package/dist/theme/theme-store.js +6 -0
  195. package/dist/theme/theme-store.js.map +1 -1
  196. package/package.json +1 -1
  197. package/src/admin/role-select.tsx +4 -1
  198. package/src/components/amount-input.tsx +2 -1
  199. package/src/components/autocomplete.tsx +5 -2
  200. package/src/components/button-look.ts +15 -5
  201. package/src/components/checkbox-group.tsx +22 -4
  202. package/src/components/column-mapper.tsx +6 -2
  203. package/src/components/combobox.tsx +5 -2
  204. package/src/components/country-select.tsx +6 -1
  205. package/src/components/currency-select.tsx +12 -3
  206. package/src/components/date-field.tsx +2 -2
  207. package/src/components/date-picker.tsx +3 -0
  208. package/src/components/date-range-picker.tsx +3 -0
  209. package/src/components/entity-combobox.tsx +5 -2
  210. package/src/components/field-parts.tsx +107 -19
  211. package/src/components/field-strip.tsx +4 -3
  212. package/src/components/field.tsx +28 -15
  213. package/src/components/help-popover.tsx +9 -7
  214. package/src/components/icon-picker.tsx +8 -3
  215. package/src/components/inline-entity-combobox.tsx +5 -2
  216. package/src/components/input.tsx +4 -2
  217. package/src/components/month-picker.tsx +10 -3
  218. package/src/components/multi-entity-combobox.tsx +5 -2
  219. package/src/components/multi-select.tsx +6 -1
  220. package/src/components/number-input.tsx +2 -2
  221. package/src/components/numeric-field.ts +10 -2
  222. package/src/components/one-time-code-input.tsx +5 -3
  223. package/src/components/phone-input.tsx +4 -0
  224. package/src/components/popover.tsx +3 -1
  225. package/src/components/select.tsx +6 -3
  226. package/src/components/slider.tsx +10 -3
  227. package/src/components/stat-tile.tsx +3 -18
  228. package/src/components/swatch-picker.tsx +9 -4
  229. package/src/components/text-link.tsx +3 -1
  230. package/src/components/textarea.tsx +11 -4
  231. package/src/components/toggle-group.tsx +21 -5
  232. package/src/eslint/role-utilities.ts +67 -11
  233. package/src/i18n/defaults.ts +4 -0
  234. package/src/i18n/german.ts +113 -0
  235. package/src/i18n/kit-labels.tsx +52 -1
  236. package/src/i18n/locales/en.ts +6 -0
  237. package/src/i18n/locales/es.ts +106 -0
  238. package/src/i18n/locales/fr.ts +106 -0
  239. package/src/i18n/locales/hu.ts +104 -0
  240. package/src/i18n/locales/it.ts +105 -0
  241. package/src/i18n/locales/zh.ts +96 -0
  242. package/src/i18n/review.ts +13 -0
  243. package/src/i18n/shared-labels.ts +2 -0
  244. package/src/index.ts +50 -0
  245. package/src/lib/device-settings.ts +146 -0
  246. package/src/lib/plain-text.ts +22 -0
  247. package/src/lib/safe-storage.ts +16 -0
  248. package/src/notifications/notification-parts.tsx +83 -0
  249. package/src/notifications/notification-types-setting.tsx +309 -0
  250. package/src/notifications/notification-types.ts +80 -0
  251. package/src/notifications/notifications-labels.ts +238 -0
  252. package/src/notifications/push-device-setting.tsx +224 -0
  253. package/src/notifications/push-devices-setting.tsx +306 -0
  254. package/src/notifications/quiet-hours-setting.tsx +185 -0
  255. package/src/notifications/time-zone-select.tsx +126 -0
  256. package/src/notifications/unsubscribe-panel.tsx +203 -0
  257. package/src/notifications/use-push-device.ts +392 -0
  258. package/src/rhf/fields.tsx +77 -29
  259. package/src/settings/device-reset-setting-labels.ts +20 -0
  260. package/src/settings/device-reset-setting.tsx +119 -0
  261. package/src/shell/app-shell.tsx +8 -0
  262. package/src/theme/contrast-tokens.ts +106 -12
  263. package/src/theme/contrast.ts +30 -4
  264. package/src/theme/palette-derive.ts +68 -7
  265. package/src/theme/palette-presets.ts +9 -6
  266. package/src/theme/palette-store.ts +12 -0
  267. package/src/theme/text-size.ts +8 -0
  268. package/src/theme/theme-store.ts +10 -0
  269. package/tokens.css +38 -4
@@ -148,6 +148,7 @@ export function PhoneInput({
148
148
  inputClassName,
149
149
  label,
150
150
  hint,
151
+ help,
151
152
  error,
152
153
  invalid,
153
154
  disabled,
@@ -225,6 +226,9 @@ export function PhoneInput({
225
226
  // since 0.34, not a copy of its rule (DUP-2, DUP-8).
226
227
  const messages = useFieldMessages({
227
228
  hint,
229
+ // The number's label line takes the help's "?" (0.36); the hint is then the caption.
230
+ help,
231
+ name: label ?? rest["aria-label"],
228
232
  error,
229
233
  invalid,
230
234
  describedBy: ariaDescribedBy,
@@ -10,7 +10,9 @@ import { dirOf } from "../lib/direction";
10
10
  import type { Direction } from "../lib/direction";
11
11
  import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
12
12
  import { DEFAULT_POPOVER_LABELS } from "./popover-labels";
13
- import { mergeRefs } from "./field-parts";
13
+ // From ../lib/refs, not field-parts (round 0.36): field-parts renders a HelpPopover for a
14
+ // field's `help`, so importing from there would close a cycle, as it did for Tooltip.
15
+ import { mergeRefs } from "../lib/refs";
14
16
 
15
17
  /** The panel's accessible name. A `role="dialog"` with no name announces as "dialog"
16
18
  * and nothing else, which is worse than the unlabelled div it replaced — so it is a
@@ -6,6 +6,7 @@ import {
6
6
  LockedReason,
7
7
  mergeDescribedBy,
8
8
  type DisabledReasonDisplay,
9
+ type FieldHelpProps,
9
10
  useFieldError,
10
11
  useFieldHint,
11
12
  useLockReason,
@@ -20,7 +21,7 @@ import {
20
21
  fieldBelow,
21
22
  } from "./field-base";
22
23
 
23
- export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "size"> {
24
+ export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "size">, FieldHelpProps {
24
25
  label?: ReactNode;
25
26
  /** See {@link Input}'s `invalid`. */
26
27
  invalid?: boolean;
@@ -33,7 +34,8 @@ export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement
33
34
  * set in the value's type on top of both the label and the value. On an
34
35
  * UNLABELLED Select (no label line) a FieldHint sits at the field's end edge,
35
36
  * outside the box. Since 0.34 a FieldHint is the caption at Large and on a touch
36
- * screen, as on Input (`useInlineHint`); a `HelpPopover` stays a "?". */
37
+ * screen, as on Input (`useInlineHint`); a `HelpPopover` stays a "?". Beside a
38
+ * `help` (0.36) the hint is the caption at every size: the help's "?" is the only one. */
37
39
  hint?: ReactNode;
38
40
  /**
39
41
  * `"sm"`: a 28px, 12px-type select for a toolbar or a table header, where the
@@ -109,6 +111,7 @@ export function Select(props: SelectProps) {
109
111
  invalid,
110
112
  error,
111
113
  hint: hintProp,
114
+ help,
112
115
  size,
113
116
  selectClassName,
114
117
  disabledReason,
@@ -138,7 +141,7 @@ export function Select(props: SelectProps) {
138
141
  // and on a touch screen is its words, a caption like any text (§4; a HelpPopover stays
139
142
  // on the label line, §10.23). See `hint` above. Through useFieldHint since 0.34, not a
140
143
  // copy of its rule (DUP-2, refactor audit 2026-10).
141
- const hintParts = useFieldHint(hintProp, rest["aria-describedby"]);
144
+ const hintParts = useFieldHint(hintProp, rest["aria-describedby"], { help, name: label ?? rest["aria-label"] });
142
145
  const hint = hintParts.labelHint;
143
146
  const { isInvalid, describedBy, errorEl } = useFieldError(
144
147
  error,
@@ -1,7 +1,7 @@
1
1
  import { useId } from "react";
2
2
  import type { CSSProperties, ChangeEvent, InputHTMLAttributes, ReactNode, Ref } from "react";
3
3
  import { cn } from "../lib/cn";
4
- import { useFieldMessages } from "./field-parts";
4
+ import { useFieldMessages, type FieldHelpProps } from "./field-parts";
5
5
 
6
6
  /**
7
7
  * A range control on a native `<input type="range">`.
@@ -123,7 +123,8 @@ export interface SliderProps
123
123
  extends Omit<
124
124
  InputHTMLAttributes<HTMLInputElement>,
125
125
  "type" | "value" | "defaultValue" | "onChange" | "min" | "max" | "step" | "children"
126
- > {
126
+ >,
127
+ FieldHelpProps {
127
128
  /** The value. On a log scale this is the real value, not the track position. */
128
129
  value: number;
129
130
  /** The new value — a number, not the DOM event, like `ToggleGroup`. Fired on every
@@ -201,6 +202,7 @@ export function Slider({
201
202
  formatValue,
202
203
  label,
203
204
  hint,
205
+ help,
204
206
  readout,
205
207
  marks,
206
208
  className,
@@ -254,7 +256,12 @@ export function Slider({
254
256
  const hasMarkLabels = normalizedMarks.some((m) => m.label !== undefined);
255
257
 
256
258
  // The label line's "?", or the caption under the track (DUP-2).
257
- const messages = useFieldMessages({ hint, describedBy: rest["aria-describedby"] });
259
+ const messages = useFieldMessages({
260
+ hint,
261
+ help,
262
+ name: label ?? rest["aria-label"],
263
+ describedBy: rest["aria-describedby"],
264
+ });
258
265
  const labelHint = messages.labelHint;
259
266
  const hasHeader = label !== undefined || labelHint !== undefined || readout !== undefined;
260
267
  const plainReadout = typeof readout === "string" || typeof readout === "number";
@@ -1,7 +1,9 @@
1
- import { createContext, isValidElement, useContext, useId, useLayoutEffect, useState } from "react";
1
+ import { createContext, useContext, useId, useLayoutEffect, useState } from "react";
2
2
  import type { ComponentPropsWithRef, CSSProperties, ReactElement, ReactNode } from "react";
3
3
  import { ArrowDown, ArrowUp, Minus } from "lucide-react";
4
4
  import { cn } from "../lib/cn";
5
+ // The sparkline's name: the label's text (lib since 0.36, which a field's `help` "?" shares).
6
+ import { plainText } from "../lib/plain-text";
5
7
  import { useKitLabels, useKitLink, useKitLocale, type LabelOverride } from "../i18n/kit-labels";
6
8
  import { pickLinkRenderer, RenderedKitLink } from "./text-link";
7
9
  import { Card } from "./card";
@@ -612,23 +614,6 @@ function FigureRow({
612
614
  );
613
615
  }
614
616
 
615
- /**
616
- * The text of a label node, for the sparkline's name: a string or number as is, an
617
- * element by its children, recursively. A `<Tooltip label="…">Spend</Tooltip>` label
618
- * yields "Spend" — the text the reader sees, not the bubble's. `undefined` when there
619
- * is no text to find (an icon), so the sparkline falls back to its unnamed summary.
620
- */
621
- function plainText(node: ReactNode): string | undefined {
622
- const walk = (n: ReactNode): string => {
623
- if (typeof n === "string" || typeof n === "number") return String(n);
624
- if (Array.isArray(n)) return n.map(walk).join("");
625
- if (isValidElement<{ children?: ReactNode }>(n)) return walk(n.props.children);
626
- return "";
627
- };
628
- const text = walk(node).replace(/\s+/g, " ").trim();
629
- return text === "" ? undefined : text;
630
- }
631
-
632
617
  /**
633
618
  * Whether `el`'s text is wider than its box — i.e. whether `truncate` is cutting it.
634
619
  * Re-measured whenever the box resizes (a grid reflowing, a translation loading), not
@@ -7,7 +7,7 @@ import type { TileItem, TileSize } from "./tile-radio";
7
7
  import { Tooltip } from "./tooltip";
8
8
  import { useCommitReason, type CommitScope } from "./write-lock";
9
9
  import { hasContent } from "../lib/presence";
10
- import { LabelStrip, mergeDescribedBy, useFieldMessages } from "./field-parts";
10
+ import { LabelStrip, mergeDescribedBy, useFieldMessages, type FieldHelpProps } from "./field-parts";
11
11
  import { DEFAULT_SWATCH_PICKER_LABELS } from "./swatch-picker-labels";
12
12
 
13
13
  export interface SwatchPickerLabels {
@@ -62,7 +62,8 @@ export interface SwatchOption<T extends string> {
62
62
  * is omitted rather than shadowed, as on {@link ToggleGroup}.
63
63
  */
64
64
  export interface SwatchPickerProps<T extends string>
65
- extends Omit<ComponentPropsWithRef<"div">, "onChange" | "children" | "defaultValue"> {
65
+ extends Omit<ComponentPropsWithRef<"div">, "onChange" | "children" | "defaultValue">,
66
+ FieldHelpProps {
66
67
  options: SwatchOption<T>[];
67
68
  /** The chosen colour, or `null` for none. */
68
69
  value: T | null;
@@ -145,6 +146,7 @@ export function SwatchPicker<T extends string>({
145
146
  labels,
146
147
  label,
147
148
  hint,
149
+ help,
148
150
  disabledReason: ownDisabledReason,
149
151
  commit,
150
152
  className,
@@ -157,10 +159,13 @@ export function SwatchPicker<T extends string>({
157
159
  const disabledReason = useCommitReason(commit, ownDisabledReason);
158
160
  const locked = hasContent(disabledReason);
159
161
  const labelled = hasContent(label);
160
- // The label line's "?", or the caption under the swatches (DUP-2). Only with a label.
161
- // The caller's ids, the caption, then the group's state: mixed, locked.
162
+ // The label line's "?", or the caption under the swatches (DUP-2). Only with a label,
163
+ // the help's "?" too (0.36). The caller's ids, the caption, then the group's state:
164
+ // mixed, locked.
162
165
  const messages = useFieldMessages({
163
166
  hint: labelled ? hint : undefined,
167
+ help: labelled ? help : undefined,
168
+ name: label,
164
169
  describedBy: rest["aria-describedby"],
165
170
  reasonId: mergeDescribedBy(mixed && mixedId, locked && reasonId),
166
171
  });
@@ -228,7 +228,9 @@ export interface TextLinkProps
228
228
  }
229
229
 
230
230
  const TONE: Record<TextLinkTone, string> = {
231
- brand: "text-brand hover:text-[var(--brand-hover)]",
231
+ // Under More contrast the brand as text, `--brand-muted`, at rest and hovered (0.36,
232
+ // colour-roles §13.3 decision 8): `--brand` is a mark's colour, solved to 3:1.
233
+ brand: "text-brand hover:text-[var(--brand-hover)] contrast:text-brand-muted contrast:hover:text-brand-muted",
232
234
  muted: "text-muted hover:text-primary",
233
235
  danger: "text-danger hover:text-[var(--danger-hover)]",
234
236
  inherit: "",
@@ -1,7 +1,14 @@
1
1
  import { useId, useLayoutEffect, useRef } from "react";
2
2
  import type { ChangeEvent, ReactNode, Ref, TextareaHTMLAttributes } from "react";
3
3
  import { cn } from "../lib/cn";
4
- import { EndHintRow, FieldCaption, mergeDescribedBy, useFieldError, useFieldHint } from "./field-parts";
4
+ import {
5
+ EndHintRow,
6
+ FieldCaption,
7
+ mergeDescribedBy,
8
+ useFieldError,
9
+ useFieldHint,
10
+ type FieldHelpProps,
11
+ } from "./field-parts";
5
12
  import {
6
13
  CharacterCount,
7
14
  FIELD_BASE,
@@ -221,7 +228,7 @@ function TextareaLabelStrip() {
221
228
  return <span ref={ref} aria-hidden data-slot="label-strip" className={TEXTAREA_LABEL_STRIP} />;
222
229
  }
223
230
 
224
- export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
231
+ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement>, FieldHelpProps {
225
232
  label?: ReactNode;
226
233
  /** See {@link Input}'s `invalid`. */
227
234
  invalid?: boolean;
@@ -240,13 +247,13 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
240
247
  }
241
248
 
242
249
  export function Textarea(props: TextareaProps) {
243
- const { ref, className, label, id, placeholder, invalid, error, hint, showCount, countLabels, ...rest } = props;
250
+ const { ref, className, label, id, placeholder, invalid, error, hint, help, showCount, countLabels, ...rest } = props;
244
251
  // See Input's `reserve`.
245
252
  const reserve = "error" in props || "hint" in props || "showCount" in props;
246
253
  const generated = useId();
247
254
  const fieldId = id ?? generated;
248
255
  const countId = useId();
249
- const hintParts = useFieldHint(hint, rest["aria-describedby"]);
256
+ const hintParts = useFieldHint(hint, rest["aria-describedby"], { help, name: label ?? rest["aria-label"] });
250
257
  const maxLength = rest.maxLength;
251
258
  const counting = Boolean(showCount) && typeof maxLength === "number" && maxLength > 0;
252
259
  const length = useTypedLength(rest.value, rest.defaultValue);
@@ -11,11 +11,13 @@ import {
11
11
  DISABLED_REASON_LINE_CLASS,
12
12
  FIELD_CAPTION_CLASS,
13
13
  FIELD_ERROR_CLASS,
14
+ fieldHelpMark,
14
15
  isTextHint,
15
16
  LabelStrip,
16
17
  useDisabledReasonLine,
17
18
  useInlineHint,
18
19
  type DisabledReasonDisplay,
20
+ type FieldHelpProps,
19
21
  } from "./field-parts";
20
22
  import { FOCUS_RING_WIDTH } from "./focus-ring";
21
23
 
@@ -35,7 +37,8 @@ export interface ToggleOption<T extends string> {
35
37
  * worse than one that does not compile.
36
38
  */
37
39
  export interface ToggleGroupBaseProps<T extends string>
38
- extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
40
+ extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "children">,
41
+ FieldHelpProps {
39
42
  options: ToggleOption<T>[];
40
43
  className?: string;
41
44
  /** Applied to every option button (e.g. to tune height/rounding to match
@@ -380,6 +383,7 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
380
383
  labelPlacement = "field",
381
384
  chromeClassName,
382
385
  hint,
386
+ help,
383
387
  error,
384
388
  disabledReason: ownDisabledReason,
385
389
  disabledReasonDisplay,
@@ -407,10 +411,22 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
407
411
  // §4: at Large and on touch the reason is a line under the group, not a bubble.
408
412
  const reasonLine = useDisabledReasonLine(disabledReasonDisplay) && locked;
409
413
  // §4: likewise a FieldHint "?" on the label line becomes a caption under the group at
410
- // Large and on touch; text is a caption at every size, as Select's (see `hint`).
411
- const inlineHint = useInlineHint(hint);
412
- const hintAsCaption = isTextHint(hint) ? hint : inlineHint !== hint ? inlineHint : undefined;
413
- const labelHint = hintAsCaption === undefined ? hint : undefined;
414
+ // Large and on touch; text is a caption at every size, as Select's (see `hint`). With
415
+ // a `help` (0.36, only with `label`, as `hint`), its "?" is the label line's only one,
416
+ // and the hint — a FieldHint's words included — is the caption at every size.
417
+ const helpMark = fieldHelpMark(hasContent(label) ? help : undefined, label);
418
+ const withHelp = helpMark !== undefined;
419
+ const inlineHint = useInlineHint(hint, withHelp);
420
+ const hintAsCaption = withHelp
421
+ ? hasContent(inlineHint)
422
+ ? inlineHint
423
+ : undefined
424
+ : isTextHint(hint)
425
+ ? hint
426
+ : inlineHint !== hint
427
+ ? inlineHint
428
+ : undefined;
429
+ const labelHint = helpMark ?? (hintAsCaption === undefined ? hint : undefined);
414
430
  // Locked looks disabled — the dimmed group, the not-allowed cursor, no hover offer —
415
431
  // but stays focusable; `disabled` alone also takes the segments out of the tab order.
416
432
  const dimmed = disabled || locked;
@@ -5,7 +5,9 @@
5
5
  // The table itself is role-utilities.generated.ts, re-rendered from tokens.css's
6
6
  // `@theme inline` block by scripts/gen-role-utilities.mjs with the parser below; the
7
7
  // check fails while it is stale. So tokens.css stays the source: a role that gains a
8
- // utility there is flagged in the kit and in the apps from the next release on.
8
+ // utility there is flagged in the kit and in the apps from the next release on. The
9
+ // README's role-utility table (scripts/gen-styling-inventory.mjs) reads the block with
10
+ // the same parser since 0.37, so the README, the check and the plugin cannot disagree.
9
11
  //
10
12
  // This module imports NOTHING, and its TypeScript is erasable only (no enum, no
11
13
  // namespace, no parameter property): the check imports it from source under Node's type
@@ -43,20 +45,74 @@ export type RoleUtilityTable = {
43
45
  readonly [P in RoleProperty]: Readonly<Record<string, readonly string[]>>;
44
46
  };
45
47
 
48
+ /** One `--<namespace>-<name>: <value>;` of the `@theme inline` block. */
49
+ export interface ThemeInlineDeclaration {
50
+ /** The theme variable without its dashes: `text-color-muted`, `ring-width-focus`. */
51
+ variable: string;
52
+ /** The property whose namespace it is in; `null` for a namespace ROLE_NAMESPACES lacks. */
53
+ property: RoleProperty | null;
54
+ /** The name after the namespace, `muted`; `null` with `property`. */
55
+ name: string | null;
56
+ /** The utility it makes: `text-muted`, `ring-focus` (a width is `ring-*` like a colour). */
57
+ utility: string | null;
58
+ /** The token the value reads, `--text-muted`; `null` unless the value is a bare `var()`. */
59
+ token: string | null;
60
+ /** The value as written, trimmed. */
61
+ value: string;
62
+ }
63
+
64
+ /** The `@theme inline` block: where it stands, and every declaration in it in block order. */
65
+ export interface ThemeInlineBlock {
66
+ /** Its first and one-past-last offset in `stripCssComments(css)`, not in `css`. */
67
+ start: number;
68
+ end: number;
69
+ declarations: ThemeInlineDeclaration[];
70
+ }
71
+
72
+ /** `css` without its comments: a declaration commented out is not one. */
73
+ export function stripCssComments(css: string): string {
74
+ return css.replace(/\/\*[\s\S]*?\*\//g, "");
75
+ }
76
+
77
+ const PROPERTIES = Object.keys(ROLE_NAMESPACES) as RoleProperty[];
78
+
79
+ /**
80
+ * tokens.css's `@theme inline` block, read once for every reader: the table below, and
81
+ * the README's (scripts/gen-styling-inventory.mjs), which fails on a `property: null`
82
+ * where the table leaves it out. `null` when there is no such block — the role utilities
83
+ * are gone, or this parser is. Theme blocks hold only declarations, so the first `}`
84
+ * closes it; no namespace is another's prefix, so the first that fits is the one.
85
+ */
86
+ export function parseThemeInline(css: string): ThemeInlineBlock | null {
87
+ const block = /@theme\s+inline\s*\{([^}]*)\}/.exec(stripCssComments(css));
88
+ if (!block) return null;
89
+ const declarations = [...block[1].matchAll(/--([a-z0-9-]+)\s*:\s*([^;]+);/g)].map(([, variable, raw]) => {
90
+ const value = raw.trim();
91
+ const property = PROPERTIES.find((p) => variable.startsWith(`${ROLE_NAMESPACES[p]}-`)) ?? null;
92
+ const name = property && variable.slice(ROLE_NAMESPACES[property].length + 1);
93
+ return {
94
+ variable,
95
+ property,
96
+ name,
97
+ utility: property && `${property.replace(/-width$/, "")}-${name}`,
98
+ token: /^var\((--[a-z0-9-]+)\)$/.exec(value)?.[1] ?? null,
99
+ value,
100
+ };
101
+ });
102
+ return { start: block.index, end: block.index + block[0].length, declarations };
103
+ }
104
+
46
105
  /**
47
106
  * The table `tokens.css` declares: every `--<namespace>-<name>: var(--<token>)` in its
48
- * `@theme inline` block, in block order. `null` when there is no such block — the role
49
- * utilities are gone, or this parser is.
107
+ * `@theme inline` block, in block order, each property present (in ROLE_NAMESPACES' order)
108
+ * even when it has none. `null` when there is no such block.
50
109
  */
51
110
  export function roleUtilitiesFromCss(css: string): RoleUtilityTable | null {
52
- const theme = /@theme inline\s*\{([^}]*)\}/.exec(css.replace(/\/\*[\s\S]*?\*\//g, ""))?.[1];
53
- if (theme === undefined) return null;
54
- const table = {} as Record<RoleProperty, Record<string, string[]>>;
55
- for (const prop of Object.keys(ROLE_NAMESPACES) as RoleProperty[]) {
56
- const names: Record<string, string[]> = {};
57
- const declared = new RegExp(`--${ROLE_NAMESPACES[prop]}-([a-z0-9-]+)\\s*:\\s*var\\((--[a-z0-9-]+)\\)`, "g");
58
- for (const [, name, token] of theme.matchAll(declared)) (names[token] ??= []).push(name);
59
- table[prop] = names;
111
+ const block = parseThemeInline(css);
112
+ if (!block) return null;
113
+ const table = Object.fromEntries(PROPERTIES.map((p) => [p, {}])) as Record<RoleProperty, Record<string, string[]>>;
114
+ for (const { property, name, token } of block.declarations) {
115
+ if (property && name && token) (table[property][token] ??= []).push(name);
60
116
  }
61
117
  return table;
62
118
  }
@@ -87,6 +87,7 @@ import { DEFAULT_DEMO_LABELS } from "../demo/demo-labels";
87
87
  import { DEFAULT_BILLING_LABELS } from "../billing/billing-labels";
88
88
  import { DEFAULT_PLAN_CHANGE_LABELS } from "../admin/plan-change-labels";
89
89
  import { DEFAULT_APPEARANCE_LABELS } from "../components/appearance-labels";
90
+ import { DEFAULT_DEVICE_RESET_LABELS } from "../settings/device-reset-setting-labels";
90
91
  import { DEFAULT_ROW_ACTIONS_LABELS } from "../components/row-actions-labels";
91
92
  import { DEFAULT_APP_SHELL_MORE_LABELS } from "../shell/app-shell-labels";
92
93
  import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips-labels";
@@ -100,6 +101,7 @@ import { DEFAULT_IBAN_INPUT_LABELS } from "../components/iban-input-labels";
100
101
  import { DEFAULT_PHONE_INPUT_LABELS } from "../components/phone-input-labels";
101
102
  import { DEFAULT_SIGN_CHIP_LABELS } from "../components/sign-chip-labels";
102
103
  import { DEFAULT_COLUMN_MAPPER_LABELS } from "../components/column-mapper-labels";
104
+ import { DEFAULT_NOTIFICATIONS_LABELS } from "../notifications/notifications-labels";
103
105
  import {
104
106
  DEFAULT_APP_SHELL_LABELS,
105
107
  DEFAULT_CALCULATOR_LABELS,
@@ -231,6 +233,7 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
231
233
  billing: DEFAULT_BILLING_LABELS,
232
234
  planChange: DEFAULT_PLAN_CHANGE_LABELS,
233
235
  appearance: DEFAULT_APPEARANCE_LABELS,
236
+ deviceReset: DEFAULT_DEVICE_RESET_LABELS,
234
237
  rowActions: DEFAULT_ROW_ACTIONS_LABELS,
235
238
  appShellMore: DEFAULT_APP_SHELL_MORE_LABELS,
236
239
  characterCount: DEFAULT_CHARACTER_COUNT_LABELS,
@@ -240,4 +243,5 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
240
243
  phoneInput: DEFAULT_PHONE_INPUT_LABELS,
241
244
  signChip: DEFAULT_SIGN_CHIP_LABELS,
242
245
  columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,
246
+ notifications: DEFAULT_NOTIFICATIONS_LABELS,
243
247
  };
@@ -268,6 +268,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
268
268
  noResults: "Keine Ergebnisse",
269
269
  fieldValue: (field, value) => `${field}: ${value}`,
270
270
  opensInNewTab: "öffnet in einem neuen Tab",
271
+ aboutField: (field) => `Hilfe zu „${field}“`,
272
+ help: "Hilfe",
271
273
  },
272
274
  dataTable: {
273
275
  columns: "Spalten",
@@ -1072,6 +1074,102 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1072
1074
  rateLimited,
1073
1075
  failed: "Das hat nicht geklappt. Bitte erneut versuchen.",
1074
1076
  },
1077
+ // 0.36.0 — docs/notifications-harmonization.md §4.2: the notification cards. The push
1078
+ // and device words follow keksdose's reviewed German, formal.
1079
+ notifications: {
1080
+ typesTitle: "Benachrichtigungen zu",
1081
+ typesDescription: "Wählen Sie, worüber Sie benachrichtigt werden, und wie.",
1082
+ byEmail: "Per E-Mail",
1083
+ emailUnverified: "Bestätigen Sie Ihre Adresse, um diese zu erhalten.",
1084
+ digestCadence: "Häufigkeit",
1085
+ digestWeekly: "Wöchentlich",
1086
+ digestMonthly: "Monatlich",
1087
+ typesLoading: "Benachrichtigungseinstellungen werden geladen…",
1088
+ typesEmpty: "Für dieses Konto gibt es keine Benachrichtigungen.",
1089
+ quietTitle: "Ruhezeiten",
1090
+ quietDescription: (inbox) =>
1091
+ inbox
1092
+ ? "In diesem Zeitfenster kommen keine Push-Benachrichtigungen. Im Posteingang kommen sie trotzdem an."
1093
+ : "In diesem Zeitfenster kommen keine Push-Benachrichtigungen.",
1094
+ quietFrom: "Von",
1095
+ quietUntil: "Bis",
1096
+ quietIncomplete: "Legen Sie eine Start- und eine Endzeit fest. Ruhezeiten gelten erst, wenn beide gesetzt sind.",
1097
+ timeZone: "Zeitzone",
1098
+ deviceZone: (zone) => `Dieses Gerät ist in der Zeitzone ${zone}.`,
1099
+ useDeviceZone: "Übernehmen",
1100
+ regions: {
1101
+ Africa: "Afrika",
1102
+ America: "Amerika",
1103
+ Antarctica: "Antarktis",
1104
+ Arctic: "Arktis",
1105
+ Asia: "Asien",
1106
+ Atlantic: "Atlantik",
1107
+ Australia: "Australien",
1108
+ Europe: "Europa",
1109
+ Indian: "Indischer Ozean",
1110
+ Pacific: "Pazifik",
1111
+ },
1112
+ pushTitle: "Push-Benachrichtigungen",
1113
+ pushDescription: "Benachrichtigungen auf diesem Gerät, auch wenn die App geschlossen ist.",
1114
+ unsupported: "Dieser Browser unterstützt keine Push-Benachrichtigungen.",
1115
+ iosInstall: (app) =>
1116
+ `Fügen Sie ${app} auf dem iPhone oder iPad zuerst zum Home-Bildschirm hinzu: iOS stellt Benachrichtigungen nur der installierten App zu.`,
1117
+ notConfigured: "Push-Benachrichtigungen sind auf diesem Server nicht eingerichtet.",
1118
+ blocked: "Benachrichtigungen sind in den Einstellungen dieses Browsers blockiert.",
1119
+ on: "Benachrichtigungen sind für dieses Gerät eingeschaltet.",
1120
+ off: "Benachrichtigungen sind für dieses Gerät ausgeschaltet.",
1121
+ otherAccount:
1122
+ "Dieser Browser erhält Benachrichtigungen für ein anderes Konto. Schalten Sie sie hier ein, um sie für das angemeldete Konto zu erhalten.",
1123
+ enable: "Benachrichtigungen einschalten",
1124
+ disable: "Ausschalten",
1125
+ sendTest: "Test senden",
1126
+ enableFailed: "Benachrichtigungen konnten nicht eingeschaltet werden.",
1127
+ noServiceWorker:
1128
+ "Der Service Worker der App ist nicht erreichbar. Laden Sie die Seite neu und versuchen Sie es erneut.",
1129
+ pushTimeout:
1130
+ "Der Push-Dienst des Browsers antwortet nicht. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
1131
+ testSent: (count) =>
1132
+ count === 1 ? `Test an ${n(count)} Gerät gesendet` : `Test an ${n(count)} Geräte gesendet`,
1133
+ testNone:
1134
+ "Kein Gerät hat den Test angenommen. Schalten Sie zuerst die Benachrichtigungen für dieses Gerät ein.",
1135
+ testFailed: "Die Test-Benachrichtigung konnte nicht gesendet werden.",
1136
+ explainerTitle: "Benachrichtigungen einschalten?",
1137
+ explainerBody: (app) =>
1138
+ `${app} sendet nur Benachrichtigungen zu dem, was Sie auswählen, und Sie können sie jederzeit ausschalten.`,
1139
+ explainerHint: "Als Nächstes fragt Ihr Browser nach der Berechtigung.",
1140
+ explainerContinue: "Weiter",
1141
+ devicesTitle: "Benachrichtigungsgeräte",
1142
+ devicesDescription:
1143
+ "Alle Geräte, die Push-Benachrichtigungen für Ihr Konto erhalten. Entfernen Sie die, die Sie nicht mehr nutzen.",
1144
+ devicesLoading: "Geräte werden geladen…",
1145
+ devicesEmpty: "Noch kein Gerät erhält Benachrichtigungen.",
1146
+ unknownDevice: "Unbekanntes Gerät",
1147
+ thisDevice: "Dieses Gerät",
1148
+ added: (date) => `Hinzugefügt am ${date}`,
1149
+ lastDelivery: (date) => `Letzte Zustellung am ${date}`,
1150
+ noDelivery: "Noch keine Zustellung",
1151
+ failures: (count) =>
1152
+ count === 1
1153
+ ? `${n(count)} fehlgeschlagene Zustellung in Folge`
1154
+ : `${n(count)} fehlgeschlagene Zustellungen in Folge`,
1155
+ remove: "Entfernen",
1156
+ // Starts with `remove`, the button's visible text, for voice control.
1157
+ removeItem: (device) => `Entfernen: ${device}`,
1158
+ removePrompt: "Keine Benachrichtigungen mehr an dieses Gerät senden?",
1159
+ unsubscribeTitle: "E-Mail-Benachrichtigungen",
1160
+ unsubscribeAbout: (type) => `Sie erhalten E-Mails zu: ${type}.`,
1161
+ unsubscribeGeneric: "Sie erhalten Benachrichtigungs-E-Mails.",
1162
+ stopThese: "Diese E-Mails abbestellen",
1163
+ stopAll: (app) => `Alle Benachrichtigungs-E-Mails von ${app} abbestellen`,
1164
+ stoppedThese: "Erledigt. Sie erhalten diese E-Mails nicht mehr.",
1165
+ stoppedAll: (app) => `Erledigt. Sie erhalten keine Benachrichtigungs-E-Mails von ${app} mehr.`,
1166
+ accountMail: "E-Mails zu Ihrem Konto erhalten Sie weiterhin.",
1167
+ turnBackOn: "Sie können sie jederzeit in den Einstellungen wieder einschalten.",
1168
+ openSettings: "Einstellungen öffnen",
1169
+ invalidLink:
1170
+ "Dieser Link funktioniert nicht mehr. Sie können E-Mails stattdessen in den Einstellungen abbestellen.",
1171
+ failed: "Das hat nicht geklappt. Bitte erneut versuchen.",
1172
+ },
1075
1173
  sessions: {
1076
1174
  title: "Sitzungen",
1077
1175
  description:
@@ -1634,6 +1732,21 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1634
1732
  contrastModes: { system: "System", standard: "Standard", more: "Mehr" },
1635
1733
  saveFailed: "Konnte nicht in Ihrem Konto gespeichert werden. Auf diesem Gerät gilt es trotzdem.",
1636
1734
  },
1735
+ // 0.36.0 — docs/settings-harmonization.md §11.3: resetting this device's settings, the
1736
+ // last card of "Darstellung". "Theme", as `topBar.theme`; "Bereiche" for the panels
1737
+ // (the sidebar, folded groups).
1738
+ deviceReset: {
1739
+ title: "Dieses Gerät zurücksetzen",
1740
+ description:
1741
+ "Setzt die Darstellung auf diesem Gerät und die Ansicht seiner Tabellen und Bereiche auf die Standardwerte zurück. Ihr Konto, Ihre Daten und Ihre Anmeldung bleiben unberührt.",
1742
+ action: "Einstellungen dieses Geräts zurücksetzen",
1743
+ confirmTitle: "Einstellungen dieses Geräts zurücksetzen?",
1744
+ confirmBody:
1745
+ "Theme, Farben, Textgröße, Kontrast und Sprache kehren hier zu ihren Standardwerten zurück, und Tabellen und Bereiche öffnen sich wie beim ersten Mal. Wo Ihr Konto eine Sprache, eine Textgröße oder einen Kontrast festlegt, folgt dieses Gerät wieder dem Konto. Ihr Konto und Ihre anderen Geräte bleiben, wie sie sind.",
1746
+ confirmAction: "Zurücksetzen",
1747
+ done: "Die Einstellungen dieses Geräts sind auf die Standardwerte zurückgesetzt.",
1748
+ keywords: "zurücksetzen standard standardwerte gerät wiederherstellen darstellung tabellen layout",
1749
+ },
1637
1750
  rowActions: {
1638
1751
  actions: "Aktionen",
1639
1752
  actionsFor: (name) => `Aktionen für ${name}`,