@eifi1/ui-kit 0.17.0 → 0.19.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 +83 -4
- package/dist/chart.d.ts +13 -1
- package/dist/components/account-chips.d.ts +153 -0
- package/dist/components/account-chips.js +135 -0
- package/dist/components/account-chips.js.map +1 -0
- package/dist/components/account-settings-labels.d.ts +10 -2
- package/dist/components/account-settings-labels.js +3 -0
- package/dist/components/account-settings-labels.js.map +1 -1
- package/dist/components/amount-input.d.ts +13 -1
- package/dist/components/button-group.d.ts +13 -1
- package/dist/components/calculator.d.ts +13 -1
- package/dist/components/chip.d.ts +25 -1
- package/dist/components/chip.js +143 -6
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +42 -1
- package/dist/components/confirm-dialog.js +36 -5
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +13 -1
- package/dist/components/danger-confirm.d.ts +55 -4
- package/dist/components/danger-confirm.js +67 -30
- package/dist/components/danger-confirm.js.map +1 -1
- 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.d.ts +1 -1
- package/dist/components/data-table.js +2 -0
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/facing-pair.d.ts +13 -1
- package/dist/components/file-button.d.ts +13 -1
- package/dist/components/file-dropzone.d.ts +13 -1
- package/dist/components/floating-panel.d.ts +4 -1
- package/dist/components/floating-panel.js +15 -1
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +13 -1
- package/dist/components/form-actions.js +3 -0
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/legal.d.ts +84 -0
- package/dist/components/legal.js +55 -0
- package/dist/components/legal.js.map +1 -0
- package/dist/components/money-field.d.ts +13 -1
- package/dist/components/number-field.d.ts +14 -2
- package/dist/components/number-field.js +23 -22
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +13 -1
- package/dist/components/numpad-sheet.d.ts +13 -1
- package/dist/components/passkeys-setting.d.ts +27 -1
- package/dist/components/passkeys-setting.js +31 -5
- package/dist/components/passkeys-setting.js.map +1 -1
- package/dist/components/progress-bar.d.ts +15 -1
- package/dist/components/progress-bar.js +8 -1
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.d.ts +90 -0
- package/dist/components/reauth-dialog.js +158 -0
- package/dist/components/reauth-dialog.js.map +1 -0
- package/dist/components/series-chart.d.ts +13 -1
- package/dist/components/server-wake.d.ts +60 -0
- package/dist/components/server-wake.js +79 -0
- package/dist/components/server-wake.js.map +1 -0
- package/dist/components/settings-fields.d.ts +13 -1
- package/dist/components/share-card.d.ts +64 -0
- package/dist/components/share-card.js +350 -0
- package/dist/components/share-card.js.map +1 -0
- package/dist/components/status-dot.d.ts +2 -0
- package/dist/components/text-link.d.ts +13 -1
- package/dist/components/time-input.d.ts +13 -1
- package/dist/components/toast.d.ts +10 -1
- package/dist/components/toast.js +42 -2
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle-group.js +5 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +56 -0
- package/dist/components/translation-review-editor.js +159 -0
- package/dist/components/translation-review-editor.js.map +1 -0
- package/dist/components/translation-review-labels.d.ts +92 -0
- package/dist/components/translation-review-labels.js +67 -0
- package/dist/components/translation-review-labels.js.map +1 -0
- package/dist/components/translation-review-parts.d.ts +97 -0
- package/dist/components/translation-review-parts.js +163 -0
- package/dist/components/translation-review-parts.js.map +1 -0
- package/dist/components/translation-review.d.ts +94 -0
- package/dist/components/translation-review.js +344 -0
- package/dist/components/translation-review.js.map +1 -0
- package/dist/components/ui.d.ts +13 -1
- package/dist/components/ui.js +43 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/user-avatar.d.ts +2 -0
- package/dist/components/write-lock.d.ts +68 -0
- package/dist/components/write-lock.js +33 -0
- package/dist/components/write-lock.js.map +1 -0
- package/dist/{data-table-CpkWUJBY.d.ts → data-table-3mi2R3so.d.ts} +19 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/hooks/use-file-drop.d.ts +13 -1
- package/dist/hooks/use-overlay-history.js +16 -0
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.d.ts +13 -1
- package/dist/i18n/defaults.js +15 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +81 -0
- package/dist/i18n/{locales/de.js → german.js} +110 -9
- package/dist/i18n/german.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +13 -1
- package/dist/i18n/kit-labels.js +5 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +243 -0
- package/dist/i18n/languages.js +70 -0
- package/dist/i18n/languages.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +26 -8
- package/dist/i18n/locales/de-CH.js +7 -3
- package/dist/i18n/locales/de-CH.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +13 -1
- package/dist/i18n/locales/en.js +8 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +13 -1
- package/dist/i18n/locales/es.js +127 -27
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +13 -1
- package/dist/i18n/locales/fr.js +101 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +13 -1
- package/dist/i18n/locales/hu.js +105 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +13 -1
- package/dist/i18n/locales/it.js +134 -34
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +13 -1
- package/dist/i18n/locales/zh.js +101 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +102 -0
- package/dist/i18n/review.js +158 -0
- package/dist/i18n/review.js.map +1 -0
- package/dist/i18n/swiss.d.ts +3 -3
- package/dist/i18n/swiss.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +97 -1
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-C4uB_zmg.d.ts → kit-labels-D1J2_jyD.d.ts} +236 -9
- package/dist/lib/floating-stack.d.ts +31 -0
- package/dist/lib/floating-stack.js +42 -0
- package/dist/lib/floating-stack.js.map +1 -0
- package/dist/lib/server-wake.d.ts +146 -0
- package/dist/lib/server-wake.js +122 -0
- package/dist/lib/server-wake.js.map +1 -0
- package/dist/lib/translation-review.d.ts +232 -0
- package/dist/lib/translation-review.js +194 -0
- package/dist/lib/translation-review.js.map +1 -0
- package/dist/rhf/fields.d.ts +13 -1
- package/dist/rhf/form.d.ts +13 -1
- package/dist/rhf.d.ts +13 -1
- package/dist/search/global-search.d.ts +3 -1
- package/dist/search/global-search.js.map +1 -1
- package/dist/shell/app-shell.d.ts +13 -1
- package/dist/shell/top-bar-brand.d.ts +13 -1
- package/dist/shell/topbar-action-menu.d.ts +2 -0
- package/dist/shell.d.ts +13 -1
- package/dist/wizard/stepper-nav.d.ts +13 -1
- package/dist/wizard.d.ts +13 -1
- package/package.json +1 -1
- package/src/components/account-chips.tsx +302 -0
- package/src/components/account-settings-labels.ts +16 -2
- package/src/components/chip.tsx +239 -6
- package/src/components/confirm-dialog.tsx +82 -7
- package/src/components/danger-confirm.tsx +118 -33
- package/src/components/data-table.tsx +20 -0
- package/src/components/floating-panel.tsx +19 -1
- package/src/components/form-actions.tsx +22 -3
- package/src/components/legal.tsx +159 -0
- package/src/components/number-field.tsx +32 -26
- package/src/components/passkeys-setting.tsx +68 -7
- package/src/components/progress-bar.tsx +21 -1
- package/src/components/reauth-dialog.tsx +273 -0
- package/src/components/server-wake.tsx +156 -0
- package/src/components/share-card.tsx +615 -0
- package/src/components/toast.tsx +72 -2
- package/src/components/toggle-group.tsx +5 -2
- package/src/components/translation-review-editor.tsx +261 -0
- package/src/components/translation-review-labels.ts +152 -0
- package/src/components/translation-review-parts.tsx +269 -0
- package/src/components/translation-review.tsx +502 -0
- package/src/components/ui.tsx +89 -32
- package/src/components/write-lock.tsx +96 -0
- package/src/hooks/use-overlay-history.ts +51 -0
- package/src/i18n/defaults.ts +14 -0
- package/src/i18n/{locales/de.ts → german.ts} +120 -13
- package/src/i18n/kit-labels.tsx +29 -2
- package/src/i18n/languages.ts +285 -0
- package/src/i18n/locales/de-CH.ts +17 -8
- package/src/i18n/locales/en.ts +8 -0
- package/src/i18n/locales/es.ts +128 -26
- package/src/i18n/locales/fr.ts +103 -0
- package/src/i18n/locales/hu.ts +107 -0
- package/src/i18n/locales/it.ts +135 -33
- package/src/i18n/locales/zh.ts +102 -0
- package/src/i18n/review.ts +193 -0
- package/src/i18n/swiss.ts +3 -3
- package/src/index.ts +86 -1
- package/src/lib/floating-stack.ts +66 -0
- package/src/lib/server-wake.ts +291 -0
- package/src/lib/translation-review.ts +465 -0
- package/src/search/global-search.tsx +3 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +0 -61
- package/dist/i18n/locales/de-CH-informal.js +0 -8
- package/dist/i18n/locales/de-CH-informal.js.map +0 -1
- package/dist/i18n/locales/de-informal.d.ts +0 -82
- package/dist/i18n/locales/de-informal.js +0 -37
- package/dist/i18n/locales/de-informal.js.map +0 -1
- package/dist/i18n/locales/de.d.ts +0 -66
- package/dist/i18n/locales/de.js.map +0 -1
- package/src/i18n/locales/de-CH-informal.ts +0 -10
- package/src/i18n/locales/de-informal.ts +0 -61
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { Check, ListChecks, RotateCcw } from "lucide-react";
|
|
5
|
+
import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_TRANSLATION_REVIEW_FILTER,
|
|
9
|
+
REVIEW_STATUSES,
|
|
10
|
+
filterTranslationRows,
|
|
11
|
+
keyInAreas,
|
|
12
|
+
reviewWrite,
|
|
13
|
+
summariseRows
|
|
14
|
+
} from "../lib/translation-review.js";
|
|
15
|
+
import { AlertBanner } from "./alert-banner.js";
|
|
16
|
+
import { BulkActionBar } from "./bulk-action-bar.js";
|
|
17
|
+
import { Checkbox } from "./checkbox.js";
|
|
18
|
+
import { Chip } from "./chip.js";
|
|
19
|
+
import { DataTable } from "./data-table.js";
|
|
20
|
+
import { SearchField } from "./search-field.js";
|
|
21
|
+
import { ToggleGroup } from "./toggle-group.js";
|
|
22
|
+
import { TranslationReviewEditor } from "./translation-review-editor.js";
|
|
23
|
+
import { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from "./translation-review-labels.js";
|
|
24
|
+
import { ReviewStatusChip, TranslationProgress } from "./translation-review-parts.js";
|
|
25
|
+
import { Button, IconButton, Select } from "./ui.js";
|
|
26
|
+
import {
|
|
27
|
+
REVIEW_STATUS_TONES,
|
|
28
|
+
ReviewStatusChip as ReviewStatusChip2,
|
|
29
|
+
TranslationExportButton,
|
|
30
|
+
TranslationLocaleTabs,
|
|
31
|
+
TranslationProgress as TranslationProgress2
|
|
32
|
+
} from "./translation-review-parts.js";
|
|
33
|
+
import { TranslationReviewEditor as TranslationReviewEditor2 } from "./translation-review-editor.js";
|
|
34
|
+
import { DEFAULT_TRANSLATION_REVIEW_LABELS as DEFAULT_TRANSLATION_REVIEW_LABELS2 } from "./translation-review-labels.js";
|
|
35
|
+
function TextCell({ row, labels, clamp }) {
|
|
36
|
+
return row.text === "" ? /* @__PURE__ */ jsx("span", { className: "italic text-[var(--text-muted)]", children: labels.missingText }) : /* @__PURE__ */ jsx("span", { lang: row.locale, className: cn("whitespace-pre-line break-words", clamp && "line-clamp-3"), children: row.text });
|
|
37
|
+
}
|
|
38
|
+
function TranslationReviewPanel({
|
|
39
|
+
rows: rowsProp,
|
|
40
|
+
localeLabel,
|
|
41
|
+
referenceLabel,
|
|
42
|
+
onSave,
|
|
43
|
+
onClear,
|
|
44
|
+
readOnly = false,
|
|
45
|
+
areas,
|
|
46
|
+
areaLabels,
|
|
47
|
+
inArea,
|
|
48
|
+
sourceLabels,
|
|
49
|
+
filter: filterProp,
|
|
50
|
+
onFilterChange,
|
|
51
|
+
progress = true,
|
|
52
|
+
pageSize = 50,
|
|
53
|
+
storageKey,
|
|
54
|
+
formatDate,
|
|
55
|
+
formatError,
|
|
56
|
+
className,
|
|
57
|
+
labels: labelsProp
|
|
58
|
+
}) {
|
|
59
|
+
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
60
|
+
const locale = useKitLocale();
|
|
61
|
+
const editable = !readOnly && onSave !== void 0;
|
|
62
|
+
const [ownFilter, setOwnFilter] = useState(DEFAULT_TRANSLATION_REVIEW_FILTER);
|
|
63
|
+
const filter = { ...ownFilter };
|
|
64
|
+
for (const [name, value] of Object.entries(filterProp ?? {})) {
|
|
65
|
+
if (value !== void 0) Object.assign(filter, { [name]: value });
|
|
66
|
+
}
|
|
67
|
+
const [openId, setOpenId] = useState(null);
|
|
68
|
+
const [selected, setSelected] = useState(() => /* @__PURE__ */ new Set());
|
|
69
|
+
const [busy, setBusy] = useState(null);
|
|
70
|
+
const [failure, setFailure] = useState(null);
|
|
71
|
+
const rows = useMemo(
|
|
72
|
+
() => areas ? rowsProp.filter((row) => keyInAreas(row.key, areas, inArea)) : [...rowsProp],
|
|
73
|
+
[rowsProp, areas, inArea]
|
|
74
|
+
);
|
|
75
|
+
const summary = useMemo(() => summariseRows(rows), [rows]);
|
|
76
|
+
const sources = useMemo(() => [...new Set(rows.map((r) => r.source))], [rows]);
|
|
77
|
+
const source = sources.includes(filter.source) ? filter.source : "all";
|
|
78
|
+
const namespaces = useMemo(
|
|
79
|
+
() => [...new Set(rows.filter((r) => source === "all" || r.source === source).map((r) => r.namespace))],
|
|
80
|
+
[rows, source]
|
|
81
|
+
);
|
|
82
|
+
const namespace = namespaces.includes(filter.namespace) ? filter.namespace : "all";
|
|
83
|
+
const mismatches = useMemo(() => rows.filter((r) => r.placeholderMismatch).length, [rows]);
|
|
84
|
+
const { status, query, placeholdersOnly } = filter;
|
|
85
|
+
const visible = useMemo(
|
|
86
|
+
() => filterTranslationRows(rows, { status, source, namespace, query, placeholdersOnly }),
|
|
87
|
+
[rows, status, source, namespace, query, placeholdersOnly]
|
|
88
|
+
);
|
|
89
|
+
const setFilter = (patch) => {
|
|
90
|
+
const next = { ...filter, ...patch };
|
|
91
|
+
setOwnFilter(next);
|
|
92
|
+
onFilterChange?.(next);
|
|
93
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
94
|
+
};
|
|
95
|
+
const run = async (key, action) => {
|
|
96
|
+
setBusy(key);
|
|
97
|
+
setFailure(null);
|
|
98
|
+
try {
|
|
99
|
+
await action();
|
|
100
|
+
return true;
|
|
101
|
+
} catch (caught) {
|
|
102
|
+
setFailure(formatError ? formatError(caught) : labels.failed);
|
|
103
|
+
return false;
|
|
104
|
+
} finally {
|
|
105
|
+
setBusy(null);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const visibleIds = new Set(visible.map((r) => r.id));
|
|
109
|
+
const selectedRows = visible.filter((r) => selected.has(r.id));
|
|
110
|
+
const allSelected = visible.length > 0 && selectedRows.length === visible.length;
|
|
111
|
+
const toApprove = selectedRows.filter((r) => r.text !== "" && r.status !== "approved");
|
|
112
|
+
const toReset = selectedRows.filter((r) => r.review !== null);
|
|
113
|
+
const approveSelected = async () => {
|
|
114
|
+
if (!onSave || toApprove.length === 0) return;
|
|
115
|
+
if (await run("bulk-approve", () => onSave(toApprove.map((r) => reviewWrite(r, "APPROVED"))))) {
|
|
116
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const resetSelected = async () => {
|
|
120
|
+
if (!onClear || toReset.length === 0) return;
|
|
121
|
+
if (await run("bulk-reset", () => onClear(toReset.map((r) => ({ locale: r.locale, key: r.key }))))) {
|
|
122
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const statuses = REVIEW_STATUSES.filter((s) => s !== "missing" || summary.missing > 0 || filter.status === s);
|
|
126
|
+
const scopeNames = (areas ?? []).map((area) => areaLabels?.[area] ?? area);
|
|
127
|
+
const scopeText = scopeNames.length > 0 ? new Intl.ListFormat(locale, { type: "conjunction", style: "long" }).format(scopeNames) : "";
|
|
128
|
+
const columns = [
|
|
129
|
+
{
|
|
130
|
+
key: "key",
|
|
131
|
+
header: labels.key,
|
|
132
|
+
cell: (r) => /* @__PURE__ */ jsx("code", { className: "break-all text-xs text-[var(--text-secondary)]", children: r.key }),
|
|
133
|
+
sortBy: (r) => r.key,
|
|
134
|
+
mobileHidden: true
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
key: "reference",
|
|
138
|
+
header: referenceLabel,
|
|
139
|
+
cell: (r) => /* @__PURE__ */ jsx("span", { className: "line-clamp-3 whitespace-pre-line break-words text-[var(--text-secondary)]", children: r.reference }),
|
|
140
|
+
mobileHidden: true
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
key: "text",
|
|
144
|
+
header: localeLabel,
|
|
145
|
+
cell: (r) => /* @__PURE__ */ jsx(TextCell, { row: r, labels, clamp: true }),
|
|
146
|
+
mobilePrimary: true
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: "status",
|
|
150
|
+
header: labels.statusColumn,
|
|
151
|
+
cell: (r) => /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-wrap gap-1", children: [
|
|
152
|
+
/* @__PURE__ */ jsx(ReviewStatusChip, { status: r.status, labels: labelsProp }),
|
|
153
|
+
r.placeholderMismatch && /* @__PURE__ */ jsx(Chip, { size: "sm", tone: "danger", variant: "outline", children: labels.placeholderChip })
|
|
154
|
+
] }),
|
|
155
|
+
sortBy: (r) => REVIEW_STATUSES.indexOf(r.status)
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
if (editable && onSave) {
|
|
159
|
+
const save = onSave;
|
|
160
|
+
columns.push({
|
|
161
|
+
key: "approve",
|
|
162
|
+
header: /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.approve }),
|
|
163
|
+
cell: (r) => r.status === "approved" || r.text === "" ? null : /* @__PURE__ */ jsx(
|
|
164
|
+
IconButton,
|
|
165
|
+
{
|
|
166
|
+
type: "button",
|
|
167
|
+
size: "xs",
|
|
168
|
+
tone: "muted",
|
|
169
|
+
commit: true,
|
|
170
|
+
label: labels.approve,
|
|
171
|
+
pending: busy === `row:${r.id}`,
|
|
172
|
+
disabled: busy !== null,
|
|
173
|
+
onClick: (event) => {
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
void run(`row:${r.id}`, () => save([reviewWrite(r, "APPROVED")]));
|
|
176
|
+
},
|
|
177
|
+
children: /* @__PURE__ */ jsx(Check, {})
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0 space-y-4", className), children: [
|
|
183
|
+
scopeText && /* @__PURE__ */ jsx(AlertBanner, { tone: "info", variant: "inline", size: "sm", block: true, live: false, children: labels.scope(scopeText) }),
|
|
184
|
+
readOnly && /* @__PURE__ */ jsx(AlertBanner, { tone: "info", variant: "inline", size: "sm", block: true, live: false, children: labels.readOnly }),
|
|
185
|
+
progress && /* @__PURE__ */ jsx(TranslationProgress, { summary, labels: labelsProp }),
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
ToggleGroup,
|
|
188
|
+
{
|
|
189
|
+
"aria-label": labels.statusFilter,
|
|
190
|
+
overflow: "wrap",
|
|
191
|
+
size: "sm",
|
|
192
|
+
value: filter.status,
|
|
193
|
+
onChange: (next) => setFilter({ status: next }),
|
|
194
|
+
options: [
|
|
195
|
+
{ value: "all", label: labels.filterCount(labels.all, summary.total) },
|
|
196
|
+
...statuses.map((s) => ({ value: s, label: labels.filterCount(reviewStatusLabel(labels, s), summary[s]) }))
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
),
|
|
200
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:flex-wrap md:items-end", children: [
|
|
201
|
+
sources.length > 1 && /* @__PURE__ */ jsx("div", { className: "md:w-48", children: /* @__PURE__ */ jsxs(Select, { label: labels.source, value: source, onChange: (e) => setFilter({ source: e.target.value }), children: [
|
|
202
|
+
/* @__PURE__ */ jsx("option", { value: "all", children: labels.allSources }),
|
|
203
|
+
sources.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: sourceLabels?.[s] ?? s }, s))
|
|
204
|
+
] }) }),
|
|
205
|
+
/* @__PURE__ */ jsx("div", { className: "md:w-56", children: /* @__PURE__ */ jsxs(Select, { label: labels.namespace, value: namespace, onChange: (e) => setFilter({ namespace: e.target.value }), children: [
|
|
206
|
+
/* @__PURE__ */ jsx("option", { value: "all", children: labels.allNamespaces }),
|
|
207
|
+
namespaces.map((ns) => /* @__PURE__ */ jsx("option", { value: ns, children: ns }, ns))
|
|
208
|
+
] }) }),
|
|
209
|
+
/* @__PURE__ */ jsx(
|
|
210
|
+
SearchField,
|
|
211
|
+
{
|
|
212
|
+
className: "min-w-0 md:flex-1",
|
|
213
|
+
label: labels.search,
|
|
214
|
+
value: filter.query,
|
|
215
|
+
onChange: (next) => setFilter({ query: next })
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ jsx(
|
|
219
|
+
Checkbox,
|
|
220
|
+
{
|
|
221
|
+
label: labels.placeholdersOnly(mismatches),
|
|
222
|
+
checked: filter.placeholdersOnly,
|
|
223
|
+
onCheckedChange: (next) => setFilter({ placeholdersOnly: next })
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
] }),
|
|
227
|
+
editable && visible.length > 0 && // The phone's table has no checkboxes; this is its way into the bulk actions.
|
|
228
|
+
/* @__PURE__ */ jsxs(
|
|
229
|
+
Button,
|
|
230
|
+
{
|
|
231
|
+
variant: "ghost",
|
|
232
|
+
size: "sm",
|
|
233
|
+
className: "md:hidden",
|
|
234
|
+
disabled: allSelected,
|
|
235
|
+
onClick: () => setSelected(new Set(visible.map((r) => r.id))),
|
|
236
|
+
children: [
|
|
237
|
+
/* @__PURE__ */ jsx(ListChecks, { className: "size-4", "aria-hidden": true }),
|
|
238
|
+
labels.selectShown
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
failure && /* @__PURE__ */ jsx(AlertBanner, { tone: "danger", size: "sm", role: "alert", children: failure }),
|
|
243
|
+
editable && /* @__PURE__ */ jsxs(BulkActionBar, { count: selectedRows.length, onClear: () => setSelected(/* @__PURE__ */ new Set()), children: [
|
|
244
|
+
/* @__PURE__ */ jsxs(
|
|
245
|
+
Button,
|
|
246
|
+
{
|
|
247
|
+
variant: "primary",
|
|
248
|
+
size: "sm",
|
|
249
|
+
commit: true,
|
|
250
|
+
pending: busy === "bulk-approve",
|
|
251
|
+
disabled: busy !== null || toApprove.length === 0,
|
|
252
|
+
onClick: () => void approveSelected(),
|
|
253
|
+
children: [
|
|
254
|
+
/* @__PURE__ */ jsx(Check, { className: "size-4", "aria-hidden": true }),
|
|
255
|
+
labels.approveSelected
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
),
|
|
259
|
+
onClear && /* @__PURE__ */ jsxs(
|
|
260
|
+
Button,
|
|
261
|
+
{
|
|
262
|
+
variant: "ghost",
|
|
263
|
+
size: "sm",
|
|
264
|
+
commit: true,
|
|
265
|
+
pending: busy === "bulk-reset",
|
|
266
|
+
disabled: busy !== null || toReset.length === 0,
|
|
267
|
+
onClick: () => void resetSelected(),
|
|
268
|
+
children: [
|
|
269
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "size-4", "aria-hidden": true }),
|
|
270
|
+
labels.resetSelected
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
] }),
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
DataTable,
|
|
277
|
+
{
|
|
278
|
+
rows: visible,
|
|
279
|
+
rowKey: (r) => r.id,
|
|
280
|
+
columns,
|
|
281
|
+
empty: labels.empty,
|
|
282
|
+
defaultPageSize: pageSize,
|
|
283
|
+
storageKey,
|
|
284
|
+
isExpanded: (r) => r.id === openId,
|
|
285
|
+
onRowClick: (r) => setOpenId((open) => open === r.id ? null : r.id),
|
|
286
|
+
expandedRow: (r) => /* @__PURE__ */ jsx(
|
|
287
|
+
TranslationReviewEditor,
|
|
288
|
+
{
|
|
289
|
+
row: r,
|
|
290
|
+
referenceLabel,
|
|
291
|
+
localeLabel,
|
|
292
|
+
readOnly: !editable,
|
|
293
|
+
onSave: onSave ? (write) => onSave([write]) : void 0,
|
|
294
|
+
onClear: onClear ? (key) => onClear([key]) : void 0,
|
|
295
|
+
onClose: () => setOpenId(null),
|
|
296
|
+
formatDate,
|
|
297
|
+
formatError,
|
|
298
|
+
labels: labelsProp
|
|
299
|
+
},
|
|
300
|
+
r.id
|
|
301
|
+
),
|
|
302
|
+
mobileCard: (r) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
|
|
303
|
+
/* @__PURE__ */ jsx("code", { className: "block break-all text-xs text-[var(--text-muted)]", children: r.key }),
|
|
304
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-[var(--text-primary)]", children: /* @__PURE__ */ jsx(TextCell, { row: r, labels, clamp: true }) }),
|
|
305
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
|
|
306
|
+
/* @__PURE__ */ jsx(ReviewStatusChip, { status: r.status, labels: labelsProp }),
|
|
307
|
+
r.placeholderMismatch && /* @__PURE__ */ jsx(Chip, { size: "sm", tone: "danger", variant: "outline", children: labels.placeholderChip })
|
|
308
|
+
] })
|
|
309
|
+
] }),
|
|
310
|
+
selection: editable ? {
|
|
311
|
+
isSelected: (r) => selected.has(r.id),
|
|
312
|
+
onToggle: (r, checked) => setSelected((prev) => {
|
|
313
|
+
const next = new Set([...prev].filter((id) => visibleIds.has(id)));
|
|
314
|
+
if (checked) next.add(r.id);
|
|
315
|
+
else next.delete(r.id);
|
|
316
|
+
return next;
|
|
317
|
+
}),
|
|
318
|
+
onToggleMany: (many, checked) => setSelected((prev) => {
|
|
319
|
+
const next = new Set([...prev].filter((id) => visibleIds.has(id)));
|
|
320
|
+
for (const r of many) {
|
|
321
|
+
if (checked) next.add(r.id);
|
|
322
|
+
else next.delete(r.id);
|
|
323
|
+
}
|
|
324
|
+
return next;
|
|
325
|
+
}),
|
|
326
|
+
allSelected,
|
|
327
|
+
someSelected: selectedRows.length > 0 && !allSelected,
|
|
328
|
+
onToggleAll: (checked) => setSelected(checked ? new Set(visible.map((r) => r.id)) : /* @__PURE__ */ new Set())
|
|
329
|
+
} : void 0
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
] });
|
|
333
|
+
}
|
|
334
|
+
export {
|
|
335
|
+
DEFAULT_TRANSLATION_REVIEW_LABELS2 as DEFAULT_TRANSLATION_REVIEW_LABELS,
|
|
336
|
+
REVIEW_STATUS_TONES,
|
|
337
|
+
ReviewStatusChip2 as ReviewStatusChip,
|
|
338
|
+
TranslationExportButton,
|
|
339
|
+
TranslationLocaleTabs,
|
|
340
|
+
TranslationProgress2 as TranslationProgress,
|
|
341
|
+
TranslationReviewEditor2 as TranslationReviewEditor,
|
|
342
|
+
TranslationReviewPanel
|
|
343
|
+
};
|
|
344
|
+
//# sourceMappingURL=translation-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/translation-review.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Check, ListChecks, RotateCcw } from \"lucide-react\";\n\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport {\n DEFAULT_TRANSLATION_REVIEW_FILTER,\n REVIEW_STATUSES,\n filterTranslationRows,\n keyInAreas,\n reviewWrite,\n summariseRows,\n} from \"../lib/translation-review\";\nimport type {\n ReviewStatus,\n TranslationReviewFilter,\n TranslationReviewKey,\n TranslationReviewWrite,\n TranslationRow,\n} from \"../lib/translation-review\";\nimport { AlertBanner } from \"./alert-banner\";\nimport { BulkActionBar } from \"./bulk-action-bar\";\nimport { Checkbox } from \"./checkbox\";\nimport { Chip } from \"./chip\";\nimport { DataTable } from \"./data-table\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { SearchField } from \"./search-field\";\nimport { ToggleGroup } from \"./toggle-group\";\nimport { TranslationReviewEditor } from \"./translation-review-editor\";\nimport { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from \"./translation-review-labels\";\nimport type { TranslationReviewLabels } from \"./translation-review-labels\";\nimport { ReviewStatusChip, TranslationProgress } from \"./translation-review-parts\";\nimport { Button, IconButton, Select } from \"./ui\";\n\nexport {\n REVIEW_STATUS_TONES,\n ReviewStatusChip,\n TranslationExportButton,\n TranslationLocaleTabs,\n TranslationProgress,\n} from \"./translation-review-parts\";\nexport type {\n ReviewStatusChipProps,\n TranslationExportButtonProps,\n TranslationLocaleTab,\n TranslationLocaleTabsProps,\n TranslationProgressProps,\n} from \"./translation-review-parts\";\nexport { TranslationReviewEditor } from \"./translation-review-editor\";\nexport type { TranslationReviewEditorProps } from \"./translation-review-editor\";\nexport { DEFAULT_TRANSLATION_REVIEW_LABELS } from \"./translation-review-labels\";\nexport type { TranslationReviewLabels } from \"./translation-review-labels\";\n\n/**\n * An app's translation review page, as kit parts (H1, docs/i18n-harmonization.md):\n * keksdose's /translations, which kastlan was about to build a second time. The app keeps\n * what is its own — where the strings come from, which locale is read against which, who\n * may review what, every request — and hands the kit rows (`translationRows`) and two\n * callbacks. Everything a reviewer touches is here.\n *\n * Presentational — no request, no route — and one LOCALE at a time. Its table is the\n * kit's DataTable, so like every DataTable it renders under the app's Router. The page\n * around it puts\n * {@link TranslationLocaleTabs} above it and {@link TranslationExportButton} in its header,\n * because both span every locale while this panel is the one open.\n *\n * Callbacks may return promises, and the panel waits for them: an editor stays open with\n * its text and the error when a save is refused (a 403 for a key outside the reviewer's\n * areas, a network failure), a bulk action keeps its selection. Chunking a long bulk\n * write to the server's batch limit is the app's; the panel sends one call per action.\n */\n\ntype MaybePromise = void | Promise<unknown>;\n\nexport interface TranslationReviewPanelProps {\n /** One locale's rows, from `translationRows`. */\n rows: readonly TranslationRow[];\n /** The reviewed language's own name — the text column's header (\"Français\"). */\n localeLabel: string;\n /** The reference language's own name — the reference column's header. */\n referenceLabel: string;\n /** Store verdicts: one for an editor or a row's approve button, many for a bulk\n * approve. Left out (or `readOnly`): nothing can be changed. */\n onSave?: (writes: TranslationReviewWrite[]) => MaybePromise;\n /** Clear verdicts — back to unreviewed. Left out: no reset. */\n onClear?: (keys: TranslationReviewKey[]) => MaybePromise;\n /** A locale the viewer may read but not review: the rows and their verdicts, no\n * actions, and a line saying so. */\n readOnly?: boolean;\n /**\n * The areas the reviewer is limited to (keksdose's lawyer: `[\"legal\"]`), as the server\n * answers them; `null` is every area. Rows outside are not shown or counted — build the\n * rows with the same `areas` and they never exist — and a notice names the areas.\n */\n areas?: readonly string[] | null;\n /** An area's name for the notice (\"the legal pages (Imprint, …)\"). Default: the key. */\n areaLabels?: Readonly<Record<string, string>>;\n /** Default `keyInArea`: the key is the area or sits under it (`legal.…`, `legal:…`). */\n inArea?: (key: string, area: string) => boolean;\n /** A source's name for the source filter, which appears once the rows come from more\n * than one (kastlan: `{ screen: \"Screen\", documents: \"PDFs and emails\" }`). Default:\n * the source itself. */\n sourceLabels?: Readonly<Record<string, string>>;\n /**\n * Controlled filters — for an app that keeps them in the URL (keksdose's `?status=`,\n * `?area=`, `?q=`). Whatever is left out is the panel's own. Pair with `onFilterChange`.\n */\n filter?: Partial<TranslationReviewFilter>;\n onFilterChange?: (filter: TranslationReviewFilter) => void;\n /** The progress bar over the filters. Default true. */\n progress?: boolean;\n /** Rows per page. Default 50. */\n pageSize?: number;\n /** Persists the table's sort and page size (DataTable's `storageKey`). */\n storageKey?: string;\n /** How a verdict's date reads in the editor. */\n formatDate?: (iso: string) => string;\n /** Turns a callback's rejection into words. Default {@link TranslationReviewLabels.failed}. */\n formatError?: (error: unknown) => ReactNode;\n className?: string;\n labels?: Partial<TranslationReviewLabels>;\n}\n\n/** The row's text — or, for a missing string, the line that says so. */\nfunction TextCell({ row, labels, clamp }: { row: TranslationRow; labels: TranslationReviewLabels; clamp?: boolean }) {\n return row.text === \"\" ? (\n <span className=\"italic text-[var(--text-muted)]\">{labels.missingText}</span>\n ) : (\n <span lang={row.locale} className={cn(\"whitespace-pre-line break-words\", clamp && \"line-clamp-3\")}>\n {row.text}\n </span>\n );\n}\n\n/**\n * The page body for one locale: progress, filters (status, source, namespace, search,\n * placeholder problems), the strings with an editor that unfolds under each, and the\n * bulk actions over a selection.\n *\n * The status filter is the work queue in order — what is not there, what nobody has\n * read, what changed after it was read, what was sent back, what is done — with each\n * count on it. \"Missing\" is offered only where something is.\n *\n * Bulk approve takes the selected rows that are not approved yet and have a text:\n * approving an absent string says nothing, and re-approving one says it again. On a\n * phone the table has no checkboxes, so \"Select all shown\" selects what the filters\n * show — filter to `Unreviewed` in one area, read, select, approve.\n */\nexport function TranslationReviewPanel({\n rows: rowsProp,\n localeLabel,\n referenceLabel,\n onSave,\n onClear,\n readOnly = false,\n areas,\n areaLabels,\n inArea,\n sourceLabels,\n filter: filterProp,\n onFilterChange,\n progress = true,\n pageSize = 50,\n storageKey,\n formatDate,\n formatError,\n className,\n labels: labelsProp,\n}: TranslationReviewPanelProps) {\n const labels = useKitLabels(\"translationReview\", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);\n const locale = useKitLocale();\n const editable = !readOnly && onSave !== undefined;\n\n const [ownFilter, setOwnFilter] = useState<TranslationReviewFilter>(DEFAULT_TRANSLATION_REVIEW_FILTER);\n // The app's fields over the panel's own; a field the app leaves `undefined` is the\n // panel's, not a blank.\n const filter: TranslationReviewFilter = { ...ownFilter };\n for (const [name, value] of Object.entries(filterProp ?? {})) {\n if (value !== undefined) Object.assign(filter, { [name]: value });\n }\n const [openId, setOpenId] = useState<string | null>(null);\n const [selected, setSelected] = useState<ReadonlySet<string>>(() => new Set());\n // One bulk or row action at a time: two writes in flight on one locale's verdicts can\n // answer out of order. The editor keeps its own, for its own buttons.\n const [busy, setBusy] = useState<string | null>(null);\n const [failure, setFailure] = useState<ReactNode>(null);\n\n const rows = useMemo(\n () => (areas ? rowsProp.filter((row) => keyInAreas(row.key, areas, inArea)) : [...rowsProp]),\n [rowsProp, areas, inArea],\n );\n const summary = useMemo(() => summariseRows(rows), [rows]);\n const sources = useMemo(() => [...new Set(rows.map((r) => r.source))], [rows]);\n const source = sources.includes(filter.source) ? filter.source : \"all\";\n const namespaces = useMemo(\n () => [...new Set(rows.filter((r) => source === \"all\" || r.source === source).map((r) => r.namespace))],\n [rows, source],\n );\n const namespace = namespaces.includes(filter.namespace) ? filter.namespace : \"all\";\n const mismatches = useMemo(() => rows.filter((r) => r.placeholderMismatch).length, [rows]);\n // `filter` is rebuilt every render; its fields are what the list depends on.\n const { status, query, placeholdersOnly } = filter;\n const visible = useMemo(\n () => filterTranslationRows(rows, { status, source, namespace, query, placeholdersOnly }),\n [rows, status, source, namespace, query, placeholdersOnly],\n );\n\n const setFilter = (patch: Partial<TranslationReviewFilter>) => {\n const next = { ...filter, ...patch };\n setOwnFilter(next);\n onFilterChange?.(next);\n // A selection the new filter hides would be acted on unseen.\n setSelected(new Set());\n };\n\n const run = async (key: string, action: () => MaybePromise): Promise<boolean> => {\n setBusy(key);\n setFailure(null);\n try {\n await action();\n return true;\n } catch (caught) {\n setFailure(formatError ? formatError(caught) : labels.failed);\n return false;\n } finally {\n setBusy(null);\n }\n };\n\n const visibleIds = new Set(visible.map((r) => r.id));\n const selectedRows = visible.filter((r) => selected.has(r.id));\n const allSelected = visible.length > 0 && selectedRows.length === visible.length;\n const toApprove = selectedRows.filter((r) => r.text !== \"\" && r.status !== \"approved\");\n const toReset = selectedRows.filter((r) => r.review !== null);\n\n const approveSelected = async () => {\n if (!onSave || toApprove.length === 0) return;\n if (await run(\"bulk-approve\", () => onSave(toApprove.map((r) => reviewWrite(r, \"APPROVED\"))))) {\n setSelected(new Set());\n }\n };\n const resetSelected = async () => {\n if (!onClear || toReset.length === 0) return;\n if (await run(\"bulk-reset\", () => onClear(toReset.map((r) => ({ locale: r.locale, key: r.key }))))) {\n setSelected(new Set());\n }\n };\n\n // The status options: \"missing\" only where something is (or it is the filter).\n const statuses = REVIEW_STATUSES.filter((s) => s !== \"missing\" || summary.missing > 0 || filter.status === s);\n const scopeNames = (areas ?? []).map((area) => areaLabels?.[area] ?? area);\n const scopeText =\n scopeNames.length > 0 ? new Intl.ListFormat(locale, { type: \"conjunction\", style: \"long\" }).format(scopeNames) : \"\";\n\n const columns: DataTableColumn<TranslationRow>[] = [\n {\n key: \"key\",\n header: labels.key,\n cell: (r) => <code className=\"break-all text-xs text-[var(--text-secondary)]\">{r.key}</code>,\n sortBy: (r) => r.key,\n mobileHidden: true,\n },\n {\n key: \"reference\",\n header: referenceLabel,\n cell: (r) => (\n <span className=\"line-clamp-3 whitespace-pre-line break-words text-[var(--text-secondary)]\">{r.reference}</span>\n ),\n mobileHidden: true,\n },\n {\n key: \"text\",\n header: localeLabel,\n cell: (r) => <TextCell row={r} labels={labels} clamp />,\n mobilePrimary: true,\n },\n {\n key: \"status\",\n header: labels.statusColumn,\n cell: (r) => (\n <span className=\"inline-flex flex-wrap gap-1\">\n <ReviewStatusChip status={r.status} labels={labelsProp} />\n {r.placeholderMismatch && (\n <Chip size=\"sm\" tone=\"danger\" variant=\"outline\">\n {labels.placeholderChip}\n </Chip>\n )}\n </span>\n ),\n sortBy: (r) => REVIEW_STATUSES.indexOf(r.status),\n },\n ];\n if (editable && onSave) {\n const save = onSave;\n columns.push({\n key: \"approve\",\n header: <span className=\"sr-only\">{labels.approve}</span>,\n cell: (r) =>\n r.status === \"approved\" || r.text === \"\" ? null : (\n <IconButton\n type=\"button\"\n size=\"xs\"\n tone=\"muted\"\n commit\n label={labels.approve}\n pending={busy === `row:${r.id}`}\n disabled={busy !== null}\n onClick={(event) => {\n // The row's own click opens the editor; this one only approves.\n event.stopPropagation();\n void run(`row:${r.id}`, () => save([reviewWrite(r, \"APPROVED\")]));\n }}\n >\n <Check />\n </IconButton>\n ),\n });\n }\n\n return (\n <div className={cn(\"min-w-0 space-y-4\", className)}>\n {scopeText && (\n <AlertBanner tone=\"info\" variant=\"inline\" size=\"sm\" block live={false}>\n {labels.scope(scopeText)}\n </AlertBanner>\n )}\n {readOnly && (\n <AlertBanner tone=\"info\" variant=\"inline\" size=\"sm\" block live={false}>\n {labels.readOnly}\n </AlertBanner>\n )}\n\n {progress && <TranslationProgress summary={summary} labels={labelsProp} />}\n\n <ToggleGroup<ReviewStatus | \"all\">\n aria-label={labels.statusFilter}\n overflow=\"wrap\"\n size=\"sm\"\n value={filter.status}\n onChange={(next) => setFilter({ status: next })}\n options={[\n { value: \"all\", label: labels.filterCount(labels.all, summary.total) },\n ...statuses.map((s) => ({ value: s, label: labels.filterCount(reviewStatusLabel(labels, s), summary[s]) })),\n ]}\n />\n\n <div className=\"flex flex-col gap-3 md:flex-row md:flex-wrap md:items-end\">\n {sources.length > 1 && (\n <div className=\"md:w-48\">\n <Select label={labels.source} value={source} onChange={(e) => setFilter({ source: e.target.value })}>\n <option value=\"all\">{labels.allSources}</option>\n {sources.map((s) => (\n <option key={s} value={s}>\n {sourceLabels?.[s] ?? s}\n </option>\n ))}\n </Select>\n </div>\n )}\n <div className=\"md:w-56\">\n <Select label={labels.namespace} value={namespace} onChange={(e) => setFilter({ namespace: e.target.value })}>\n <option value=\"all\">{labels.allNamespaces}</option>\n {namespaces.map((ns) => (\n <option key={ns} value={ns}>\n {ns}\n </option>\n ))}\n </Select>\n </div>\n <SearchField\n className=\"min-w-0 md:flex-1\"\n label={labels.search}\n value={filter.query}\n onChange={(next) => setFilter({ query: next })}\n />\n <Checkbox\n label={labels.placeholdersOnly(mismatches)}\n checked={filter.placeholdersOnly}\n onCheckedChange={(next) => setFilter({ placeholdersOnly: next })}\n />\n </div>\n\n {editable && visible.length > 0 && (\n // The phone's table has no checkboxes; this is its way into the bulk actions.\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"md:hidden\"\n disabled={allSelected}\n onClick={() => setSelected(new Set(visible.map((r) => r.id)))}\n >\n <ListChecks className=\"size-4\" aria-hidden />\n {labels.selectShown}\n </Button>\n )}\n\n {failure && (\n <AlertBanner tone=\"danger\" size=\"sm\" role=\"alert\">\n {failure}\n </AlertBanner>\n )}\n\n {editable && (\n <BulkActionBar count={selectedRows.length} onClear={() => setSelected(new Set())}>\n <Button\n variant=\"primary\"\n size=\"sm\"\n commit\n pending={busy === \"bulk-approve\"}\n disabled={busy !== null || toApprove.length === 0}\n onClick={() => void approveSelected()}\n >\n <Check className=\"size-4\" aria-hidden />\n {labels.approveSelected}\n </Button>\n {onClear && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n commit\n pending={busy === \"bulk-reset\"}\n disabled={busy !== null || toReset.length === 0}\n onClick={() => void resetSelected()}\n >\n <RotateCcw className=\"size-4\" aria-hidden />\n {labels.resetSelected}\n </Button>\n )}\n </BulkActionBar>\n )}\n\n <DataTable<TranslationRow>\n rows={visible}\n rowKey={(r) => r.id}\n columns={columns}\n empty={labels.empty}\n defaultPageSize={pageSize}\n storageKey={storageKey}\n isExpanded={(r) => r.id === openId}\n onRowClick={(r) => setOpenId((open) => (open === r.id ? null : r.id))}\n expandedRow={(r) => (\n <TranslationReviewEditor\n key={r.id}\n row={r}\n referenceLabel={referenceLabel}\n localeLabel={localeLabel}\n readOnly={!editable}\n onSave={onSave ? (write) => onSave([write]) : undefined}\n onClear={onClear ? (key) => onClear([key]) : undefined}\n onClose={() => setOpenId(null)}\n formatDate={formatDate}\n formatError={formatError}\n labels={labelsProp}\n />\n )}\n mobileCard={(r) => (\n <div className=\"min-w-0 space-y-1\">\n <code className=\"block break-all text-xs text-[var(--text-muted)]\">{r.key}</code>\n <div className=\"text-sm text-[var(--text-primary)]\">\n <TextCell row={r} labels={labels} clamp />\n </div>\n <div className=\"flex flex-wrap gap-1\">\n <ReviewStatusChip status={r.status} labels={labelsProp} />\n {r.placeholderMismatch && (\n <Chip size=\"sm\" tone=\"danger\" variant=\"outline\">\n {labels.placeholderChip}\n </Chip>\n )}\n </div>\n </div>\n )}\n selection={\n editable\n ? {\n isSelected: (r) => selected.has(r.id),\n onToggle: (r, checked) =>\n setSelected((prev) => {\n const next = new Set([...prev].filter((id) => visibleIds.has(id)));\n if (checked) next.add(r.id);\n else next.delete(r.id);\n return next;\n }),\n onToggleMany: (many, checked) =>\n setSelected((prev) => {\n const next = new Set([...prev].filter((id) => visibleIds.has(id)));\n for (const r of many) {\n if (checked) next.add(r.id);\n else next.delete(r.id);\n }\n return next;\n }),\n allSelected,\n someSelected: selectedRows.length > 0 && !allSelected,\n onToggleAll: (checked) => setSelected(checked ? new Set(visible.map((r) => r.id)) : new Set()),\n }\n : undefined\n }\n />\n </div>\n );\n}\n"],"mappings":"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","names":["ReviewStatusChip","TranslationProgress","TranslationReviewEditor","DEFAULT_TRANSLATION_REVIEW_LABELS"]}
|
package/dist/components/ui.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'react';
|
|
2
2
|
import './tooltip.js';
|
|
3
|
-
export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDensity, m as CardDescription, n as CardDescriptionProps, o as CardFooter, p as CardFooterProps, q as CardHeader, r as CardHeaderProps, s as CardProps, t as CardTitle, u as CardTitleLevel, v as CardTitleProps, w as CardTone, S as DEFAULT_PASSWORD_REVEAL_LABELS,
|
|
3
|
+
export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDensity, m as CardDescription, n as CardDescriptionProps, o as CardFooter, p as CardFooterProps, q as CardHeader, r as CardHeaderProps, s as CardProps, t as CardTitle, u as CardTitleLevel, v as CardTitleProps, w as CardTone, S as DEFAULT_PASSWORD_REVEAL_LABELS, W as DEFAULT_TABS_LABELS, Z as EmptyState, _ as EmptyStateProps, $ as FIELD_BASE, a0 as FIELD_DISPLAY, a1 as FIELD_FLOATING_PAD, a2 as FIELD_INVALID, a3 as FIELD_TRIGGER, a4 as FIELD_WRITABLE_LOOK, a5 as FLOATING_INPUT_CLASS, a6 as FLOATING_LABEL_CLASS, a7 as FLOATING_LABEL_STATIC, a8 as FieldChevron, a9 as FieldChevronProps, aa as FieldHint, ab as FieldHintProps, ac as FieldLabel, ad as FieldLabelProps, al as FloatingField, am as FloatingFieldProps, av as IconButton, aw as IconButtonGlyphSize, ax as IconButtonLinkProps, ay as IconButtonProps, az as IconButtonSize, aA as IconButtonTone, aB as IconButtonToneColor, aC as IconButtonVariant, aD as Input, aE as InputProps, aH as Label, aJ as LabelProps, aL as PHONE_QUERY, aM as PasswordRevealLabels, aP as Select, aQ as SelectProps, b1 as Spinner, b2 as SpinnerProps, b4 as TabItem, b5 as Tabs, b6 as TabsLabels, b7 as TabsProps, b8 as Textarea, b9 as TextareaProps, bh as buttonClasses, bl as resolvePasswordRevealLabels } from '../kit-labels-D1J2_jyD.js';
|
|
4
4
|
import '../hooks/use-anchored-rect.js';
|
|
5
5
|
import '../lib/clipping.js';
|
|
6
6
|
import './data-table-labels.js';
|
|
@@ -50,3 +50,15 @@ import './authed-image.js';
|
|
|
50
50
|
import '../hooks/use-authed-src.js';
|
|
51
51
|
import './image-grid.js';
|
|
52
52
|
import './lightbox.js';
|
|
53
|
+
import './write-lock.js';
|
|
54
|
+
import './legal.js';
|
|
55
|
+
import './account-chips.js';
|
|
56
|
+
import '../lib/format.js';
|
|
57
|
+
import '../data-table-3mi2R3so.js';
|
|
58
|
+
import './data-table-sort.js';
|
|
59
|
+
import './swipeable-row.js';
|
|
60
|
+
import './reauth-dialog.js';
|
|
61
|
+
import './server-wake.js';
|
|
62
|
+
import '../lib/server-wake.js';
|
|
63
|
+
import './translation-review-labels.js';
|
|
64
|
+
import '../lib/translation-review.js';
|
package/dist/components/ui.js
CHANGED
|
@@ -9,6 +9,7 @@ import { useMediaQuery } from "../hooks/use-media-query.js";
|
|
|
9
9
|
import { Tooltip } from "./tooltip.js";
|
|
10
10
|
import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLink } from "../i18n/kit-labels.js";
|
|
11
11
|
import { pickLinkRenderer, replacingClick, routerLinkNavigation } from "./text-link.js";
|
|
12
|
+
import { useCommitReason } from "./write-lock.js";
|
|
12
13
|
const BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
13
14
|
const BUTTON_SIZES = {
|
|
14
15
|
md: "gap-2 px-3 py-2 text-sm",
|
|
@@ -144,7 +145,8 @@ function ButtonElement({
|
|
|
144
145
|
stretch,
|
|
145
146
|
tone = "default",
|
|
146
147
|
pressed,
|
|
147
|
-
disabledReason,
|
|
148
|
+
disabledReason: ownDisabledReason,
|
|
149
|
+
commit,
|
|
148
150
|
pending,
|
|
149
151
|
className,
|
|
150
152
|
onClick,
|
|
@@ -157,6 +159,7 @@ function ButtonElement({
|
|
|
157
159
|
...rest
|
|
158
160
|
}) {
|
|
159
161
|
const reasonId = useId();
|
|
162
|
+
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
160
163
|
const locked = hasContent(disabledReason);
|
|
161
164
|
const inert = locked || Boolean(pending);
|
|
162
165
|
const ownDescribedBy = rest["aria-describedby"];
|
|
@@ -486,7 +489,8 @@ function IconButtonElement({
|
|
|
486
489
|
tooltipSide,
|
|
487
490
|
tooltipPortal,
|
|
488
491
|
tooltipLazy = true,
|
|
489
|
-
disabledReason,
|
|
492
|
+
disabledReason: ownDisabledReason,
|
|
493
|
+
commit,
|
|
490
494
|
pending,
|
|
491
495
|
glyphSize,
|
|
492
496
|
badge,
|
|
@@ -507,6 +511,7 @@ function IconButtonElement({
|
|
|
507
511
|
const tone = toneProp ?? (toneColor !== void 0 ? "custom" : "default");
|
|
508
512
|
const keep = disabledStyle === "keep";
|
|
509
513
|
const reasonId = useId();
|
|
514
|
+
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
510
515
|
const locked = hasContent(disabledReason);
|
|
511
516
|
const inert = locked || Boolean(pending);
|
|
512
517
|
const ownDescribedBy = rest["aria-describedby"];
|
|
@@ -742,8 +747,12 @@ function useFieldError(error, invalid, describedBy, ariaInvalid) {
|
|
|
742
747
|
errorEl: hasError ? /* @__PURE__ */ jsx("p", { id: errorId, className: FIELD_ERROR_CLASS, children: error }) : null
|
|
743
748
|
};
|
|
744
749
|
}
|
|
745
|
-
function FieldGroup({
|
|
746
|
-
|
|
750
|
+
function FieldGroup({
|
|
751
|
+
errorEl,
|
|
752
|
+
reserve = false,
|
|
753
|
+
children
|
|
754
|
+
}) {
|
|
755
|
+
if (errorEl === null && !reserve) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
747
756
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
748
757
|
children,
|
|
749
758
|
errorEl
|
|
@@ -758,19 +767,21 @@ function resolvePasswordRevealLabels(partial) {
|
|
|
758
767
|
return { ...DEFAULT_PASSWORD_REVEAL_LABELS, ...partial };
|
|
759
768
|
}
|
|
760
769
|
const PICKER_TYPES = /* @__PURE__ */ new Set(["date", "datetime-local", "month", "time", "week"]);
|
|
761
|
-
const Input = forwardRef(function Input2({
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
770
|
+
const Input = forwardRef(function Input2(props, ref) {
|
|
771
|
+
const {
|
|
772
|
+
className,
|
|
773
|
+
inputClassName,
|
|
774
|
+
label,
|
|
775
|
+
id,
|
|
776
|
+
placeholder,
|
|
777
|
+
type,
|
|
778
|
+
variant = "field",
|
|
779
|
+
invalid,
|
|
780
|
+
error,
|
|
781
|
+
passwordLabels,
|
|
782
|
+
...rest
|
|
783
|
+
} = props;
|
|
784
|
+
const reserve = "error" in props;
|
|
774
785
|
const generated = useId();
|
|
775
786
|
const fieldId = id ?? generated;
|
|
776
787
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
@@ -805,7 +816,7 @@ const Input = forwardRef(function Input2({
|
|
|
805
816
|
) : null;
|
|
806
817
|
if (label === void 0) {
|
|
807
818
|
if (!isPassword) {
|
|
808
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsx(
|
|
819
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsx(
|
|
809
820
|
"input",
|
|
810
821
|
{
|
|
811
822
|
ref,
|
|
@@ -820,7 +831,7 @@ const Input = forwardRef(function Input2({
|
|
|
820
831
|
}
|
|
821
832
|
) });
|
|
822
833
|
}
|
|
823
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
834
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
824
835
|
/* @__PURE__ */ jsx(
|
|
825
836
|
"input",
|
|
826
837
|
{
|
|
@@ -837,7 +848,7 @@ const Input = forwardRef(function Input2({
|
|
|
837
848
|
revealToggle
|
|
838
849
|
] }) });
|
|
839
850
|
}
|
|
840
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsxs(FloatingField, { className, htmlFor: fieldId, label, srOnlyLabel: asDisplay, children: [
|
|
851
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsxs(FloatingField, { className, htmlFor: fieldId, label, srOnlyLabel: asDisplay, children: [
|
|
841
852
|
/* @__PURE__ */ jsx(
|
|
842
853
|
"input",
|
|
843
854
|
{
|
|
@@ -862,7 +873,9 @@ const Input = forwardRef(function Input2({
|
|
|
862
873
|
});
|
|
863
874
|
Input.displayName = "Input";
|
|
864
875
|
const SELECT_SM = "h-7 py-0 ps-2 pe-7 text-xs";
|
|
865
|
-
const Select = forwardRef(function Select2(
|
|
876
|
+
const Select = forwardRef(function Select2(props, ref) {
|
|
877
|
+
const { className, label, id, children, invalid, error, hint, size, selectClassName, ...rest } = props;
|
|
878
|
+
const reserve = "error" in props;
|
|
866
879
|
const generated = useId();
|
|
867
880
|
const nativeSize = typeof size === "number" ? size : void 0;
|
|
868
881
|
const listBox = nativeSize !== void 0 && nativeSize > 1 || Boolean(rest.multiple);
|
|
@@ -906,7 +919,7 @@ const Select = forwardRef(function Select2({ className, label, id, children, inv
|
|
|
906
919
|
}
|
|
907
920
|
);
|
|
908
921
|
if (hasContent(hint) && !textHint) {
|
|
909
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, children: /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5", className), children: [
|
|
922
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5", className), children: [
|
|
910
923
|
/* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
|
|
911
924
|
select,
|
|
912
925
|
chevron
|
|
@@ -914,12 +927,12 @@ const Select = forwardRef(function Select2({ className, label, id, children, inv
|
|
|
914
927
|
/* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: hint })
|
|
915
928
|
] }) });
|
|
916
929
|
}
|
|
917
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
930
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
918
931
|
select,
|
|
919
932
|
chevron
|
|
920
933
|
] }) });
|
|
921
934
|
}
|
|
922
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, children: /* @__PURE__ */ jsxs(FloatingField, { className, htmlFor: fieldId, label, staticLabel: true, hint: textHint ? void 0 : hint, children: [
|
|
935
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs(FloatingField, { className, htmlFor: fieldId, label, staticLabel: true, hint: textHint ? void 0 : hint, children: [
|
|
923
936
|
/* @__PURE__ */ jsx(
|
|
924
937
|
"select",
|
|
925
938
|
{
|
|
@@ -947,7 +960,9 @@ const Select = forwardRef(function Select2({ className, label, id, children, inv
|
|
|
947
960
|
] }) });
|
|
948
961
|
});
|
|
949
962
|
Select.displayName = "Select";
|
|
950
|
-
const Textarea = forwardRef(function Textarea2(
|
|
963
|
+
const Textarea = forwardRef(function Textarea2(props, ref) {
|
|
964
|
+
const { className, label, id, placeholder, invalid, error, ...rest } = props;
|
|
965
|
+
const reserve = "error" in props;
|
|
951
966
|
const generated = useId();
|
|
952
967
|
const fieldId = id ?? generated;
|
|
953
968
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
@@ -957,7 +972,7 @@ const Textarea = forwardRef(function Textarea2({ className, label, id, placehold
|
|
|
957
972
|
rest["aria-invalid"]
|
|
958
973
|
);
|
|
959
974
|
if (label === void 0) {
|
|
960
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsx(
|
|
975
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsx(
|
|
961
976
|
"textarea",
|
|
962
977
|
{
|
|
963
978
|
ref,
|
|
@@ -970,7 +985,7 @@ const Textarea = forwardRef(function Textarea2({ className, label, id, placehold
|
|
|
970
985
|
}
|
|
971
986
|
) });
|
|
972
987
|
}
|
|
973
|
-
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsx(FloatingField, { className, htmlFor: fieldId, label, children: /* @__PURE__ */ jsx(
|
|
988
|
+
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsx(FloatingField, { className, htmlFor: fieldId, label, children: /* @__PURE__ */ jsx(
|
|
974
989
|
"textarea",
|
|
975
990
|
{
|
|
976
991
|
ref,
|
|
@@ -1114,7 +1129,7 @@ function CardTitle({ className, as: Tag = "div", ...props }) {
|
|
|
1114
1129
|
Tag,
|
|
1115
1130
|
{
|
|
1116
1131
|
"data-slot": "card-title",
|
|
1117
|
-
className: cn(compact ? "text-sm font-medium" : "font-semibold
|
|
1132
|
+
className: cn(compact ? "text-sm font-medium leading-snug" : "font-semibold leading-none", className),
|
|
1118
1133
|
...props
|
|
1119
1134
|
}
|
|
1120
1135
|
);
|