@eifi1/ui-kit 0.39.1 → 0.40.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 (143) hide show
  1. package/README.md +8 -7
  2. package/dist/billing/billing-overview.d.ts +10 -0
  3. package/dist/components/autocomplete.js +5 -1
  4. package/dist/components/autocomplete.js.map +1 -1
  5. package/dist/components/combobox-core.d.ts +14 -1
  6. package/dist/components/combobox-core.js +4 -0
  7. package/dist/components/combobox-core.js.map +1 -1
  8. package/dist/components/combobox.js +9 -3
  9. package/dist/components/combobox.js.map +1 -1
  10. package/dist/components/data-table-labels.d.ts +5 -0
  11. package/dist/components/data-table-labels.js +2 -0
  12. package/dist/components/data-table-labels.js.map +1 -1
  13. package/dist/components/data-table-mobile.d.ts +36 -1
  14. package/dist/components/data-table-mobile.js +162 -15
  15. package/dist/components/data-table-mobile.js.map +1 -1
  16. package/dist/components/data-table.d.ts +39 -12
  17. package/dist/components/data-table.js +74 -13
  18. package/dist/components/data-table.js.map +1 -1
  19. package/dist/components/inline-entity-combobox.js +10 -3
  20. package/dist/components/inline-entity-combobox.js.map +1 -1
  21. package/dist/components/picker-sheet.d.ts +18 -1
  22. package/dist/components/picker-sheet.js +20 -2
  23. package/dist/components/picker-sheet.js.map +1 -1
  24. package/dist/components/tabs.d.ts +26 -2
  25. package/dist/components/tabs.js +41 -2
  26. package/dist/components/tabs.js.map +1 -1
  27. package/dist/components/translation-review-parts.d.ts +2 -1
  28. package/dist/components/translation-review-parts.js +1 -1
  29. package/dist/components/translation-review-parts.js.map +1 -1
  30. package/dist/components/ui-labels.js +3 -1
  31. package/dist/components/ui-labels.js.map +1 -1
  32. package/dist/eslint/index.js +1 -1
  33. package/dist/feedback/feedback-comment-thread.d.ts +8 -1
  34. package/dist/feedback/feedback-comment-thread.js +26 -1
  35. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  36. package/dist/feedback/feedback-labels.d.ts +2 -1
  37. package/dist/feedback/feedback-labels.js +1 -0
  38. package/dist/feedback/feedback-labels.js.map +1 -1
  39. package/dist/feedback/feedback-menu-labels.js +3 -1
  40. package/dist/feedback/feedback-menu-labels.js.map +1 -1
  41. package/dist/feedback/feedback-menu.d.ts +31 -24
  42. package/dist/feedback/feedback-menu.js +16 -7
  43. package/dist/feedback/feedback-menu.js.map +1 -1
  44. package/dist/feedback/feedback-public-labels.d.ts +3 -2
  45. package/dist/feedback/feedback-public-labels.js +1 -1
  46. package/dist/feedback/feedback-public-labels.js.map +1 -1
  47. package/dist/feedback/feedback-public-list.js +1 -0
  48. package/dist/feedback/feedback-public-list.js.map +1 -1
  49. package/dist/feedback/feedback-public-types.d.ts +16 -3
  50. package/dist/feedback/feedback-record.d.ts +20 -3
  51. package/dist/feedback/feedback-record.js +15 -0
  52. package/dist/feedback/feedback-record.js.map +1 -1
  53. package/dist/feedback/feedback-replies.d.ts +13 -2
  54. package/dist/feedback/feedback-replies.js +67 -33
  55. package/dist/feedback/feedback-replies.js.map +1 -1
  56. package/dist/feedback/feedback-rework-entry-chip.d.ts +9 -0
  57. package/dist/feedback/feedback-rework-entry-chip.js +13 -0
  58. package/dist/feedback/feedback-rework-entry-chip.js.map +1 -0
  59. package/dist/feedback/feedback-row-detail.d.ts +31 -8
  60. package/dist/feedback/feedback-row-detail.js +31 -7
  61. package/dist/feedback/feedback-row-detail.js.map +1 -1
  62. package/dist/feedback/feedback-table-labels.js +1 -1
  63. package/dist/feedback/feedback-table-labels.js.map +1 -1
  64. package/dist/feedback/feedback-table.d.ts +5 -2
  65. package/dist/feedback/feedback-table.js +2 -2
  66. package/dist/feedback/feedback-table.js.map +1 -1
  67. package/dist/feedback/feedback-thread.js +3 -1
  68. package/dist/feedback/feedback-thread.js.map +1 -1
  69. package/dist/feedback/feedback-vocabulary.d.ts +9 -6
  70. package/dist/feedback/feedback-vocabulary.js +16 -1
  71. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  72. package/dist/hooks/use-long-press.d.ts +30 -0
  73. package/dist/hooks/use-long-press.js +77 -0
  74. package/dist/hooks/use-long-press.js.map +1 -0
  75. package/dist/hooks/use-overlay-history.js +37 -3
  76. package/dist/hooks/use-overlay-history.js.map +1 -1
  77. package/dist/i18n/german.js +13 -5
  78. package/dist/i18n/german.js.map +1 -1
  79. package/dist/i18n/kit-labels.d.ts +3 -0
  80. package/dist/i18n/kit-labels.js.map +1 -1
  81. package/dist/i18n/locales/en.js +12 -5
  82. package/dist/i18n/locales/en.js.map +2 -2
  83. package/dist/i18n/locales/es.js +13 -5
  84. package/dist/i18n/locales/es.js.map +1 -1
  85. package/dist/i18n/locales/fr.js +13 -5
  86. package/dist/i18n/locales/fr.js.map +1 -1
  87. package/dist/i18n/locales/hu.js +13 -5
  88. package/dist/i18n/locales/hu.js.map +1 -1
  89. package/dist/i18n/locales/it.js +13 -5
  90. package/dist/i18n/locales/it.js.map +1 -1
  91. package/dist/i18n/locales/zh.js +13 -5
  92. package/dist/i18n/locales/zh.js.map +1 -1
  93. package/dist/i18n/shared-labels.js +1 -1
  94. package/dist/i18n/shared-labels.js.map +1 -1
  95. package/dist/shell/app-shell.js +9 -0
  96. package/dist/shell/app-shell.js.map +1 -1
  97. package/dist/shell/fill-height.d.ts +38 -0
  98. package/dist/shell/fill-height.js +12 -0
  99. package/dist/shell/fill-height.js.map +1 -0
  100. package/dist/shell.d.ts +2 -0
  101. package/dist/shell.js +3 -0
  102. package/dist/shell.js.map +1 -1
  103. package/package.json +1 -1
  104. package/src/billing/billing-overview.ts +10 -0
  105. package/src/components/autocomplete.tsx +5 -1
  106. package/src/components/combobox-core.tsx +20 -1
  107. package/src/components/combobox.tsx +9 -3
  108. package/src/components/data-table-labels.ts +7 -0
  109. package/src/components/data-table-mobile.tsx +193 -9
  110. package/src/components/data-table.tsx +141 -24
  111. package/src/components/inline-entity-combobox.tsx +12 -4
  112. package/src/components/picker-sheet.tsx +38 -1
  113. package/src/components/tabs.tsx +73 -3
  114. package/src/components/translation-review-parts.tsx +3 -2
  115. package/src/components/ui-labels.ts +2 -0
  116. package/src/feedback/feedback-comment-thread.tsx +39 -1
  117. package/src/feedback/feedback-labels.ts +3 -1
  118. package/src/feedback/feedback-menu-labels.ts +2 -0
  119. package/src/feedback/feedback-menu.tsx +48 -30
  120. package/src/feedback/feedback-public-labels.ts +4 -3
  121. package/src/feedback/feedback-public-list.tsx +4 -1
  122. package/src/feedback/feedback-public-types.ts +17 -3
  123. package/src/feedback/feedback-record.ts +34 -3
  124. package/src/feedback/feedback-replies.tsx +100 -31
  125. package/src/feedback/feedback-rework-entry-chip.tsx +18 -0
  126. package/src/feedback/feedback-row-detail.tsx +65 -12
  127. package/src/feedback/feedback-table-labels.ts +1 -1
  128. package/src/feedback/feedback-table.tsx +7 -4
  129. package/src/feedback/feedback-thread.tsx +5 -1
  130. package/src/feedback/feedback-vocabulary.ts +27 -6
  131. package/src/hooks/use-long-press.ts +126 -0
  132. package/src/hooks/use-overlay-history.ts +74 -3
  133. package/src/i18n/german.ts +10 -2
  134. package/src/i18n/kit-labels.tsx +3 -0
  135. package/src/i18n/locales/es.ts +10 -2
  136. package/src/i18n/locales/fr.ts +10 -2
  137. package/src/i18n/locales/hu.ts +10 -2
  138. package/src/i18n/locales/it.ts +10 -2
  139. package/src/i18n/locales/zh.ts +10 -2
  140. package/src/i18n/shared-labels.ts +1 -1
  141. package/src/shell/app-shell.tsx +9 -0
  142. package/src/shell/fill-height.tsx +45 -0
  143. package/src/shell.ts +3 -0
@@ -0,0 +1,126 @@
1
+ import { useCallback, useEffect, useRef, type PointerEvent as ReactPointerEvent } from "react";
2
+
3
+ // The press-and-hold that puts DataTable's phone cards into selection mode (0.40,
4
+ // keksdose live #421). Taken over from keksdose's register list
5
+ // (frontend/src/shared/hooks/use-long-press.ts, its feedback #153) so the kit's cards
6
+ // and kk's register answer the same gesture the same way: 500 ms, an 8 px dead zone,
7
+ // cancelled by a scroll, the trailing click swallowed.
8
+
9
+ const LONG_PRESS_MS = 500;
10
+ /** The same 8 px dead zone `useRowSwipe` locks its axis at: the press is dead by the
11
+ * time the swipe hook takes pointer capture on its (ancestor) layer. */
12
+ const SLOP_PX = 8;
13
+
14
+ const movedBeyondSlop = (from: { x: number; y: number }, x: number, y: number) =>
15
+ Math.hypot(x - from.x, y - from.y) > SLOP_PX;
16
+
17
+ export interface LongPressResult {
18
+ handlers: {
19
+ onPointerDown: (e: ReactPointerEvent) => void;
20
+ onPointerMove: (e: ReactPointerEvent) => void;
21
+ onContextMenu: (e: { preventDefault: () => void }) => void;
22
+ };
23
+ /** True exactly once after a long press fired: call it from an `onClickCapture` to
24
+ * swallow the click the browser still sends when the finger lifts. */
25
+ consumeClick: () => boolean;
26
+ }
27
+
28
+ /**
29
+ * A press-and-hold on a touch row that may ALSO be swipeable.
30
+ *
31
+ * - The cancel listeners live on `window`: `useRowSwipe` captures the pointer on an
32
+ * ancestor once a drag resolves to the x axis, after which the pressed element's
33
+ * own pointer handlers stop firing, and a timer cancelled only there would fire
34
+ * mid-swipe.
35
+ * - A scroll cancels too (`touch-action: pan-y` hands vertical panning to the
36
+ * browser, which sends `pointercancel`; the capture-phase `scroll` is the backstop).
37
+ * - `contextmenu` is suppressed while enabled — Android fires it at ~500 ms. Pair it
38
+ * with `select-none [-webkit-touch-callout:none]` on the element: iOS's callout
39
+ * only yields to that CSS.
40
+ */
41
+ export function useLongPress(
42
+ onLongPress: () => void,
43
+ { enabled = true, delay = LONG_PRESS_MS }: { enabled?: boolean; delay?: number } = {},
44
+ ): LongPressResult {
45
+ const timer = useRef<number | null>(null);
46
+ const start = useRef({ x: 0, y: 0 });
47
+ const fired = useRef(false);
48
+ const teardown = useRef<(() => void) | null>(null);
49
+ const cbRef = useRef(onLongPress);
50
+ useEffect(() => {
51
+ cbRef.current = onLongPress;
52
+ });
53
+
54
+ const cancel = useCallback(() => {
55
+ if (timer.current !== null) {
56
+ window.clearTimeout(timer.current);
57
+ timer.current = null;
58
+ }
59
+ teardown.current?.();
60
+ teardown.current = null;
61
+ }, []);
62
+
63
+ const onPointerDown = useCallback(
64
+ (e: ReactPointerEvent) => {
65
+ // Cleared above every early return: a press whose trailing click never arrives
66
+ // (the finger lifted over another row) would otherwise leave the latch armed,
67
+ // and it would eat the next real tap once the card has switched modes.
68
+ fired.current = false;
69
+ if (!enabled) return;
70
+ if (e.pointerType === "mouse" && e.button !== 0) return;
71
+ cancel();
72
+ start.current = { x: e.clientX, y: e.clientY };
73
+
74
+ const onEnd = () => cancel();
75
+ const onMove = (ev: PointerEvent) => {
76
+ if (movedBeyondSlop(start.current, ev.clientX, ev.clientY)) cancel();
77
+ };
78
+ window.addEventListener("pointerup", onEnd);
79
+ window.addEventListener("pointercancel", onEnd);
80
+ window.addEventListener("pointermove", onMove);
81
+ window.addEventListener("scroll", onEnd, { capture: true, passive: true });
82
+ teardown.current = () => {
83
+ window.removeEventListener("pointerup", onEnd);
84
+ window.removeEventListener("pointercancel", onEnd);
85
+ window.removeEventListener("pointermove", onMove);
86
+ window.removeEventListener("scroll", onEnd, { capture: true });
87
+ };
88
+
89
+ timer.current = window.setTimeout(() => {
90
+ cancel();
91
+ fired.current = true;
92
+ // The swipe stages' haptic vocabulary; absent on iOS Safari.
93
+ navigator.vibrate?.(20);
94
+ cbRef.current();
95
+ }, delay);
96
+ },
97
+ [cancel, delay, enabled],
98
+ );
99
+
100
+ // Also on the element: it still cancels if an ancestor stops the native event
101
+ // before it reaches `window`.
102
+ const onPointerMove = useCallback(
103
+ (e: ReactPointerEvent) => {
104
+ if (timer.current === null) return;
105
+ if (movedBeyondSlop(start.current, e.clientX, e.clientY)) cancel();
106
+ },
107
+ [cancel],
108
+ );
109
+
110
+ const onContextMenu = useCallback(
111
+ (e: { preventDefault: () => void }) => {
112
+ if (enabled) e.preventDefault();
113
+ },
114
+ [enabled],
115
+ );
116
+
117
+ const consumeClick = useCallback(() => {
118
+ if (!fired.current) return false;
119
+ fired.current = false;
120
+ return true;
121
+ }, []);
122
+
123
+ useEffect(() => cancel, [cancel]);
124
+
125
+ return { handlers: { onPointerDown, onPointerMove, onContextMenu }, consumeClick };
126
+ }
@@ -48,6 +48,16 @@ import { useEffect, useRef } from "react";
48
48
  * {@link noteAddress}: an address the overlay LIVED THROUGH (a filter the table wrote
49
49
  * while its sheet was up) is still unwound, because the user has acted since.
50
50
  *
51
+ * ## Not undoing the owner's replaces either
52
+ *
53
+ * An address the overlay lived through is unwound as an ENTRY, never as an address
54
+ * (keksdose live #412). A report that writes each tick of its payee sheet into the query
55
+ * with `setSearchParams(…, { replace: true })` rewrites the overlay's own entry, and the
56
+ * page entry beneath still carries the address from before the sheet opened — so the
57
+ * traversal that took our entry away also took every tick with it. See {@link carryOnto}:
58
+ * whichever way the entry goes (the unwind after a press, or a Back press), the address it
59
+ * last showed is carried onto the entry it lands on, and the router is told.
60
+ *
51
61
  * ## The traversal is deferred
52
62
  *
53
63
  * And it is *scheduled* rather than issued, because `history.go()` lands a task
@@ -99,6 +109,10 @@ interface PushedEntry {
99
109
  key: string | undefined;
100
110
  /** {@link routeOf} the address the marker was placed at. */
101
111
  route: string;
112
+ /** The entry's address as last OBSERVED, after the user's last gesture had run its
113
+ * handlers — what a Back press must carry down (see {@link carryOnto}). `href` is
114
+ * the snapshot BEFORE the gesture, which the owner's-close check needs instead. */
115
+ seen: string;
102
116
  }
103
117
  const pushed: PushedEntry[] = [];
104
118
 
@@ -197,10 +211,41 @@ function runUnwind(): void {
197
211
  if (taggedSentinel() === null) buryUnder(job.entries);
198
212
  return;
199
213
  }
214
+ const bottom = job.entries[0];
215
+ carry = { href: job.href, idx: bottom.idx, key: bottom.key };
200
216
  pendingProgrammatic += 1;
201
217
  window.history.go(-job.entries.length);
202
218
  }
203
219
 
220
+ /** The address an unwind in flight must carry onto the entry it lands on. */
221
+ let carry: { href: string; idx: number | undefined; key: string | undefined } | null = null;
222
+
223
+ /**
224
+ * Put `href` — the address the overlay's entry last showed — onto the entry a traversal
225
+ * has just landed on, and tell the router (keksdose live #412).
226
+ *
227
+ * Only the query and the hash can differ: an address naming another page is a
228
+ * navigation, never carried. And only onto the page entry the overlay was opened over,
229
+ * which our push copied the router's `{idx, key}` from — a traversal that came to rest
230
+ * anywhere else is not ours to rewrite.
231
+ *
232
+ * The router heard the traversal land on the OLD address in this same event, so a
233
+ * `replaceState` alone would leave it showing the old query under the new one. A
234
+ * synthetic `popstate` makes it read the address again; both reads happen in one event
235
+ * dispatch, so React commits only the second. Ours absorbs it as programmatic.
236
+ */
237
+ function carryOnto(href: string, mark: { idx: number | undefined; key: string | undefined }): void {
238
+ const here = currentHref();
239
+ if (here === href || routeOf(here) !== routeOf(href)) return;
240
+ const now = routerMark();
241
+ if (mark.idx !== undefined && now.idx !== undefined && mark.idx !== now.idx) return;
242
+ if (mark.key !== undefined && now.key !== undefined && mark.key !== now.key) return;
243
+ const state: unknown = window.history.state;
244
+ window.history.replaceState(state, "", href);
245
+ pendingProgrammatic += 1;
246
+ window.dispatchEvent(new PopStateEvent("popstate", { state }));
247
+ }
248
+
204
249
  function currentHref(): string {
205
250
  return typeof window === "undefined" ? "" : window.location.href;
206
251
  }
@@ -416,7 +461,25 @@ function noteAddress(): void {
416
461
  if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;
417
462
  const tag = taggedSentinel();
418
463
  // Read without `currentSentinel`'s repair: an observation must not write history.
419
- if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) top.href = currentHref();
464
+ if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) {
465
+ top.href = currentHref();
466
+ top.seen = top.href;
467
+ }
468
+ }
469
+
470
+ /** After the user's gesture has run its handlers: the address its entry now shows —
471
+ * what a Back press must carry down (keksdose live #412). Again a little later, for a
472
+ * handler that writes the address a task after the event. */
473
+ function noteSeen(): void {
474
+ const top = pushed[pushed.length - 1];
475
+ if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;
476
+ const tag = taggedSentinel();
477
+ if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) top.seen = currentHref();
478
+ }
479
+
480
+ function noteSeenNowAndLater(): void {
481
+ noteSeen();
482
+ setTimeout(noteSeen, 0);
420
483
  }
421
484
 
422
485
  function handlePop() {
@@ -428,6 +491,9 @@ function handlePop() {
428
491
  lastSeen = routerMark();
429
492
  if (pendingProgrammatic > 0) {
430
493
  pendingProgrammatic -= 1;
494
+ const job = carry;
495
+ carry = null;
496
+ if (job) carryOnto(job.href, job);
431
497
  return;
432
498
  }
433
499
  closeOnPop();
@@ -451,7 +517,10 @@ function closeOnPop() {
451
517
  // The browser consumed this entry, so the overlay's own cleanup has nothing left
452
518
  // to unwind and nothing to record as owed.
453
519
  consumed.add(top.id);
520
+ const record = pushed.find((p) => p.id === top.id);
454
521
  forgetPushed(top.id);
522
+ // What the page wrote into the address while we were up stays (#412).
523
+ if (record && !record.dead) carryOnto(record.seen, record);
455
524
  top.close();
456
525
  }
457
526
 
@@ -460,6 +529,7 @@ function ensureListening() {
460
529
  window.addEventListener("popstate", handlePop);
461
530
  window.addEventListener("pointerdown", noteAddress, true);
462
531
  window.addEventListener("keydown", noteAddress, true);
532
+ for (const type of ["click", "keyup", "change", "input"]) window.addEventListener(type, noteSeenNowAndLater);
463
533
  listening = true;
464
534
  }
465
535
 
@@ -501,7 +571,7 @@ export function useOverlayHistory(open: boolean, onClose: () => void): void {
501
571
  // The abandoned ones beneath come back owed; ours takes the top slot.
502
572
  for (const owed of inherited.slice(0, -1)) pushed.push({ ...owed, dead: true });
503
573
  const was = inherited[inherited.length - 1];
504
- pushed.push({ id, href: currentHref(), dead: false, idx: was.idx, key: was.key, route: was.route });
574
+ pushed.push({ id, href: currentHref(), seen: currentHref(), dead: false, idx: was.idx, key: was.key, route: was.route });
505
575
  standing = id;
506
576
  } else {
507
577
  // A push DESTROYS every entry ahead of the one we are on, so anything we
@@ -516,7 +586,8 @@ export function useOverlayHistory(open: boolean, onClose: () => void): void {
516
586
  // the URL is unchanged, so the router treats popping this as a no-op re-render.
517
587
  window.history.pushState(marked, "");
518
588
  const page = routerMark();
519
- pushed.push({ id, href: currentHref(), dead: false, idx: page.idx, key: page.key, route: routeOf(currentHref()) });
589
+ const at = currentHref();
590
+ pushed.push({ id, href: at, seen: at, dead: false, idx: page.idx, key: page.key, route: routeOf(at) });
520
591
  standing = id;
521
592
  }
522
593
  return () => {
@@ -85,6 +85,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
85
85
  feedbackStatus: {
86
86
  OPEN: "Offen",
87
87
  READY: "Bereit zur Umsetzung",
88
+ REWORK: "Nacharbeit angefordert",
88
89
  IN_PROGRESS: "In Bearbeitung",
89
90
  IN_EVALUATION: "Zur Prüfung",
90
91
  NEEDS_LIVE_TEST: "Live testen",
@@ -122,11 +123,13 @@ export function germanLabels(numberLocale: string): UiKitLabels {
122
123
  myFeedback: "Mein Feedback",
123
124
  viewFeedback: "Feedback ansehen",
124
125
  allFeedback: "Alle Rückmeldungen",
126
+ discussion: "Feedback-Diskussion",
127
+ inbox: "Feedback-Eingang",
125
128
  },
126
129
  // 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
127
130
  // notification types. "Rückmeldungen" and "Eine Person" are the contract's (§9.2, §9.5).
128
131
  feedbackPublic: {
129
- title: "Alle Rückmeldungen",
132
+ title: "Feedback-Diskussion",
130
133
  sort: "Sortieren",
131
134
  sortNewest: "Neueste",
132
135
  sortVotes: "Meistgewünscht",
@@ -158,7 +161,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
158
161
  attachUrl: "Aktuelle Seiten-URL anhängen",
159
162
  },
160
163
  feedbackPage: {
161
- title: "Feedback",
164
+ title: "Feedback-Eingang",
162
165
  myTitle: "Mein Feedback",
163
166
  columnId: "#",
164
167
  columnDate: "Datum",
@@ -344,6 +347,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
344
347
  filter: "Filter",
345
348
  close: "Schließen",
346
349
  selectRow: "Zeile auswählen",
350
+ selectRows: "Zeilen auswählen",
351
+ cancelSelection: "Abbrechen",
347
352
  rowDetails: "Details",
348
353
  autoSize: "Spaltenbreiten anpassen",
349
354
  loading: "Wird geladen…",
@@ -534,6 +539,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
534
539
  // "Tab" is what German UIs say; "Registerkarte" reads like a 1990s manual.
535
540
  add: "Tab hinzufügen",
536
541
  remove: (tab) => `${tab} entfernen`,
542
+ more: "Weitere Tabs",
543
+ backToFirst: "Zurück zum ersten Tab",
537
544
  },
538
545
  appShell: {
539
546
  collapse: "Seitenleiste einklappen",
@@ -549,6 +556,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
549
556
  },
550
557
  pickerSheet: {
551
558
  close: "Schließen",
559
+ done: "Fertig",
552
560
  },
553
561
  dialogFrame: {
554
562
  close: "Schließen",
@@ -279,6 +279,9 @@ export interface TopBarLabels {
279
279
 
280
280
  export interface PickerSheetLabels {
281
281
  close: string;
282
+ /** The sticky footer button that closes a multi-select sheet once the rows are ticked
283
+ * (0.40) — the ticks are already applied, so it confirms rather than saves. */
284
+ done: string;
282
285
  }
283
286
 
284
287
  export interface SwipeableRowLabels {
@@ -76,6 +76,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
76
76
  feedbackStatus: {
77
77
  OPEN: "Abierto",
78
78
  READY: "Listo para implementar",
79
+ REWORK: "Corrección solicitada",
79
80
  IN_PROGRESS: "En curso",
80
81
  IN_EVALUATION: "En evaluación",
81
82
  NEEDS_LIVE_TEST: "Probar en producción",
@@ -113,11 +114,13 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
113
114
  myFeedback: "Mis comentarios",
114
115
  viewFeedback: "Ver comentarios",
115
116
  allFeedback: "Todos los comentarios",
117
+ discussion: "Debate de comentarios",
118
+ inbox: "Comentarios recibidos",
116
119
  },
117
120
  // 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
118
121
  // notification types. "Informe" for one report, so it never reads as "comentario".
119
122
  feedbackPublic: {
120
- title: "Todos los comentarios",
123
+ title: "Debate de comentarios",
121
124
  sort: "Ordenar",
122
125
  sortNewest: "Más recientes",
123
126
  sortVotes: "Más deseados",
@@ -149,7 +152,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
149
152
  attachUrl: "Adjuntar la URL de la página actual",
150
153
  },
151
154
  feedbackPage: {
152
- title: "Comentarios",
155
+ title: "Comentarios recibidos",
153
156
  myTitle: "Mis comentarios",
154
157
  columnId: "#",
155
158
  columnDate: "Fecha",
@@ -335,6 +338,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
335
338
  filter: "Filtrar",
336
339
  close: "Cerrar",
337
340
  selectRow: "Seleccionar fila",
341
+ selectRows: "Seleccionar filas",
342
+ cancelSelection: "Cancelar",
338
343
  rowDetails: "Detalles",
339
344
  autoSize: "Ajustar ancho de columnas",
340
345
  loading: "Cargando…",
@@ -521,6 +526,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
521
526
  tabs: {
522
527
  add: "Añadir pestaña",
523
528
  remove: (tab) => `Quitar ${tab}`,
529
+ more: "Más pestañas",
530
+ backToFirst: "Volver a la primera pestaña",
524
531
  },
525
532
  appShell: {
526
533
  collapse: "Contraer barra lateral",
@@ -536,6 +543,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
536
543
  },
537
544
  pickerSheet: {
538
545
  close: "Cerrar",
546
+ done: "Listo",
539
547
  },
540
548
  dialogFrame: {
541
549
  close: "Cerrar",
@@ -76,6 +76,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
76
76
  feedbackStatus: {
77
77
  OPEN: "Ouvert",
78
78
  READY: "Prêt à implémenter",
79
+ REWORK: "Reprise demandée",
79
80
  IN_PROGRESS: "En cours",
80
81
  IN_EVALUATION: "En évaluation",
81
82
  NEEDS_LIVE_TEST: "À tester en production",
@@ -111,11 +112,13 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
111
112
  myFeedback: "Mes retours",
112
113
  viewFeedback: "Voir les retours",
113
114
  allFeedback: "Tous les retours",
115
+ discussion: "Discussion des retours",
116
+ inbox: "Retours reçus",
114
117
  },
115
118
  // 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
116
119
  // notification types. "Retour" for a report, as `feedbackMenu`.
117
120
  feedbackPublic: {
118
- title: "Tous les retours",
121
+ title: "Discussion des retours",
119
122
  sort: "Trier",
120
123
  sortNewest: "Plus récents",
121
124
  sortVotes: "Plus demandés",
@@ -147,7 +150,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
147
150
  attachUrl: "Joindre l’URL de la page actuelle",
148
151
  },
149
152
  feedbackPage: {
150
- title: "Retours",
153
+ title: "Retours reçus",
151
154
  myTitle: "Mes retours",
152
155
  columnId: "#",
153
156
  columnDate: "Date",
@@ -341,6 +344,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
341
344
  filter: "Filtrer",
342
345
  close: "Fermer",
343
346
  selectRow: "Sélectionner la ligne",
347
+ selectRows: "Sélectionner des lignes",
348
+ cancelSelection: "Annuler",
344
349
  rowDetails: "Détails",
345
350
  autoSize: "Ajuster la largeur des colonnes",
346
351
  loading: "Chargement…",
@@ -529,6 +534,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
529
534
  tabs: {
530
535
  add: "Ajouter un onglet",
531
536
  remove: (tab) => `Supprimer ${tab}`,
537
+ more: "Autres onglets",
538
+ backToFirst: "Retour au premier onglet",
532
539
  },
533
540
  appShell: {
534
541
  collapse: "Réduire la barre latérale",
@@ -544,6 +551,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
544
551
  },
545
552
  pickerSheet: {
546
553
  close: "Fermer",
554
+ done: "Terminé",
547
555
  },
548
556
  dialogFrame: {
549
557
  close: "Fermer",
@@ -76,6 +76,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
76
76
  feedbackStatus: {
77
77
  OPEN: "Nyitott",
78
78
  READY: "Megvalósításra kész",
79
+ REWORK: "Átdolgozás kérve",
79
80
  IN_PROGRESS: "Folyamatban",
80
81
  IN_EVALUATION: "Értékelés alatt",
81
82
  NEEDS_LIVE_TEST: "Élesben tesztelendő",
@@ -110,11 +111,13 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
110
111
  myFeedback: "Visszajelzéseim",
111
112
  viewFeedback: "Visszajelzések megtekintése",
112
113
  allFeedback: "Összes visszajelzés",
114
+ discussion: "Visszajelzések megvitatása",
115
+ inbox: "Beérkezett visszajelzések",
113
116
  },
114
117
  // 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
115
118
  // notification types. A noun after a number stays singular in Hungarian.
116
119
  feedbackPublic: {
117
- title: "Összes visszajelzés",
120
+ title: "Visszajelzések megvitatása",
118
121
  sort: "Rendezés",
119
122
  sortNewest: "Legújabb",
120
123
  sortVotes: "Legkeresettebb",
@@ -146,7 +149,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
146
149
  attachUrl: "Az aktuális oldal URL-jének csatolása",
147
150
  },
148
151
  feedbackPage: {
149
- title: "Visszajelzések",
152
+ title: "Beérkezett visszajelzések",
150
153
  myTitle: "Visszajelzéseim",
151
154
  columnId: "#",
152
155
  columnDate: "Dátum",
@@ -333,6 +336,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
333
336
  filter: "Szűrés",
334
337
  close: "Bezárás",
335
338
  selectRow: "Sor kijelölése",
339
+ selectRows: "Sorok kijelölése",
340
+ cancelSelection: "Mégse",
336
341
  rowDetails: "Részletek",
337
342
  autoSize: "Oszlopszélességek igazítása",
338
343
  loading: "Betöltés…",
@@ -519,6 +524,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
519
524
  tabs: {
520
525
  add: "Lap hozzáadása",
521
526
  remove: (tab) => `${tab} eltávolítása`,
527
+ more: "További lapok",
528
+ backToFirst: "Vissza az első lapra",
522
529
  },
523
530
  appShell: {
524
531
  collapse: "Oldalsáv összecsukása",
@@ -534,6 +541,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
534
541
  },
535
542
  pickerSheet: {
536
543
  close: "Bezárás",
544
+ done: "Kész",
537
545
  },
538
546
  dialogFrame: {
539
547
  close: "Bezárás",
@@ -76,6 +76,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
76
76
  feedbackStatus: {
77
77
  OPEN: "Aperto",
78
78
  READY: "Pronto da implementare",
79
+ REWORK: "Rielaborazione richiesta",
79
80
  IN_PROGRESS: "In corso",
80
81
  IN_EVALUATION: "In valutazione",
81
82
  NEEDS_LIVE_TEST: "Da testare in produzione",
@@ -113,11 +114,13 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
113
114
  myFeedback: "I miei feedback",
114
115
  viewFeedback: "Visualizza feedback",
115
116
  allFeedback: "Tutti i feedback",
117
+ discussion: "Discussione sui feedback",
118
+ inbox: "Feedback ricevuti",
116
119
  },
117
120
  // 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
118
121
  // notification types. "Segnalazione" for one report; formal "Lei", as elsewhere.
119
122
  feedbackPublic: {
120
- title: "Tutti i feedback",
123
+ title: "Discussione sui feedback",
121
124
  sort: "Ordina",
122
125
  sortNewest: "Più recenti",
123
126
  sortVotes: "Più richiesti",
@@ -149,7 +152,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
149
152
  attachUrl: "Allega l’URL della pagina corrente",
150
153
  },
151
154
  feedbackPage: {
152
- title: "Feedback",
155
+ title: "Feedback ricevuti",
153
156
  myTitle: "I miei feedback",
154
157
  columnId: "#",
155
158
  columnDate: "Data",
@@ -337,6 +340,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
337
340
  filter: "Filtra",
338
341
  close: "Chiudi",
339
342
  selectRow: "Seleziona riga",
343
+ selectRows: "Seleziona righe",
344
+ cancelSelection: "Annulla",
340
345
  rowDetails: "Dettagli",
341
346
  autoSize: "Adatta larghezza colonne",
342
347
  loading: "Caricamento…",
@@ -524,6 +529,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
524
529
  tabs: {
525
530
  add: "Aggiungi scheda",
526
531
  remove: (tab) => `Rimuovi ${tab}`,
532
+ more: "Altre schede",
533
+ backToFirst: "Torna alla prima scheda",
527
534
  },
528
535
  appShell: {
529
536
  collapse: "Comprimi barra laterale",
@@ -539,6 +546,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
539
546
  },
540
547
  pickerSheet: {
541
548
  close: "Chiudi",
549
+ done: "Fine",
542
550
  },
543
551
  dialogFrame: {
544
552
  close: "Chiudi",
@@ -69,6 +69,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
69
69
  feedbackStatus: {
70
70
  OPEN: "待处理",
71
71
  READY: "待实现",
72
+ REWORK: "已要求返工",
72
73
  IN_PROGRESS: "处理中",
73
74
  IN_EVALUATION: "评估中",
74
75
  NEEDS_LIVE_TEST: "待线上测试",
@@ -103,11 +104,13 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
103
104
  myFeedback: "我的反馈",
104
105
  viewFeedback: "查看反馈",
105
106
  allFeedback: "全部反馈",
107
+ discussion: "反馈讨论",
108
+ inbox: "反馈收件箱",
106
109
  },
107
110
  // 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
108
111
  // notification types.
109
112
  feedbackPublic: {
110
- title: "全部反馈",
113
+ title: "反馈讨论",
111
114
  sort: "排序",
112
115
  sortNewest: "最新",
113
116
  sortVotes: "最多人想要",
@@ -139,7 +142,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
139
142
  attachUrl: "附加当前页面 URL",
140
143
  },
141
144
  feedbackPage: {
142
- title: "反馈",
145
+ title: "反馈收件箱",
143
146
  myTitle: "我的反馈",
144
147
  columnId: "#",
145
148
  columnDate: "日期",
@@ -325,6 +328,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
325
328
  filter: "筛选",
326
329
  close: "关闭",
327
330
  selectRow: "选择行",
331
+ selectRows: "选择多行",
332
+ cancelSelection: "取消",
328
333
  rowDetails: "详情",
329
334
  autoSize: "自动调整列宽",
330
335
  loading: "加载中…",
@@ -507,6 +512,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
507
512
  add: "添加标签页",
508
513
  // Quoted, so the spacing rule holds whether the tab's name is Han or Latin.
509
514
  remove: (tab) => `移除“${tab}”`,
515
+ more: "更多标签页",
516
+ backToFirst: "返回第一个标签页",
510
517
  },
511
518
  appShell: {
512
519
  collapse: "收起侧边栏",
@@ -522,6 +529,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
522
529
  },
523
530
  pickerSheet: {
524
531
  close: "关闭",
532
+ done: "完成",
525
533
  },
526
534
  dialogFrame: {
527
535
  close: "关闭",
@@ -108,7 +108,7 @@ export const DEFAULT_TOP_BAR_LABELS: TopBarLabels = {
108
108
  role: (value) => `Role: ${value}`,
109
109
  };
110
110
 
111
- export const DEFAULT_PICKER_SHEET_LABELS: PickerSheetLabels = { close: "Close" };
111
+ export const DEFAULT_PICKER_SHEET_LABELS: PickerSheetLabels = { close: "Close", done: "Done" };
112
112
 
113
113
  export const DEFAULT_SWIPEABLE_ROW_LABELS: SwipeableRowLabels = { actions: "Row actions" };
114
114
 
@@ -537,6 +537,15 @@ export function AppShell({
537
537
  <main
538
538
  className={cn(
539
539
  "relative flex-1 max-w-full overflow-x-clip pb-[calc(var(--app-nav-h,4rem)+1.5rem)] md:pb-0 md:min-h-0 md:overflow-y-auto md:[scrollbar-gutter:stable_both-edges]",
540
+ // The list page's fill (0.40, docs/page-layout-harmonization.md §1.3): while
541
+ // a `fillHeight` DataTable is inside, a flex column — a banner above the
542
+ // page keeps its own height and the page (a `FillHeight`) takes the rest,
543
+ // so `<main>` no longer overflows by the banner. Its children do not shrink
544
+ // (a banner of a fixed height or `overflow-hidden` would): the page grows
545
+ // into the rest from a zero basis, and a window too short for the page's
546
+ // floor scrolls `<main>`. Every other page lays out as before. Below `md`
547
+ // the document scrolls and nothing fills.
548
+ "md:has-[[data-fill-height]]:flex md:has-[[data-fill-height]]:flex-col md:has-[[data-fill-height]]:*:shrink-0",
540
549
  // Embedded there is no document scroll to lean on below `md` either.
541
550
  embedded && "min-h-0 overflow-y-auto",
542
551
  )}