@eifi1/ui-kit 0.45.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -14
- package/dist/admin/admin-area-labels.d.ts +9 -0
- package/dist/admin/admin-area-labels.js +5 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.d.ts +16 -1
- package/dist/admin/admin-overview.js +12 -1
- package/dist/admin/admin-overview.js.map +1 -1
- package/dist/admin/mail-test-card.d.ts +3 -2
- package/dist/admin/mail-test-card.js +3 -1
- package/dist/admin/mail-test-card.js.map +1 -1
- package/dist/admin/user-roster.d.ts +6 -2
- package/dist/admin/user-roster.js +7 -3
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/components/appearance-labels.d.ts +10 -0
- package/dist/components/appearance-labels.js +6 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-head.js +2 -2
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +35 -2
- package/dist/components/data-table-row-actions.js +98 -5
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-shared.js +9 -2
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +58 -2
- package/dist/components/data-table.js +11 -4
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-format-setting.d.ts +29 -0
- package/dist/components/date-format-setting.js +36 -0
- package/dist/components/date-format-setting.js.map +1 -0
- package/dist/components/row-actions.d.ts +12 -5
- package/dist/components/row-actions.js +11 -4
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +8 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tabs.d.ts +3 -2
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -0
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-labels.d.ts +5 -0
- package/dist/components/translation-review-labels.js +2 -0
- package/dist/components/translation-review-labels.js.map +1 -1
- package/dist/components/translation-review.d.ts +18 -8
- package/dist/components/translation-review.js +67 -8
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/write-lock-labels.js +2 -1
- package/dist/components/write-lock-labels.js.map +1 -1
- package/dist/components/write-lock.d.ts +33 -3
- package/dist/components/write-lock.js +9 -0
- package/dist/components/write-lock.js.map +1 -1
- package/dist/data-table.d.ts +2 -1
- package/dist/data-table.js +2 -0
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +4 -2
- package/dist/feedback/feedback-row-detail.js +17 -26
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +18 -0
- package/dist/feedback/feedback-swipe.js +10 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/i18n/german.js +41 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +12 -1
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +40 -6
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +41 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +41 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +41 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +41 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +41 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +3 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +38 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/date-format.d.ts +81 -0
- package/dist/lib/date-format.js +56 -0
- package/dist/lib/date-format.js.map +1 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +5 -4
- package/dist/lib/format.js.map +1 -1
- package/dist/notifications/device-zone-report.d.ts +34 -0
- package/dist/notifications/device-zone-report.js +31 -0
- package/dist/notifications/device-zone-report.js.map +1 -0
- package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
- package/dist/notifications/feedback-notification-types-labels.js +4 -1
- package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
- package/dist/notifications/feedback-notification-types.d.ts +24 -5
- package/dist/notifications/feedback-notification-types.js +7 -2
- package/dist/notifications/feedback-notification-types.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +33 -0
- package/dist/notifications/notification-bell.js +74 -0
- package/dist/notifications/notification-bell.js.map +1 -0
- package/dist/notifications/notification-inbox.d.ts +86 -0
- package/dist/notifications/notification-inbox.js +206 -0
- package/dist/notifications/notification-inbox.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +6 -4
- package/dist/notifications/notification-types-setting.js +103 -45
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +19 -7
- package/dist/notifications/notifications-labels.d.ts +44 -8
- package/dist/notifications/notifications-labels.js +18 -1
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +20 -3
- package/dist/notifications/push-devices-setting.js +93 -33
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/push-worker.d.ts +108 -0
- package/dist/notifications/push-worker.js +62 -0
- package/dist/notifications/push-worker.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +29 -17
- package/dist/notifications/quiet-hours-setting.js +9 -41
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/time-zone-select.d.ts +8 -4
- package/dist/notifications/time-zone-select.js +2 -7
- package/dist/notifications/time-zone-select.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +4 -17
- package/dist/notifications/use-push-device.js +7 -15
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/local-first.d.ts +42 -0
- package/dist/offline/local-first.js +53 -0
- package/dist/offline/local-first.js.map +1 -0
- package/dist/offline/query-persistence.d.ts +26 -1
- package/dist/offline/query-persistence.js +27 -23
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/offline/service-worker-updates.d.ts +46 -0
- package/dist/offline/service-worker-updates.js +39 -0
- package/dist/offline/service-worker-updates.js.map +1 -0
- package/dist/offline.d.ts +5 -2
- package/dist/offline.js +12 -2
- package/dist/offline.js.map +1 -1
- package/dist/push-worker.d.ts +2 -0
- package/dist/push-worker.js +14 -0
- package/dist/push-worker.js.map +1 -0
- package/dist/shell/account-menu-text-size.d.ts +33 -0
- package/dist/shell/account-menu-text-size.js +51 -0
- package/dist/shell/account-menu-text-size.js.map +1 -0
- package/dist/shell/top-bar-actions.d.ts +6 -2
- package/dist/shell/top-bar-actions.js +1 -0
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/db.d.ts +93 -0
- package/dist/sync/db.js +33 -0
- package/dist/sync/db.js.map +1 -0
- package/dist/sync/engine.d.ts +126 -0
- package/dist/sync/engine.js +474 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/hooks.d.ts +35 -0
- package/dist/sync/hooks.js +67 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/testing.d.ts +28 -0
- package/dist/sync/testing.js +41 -0
- package/dist/sync/testing.js.map +1 -0
- package/dist/sync/wire.d.ts +50 -0
- package/dist/sync/wire.js +2 -0
- package/dist/sync/wire.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +25 -0
- package/dist/sync.js.map +1 -0
- package/dist/tour/tour-labels.js +1 -0
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +30 -0
- package/dist/tour/tour.js +50 -6
- package/dist/tour/tour.js.map +1 -1
- package/package.json +19 -1
- package/src/admin/admin-area-labels.ts +13 -0
- package/src/admin/admin-overview.tsx +37 -2
- package/src/admin/mail-test-card.tsx +7 -3
- package/src/admin/user-roster.tsx +12 -3
- package/src/components/appearance-labels.ts +16 -0
- package/src/components/data-table-column-settings.tsx +2 -2
- package/src/components/data-table-head.tsx +2 -2
- package/src/components/data-table-row-actions.tsx +145 -5
- package/src/components/data-table-shared.tsx +21 -2
- package/src/components/data-table.tsx +68 -6
- package/src/components/date-format-setting.tsx +64 -0
- package/src/components/row-actions.tsx +33 -9
- package/src/components/swipeable-row.tsx +8 -0
- package/src/components/tabs.tsx +4 -3
- package/src/components/translation-review-editor.tsx +2 -0
- package/src/components/translation-review-labels.ts +7 -0
- package/src/components/translation-review.tsx +105 -15
- package/src/components/write-lock-labels.ts +1 -0
- package/src/components/write-lock.tsx +36 -3
- package/src/data-table.ts +4 -0
- package/src/feedback/feedback-row-detail.tsx +18 -23
- package/src/feedback/feedback-swipe.tsx +28 -1
- package/src/i18n/german.ts +44 -4
- package/src/i18n/kit-labels.tsx +18 -1
- package/src/i18n/locales/en.ts +1 -0
- package/src/i18n/locales/es.ts +44 -4
- package/src/i18n/locales/fr.ts +44 -4
- package/src/i18n/locales/hu.ts +44 -4
- package/src/i18n/locales/it.ts +44 -4
- package/src/i18n/locales/zh.ts +44 -2
- package/src/i18n/review.ts +3 -1
- package/src/index.ts +50 -1
- package/src/lib/date-format.ts +151 -0
- package/src/lib/format.ts +10 -4
- package/src/notifications/device-zone-report.ts +67 -0
- package/src/notifications/feedback-notification-types-labels.ts +10 -1
- package/src/notifications/feedback-notification-types.ts +30 -5
- package/src/notifications/notification-bell.tsx +116 -0
- package/src/notifications/notification-inbox.tsx +314 -0
- package/src/notifications/notification-types-setting.tsx +151 -55
- package/src/notifications/notification-types.ts +20 -8
- package/src/notifications/notifications-labels.ts +72 -12
- package/src/notifications/push-devices-setting.tsx +123 -34
- package/src/notifications/push-worker.ts +160 -0
- package/src/notifications/quiet-hours-setting.tsx +41 -58
- package/src/notifications/time-zone-select.tsx +9 -10
- package/src/notifications/use-push-device.ts +20 -33
- package/src/offline/local-first.ts +100 -0
- package/src/offline/query-persistence.ts +57 -24
- package/src/offline/service-worker-updates.ts +81 -0
- package/src/offline.ts +7 -1
- package/src/push-worker.ts +25 -0
- package/src/shell/account-menu-text-size.tsx +77 -0
- package/src/shell/top-bar-actions.tsx +6 -1
- package/src/shell.ts +3 -0
- package/src/sync/db.ts +125 -0
- package/src/sync/engine.ts +701 -0
- package/src/sync/hooks.ts +98 -0
- package/src/sync/testing.ts +73 -0
- package/src/sync/wire.ts +61 -0
- package/src/sync.ts +29 -0
- package/src/tour/tour-labels.ts +1 -0
- package/src/tour/tour.tsx +100 -7
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { isValidElement, useCallback, useMemo } from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { Pencil, Trash2 } from "lucide-react";
|
|
4
|
-
import { RowActions, type RowAction } from "./row-actions";
|
|
4
|
+
import { RowActions, type RowAction, type RowActionTone } from "./row-actions";
|
|
5
5
|
import type { SwipeAction } from "./swipeable-row";
|
|
6
6
|
import type { DataTableLabels } from "./data-table-labels";
|
|
7
7
|
import type {
|
|
8
8
|
DataTableColumn,
|
|
9
9
|
DataTableDensity,
|
|
10
|
+
DataTableDesktopSwipeActions,
|
|
10
11
|
DataTableProps,
|
|
11
12
|
DataTableRowAction,
|
|
12
13
|
MobileSwipeActions,
|
|
@@ -20,6 +21,85 @@ import type {
|
|
|
20
21
|
* column-settings panel. */
|
|
21
22
|
export const ROW_ACTIONS_KEY = "__rowActions";
|
|
22
23
|
|
|
24
|
+
/** The key of the column derived from `mobileSwipeActions` (0.46, dev#598) — the
|
|
25
|
+
* table's own too. */
|
|
26
|
+
export const SWIPE_ACTIONS_KEY = "__swipeActions";
|
|
27
|
+
|
|
28
|
+
/** A column the TABLE adds, not the caller: out of the column settings and the count,
|
|
29
|
+
* never resized, never the totals row's header. */
|
|
30
|
+
export function isTableOwnColumn(key: string): boolean {
|
|
31
|
+
return key === ROW_ACTIONS_KEY || key === SWIPE_ACTIONS_KEY;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* An actions column's heading (0.46.1, keksdose dev#598 rework — Marcel: a VISIBLE
|
|
36
|
+
* "Actions" heading on every table with an actions column): the caller's node, else the
|
|
37
|
+
* word on screen; `false` is the pre-0.46.1 heading, a screen reader's name only.
|
|
38
|
+
*/
|
|
39
|
+
export function actionsHeading(header: ReactNode | false | undefined, word: string): ReactNode {
|
|
40
|
+
if (header === false) return <span className="sr-only">{word}</span>;
|
|
41
|
+
return header ?? word;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A curtain's tone, as an icon button's (keksdose's `toneOf`): only the tones that MEAN
|
|
45
|
+
* something on a quiet icon carry over; a status colour, a hue or an app's `paint` stays
|
|
46
|
+
* on the curtain and reads muted. */
|
|
47
|
+
function swipeTone(action: SwipeAction): RowActionTone {
|
|
48
|
+
switch (action.tone) {
|
|
49
|
+
case "danger":
|
|
50
|
+
case "warning":
|
|
51
|
+
case "info":
|
|
52
|
+
return action.tone;
|
|
53
|
+
default:
|
|
54
|
+
return "muted";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A row's swipe plan as desktop row actions (dev#598) — what DataTable's
|
|
60
|
+
* `desktopSwipeActions` column draws, for an app that builds its own actions column (or
|
|
61
|
+
* a `RowActions` outside a table) from the same `mobileSwipeActions` plan, so the two
|
|
62
|
+
* cannot drift (public since 0.46.1, keksdose N3).
|
|
63
|
+
*
|
|
64
|
+
* `end` then `start` — the physical `right` / `left` only where the logical side is
|
|
65
|
+
* missing, as the card reads them — each action once, told apart by its `id`, else by its
|
|
66
|
+
* label (two stages with one label would be one button twice). Each button runs the
|
|
67
|
+
* swipe's own `onCommit`; a swipe's `danger` / `warning` / `info` tone carries over, any
|
|
68
|
+
* other reads `muted`. `options.omit` leaves an action out, `options.commit` gives it a
|
|
69
|
+
* `CommitScope` (see {@link DataTableDesktopSwipeActions}). A `null` plan is `[]`.
|
|
70
|
+
*
|
|
71
|
+
* <RowActions actions={swipePlanRowActions(plan(row), row, { omit })} name={row.title} />
|
|
72
|
+
*/
|
|
73
|
+
export function swipePlanRowActions<T>(
|
|
74
|
+
plan: MobileSwipeActions | null | undefined,
|
|
75
|
+
row: T,
|
|
76
|
+
options: Pick<DataTableDesktopSwipeActions<T>, "omit" | "commit"> = {},
|
|
77
|
+
): RowAction[] {
|
|
78
|
+
if (!plan) return [];
|
|
79
|
+
const out: RowAction[] = [];
|
|
80
|
+
const seen = new Set<string>();
|
|
81
|
+
for (const action of [...(plan.end ?? plan.right ?? []), ...(plan.start ?? plan.left ?? [])]) {
|
|
82
|
+
const key = action.id ?? action.label;
|
|
83
|
+
if (seen.has(key)) continue;
|
|
84
|
+
seen.add(key);
|
|
85
|
+
if (options.omit?.(action, row)) continue;
|
|
86
|
+
const commit = typeof options.commit === "function" ? options.commit(action, row) : options.commit;
|
|
87
|
+
out.push({
|
|
88
|
+
key,
|
|
89
|
+
label: action.label,
|
|
90
|
+
icon: isValidElement(action.icon)
|
|
91
|
+
? action.icon
|
|
92
|
+
: action.icon != null && action.icon !== false
|
|
93
|
+
? <>{action.icon}</>
|
|
94
|
+
: undefined,
|
|
95
|
+
tone: swipeTone(action),
|
|
96
|
+
commit,
|
|
97
|
+
onSelect: action.onCommit,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
23
103
|
interface ResolvedRowAction<T> {
|
|
24
104
|
key: string;
|
|
25
105
|
label: string;
|
|
@@ -78,15 +158,21 @@ function toRowAction<T>(a: ResolvedRowAction<T>, row: T): RowAction {
|
|
|
78
158
|
export function useRowActionColumn<T>({
|
|
79
159
|
columns: columnsProp,
|
|
80
160
|
rowActions,
|
|
161
|
+
rowActionsHeader,
|
|
81
162
|
rowName,
|
|
82
163
|
labels,
|
|
83
164
|
density,
|
|
165
|
+
mobileSwipeActions,
|
|
166
|
+
desktopSwipeActions,
|
|
84
167
|
}: {
|
|
85
168
|
columns: DataTableColumn<T>[];
|
|
86
169
|
rowActions: DataTableProps<T>["rowActions"];
|
|
170
|
+
rowActionsHeader?: DataTableProps<T>["rowActionsHeader"];
|
|
87
171
|
rowName: DataTableProps<T>["rowName"];
|
|
88
172
|
labels: DataTableLabels;
|
|
89
173
|
density: DataTableDensity;
|
|
174
|
+
mobileSwipeActions?: DataTableProps<T>["mobileSwipeActions"];
|
|
175
|
+
desktopSwipeActions?: DataTableProps<T>["desktopSwipeActions"];
|
|
90
176
|
}): {
|
|
91
177
|
columns: DataTableColumn<T>[];
|
|
92
178
|
swipesFromActions: (row: T) => MobileSwipeActions | null;
|
|
@@ -100,14 +186,64 @@ export function useRowActionColumn<T>({
|
|
|
100
186
|
: [],
|
|
101
187
|
[rowActions, labels],
|
|
102
188
|
);
|
|
103
|
-
|
|
189
|
+
// dev#598: the desktop half of the caller's swipes. On by default, except beside
|
|
190
|
+
// `rowActions`, whose column already is the desktop's (and whose swipes these usually
|
|
191
|
+
// are). The options object is often a fresh literal per render; its parts are the deps.
|
|
192
|
+
const swipeOptions = typeof desktopSwipeActions === "object" ? desktopSwipeActions : undefined;
|
|
193
|
+
const swipeColumnOn =
|
|
194
|
+
!!mobileSwipeActions && (desktopSwipeActions === undefined ? !rowActions : desktopSwipeActions !== false);
|
|
195
|
+
const swipeOmit = swipeOptions?.omit;
|
|
196
|
+
const swipeName = swipeOptions?.name;
|
|
197
|
+
const swipeCollapse = swipeOptions?.collapse;
|
|
198
|
+
const swipeCommit = swipeOptions?.commit;
|
|
199
|
+
const swipeHeader = swipeOptions?.header;
|
|
200
|
+
const swipeColumn = useMemo<DataTableColumn<T> | null>(
|
|
201
|
+
() =>
|
|
202
|
+
swipeColumnOn
|
|
203
|
+
? {
|
|
204
|
+
key: SWIPE_ACTIONS_KEY,
|
|
205
|
+
header: actionsHeading(swipeHeader, labels.actions),
|
|
206
|
+
cell: (row) => (
|
|
207
|
+
<RowActions
|
|
208
|
+
actions={swipePlanRowActions(mobileSwipeActions!(row), row, { omit: swipeOmit, commit: swipeCommit })}
|
|
209
|
+
name={(swipeName ?? rowName)?.(row)}
|
|
210
|
+
collapse={swipeCollapse}
|
|
211
|
+
size={density === "compact" ? "2xs" : "xs"}
|
|
212
|
+
// The last column's tooltips open toward the row's start.
|
|
213
|
+
tooltipSide="start"
|
|
214
|
+
labels={{ actions: labels.actions }}
|
|
215
|
+
className="justify-end"
|
|
216
|
+
/>
|
|
217
|
+
),
|
|
218
|
+
className: "w-px whitespace-nowrap text-end",
|
|
219
|
+
// The heading over the buttons, at the row's end (0.46.1).
|
|
220
|
+
headClassName: "w-px text-end",
|
|
221
|
+
// On a phone the same actions are the card's swipes.
|
|
222
|
+
mobileHidden: true,
|
|
223
|
+
noRowLink: true,
|
|
224
|
+
}
|
|
225
|
+
: null,
|
|
226
|
+
[
|
|
227
|
+
swipeColumnOn,
|
|
228
|
+
mobileSwipeActions,
|
|
229
|
+
swipeOmit,
|
|
230
|
+
swipeCommit,
|
|
231
|
+
swipeName,
|
|
232
|
+
rowName,
|
|
233
|
+
swipeCollapse,
|
|
234
|
+
swipeHeader,
|
|
235
|
+
density,
|
|
236
|
+
labels.actions,
|
|
237
|
+
],
|
|
238
|
+
);
|
|
239
|
+
const actionColumns = useMemo<DataTableColumn<T>[]>(
|
|
104
240
|
() =>
|
|
105
241
|
rowActions
|
|
106
242
|
? [
|
|
107
243
|
...columnsProp,
|
|
108
244
|
{
|
|
109
245
|
key: ROW_ACTIONS_KEY,
|
|
110
|
-
header:
|
|
246
|
+
header: actionsHeading(rowActionsHeader, labels.actions),
|
|
111
247
|
// Through RowActions (0.32, §10.8): the icons inline at Normal, one "⋯"
|
|
112
248
|
// menu at Large once there are two — every label at once does not fit a
|
|
113
249
|
// row. Each control stops its own click: the row's handler already skips
|
|
@@ -123,14 +259,18 @@ export function useRowActionColumn<T>({
|
|
|
123
259
|
/>
|
|
124
260
|
),
|
|
125
261
|
className: "w-px whitespace-nowrap text-end",
|
|
126
|
-
headClassName: "w-px",
|
|
262
|
+
headClassName: "w-px text-end",
|
|
127
263
|
// On a phone the same actions are the card's swipes.
|
|
128
264
|
mobileHidden: true,
|
|
129
265
|
noRowLink: true,
|
|
130
266
|
},
|
|
131
267
|
]
|
|
132
268
|
: columnsProp,
|
|
133
|
-
[columnsProp, rowActions, rowActionsFor, rowName, labels.actions, density],
|
|
269
|
+
[columnsProp, rowActions, rowActionsHeader, rowActionsFor, rowName, labels.actions, density],
|
|
270
|
+
);
|
|
271
|
+
const columns = useMemo(
|
|
272
|
+
() => (swipeColumn ? [...actionColumns, swipeColumn] : actionColumns),
|
|
273
|
+
[actionColumns, swipeColumn],
|
|
134
274
|
);
|
|
135
275
|
const swipesFromActions = (row: T): MobileSwipeActions | null => {
|
|
136
276
|
const swipes = rowActionsFor(row).filter((a) => a.swipe && !a.action.disabledReason?.(row));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { isTableOwnColumn } from "./data-table-row-actions";
|
|
3
3
|
import { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from "./data-table-labels";
|
|
4
4
|
import type { DataTableCellProps, DataTableColumn, DataTableHeadProps } from "./data-table";
|
|
5
5
|
import type { LabelOverride } from "../i18n/kit-labels";
|
|
@@ -79,7 +79,7 @@ export function cleanAttrs(
|
|
|
79
79
|
* should pass a string header, and that is visible in what gets announced.
|
|
80
80
|
*/
|
|
81
81
|
export function columnLabel<T>(col: DataTableColumn<T>, labels?: DataTableLabels): string {
|
|
82
|
-
if (col.key
|
|
82
|
+
if (isTableOwnColumn(col.key) && labels) return labels.actions;
|
|
83
83
|
return col.headerText ?? (typeof col.header === "string" ? col.header : col.key);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -162,6 +162,22 @@ export function fromOwnControl(target: EventTarget | null, row: HTMLElement): bo
|
|
|
162
162
|
return !!hit && hit !== row && row.contains(hit) && !hit.hasAttribute("data-row-link");
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The controls whose own default IS the click — a native checkbox's or radio's toggle, a
|
|
167
|
+
* label's click through to its field, a summary's disclosure (0.46, the 0.45 note from
|
|
168
|
+
* UM): cancelling the click undoes it. Each is the click's own activation target, so a
|
|
169
|
+
* click on one does not follow the link around it, and {@link RowLink} leaves its
|
|
170
|
+
* default alone.
|
|
171
|
+
*/
|
|
172
|
+
const OWN_DEFAULT = 'input[type="checkbox"],input[type="radio"],label,summary';
|
|
173
|
+
|
|
174
|
+
/** Whether a click inside `link` landed on (or in) one of {@link OWN_DEFAULT}. */
|
|
175
|
+
function onOwnDefault(target: EventTarget | null, link: HTMLElement): boolean {
|
|
176
|
+
if (!(target instanceof Element)) return false;
|
|
177
|
+
const hit = target.closest(OWN_DEFAULT);
|
|
178
|
+
return !!hit && hit !== link && link.contains(hit);
|
|
179
|
+
}
|
|
180
|
+
|
|
165
181
|
/** A click the APP owns. Anything else — middle, ⌘/Ctrl, Shift, Alt — belongs to
|
|
166
182
|
* the browser, and the whole feature is not touching it. */
|
|
167
183
|
const isPlainLeftClick = (e: React.MouseEvent) =>
|
|
@@ -220,6 +236,9 @@ export function RowLink({
|
|
|
220
236
|
// A click in a portal a descendant opened is not a click on the link: leave
|
|
221
237
|
// it (and a link inside it) alone. See `fromOutside`.
|
|
222
238
|
if (fromOutside(e.target, e.currentTarget)) return;
|
|
239
|
+
// A checkbox, a radio, a label or a summary keeps its own default: cancelling
|
|
240
|
+
// it would undo the toggle, and the link is not followed from it anyway.
|
|
241
|
+
if (onOwnDefault(e.target, e.currentTarget)) return;
|
|
223
242
|
if (isPlainLeftClick(e)) e.preventDefault();
|
|
224
243
|
}}
|
|
225
244
|
onClick={(e) => {
|
|
@@ -2,7 +2,7 @@ import { Fragment, useEffect, useId, useLayoutEffect, useMemo, useRef, useState
|
|
|
2
2
|
import type { ReactNode, TdHTMLAttributes, ThHTMLAttributes } from "react";
|
|
3
3
|
import { Card } from "./card";
|
|
4
4
|
import { Spinner } from "./spinner";
|
|
5
|
-
import {
|
|
5
|
+
import { isTableOwnColumn, useRowActionColumn } from "./data-table-row-actions";
|
|
6
6
|
import { useColumnResize } from "./data-table-resize";
|
|
7
7
|
import { useRangeSelection } from "./data-table-selection";
|
|
8
8
|
import {
|
|
@@ -26,6 +26,8 @@ import { nextSorts } from "./data-table-sort";
|
|
|
26
26
|
import type { SortCycle, SortDir, SortState } from "./data-table-sort";
|
|
27
27
|
import { Pagination } from "./data-table-pagination";
|
|
28
28
|
import type { SwipeAction } from "./swipeable-row";
|
|
29
|
+
import type { RowActionsCollapse } from "./row-actions";
|
|
30
|
+
import type { CommitScope } from "./write-lock";
|
|
29
31
|
import { FullBleedDialog } from "./full-bleed-dialog";
|
|
30
32
|
import { usePhoneLayout } from "../hooks/use-breakpoint";
|
|
31
33
|
import { useAnnounce } from "../hooks/use-announce";
|
|
@@ -472,9 +474,28 @@ export interface DataTableProps<T> {
|
|
|
472
474
|
* Mobile only, and deliberately so: the desktop table already has room for an
|
|
473
475
|
* actions column, and a drag gesture on a pointer device is a worse version of a
|
|
474
476
|
* button. Wraps only the row body, so an expansion panel below stays put while the
|
|
475
|
-
* row above it slides.
|
|
477
|
+
* row above it slides. The desktop's buttons for the same actions come with it — see
|
|
478
|
+
* {@link DataTableProps.desktopSwipeActions}.
|
|
476
479
|
*/
|
|
477
480
|
mobileSwipeActions?: (row: T) => MobileSwipeActions | null;
|
|
481
|
+
/**
|
|
482
|
+
* The desktop half of `mobileSwipeActions` (0.46, keksdose dev#598 — Marcel: every
|
|
483
|
+
* table whose phone cards swipe gets a desktop actions column with the same actions):
|
|
484
|
+
* an actions column DERIVED from the very plan the card swipes with, so the two
|
|
485
|
+
* viewports offer the same functions and cannot drift. Each button runs the swipe's own
|
|
486
|
+
* `onCommit` — the same confirm, the same undo toast. The reading-end side's actions
|
|
487
|
+
* first, then the start side's, each once (by `SwipeAction.id`, else by label); a row
|
|
488
|
+
* whose plan is `null` gets an empty cell. The column is hidden on the phone, where the
|
|
489
|
+
* actions are the swipes, kept off the column settings, and its tooltips open toward
|
|
490
|
+
* the row's start.
|
|
491
|
+
*
|
|
492
|
+
* On by default wherever `mobileSwipeActions` is given, so a new swipeable table cannot
|
|
493
|
+
* forget it — except beside `rowActions`, whose column is the desktop's already (pass
|
|
494
|
+
* `true` or options to add this one too). `false` where the desktop row carries every
|
|
495
|
+
* one of those actions as its own buttons; say so at the call site. Options:
|
|
496
|
+
* {@link DataTableDesktopSwipeActions}.
|
|
497
|
+
*/
|
|
498
|
+
desktopSwipeActions?: boolean | DataTableDesktopSwipeActions<T>;
|
|
478
499
|
/**
|
|
479
500
|
* Renders as a full-width row above the data rows in the desktop table
|
|
480
501
|
* (mobile has no equivalent list-row slot, so it's desktop-only). For a
|
|
@@ -588,6 +609,14 @@ export interface DataTableProps<T> {
|
|
|
588
609
|
* `mobileSwipeActions` wins over the swipes derived from here.
|
|
589
610
|
*/
|
|
590
611
|
rowActions?: DataTableRowAction<T>[] | ((row: T) => DataTableRowAction<T>[]);
|
|
612
|
+
/**
|
|
613
|
+
* The `rowActions` column's heading (0.46.1, keksdose dev#598 rework — Marcel: a
|
|
614
|
+
* VISIBLE "Actions" heading on every table with an actions column). Default: the
|
|
615
|
+
* provider's `dataTable.actions` ("Actions"), shown, end-aligned over the buttons. A
|
|
616
|
+
* node replaces it; `false` keeps the pre-0.46.1 heading, named for a screen reader
|
|
617
|
+
* only.
|
|
618
|
+
*/
|
|
619
|
+
rowActionsHeader?: ReactNode | false;
|
|
591
620
|
/**
|
|
592
621
|
* The bar above the table — a search field, filter chips, a create button. Inside
|
|
593
622
|
* the table's frame, above the header, on desktop and phone alike (resource-list
|
|
@@ -663,6 +692,34 @@ export interface MobileSwipeActions {
|
|
|
663
692
|
right?: SwipeAction[];
|
|
664
693
|
}
|
|
665
694
|
|
|
695
|
+
/** How {@link DataTableProps.desktopSwipeActions} draws the column (0.46, dev#598). */
|
|
696
|
+
export interface DataTableDesktopSwipeActions<T> {
|
|
697
|
+
/** Leave an action out because the row already has its own button for it — keksdose's
|
|
698
|
+
* feedback inbox, whose status cell offers `advance` and `wont_do`
|
|
699
|
+
* (`feedbackSwipeOmitShown`). Match on `action.id` rather than the translated label. */
|
|
700
|
+
omit?: (action: SwipeAction, row: T) => boolean;
|
|
701
|
+
/** The row's name for the "⋯" menu ("Actions for …"). Default: the table's `rowName`. */
|
|
702
|
+
name?: (row: T) => string | null | undefined;
|
|
703
|
+
/** {@link RowActionsCollapse}; default `"auto"`. A table whose rows offer more than two
|
|
704
|
+
* actions passes `"menu"`, so the column does not switch between icons and a menu. */
|
|
705
|
+
collapse?: RowActionsCollapse;
|
|
706
|
+
/**
|
|
707
|
+
* Whether an action COMMITS, refused with the reason under a locked
|
|
708
|
+
* `WriteLockProvider` ({@link CommitScope}) — a swipe has no such state, so a plan
|
|
709
|
+
* usually returns nothing for a locked row and the desktop shows nothing either; with
|
|
710
|
+
* this the plan can stay and the desktop says why. Per action as a function. Default:
|
|
711
|
+
* no commit scope (the plan decides).
|
|
712
|
+
*/
|
|
713
|
+
commit?: CommitScope | ((action: SwipeAction, row: T) => CommitScope | undefined);
|
|
714
|
+
/**
|
|
715
|
+
* The column's heading (0.46.1, keksdose dev#598 rework — Marcel: a VISIBLE "Actions"
|
|
716
|
+
* heading on every table with an actions column). Default: the provider's
|
|
717
|
+
* `dataTable.actions` ("Actions"), shown, end-aligned over the buttons. A node
|
|
718
|
+
* replaces it; `false` keeps the 0.46.0 heading, named for a screen reader only.
|
|
719
|
+
*/
|
|
720
|
+
header?: ReactNode | false;
|
|
721
|
+
}
|
|
722
|
+
|
|
666
723
|
/**
|
|
667
724
|
* A totals-row cell: pinned to the scroller's bottom edge, on the header's surface.
|
|
668
725
|
* The rule above it is an inset shadow rather than a `border-t`: the table collapses
|
|
@@ -774,6 +831,7 @@ export function DataTable<T>({
|
|
|
774
831
|
mobileCard,
|
|
775
832
|
mobileSort = false,
|
|
776
833
|
mobileSwipeActions,
|
|
834
|
+
desktopSwipeActions,
|
|
777
835
|
leadingRow,
|
|
778
836
|
density = "comfortable",
|
|
779
837
|
sortCycle = "tri",
|
|
@@ -785,6 +843,7 @@ export function DataTable<T>({
|
|
|
785
843
|
edgeFade = false,
|
|
786
844
|
className,
|
|
787
845
|
rowActions,
|
|
846
|
+
rowActionsHeader,
|
|
788
847
|
toolbar,
|
|
789
848
|
footerLabel,
|
|
790
849
|
footer: footerProp,
|
|
@@ -817,9 +876,12 @@ export function DataTable<T>({
|
|
|
817
876
|
const { columns, swipesFromActions } = useRowActionColumn({
|
|
818
877
|
columns: columnsProp,
|
|
819
878
|
rowActions,
|
|
879
|
+
rowActionsHeader,
|
|
820
880
|
rowName,
|
|
821
881
|
labels,
|
|
822
882
|
density,
|
|
883
|
+
mobileSwipeActions,
|
|
884
|
+
desktopSwipeActions,
|
|
823
885
|
});
|
|
824
886
|
const swipeActionsFor = mobileSwipeActions ?? (rowActions ? swipesFromActions : undefined);
|
|
825
887
|
|
|
@@ -1130,16 +1192,16 @@ export function DataTable<T>({
|
|
|
1130
1192
|
// show — so hiding the "Account" column moves "Total" into "Name" rather than
|
|
1131
1193
|
// dropping it — and on the first visible column when every one carries a value.
|
|
1132
1194
|
const footerLabelCol = showFooter
|
|
1133
|
-
? (visibleColumns.find((c) => !c.footer && c.key
|
|
1195
|
+
? (visibleColumns.find((c) => !c.footer && !isTableOwnColumn(c.key)) ?? visibleColumns[0])
|
|
1134
1196
|
: undefined;
|
|
1135
1197
|
const footerValue = (col: DataTableColumn<T>) => col.footer?.(sorted, slice);
|
|
1136
1198
|
const footerHeadingId = useId();
|
|
1137
1199
|
// Each open row's detail row, for its disclosure button's `aria-controls`.
|
|
1138
1200
|
const detailIdBase = useId();
|
|
1139
1201
|
// The actions column is the table's own, not one the user can hide: out of the count.
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1202
|
+
const ownColumns = columns.filter((c) => isTableOwnColumn(c.key)).length;
|
|
1203
|
+
const ownVisible = visibleColumns.filter((c) => isTableOwnColumn(c.key)).length;
|
|
1204
|
+
const columnsCountLabel = labels.columnsCount(visibleCount - ownVisible, totalCount - ownColumns);
|
|
1143
1205
|
// Match the `md:` breakpoint at the text size in force: we render either the table or
|
|
1144
1206
|
// the card list — never both — so we don't double up DOM nodes that screen readers and
|
|
1145
1207
|
// integration tests would have to disambiguate. `usePhoneLayout()` itself, not its twin
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Select } from "./select";
|
|
3
|
+
import type { SelectProps } from "./select";
|
|
4
|
+
import { useAppearanceLabels } from "./appearance-labels";
|
|
5
|
+
import type { AppearanceLabels } from "./appearance-labels";
|
|
6
|
+
import { useKitLocale } from "../i18n/kit-labels";
|
|
7
|
+
import type { LabelOverride } from "../i18n/kit-labels";
|
|
8
|
+
import { DATE_FORMAT_PREFERENCES, dateFormatSample } from "../lib/date-format";
|
|
9
|
+
import type { DateFormatPreference } from "../lib/date-format";
|
|
10
|
+
|
|
11
|
+
/** A {@link Select}'s props minus the value, the change handler and the options, which
|
|
12
|
+
* this owns. */
|
|
13
|
+
export interface DateFormatSettingProps extends Omit<SelectProps, "value" | "onChange" | "children" | "multiple"> {
|
|
14
|
+
value: DateFormatPreference;
|
|
15
|
+
onChange: (value: DateFormatPreference) => void;
|
|
16
|
+
/** The field's label. Default: the kit's `appearance.dateFormat` ("Date format"). */
|
|
17
|
+
label?: ReactNode;
|
|
18
|
+
/** The line under it. Default: the kit's `appearance.dateFormatHelp`; `null` for none. */
|
|
19
|
+
hint?: ReactNode;
|
|
20
|
+
/** The interface language the samples are rendered for — `"auto"`'s sample is that
|
|
21
|
+
* language's shape. Default: the provider's locale. */
|
|
22
|
+
language?: string;
|
|
23
|
+
/** The `appearance` namespace, for this instance. */
|
|
24
|
+
labels?: LabelOverride<AppearanceLabels>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The date format (0.46, keksdose's Settings ▸ Appearance select, feedback #180): "Follow
|
|
29
|
+
* language" and four explicit shapes, each option labelled by the date it produces —
|
|
30
|
+
* 01.08.2026, 01/08/2026, 08/01/2026, 2026-08-01 — rendered live by the resolver the
|
|
31
|
+
* app's dates go through ({@link dateFormatSample}), and `auto` as "Follow language —
|
|
32
|
+
* 01 Aug 2026". The words are the kit's `appearance` namespace; the value and where it
|
|
33
|
+
* is kept are the app's (see `formatDateWithPreference` and `dateFormatterFor`).
|
|
34
|
+
*/
|
|
35
|
+
export function DateFormatSetting({
|
|
36
|
+
value,
|
|
37
|
+
onChange,
|
|
38
|
+
label,
|
|
39
|
+
hint,
|
|
40
|
+
language,
|
|
41
|
+
labels: labelsProp,
|
|
42
|
+
...rest
|
|
43
|
+
}: DateFormatSettingProps) {
|
|
44
|
+
const labels = useAppearanceLabels(labelsProp);
|
|
45
|
+
const sampleLanguage = useKitLocale(language);
|
|
46
|
+
return (
|
|
47
|
+
<Select
|
|
48
|
+
{...rest}
|
|
49
|
+
label={label ?? labels.dateFormat}
|
|
50
|
+
hint={hint === null ? undefined : (hint ?? labels.dateFormatHelp)}
|
|
51
|
+
value={value}
|
|
52
|
+
onChange={(e) => onChange(e.target.value as DateFormatPreference)}
|
|
53
|
+
>
|
|
54
|
+
{DATE_FORMAT_PREFERENCES.map((preference) => {
|
|
55
|
+
const sample = dateFormatSample(preference, sampleLanguage);
|
|
56
|
+
return (
|
|
57
|
+
<option key={preference} value={preference}>
|
|
58
|
+
{preference === "auto" ? `${labels.dateFormatAuto} — ${sample}` : sample}
|
|
59
|
+
</option>
|
|
60
|
+
);
|
|
61
|
+
})}
|
|
62
|
+
</Select>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -522,10 +522,20 @@ function InlineAction({
|
|
|
522
522
|
);
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
-
/** The actions column's
|
|
526
|
-
|
|
525
|
+
/** The actions column's heading, in the provider's words: on screen since 0.46.1 (keksdose
|
|
526
|
+
* dev#598 rework), a screen reader's name only with `hidden`. */
|
|
527
|
+
function RowActionsHeader({
|
|
528
|
+
text,
|
|
529
|
+
labels,
|
|
530
|
+
hidden,
|
|
531
|
+
}: {
|
|
532
|
+
text?: string;
|
|
533
|
+
labels?: LabelOverride<RowActionsLabels>;
|
|
534
|
+
hidden?: boolean;
|
|
535
|
+
}) {
|
|
527
536
|
const resolved = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS, labels);
|
|
528
|
-
|
|
537
|
+
const word = text ?? resolved.actions;
|
|
538
|
+
return hidden ? <span className="sr-only">{word}</span> : <>{word}</>;
|
|
529
539
|
}
|
|
530
540
|
|
|
531
541
|
export interface RowActionsColumnOptions<T> {
|
|
@@ -546,10 +556,17 @@ export interface RowActionsColumnOptions<T> {
|
|
|
546
556
|
/**
|
|
547
557
|
* The column's name as plain text — the column-settings checklist and the phone
|
|
548
558
|
* card's label read it (`DataTableColumn.headerText`). Pass the app's own word; left
|
|
549
|
-
* out, the
|
|
550
|
-
*
|
|
559
|
+
* out, the heading shows the provider's `rowActions.actions` and the plain-text
|
|
560
|
+
* places fall back to the column's `key`.
|
|
551
561
|
*/
|
|
552
562
|
headerText?: string;
|
|
563
|
+
/**
|
|
564
|
+
* The heading (0.46.1, keksdose dev#598 rework — Marcel: a VISIBLE "Actions" heading on
|
|
565
|
+
* every table with an actions column). Default: `headerText`, else the provider's
|
|
566
|
+
* `rowActions.actions` ("Actions"), shown, end-aligned over the buttons. A node
|
|
567
|
+
* replaces it; `false` keeps the pre-0.46.1 heading, named for a screen reader only.
|
|
568
|
+
*/
|
|
569
|
+
header?: ReactNode | false;
|
|
553
570
|
labels?: LabelOverride<RowActionsLabels>;
|
|
554
571
|
/** Anything else for the column (`mobileHidden`, `cellProps`…), merged last. */
|
|
555
572
|
column?: Partial<DataTableColumn<T>>;
|
|
@@ -561,8 +578,8 @@ export interface RowActionsColumnOptions<T> {
|
|
|
561
578
|
*
|
|
562
579
|
* columns={[...columns, rowActionsColumn({ actions: (r) => [...], name: (r) => r.name })]}
|
|
563
580
|
*
|
|
564
|
-
* The
|
|
565
|
-
* content at the row's end, and `noRowLink` keeps a `rowHref` anchor off it, so the
|
|
581
|
+
* The heading says "Actions" on screen (0.46.1; `header` changes or hides it), the
|
|
582
|
+
* column hugs its content at the row's end, and `noRowLink` keeps a `rowHref` anchor off it, so the
|
|
566
583
|
* buttons keep their own clicks. On a phone it is a line of the card like any column;
|
|
567
584
|
* set `column: { mobileHidden: true }` to drop it there (with `mobileDetailsInRow` it
|
|
568
585
|
* then shows in the opened row instead).
|
|
@@ -576,12 +593,18 @@ export function rowActionsColumn<T>({
|
|
|
576
593
|
glyphSize,
|
|
577
594
|
tooltipSide,
|
|
578
595
|
headerText,
|
|
596
|
+
header,
|
|
579
597
|
labels,
|
|
580
598
|
column,
|
|
581
599
|
}: RowActionsColumnOptions<T>): DataTableColumn<T> {
|
|
582
600
|
return {
|
|
583
601
|
key,
|
|
584
|
-
header:
|
|
602
|
+
header:
|
|
603
|
+
header === false || header == null ? (
|
|
604
|
+
<RowActionsHeader text={headerText} labels={labels} hidden={header === false} />
|
|
605
|
+
) : (
|
|
606
|
+
header
|
|
607
|
+
),
|
|
585
608
|
headerText,
|
|
586
609
|
cell: (row) => (
|
|
587
610
|
<RowActions
|
|
@@ -595,7 +618,8 @@ export function rowActionsColumn<T>({
|
|
|
595
618
|
/>
|
|
596
619
|
),
|
|
597
620
|
className: "w-px whitespace-nowrap text-end",
|
|
598
|
-
|
|
621
|
+
// The heading over the buttons, at the row's end (0.46.1).
|
|
622
|
+
headClassName: "w-px text-end",
|
|
599
623
|
noRowLink: true,
|
|
600
624
|
...column,
|
|
601
625
|
};
|
|
@@ -31,6 +31,14 @@ export interface SwipePaint {
|
|
|
31
31
|
interface SwipeActionBase {
|
|
32
32
|
onCommit: () => void;
|
|
33
33
|
label: string;
|
|
34
|
+
/**
|
|
35
|
+
* What the action IS, apart from its words (0.46, keksdose dev#598): `"approve"`,
|
|
36
|
+
* `"wont_do"`. The swipe itself never reads it; DataTable's desktop column does — one
|
|
37
|
+
* button per id, and `desktopSwipeActions.omit` can name the actions the row already
|
|
38
|
+
* has a button for without matching on a translated label. The kit's own plans
|
|
39
|
+
* (`feedbackSwipePlan`, `TranslationReviewPanel`'s binding) set their binding's ids.
|
|
40
|
+
*/
|
|
41
|
+
id?: string;
|
|
34
42
|
/** Rendered inside the panel. Icons stay the consumer's choice — this package
|
|
35
43
|
* ships no opinion about which icon set an app uses. */
|
|
36
44
|
icon?: ReactNode;
|
package/src/components/tabs.tsx
CHANGED
|
@@ -78,7 +78,7 @@ export interface TabsProps<T extends string>
|
|
|
78
78
|
onChange: (id: T) => void;
|
|
79
79
|
className?: string;
|
|
80
80
|
/**
|
|
81
|
-
* The caret beside a SCROLLING strip
|
|
81
|
+
* The caret beside a SCROLLING strip (0.40, kk run 79 gap 1): a › while
|
|
82
82
|
* tabs lie past the strip's end — a tap scrolls it on by three quarters of its width —
|
|
83
83
|
* turning into a ‹ at the end, which goes back to the first. Shown only while the strip
|
|
84
84
|
* actually overflows.
|
|
@@ -88,7 +88,8 @@ export interface TabsProps<T extends string>
|
|
|
88
88
|
* off (keksdose live #410, whose `tab-strip-more.tsx` this replaces). One button that
|
|
89
89
|
* turns round rather than two that come and go, so the strip's width never changes
|
|
90
90
|
* under the thumb. Out of the tab order — the keyboard walks the strip with the arrow
|
|
91
|
-
* keys, which bring the focused tab into view.
|
|
91
|
+
* keys, which bring the focused tab into view. At every width since 0.46 (keksdose live
|
|
92
|
+
* #410 round 3: eleven report tabs overflow a laptop too); before, `md:hidden`. Ignored on
|
|
92
93
|
* a vertical strip (a side nav is a column, not a row). Names:
|
|
93
94
|
* `tabs.more` / `tabs.backToFirst`.
|
|
94
95
|
*/
|
|
@@ -269,7 +270,7 @@ export function Tabs<T extends string>({
|
|
|
269
270
|
};
|
|
270
271
|
const cueButton = cue ? (
|
|
271
272
|
// The strip's bottom rule continues under the caret, so the row reads as one.
|
|
272
|
-
<div data-tabs-overflow-cue="" className="flex shrink-0 items-center self-stretch border-b border-subtle
|
|
273
|
+
<div data-tabs-overflow-cue="" className="flex shrink-0 items-center self-stretch border-b border-subtle">
|
|
273
274
|
<IconButton
|
|
274
275
|
size="sm"
|
|
275
276
|
variant="ghost"
|
|
@@ -221,6 +221,8 @@ export function TranslationReviewEditor({
|
|
|
221
221
|
<>
|
|
222
222
|
<Textarea
|
|
223
223
|
ref={wordingRef}
|
|
224
|
+
// The panel's "Needs a change" swipe on an already open row finds it here (#416).
|
|
225
|
+
data-review-wording=""
|
|
224
226
|
label={missing ? labels.translation : labels.suggestion}
|
|
225
227
|
lang={row.locale}
|
|
226
228
|
rows={Math.min(8, Math.max(2, Math.ceil(Math.max(row.text.length, row.reference.length) / 80)))}
|
|
@@ -52,6 +52,11 @@ export interface TranslationReviewLabels {
|
|
|
52
52
|
flag: string;
|
|
53
53
|
/** "Needs a change" for a MISSING string: the suggestion is the translation to add. */
|
|
54
54
|
suggest: string;
|
|
55
|
+
/** 0.46 (keksdose B2/S3): the desktop row's button that OPENS the editor to send a
|
|
56
|
+
* string back — its own name, apart from the editor's `flag`, which saves. */
|
|
57
|
+
flagRow: string;
|
|
58
|
+
/** The same for a MISSING string: opens the editor to add the translation. */
|
|
59
|
+
suggestRow: string;
|
|
55
60
|
/** Clear a verdict: back to unreviewed. */
|
|
56
61
|
reset: string;
|
|
57
62
|
/** The editor's way out without saving. */
|
|
@@ -126,6 +131,8 @@ export const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels = {
|
|
|
126
131
|
approve: "Approve",
|
|
127
132
|
flag: "Needs a change",
|
|
128
133
|
suggest: "Suggest translation",
|
|
134
|
+
flagRow: "Flag for a change…",
|
|
135
|
+
suggestRow: "Add a translation…",
|
|
129
136
|
reset: "Mark unreviewed",
|
|
130
137
|
cancel: "Cancel",
|
|
131
138
|
approveSelected: "Approve selected",
|