@vertebr/react 0.19.0 → 0.20.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/dist/components/admin/AdminSyncPage.d.ts.map +1 -1
- package/dist/components/admin/AdminSyncPage.js +202 -3
- package/dist/components/dialog/SelectionTableDialog.d.ts +79 -0
- package/dist/components/dialog/SelectionTableDialog.d.ts.map +1 -0
- package/dist/components/dialog/SelectionTableDialog.js +177 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/selection-dialog.css +170 -0
- package/dist/styles/sync.css +17 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSyncPage.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSyncPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,
|
|
1
|
+
{"version":3,"file":"AdminSyncPage.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSyncPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EAKf,aAAa,EACd,MAAM,iBAAiB,CAAC;AAazB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;CAC1B,CAAC;AAiZF,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,+BAqhBpE"}
|
|
@@ -5,6 +5,7 @@ import { EnvSelect } from "../env/EnvSelect";
|
|
|
5
5
|
import { useToast } from "../../hooks/useToast";
|
|
6
6
|
import { Badge, Card } from "../ui/primitives";
|
|
7
7
|
import { IconSync } from "../ui/icons";
|
|
8
|
+
import { SelectionTableDialog, } from "../dialog/SelectionTableDialog";
|
|
8
9
|
import { SyncPreviewFrame } from "./SyncPreviewFrame";
|
|
9
10
|
function envById(environments, id) {
|
|
10
11
|
return environments.find((env) => env.id === id);
|
|
@@ -47,6 +48,132 @@ function statusLabel(status) {
|
|
|
47
48
|
return status;
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
function asStringList(value) {
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === "string" && value.trim()) {
|
|
56
|
+
return value
|
|
57
|
+
.split(",")
|
|
58
|
+
.map((item) => item.trim())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
function compactInputs(values) {
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [targetId, fields] of Object.entries(values)) {
|
|
66
|
+
const cleaned = {};
|
|
67
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
if (value.length > 0)
|
|
70
|
+
cleaned[key] = value;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
74
|
+
cleaned[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (Object.keys(cleaned).length > 0)
|
|
78
|
+
out[targetId] = cleaned;
|
|
79
|
+
}
|
|
80
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
81
|
+
}
|
|
82
|
+
function firstEnumListField(schema) {
|
|
83
|
+
if (!schema)
|
|
84
|
+
return null;
|
|
85
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
86
|
+
if (field.type === "enum[]")
|
|
87
|
+
return { key, field };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function selectionNoun(field) {
|
|
92
|
+
const label = (field.label ?? "items").trim().toLowerCase();
|
|
93
|
+
if (!label)
|
|
94
|
+
return "items";
|
|
95
|
+
return label;
|
|
96
|
+
}
|
|
97
|
+
function selectionSummary(field, selected) {
|
|
98
|
+
const total = field.options?.length ?? 0;
|
|
99
|
+
const noun = selectionNoun(field);
|
|
100
|
+
if (selected.length === 0) {
|
|
101
|
+
return total > 0 ? `All ${total} ${noun}` : `All ${noun}`;
|
|
102
|
+
}
|
|
103
|
+
if (selected.length === 1) {
|
|
104
|
+
return field.optionLabels?.[selected[0]] ?? selected[0];
|
|
105
|
+
}
|
|
106
|
+
return `${selected.length} ${noun} selected`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Which envs an option is active in. Falls back to the sync source when the
|
|
110
|
+
* host omitted `optionEnvironments`.
|
|
111
|
+
*/
|
|
112
|
+
function activeEnvIdsForOption(optionId, field, fromEnv) {
|
|
113
|
+
const declared = field.optionEnvironments?.[optionId];
|
|
114
|
+
if (Array.isArray(declared) && declared.length > 0) {
|
|
115
|
+
return [...new Set(declared.map(String).filter(Boolean))];
|
|
116
|
+
}
|
|
117
|
+
if ((field.options ?? []).includes(optionId))
|
|
118
|
+
return [fromEnv];
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
/** Prefer sync direction order for env chips. */
|
|
122
|
+
function orderedSyncEnvironments(environments, fromEnv, toEnv) {
|
|
123
|
+
const byId = new Map(environments.map((env) => [env.id, env]));
|
|
124
|
+
const ordered = [];
|
|
125
|
+
const push = (id) => {
|
|
126
|
+
const env = byId.get(id);
|
|
127
|
+
if (!env || ordered.some((item) => item.id === id))
|
|
128
|
+
return;
|
|
129
|
+
ordered.push(env);
|
|
130
|
+
};
|
|
131
|
+
push(fromEnv);
|
|
132
|
+
for (const id of toEnv)
|
|
133
|
+
push(id);
|
|
134
|
+
for (const env of environments)
|
|
135
|
+
push(env.id);
|
|
136
|
+
return ordered;
|
|
137
|
+
}
|
|
138
|
+
/** Column / copy presets for Sync target pickers (override per field key). */
|
|
139
|
+
function syncSelectionOverrides(fieldKey, field) {
|
|
140
|
+
const noun = selectionNoun(field);
|
|
141
|
+
if (fieldKey === "codes") {
|
|
142
|
+
return {
|
|
143
|
+
eyebrow: "Sync selection",
|
|
144
|
+
columns: { primary: "Code", secondary: "Offer" },
|
|
145
|
+
allSelectedFooter: "Sync all missing codes",
|
|
146
|
+
removeLabel: "Remove from sync",
|
|
147
|
+
emptyLabel: "No discount codes in the source environment.",
|
|
148
|
+
searchPlaceholder: "Search codes…",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (fieldKey === "ids") {
|
|
152
|
+
return {
|
|
153
|
+
eyebrow: "Sync selection",
|
|
154
|
+
columns: { primary: "Id", secondary: "Name" },
|
|
155
|
+
allSelectedFooter: `Sync all ${noun}`,
|
|
156
|
+
removeLabel: "Remove from sync",
|
|
157
|
+
emptyLabel: "No products in the source environment.",
|
|
158
|
+
searchPlaceholder: "Search products…",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
eyebrow: "Sync selection",
|
|
163
|
+
columns: { primary: "Id", secondary: "Name" },
|
|
164
|
+
allSelectedFooter: `Sync all ${noun}`,
|
|
165
|
+
removeLabel: "Remove from sync",
|
|
166
|
+
searchPlaceholder: `Search ${noun}…`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function syncSelectionRows(field, fromEnv) {
|
|
170
|
+
return (field.options ?? []).map((id) => ({
|
|
171
|
+
id,
|
|
172
|
+
primary: id,
|
|
173
|
+
secondary: field.optionLabels?.[id] ?? "",
|
|
174
|
+
environments: activeEnvIdsForOption(id, field, fromEnv),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
50
177
|
function formatDiffValue(value) {
|
|
51
178
|
if (value === undefined || value === null)
|
|
52
179
|
return "—";
|
|
@@ -90,6 +217,8 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
90
217
|
const [selectedTargets, setSelectedTargets] = useState(() => (sync?.targets ?? [])
|
|
91
218
|
.filter((target) => target.defaultSelected)
|
|
92
219
|
.map((target) => target.id));
|
|
220
|
+
const [targetInputValues, setTargetInputValues] = useState({});
|
|
221
|
+
const [picker, setPicker] = useState(null);
|
|
93
222
|
const [plan, setPlan] = useState(null);
|
|
94
223
|
const [planLoading, setPlanLoading] = useState(false);
|
|
95
224
|
const [running, setRunning] = useState(false);
|
|
@@ -102,6 +231,11 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
102
231
|
const targetGroups = useMemo(() => groupTargets(catalog), [catalog]);
|
|
103
232
|
const hasTargets = (sync?.targets.length ?? 0) > 0;
|
|
104
233
|
const allTargetIds = useMemo(() => (sync?.targets ?? []).map((target) => target.id), [sync?.targets]);
|
|
234
|
+
const requestInputs = useMemo(() => compactInputs(targetInputValues), [targetInputValues]);
|
|
235
|
+
const inputsForTarget = useCallback((targetId) => {
|
|
236
|
+
return (plan?.targetInputs?.[targetId] ??
|
|
237
|
+
sync?.targets.find((target) => target.id === targetId)?.inputs);
|
|
238
|
+
}, [plan?.targetInputs, sync?.targets]);
|
|
105
239
|
const refreshPlan = useCallback(async () => {
|
|
106
240
|
if (!hasTargets || toEnv.length === 0 || allTargetIds.length === 0) {
|
|
107
241
|
setPlan(null);
|
|
@@ -115,6 +249,7 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
115
249
|
from: fromEnv,
|
|
116
250
|
to: toEnv,
|
|
117
251
|
targets: allTargetIds,
|
|
252
|
+
...(requestInputs ? { inputs: requestInputs } : {}),
|
|
118
253
|
});
|
|
119
254
|
setPlan(next);
|
|
120
255
|
}
|
|
@@ -125,7 +260,7 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
125
260
|
finally {
|
|
126
261
|
setPlanLoading(false);
|
|
127
262
|
}
|
|
128
|
-
}, [client, fromEnv, toEnv, allTargetIds, hasTargets]);
|
|
263
|
+
}, [client, fromEnv, toEnv, allTargetIds, hasTargets, requestInputs]);
|
|
129
264
|
useEffect(() => {
|
|
130
265
|
void refreshPlan();
|
|
131
266
|
}, [refreshPlan]);
|
|
@@ -134,6 +269,51 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
134
269
|
? current.filter((id) => id !== targetId)
|
|
135
270
|
: [...current, targetId]);
|
|
136
271
|
};
|
|
272
|
+
const openTargetPicker = (targetId, targetLabel) => {
|
|
273
|
+
const schema = inputsForTarget(targetId);
|
|
274
|
+
const listField = firstEnumListField(schema);
|
|
275
|
+
if (!listField) {
|
|
276
|
+
toggleTarget(targetId);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
setPicker({
|
|
280
|
+
targetId,
|
|
281
|
+
targetLabel,
|
|
282
|
+
fieldKey: listField.key,
|
|
283
|
+
field: listField.field,
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const applyPickerSelection = (ids) => {
|
|
287
|
+
if (!picker)
|
|
288
|
+
return;
|
|
289
|
+
const { targetId, fieldKey } = picker;
|
|
290
|
+
setSelectedTargets((current) => current.includes(targetId) ? current : [...current, targetId]);
|
|
291
|
+
setTargetInputValues((current) => ({
|
|
292
|
+
...current,
|
|
293
|
+
[targetId]: {
|
|
294
|
+
...(current[targetId] ?? {}),
|
|
295
|
+
[fieldKey]: ids,
|
|
296
|
+
},
|
|
297
|
+
}));
|
|
298
|
+
setPicker(null);
|
|
299
|
+
};
|
|
300
|
+
const deselectFromPicker = () => {
|
|
301
|
+
if (!picker)
|
|
302
|
+
return;
|
|
303
|
+
const { targetId, fieldKey } = picker;
|
|
304
|
+
setSelectedTargets((current) => current.filter((id) => id !== targetId));
|
|
305
|
+
setTargetInputValues((current) => {
|
|
306
|
+
const next = { ...current };
|
|
307
|
+
const fields = { ...(next[targetId] ?? {}) };
|
|
308
|
+
delete fields[fieldKey];
|
|
309
|
+
if (Object.keys(fields).length === 0)
|
|
310
|
+
delete next[targetId];
|
|
311
|
+
else
|
|
312
|
+
next[targetId] = fields;
|
|
313
|
+
return next;
|
|
314
|
+
});
|
|
315
|
+
setPicker(null);
|
|
316
|
+
};
|
|
137
317
|
const runSync = async () => {
|
|
138
318
|
if (!hasTargets || toEnv.length === 0 || selectedTargets.length === 0)
|
|
139
319
|
return;
|
|
@@ -159,10 +339,16 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
159
339
|
setRunning(true);
|
|
160
340
|
setError(null);
|
|
161
341
|
try {
|
|
342
|
+
const selectedInputs = requestInputs
|
|
343
|
+
? Object.fromEntries(Object.entries(requestInputs).filter(([id]) => selectedTargets.includes(id)))
|
|
344
|
+
: undefined;
|
|
162
345
|
const result = await client.runSync({
|
|
163
346
|
from: fromEnv,
|
|
164
347
|
to: toEnv,
|
|
165
348
|
targets: selectedTargets,
|
|
349
|
+
...(selectedInputs && Object.keys(selectedInputs).length > 0
|
|
350
|
+
? { inputs: selectedInputs }
|
|
351
|
+
: {}),
|
|
166
352
|
});
|
|
167
353
|
push(result.message ?? (result.ok ? "Sync complete" : "Sync finished with errors"), result.ok ? "ok" : "danger");
|
|
168
354
|
await refreshPlan();
|
|
@@ -189,7 +375,12 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
189
375
|
setToEnv((current) => current.filter((id) => id !== next));
|
|
190
376
|
} })] }), _jsx("div", { className: "vertebr-sync-connector", "aria-hidden": "true", children: toEnvironments.length > 0 ? (toEnvironments.map((destination) => (_jsx("span", { className: "vertebr-sync-connector-arrow", children: "\u2192" }, destination.id)))) : (_jsx("span", { className: "vertebr-sync-connector-arrow", children: "\u2192" })) }), _jsxs("label", { className: "vertebr-sync-field", children: [_jsx("span", { className: "vertebr-sync-field-label", children: "To" }), _jsx(EnvMultiSelect, { environments: environments, value: toEnv, excludeId: fromEnv, onChange: setToEnv, placeholder: "Select one or more destinations" })] })] }), fromEnvironment && toEnvironments.length > 0 ? (_jsx("div", { className: "vertebr-sync-preview", "aria-label": "Sync direction", children: _jsxs("div", { className: "vertebr-sync-preview-flow", "data-dest-count": toEnvironments.length, children: [_jsxs("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-from", children: [_jsx("span", { className: "vertebr-sync-preview-caption", children: "From" }), _jsx(EnvFlowBadge, { environment: fromEnvironment })] }), _jsx("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-routes", children: toEnvironments.map((destination, index) => (_jsxs("div", { className: "vertebr-sync-preview-route", children: [_jsx("span", { className: "vertebr-sync-preview-arrow", "aria-hidden": "true", children: "\u2192" }), _jsxs("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-to", children: [index === 0 ? (_jsx("span", { className: "vertebr-sync-preview-caption", children: "To" })) : (_jsx("span", { className: "vertebr-sync-preview-caption", "aria-hidden": "true", children: "\u00A0" })), _jsx(EnvFlowBadge, { environment: destination })] })] }, destination.id))) })] }) })) : (_jsx("p", { className: "vertebr-sync-direction-hint", children: "Pick a source environment and one or more destinations to preview changes." }))] }) }), _jsxs(Card, { title: "What to sync", children: [_jsxs("div", { className: "vertebr-sync-targets-head", children: [_jsx("p", { className: "vertebr-sync-targets-copy", children: "Review what will change in each destination before syncing." }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-sync-refresh", disabled: planLoading || toEnv.length === 0, onClick: () => void refreshPlan(), children: planLoading ? "Refreshing…" : "Refresh preview" })] }), [...targetGroups.entries()].map(([group, targets]) => (_jsxs("div", { className: "vertebr-sync-target-group", children: [_jsx("div", { className: "vertebr-sync-target-group-label", children: group }), _jsx("ul", { className: "vertebr-sync-target-list", children: targets.map((target) => {
|
|
191
377
|
const selected = selectedTargets.includes(target.id);
|
|
192
|
-
|
|
378
|
+
const inputSchema = inputsForTarget(target.id);
|
|
379
|
+
const listField = firstEnumListField(inputSchema);
|
|
380
|
+
const pickedCodes = listField
|
|
381
|
+
? asStringList(targetInputValues[target.id]?.[listField.key])
|
|
382
|
+
: [];
|
|
383
|
+
return (_jsx("li", { children: _jsxs("div", { className: "vertebr-sync-target-row", "data-selected": selected || undefined, "data-has-picker": listField ? "true" : undefined, children: [_jsxs("button", { type: "button", className: "vertebr-sync-target-toggle", onClick: () => openTargetPicker(target.id, target.label), "aria-pressed": selected, children: [_jsx("span", { className: "vertebr-sync-target-check", "aria-hidden": "true", children: selected ? "✓" : "" }), _jsxs("span", { className: "vertebr-sync-target-main", children: [_jsx("span", { className: "vertebr-sync-target-title", children: target.label }), target.description ? (_jsx("span", { className: "vertebr-sync-target-desc", children: target.description })) : null, selected && listField ? (_jsxs("span", { className: "vertebr-sync-target-selection", children: [selectionSummary(listField.field, pickedCodes), _jsx("span", { className: "vertebr-sync-target-selection-hint", children: "Click to change" })] })) : listField ? (_jsxs("span", { className: "vertebr-sync-target-selection-hint", children: ["Click to choose", " ", selectionNoun(listField.field)] })) : null] })] }), _jsx("span", { className: "vertebr-sync-target-badges", children: toEnvironments.length === 0 ? (_jsx(Badge, { children: "Choose destination" })) : planLoading ? (_jsx(Badge, { children: "Previewing\u2026" })) : (toEnvironments.map((destination) => {
|
|
193
384
|
const item = planForTarget(destination.id, target.id);
|
|
194
385
|
if (!item) {
|
|
195
386
|
return (_jsx(Badge, { children: destination.label }, destination.id));
|
|
@@ -209,5 +400,13 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
209
400
|
}) })] }, group)))] }), sync?.excluded.length ? (_jsx(Card, { title: "Never synced", subdued: true, children: _jsx("ul", { className: "vertebr-sync-excluded-list", children: sync.excluded.map((item) => (_jsxs("li", { children: [_jsx("span", { className: "vertebr-sync-excluded-name", children: item.label }), _jsx("span", { className: "vertebr-sync-excluded-reason", children: item.reason })] }, item.label))) }) })) : null, error ? _jsx("div", { className: "vertebr-banner", "data-tone": "danger", children: error }) : null] }), _jsxs("div", { className: "vertebr-savebar vertebr-sync-footer", children: [_jsx("div", { className: "vertebr-savebar-message", children: syncSummary }), _jsx("div", { className: "vertebr-savebar-actions", children: _jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-primary vertebr-sync-run-btn", disabled: running ||
|
|
210
401
|
planLoading ||
|
|
211
402
|
toEnv.length === 0 ||
|
|
212
|
-
selectedTargets.length === 0, onClick: () => void runSync(), children: [_jsx(IconSync, {}), _jsx("span", { children: running ? "Syncing…" : "Sync selected" })] }) })] }), inspect ? (_jsx(SyncDiffModal, { inspect: inspect, sourceLabel: fromEnvironment?.label ?? fromEnv, onClose: () => setInspect(null) })) : null]
|
|
403
|
+
selectedTargets.length === 0, onClick: () => void runSync(), children: [_jsx(IconSync, {}), _jsx("span", { children: running ? "Syncing…" : "Sync selected" })] }) })] }), inspect ? (_jsx(SyncDiffModal, { inspect: inspect, sourceLabel: fromEnvironment?.label ?? fromEnv, onClose: () => setInspect(null) })) : null, picker ? (_jsx(SelectionTableDialog, { title: picker.targetLabel, description: (inputsForTarget(picker.targetId)?.[picker.fieldKey] ??
|
|
404
|
+
picker.field).description, sourceLabel: fromEnvironment?.label ?? fromEnv, environments: orderedSyncEnvironments(environments, fromEnv, toEnv), destinationEnvIds: toEnv, rows: syncSelectionRows(inputsForTarget(picker.targetId)?.[picker.fieldKey] ??
|
|
405
|
+
picker.field, fromEnv), selectedIds: asStringList(targetInputValues[picker.targetId]?.[picker.fieldKey]), overrides: {
|
|
406
|
+
...syncSelectionOverrides(picker.fieldKey, inputsForTarget(picker.targetId)?.[picker.fieldKey] ??
|
|
407
|
+
picker.field),
|
|
408
|
+
showRemove: selectedTargets.includes(picker.targetId),
|
|
409
|
+
}, onClose: () => setPicker(null), onApply: applyPickerSelection, onRemove: selectedTargets.includes(picker.targetId)
|
|
410
|
+
? deselectFromPicker
|
|
411
|
+
: undefined })) : null] }));
|
|
213
412
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { AdminEnvironment } from "@vertebr/client";
|
|
3
|
+
import { type TableFilterColumn } from "../table";
|
|
4
|
+
/** One selectable row in {@link SelectionTableDialog}. */
|
|
5
|
+
export type SelectionTableRow = {
|
|
6
|
+
id: string;
|
|
7
|
+
/** Primary column (id / code / slug). */
|
|
8
|
+
primary: string;
|
|
9
|
+
/** Secondary column (name / offer / label). */
|
|
10
|
+
secondary?: string;
|
|
11
|
+
/** Env ids where this row is already present. */
|
|
12
|
+
environments?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Extra values for custom filter columns (keyed by filter id).
|
|
15
|
+
* Built-in `destination` uses `"missing" | "present"`.
|
|
16
|
+
*/
|
|
17
|
+
filterValues?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
export type SelectionTableColumnLabels = {
|
|
20
|
+
primary?: string;
|
|
21
|
+
secondary?: string;
|
|
22
|
+
environments?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Optional copy / behavior overrides for {@link SelectionTableDialog}.
|
|
26
|
+
* Omit a key to keep the default.
|
|
27
|
+
*/
|
|
28
|
+
export type SelectionTableDialogOverrides = {
|
|
29
|
+
eyebrow?: string;
|
|
30
|
+
columns?: SelectionTableColumnLabels;
|
|
31
|
+
/** Show the environments column + chips. @default true when `environments` is non-empty */
|
|
32
|
+
showEnvironments?: boolean;
|
|
33
|
+
showEnvironmentFilter?: boolean;
|
|
34
|
+
showDestinationFilter?: boolean;
|
|
35
|
+
showSelectShown?: boolean;
|
|
36
|
+
showScrollControls?: boolean;
|
|
37
|
+
showRemove?: boolean;
|
|
38
|
+
searchPlaceholder?: string;
|
|
39
|
+
emptyLabel?: string;
|
|
40
|
+
noMatchLabel?: string;
|
|
41
|
+
/** Footer meta when nothing is checked (sync-all semantics). */
|
|
42
|
+
allSelectedFooter?: string;
|
|
43
|
+
/** Footer meta when one or more rows are checked. */
|
|
44
|
+
selectedFooter?: (count: number) => string;
|
|
45
|
+
applyLabel?: string;
|
|
46
|
+
cancelLabel?: string;
|
|
47
|
+
removeLabel?: string;
|
|
48
|
+
/** Extra filter columns merged after built-in ones. */
|
|
49
|
+
extraFilters?: TableFilterColumn[];
|
|
50
|
+
/** Replace built-in filters entirely. */
|
|
51
|
+
filters?: TableFilterColumn[];
|
|
52
|
+
/** Custom cell render for the secondary column. */
|
|
53
|
+
renderSecondary?: (row: SelectionTableRow) => ReactNode;
|
|
54
|
+
/** Custom cell render for an extra column after environments. */
|
|
55
|
+
renderExtra?: (row: SelectionTableRow) => ReactNode;
|
|
56
|
+
extraColumnLabel?: string;
|
|
57
|
+
};
|
|
58
|
+
export type SelectionTableDialogProps = {
|
|
59
|
+
title: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Shown next to filters, e.g. "Source: Qual". */
|
|
62
|
+
sourceLabel?: string;
|
|
63
|
+
rows: SelectionTableRow[];
|
|
64
|
+
/** Catalog environments — drives env chips + default filters. */
|
|
65
|
+
environments: AdminEnvironment[];
|
|
66
|
+
/** Env ids treated as destinations for the “missing / present” filter. */
|
|
67
|
+
destinationEnvIds?: string[];
|
|
68
|
+
selectedIds: string[];
|
|
69
|
+
overrides?: SelectionTableDialogOverrides;
|
|
70
|
+
onClose: () => void;
|
|
71
|
+
onApply: (ids: string[]) => void;
|
|
72
|
+
onRemove?: () => void;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Standard multi-select table dialog (search, filter chips, sort).
|
|
76
|
+
* Used by Sync target pickers; overrides let hosts tune copy and columns.
|
|
77
|
+
*/
|
|
78
|
+
export declare function SelectionTableDialog({ title, description, sourceLabel, rows, environments, destinationEnvIds, selectedIds, overrides, onClose, onApply, onRemove, }: SelectionTableDialogProps): import("react").JSX.Element;
|
|
79
|
+
//# sourceMappingURL=SelectionTableDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionTableDialog.d.ts","sourceRoot":"","sources":["../../../src/components/dialog/SelectionTableDialog.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { SortableHeader } from "../resources/SortableHeader";
|
|
4
|
+
import { TableFilters, TableScrollControls, TableSearch, TableWrap, matchesTableFilters, matchesTableQuery, useTableOverflow, } from "../table";
|
|
5
|
+
import { compareFieldValues, nextTableSort, } from "../../lib/sort";
|
|
6
|
+
function defaultDestinationIds(environments, destinationEnvIds, sourceLabel) {
|
|
7
|
+
if (destinationEnvIds?.length)
|
|
8
|
+
return destinationEnvIds;
|
|
9
|
+
// Best-effort: every env except one matching the source label text.
|
|
10
|
+
if (!sourceLabel)
|
|
11
|
+
return environments.map((env) => env.id).slice(1);
|
|
12
|
+
const source = environments.find((env) => env.label === sourceLabel ||
|
|
13
|
+
env.id === sourceLabel ||
|
|
14
|
+
sourceLabel.toLowerCase().includes(env.label.toLowerCase()));
|
|
15
|
+
return environments
|
|
16
|
+
.map((env) => env.id)
|
|
17
|
+
.filter((id) => id !== source?.id);
|
|
18
|
+
}
|
|
19
|
+
function EnvChips({ activeIds, columns, }) {
|
|
20
|
+
const active = new Set(activeIds);
|
|
21
|
+
return (_jsx("span", { className: "vertebr-env-chips", "aria-label": "Active environments", children: columns.map((env) => {
|
|
22
|
+
const on = active.has(env.id);
|
|
23
|
+
return (_jsxs("span", { className: "vertebr-env-chip", "data-on": on ? "true" : "false", "data-tone": env.tone ?? "neutral", title: on ? `Active in ${env.label}` : `Not in ${env.label}`, children: [_jsx("span", { className: "vertebr-env-chip-dot", "aria-hidden": "true" }), env.label] }, env.id));
|
|
24
|
+
}) }));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Standard multi-select table dialog (search, filter chips, sort).
|
|
28
|
+
* Used by Sync target pickers; overrides let hosts tune copy and columns.
|
|
29
|
+
*/
|
|
30
|
+
export function SelectionTableDialog({ title, description, sourceLabel, rows, environments, destinationEnvIds, selectedIds, overrides = {}, onClose, onApply, onRemove, }) {
|
|
31
|
+
const { eyebrow = "Selection", columns: columnLabels = {}, showEnvironments = environments.length > 0, showEnvironmentFilter = true, showDestinationFilter = true, showSelectShown = true, showScrollControls = false, showRemove = Boolean(onRemove), searchPlaceholder = "Search…", emptyLabel = "Nothing available.", noMatchLabel = "No matching rows.", allSelectedFooter = "All items", selectedFooter = (count) => `${count} selected`, applyLabel = "Apply", cancelLabel = "Cancel", removeLabel = "Remove", extraFilters, filters: filtersOverride, renderSecondary, renderExtra, extraColumnLabel, } = overrides;
|
|
32
|
+
const primaryLabel = columnLabels.primary ?? "Id";
|
|
33
|
+
const secondaryLabel = columnLabels.secondary ?? "Name";
|
|
34
|
+
const environmentsLabel = columnLabels.environments ?? "Environments";
|
|
35
|
+
const destIds = useMemo(() => defaultDestinationIds(environments, destinationEnvIds, sourceLabel), [environments, destinationEnvIds, sourceLabel]);
|
|
36
|
+
const normalizedRows = useMemo(() => {
|
|
37
|
+
return rows.map((row) => {
|
|
38
|
+
const activeEnvs = row.environments ?? [];
|
|
39
|
+
const missingOnDest = destIds.length > 0 &&
|
|
40
|
+
destIds.some((envId) => !activeEnvs.includes(envId));
|
|
41
|
+
const destination = String(row.filterValues?.destination ?? "") ||
|
|
42
|
+
(missingOnDest ? "missing" : "present");
|
|
43
|
+
const envSortKey = activeEnvs
|
|
44
|
+
.map((envId) => environments.find((env) => env.id === envId)?.label ?? envId)
|
|
45
|
+
.join(", ");
|
|
46
|
+
return {
|
|
47
|
+
...row,
|
|
48
|
+
environments: activeEnvs,
|
|
49
|
+
destination,
|
|
50
|
+
envSortKey,
|
|
51
|
+
filterValues: {
|
|
52
|
+
...(row.filterValues ?? {}),
|
|
53
|
+
destination,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}, [rows, destIds, environments]);
|
|
58
|
+
const [draft, setDraft] = useState(() => [...selectedIds]);
|
|
59
|
+
const [query, setQuery] = useState("");
|
|
60
|
+
const [filters, setFilters] = useState({});
|
|
61
|
+
const [sort, setSort] = useState({
|
|
62
|
+
field: "primary",
|
|
63
|
+
order: "asc",
|
|
64
|
+
});
|
|
65
|
+
const tableOverflow = useTableOverflow(normalizedRows.length);
|
|
66
|
+
const filterColumns = useMemo(() => {
|
|
67
|
+
if (filtersOverride)
|
|
68
|
+
return filtersOverride;
|
|
69
|
+
const cols = [];
|
|
70
|
+
if (showEnvironmentFilter && environments.length > 0) {
|
|
71
|
+
cols.push({
|
|
72
|
+
id: "environments",
|
|
73
|
+
label: "Environment",
|
|
74
|
+
multiselect: true,
|
|
75
|
+
options: environments.map((env) => ({
|
|
76
|
+
value: env.id,
|
|
77
|
+
label: env.label,
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (showDestinationFilter && destIds.length > 0) {
|
|
82
|
+
cols.push({
|
|
83
|
+
id: "destination",
|
|
84
|
+
label: "Destination",
|
|
85
|
+
multiselect: true,
|
|
86
|
+
options: [
|
|
87
|
+
{ value: "missing", label: "Missing on destination" },
|
|
88
|
+
{ value: "present", label: "Already on destination" },
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (extraFilters?.length)
|
|
93
|
+
cols.push(...extraFilters);
|
|
94
|
+
return cols;
|
|
95
|
+
}, [
|
|
96
|
+
filtersOverride,
|
|
97
|
+
showEnvironmentFilter,
|
|
98
|
+
showDestinationFilter,
|
|
99
|
+
environments,
|
|
100
|
+
destIds.length,
|
|
101
|
+
extraFilters,
|
|
102
|
+
]);
|
|
103
|
+
const filterGetters = useMemo(() => ({
|
|
104
|
+
environments: (row) => row.environments ?? [],
|
|
105
|
+
destination: (row) => row.filterValues?.destination ?? row.destination,
|
|
106
|
+
}), []);
|
|
107
|
+
const visibleRows = useMemo(() => {
|
|
108
|
+
const searched = query.trim()
|
|
109
|
+
? normalizedRows.filter((row) => matchesTableQuery(row.primary, query) ||
|
|
110
|
+
matchesTableQuery(row.secondary, query) ||
|
|
111
|
+
matchesTableQuery(row.envSortKey, query))
|
|
112
|
+
: normalizedRows;
|
|
113
|
+
const filtered = searched.filter((row) => matchesTableFilters(row, filters, {
|
|
114
|
+
...filterGetters,
|
|
115
|
+
...Object.fromEntries((extraFilters ?? []).map((column) => [
|
|
116
|
+
column.id,
|
|
117
|
+
(item) => item.filterValues?.[column.id],
|
|
118
|
+
])),
|
|
119
|
+
}));
|
|
120
|
+
return [...filtered].sort((left, right) => {
|
|
121
|
+
if (sort.field === "primary") {
|
|
122
|
+
return compareFieldValues(left.primary, right.primary, "string", sort.order);
|
|
123
|
+
}
|
|
124
|
+
if (sort.field === "secondary") {
|
|
125
|
+
return compareFieldValues(left.secondary, right.secondary, "string", sort.order);
|
|
126
|
+
}
|
|
127
|
+
if (sort.field === "environments") {
|
|
128
|
+
return compareFieldValues(left.envSortKey, right.envSortKey, "string", sort.order);
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
});
|
|
132
|
+
}, [
|
|
133
|
+
normalizedRows,
|
|
134
|
+
query,
|
|
135
|
+
filters,
|
|
136
|
+
filterGetters,
|
|
137
|
+
extraFilters,
|
|
138
|
+
sort,
|
|
139
|
+
]);
|
|
140
|
+
const allVisibleSelected = visibleRows.length > 0 &&
|
|
141
|
+
visibleRows.every((row) => draft.includes(row.id));
|
|
142
|
+
const toggle = (id) => {
|
|
143
|
+
setDraft((current) => current.includes(id)
|
|
144
|
+
? current.filter((item) => item !== id)
|
|
145
|
+
: [...current, id]);
|
|
146
|
+
};
|
|
147
|
+
const toggleAllVisible = () => {
|
|
148
|
+
setDraft((current) => {
|
|
149
|
+
if (allVisibleSelected) {
|
|
150
|
+
const drop = new Set(visibleRows.map((row) => row.id));
|
|
151
|
+
return current.filter((id) => !drop.has(id));
|
|
152
|
+
}
|
|
153
|
+
const next = new Set(current);
|
|
154
|
+
for (const row of visibleRows)
|
|
155
|
+
next.add(row.id);
|
|
156
|
+
return [...next];
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
const sortHeader = (field, label, preferredFirst = "asc") => {
|
|
160
|
+
const active = sort.field === field;
|
|
161
|
+
return (_jsx("th", { children: _jsx(SortableHeader, { label: label, active: active, order: active ? sort.order : undefined, onSort: () => setSort(nextTableSort(sort, field, preferredFirst)) }) }));
|
|
162
|
+
};
|
|
163
|
+
return (_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal vertebr-selection-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "vertebr-selection-dialog-title", onClick: (event) => event.stopPropagation(), children: [_jsxs("header", { className: "vertebr-modal-head vertebr-selection-dialog-head", children: [_jsxs("div", { className: "vertebr-modal-heading", children: [eyebrow ? (_jsx("p", { className: "vertebr-modal-eyebrow", children: eyebrow })) : null, _jsx("h2", { id: "vertebr-selection-dialog-title", children: title }), description ? (_jsx("p", { className: "vertebr-selection-dialog-sub", children: description })) : null] }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", "aria-label": "Close", onClick: onClose, children: "\u2715" })] }), _jsxs("div", { className: "vertebr-selection-dialog-body", children: [_jsxs("div", { className: "vertebr-table-toolbar vertebr-selection-dialog-toolbar", children: [_jsxs("div", { className: "vertebr-table-toolbar-start", children: [sourceLabel ? (_jsx("span", { className: "vertebr-selection-dialog-source", children: sourceLabel.startsWith("Source")
|
|
164
|
+
? sourceLabel
|
|
165
|
+
: `Source: ${sourceLabel}` })) : null, filterColumns.length > 0 ? (_jsx(TableFilters, { columns: filterColumns, selected: filters, onChange: setFilters })) : null] }), _jsxs("div", { className: "vertebr-selection-dialog-toolbar-end", children: [_jsx(TableSearch, { value: query, onChange: setQuery, "aria-label": searchPlaceholder, placeholder: searchPlaceholder }), showSelectShown ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: visibleRows.length === 0, onClick: toggleAllVisible, children: allVisibleSelected ? "Clear shown" : "Select shown" })) : null, showScrollControls ? (_jsx(TableScrollControls, { canLeft: tableOverflow.canLeft, canRight: tableOverflow.canRight, onScroll: tableOverflow.scrollBy })) : null, _jsx("span", { className: "vertebr-count", children: visibleRows.length !== normalizedRows.length
|
|
166
|
+
? `${visibleRows.length} of ${normalizedRows.length}`
|
|
167
|
+
: normalizedRows.length })] })] }), normalizedRows.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: emptyLabel })) : visibleRows.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: noMatchLabel })) : (_jsx(TableWrap, { overflow: tableOverflow, transitionKey: JSON.stringify(filters), children: _jsxs("table", { className: "vertebr-table vertebr-selection-dialog-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: "vertebr-selection-dialog-col-check", children: _jsx("span", { className: "vertebr-sr-only", children: "Select" }) }), sortHeader("primary", primaryLabel), sortHeader("secondary", secondaryLabel), showEnvironments
|
|
168
|
+
? sortHeader("environments", environmentsLabel)
|
|
169
|
+
: null, renderExtra ? (_jsx("th", { children: extraColumnLabel ?? "" })) : null] }) }), _jsx("tbody", { children: visibleRows.map((row) => {
|
|
170
|
+
const on = draft.includes(row.id);
|
|
171
|
+
return (_jsxs("tr", { "data-selected": on || undefined, onClick: () => toggle(row.id), children: [_jsx("td", { className: "vertebr-selection-dialog-col-check", children: _jsx("input", { type: "checkbox", checked: on, onChange: () => toggle(row.id), onClick: (event) => event.stopPropagation(), "aria-label": `Select ${row.primary}` }) }), _jsx("td", { children: _jsx("code", { className: "vertebr-selection-dialog-primary", children: row.primary }) }), _jsx("td", { children: renderSecondary
|
|
172
|
+
? renderSecondary(row)
|
|
173
|
+
: row.secondary || "—" }), showEnvironments ? (_jsx("td", { children: _jsx(EnvChips, { activeIds: row.environments ?? [], columns: environments }) })) : null, renderExtra ? _jsx("td", { children: renderExtra(row) }) : null] }, row.id));
|
|
174
|
+
}) })] }) }))] }), _jsxs("footer", { className: "vertebr-modal-foot vertebr-selection-dialog-foot", children: [_jsx("div", { className: "vertebr-selection-dialog-foot-meta", children: draft.length === 0
|
|
175
|
+
? allSelectedFooter
|
|
176
|
+
: selectedFooter(draft.length) }), _jsxs("div", { className: "vertebr-selection-dialog-foot-actions", children: [showRemove && onRemove ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onRemove, children: removeLabel })) : null, _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onClose, children: cancelLabel }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: () => onApply(draft), children: applyLabel })] })] })] }) }));
|
|
177
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type { EmailsLibraryPanelProps } from "./components/emails/EmailsLibraryP
|
|
|
12
12
|
export type { EmailsActivityPanelProps } from "./components/emails/EmailsActivityPanel";
|
|
13
13
|
export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
|
|
14
14
|
export { SortableHeader } from "./components/resources/SortableHeader";
|
|
15
|
+
export { SelectionTableDialog } from "./components/dialog/SelectionTableDialog";
|
|
16
|
+
export type { SelectionTableDialogProps, SelectionTableDialogOverrides, SelectionTableRow, SelectionTableColumnLabels, } from "./components/dialog/SelectionTableDialog";
|
|
15
17
|
export { compareFieldValues, nextTableSort, sortRows, type SortOrder, type TableSortState, } from "./lib/sort";
|
|
16
18
|
export { ThemeToggle } from "./components/theme/ThemeToggle";
|
|
17
19
|
export type { ThemeToggleProps } from "./components/theme/ThemeToggle";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { FontPickerField } from "./components/fonts/FontPickerField";
|
|
|
6
6
|
export { EmailsBoard } from "./components/emails/EmailsBoard";
|
|
7
7
|
export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
|
|
8
8
|
export { SortableHeader } from "./components/resources/SortableHeader";
|
|
9
|
+
export { SelectionTableDialog } from "./components/dialog/SelectionTableDialog";
|
|
9
10
|
export { compareFieldValues, nextTableSort, sortRows, } from "./lib/sort";
|
|
10
11
|
export { ThemeToggle } from "./components/theme/ThemeToggle";
|
|
11
12
|
export { ConfigForm } from "./components/config/ConfigForm";
|
package/dist/styles/index.css
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/* ── Selection table dialog (Sync pickers, multi-select tables) ─ */
|
|
2
|
+
|
|
3
|
+
.vertebr-selection-dialog {
|
|
4
|
+
width: min(52rem, calc(100vw - 2rem));
|
|
5
|
+
max-height: min(40rem, calc(100vh - 2rem));
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.vertebr-selection-dialog-head {
|
|
11
|
+
align-items: flex-start;
|
|
12
|
+
flex: 0 0 auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.vertebr-selection-dialog-sub {
|
|
16
|
+
margin: 0.4rem 0 0;
|
|
17
|
+
max-width: 40rem;
|
|
18
|
+
font-size: var(--vb-font-size-100);
|
|
19
|
+
color: var(--vb-text-muted);
|
|
20
|
+
line-height: 1.45;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.vertebr-selection-dialog-body {
|
|
24
|
+
flex: 1 1 auto;
|
|
25
|
+
min-height: 0;
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
border-top: 1px solid var(--vb-border-subdued);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vertebr-selection-dialog-toolbar {
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
gap: 0.55rem 0.75rem;
|
|
35
|
+
align-items: center;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.vertebr-selection-dialog-toolbar .vertebr-table-toolbar-start {
|
|
39
|
+
flex: 1 1 auto;
|
|
40
|
+
min-width: 0;
|
|
41
|
+
gap: 0.45rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.vertebr-selection-dialog-toolbar-end {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.45rem;
|
|
49
|
+
margin-left: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.vertebr-selection-dialog-source {
|
|
53
|
+
font-size: var(--vb-font-size-100);
|
|
54
|
+
color: var(--vb-text-subtle);
|
|
55
|
+
white-space: nowrap;
|
|
56
|
+
padding-right: 0.25rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.vertebr-selection-dialog .vertebr-table-filter-menu {
|
|
60
|
+
z-index: 50;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.vertebr-selection-dialog-body .vertebr-table-wrap {
|
|
64
|
+
flex: 1 1 auto;
|
|
65
|
+
min-height: 0;
|
|
66
|
+
border: 0;
|
|
67
|
+
border-radius: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.vertebr-selection-dialog-body .vertebr-empty {
|
|
71
|
+
margin: 1.25rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.vertebr-env-chips {
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
flex-wrap: wrap;
|
|
77
|
+
gap: 0.35rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.vertebr-env-chip {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 0.35rem;
|
|
84
|
+
padding: 0.2rem 0.55rem 0.2rem 0.4rem;
|
|
85
|
+
border-radius: 999px;
|
|
86
|
+
border: 1px solid var(--vb-border-subdued);
|
|
87
|
+
background: var(--vb-bg-muted);
|
|
88
|
+
color: var(--vb-text-subtle);
|
|
89
|
+
font-size: 0.72rem;
|
|
90
|
+
font-weight: var(--vb-font-weight-medium);
|
|
91
|
+
letter-spacing: 0.01em;
|
|
92
|
+
line-height: 1.2;
|
|
93
|
+
white-space: nowrap;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.vertebr-env-chip-dot {
|
|
97
|
+
width: 0.45rem;
|
|
98
|
+
height: 0.45rem;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
background: var(--vb-border-strong);
|
|
101
|
+
flex: 0 0 auto;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.vertebr-env-chip[data-on="true"] {
|
|
105
|
+
border-color: color-mix(in srgb, var(--vb-accent) 35%, var(--vb-border));
|
|
106
|
+
background: color-mix(in srgb, var(--vb-accent) 12%, var(--vb-bg-elevated));
|
|
107
|
+
color: var(--vb-text);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.vertebr-env-chip[data-on="true"] .vertebr-env-chip-dot {
|
|
111
|
+
background: var(--vb-accent);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.vertebr-env-chip[data-on="true"][data-tone="danger"] {
|
|
115
|
+
border-color: color-mix(in srgb, var(--vb-danger, #b42318) 40%, var(--vb-border));
|
|
116
|
+
background: color-mix(
|
|
117
|
+
in srgb,
|
|
118
|
+
var(--vb-danger, #b42318) 10%,
|
|
119
|
+
var(--vb-bg-elevated)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.vertebr-env-chip[data-on="true"][data-tone="danger"] .vertebr-env-chip-dot {
|
|
124
|
+
background: var(--vb-danger, #b42318);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.vertebr-env-chip[data-on="false"] {
|
|
128
|
+
opacity: 0.72;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.vertebr-selection-dialog-table tbody tr {
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.vertebr-selection-dialog-table tbody tr[data-selected="true"] {
|
|
136
|
+
background: color-mix(in srgb, var(--vb-accent) 8%, transparent);
|
|
137
|
+
box-shadow: inset 3px 0 0 var(--vb-accent);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.vertebr-selection-dialog-col-check {
|
|
141
|
+
width: 2.25rem;
|
|
142
|
+
text-align: center;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.vertebr-selection-dialog-primary {
|
|
146
|
+
font-family: var(--vb-font-mono, ui-monospace, monospace);
|
|
147
|
+
font-size: 0.92em;
|
|
148
|
+
font-weight: var(--vb-font-weight-semibold);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.vertebr-selection-dialog-foot {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-wrap: wrap;
|
|
154
|
+
align-items: center;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
gap: var(--vb-space-300);
|
|
157
|
+
flex: 0 0 auto;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.vertebr-selection-dialog-foot-meta {
|
|
161
|
+
font-size: var(--vb-font-size-100);
|
|
162
|
+
color: var(--vb-text-muted);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.vertebr-selection-dialog-foot-actions {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-wrap: wrap;
|
|
168
|
+
gap: var(--vb-space-200);
|
|
169
|
+
margin-left: auto;
|
|
170
|
+
}
|
package/dist/styles/sync.css
CHANGED
|
@@ -344,6 +344,23 @@
|
|
|
344
344
|
line-height: 1.45;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
.vertebr-sync-target-selection {
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-wrap: wrap;
|
|
350
|
+
align-items: center;
|
|
351
|
+
gap: 0.35rem 0.65rem;
|
|
352
|
+
margin-top: 0.15rem;
|
|
353
|
+
font-size: var(--vb-font-size-100);
|
|
354
|
+
font-weight: var(--vb-font-weight-medium);
|
|
355
|
+
color: var(--vb-text);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.vertebr-sync-target-selection-hint {
|
|
359
|
+
font-size: var(--vb-font-size-100);
|
|
360
|
+
font-weight: var(--vb-font-weight-regular);
|
|
361
|
+
color: var(--vb-text-subtle);
|
|
362
|
+
}
|
|
363
|
+
|
|
347
364
|
.vertebr-sync-target-badges {
|
|
348
365
|
display: flex;
|
|
349
366
|
flex-direction: column;
|