@eifi1/ui-kit 0.40.1 → 0.42.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 (159) hide show
  1. package/README.md +19 -6
  2. package/dist/admin/admin-area-labels.d.ts +55 -0
  3. package/dist/admin/admin-area-labels.js +33 -0
  4. package/dist/admin/admin-area-labels.js.map +1 -0
  5. package/dist/admin/admin-forbidden.d.ts +35 -0
  6. package/dist/admin/admin-forbidden.js +36 -0
  7. package/dist/admin/admin-forbidden.js.map +1 -0
  8. package/dist/admin/admin-overview.d.ts +93 -0
  9. package/dist/admin/admin-overview.js +69 -0
  10. package/dist/admin/admin-overview.js.map +1 -0
  11. package/dist/admin/admin-sections.d.ts +63 -0
  12. package/dist/admin/admin-sections.js +64 -0
  13. package/dist/admin/admin-sections.js.map +1 -0
  14. package/dist/admin/admin-tool-links.d.ts +48 -0
  15. package/dist/admin/admin-tool-links.js +31 -0
  16. package/dist/admin/admin-tool-links.js.map +1 -0
  17. package/dist/billing/billing-banner.d.ts +25 -2
  18. package/dist/billing/billing-banner.js +5 -2
  19. package/dist/billing/billing-banner.js.map +1 -1
  20. package/dist/billing/billing-labels.d.ts +32 -0
  21. package/dist/billing/billing-labels.js +9 -0
  22. package/dist/billing/billing-labels.js.map +1 -1
  23. package/dist/billing/billing-overview.d.ts +33 -0
  24. package/dist/billing/billing-overview.js +6 -0
  25. package/dist/billing/billing-overview.js.map +1 -1
  26. package/dist/billing/billing-standing.d.ts +8 -0
  27. package/dist/billing/billing-standing.js.map +1 -1
  28. package/dist/billing/cancel-subscription.d.ts +46 -0
  29. package/dist/billing/cancel-subscription.js +57 -0
  30. package/dist/billing/cancel-subscription.js.map +1 -0
  31. package/dist/billing/plan-card.d.ts +45 -1
  32. package/dist/billing/plan-card.js +53 -2
  33. package/dist/billing/plan-card.js.map +1 -1
  34. package/dist/billing/plan-switch.d.ts +10 -1
  35. package/dist/billing/plan-switch.js +22 -13
  36. package/dist/billing/plan-switch.js.map +1 -1
  37. package/dist/billing/subscription-actions.d.ts +37 -4
  38. package/dist/billing/subscription-actions.js +10 -3
  39. package/dist/billing/subscription-actions.js.map +1 -1
  40. package/dist/components/account-chips.d.ts +42 -1
  41. package/dist/components/account-chips.js +33 -0
  42. package/dist/components/account-chips.js.map +1 -1
  43. package/dist/components/empty-state.d.ts +2 -1
  44. package/dist/components/empty-state.js.map +1 -1
  45. package/dist/components/nav-pills.d.ts +2 -2
  46. package/dist/components/nav-pills.js.map +1 -1
  47. package/dist/components/tabs.d.ts +4 -32
  48. package/dist/components/tabs.js +30 -66
  49. package/dist/components/tabs.js.map +1 -1
  50. package/dist/components/translation-review-parts.d.ts +2 -2
  51. package/dist/components/translation-review-parts.js.map +1 -1
  52. package/dist/eslint/index.js +1 -1
  53. package/dist/feedback/feedback-menu-labels.js +0 -2
  54. package/dist/feedback/feedback-menu-labels.js.map +1 -1
  55. package/dist/feedback/feedback-menu.d.ts +0 -6
  56. package/dist/feedback/feedback-menu.js.map +1 -1
  57. package/dist/i18n/defaults.js +5 -1
  58. package/dist/i18n/defaults.js.map +1 -1
  59. package/dist/i18n/german.js +79 -2
  60. package/dist/i18n/german.js.map +1 -1
  61. package/dist/i18n/kit-labels.d.ts +9 -0
  62. package/dist/i18n/kit-labels.js.map +1 -1
  63. package/dist/i18n/locales/en.js +93 -3
  64. package/dist/i18n/locales/en.js.map +3 -3
  65. package/dist/i18n/locales/es.js +76 -2
  66. package/dist/i18n/locales/es.js.map +1 -1
  67. package/dist/i18n/locales/fr.js +75 -2
  68. package/dist/i18n/locales/fr.js.map +1 -1
  69. package/dist/i18n/locales/hu.js +76 -2
  70. package/dist/i18n/locales/hu.js.map +1 -1
  71. package/dist/i18n/locales/it.js +76 -2
  72. package/dist/i18n/locales/it.js.map +1 -1
  73. package/dist/i18n/locales/zh.js +75 -2
  74. package/dist/i18n/locales/zh.js.map +1 -1
  75. package/dist/i18n/review.js +20 -1
  76. package/dist/i18n/review.js.map +1 -1
  77. package/dist/index.d.ts +18 -2
  78. package/dist/index.js +23 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/settings/settings-layout.d.ts +7 -1
  81. package/dist/settings/settings-layout.js +35 -27
  82. package/dist/settings/settings-layout.js.map +1 -1
  83. package/dist/shell/account-menu-language.d.ts +46 -0
  84. package/dist/shell/account-menu-language.js +64 -0
  85. package/dist/shell/account-menu-language.js.map +1 -0
  86. package/dist/shell/admin-menu.d.ts +46 -0
  87. package/dist/shell/admin-menu.js +60 -0
  88. package/dist/shell/admin-menu.js.map +1 -0
  89. package/dist/shell/app-shell.d.ts +10 -0
  90. package/dist/shell/app-shell.js +22 -4
  91. package/dist/shell/app-shell.js.map +1 -1
  92. package/dist/shell/dev-switchers.d.ts +53 -0
  93. package/dist/shell/dev-switchers.js +102 -0
  94. package/dist/shell/dev-switchers.js.map +1 -0
  95. package/dist/shell/sync-status-indicator.d.ts +86 -0
  96. package/dist/shell/sync-status-indicator.js +170 -0
  97. package/dist/shell/sync-status-indicator.js.map +1 -0
  98. package/dist/shell/top-bar-actions.d.ts +49 -0
  99. package/dist/shell/top-bar-actions.js +31 -0
  100. package/dist/shell/top-bar-actions.js.map +1 -0
  101. package/dist/shell/top-bar-parts-labels.d.ts +84 -0
  102. package/dist/shell/top-bar-parts-labels.js +48 -0
  103. package/dist/shell/top-bar-parts-labels.js.map +1 -0
  104. package/dist/shell/topbar-action-menu.d.ts +13 -1
  105. package/dist/shell/topbar-action-menu.js +5 -2
  106. package/dist/shell/topbar-action-menu.js.map +1 -1
  107. package/dist/shell/topbar-controls.d.ts +16 -3
  108. package/dist/shell/topbar-controls.js +5 -1
  109. package/dist/shell/topbar-controls.js.map +1 -1
  110. package/dist/shell/tour-menu.d.ts +58 -0
  111. package/dist/shell/tour-menu.js +73 -0
  112. package/dist/shell/tour-menu.js.map +1 -0
  113. package/dist/shell.d.ts +14 -0
  114. package/dist/shell.js +19 -0
  115. package/dist/shell.js.map +1 -1
  116. package/package.json +1 -1
  117. package/src/admin/admin-area-labels.ts +85 -0
  118. package/src/admin/admin-forbidden.tsx +75 -0
  119. package/src/admin/admin-overview.tsx +166 -0
  120. package/src/admin/admin-sections.tsx +137 -0
  121. package/src/admin/admin-tool-links.tsx +84 -0
  122. package/src/billing/billing-banner.tsx +32 -4
  123. package/src/billing/billing-labels.ts +53 -0
  124. package/src/billing/billing-overview.ts +37 -0
  125. package/src/billing/billing-standing.ts +8 -0
  126. package/src/billing/cancel-subscription.tsx +102 -0
  127. package/src/billing/plan-card.tsx +97 -1
  128. package/src/billing/plan-switch.tsx +16 -0
  129. package/src/billing/subscription-actions.tsx +64 -15
  130. package/src/components/account-chips.tsx +81 -1
  131. package/src/components/empty-state.tsx +2 -1
  132. package/src/components/nav-pills.tsx +2 -2
  133. package/src/components/tabs.tsx +19 -110
  134. package/src/components/translation-review-parts.tsx +2 -2
  135. package/src/feedback/feedback-menu-labels.ts +0 -2
  136. package/src/feedback/feedback-menu.tsx +0 -6
  137. package/src/i18n/defaults.ts +4 -0
  138. package/src/i18n/german.ts +92 -2
  139. package/src/i18n/kit-labels.tsx +9 -0
  140. package/src/i18n/locales/en.ts +19 -0
  141. package/src/i18n/locales/es.ts +86 -2
  142. package/src/i18n/locales/fr.ts +86 -2
  143. package/src/i18n/locales/hu.ts +84 -2
  144. package/src/i18n/locales/it.ts +87 -2
  145. package/src/i18n/locales/zh.ts +79 -2
  146. package/src/i18n/review.ts +19 -0
  147. package/src/index.ts +30 -2
  148. package/src/settings/settings-layout.tsx +58 -34
  149. package/src/shell/account-menu-language.tsx +116 -0
  150. package/src/shell/admin-menu.tsx +117 -0
  151. package/src/shell/app-shell.tsx +44 -3
  152. package/src/shell/dev-switchers.tsx +171 -0
  153. package/src/shell/sync-status-indicator.tsx +301 -0
  154. package/src/shell/top-bar-actions.tsx +79 -0
  155. package/src/shell/top-bar-parts-labels.ts +133 -0
  156. package/src/shell/topbar-action-menu.tsx +15 -2
  157. package/src/shell/topbar-controls.tsx +20 -3
  158. package/src/shell/tour-menu.tsx +144 -0
  159. package/src/shell.ts +24 -0
@@ -77,34 +77,6 @@ export interface TabsProps<T extends string>
77
77
  active: T;
78
78
  onChange: (id: T) => void;
79
79
  className?: string;
80
- /** Let the strip WRAP onto as many rows as it needs below `md`, instead of
81
- * scrolling sideways — for a strip carrying more tabs than fit on a phone row.
82
- * Keksdose live #262: ten reports on a 406px screen showed about three, and the
83
- * round-2 answer (a <select> below `md`) cost a tap to open and a tap to choose.
84
- * *"I would rather keep the tabs but have multirow tabs depending on the screen
85
- * size, I will not loose the function to see all reports at once and navigate by
86
- * single click rather by double click."*
87
- *
88
- * The active MARKER has to change with the layout, which is why this cannot be
89
- * done from a call site with a `className`: the default marker is a `border-b-2`
90
- * underline riding the container's own bottom rule, and a tab sitting in a row
91
- * that does not touch that rule cannot wear it — every row but the last would
92
- * show a stray line floating mid-strip, and `-mb-px` would pull each chip a
93
- * pixel into the row beneath it. A wrapped strip marks the active tab with a
94
- * filled brand chip instead, which is also the only thing findable at a glance
95
- * among ten same-weight labels on three rows.
96
- *
97
- * From `md` up NOTHING changes: same single-line underline strip, same paddings,
98
- * same colours as every other strip in the app.
99
- *
100
- * Opt-in on purpose. The three-tab strips (invoices, statements, bank imports)
101
- * already fit a phone row, and turning those into chips would be an unrequested
102
- * redesign of three other pages.
103
- *
104
- * @deprecated 0.40 — use the scrolling strip with `overflowCue`; removed in 0.41. No
105
- * consumer is left (keksdose went back to one scrolling row, live #410); the caret
106
- * says a phone's row goes on. */
107
- wrap?: boolean;
108
80
  /**
109
81
  * The caret beside a SCROLLING strip below `md` (0.40, kk run 79 gap 1): a › while
110
82
  * tabs lie past the strip's end — a tap scrolls it on by three quarters of its width —
@@ -117,7 +89,7 @@ export interface TabsProps<T extends string>
117
89
  * turns round rather than two that come and go, so the strip's width never changes
118
90
  * under the thumb. Out of the tab order — the keyboard walks the strip with the arrow
119
91
  * keys, which bring the focused tab into view. From `md` up nothing changes. Ignored on
120
- * a `wrap` strip and a vertical one (a side nav is a column, not a row). Names:
92
+ * a vertical strip (a side nav is a column, not a row). Names:
121
93
  * `tabs.more` / `tabs.backToFirst`.
122
94
  */
123
95
  overflowCue?: boolean;
@@ -203,8 +175,8 @@ export interface TabsProps<T extends string>
203
175
  * `onRemove`, `onAdd` — means exactly what it means on the horizontal strip; the
204
176
  * badge moves to the row's end.
205
177
  *
206
- * **On a phone (`usePhoneLayout()`) it becomes the horizontal strip**, `wrap` and
207
- * all, rather than staying a column. A side nav only works beside its content; on
178
+ * **On a phone (`usePhoneLayout()`) it becomes the horizontal strip**, rather than
179
+ * staying a column. A side nav only works beside its content; on
208
180
  * a phone it has to go ABOVE it, and eight full-width rows there push the panel
209
181
  * the user picked below the fold on every visit — the two keksdose pages both
210
182
  * collapsed theirs into a scrolling row by hand for exactly that reason. The
@@ -220,14 +192,6 @@ export interface TabsProps<T extends string>
220
192
  ref?: Ref<HTMLDivElement>;
221
193
  }
222
194
 
223
- // The two shapes are written out as whole strings rather than as one base plus a
224
- // pile of `md:` overrides. The unwrapped pair is character-for-character what the
225
- // three strips that do not opt in get, so those cannot drift; the wrapped pair is
226
- // authored from scratch, so no unprefixed utility has to be beaten by its own `md:`
227
- // twin through tailwind-merge. The only ordering this relies on is Tailwind's own:
228
- // unprefixed utilities are emitted first, then `md:` — so from 768px up the wrapped
229
- // strip resolves to exactly the paint of the default one. Both themes come free now
230
- // that the colours are tokens: there is no `md:dark:` tier left to keep in step.
231
195
  const TABLIST_CLASSES =
232
196
  "flex gap-1 overflow-x-auto overflow-y-hidden border-b border-subtle";
233
197
  // The side nav. A column of full-width rows with the open one FILLED rather than
@@ -242,9 +206,6 @@ const TAB_VERTICAL_ACTIVE_CLASSES = "bg-active text-primary";
242
206
  const TAB_VERTICAL_INACTIVE_CLASSES =
243
207
  "text-muted hover:bg-hover hover:text-primary";
244
208
 
245
- const TABLIST_WRAP_CLASSES =
246
- "flex flex-wrap gap-1.5 md:flex-nowrap md:gap-1 md:overflow-x-auto md:overflow-y-hidden md:border-b md:border-subtle";
247
-
248
209
  // `max-w-full`: no tab outgrows its strip. A sheet's `detail` line ("Spurstangenkraft
249
210
  // links · … · 31 Punkte") made one tab 448px wide in a 390px strip, and a tab wider
250
211
  // than the strip cannot be scrolled into view — its end was simply off screen
@@ -256,31 +217,13 @@ const TAB_ACTIVE_CLASSES = "border-[var(--text-primary)] text-primary";
256
217
  const TAB_INACTIVE_CLASSES =
257
218
  "border-transparent text-muted hover:text-secondary hover:border-strong";
258
219
 
259
- // Below `md`, a chip: `py-2` on `text-xs` is exactly 32px tall, and ten German
260
- // report labels then land in three rows on a 406px screen (four at `text-sm`). The
261
- // active fill is the app's own selected pair, `--brand` / `--brand-contrast`: the
262
- // palette store writes both halves of it together for every preset and both themes,
263
- // so the contrast holds everywhere (5.1:1 at its worst preset) without a hard-coded
264
- // colour. Inactive chips take the raised card surface with full-strength body text —
265
- // all ten have to stay readable; it is the FILL, not the text weight, that says
266
- // which one is open.
267
- const TAB_WRAP_CLASSES =
268
- FOCUS_RING_WIDTH +
269
- " max-w-full whitespace-nowrap rounded-md px-2.5 py-2 text-xs font-medium transition-colors focus:outline-none focus-visible:ring-strong md:rounded-none md:border-b-2 md:-mb-px md:bg-transparent md:px-3 md:py-2 md:text-sm";
270
- const TAB_WRAP_ACTIVE_CLASSES =
271
- "bg-brand text-brand-contrast md:border-[var(--text-primary)] md:text-primary";
272
- const TAB_WRAP_INACTIVE_CLASSES =
273
- "bg-surface text-primary md:border-transparent md:text-muted md:hover:border-strong md:hover:text-secondary";
274
-
275
220
  // The add/remove chrome. The × reserves its gutter on every removable tab (see
276
- // `onRemove`); `md:pe-8` is spelled out because the wrapped chip's `md:px-3` would
277
- // otherwise hand the gutter back from 768px up.
278
- const TAB_REMOVABLE_CLASSES = "pe-8 md:pe-8";
221
+ // `onRemove`).
222
+ const TAB_REMOVABLE_CLASSES = "pe-8";
279
223
  const TAB_EMPTY_CLASSES = "-mx-1.5 rounded border border-dashed border-strong px-1.5";
280
224
  const TAB_ADD_CLASSES =
281
225
  FOCUS_RING_WIDTH +
282
226
  " inline-flex items-center gap-1 whitespace-nowrap rounded-md px-3 py-2 text-sm font-medium text-muted transition-colors hover:text-primary focus:outline-none focus-visible:ring-strong disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-4";
283
- const TAB_ADD_WRAP_CLASSES = "px-2.5 text-xs md:px-3 md:text-sm";
284
227
  // `relative`: the phone's visually hidden label needs a positioned ancestor (see the
285
228
  // sr-only rule in CONTRIBUTING); `max-md:px-2` squares the lone "+".
286
229
  const TAB_ADD_PHONE_CLASSES = "relative max-md:px-2";
@@ -290,7 +233,6 @@ export function Tabs<T extends string>({
290
233
  active,
291
234
  onChange,
292
235
  className,
293
- wrap = false,
294
236
  overflowCue = false,
295
237
  panelId,
296
238
  onRemove,
@@ -312,7 +254,7 @@ export function Tabs<T extends string>({
312
254
  const setStrip = useMemo(() => mergeRefs(stripRef, ref), [ref]);
313
255
  const fade = useStripFade(stripRef);
314
256
  // See `overflowCue`. `start` alone means the strip is scrolled to its end.
315
- const cue = overflowCue && !wrap && !vertical && fade.overflow !== undefined;
257
+ const cue = overflowCue && !vertical && fade.overflow !== undefined;
316
258
  const cueAtEnd = fade.overflow === "start";
317
259
  const onCue = () => {
318
260
  const strip = stripRef.current;
@@ -394,9 +336,7 @@ export function Tabs<T extends string>({
394
336
  strip.querySelectorAll<HTMLElement>('[role="tab"]')[index]?.focus();
395
337
  }, [tabs, active]);
396
338
 
397
- // Arrow keys walk the strip in DOM order (ARIA tabs pattern), which is what keeps
398
- // a WRAPPED strip navigable: the rows flow in DOM order too, so Right off the end
399
- // of row one lands on the first chip of row two rather than nowhere. Home/End jump
339
+ // Arrow keys walk the strip in DOM order (ARIA tabs pattern). Home/End jump
400
340
  // to the ends. Focus only — activation stays on click/Enter/Space, because a tab
401
341
  // here can be a real route and moving focus must not navigate.
402
342
  //
@@ -465,8 +405,8 @@ export function Tabs<T extends string>({
465
405
  // With an add button the strip gains an outer box, and `className` goes there
466
406
  // — it is the box a caller's margin or width is meant for.
467
407
  className={cn(
468
- vertical ? TABLIST_VERTICAL_CLASSES : wrap ? TABLIST_WRAP_CLASSES : TABLIST_CLASSES,
469
- onAdd ? "min-w-0" : overflowCue && !wrap && !vertical ? "min-w-0 flex-1" : className,
408
+ vertical ? TABLIST_VERTICAL_CLASSES : TABLIST_CLASSES,
409
+ onAdd ? "min-w-0" : overflowCue && !vertical ? "min-w-0 flex-1" : className,
470
410
  )}
471
411
  >
472
412
  {tabs.map((tab) => {
@@ -502,14 +442,8 @@ export function Tabs<T extends string>({
502
442
  removable && "pe-8",
503
443
  )
504
444
  : cn(
505
- wrap ? TAB_WRAP_CLASSES : TAB_CLASSES,
506
- wrap
507
- ? isActive
508
- ? TAB_WRAP_ACTIVE_CLASSES
509
- : TAB_WRAP_INACTIVE_CLASSES
510
- : isActive
511
- ? TAB_ACTIVE_CLASSES
512
- : TAB_INACTIVE_CLASSES,
445
+ TAB_CLASSES,
446
+ isActive ? TAB_ACTIVE_CLASSES : TAB_INACTIVE_CLASSES,
513
447
  removable && TAB_REMOVABLE_CLASSES,
514
448
  ),
515
449
  };
@@ -537,16 +471,7 @@ export function Tabs<T extends string>({
537
471
  {/* A separator for the NAME only — whitespace between flex items is
538
472
  not drawn, and without it the two lines ran together into one
539
473
  word for a screen reader ("80 km/hloop 2"). */}{" "}
540
- <span
541
- className={cn(
542
- "max-w-full truncate-until-large text-caption font-normal leading-tight text-muted",
543
- // On a wrapped strip's filled chip (below `md`) the muted grey
544
- // was dark text on the brand fill — "loop 2" all but vanished
545
- // under the one label that most needed reading. It takes the
546
- // chip's own ink instead, as the label and the × already do.
547
- wrap && isActive && !vertical && "text-brand-contrast md:text-muted",
548
- )}
549
- >
474
+ <span className="max-w-full truncate-until-large text-caption font-normal leading-tight text-muted">
550
475
  {tab.detail}
551
476
  </span>
552
477
  </span>
@@ -620,12 +545,7 @@ export function Tabs<T extends string>({
620
545
  type="button"
621
546
  disabled={busy}
622
547
  onClick={() => requestRemove(tab.id)}
623
- className={cn(
624
- "absolute inset-y-0 end-1 my-auto",
625
- // On a wrapped strip's filled chip the quiet grey would vanish into
626
- // the brand fill below `md`.
627
- wrap && "text-brand-contrast md:text-placeholder",
628
- )}
548
+ className="absolute inset-y-0 end-1 my-auto"
629
549
  >
630
550
  <X />
631
551
  </IconButton>
@@ -654,7 +574,7 @@ export function Tabs<T extends string>({
654
574
  {text.remove(nameOf(activeTab))}
655
575
  </button>
656
576
  ) : null;
657
- if (!onAdd && overflowCue && !wrap && !vertical) {
577
+ if (!onAdd && overflowCue && !vertical) {
658
578
  // The caret needs a row to sit in beside the strip, and `className` goes there — it
659
579
  // is the box a caller's margin or width is meant for, as with an add button. The box
660
580
  // is there whether or not the strip overflows right now, so a resize never moves
@@ -692,37 +612,26 @@ export function Tabs<T extends string>({
692
612
  );
693
613
  }
694
614
  return (
695
- <div
696
- className={cn(
697
- wrap ? "flex flex-wrap items-end gap-1.5 md:flex-nowrap md:gap-0" : "flex items-end",
698
- className,
699
- )}
700
- >
615
+ <div className={cn("flex items-end", className)}>
701
616
  {tablist}
702
617
  {removeButton}
703
618
  {cueButton}
704
619
  {/* Outside the tablist: an action, not a tab — and a tablist may own only
705
620
  tabs. The rule under the strip carries on beneath it. */}
706
621
  <div
707
- className={cn(
708
- "flex flex-1 items-center",
709
- wrap
710
- ? "md:self-stretch md:border-b md:border-subtle"
711
- : "self-stretch border-b border-subtle",
712
- )}
622
+ className="flex flex-1 items-center self-stretch border-b border-subtle"
713
623
  >
714
624
  <button
715
625
  type="button"
716
626
  onClick={onAdd}
717
627
  disabled={busy}
718
- className={cn(TAB_ADD_CLASSES, wrap ? TAB_ADD_WRAP_CLASSES : TAB_ADD_PHONE_CLASSES)}
628
+ className={cn(TAB_ADD_CLASSES, TAB_ADD_PHONE_CLASSES)}
719
629
  >
720
630
  <Plus aria-hidden />
721
631
  {/* Below md a scrolling strip keeps the room for tabs: the label is visually
722
632
  hidden and the "+" carries it as its name. Beside "+ Add loop" a 390px strip
723
- showed the open tab and a sliver of the next (showcase audit). A wrapping
724
- strip has its own row there, so it keeps the label. */}
725
- <span className={wrap ? undefined : "max-md:sr-only"}>{addLabel ?? text.add}</span>
633
+ showed the open tab and a sliver of the next (showcase audit). */}
634
+ <span className="max-md:sr-only">{addLabel ?? text.add}</span>
726
635
  </button>
727
636
  </div>
728
637
  </div>
@@ -170,8 +170,8 @@ export interface TranslationLocaleTabsProps {
170
170
 
171
171
  /**
172
172
  * One tab per locale with its approved/total, one scrolling row on a phone with the
173
- * strip's caret saying it goes on (`overflowCue`; `wrap` is deprecated in 0.40). Nothing for a single
174
- * locale: a strip with one tab is a heading that looks clickable.
173
+ * strip's caret saying it goes on (`overflowCue`). Nothing for a single locale:
174
+ * a strip with one tab is a heading that looks clickable.
175
175
  */
176
176
  export function TranslationLocaleTabs({ locales, active, onChange, className, labels: labelsProp }: TranslationLocaleTabsProps) {
177
177
  const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
@@ -7,8 +7,6 @@ import type { FeedbackMenuLabels } from "./feedback-menu";
7
7
  export const DEFAULT_FEEDBACK_MENU_LABELS: FeedbackMenuLabels = {
8
8
  trigger: "Send feedback",
9
9
  myFeedback: "My feedback",
10
- viewFeedback: "View feedback",
11
- allFeedback: "All feedback",
12
10
  discussion: "Feedback discussion",
13
11
  inbox: "Feedback inbox",
14
12
  };
@@ -28,12 +28,6 @@ export interface FeedbackMenuLabels {
28
28
  /** 0.40: the admin's link to the triage list, {@link FeedbackMenuProps.inboxHref}
29
29
  * ("View feedback" before). de-CH "Feedback-Eingang". */
30
30
  inbox: string;
31
- /** @deprecated 0.40 — use {@link inbox}; removed in 0.41. Unused: the inbox row reads
32
- * `inbox` (feedback §10.6). */
33
- viewFeedback?: string;
34
- /** @deprecated 0.40 — use {@link discussion}; removed in 0.41. Unused: the public row
35
- * reads `discussion` (feedback §10.6). */
36
- allFeedback?: string;
37
31
  }
38
32
 
39
33
  // Defined in ./feedback-menu-labels, which holds no component code (0.34, KS U8).
@@ -98,6 +98,8 @@ import { DEFAULT_DEVICE_RESET_LABELS } from "../settings/device-reset-setting-la
98
98
  import { DEFAULT_ROW_ACTIONS_LABELS } from "../components/row-actions-labels";
99
99
  import { DEFAULT_APP_SHELL_MORE_LABELS } from "../shell/app-shell-labels";
100
100
  import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips-labels";
101
+ import { DEFAULT_ADMIN_AREA_LABELS } from "../admin/admin-area-labels";
102
+ import { DEFAULT_TOP_BAR_PARTS_LABELS } from "../shell/top-bar-parts-labels";
101
103
  import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card-labels";
102
104
  import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog-labels";
103
105
  import { DEFAULT_SERVER_WAKE_LABELS } from "../components/server-wake-labels";
@@ -256,4 +258,6 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
256
258
  signChip: DEFAULT_SIGN_CHIP_LABELS,
257
259
  columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,
258
260
  notifications: DEFAULT_NOTIFICATIONS_LABELS,
261
+ adminArea: DEFAULT_ADMIN_AREA_LABELS,
262
+ topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,
259
263
  };
@@ -121,8 +121,6 @@ export function germanLabels(numberLocale: string): UiKitLabels {
121
121
  feedbackMenu: {
122
122
  trigger: "Feedback senden",
123
123
  myFeedback: "Mein Feedback",
124
- viewFeedback: "Feedback ansehen",
125
- allFeedback: "Alle Rückmeldungen",
126
124
  discussion: "Feedback-Diskussion",
127
125
  inbox: "Feedback-Eingang",
128
126
  },
@@ -1731,6 +1729,12 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1731
1729
  "Ihre Zahlung wird verarbeitet. Ihr Tarif beginnt, sobald sie bestätigt ist; diese Seite aktualisiert sich von selbst.",
1732
1730
  choosePlan: "Tarif wählen",
1733
1731
  updatePayment: "Zahlungsmethode aktualisieren",
1732
+ // 0.42 (§16.7 A): for an admin who isn't the buyer. "Das Admin-Konto", which gives the
1733
+ // reader no gender.
1734
+ paymentFailedAskBuyer: (buyer) =>
1735
+ buyer
1736
+ ? `Die Zahlung ist fehlgeschlagen. ${buyer} kann die Zahlungsmethode aktualisieren.`
1737
+ : "Die Zahlung ist fehlgeschlagen. Die Zahlungsmethode kann über das Admin-Konto aktualisiert werden, mit dem das Abonnement abgeschlossen wurde.",
1734
1738
  checkAgain: "Erneut prüfen",
1735
1739
  lockReason: "Ihr Tarif ist abgelaufen. Wählen Sie einen Tarif, um wieder Änderungen vorzunehmen.",
1736
1740
  // As `characterCount.limitReached` ("Zeichenlimit erreicht").
@@ -1765,6 +1769,20 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1765
1769
  switchCredited: "Gutschrift auf die nächsten Belastungen",
1766
1770
  switchNextCharge: (date) => `Nächste Belastung am ${date}`,
1767
1771
  switchConfirm: "Tarif ändern",
1772
+ // 0.42 (§16): the portal for the buyer only, the in-app cancel, the grant warning.
1773
+ buyerHolds: (buyer) =>
1774
+ buyer
1775
+ ? `Zahlungsmethode und Rechnungen liegen bei ${buyer}: Das Abonnement wurde über dieses Konto abgeschlossen.`
1776
+ : "Zahlungsmethode und Rechnungen liegen beim Admin-Konto, mit dem das Abonnement abgeschlossen wurde.",
1777
+ cancelConfirmTitle: "Abonnement kündigen",
1778
+ cancelConfirmBody: (date) => `Ihr Abonnement endet am ${date}. Bis dahin funktioniert alles wie bisher.`,
1779
+ // §312k BGB: the confirmation button says exactly this.
1780
+ cancelConfirmAction: "Jetzt kündigen",
1781
+ keepSubscription: "Abonnement behalten",
1782
+ grantWarningTitle: "Ihr kostenloser Zugang läuft weiter",
1783
+ grantWarningBody:
1784
+ "Ihr kostenloser Zugang hat kein Enddatum. Solange er läuft, ändert ein Kauf Ihren Tarif nicht: Sie würden für einen Tarif bezahlen, der nicht gilt.",
1785
+ grantWarningAction: "Trotzdem kaufen",
1768
1786
  waitingChanges: (count) =>
1769
1787
  count === 1
1770
1788
  ? `${n(count)} Änderung wartet auf einen Tarif`
@@ -1841,5 +1859,77 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1841
1859
  more: "Mehr",
1842
1860
  moreTitle: "Weitere Seiten",
1843
1861
  },
1862
+ // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Verwaltung"
1863
+ // as settings §5 names the page; "mit Administratorrechten" rather than "für
1864
+ // Administratoren", which would give the reader a gender (as `transferOwnership`).
1865
+ adminArea: {
1866
+ title: "Verwaltung",
1867
+ waiting: (count) => `${n(count)} offen`,
1868
+ forbiddenTitle: "Nur mit Administratorrechten",
1869
+ forbiddenBody: (appName) => `Hier wird ${appName} verwaltet. Ihr Konto kann diese Seite nicht öffnen.`,
1870
+ forbiddenHome: "Zur Startseite",
1871
+ forbiddenReviewer: "Übersetzungen prüfen",
1872
+ sections: {
1873
+ overview: { title: "Übersicht", help: "Was gerade wartet, auf einen Blick." },
1874
+ users: { title: "Benutzer", help: "Wer ein Konto hat und was jede Person darf." },
1875
+ invitations: { title: "Einladungen", help: "Die Links, mit denen neue Personen dazukommen." },
1876
+ billing: { title: "Abrechnung", help: "Tarif, Zahlung und Rechnungen." },
1877
+ activity: { title: "Aktivität", help: "Was in der Verwaltung getan wurde, das Neueste zuerst." },
1878
+ },
1879
+ tiles: {
1880
+ feedbackWaiting: "Zu sichten",
1881
+ deletionRequests: "Löschanfragen",
1882
+ invitationsPending: "Offene Einladungen",
1883
+ users: "Benutzer",
1884
+ usersNew: (count) => `${n(count)} neu in 7 Tagen`,
1885
+ },
1886
+ tools: "Werkzeuge",
1887
+ },
1888
+ // 0.42.0 — docs/top-bar-harmonization.md §3.1: the top bar's shared parts. keksdose's
1889
+ // reviewed sync words where they are not about its budgets; "Tarif" as `billing`.
1890
+ topBarParts: {
1891
+ tours: {
1892
+ menu: "Geführte Touren",
1893
+ newDot: "Neue Touren",
1894
+ thisPage: "Auf dieser Seite",
1895
+ newTours: "Neue Touren",
1896
+ allTours: "Alle Touren…",
1897
+ badgeNew: "Neu",
1898
+ completed: "Abgeschlossen",
1899
+ },
1900
+ sync: {
1901
+ blocked: (count) =>
1902
+ count === 1
1903
+ ? `${n(count)} Änderung kann von diesem Gerät nicht gesendet werden`
1904
+ : `${n(count)} Änderungen können von diesem Gerät nicht gesendet werden`,
1905
+ offline: "Offline – Änderungen werden auf diesem Gerät gespeichert und bei erneuter Verbindung synchronisiert",
1906
+ refused: "Der Server hat den letzten Abgleich abgelehnt",
1907
+ syncing: "Wird synchronisiert…",
1908
+ pending: (count) =>
1909
+ count === 1
1910
+ ? `${n(count)} Änderung wartet auf Synchronisierung`
1911
+ : `${n(count)} Änderungen warten auf Synchronisierung`,
1912
+ syncedAt: (time) => `Synchronisiert um ${time}`,
1913
+ idle: "Aktuell",
1914
+ syncHint: "Tippen, um jetzt zu synchronisieren",
1915
+ offlineHint: "offline gibt es nichts zu synchronisieren",
1916
+ withHint: (state, hint) => `${state} – ${hint}`,
1917
+ scoped: (scope, state) => `${scope}: ${state}`,
1918
+ offlineShort: "Offline",
1919
+ syncingShort: "Sync läuft…",
1920
+ waitingShort: (count) => `${n(count)} ausstehend`,
1921
+ refusedShort: "Nicht synchronisiert",
1922
+ offlineUnsaved: "Offline – Änderungen können erst nach erneuter Verbindung gespeichert werden",
1923
+ saving: "Wird gespeichert…",
1924
+ },
1925
+ dev: {
1926
+ roleHeading: "Dev · Rolle",
1927
+ switchPlan: "Tarif wechseln",
1928
+ plan: (value) => `Tarif: ${value}`,
1929
+ planHeading: "Dev · Tarif",
1930
+ backTo: (role) => `Zurück zu ${role}`,
1931
+ billingOff: "Die Abrechnung ist lokal aus: Tarife ändern nichts",
1932
+ },
1933
+ },
1844
1934
  };
1845
1935
  }
@@ -118,6 +118,8 @@ import type { PhoneInputLabels } from "../components/phone-input";
118
118
  import type { SignChipLabels } from "../components/sign-chip";
119
119
  import type { ColumnMapperLabels } from "../components/column-mapper";
120
120
  import type { NotificationsLabels } from "../notifications/notifications-labels";
121
+ import type { AdminAreaLabels } from "../admin/admin-area-labels";
122
+ import type { TopBarPartsLabels } from "../shell/top-bar-parts-labels";
121
123
 
122
124
  /**
123
125
  * EVERY string the kit renders, as one typed tree — and an optional provider that
@@ -475,6 +477,13 @@ export interface UiKitLabels {
475
477
  * device's push, the device list, and the unsubscribe page
476
478
  * (docs/notifications-harmonization.md §4.2). */
477
479
  notifications: NotificationsLabels;
480
+ /** 0.42.0: the admin area's shared words — the account menu's "Administration" row, the
481
+ * 403 page, the shared sections, the overview's tiles and the Tools list
482
+ * (docs/admin-harmonization.md §3.1). */
483
+ adminArea: AdminAreaLabels;
484
+ /** 0.42.0: the top bar's shared parts — `TourMenu`, `SyncStatusIndicator`, `DevSwitchers`
485
+ * (docs/top-bar-harmonization.md §3.1). */
486
+ topBarParts: TopBarPartsLabels;
478
487
  }
479
488
 
480
489
  /**
@@ -216,6 +216,25 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
216
216
  ...d.settings,
217
217
  matchCount: (count) => (count === 1 ? "1 match" : `${n(count)} matches`),
218
218
  },
219
+ // 0.42.0: the admin area's two counts (docs/admin-harmonization.md §3.1).
220
+ adminArea: {
221
+ ...d.adminArea,
222
+ waiting: (count) => `${n(count)} waiting`,
223
+ tiles: { ...d.adminArea.tiles, usersNew: (count) => `${n(count)} new in 7 days` },
224
+ },
225
+ // 0.42.0: the top bar's three counts (docs/top-bar-harmonization.md §3.1).
226
+ topBarParts: {
227
+ ...d.topBarParts,
228
+ sync: {
229
+ ...d.topBarParts.sync,
230
+ blocked: (count) =>
231
+ count === 1
232
+ ? "1 change cannot be sent from this device"
233
+ : `${n(count)} changes cannot be sent from this device`,
234
+ pending: (count) => (count === 1 ? "1 change waiting to sync" : `${n(count)} changes waiting to sync`),
235
+ waitingShort: (count) => `${n(count)} waiting`,
236
+ },
237
+ },
219
238
  demo: {
220
239
  ...d.demo,
221
240
  rateLimited: (minutes) =>
@@ -112,8 +112,6 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
112
112
  feedbackMenu: {
113
113
  trigger: "Enviar comentarios",
114
114
  myFeedback: "Mis comentarios",
115
- viewFeedback: "Ver comentarios",
116
- allFeedback: "Todos los comentarios",
117
115
  discussion: "Debate de comentarios",
118
116
  inbox: "Comentarios recibidos",
119
117
  },
@@ -1685,6 +1683,11 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1685
1683
  "Su pago se está procesando. Su plan empieza en cuanto se confirme; esta página se actualiza sola.",
1686
1684
  choosePlan: "Elegir un plan",
1687
1685
  updatePayment: "Actualizar el método de pago",
1686
+ // 0.42 (§16.7 A): for an admin who isn't the buyer.
1687
+ paymentFailedAskBuyer: (buyer) =>
1688
+ buyer
1689
+ ? `El pago no se ha completado. ${buyer} puede actualizar el método de pago.`
1690
+ : "El pago no se ha completado. El administrador que contrató la suscripción puede actualizar el método de pago.",
1688
1691
  checkAgain: "Volver a comprobar",
1689
1692
  lockReason: "Su plan ha terminado. Elija un plan para volver a hacer cambios.",
1690
1693
  // As `characterCount.limitReached` ("Límite de caracteres alcanzado").
@@ -1715,6 +1718,19 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1715
1718
  switchCredited: "Abonado a sus próximos cargos",
1716
1719
  switchNextCharge: (date) => `Próximo cargo el ${date}`,
1717
1720
  switchConfirm: "Cambiar de plan",
1721
+ // 0.42 (§16): the portal for the buyer only, the in-app cancel, the grant warning.
1722
+ buyerHolds: (buyer) =>
1723
+ buyer
1724
+ ? `El método de pago y las facturas los gestiona ${buyer}, que contrató la suscripción.`
1725
+ : "El método de pago y las facturas los gestiona el administrador que contrató la suscripción.",
1726
+ cancelConfirmTitle: "Cancelar la suscripción",
1727
+ cancelConfirmBody: (date) => `Su suscripción termina el ${date}. Hasta entonces, todo funciona como antes.`,
1728
+ cancelConfirmAction: "Cancelar ahora la suscripción",
1729
+ keepSubscription: "Mantener la suscripción",
1730
+ grantWarningTitle: "Su acceso gratuito continúa",
1731
+ grantWarningBody:
1732
+ "Su acceso gratuito no tiene fecha de fin. Mientras dure, una compra no cambia su plan: pagaría por un plan que no se aplica.",
1733
+ grantWarningAction: "Comprar de todos modos",
1718
1734
  waitingChanges: (count) => `${n(count)} ${plural(count, "cambio", "cambios")} a la espera de un plan`,
1719
1735
  // "Descartar", as `wizard.cancelConfirmLabel`.
1720
1736
  discardWaiting: "Descartar los cambios en espera",
@@ -1783,6 +1799,74 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1783
1799
  more: "Más",
1784
1800
  moreTitle: "Más páginas",
1785
1801
  },
1802
+ // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Usted",
1803
+ // as the rest of the catalogue.
1804
+ adminArea: {
1805
+ title: "Administración",
1806
+ waiting: (count) => `${n(count)} ${plural(count, "pendiente", "pendientes")}`,
1807
+ forbiddenTitle: "Solo para administradores",
1808
+ forbiddenBody: (appName) =>
1809
+ `Aquí es donde los administradores gestionan ${appName}. Su cuenta no puede abrir esta página.`,
1810
+ forbiddenHome: "Ir a la página de inicio",
1811
+ forbiddenReviewer: "Revisar traducciones",
1812
+ sections: {
1813
+ overview: { title: "Resumen", help: "Lo que está pendiente, de un vistazo." },
1814
+ users: { title: "Usuarios", help: "Quién tiene una cuenta y qué puede hacer cada uno." },
1815
+ invitations: { title: "Invitaciones", help: "Los enlaces con los que entra una persona nueva." },
1816
+ billing: { title: "Facturación", help: "El plan, el pago y las facturas." },
1817
+ activity: { title: "Actividad", help: "Lo que hicieron los administradores, lo más reciente primero." },
1818
+ },
1819
+ tiles: {
1820
+ feedbackWaiting: "Por clasificar",
1821
+ deletionRequests: "Solicitudes de eliminación",
1822
+ invitationsPending: "Invitaciones pendientes",
1823
+ users: "Usuarios",
1824
+ usersNew: (count) => `${n(count)} ${plural(count, "nuevo", "nuevos")} en 7 días`,
1825
+ },
1826
+ tools: "Herramientas",
1827
+ },
1828
+ // 0.42.0 — docs/top-bar-harmonization.md §3.1: the top bar's shared parts; "plan" as
1829
+ // `billing`, "usted" as the rest of the catalogue.
1830
+ topBarParts: {
1831
+ tours: {
1832
+ menu: "Visitas guiadas",
1833
+ newDot: "Visitas nuevas",
1834
+ thisPage: "En esta página",
1835
+ newTours: "Visitas nuevas",
1836
+ allTours: "Todas las visitas…",
1837
+ badgeNew: "Nuevo",
1838
+ completed: "Completada",
1839
+ },
1840
+ sync: {
1841
+ blocked: (count) =>
1842
+ `${n(count)} ${plural(count, "cambio no se puede enviar", "cambios no se pueden enviar")} desde este dispositivo`,
1843
+ offline: "Sin conexión: los cambios se guardan en este dispositivo y se sincronizarán al volver a conectarse",
1844
+ refused: "El servidor rechazó la última sincronización",
1845
+ syncing: "Sincronizando…",
1846
+ pending: (count) =>
1847
+ `${n(count)} ${plural(count, "cambio", "cambios")} a la espera de sincronizarse`,
1848
+ syncedAt: (time) => `Sincronizado a las ${time}`,
1849
+ idle: "Al día",
1850
+ syncHint: "Toque para sincronizar ahora",
1851
+ offlineHint: "no hay nada que sincronizar sin conexión",
1852
+ withHint: (state, hint) => `${state} — ${hint}`,
1853
+ scoped: (scope, state) => `${scope}: ${state}`,
1854
+ offlineShort: "Sin conexión",
1855
+ syncingShort: "Sincronizando…",
1856
+ waitingShort: (count) => `${n(count)} en espera`,
1857
+ refusedShort: "Sin sincronizar",
1858
+ offlineUnsaved: "Sin conexión: los cambios no se pueden guardar hasta que vuelva a conectarse",
1859
+ saving: "Guardando…",
1860
+ },
1861
+ dev: {
1862
+ roleHeading: "Dev · rol",
1863
+ switchPlan: "Cambiar de plan",
1864
+ plan: (value) => `Plan: ${value}`,
1865
+ planHeading: "Dev · plan",
1866
+ backTo: (role) => `Volver a ${role}`,
1867
+ billingOff: "La facturación está desactivada en local: los planes no cambian nada",
1868
+ },
1869
+ },
1786
1870
  };
1787
1871
  }
1788
1872