@focus-reactive/payload-plugin-translator 0.9.2 → 0.10.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 +25 -0
- package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslation.d.ts +1 -1
- package/dist/client/entities/translation/ui/AutoTranslateMarker/AutoTranslateMarker.d.ts +3 -4
- package/dist/client/entities/translation/ui/AutoTranslateMarker/AutoTranslateMarker.js +8 -12
- package/dist/client/entities/translation/ui/AutoTranslateMarker/styles.module.scss +3 -12
- package/dist/client/features/collection-translation-form/model/schema.d.ts +3 -3
- package/dist/client/features/collection-translation-form/model/schema.js +8 -1
- package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.d.ts +3 -1
- package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.js +36 -6
- package/dist/client/features/translate-document-form/model/schema.d.ts +3 -3
- package/dist/client/features/translate-document-form/model/schema.js +8 -1
- package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.d.ts +3 -1
- package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.js +36 -6
- package/dist/client/shared/lib/forms/pruneSourceFromTarget.d.ts +7 -0
- package/dist/client/shared/lib/forms/pruneSourceFromTarget.js +14 -0
- package/dist/client/shared/ui/InfoPopover/InfoPopover.d.ts +21 -0
- package/dist/client/shared/ui/InfoPopover/InfoPopover.js +45 -0
- package/dist/client/shared/ui/InfoPopover/index.d.ts +1 -0
- package/dist/client/shared/ui/InfoPopover/index.js +3 -0
- package/dist/client/shared/ui/InfoPopover/styles.module.scss +49 -0
- package/dist/client/shared/ui/MultiSelect/MultiSelect.d.ts +40 -0
- package/dist/client/shared/ui/MultiSelect/MultiSelect.js +129 -0
- package/dist/client/shared/ui/MultiSelect/index.d.ts +2 -0
- package/dist/client/shared/ui/MultiSelect/index.js +3 -0
- package/dist/client/shared/ui/MultiSelect/multiSelectModel.d.ts +25 -0
- package/dist/client/shared/ui/MultiSelect/multiSelectModel.js +41 -0
- package/dist/client/shared/ui/MultiSelect/styles.module.scss +126 -0
- package/dist/client/shared/ui/form/FormCheckboxPublish/FormCheckboxPublish.js +4 -7
- package/dist/client/shared/ui/form/FormMultiSelect/FormMultiSelect.d.ts +22 -0
- package/dist/client/shared/ui/form/FormMultiSelect/FormMultiSelect.js +70 -0
- package/dist/client/shared/ui/form/FormMultiSelect/index.d.ts +1 -0
- package/dist/client/shared/ui/form/FormMultiSelect/index.js +3 -0
- package/dist/client/shared/ui/form/FormSelectStrategy/FormSelectStrategy.js +4 -7
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.d.ts +3 -1
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export.d.ts +3 -1
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export.js +5 -2
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.js +8 -3
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server.d.ts +2 -0
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server.js +2 -1
- package/dist/client/widgets/translate-document/ui/TranslateDocument.d.ts +3 -1
- package/dist/client/widgets/translate-document/ui/TranslateDocument.export.d.ts +3 -1
- package/dist/client/widgets/translate-document/ui/TranslateDocument.export.js +5 -2
- package/dist/client/widgets/translate-document/ui/TranslateDocument.js +7 -3
- package/dist/client/widgets/translate-document/ui/TranslateDocument.server.d.ts +2 -0
- package/dist/client/widgets/translate-document/ui/TranslateDocument.server.js +2 -1
- package/dist/composition/levels/collectionLevel.js +1 -1
- package/dist/composition/levels/documentLevel.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.js +3 -2
- package/dist/server/features/enqueue-translation/handler.js +28 -8
- package/dist/server/features/enqueue-translation/model.d.ts +3 -3
- package/dist/server/features/enqueue-translation/model.js +6 -1
- package/dist/server/features/enqueue-translation/resolveTargetLocales.d.ts +29 -0
- package/dist/server/features/enqueue-translation/resolveTargetLocales.js +41 -0
- package/dist/server/modules/auto-translate/index.d.ts +2 -0
- package/dist/server/modules/auto-translate/index.js +2 -0
- package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +2 -0
- package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -0
- package/dist/server/modules/translation-levels/types.d.ts +4 -0
- package/dist/types/TargetSelection.d.ts +10 -0
- package/dist/types/TargetSelection.js +11 -0
- package/package.json +1 -1
- package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslations.d.ts +0 -8
- package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslations.js +0 -20
- package/dist/client/shared/ui/form/FormSelectLocale/FormSelectLocale.d.ts +0 -11
- package/dist/client/shared/ui/form/FormSelectLocale/FormSelectLocale.js +0 -19
- package/dist/client/shared/ui/form/FormSelectLocale/index.d.ts +0 -1
- package/dist/client/shared/ui/form/FormSelectLocale/index.js +0 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { forwardRef, useState } from "react";
|
|
4
|
+
import { Checkbox } from "../Checkbox";
|
|
5
|
+
import Popup from "../Popup";
|
|
6
|
+
import { filterOptions, isAllSelected, selectAllValue, summarizeSelection, toggleValue } from "./multiSelectModel";
|
|
7
|
+
import styles from "./styles.module.scss";
|
|
8
|
+
// Same size ramp as the single Select, driven through CSS custom properties so both controls collapse
|
|
9
|
+
// to an identical footprint.
|
|
10
|
+
const sizes = {
|
|
11
|
+
sm: {
|
|
12
|
+
height: "24px",
|
|
13
|
+
paddingInline: "0.5rem",
|
|
14
|
+
fontSize: "0.875rem"
|
|
15
|
+
},
|
|
16
|
+
md: {
|
|
17
|
+
height: "32px",
|
|
18
|
+
paddingInline: "0.5rem",
|
|
19
|
+
fontSize: "1rem"
|
|
20
|
+
},
|
|
21
|
+
lg: {
|
|
22
|
+
height: "40px",
|
|
23
|
+
paddingInline: "0.75rem",
|
|
24
|
+
fontSize: "1rem"
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A controlled multi-select whose collapsed trigger is a `<button>` styled identically to the single
|
|
29
|
+
* `Select` (same border, chevron, focus ring, error + disabled states, size ramp). The multiplicity
|
|
30
|
+
* lives in a checkbox popover with a "select all" row and a progressive filter — the trigger shows a
|
|
31
|
+
* compact summary (`de, fr +2` / `All (N)`) and never grows with the selection. All decision logic
|
|
32
|
+
* lives in `./multiSelectModel`; the popover width tracks the trigger and grows to its content.
|
|
33
|
+
*/ const MultiSelect = /*#__PURE__*/ forwardRef(function MultiSelect({ value, onChange, onBlur, options, placeholder = "-", hasError = false, $size = "lg", $fullWidth = true, selectAllLabel = "Select all", filterThreshold = 8, className = "", disabled, style, "aria-invalid": ariaInvalid, ...rest }, ref) {
|
|
34
|
+
const [open, setOpen] = useState(false);
|
|
35
|
+
const [query, setQuery] = useState("");
|
|
36
|
+
// No options to choose → nothing the popover can do; present as disabled (mirrors an empty select).
|
|
37
|
+
const isDisabled = Boolean(disabled) || options.length === 0;
|
|
38
|
+
const selected = new Set(value);
|
|
39
|
+
const allSelected = isAllSelected(value, options);
|
|
40
|
+
const showFilter = options.length > filterThreshold;
|
|
41
|
+
const visibleOptions = showFilter ? filterOptions(options, query) : options;
|
|
42
|
+
const _style = {
|
|
43
|
+
"--height": sizes[$size].height,
|
|
44
|
+
"--padding-inline": sizes[$size].paddingInline,
|
|
45
|
+
"--font-size": sizes[$size].fontSize,
|
|
46
|
+
...style
|
|
47
|
+
};
|
|
48
|
+
const triggerClassName = classNames(styles.trigger, styles[`size-${$size}`], {
|
|
49
|
+
[styles.triggerError]: hasError,
|
|
50
|
+
[styles.auto]: !$fullWidth
|
|
51
|
+
}, className);
|
|
52
|
+
const trigger = /*#__PURE__*/ _jsx("button", {
|
|
53
|
+
ref: ref,
|
|
54
|
+
type: "button",
|
|
55
|
+
className: triggerClassName,
|
|
56
|
+
style: _style,
|
|
57
|
+
disabled: isDisabled,
|
|
58
|
+
"aria-invalid": ariaInvalid ?? hasError,
|
|
59
|
+
...rest,
|
|
60
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
61
|
+
className: styles.summary,
|
|
62
|
+
children: summarizeSelection(value, options, placeholder)
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
const handleOpenChange = (next)=>{
|
|
66
|
+
setOpen(next);
|
|
67
|
+
// Popover closed → the user is done with the field; only now mark it touched.
|
|
68
|
+
if (!next) onBlur?.();
|
|
69
|
+
};
|
|
70
|
+
return /*#__PURE__*/ _jsx(Popup, {
|
|
71
|
+
open: open && !isDisabled,
|
|
72
|
+
onOpenChange: handleOpenChange,
|
|
73
|
+
$trigger: trigger,
|
|
74
|
+
$align: "start",
|
|
75
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
76
|
+
className: styles.menu,
|
|
77
|
+
role: "group",
|
|
78
|
+
"aria-label": selectAllLabel,
|
|
79
|
+
children: [
|
|
80
|
+
showFilter && /*#__PURE__*/ _jsx("input", {
|
|
81
|
+
className: styles.filter,
|
|
82
|
+
type: "text",
|
|
83
|
+
value: query,
|
|
84
|
+
placeholder: "Filter…",
|
|
85
|
+
onChange: (event)=>setQuery(event.target.value),
|
|
86
|
+
"aria-label": "Filter options"
|
|
87
|
+
}),
|
|
88
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
89
|
+
className: styles.option,
|
|
90
|
+
children: [
|
|
91
|
+
/*#__PURE__*/ _jsx(Checkbox, {
|
|
92
|
+
checked: allSelected,
|
|
93
|
+
onChange: ()=>onChange(selectAllValue(value, options)),
|
|
94
|
+
"aria-label": selectAllLabel
|
|
95
|
+
}),
|
|
96
|
+
/*#__PURE__*/ _jsx("span", {
|
|
97
|
+
className: styles.optionLabel,
|
|
98
|
+
children: selectAllLabel
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
}),
|
|
102
|
+
/*#__PURE__*/ _jsx("div", {
|
|
103
|
+
className: styles.divider,
|
|
104
|
+
role: "separator"
|
|
105
|
+
}),
|
|
106
|
+
visibleOptions.map((option)=>/*#__PURE__*/ _jsxs("label", {
|
|
107
|
+
className: styles.option,
|
|
108
|
+
children: [
|
|
109
|
+
/*#__PURE__*/ _jsx(Checkbox, {
|
|
110
|
+
checked: selected.has(option.value),
|
|
111
|
+
onChange: ()=>onChange(toggleValue(value, option.value))
|
|
112
|
+
}),
|
|
113
|
+
/*#__PURE__*/ _jsx("span", {
|
|
114
|
+
className: styles.optionCode,
|
|
115
|
+
children: option.value
|
|
116
|
+
}),
|
|
117
|
+
option.label !== option.value && /*#__PURE__*/ _jsx("span", {
|
|
118
|
+
className: styles.optionLabel,
|
|
119
|
+
children: option.label
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
}, option.value))
|
|
123
|
+
]
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
export default MultiSelect;
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=MultiSelect.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** An option in a {@link MultiSelect} — same shape as the single `Select`'s option list. */
|
|
2
|
+
export type MultiSelectOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* The selected values in option order, restricted to values that still exist in `options`. Using this
|
|
8
|
+
* everywhere (display + counts) keeps the control stable when the selection holds a stale value that is
|
|
9
|
+
* no longer a configured option — it is simply ignored rather than skewing the summary.
|
|
10
|
+
*/
|
|
11
|
+
export declare function orderedSelection(value: string[], options: MultiSelectOption[]): string[];
|
|
12
|
+
/** True when every option is selected (and there is at least one option). */
|
|
13
|
+
export declare function isAllSelected(value: string[], options: MultiSelectOption[]): boolean;
|
|
14
|
+
/** Toggle one value in/out of the selection, preserving the existing order (append on add). */
|
|
15
|
+
export declare function toggleValue(value: string[], option: string): string[];
|
|
16
|
+
/** The next value for a "select all" toggle: clear when all are already selected, else select all. */
|
|
17
|
+
export declare function selectAllValue(value: string[], options: MultiSelectOption[]): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Compact single-line trigger summary — `placeholder` when nothing is selected, `All (N)` when every
|
|
20
|
+
* option is, otherwise up to two locale codes followed by a `+N` overflow (e.g. `de, fr +2`). Ordered
|
|
21
|
+
* by option order so it never depends on the click order.
|
|
22
|
+
*/
|
|
23
|
+
export declare function summarizeSelection(value: string[], options: MultiSelectOption[], placeholder: string): string;
|
|
24
|
+
/** Case-insensitive filter over option value + label; a blank/whitespace query returns all options. */
|
|
25
|
+
export declare function filterOptions(options: MultiSelectOption[], query: string): MultiSelectOption[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** An option in a {@link MultiSelect} — same shape as the single `Select`'s option list. */ /**
|
|
2
|
+
* The selected values in option order, restricted to values that still exist in `options`. Using this
|
|
3
|
+
* everywhere (display + counts) keeps the control stable when the selection holds a stale value that is
|
|
4
|
+
* no longer a configured option — it is simply ignored rather than skewing the summary.
|
|
5
|
+
*/ export function orderedSelection(value, options) {
|
|
6
|
+
const selected = new Set(value);
|
|
7
|
+
return options.filter((option)=>selected.has(option.value)).map((option)=>option.value);
|
|
8
|
+
}
|
|
9
|
+
/** True when every option is selected (and there is at least one option). */ export function isAllSelected(value, options) {
|
|
10
|
+
if (options.length === 0) return false;
|
|
11
|
+
const selected = new Set(value);
|
|
12
|
+
return options.every((option)=>selected.has(option.value));
|
|
13
|
+
}
|
|
14
|
+
/** Toggle one value in/out of the selection, preserving the existing order (append on add). */ export function toggleValue(value, option) {
|
|
15
|
+
return value.includes(option) ? value.filter((v)=>v !== option) : [
|
|
16
|
+
...value,
|
|
17
|
+
option
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
/** The next value for a "select all" toggle: clear when all are already selected, else select all. */ export function selectAllValue(value, options) {
|
|
21
|
+
return isAllSelected(value, options) ? [] : options.map((option)=>option.value);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compact single-line trigger summary — `placeholder` when nothing is selected, `All (N)` when every
|
|
25
|
+
* option is, otherwise up to two locale codes followed by a `+N` overflow (e.g. `de, fr +2`). Ordered
|
|
26
|
+
* by option order so it never depends on the click order.
|
|
27
|
+
*/ export function summarizeSelection(value, options, placeholder) {
|
|
28
|
+
const selected = orderedSelection(value, options);
|
|
29
|
+
if (selected.length === 0) return placeholder;
|
|
30
|
+
if (isAllSelected(value, options)) return `All (${options.length})`;
|
|
31
|
+
const shown = selected.slice(0, 2).join(", ");
|
|
32
|
+
const extra = selected.length - 2;
|
|
33
|
+
return extra > 0 ? `${shown} +${extra}` : shown;
|
|
34
|
+
}
|
|
35
|
+
/** Case-insensitive filter over option value + label; a blank/whitespace query returns all options. */ export function filterOptions(options, query) {
|
|
36
|
+
const q = query.trim().toLowerCase();
|
|
37
|
+
if (q.length === 0) return options;
|
|
38
|
+
return options.filter((option)=>option.value.toLowerCase().includes(q) || option.label.toLowerCase().includes(q));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=multiSelectModel.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// The collapsed trigger is a <button> made to look identical to the single Select's <select>:
|
|
2
|
+
// same box, same inline-SVG chevron, same focus ring / error / disabled states and size ramp.
|
|
3
|
+
.trigger {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: var(--height, 40px);
|
|
8
|
+
padding-top: calc((var(--height, 40px) - var(--font-size, 1rem) * 1.2) / 2);
|
|
9
|
+
padding-bottom: calc((var(--height, 40px) - var(--font-size, 1rem) * 1.2) / 2);
|
|
10
|
+
padding-left: var(--padding-inline, 0.75rem);
|
|
11
|
+
padding-right: calc(var(--padding-inline, 0.75rem) + 24px); // room for the chevron
|
|
12
|
+
border: 1px solid var(--theme-elevation-150);
|
|
13
|
+
border-radius: var(--style-radius-s);
|
|
14
|
+
background-color: var(--theme-input-bg);
|
|
15
|
+
color: var(--theme-elevation-800);
|
|
16
|
+
font-size: var(--font-size, 1rem);
|
|
17
|
+
text-align: left;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
|
|
21
|
+
// Same chevron as Select (inline SVG in the background), right-aligned.
|
|
22
|
+
appearance: none;
|
|
23
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 20 20' %3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
24
|
+
background-position: right var(--padding-inline, 0.75rem) center;
|
|
25
|
+
background-repeat: no-repeat;
|
|
26
|
+
background-size: 16px 16px;
|
|
27
|
+
|
|
28
|
+
&:focus {
|
|
29
|
+
outline: 2px solid var(--theme-success-200);
|
|
30
|
+
outline-offset: -1px;
|
|
31
|
+
border-color: var(--theme-success-500);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:disabled {
|
|
35
|
+
opacity: 0.5;
|
|
36
|
+
cursor: not-allowed;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Opt out of full width (`$fullWidth={false}`) — size to content, e.g. inline in a row.
|
|
41
|
+
.auto {
|
|
42
|
+
width: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.size-sm {
|
|
46
|
+
background-size: 12px 12px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.size-md {
|
|
50
|
+
background-size: 14px 14px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.size-lg {
|
|
54
|
+
background-size: 16px 16px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.triggerError {
|
|
58
|
+
border-color: var(--theme-error-600);
|
|
59
|
+
background-color: var(--theme-error-100);
|
|
60
|
+
|
|
61
|
+
&:focus {
|
|
62
|
+
outline: 2px solid var(--theme-error-200);
|
|
63
|
+
outline-offset: -1px;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.summary {
|
|
68
|
+
flex: 1;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.menu {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
gap: 0.125rem;
|
|
79
|
+
// Never narrower than the trigger, but grow to fit the option content (Radix exposes the width).
|
|
80
|
+
min-width: var(--radix-popover-trigger-width);
|
|
81
|
+
width: max-content;
|
|
82
|
+
max-width: 320px;
|
|
83
|
+
max-height: 280px;
|
|
84
|
+
overflow-y: auto;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.filter {
|
|
88
|
+
margin-bottom: 0.25rem;
|
|
89
|
+
padding: 0.25rem 0.5rem;
|
|
90
|
+
border: 1px solid var(--theme-elevation-150);
|
|
91
|
+
border-radius: var(--style-radius-s);
|
|
92
|
+
background: var(--theme-input-bg);
|
|
93
|
+
color: var(--theme-text);
|
|
94
|
+
font-size: 0.875rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.option {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 0.5rem;
|
|
101
|
+
padding: 0.25rem 0.375rem;
|
|
102
|
+
border-radius: var(--style-radius-s);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
font-size: 0.875rem;
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
|
|
107
|
+
&:hover {
|
|
108
|
+
background: var(--theme-elevation-100);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.optionCode {
|
|
113
|
+
font-variant-numeric: tabular-nums;
|
|
114
|
+
text-transform: uppercase;
|
|
115
|
+
font-size: 0.8125rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.optionLabel {
|
|
119
|
+
color: var(--theme-elevation-600);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.divider {
|
|
123
|
+
height: 1px;
|
|
124
|
+
margin: 0.125rem 0;
|
|
125
|
+
background: var(--theme-elevation-150);
|
|
126
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { FormCheckbox } from "../FormCheckbox";
|
|
3
|
-
import
|
|
4
|
-
import { QuestionCircleIcon } from "../../../lib/assets/icons/QuestionCircleIcon";
|
|
3
|
+
import InfoPopover from "../../InfoPopover";
|
|
5
4
|
import styles from "./styles.module.scss";
|
|
6
5
|
const PUBLISH_LABEL = "Publish after translation";
|
|
7
6
|
const PUBLISH_TOOLTIP = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
@@ -15,13 +14,11 @@ const PublishLabel = /*#__PURE__*/ _jsxs("span", {
|
|
|
15
14
|
className: styles.label,
|
|
16
15
|
children: [
|
|
17
16
|
PUBLISH_LABEL,
|
|
18
|
-
/*#__PURE__*/ _jsx(
|
|
17
|
+
/*#__PURE__*/ _jsx(InfoPopover, {
|
|
18
|
+
label: "What does publish after translation do?",
|
|
19
19
|
content: PUBLISH_TOOLTIP,
|
|
20
20
|
side: "bottom",
|
|
21
|
-
|
|
22
|
-
className: styles.label__icon,
|
|
23
|
-
children: /*#__PURE__*/ _jsx(QuestionCircleIcon, {})
|
|
24
|
-
})
|
|
21
|
+
className: styles.label__icon
|
|
25
22
|
})
|
|
26
23
|
]
|
|
27
24
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { MultiSelectOption } from "../../MultiSelect";
|
|
3
|
+
type FormMultiSelectProps = {
|
|
4
|
+
name: string;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
options: MultiSelectOption[];
|
|
7
|
+
label?: string | ReactElement;
|
|
8
|
+
description?: string;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
size?: "sm" | "md" | "lg";
|
|
12
|
+
"aria-label"?: string;
|
|
13
|
+
"aria-labelledby"?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Binds a react-hook-form field (a `string[]` value) to the {@link MultiSelect} primitive, with the
|
|
18
|
+
* same label / description / error affordances as {@link FormSelect}. The single-select analogue is
|
|
19
|
+
* `FormSelect`; this is its multi counterpart. A non-array field value is coerced to `[]`.
|
|
20
|
+
*/
|
|
21
|
+
export default function FormMultiSelect({ name, required, options, label, description, placeholder, disabled, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className, }: FormMultiSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import { useController } from "react-hook-form";
|
|
4
|
+
import Description from "../../Description";
|
|
5
|
+
import Label from "../../Label";
|
|
6
|
+
import MultiSelect from "../../MultiSelect";
|
|
7
|
+
/**
|
|
8
|
+
* Binds a react-hook-form field (a `string[]` value) to the {@link MultiSelect} primitive, with the
|
|
9
|
+
* same label / description / error affordances as {@link FormSelect}. The single-select analogue is
|
|
10
|
+
* `FormSelect`; this is its multi counterpart. A non-array field value is coerced to `[]`.
|
|
11
|
+
*/ export default function FormMultiSelect({ name, required, options, label, description, placeholder, disabled, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className }) {
|
|
12
|
+
const { field, fieldState, formState } = useController({
|
|
13
|
+
name,
|
|
14
|
+
rules: {
|
|
15
|
+
required
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const errorId = useId();
|
|
19
|
+
const descriptionId = useId();
|
|
20
|
+
const value = Array.isArray(field.value) ? field.value : [];
|
|
21
|
+
const isDisabled = field.disabled || formState.disabled || formState.isSubmitting || disabled;
|
|
22
|
+
const control = /*#__PURE__*/ _jsx(MultiSelect, {
|
|
23
|
+
ref: field.ref,
|
|
24
|
+
value: value,
|
|
25
|
+
onChange: field.onChange,
|
|
26
|
+
onBlur: field.onBlur,
|
|
27
|
+
options: options,
|
|
28
|
+
name: field.name,
|
|
29
|
+
placeholder: placeholder,
|
|
30
|
+
disabled: isDisabled,
|
|
31
|
+
hasError: fieldState.invalid,
|
|
32
|
+
$size: size,
|
|
33
|
+
"aria-describedby": `${errorId} ${descriptionId}`,
|
|
34
|
+
"aria-label": label ? undefined : ariaLabel ?? "Select options",
|
|
35
|
+
"aria-labelledby": label ? undefined : ariaLabelledby
|
|
36
|
+
});
|
|
37
|
+
const errorAndDescription = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
38
|
+
children: [
|
|
39
|
+
fieldState.error && /*#__PURE__*/ _jsx(Description, {
|
|
40
|
+
variant: "error",
|
|
41
|
+
id: errorId,
|
|
42
|
+
children: fieldState.error.message
|
|
43
|
+
}),
|
|
44
|
+
description && /*#__PURE__*/ _jsx(Description, {
|
|
45
|
+
id: descriptionId,
|
|
46
|
+
children: description
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
if (!label) {
|
|
51
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
52
|
+
className: className,
|
|
53
|
+
children: [
|
|
54
|
+
control,
|
|
55
|
+
errorAndDescription
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return /*#__PURE__*/ _jsxs(Label, {
|
|
60
|
+
className: className,
|
|
61
|
+
label: label,
|
|
62
|
+
$size: size,
|
|
63
|
+
children: [
|
|
64
|
+
control,
|
|
65
|
+
errorAndDescription
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=FormMultiSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./FormMultiSelect";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import FormSelect from "../FormSelect";
|
|
3
|
-
import
|
|
4
|
-
import { QuestionCircleIcon } from "../../../lib/assets/icons/QuestionCircleIcon";
|
|
3
|
+
import InfoPopover from "../../InfoPopover";
|
|
5
4
|
import styles from "./styles.module.scss";
|
|
6
5
|
const STRATEGY_OPTIONS = [
|
|
7
6
|
{
|
|
@@ -31,13 +30,11 @@ const StrategyLabel = /*#__PURE__*/ _jsxs("span", {
|
|
|
31
30
|
className: styles.label,
|
|
32
31
|
children: [
|
|
33
32
|
STRATEGY_LABEL,
|
|
34
|
-
/*#__PURE__*/ _jsx(
|
|
33
|
+
/*#__PURE__*/ _jsx(InfoPopover, {
|
|
34
|
+
label: "What is update mode?",
|
|
35
35
|
content: STRATEGY_TOOLTIP,
|
|
36
36
|
side: "bottom",
|
|
37
|
-
|
|
38
|
-
className: styles.label__icon,
|
|
39
|
-
children: /*#__PURE__*/ _jsx(QuestionCircleIcon, {})
|
|
40
|
-
})
|
|
37
|
+
className: styles.label__icon
|
|
41
38
|
})
|
|
42
39
|
]
|
|
43
40
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { AutoTranslateSummary } from "../../../entities/translation/model/autoTranslateSummary";
|
|
2
|
+
import type { TargetSelectionMode } from "../../../../types/TargetSelection";
|
|
2
3
|
type BulkTranslationDashboardProps = {
|
|
3
4
|
hasDrafts: boolean;
|
|
4
5
|
autoTranslate: AutoTranslateSummary | null;
|
|
6
|
+
targetSelection: TargetSelectionMode;
|
|
5
7
|
};
|
|
6
|
-
export default function BulkTranslationDashboard({ hasDrafts, autoTranslate, }: BulkTranslationDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default function BulkTranslationDashboard({ hasDrafts, autoTranslate, targetSelection, }: BulkTranslationDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
7
9
|
export {};
|
package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AccessGuard } from "../../../../types/AccessGuard";
|
|
2
2
|
import type { RawPayloadComponentExport } from "../../../../types/PayloadComponentExport";
|
|
3
|
+
import type { TargetSelectionMode } from "../../../../types/TargetSelection";
|
|
3
4
|
export declare class BulkDocumentTranslationDashboard implements RawPayloadComponentExport {
|
|
4
5
|
readonly access: AccessGuard;
|
|
6
|
+
readonly targetSelection: TargetSelectionMode;
|
|
5
7
|
serverProps?: object | Record<string, any> | undefined;
|
|
6
|
-
constructor(access?: AccessGuard);
|
|
8
|
+
constructor(access?: AccessGuard, targetSelection?: TargetSelectionMode);
|
|
7
9
|
path: string;
|
|
8
10
|
}
|
package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export.js
CHANGED
|
@@ -2,12 +2,15 @@ import { AnyAccessGuard } from "../../../../server/shared";
|
|
|
2
2
|
import { clientComponentPath } from "../../../shared/utils/componentPath";
|
|
3
3
|
export class BulkDocumentTranslationDashboard {
|
|
4
4
|
access;
|
|
5
|
+
targetSelection;
|
|
5
6
|
serverProps;
|
|
6
|
-
constructor(access = new AnyAccessGuard()){
|
|
7
|
+
constructor(access = new AnyAccessGuard(), targetSelection = "single"){
|
|
7
8
|
this.access = access;
|
|
9
|
+
this.targetSelection = targetSelection;
|
|
8
10
|
this.path = clientComponentPath("widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server");
|
|
9
11
|
this.serverProps = {
|
|
10
|
-
access
|
|
12
|
+
access,
|
|
13
|
+
targetSelection
|
|
11
14
|
};
|
|
12
15
|
}
|
|
13
16
|
path;
|
|
@@ -11,7 +11,7 @@ import { CollectionTranslationProgress } from "../../../features/collection-tran
|
|
|
11
11
|
import { handleFormError } from "../../../shared/lib/forms/handle-form-error";
|
|
12
12
|
import { useCollectionDashboardUrlParams } from "../../../shared/lib/payload/hooks/useCollectionDashboardUrlParams";
|
|
13
13
|
import styles from "./styles.module.scss";
|
|
14
|
-
export default function BulkTranslationDashboard({ hasDrafts, autoTranslate }) {
|
|
14
|
+
export default function BulkTranslationDashboard({ hasDrafts, autoTranslate, targetSelection }) {
|
|
15
15
|
const locale = useLocale();
|
|
16
16
|
const { collection } = useCollectionDashboardUrlParams();
|
|
17
17
|
const documentsSelection = useSelection();
|
|
@@ -28,10 +28,14 @@ export default function BulkTranslationDashboard({ hasDrafts, autoTranslate }) {
|
|
|
28
28
|
]);
|
|
29
29
|
const initialValues = useMemo(()=>({
|
|
30
30
|
[FORM_FIELDS.SOURCE_LNG]: locale.code,
|
|
31
|
+
// Multi mode binds an array; single mode a string. Seed the matching empty value so the field
|
|
32
|
+
// starts with the right shape (and invalid until a target is chosen).
|
|
33
|
+
[FORM_FIELDS.TARGET_LNG]: targetSelection === "multi" ? [] : "",
|
|
31
34
|
[FORM_FIELDS.HIDDEN_COLLECTION_SLUG]: collection
|
|
32
35
|
}), [
|
|
33
36
|
locale.code,
|
|
34
|
-
collection
|
|
37
|
+
collection,
|
|
38
|
+
targetSelection
|
|
35
39
|
]);
|
|
36
40
|
const { form } = CollectionTranslationFormModel.useForm({
|
|
37
41
|
initialValues,
|
|
@@ -82,7 +86,8 @@ export default function BulkTranslationDashboard({ hasDrafts, autoTranslate }) {
|
|
|
82
86
|
form: form,
|
|
83
87
|
onSubmit: handleSubmit,
|
|
84
88
|
selectedCount: selectedCount,
|
|
85
|
-
hasDrafts: hasDrafts
|
|
89
|
+
hasDrafts: hasDrafts,
|
|
90
|
+
targetSelection: targetSelection
|
|
86
91
|
})
|
|
87
92
|
]
|
|
88
93
|
}),
|
package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { BeforeListTableServerProps } from "payload";
|
|
2
2
|
import type { AccessGuard } from "../../../../types/AccessGuard";
|
|
3
|
+
import type { TargetSelectionMode } from "../../../../types/TargetSelection";
|
|
3
4
|
type BulkTranslationDashboardServerProps = BeforeListTableServerProps & {
|
|
4
5
|
access: AccessGuard;
|
|
6
|
+
targetSelection: TargetSelectionMode;
|
|
5
7
|
};
|
|
6
8
|
declare const BulkTranslationDashboardServer: (props: BulkTranslationDashboardServerProps) => Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
7
9
|
export default BulkTranslationDashboardServer;
|
package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server.js
CHANGED
|
@@ -19,7 +19,8 @@ const BulkTranslationDashboardServer = async (props)=>{
|
|
|
19
19
|
const autoTranslate = resolveAutoTranslateSummary(collection, props.payload.config.localization ? props.payload.config.localization.defaultLocale : undefined);
|
|
20
20
|
return /*#__PURE__*/ _jsx(BulkTranslationDashboard, {
|
|
21
21
|
hasDrafts: hasDrafts,
|
|
22
|
-
autoTranslate: autoTranslate
|
|
22
|
+
autoTranslate: autoTranslate,
|
|
23
|
+
targetSelection: props.targetSelection
|
|
23
24
|
});
|
|
24
25
|
};
|
|
25
26
|
export default BulkTranslationDashboardServer;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { AutoTranslateSummary } from "../../../entities/translation/model/autoTranslateSummary";
|
|
2
|
+
import type { TargetSelectionMode } from "../../../../types/TargetSelection";
|
|
2
3
|
type TranslateDocumentProps = {
|
|
3
4
|
hasDrafts: boolean;
|
|
4
5
|
autoTranslate: AutoTranslateSummary | null;
|
|
6
|
+
targetSelection: TargetSelectionMode;
|
|
5
7
|
};
|
|
6
|
-
declare const TranslateDocument: ({ hasDrafts, autoTranslate }: TranslateDocumentProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare const TranslateDocument: ({ hasDrafts, autoTranslate, targetSelection, }: TranslateDocumentProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
9
|
export default TranslateDocument;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { CollectionConfig } from "payload";
|
|
2
2
|
import type { AccessGuard } from "../../../../types/AccessGuard";
|
|
3
3
|
import type { RawPayloadComponentExport } from "../../../../types/PayloadComponentExport";
|
|
4
|
+
import type { TargetSelectionMode } from "../../../../types/TargetSelection";
|
|
4
5
|
export declare class TranslateDocumentExport implements RawPayloadComponentExport {
|
|
5
6
|
readonly collection: CollectionConfig;
|
|
6
7
|
readonly access: AccessGuard;
|
|
8
|
+
readonly targetSelection: TargetSelectionMode;
|
|
7
9
|
serverProps?: object | Record<string, any> | undefined;
|
|
8
|
-
constructor(collection: CollectionConfig, access?: AccessGuard);
|
|
10
|
+
constructor(collection: CollectionConfig, access?: AccessGuard, targetSelection?: TargetSelectionMode);
|
|
9
11
|
path: string;
|
|
10
12
|
}
|
|
@@ -3,14 +3,17 @@ import { clientComponentPath } from "../../../shared/utils/componentPath";
|
|
|
3
3
|
export class TranslateDocumentExport {
|
|
4
4
|
collection;
|
|
5
5
|
access;
|
|
6
|
+
targetSelection;
|
|
6
7
|
serverProps;
|
|
7
|
-
constructor(collection, access = new AnyAccessGuard()){
|
|
8
|
+
constructor(collection, access = new AnyAccessGuard(), targetSelection = "single"){
|
|
8
9
|
this.collection = collection;
|
|
9
10
|
this.access = access;
|
|
11
|
+
this.targetSelection = targetSelection;
|
|
10
12
|
this.path = clientComponentPath("widgets/translate-document/ui/TranslateDocument.server");
|
|
11
13
|
this.serverProps = {
|
|
12
14
|
collection,
|
|
13
|
-
access
|
|
15
|
+
access,
|
|
16
|
+
targetSelection
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
path;
|