@byline/admin 4.1.0 → 4.2.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/fields/array/array-field.js +20 -8
- package/dist/fields/array/array-field.module.js +5 -1
- package/dist/fields/array/array-field_module.css +23 -0
- package/dist/fields/blocks/blocks-field.js +33 -12
- package/dist/fields/blocks/blocks-field.module.js +2 -0
- package/dist/fields/blocks/blocks-field_module.css +37 -0
- package/dist/fields/code/code-editor.d.ts +20 -0
- package/dist/fields/code/code-editor.js +239 -0
- package/dist/fields/code/code-field.d.ts +21 -0
- package/dist/fields/code/code-field.js +124 -0
- package/dist/fields/code/code-field.module.js +7 -0
- package/dist/fields/code/code-field_module.css +58 -0
- package/dist/fields/field-helpers.js +1 -0
- package/dist/fields/field-renderer.js +15 -1
- package/dist/fields/group/group-field.d.ts +10 -2
- package/dist/fields/group/group-field.js +4 -2
- package/dist/fields/relation/relation-picker.js +8 -2
- package/dist/fields/select/select-field.js +4 -2
- package/dist/fields/select/select-field.module.js +1 -0
- package/dist/fields/select/select-field_module.css +4 -0
- package/dist/fields/text/text-field_module.css +1 -0
- package/dist/fields/text-area/text-area-field_module.css +1 -0
- package/dist/forms/form-renderer.d.ts +1 -1
- package/dist/forms/tree-placement-widget.d.ts +1 -1
- package/package.json +18 -5
- package/src/fields/array/array-field.module.css +30 -0
- package/src/fields/array/array-field.tsx +15 -2
- package/src/fields/blocks/blocks-field.module.css +53 -0
- package/src/fields/blocks/blocks-field.tsx +33 -1
- package/src/fields/code/code-editor.tsx +246 -0
- package/src/fields/code/code-field.module.css +84 -0
- package/src/fields/code/code-field.tsx +191 -0
- package/src/fields/field-helpers.ts +1 -0
- package/src/fields/field-renderer.tsx +19 -2
- package/src/fields/field-services-types.ts +1 -1
- package/src/fields/group/group-field.tsx +12 -1
- package/src/fields/relation/relation-picker.tsx +11 -0
- package/src/fields/select/select-field.module.css +5 -0
- package/src/fields/select/select-field.tsx +9 -2
- package/src/fields/text/text-field.module.css +1 -0
- package/src/fields/text-area/text-area-field.module.css +1 -0
- package/src/forms/form-renderer.tsx +1 -1
- package/src/forms/tree-placement-widget.tsx +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
:is(.label-row-bNilI8, .byline-field-code-label-row) {
|
|
2
|
+
align-items: center;
|
|
3
|
+
margin-bottom: .25rem;
|
|
4
|
+
display: flex;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.byline-field-code {
|
|
8
|
+
--byline-code-bg: var(--canvas-50, #fafafa);
|
|
9
|
+
--byline-code-fg: var(--canvas-950, #1c1c1e);
|
|
10
|
+
--byline-code-border: var(--canvas-300, #d6d6d6);
|
|
11
|
+
--byline-code-gutter-bg: var(--canvas-100, #f2f2f2);
|
|
12
|
+
--byline-code-gutter-fg: var(--canvas-500, #8b8b90);
|
|
13
|
+
--byline-code-active-line: #0000000a;
|
|
14
|
+
--byline-code-selection: #3b82f62e;
|
|
15
|
+
--byline-code-caret: var(--canvas-950, #1c1c1e);
|
|
16
|
+
--byline-code-focus-ring: #3b82f699;
|
|
17
|
+
--byline-code-keyword: #7c3aed;
|
|
18
|
+
--byline-code-string: #15803d;
|
|
19
|
+
--byline-code-comment: #767680;
|
|
20
|
+
--byline-code-number: #c2410c;
|
|
21
|
+
--byline-code-function: #1d4ed8;
|
|
22
|
+
--byline-code-type: #0f766e;
|
|
23
|
+
--byline-code-property: #a16207;
|
|
24
|
+
--byline-code-operator: #52525b;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:is([data-theme="dark"], .dark) .byline-field-code {
|
|
28
|
+
--byline-code-bg: var(--canvas-900, #1b1b1f);
|
|
29
|
+
--byline-code-fg: var(--canvas-100, #e6e6ea);
|
|
30
|
+
--byline-code-border: var(--canvas-700, #3f3f46);
|
|
31
|
+
--byline-code-gutter-bg: var(--canvas-800, #232327);
|
|
32
|
+
--byline-code-gutter-fg: var(--canvas-500, #77777f);
|
|
33
|
+
--byline-code-active-line: #ffffff0d;
|
|
34
|
+
--byline-code-selection: #60a5fa47;
|
|
35
|
+
--byline-code-caret: var(--canvas-100, #e6e6ea);
|
|
36
|
+
--byline-code-focus-ring: #60a5fa8c;
|
|
37
|
+
--byline-code-keyword: #c4b5fd;
|
|
38
|
+
--byline-code-string: #86efac;
|
|
39
|
+
--byline-code-comment: #8e8e96;
|
|
40
|
+
--byline-code-number: #fdba74;
|
|
41
|
+
--byline-code-function: #93c5fd;
|
|
42
|
+
--byline-code-type: #5eead4;
|
|
43
|
+
--byline-code-property: #fde047;
|
|
44
|
+
--byline-code-operator: #a1a1aa;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:is(.loading-sejBT6, .byline-field-code-loading) {
|
|
48
|
+
resize: none;
|
|
49
|
+
width: 100%;
|
|
50
|
+
font-family: var(--byline-code-font, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
51
|
+
font-size: var(--byline-code-font-size, 13px);
|
|
52
|
+
color: var(--byline-code-fg);
|
|
53
|
+
background-color: var(--byline-code-bg);
|
|
54
|
+
border: 1px solid var(--byline-code-border);
|
|
55
|
+
border-radius: var(--byline-code-radius, 4px);
|
|
56
|
+
padding: .5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
@@ -5,6 +5,7 @@ import { useFormContext } from "../forms/form-context.js";
|
|
|
5
5
|
import { ArrayField } from "./array/array-field.js";
|
|
6
6
|
import { BlocksField } from "./blocks/blocks-field.js";
|
|
7
7
|
import { CheckboxField } from "./checkbox/checkbox-field.js";
|
|
8
|
+
import { CodeField } from "./code/code-field.js";
|
|
8
9
|
import { DateTimeField } from "./datetime/datetime-field.js";
|
|
9
10
|
import field_renderer_module from "./field-renderer.module.js";
|
|
10
11
|
import { FileField } from "./file/file-field.js";
|
|
@@ -60,6 +61,19 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
60
61
|
locale: isLocalised ? contentLocale : void 0,
|
|
61
62
|
components: components
|
|
62
63
|
});
|
|
64
|
+
case 'code':
|
|
65
|
+
return /*#__PURE__*/ jsx(CodeField, {
|
|
66
|
+
field: hideLabel ? {
|
|
67
|
+
...field,
|
|
68
|
+
label: void 0
|
|
69
|
+
} : field,
|
|
70
|
+
defaultValue: defaultValue,
|
|
71
|
+
onChange: handleChange,
|
|
72
|
+
path: path,
|
|
73
|
+
id: htmlId,
|
|
74
|
+
locale: isLocalised ? contentLocale : void 0,
|
|
75
|
+
components: components
|
|
76
|
+
});
|
|
63
77
|
case 'checkbox':
|
|
64
78
|
return /*#__PURE__*/ jsx(CheckboxField, {
|
|
65
79
|
field: hideLabel ? {
|
|
@@ -214,7 +228,7 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
214
228
|
return null;
|
|
215
229
|
}
|
|
216
230
|
};
|
|
217
|
-
const selfBadge = 'text' === field.type || 'textArea' === field.type || 'richText' === field.type;
|
|
231
|
+
const selfBadge = 'text' === field.type || 'textArea' === field.type || 'code' === field.type || 'richText' === field.type;
|
|
218
232
|
if (badge && !selfBadge) return /*#__PURE__*/ jsxs("div", {
|
|
219
233
|
className: classnames('byline-field-localized-wrap', field_renderer_module["localized-wrap"]),
|
|
220
234
|
children: [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import type { GroupField as GroupFieldType } from '@byline/core';
|
|
8
|
+
import type { FieldAdminConfig, GroupField as GroupFieldType } from '@byline/core';
|
|
9
9
|
interface GroupFieldProps {
|
|
10
10
|
field: GroupFieldType;
|
|
11
11
|
defaultValue: any;
|
|
@@ -22,6 +22,14 @@ interface GroupFieldProps {
|
|
|
22
22
|
* locale badge.
|
|
23
23
|
*/
|
|
24
24
|
contentLocale?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Per-child-field admin overrides (`components` slots, richtext `editor`),
|
|
27
|
+
* keyed by child field name. Threaded by `BlocksField` from the site-wide
|
|
28
|
+
* `ClientConfig.blockAdmin` registry so block children can take per-field
|
|
29
|
+
* admin config; plain groups receive none today (their children inherit
|
|
30
|
+
* site-wide defaults).
|
|
31
|
+
*/
|
|
32
|
+
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
25
33
|
}
|
|
26
|
-
export declare const GroupField: ({ field, defaultValue, path, collectionPath, contentLocale, }: GroupFieldProps) => import("react").JSX.Element;
|
|
34
|
+
export declare const GroupField: ({ field, defaultValue, path, collectionPath, contentLocale, fieldAdmin, }: GroupFieldProps) => import("react").JSX.Element;
|
|
27
35
|
export {};
|
|
@@ -6,7 +6,7 @@ import { placeholderForField } from "../field-helpers.js";
|
|
|
6
6
|
import { FieldRenderer } from "../field-renderer.js";
|
|
7
7
|
import { useFieldError } from "../../forms/form-context.js";
|
|
8
8
|
import group_field_module from "./group-field.module.js";
|
|
9
|
-
const GroupField = ({ field, defaultValue, path, collectionPath, contentLocale })=>{
|
|
9
|
+
const GroupField = ({ field, defaultValue, path, collectionPath, contentLocale, fieldAdmin })=>{
|
|
10
10
|
const fieldError = useFieldError(field.name);
|
|
11
11
|
const groupData = useMemo(()=>{
|
|
12
12
|
if (defaultValue && 'object' == typeof defaultValue && !Array.isArray(defaultValue)) return defaultValue;
|
|
@@ -48,7 +48,9 @@ const GroupField = ({ field, defaultValue, path, collectionPath, contentLocale }
|
|
|
48
48
|
basePath: path,
|
|
49
49
|
disableSorting: true,
|
|
50
50
|
collectionPath: collectionPath,
|
|
51
|
-
contentLocale: contentLocale
|
|
51
|
+
contentLocale: contentLocale,
|
|
52
|
+
components: fieldAdmin?.[innerField.name]?.components,
|
|
53
|
+
editor: fieldAdmin?.[innerField.name]?.editor
|
|
52
54
|
}, innerField.name))
|
|
53
55
|
}),
|
|
54
56
|
fieldError && /*#__PURE__*/ jsx(ErrorText, {
|
|
@@ -39,13 +39,18 @@ const RelationPicker = ({ targetCollectionPath, targetDefinition, displayField,
|
|
|
39
39
|
const selectFields = resolveSelectFields(targetDefinition, displayField, pickerColumns, extraSelectFields);
|
|
40
40
|
setLoading(true);
|
|
41
41
|
setError(null);
|
|
42
|
+
const itemViewSort = targetAdminConfig?.itemViewSort;
|
|
42
43
|
getCollectionDocuments({
|
|
43
44
|
collection: targetCollectionPath,
|
|
44
45
|
params: {
|
|
45
46
|
page,
|
|
46
47
|
page_size: PAGE_SIZE,
|
|
47
48
|
query: query.length > 0 ? query : void 0,
|
|
48
|
-
fields: selectFields
|
|
49
|
+
fields: selectFields,
|
|
50
|
+
...null != itemViewSort ? {
|
|
51
|
+
order: String(itemViewSort.field),
|
|
52
|
+
desc: 'desc' === itemViewSort.direction
|
|
53
|
+
} : {}
|
|
49
54
|
}
|
|
50
55
|
}).then((response)=>{
|
|
51
56
|
if (cancelled) return;
|
|
@@ -71,7 +76,8 @@ const RelationPicker = ({ targetCollectionPath, targetDefinition, displayField,
|
|
|
71
76
|
targetDefinition,
|
|
72
77
|
pickerColumns,
|
|
73
78
|
getCollectionDocuments,
|
|
74
|
-
t
|
|
79
|
+
t,
|
|
80
|
+
targetAdminConfig?.itemViewSort
|
|
75
81
|
]);
|
|
76
82
|
const resolvedDisplayField = displayField ?? targetDefinition?.useAsTitle ?? resolveFallbackDisplayField(targetDefinition) ?? null;
|
|
77
83
|
const handleSelect = useCallback(()=>{
|
|
@@ -17,10 +17,12 @@ const SelectField = ({ field, value, defaultValue, onChange, id, path })=>{
|
|
|
17
17
|
id: htmlId,
|
|
18
18
|
htmlFor: htmlId,
|
|
19
19
|
label: field.label,
|
|
20
|
-
required: !field.optional
|
|
20
|
+
required: !field.optional,
|
|
21
|
+
className: classnames('byline-field-select-label', select_field_module.label)
|
|
21
22
|
}),
|
|
22
23
|
/*#__PURE__*/ jsx(Select, {
|
|
23
|
-
size: "
|
|
24
|
+
size: "xs",
|
|
25
|
+
variant: "outlined",
|
|
24
26
|
id: htmlId,
|
|
25
27
|
name: field.name,
|
|
26
28
|
placeholder: "Select an option",
|
|
@@ -104,7 +104,7 @@ export interface FormRendererProps {
|
|
|
104
104
|
* Opts the document-tree placement widget into the sidebar (above the
|
|
105
105
|
* available-locales widget). Sourced from `CollectionDefinition.tree` by the
|
|
106
106
|
* caller. Renders only in edit mode (placement needs a persisted document)
|
|
107
|
-
* and only when the host wires the tree services. See docs/04-collections/
|
|
107
|
+
* and only when the host wires the tree services. See docs/04-collections/04-document-trees.md.
|
|
108
108
|
*/
|
|
109
109
|
tree?: boolean;
|
|
110
110
|
headingLabel?: string;
|
|
@@ -8,7 +8,7 @@ export interface TreePlacementWidgetProps {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Sidebar widget for placing the current document within its collection's
|
|
11
|
-
* single-parent document tree (the `tree: true` primitive — docs/04-collections/
|
|
11
|
+
* single-parent document tree (the `tree: true` primitive — docs/04-collections/04-document-trees.md).
|
|
12
12
|
*
|
|
13
13
|
* The tree is document-grain and **unversioned**, so changes here write
|
|
14
14
|
* immediately (independent of the form's content save). The editor picks a
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/admin",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.2.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -149,16 +149,29 @@
|
|
|
149
149
|
"src/"
|
|
150
150
|
],
|
|
151
151
|
"dependencies": {
|
|
152
|
+
"@codemirror/commands": "^6.10.4",
|
|
153
|
+
"@codemirror/lang-css": "^6.3.1",
|
|
154
|
+
"@codemirror/lang-html": "^6.4.11",
|
|
155
|
+
"@codemirror/lang-javascript": "^6.2.5",
|
|
156
|
+
"@codemirror/lang-json": "^6.0.2",
|
|
157
|
+
"@codemirror/lang-markdown": "^6.5.1",
|
|
158
|
+
"@codemirror/lang-python": "^6.2.1",
|
|
159
|
+
"@codemirror/lang-sql": "^6.10.0",
|
|
160
|
+
"@codemirror/lang-yaml": "^6.1.3",
|
|
161
|
+
"@codemirror/language": "^6.12.4",
|
|
162
|
+
"@codemirror/state": "^6.7.1",
|
|
163
|
+
"@codemirror/view": "^6.43.6",
|
|
164
|
+
"@lezer/highlight": "^1.2.3",
|
|
152
165
|
"@tanstack/react-form-start": "^1.33.0",
|
|
153
166
|
"classnames": "^2.5.1",
|
|
154
167
|
"jose": "^6.2.3",
|
|
155
168
|
"react-diff-viewer-continued": "^4.2.2",
|
|
156
169
|
"uuid": "^14.0.1",
|
|
157
170
|
"zod": "^4.4.3",
|
|
158
|
-
"@byline/core": "4.
|
|
159
|
-
"@byline/
|
|
160
|
-
"@byline/auth": "4.
|
|
161
|
-
"@byline/
|
|
171
|
+
"@byline/core": "4.2.0",
|
|
172
|
+
"@byline/i18n": "4.2.0",
|
|
173
|
+
"@byline/auth": "4.2.0",
|
|
174
|
+
"@byline/ui": "4.2.0"
|
|
162
175
|
},
|
|
163
176
|
"peerDependencies": {
|
|
164
177
|
"react": "^19.0.0",
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* .byline-field-array — outer wrapper
|
|
6
6
|
* .byline-field-array-title — array label heading
|
|
7
7
|
* .byline-field-array-stack — vertical layout for items + add row
|
|
8
|
+
* .byline-field-array-add-row — add-button row at the bottom
|
|
9
|
+
* .byline-field-array-add-label — clickable text label in the add row
|
|
8
10
|
* .byline-field-array-card — disable-sorting fallback card per item
|
|
9
11
|
* .byline-field-array-group-header — sub-group title above grouped fields
|
|
10
12
|
* .byline-field-array-group-fields — vertical stack of fields inside an item
|
|
@@ -46,3 +48,31 @@
|
|
|
46
48
|
flex-direction: column;
|
|
47
49
|
gap: var(--spacing-16);
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
.add-row,
|
|
53
|
+
:global(.byline-field-array-add-row) {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
font-size: 0.9rem;
|
|
57
|
+
color: var(--muted);
|
|
58
|
+
gap: var(--spacing-8);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Text-styled companion button for the add row's label — clickable like the
|
|
62
|
+
icon button. Hovering anywhere on the row brightens the label text. */
|
|
63
|
+
.add-label,
|
|
64
|
+
:global(.byline-field-array-add-label) {
|
|
65
|
+
appearance: none;
|
|
66
|
+
background: none;
|
|
67
|
+
border: none;
|
|
68
|
+
padding: 0;
|
|
69
|
+
font: inherit;
|
|
70
|
+
color: inherit;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
transition: color 120ms ease;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.add-row:hover .add-label,
|
|
76
|
+
:global(.byline-field-array-add-row):hover :global(.byline-field-array-add-label) {
|
|
77
|
+
color: var(--text);
|
|
78
|
+
}
|
|
@@ -290,7 +290,7 @@ export const ArrayField = ({
|
|
|
290
290
|
className={cx('byline-field-array-stack', styles.stack)}
|
|
291
291
|
>
|
|
292
292
|
{items.map((item, index) => renderItem(item, index))}
|
|
293
|
-
<
|
|
293
|
+
<div className={cx('byline-field-array-add-row', styles['add-row'])}>
|
|
294
294
|
<IconButton
|
|
295
295
|
onClick={() => {
|
|
296
296
|
void handleAddItem()
|
|
@@ -299,7 +299,20 @@ export const ArrayField = ({
|
|
|
299
299
|
>
|
|
300
300
|
<PlusIcon />
|
|
301
301
|
</IconButton>
|
|
302
|
-
|
|
302
|
+
{/* Text-styled button so the label is clickable too. Removed from
|
|
303
|
+
the tab order (tabIndex -1) — the IconButton is the single
|
|
304
|
+
keyboard/screen-reader control for this action. */}
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
tabIndex={-1}
|
|
308
|
+
onClick={() => {
|
|
309
|
+
void handleAddItem()
|
|
310
|
+
}}
|
|
311
|
+
className={cx('byline-field-array-add-label', styles['add-label'])}
|
|
312
|
+
>
|
|
313
|
+
{t('fields.array.addItem')}
|
|
314
|
+
</button>
|
|
315
|
+
</div>
|
|
303
316
|
</DraggableSortable>
|
|
304
317
|
)}
|
|
305
318
|
</div>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* .byline-field-blocks-title — block-list label heading
|
|
7
7
|
* .byline-field-blocks-stack — vertical stack of blocks + add row
|
|
8
8
|
* .byline-field-blocks-add-row — add-button row at the bottom
|
|
9
|
+
* .byline-field-blocks-add-label — clickable text label in the add row
|
|
9
10
|
* .byline-field-blocks-modal-head — picker modal header
|
|
10
11
|
* .byline-field-blocks-modal-title — picker modal title
|
|
11
12
|
* .byline-field-blocks-grid — grid of variant cards
|
|
@@ -36,9 +37,35 @@
|
|
|
36
37
|
:global(.byline-field-blocks-add-row) {
|
|
37
38
|
display: flex;
|
|
38
39
|
align-items: center;
|
|
40
|
+
font-size: 0.9rem;
|
|
41
|
+
color: var(--muted);
|
|
39
42
|
gap: var(--spacing-8);
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
/* Text-styled companion button for the add row's label — clickable like the
|
|
46
|
+
icon button. Hovering anywhere on the row brightens the label text. */
|
|
47
|
+
.add-label,
|
|
48
|
+
:global(.byline-field-blocks-add-label) {
|
|
49
|
+
appearance: none;
|
|
50
|
+
background: none;
|
|
51
|
+
border: none;
|
|
52
|
+
padding: 0;
|
|
53
|
+
font: inherit;
|
|
54
|
+
color: inherit;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
transition: color 120ms ease;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.add-label:disabled,
|
|
60
|
+
:global(.byline-field-blocks-add-label):disabled {
|
|
61
|
+
cursor: default;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.add-row:hover .add-label:not(:disabled),
|
|
65
|
+
:global(.byline-field-blocks-add-row):hover :global(.byline-field-blocks-add-label):not(:disabled) {
|
|
66
|
+
color: var(--text);
|
|
67
|
+
}
|
|
68
|
+
|
|
42
69
|
.modal-head,
|
|
43
70
|
:global(.byline-field-blocks-modal-head) {
|
|
44
71
|
padding-top: 1rem;
|
|
@@ -54,6 +81,32 @@
|
|
|
54
81
|
.modal-content,
|
|
55
82
|
:global(.byline-field-blocks-card-cursor) {
|
|
56
83
|
cursor: pointer;
|
|
84
|
+
/* Cap the picker's height and scroll the card grid when a collection
|
|
85
|
+
declares more blocks than fit. Mobile-first: 80% of the (dynamic)
|
|
86
|
+
viewport height, tightening to 70% on tablet and 60% on desktop —
|
|
87
|
+
smaller screens get proportionally more of the picker. `min-height: 0`
|
|
88
|
+
lets this flex child (Modal.Content is a flex column item) actually
|
|
89
|
+
shrink and scroll; `overscroll-behavior` keeps wheel momentum from
|
|
90
|
+
spilling into the document behind the modal. All caps stay inside the
|
|
91
|
+
Modal.Container's own viewport-height limit. */
|
|
92
|
+
max-height: 80dvh;
|
|
93
|
+
min-height: 0;
|
|
94
|
+
overflow-y: auto;
|
|
95
|
+
overscroll-behavior: contain;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (min-width: 40rem) {
|
|
99
|
+
.modal-content,
|
|
100
|
+
:global(.byline-field-blocks-card-cursor) {
|
|
101
|
+
max-height: 70dvh;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@media (min-width: 64rem) {
|
|
106
|
+
.modal-content,
|
|
107
|
+
:global(.byline-field-blocks-card-cursor) {
|
|
108
|
+
max-height: 60dvh;
|
|
109
|
+
}
|
|
57
110
|
}
|
|
58
111
|
|
|
59
112
|
.grid,
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
import { useEffect, useMemo, useState } from 'react'
|
|
10
10
|
|
|
11
11
|
import type {
|
|
12
|
+
BlockAdminConfig,
|
|
12
13
|
BlocksField as BlocksFieldType,
|
|
13
14
|
Field,
|
|
14
15
|
GroupField as GroupFieldType,
|
|
15
16
|
} from '@byline/core'
|
|
17
|
+
import { getClientConfig } from '@byline/core'
|
|
16
18
|
import { useTranslation } from '@byline/i18n/react'
|
|
17
19
|
import {
|
|
18
20
|
Card,
|
|
@@ -59,6 +61,19 @@ export const BlocksField = ({
|
|
|
59
61
|
const [pendingInsertIndex, setPendingInsertIndex] = useState<number | null>(null)
|
|
60
62
|
|
|
61
63
|
const availableBlocks = useMemo(() => field.blocks ?? [], [field.blocks])
|
|
64
|
+
|
|
65
|
+
// Site-wide per-block admin config (`ClientConfig.blockAdmin`), resolved by
|
|
66
|
+
// blockType. Registry-based on purpose: blocks are cross-collection units,
|
|
67
|
+
// so their admin config applies wherever the block renders — no threading
|
|
68
|
+
// from the collection admin config is needed (mirrors how FieldRenderer
|
|
69
|
+
// resolves the global richText editor from client config).
|
|
70
|
+
const blockAdminByType = useMemo(() => {
|
|
71
|
+
const map = new Map<string, BlockAdminConfig>()
|
|
72
|
+
for (const entry of getClientConfig().blockAdmin ?? []) {
|
|
73
|
+
map.set(entry.blockType, entry)
|
|
74
|
+
}
|
|
75
|
+
return map
|
|
76
|
+
}, [])
|
|
62
77
|
const [selectedBlockName, setSelectedBlockName] = useState<string>(
|
|
63
78
|
() => availableBlocks[0]?.blockType ?? ''
|
|
64
79
|
)
|
|
@@ -235,6 +250,7 @@ export const BlocksField = ({
|
|
|
235
250
|
defaultValue={fieldData}
|
|
236
251
|
path={arrayElementPath}
|
|
237
252
|
contentLocale={contentLocale}
|
|
253
|
+
fieldAdmin={blockAdminByType.get(item._type)?.fields}
|
|
238
254
|
/>
|
|
239
255
|
)
|
|
240
256
|
|
|
@@ -270,9 +286,25 @@ export const BlocksField = ({
|
|
|
270
286
|
}}
|
|
271
287
|
disabled={!selectedBlockName}
|
|
272
288
|
aria-label={t('fields.blocks.addBlockAriaLabel')}
|
|
289
|
+
variant="outlined"
|
|
273
290
|
>
|
|
274
291
|
<PlusIcon />
|
|
275
292
|
</IconButton>
|
|
293
|
+
{/* Text-styled button so the label is clickable too. Removed from
|
|
294
|
+
the tab order (tabIndex -1) — the IconButton is the single
|
|
295
|
+
keyboard/screen-reader control for this action. */}
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
tabIndex={-1}
|
|
299
|
+
disabled={!selectedBlockName}
|
|
300
|
+
onClick={() => {
|
|
301
|
+
setPendingInsertIndex(null)
|
|
302
|
+
setShowAddBlockModal(true)
|
|
303
|
+
}}
|
|
304
|
+
className={cx('byline-field-blocks-add-label', styles['add-label'])}
|
|
305
|
+
>
|
|
306
|
+
{t('fields.blocks.addBlock')}
|
|
307
|
+
</button>
|
|
276
308
|
</div>
|
|
277
309
|
</DraggableSortable>
|
|
278
310
|
<Modal
|
|
@@ -283,7 +315,7 @@ export const BlocksField = ({
|
|
|
283
315
|
setPendingInsertIndex(null)
|
|
284
316
|
}}
|
|
285
317
|
>
|
|
286
|
-
<Modal.Container style={{ maxWidth: '
|
|
318
|
+
<Modal.Container style={{ maxWidth: '700px' }}>
|
|
287
319
|
<Modal.Header className={cx('byline-field-blocks-modal-head', styles['modal-head'])}>
|
|
288
320
|
<h3 className={cx('byline-field-blocks-modal-title', styles['modal-title'])}>
|
|
289
321
|
{t('fields.blocks.modalTitle')}
|