@eifi1/ui-kit 0.25.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/dist/chart.d.ts +13 -9
- package/dist/components/account-chips.d.ts +1 -1
- package/dist/components/amount-input.d.ts +13 -9
- package/dist/components/button-group.d.ts +13 -9
- package/dist/components/calculator.d.ts +13 -9
- package/dist/components/confirm-dialog.d.ts +13 -9
- package/dist/components/copy-button.d.ts +10 -6
- package/dist/components/danger-confirm.d.ts +13 -9
- package/dist/components/data-table-cells.d.ts +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-labels.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +3 -2
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/facing-pair.d.ts +13 -9
- package/dist/components/file-button.d.ts +13 -9
- package/dist/components/file-dropzone.d.ts +13 -9
- package/dist/components/form-actions.d.ts +13 -9
- package/dist/components/iban-input.d.ts +13 -9
- package/dist/components/language-select.d.ts +13 -9
- package/dist/components/money-field.d.ts +13 -9
- package/dist/components/number-field.d.ts +13 -9
- package/dist/components/number-input.d.ts +13 -9
- package/dist/components/numpad-sheet.d.ts +13 -9
- package/dist/components/phone-input.d.ts +13 -9
- package/dist/components/series-chart.d.ts +13 -9
- package/dist/components/settings-fields.d.ts +13 -9
- package/dist/components/share-card.d.ts +13 -9
- package/dist/components/swipeable-row.d.ts +5 -1
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/text-link.d.ts +13 -9
- package/dist/components/time-input.d.ts +13 -9
- package/dist/components/translation-review-labels.d.ts +4 -0
- package/dist/components/translation-review-labels.js +2 -1
- package/dist/components/translation-review-labels.js.map +1 -1
- package/dist/components/translation-review.d.ts +27 -3
- package/dist/components/translation-review.js +50 -21
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +10 -6
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/{data-table-labels-Cdn1QS2k.d.ts → data-table-labels-DUELeXpa.d.ts} +5 -0
- package/dist/data-table.d.ts +1 -1
- package/dist/feedback/feedback-attachment.d.ts +13 -9
- package/dist/feedback/feedback-attachment.js +3 -3
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-capture.d.ts +71 -0
- package/dist/feedback/feedback-capture.js +194 -0
- package/dist/feedback/feedback-capture.js.map +1 -0
- package/dist/feedback/feedback-context.d.ts +71 -0
- package/dist/feedback/feedback-context.js +96 -0
- package/dist/feedback/feedback-context.js.map +1 -0
- package/dist/feedback/feedback-crash.d.ts +276 -0
- package/dist/feedback/feedback-crash.js +225 -0
- package/dist/feedback/feedback-crash.js.map +1 -0
- package/dist/feedback/feedback-dialog.d.ts +13 -9
- package/dist/feedback/feedback-dialog.js +5 -3
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +13 -9
- package/dist/feedback/feedback-inbox.js +89 -49
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-labels.d.ts +71 -0
- package/dist/feedback/feedback-labels.js +48 -0
- package/dist/feedback/feedback-labels.js.map +1 -0
- package/dist/feedback/feedback-menu.d.ts +71 -0
- package/dist/feedback/feedback-menu.js +61 -0
- package/dist/feedback/feedback-menu.js.map +1 -0
- package/dist/feedback/feedback-record.d.ts +71 -0
- package/dist/feedback/feedback-record.js +104 -0
- package/dist/feedback/feedback-record.js.map +1 -0
- package/dist/feedback/feedback-row-detail.d.ts +71 -0
- package/dist/feedback/feedback-row-detail.js +437 -0
- package/dist/feedback/feedback-row-detail.js.map +1 -0
- package/dist/feedback/feedback-status-undo.d.ts +71 -0
- package/dist/feedback/feedback-status-undo.js +40 -0
- package/dist/feedback/feedback-status-undo.js.map +1 -0
- package/dist/feedback/feedback-submit.d.ts +182 -0
- package/dist/feedback/feedback-submit.js +101 -0
- package/dist/feedback/feedback-submit.js.map +1 -0
- package/dist/feedback/feedback-swipe.d.ts +152 -0
- package/dist/feedback/feedback-swipe.js +73 -0
- package/dist/feedback/feedback-swipe.js.map +1 -0
- package/dist/feedback/feedback-table.d.ts +71 -0
- package/dist/feedback/feedback-table.js +357 -0
- package/dist/feedback/feedback-table.js.map +1 -0
- package/dist/feedback/feedback-thread.d.ts +13 -9
- package/dist/feedback.d.ts +17 -9
- package/dist/feedback.js +11 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +13 -9
- package/dist/i18n/defaults.d.ts +13 -9
- package/dist/i18n/defaults.js +16 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +13 -9
- package/dist/i18n/german.js +92 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +7 -3
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +13 -9
- package/dist/i18n/locales/de-CH.d.ts +13 -9
- package/dist/i18n/locales/en.d.ts +13 -9
- package/dist/i18n/locales/en.js +11 -1
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +13 -9
- package/dist/i18n/locales/es.js +90 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +13 -9
- package/dist/i18n/locales/fr.js +90 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +13 -9
- package/dist/i18n/locales/hu.js +91 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +13 -9
- package/dist/i18n/locales/it.js +90 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +13 -9
- package/dist/i18n/locales/zh.js +90 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +16 -11
- package/dist/i18n/review.js +12 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/swiss.js +3 -1
- package/dist/i18n/swiss.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/{feedback-BXKH-ToU.d.ts → kit-labels-DQg82eHB.d.ts} +1275 -25
- package/dist/lib/translation-review.d.ts +76 -1
- package/dist/lib/translation-review.js +30 -0
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/rhf/fields.d.ts +13 -9
- package/dist/rhf/form.d.ts +13 -9
- package/dist/rhf.d.ts +13 -9
- package/dist/shell/app-shell.d.ts +13 -9
- package/dist/shell/top-bar-brand.d.ts +13 -9
- package/dist/shell.d.ts +11 -11
- package/dist/wizard/stepper-nav.d.ts +13 -9
- package/dist/wizard.d.ts +13 -9
- package/package.json +6 -1
- package/src/components/data-table.tsx +14 -2
- package/src/components/swipeable-row.tsx +5 -1
- package/src/components/translation-review-labels.ts +5 -0
- package/src/components/translation-review.tsx +102 -35
- package/src/feedback/feedback-attachment.tsx +19 -3
- package/src/feedback/feedback-capture.ts +285 -0
- package/src/feedback/feedback-context.tsx +217 -0
- package/src/feedback/feedback-crash.ts +547 -0
- package/src/feedback/feedback-dialog.tsx +29 -6
- package/src/feedback/feedback-inbox.tsx +131 -27
- package/src/feedback/feedback-labels.ts +127 -0
- package/src/feedback/feedback-menu.tsx +132 -0
- package/src/feedback/feedback-record.ts +345 -0
- package/src/feedback/feedback-row-detail.tsx +825 -0
- package/src/feedback/feedback-status-undo.ts +111 -0
- package/src/feedback/feedback-submit.tsx +224 -0
- package/src/feedback/feedback-swipe.tsx +170 -0
- package/src/feedback/feedback-table.tsx +757 -0
- package/src/feedback.ts +13 -0
- package/src/i18n/defaults.ts +16 -0
- package/src/i18n/german.ts +99 -5
- package/src/i18n/kit-labels.tsx +20 -0
- package/src/i18n/locales/en.ts +16 -1
- package/src/i18n/locales/es.ts +97 -5
- package/src/i18n/locales/fr.ts +96 -5
- package/src/i18n/locales/hu.ts +95 -5
- package/src/i18n/locales/it.ts +98 -5
- package/src/i18n/locales/zh.ts +91 -5
- package/src/i18n/review.ts +15 -2
- package/src/i18n/swiss.ts +5 -1
- package/src/index.ts +19 -0
- package/src/lib/translation-review.ts +109 -0
- package/tokens.css +8 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { bA as ButtonVariant } from '../kit-labels-DQg82eHB.js';
|
|
4
4
|
import { UseWizardReturn, WizardLabels } from './types.js';
|
|
5
|
-
import '../
|
|
6
|
-
import '../hooks/use-anchored-rect.js';
|
|
7
|
-
import '../lib/clipping.js';
|
|
8
|
-
import '../data-table-labels-Cdn1QS2k.js';
|
|
5
|
+
import '../data-table-labels-DUELeXpa.js';
|
|
9
6
|
import '../components/data-table-sort.js';
|
|
10
7
|
import '../components/swipeable-row.js';
|
|
11
8
|
import '../components/mini-calendar.js';
|
|
@@ -14,6 +11,9 @@ import '../components/popover.js';
|
|
|
14
11
|
import '../components/chip.js';
|
|
15
12
|
import 'lucide-react';
|
|
16
13
|
import '../components/field-sync.js';
|
|
14
|
+
import '../components/tooltip.js';
|
|
15
|
+
import '../hooks/use-anchored-rect.js';
|
|
16
|
+
import '../lib/clipping.js';
|
|
17
17
|
import '../tour/tour.js';
|
|
18
18
|
import '../search/command-palette.js';
|
|
19
19
|
import '../search/global-search.js';
|
|
@@ -33,19 +33,23 @@ import '../components/dialog-frame.js';
|
|
|
33
33
|
import '../components/modal.js';
|
|
34
34
|
import '../hooks/use-search-param-state.js';
|
|
35
35
|
import '../components/measured-grid.js';
|
|
36
|
-
import '../components/
|
|
36
|
+
import '../components/user-avatar.js';
|
|
37
|
+
import '../components/status-dot.js';
|
|
38
|
+
import '../components/progress-bar.js';
|
|
39
|
+
import '../components/signed-amount.js';
|
|
40
|
+
import '../shell/topbar-action-menu.js';
|
|
41
|
+
import '../components/menu-item.js';
|
|
37
42
|
import '../components/floating-panel.js';
|
|
43
|
+
import '../hooks/use-authed-src.js';
|
|
44
|
+
import '../components/account-settings-labels.js';
|
|
38
45
|
import '../components/bulk-action-bar.js';
|
|
39
46
|
import '../components/list.js';
|
|
40
|
-
import '../components/signed-amount.js';
|
|
41
47
|
import '../components/breadcrumbs.js';
|
|
42
48
|
import '../components/toast.js';
|
|
43
49
|
import '../components/description-list.js';
|
|
44
50
|
import '../components/line-items.js';
|
|
45
|
-
import '../components/progress-bar.js';
|
|
46
51
|
import '../components/error-boundary.js';
|
|
47
52
|
import '../components/authed-image.js';
|
|
48
|
-
import '../hooks/use-authed-src.js';
|
|
49
53
|
import '../components/image-grid.js';
|
|
50
54
|
import '../components/lightbox.js';
|
|
51
55
|
import '../components/write-lock.js';
|
package/dist/wizard.d.ts
CHANGED
|
@@ -6,11 +6,8 @@ export { WizardStep, WizardStepProps } from './wizard/wizard-step.js';
|
|
|
6
6
|
export { WizardSummary } from './wizard/wizard-summary.js';
|
|
7
7
|
export { StepperNav } from './wizard/stepper-nav.js';
|
|
8
8
|
import 'react';
|
|
9
|
-
import './
|
|
10
|
-
import './
|
|
11
|
-
import './hooks/use-anchored-rect.js';
|
|
12
|
-
import './lib/clipping.js';
|
|
13
|
-
import './data-table-labels-Cdn1QS2k.js';
|
|
9
|
+
import './kit-labels-DQg82eHB.js';
|
|
10
|
+
import './data-table-labels-DUELeXpa.js';
|
|
14
11
|
import './components/data-table-sort.js';
|
|
15
12
|
import './components/swipeable-row.js';
|
|
16
13
|
import './components/mini-calendar.js';
|
|
@@ -19,6 +16,9 @@ import './components/popover.js';
|
|
|
19
16
|
import './components/chip.js';
|
|
20
17
|
import 'lucide-react';
|
|
21
18
|
import './components/field-sync.js';
|
|
19
|
+
import './components/tooltip.js';
|
|
20
|
+
import './hooks/use-anchored-rect.js';
|
|
21
|
+
import './lib/clipping.js';
|
|
22
22
|
import './tour/tour.js';
|
|
23
23
|
import './search/command-palette.js';
|
|
24
24
|
import './search/global-search.js';
|
|
@@ -38,19 +38,23 @@ import './components/dialog-frame.js';
|
|
|
38
38
|
import './components/modal.js';
|
|
39
39
|
import './hooks/use-search-param-state.js';
|
|
40
40
|
import './components/measured-grid.js';
|
|
41
|
-
import './components/
|
|
41
|
+
import './components/user-avatar.js';
|
|
42
|
+
import './components/status-dot.js';
|
|
43
|
+
import './components/progress-bar.js';
|
|
44
|
+
import './components/signed-amount.js';
|
|
45
|
+
import './shell/topbar-action-menu.js';
|
|
46
|
+
import './components/menu-item.js';
|
|
42
47
|
import './components/floating-panel.js';
|
|
48
|
+
import './hooks/use-authed-src.js';
|
|
49
|
+
import './components/account-settings-labels.js';
|
|
43
50
|
import './components/bulk-action-bar.js';
|
|
44
51
|
import './components/list.js';
|
|
45
|
-
import './components/signed-amount.js';
|
|
46
52
|
import './components/breadcrumbs.js';
|
|
47
53
|
import './components/toast.js';
|
|
48
54
|
import './components/description-list.js';
|
|
49
55
|
import './components/line-items.js';
|
|
50
|
-
import './components/progress-bar.js';
|
|
51
56
|
import './components/error-boundary.js';
|
|
52
57
|
import './components/authed-image.js';
|
|
53
|
-
import './hooks/use-authed-src.js';
|
|
54
58
|
import './components/image-grid.js';
|
|
55
59
|
import './components/lightbox.js';
|
|
56
60
|
import './components/write-lock.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
101
|
"lucide-react": ">=0.400.0 <2",
|
|
102
|
+
"modern-screenshot": "^4.7.0",
|
|
102
103
|
"react": ">=19",
|
|
103
104
|
"react-dom": ">=19",
|
|
104
105
|
"react-hook-form": "^7.55.0",
|
|
@@ -107,6 +108,9 @@
|
|
|
107
108
|
"sonner": ">=2"
|
|
108
109
|
},
|
|
109
110
|
"peerDependenciesMeta": {
|
|
111
|
+
"modern-screenshot": {
|
|
112
|
+
"optional": true
|
|
113
|
+
},
|
|
110
114
|
"react-hook-form": {
|
|
111
115
|
"optional": true
|
|
112
116
|
},
|
|
@@ -142,6 +146,7 @@
|
|
|
142
146
|
"husky": "^9.1.7",
|
|
143
147
|
"jsdom": "^30.0.1",
|
|
144
148
|
"lucide-react": "^1.47.0",
|
|
149
|
+
"modern-screenshot": "^4.7.0",
|
|
145
150
|
"react-hook-form": "^7.88.0",
|
|
146
151
|
"react-router": "^8.3.0",
|
|
147
152
|
"recharts": "^3.8.1",
|
|
@@ -399,6 +399,11 @@ export interface DataTableProps<T> {
|
|
|
399
399
|
* key/value rows) with a compact custom layout, while keeping the shared
|
|
400
400
|
* clickable/expandable row wrapper. Use when the stacked label/value grid
|
|
401
401
|
* wastes space (feedback #317). Desktop is unaffected.
|
|
402
|
+
*
|
|
403
|
+
* It replaces the WHOLE body, `mobilePrimary` included: a column's
|
|
404
|
+
* `mobilePrimary` cell is not drawn when `mobileCard` is set, so anything that
|
|
405
|
+
* cell carried (keksdose's environment and rework chips, 0.27) must be drawn by
|
|
406
|
+
* the card itself — as `FeedbackMobileCard` does.
|
|
402
407
|
*/
|
|
403
408
|
mobileCard?: (row: T) => ReactNode;
|
|
404
409
|
/**
|
|
@@ -1116,8 +1121,15 @@ export function DataTable<T>({
|
|
|
1116
1121
|
label: a.label,
|
|
1117
1122
|
icon: a.icon,
|
|
1118
1123
|
onCommit: () => void runRowAction(a.action, row),
|
|
1119
|
-
|
|
1120
|
-
|
|
1124
|
+
// Each fill's own text colour — the fills are pastels in dark mode (0.26).
|
|
1125
|
+
className:
|
|
1126
|
+
a.tone === "danger"
|
|
1127
|
+
? "bg-[var(--danger)] text-[var(--danger-contrast)]"
|
|
1128
|
+
: "bg-[var(--brand)] text-[var(--brand-contrast)]",
|
|
1129
|
+
armedClassName:
|
|
1130
|
+
a.tone === "danger"
|
|
1131
|
+
? "bg-[var(--danger-hover)] text-[var(--danger-contrast)]"
|
|
1132
|
+
: "bg-[var(--brand-hover)] text-[var(--brand-contrast)]",
|
|
1121
1133
|
});
|
|
1122
1134
|
return {
|
|
1123
1135
|
start: swipes.filter((a) => a.swipe === "start").map(toSwipe),
|
|
@@ -18,7 +18,11 @@ export interface SwipeAction {
|
|
|
18
18
|
/** Rendered inside the panel. Icons stay the consumer's choice — this package
|
|
19
19
|
* ships no opinion about which icon set an app uses. */
|
|
20
20
|
icon?: ReactNode;
|
|
21
|
-
/** Background utility class while the action is previewed but not yet armed.
|
|
21
|
+
/** Background utility class while the action is previewed but not yet armed. The
|
|
22
|
+
* panel's text is white unless this sets a text colour too — and a fill that turns
|
|
23
|
+
* light in dark mode needs one: pass the fill's contrast token
|
|
24
|
+
* (`bg-[var(--danger)] text-[var(--danger-contrast)]`; also `--brand-contrast`,
|
|
25
|
+
* `--success-contrast`), as the kit's own swipes do since 0.26. */
|
|
22
26
|
className: string;
|
|
23
27
|
/** Background utility class once the drag has passed this action's threshold. */
|
|
24
28
|
armedClassName: string;
|
|
@@ -94,6 +94,10 @@ export interface TranslationReviewLabels {
|
|
|
94
94
|
/** 0.25: asked first when a group's bulk approve is larger than a page — more than the
|
|
95
95
|
* reviewer can have had on screen. `group` is the group's name. */
|
|
96
96
|
confirmGroup: (count: number, group: string) => string;
|
|
97
|
+
/** The Undo toast after a swipe bound to `clear` took a verdict back (keksdose, live
|
|
98
|
+
* #377 rework: the swipes bound in its settings) — one string, or a batch of them.
|
|
99
|
+
* The swipe itself is named by {@link reset}. */
|
|
100
|
+
clearedToast: (count: number) => string;
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
export const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels = {
|
|
@@ -150,6 +154,7 @@ export const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels = {
|
|
|
150
154
|
count === 1
|
|
151
155
|
? `Approve the unreviewed string in ${group}?`
|
|
152
156
|
: `Approve all ${count} unreviewed strings in ${group}, including those not on screen?`,
|
|
157
|
+
clearedToast: (count) => (count === 1 ? "String marked unreviewed" : `${count} strings marked unreviewed`),
|
|
153
158
|
};
|
|
154
159
|
|
|
155
160
|
/** A status's word, from the namespace's five. */
|
|
@@ -7,6 +7,7 @@ import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
|
7
7
|
import { cn } from "../lib/cn";
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_TRANSLATION_REVIEW_FILTER,
|
|
10
|
+
DEFAULT_TRANSLATION_REVIEW_SWIPE,
|
|
10
11
|
REVIEW_STATUSES,
|
|
11
12
|
filterTranslationRows,
|
|
12
13
|
groupTranslationRows,
|
|
@@ -14,12 +15,15 @@ import {
|
|
|
14
15
|
reviewUndo,
|
|
15
16
|
reviewWrite,
|
|
16
17
|
summariseRows,
|
|
18
|
+
translationReviewSwipePlan,
|
|
17
19
|
unreviewedRows,
|
|
18
20
|
} from "../lib/translation-review";
|
|
19
21
|
import type {
|
|
20
22
|
ReviewStatus,
|
|
21
23
|
TranslationReviewFilter,
|
|
22
24
|
TranslationReviewKey,
|
|
25
|
+
TranslationReviewSwipeAction,
|
|
26
|
+
TranslationReviewSwipeBinding,
|
|
23
27
|
TranslationReviewWrite,
|
|
24
28
|
TranslationRow,
|
|
25
29
|
TranslationRowGroup,
|
|
@@ -33,6 +37,7 @@ import type { DataTableColumn, DataTableProps, MobileSwipeActions, SortState } f
|
|
|
33
37
|
import { Disclosure } from "./disclosure";
|
|
34
38
|
import { SearchField } from "./search-field";
|
|
35
39
|
import { toast } from "./toast";
|
|
40
|
+
import type { SwipeAction } from "./swipeable-row";
|
|
36
41
|
import { ToggleGroup } from "./toggle-group";
|
|
37
42
|
import { TranslationReviewEditor } from "./translation-review-editor";
|
|
38
43
|
import { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from "./translation-review-labels";
|
|
@@ -101,7 +106,8 @@ export interface TranslationReviewSaveInfo {
|
|
|
101
106
|
/** The open editor, a row's approve button, a swipe on a phone card, a group's
|
|
102
107
|
* "Approve unreviewed", the selection's bulk bar, or an Undo pressed on the toast. */
|
|
103
108
|
origin: TranslationReviewOrigin;
|
|
104
|
-
/** The panel shows its own toast for this write (an approval's Undo toast
|
|
109
|
+
/** The panel shows its own toast for this write (an approval's Undo toast, or a
|
|
110
|
+
* swiped `clear`'s). */
|
|
105
111
|
toasted: boolean;
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -181,8 +187,31 @@ export interface TranslationReviewPanelProps {
|
|
|
181
187
|
*
|
|
182
188
|
* Off by default, so no existing page gains a gesture on the bump. Phones only; the
|
|
183
189
|
* desktop table keeps its approve button.
|
|
190
|
+
*
|
|
191
|
+
* A BINDING instead of `true` (keksdose — Marcel's live #377 rework, 2026-10-03: *"Add
|
|
192
|
+
* the swipe options to be shown also to the settings /settings#interaction area where
|
|
193
|
+
* the other swipe options are defined"*): keksdose lets the user bind every list's
|
|
194
|
+
* swipes in Settings → Interaction, per side a primary action at the first threshold and
|
|
195
|
+
* a secondary one at a longer drag, and a fixed mapping is one its settings cannot
|
|
196
|
+
* reach. So each logical side takes an ordered ladder of
|
|
197
|
+
* {@link TranslationReviewSwipeAction}s — index 0 at the first threshold, index 1 at the
|
|
198
|
+
* longer drag, as {@link SwipeableRow} stages them:
|
|
199
|
+
*
|
|
200
|
+
* swipe={{ end: ["approve", "clear"], start: ["edit"] }}
|
|
201
|
+
*
|
|
202
|
+
* `approve` (with Undo), `edit` (the editor, cursor in the wording — "Needs a change"),
|
|
203
|
+
* `clear` (the verdict taken back, with Undo, which stores it again; only with
|
|
204
|
+
* `onClear`). An action a row cannot offer drops out and the ladder closes up behind it
|
|
205
|
+
* ({@link translationReviewSwipePlan}): `approve` on an approved or missing string,
|
|
206
|
+
* `clear` on a row with no verdict. Every rule above still holds for every binding —
|
|
207
|
+
* no side swipes in a `readOnly` locale, under a write lock, with the editor open or
|
|
208
|
+
* the row's write out.
|
|
209
|
+
*
|
|
210
|
+
* `true` is {@link DEFAULT_TRANSLATION_REVIEW_SWIPE} — `{ end: ["approve"], start:
|
|
211
|
+
* ["edit"] }`, 0.25's mapping exactly — and `false` or nothing is no swipe. A binding
|
|
212
|
+
* turns the Undo toast on by default, as `true` does.
|
|
184
213
|
*/
|
|
185
|
-
swipe?: boolean;
|
|
214
|
+
swipe?: boolean | TranslationReviewSwipeBinding;
|
|
186
215
|
/**
|
|
187
216
|
* Answer every approval with the kit's Undo toast (`toast.undo`): a row's button, a
|
|
188
217
|
* swipe, the editor's Approve, a group's and the selection's bulk approve. Undo stores
|
|
@@ -435,7 +464,9 @@ export function TranslationReviewPanel({
|
|
|
435
464
|
const baseId = useId();
|
|
436
465
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
437
466
|
const editable = !readOnly && onSave !== undefined;
|
|
438
|
-
const
|
|
467
|
+
const binding: TranslationReviewSwipeBinding | null =
|
|
468
|
+
swipe === true ? DEFAULT_TRANSLATION_REVIEW_SWIPE : swipe || null;
|
|
469
|
+
const undoOn = undoProp ?? (binding !== null || groupBy !== undefined);
|
|
439
470
|
|
|
440
471
|
const [ownFilter, setOwnFilter] = useState<TranslationReviewFilter>(DEFAULT_TRANSLATION_REVIEW_FILTER);
|
|
441
472
|
// The app's fields over the panel's own; a field the app leaves `undefined` is the
|
|
@@ -546,10 +577,12 @@ export function TranslationReviewPanel({
|
|
|
546
577
|
};
|
|
547
578
|
|
|
548
579
|
/** "String approved — Undo", once `targets` (as they were BEFORE the write) are
|
|
549
|
-
* approved
|
|
550
|
-
|
|
580
|
+
* approved — or, after a swiped `clear`, "String marked unreviewed — Undo". Either way
|
|
581
|
+
* Undo puts back what the rows said before ({@link reviewUndo}), through the same queue
|
|
582
|
+
* as every write. */
|
|
583
|
+
const sayDone = (targets: readonly TranslationRow[], description?: ReactNode, cleared = false) => {
|
|
551
584
|
const back = reviewUndo(targets);
|
|
552
|
-
const message = labels.approvedToast(targets.length);
|
|
585
|
+
const message = (cleared ? labels.clearedToast : labels.approvedToast)(targets.length);
|
|
553
586
|
const save = onSave;
|
|
554
587
|
const clear = onClear;
|
|
555
588
|
// Taking back the approval of a row that had no verdict is a clear: without
|
|
@@ -578,16 +611,27 @@ export function TranslationReviewPanel({
|
|
|
578
611
|
if (!onSave || targets.length === 0) return false;
|
|
579
612
|
const save = onSave;
|
|
580
613
|
const ok = await run(key, () => save(targets.map((r) => reviewWrite(r, "APPROVED")), { origin, toasted: undoOn }));
|
|
581
|
-
if (ok && undoOn)
|
|
614
|
+
if (ok && undoOn) sayDone(targets, description);
|
|
582
615
|
return ok;
|
|
583
616
|
};
|
|
584
617
|
|
|
618
|
+
/** A swipe bound to `clear`: the row's verdict taken back, answered like an approval —
|
|
619
|
+
* the Undo toast stores the verdict again. Under the row's own key, so it queues behind
|
|
620
|
+
* (and never overlaps) an approval of the same row. */
|
|
621
|
+
const clearRow = async (row: TranslationRow) => {
|
|
622
|
+
if (!onClear || row.review === null) return;
|
|
623
|
+
const clear = onClear;
|
|
624
|
+
const keys = [{ locale: row.locale, key: row.key }];
|
|
625
|
+
const ok = await run(`row:${row.id}`, () => clear(keys, { origin: "swipe", toasted: undoOn }));
|
|
626
|
+
if (ok && undoOn) sayDone([row], row.key, true);
|
|
627
|
+
};
|
|
628
|
+
|
|
585
629
|
/** The editor's own save: it waits, closes and shows its errors itself, so it does
|
|
586
630
|
* not go through the queue — but an approval there is answered like any other. */
|
|
587
631
|
const saveFromEditor = async (row: TranslationRow, write: TranslationReviewWrite) => {
|
|
588
632
|
const toasted = undoOn && write.verdict === "APPROVED";
|
|
589
633
|
await onSave?.([write], { origin: "editor", toasted });
|
|
590
|
-
if (toasted)
|
|
634
|
+
if (toasted) sayDone([row], row.key);
|
|
591
635
|
};
|
|
592
636
|
|
|
593
637
|
/**
|
|
@@ -713,29 +757,22 @@ export function TranslationReviewPanel({
|
|
|
713
757
|
});
|
|
714
758
|
}
|
|
715
759
|
|
|
716
|
-
/**
|
|
717
|
-
|
|
718
|
-
* None where the row must not be acted on — the same conditions under which its
|
|
719
|
-
* buttons are locked or absent.
|
|
720
|
-
*/
|
|
721
|
-
const swipeActions = (r: TranslationRow, groupKey?: string): MobileSwipeActions | null => {
|
|
722
|
-
if (!editable || lock.locked || isQueued(`row:${r.id}`)) return null;
|
|
760
|
+
/** One bound swipe action on one row, as {@link SwipeableRow} draws and commits it. */
|
|
761
|
+
const swipeAction = (r: TranslationRow, id: TranslationReviewSwipeAction, groupKey?: string): SwipeAction => {
|
|
723
762
|
const missing = r.text === "";
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
:
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
start: [
|
|
738
|
-
{
|
|
763
|
+
switch (id) {
|
|
764
|
+
case "approve":
|
|
765
|
+
return {
|
|
766
|
+
label: labels.approve,
|
|
767
|
+
icon: <Check className="size-4" aria-hidden />,
|
|
768
|
+
onCommit: () => void approve(`row:${r.id}`, [r], "swipe", r.key),
|
|
769
|
+
// Text on the fill in its own contrast colour: the fills are pastels in dark
|
|
770
|
+
// mode, where SwipeableRow's default white text was unreadable (0.26).
|
|
771
|
+
className: "bg-[var(--success)] text-[var(--success-contrast)]",
|
|
772
|
+
armedClassName: "bg-[var(--success)] text-[var(--success-contrast)]",
|
|
773
|
+
};
|
|
774
|
+
case "edit":
|
|
775
|
+
return {
|
|
739
776
|
label: missing ? labels.suggest : labels.flag,
|
|
740
777
|
icon: <PencilLine className="size-4" aria-hidden />,
|
|
741
778
|
// Opens the editor rather than sending the string back: "needs a change"
|
|
@@ -745,10 +782,40 @@ export function TranslationReviewPanel({
|
|
|
745
782
|
setOpenId(r.id);
|
|
746
783
|
setFocusId(r.id);
|
|
747
784
|
},
|
|
748
|
-
className: missing
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
785
|
+
className: missing
|
|
786
|
+
? "bg-[var(--brand)] text-[var(--brand-contrast)]"
|
|
787
|
+
: "bg-[var(--danger)] text-[var(--danger-contrast)]",
|
|
788
|
+
armedClassName: missing
|
|
789
|
+
? "bg-[var(--brand-hover)] text-[var(--brand-contrast)]"
|
|
790
|
+
: "bg-[var(--danger-hover)] text-[var(--danger-contrast)]",
|
|
791
|
+
};
|
|
792
|
+
case "clear":
|
|
793
|
+
return {
|
|
794
|
+
label: labels.reset,
|
|
795
|
+
icon: <RotateCcw className="size-4" aria-hidden />,
|
|
796
|
+
onCommit: () => void clearRow(r),
|
|
797
|
+
// A step back, not a verdict: neither the approval's green nor the send-back's red.
|
|
798
|
+
// The surface colour as text: white on the grey in light mode, the dark page on
|
|
799
|
+
// the light grey in dark mode.
|
|
800
|
+
className: "bg-[var(--text-muted)] text-[var(--bg-surface)]",
|
|
801
|
+
armedClassName: "bg-[var(--text-secondary)] text-[var(--bg-surface)]",
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* A phone card's swipes: the binding ({@link TranslationReviewPanelProps.swipe}) resolved
|
|
808
|
+
* against the row, each side's ladder closed up over what the row cannot offer. None
|
|
809
|
+
* where the row must not be acted on — the same conditions under which its buttons are
|
|
810
|
+
* locked or absent.
|
|
811
|
+
*/
|
|
812
|
+
const swipeActions = (r: TranslationRow, groupKey?: string): MobileSwipeActions | null => {
|
|
813
|
+
if (!binding || !editable || lock.locked || isQueued(`row:${r.id}`)) return null;
|
|
814
|
+
const plan = translationReviewSwipePlan(binding, r, { canClear: onClear !== undefined });
|
|
815
|
+
if (plan.start.length === 0 && plan.end.length === 0) return null;
|
|
816
|
+
return {
|
|
817
|
+
start: plan.start.map((id) => swipeAction(r, id, groupKey)),
|
|
818
|
+
end: plan.end.map((id) => swipeAction(r, id, groupKey)),
|
|
752
819
|
};
|
|
753
820
|
};
|
|
754
821
|
|
|
@@ -834,7 +901,7 @@ export function TranslationReviewPanel({
|
|
|
834
901
|
</div>
|
|
835
902
|
</div>
|
|
836
903
|
)}
|
|
837
|
-
mobileSwipeActions={
|
|
904
|
+
mobileSwipeActions={binding ? (r) => swipeActions(r, group?.key) : undefined}
|
|
838
905
|
selection={selectionFor(list)}
|
|
839
906
|
/>
|
|
840
907
|
);
|
|
@@ -65,16 +65,32 @@ export interface FeedbackAttachmentFieldLabels {
|
|
|
65
65
|
attachmentUploadFailed?: (name: string) => string;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* English, in the feedback contract's wording since 0.27.0 (docs/feedback-harmonization.md
|
|
70
|
+
* §4.2: keksdose's `feedback.*` keys are the canon, and the kit owns them now). Three
|
|
71
|
+
* strings moved with it:
|
|
72
|
+
*
|
|
73
|
+
* - `attachmentAdd` "Attach image" → **"Add attachment"**: the field takes a PDF or a text
|
|
74
|
+
* log as well wherever the app passes `FEEDBACK_ATTACHMENT_ACCEPT` (keksdose feedback
|
|
75
|
+
* #87), and a button that says "image" over a picker that offers a PDF is wrong in one
|
|
76
|
+
* of the two places.
|
|
77
|
+
* - `attachmentPaste` → keksdose's longer line, which says WHY one would paste — a region
|
|
78
|
+
* snip shows one corner rather than the whole page (Steering Design feedback #39).
|
|
79
|
+
* - `attachmentLimit` for one: "remove **it**", as keksdose's `attachments_full_one`;
|
|
80
|
+
* "remove one" of a single file read oddly.
|
|
81
|
+
*/
|
|
68
82
|
export const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels = {
|
|
69
|
-
attachmentAdd: "
|
|
83
|
+
attachmentAdd: "Add attachment",
|
|
70
84
|
attachmentCapture: "Capture screenshot",
|
|
71
|
-
attachmentPaste: "…or paste a screenshot from the clipboard.",
|
|
85
|
+
attachmentPaste: "…or paste a screenshot straight from the clipboard, so you can show one corner rather than the whole page.",
|
|
72
86
|
attachmentRemove: "Remove attachment",
|
|
73
87
|
attachmentList: "Attachments",
|
|
74
88
|
attachmentScreenshot: "Screenshot",
|
|
75
89
|
attachmentRemoveFile: (name) => `Remove ${name}`,
|
|
76
90
|
attachmentLimit: (max) =>
|
|
77
|
-
|
|
91
|
+
max === 1
|
|
92
|
+
? "Up to 1 attachment — remove it to add another."
|
|
93
|
+
: `Up to ${max} attachments — remove one to add another.`,
|
|
78
94
|
attachmentUploading: "Uploading…",
|
|
79
95
|
attachmentUploadFailed: (name) => `“${name}” could not be uploaded`,
|
|
80
96
|
};
|