@byline/ui 2.5.2 → 2.6.1
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/shimmer/shimmer.d.ts +13 -1
- package/dist/components/shimmer/shimmer.js +29 -20
- package/dist/components/shimmer/shimmer_module.css +4 -4
- package/dist/dnd/draggable-sortable/demo/draggable-list-demo.js +1 -1
- package/dist/react.d.ts +18 -54
- package/dist/react.js +0 -35
- package/dist/styles/styles.css +3 -0
- package/dist/uikit.d.ts +1 -0
- package/dist/uikit.js +1 -0
- package/package.json +2 -8
- package/src/components/shimmer/shimmer.module.css +8 -4
- package/src/components/shimmer/shimmer.tsx +34 -9
- package/src/dnd/draggable-sortable/demo/draggable-list-demo.tsx +1 -1
- package/src/react.ts +20 -68
- package/src/styles/functional/surfaces.css +13 -1
- package/src/uikit.ts +1 -0
- package/dist/admin/group.d.ts +0 -27
- package/dist/admin/group.js +0 -14
- package/dist/admin/group.module.js +0 -6
- package/dist/admin/group_module.css +0 -19
- package/dist/admin/row.d.ts +0 -25
- package/dist/admin/row.js +0 -8
- package/dist/admin/row.module.js +0 -5
- package/dist/admin/row_module.css +0 -18
- package/dist/admin/tabs.d.ts +0 -25
- package/dist/admin/tabs.js +0 -35
- package/dist/admin/tabs.module.js +0 -10
- package/dist/admin/tabs_module.css +0 -68
- package/dist/fields/array/array-field.d.ts +0 -14
- package/dist/fields/array/array-field.js +0 -176
- package/dist/fields/array/array-field.module.js +0 -11
- package/dist/fields/array/array-field_module.css +0 -32
- package/dist/fields/blocks/blocks-field.d.ts +0 -13
- package/dist/fields/blocks/blocks-field.js +0 -244
- package/dist/fields/blocks/blocks-field.module.js +0 -26
- package/dist/fields/blocks/blocks-field_module.css +0 -107
- package/dist/fields/checkbox/checkbox-field.d.ts +0 -16
- package/dist/fields/checkbox/checkbox-field.js +0 -28
- package/dist/fields/checkbox/checkbox-field.module.js +0 -6
- package/dist/fields/checkbox/checkbox-field_module.css +0 -4
- package/dist/fields/column-formatter.d.ts +0 -20
- package/dist/fields/column-formatter.js +0 -15
- package/dist/fields/date-time-formatter.d.ts +0 -16
- package/dist/fields/date-time-formatter.js +0 -8
- package/dist/fields/datetime/datetime-field.d.ts +0 -16
- package/dist/fields/datetime/datetime-field.js +0 -37
- package/dist/fields/datetime/datetime-field.module.js +0 -5
- package/dist/fields/datetime/datetime-field_module.css +0 -4
- package/dist/fields/draggable-context-menu.d.ts +0 -6
- package/dist/fields/draggable-context-menu.js +0 -83
- package/dist/fields/draggable-context-menu.module.js +0 -15
- package/dist/fields/draggable-context-menu_module.css +0 -91
- package/dist/fields/field-helpers.d.ts +0 -26
- package/dist/fields/field-helpers.js +0 -50
- package/dist/fields/field-renderer.d.ts +0 -37
- package/dist/fields/field-renderer.js +0 -206
- package/dist/fields/field-renderer.module.js +0 -8
- package/dist/fields/field-renderer_module.css +0 -11
- package/dist/fields/file/file-field.d.ts +0 -19
- package/dist/fields/file/file-field.js +0 -226
- package/dist/fields/file/file-field.module.js +0 -18
- package/dist/fields/file/file-field_module.css +0 -131
- package/dist/fields/file/file-upload-field.d.ts +0 -21
- package/dist/fields/file/file-upload-field.js +0 -128
- package/dist/fields/file/file-upload-field.module.js +0 -15
- package/dist/fields/file/file-upload-field_module.css +0 -74
- package/dist/fields/group/group-field.d.ts +0 -15
- package/dist/fields/group/group-field.js +0 -59
- package/dist/fields/group/group-field.module.js +0 -9
- package/dist/fields/group/group-field_module.css +0 -27
- package/dist/fields/image/image-field.d.ts +0 -19
- package/dist/fields/image/image-field.js +0 -242
- package/dist/fields/image/image-field.module.js +0 -22
- package/dist/fields/image/image-field_module.css +0 -121
- package/dist/fields/image/image-upload-field.d.ts +0 -21
- package/dist/fields/image/image-upload-field.js +0 -187
- package/dist/fields/image/image-upload-field.module.js +0 -19
- package/dist/fields/image/image-upload-field_module.css +0 -92
- package/dist/fields/local-date-time.d.ts +0 -27
- package/dist/fields/local-date-time.js +0 -49
- package/dist/fields/locale-badge.d.ts +0 -18
- package/dist/fields/locale-badge.js +0 -10
- package/dist/fields/locale-badge.module.js +0 -5
- package/dist/fields/locale-badge_module.css +0 -27
- package/dist/fields/numerical/numerical-field.d.ts +0 -18
- package/dist/fields/numerical/numerical-field.js +0 -74
- package/dist/fields/relation/relation-display.d.ts +0 -40
- package/dist/fields/relation/relation-display.js +0 -58
- package/dist/fields/relation/relation-display.module.js +0 -9
- package/dist/fields/relation/relation-display_module.css +0 -21
- package/dist/fields/relation/relation-field.d.ts +0 -18
- package/dist/fields/relation/relation-field.js +0 -146
- package/dist/fields/relation/relation-field.module.js +0 -13
- package/dist/fields/relation/relation-field_module.css +0 -62
- package/dist/fields/relation/relation-picker.d.ts +0 -49
- package/dist/fields/relation/relation-picker.js +0 -233
- package/dist/fields/relation/relation-picker.module.js +0 -26
- package/dist/fields/relation/relation-picker_module.css +0 -124
- package/dist/fields/relation/relation-summary.d.ts +0 -31
- package/dist/fields/relation/relation-summary.js +0 -50
- package/dist/fields/relation/relation-summary.module.js +0 -11
- package/dist/fields/relation/relation-summary_module.css +0 -37
- package/dist/fields/select/select-field.d.ts +0 -16
- package/dist/fields/select/select-field.js +0 -50
- package/dist/fields/select/select-field.module.js +0 -5
- package/dist/fields/select/select-field_module.css +0 -4
- package/dist/fields/sortable-item.d.ts +0 -15
- package/dist/fields/sortable-item.js +0 -80
- package/dist/fields/sortable-item.module.js +0 -22
- package/dist/fields/sortable-item_module.css +0 -124
- package/dist/fields/text/text-field.d.ts +0 -20
- package/dist/fields/text/text-field.js +0 -104
- package/dist/fields/text/text-field.module.js +0 -6
- package/dist/fields/text/text-field_module.css +0 -5
- package/dist/fields/text-area/text-area-field.d.ts +0 -20
- package/dist/fields/text-area/text-area-field.js +0 -105
- package/dist/fields/text-area/text-area-field.module.js +0 -6
- package/dist/fields/text-area/text-area-field_module.css +0 -5
- package/dist/fields/use-field-change-handler.d.ts +0 -23
- package/dist/fields/use-field-change-handler.js +0 -52
- package/dist/forms/document-actions.d.ts +0 -48
- package/dist/forms/document-actions.js +0 -469
- package/dist/forms/document-actions.module.js +0 -34
- package/dist/forms/document-actions_module.css +0 -118
- package/dist/forms/form-context.d.ts +0 -89
- package/dist/forms/form-context.js +0 -466
- package/dist/forms/form-renderer.d.ts +0 -98
- package/dist/forms/form-renderer.js +0 -591
- package/dist/forms/form-renderer.module.js +0 -46
- package/dist/forms/form-renderer_module.css +0 -245
- package/dist/forms/navigation-guard.d.ts +0 -54
- package/dist/forms/navigation-guard.js +0 -22
- package/dist/forms/path-widget.d.ts +0 -36
- package/dist/forms/path-widget.js +0 -107
- package/dist/forms/path-widget.module.js +0 -8
- package/dist/forms/path-widget_module.css +0 -29
- package/dist/forms/upload-executor.d.ts +0 -57
- package/dist/forms/upload-executor.js +0 -92
- package/dist/services/field-services-context.d.ts +0 -16
- package/dist/services/field-services-context.js +0 -13
- package/dist/services/field-services-types.d.ts +0 -63
- package/dist/services/field-services-types.js +0 -1
- package/dist/widgets/diff-viewer/diff-modal.d.ts +0 -22
- package/dist/widgets/diff-viewer/diff-modal.js +0 -146
- package/dist/widgets/diff-viewer/diff-modal.module.js +0 -14
- package/dist/widgets/diff-viewer/diff-modal_module.css +0 -56
- package/dist/widgets/status-badge/status-badge.d.ts +0 -25
- package/dist/widgets/status-badge/status-badge.js +0 -35
- package/dist/widgets/status-badge/status-badge.module.js +0 -7
- package/dist/widgets/status-badge/status-badge_module.css +0 -20
- package/src/admin/group.module.css +0 -41
- package/src/admin/group.tsx +0 -40
- package/src/admin/row.module.css +0 -32
- package/src/admin/row.tsx +0 -33
- package/src/admin/tabs.module.css +0 -107
- package/src/admin/tabs.tsx +0 -82
- package/src/fields/array/array-field.module.css +0 -48
- package/src/fields/array/array-field.tsx +0 -266
- package/src/fields/blocks/blocks-field.module.css +0 -148
- package/src/fields/blocks/blocks-field.tsx +0 -312
- package/src/fields/checkbox/checkbox-field.module.css +0 -4
- package/src/fields/checkbox/checkbox-field.tsx +0 -54
- package/src/fields/column-formatter.tsx +0 -31
- package/src/fields/date-time-formatter.tsx +0 -22
- package/src/fields/datetime/datetime-field.module.css +0 -13
- package/src/fields/datetime/datetime-field.tsx +0 -54
- package/src/fields/draggable-context-menu.module.css +0 -127
- package/src/fields/draggable-context-menu.tsx +0 -85
- package/src/fields/field-helpers.ts +0 -69
- package/src/fields/field-renderer.module.css +0 -22
- package/src/fields/field-renderer.tsx +0 -288
- package/src/fields/file/file-field.module.css +0 -153
- package/src/fields/file/file-field.tsx +0 -271
- package/src/fields/file/file-upload-field.module.css +0 -101
- package/src/fields/file/file-upload-field.tsx +0 -183
- package/src/fields/group/group-field.module.css +0 -43
- package/src/fields/group/group-field.tsx +0 -84
- package/src/fields/image/image-field.module.css +0 -155
- package/src/fields/image/image-field.tsx +0 -291
- package/src/fields/image/image-upload-field.module.css +0 -123
- package/src/fields/image/image-upload-field.tsx +0 -270
- package/src/fields/local-date-time.tsx +0 -88
- package/src/fields/locale-badge.module.css +0 -37
- package/src/fields/locale-badge.tsx +0 -32
- package/src/fields/numerical/numerical-field.tsx +0 -114
- package/src/fields/relation/relation-display.module.css +0 -36
- package/src/fields/relation/relation-display.tsx +0 -130
- package/src/fields/relation/relation-field.module.css +0 -83
- package/src/fields/relation/relation-field.tsx +0 -206
- package/src/fields/relation/relation-picker.module.css +0 -168
- package/src/fields/relation/relation-picker.tsx +0 -325
- package/src/fields/relation/relation-summary.module.css +0 -55
- package/src/fields/relation/relation-summary.tsx +0 -123
- package/src/fields/select/select-field.module.css +0 -13
- package/src/fields/select/select-field.tsx +0 -61
- package/src/fields/sortable-item.module.css +0 -167
- package/src/fields/sortable-item.tsx +0 -101
- package/src/fields/text/text-field.module.css +0 -13
- package/src/fields/text/text-field.tsx +0 -146
- package/src/fields/text-area/text-area-field.module.css +0 -13
- package/src/fields/text-area/text-area-field.tsx +0 -147
- package/src/fields/use-field-change-handler.ts +0 -112
- package/src/forms/document-actions.module.css +0 -160
- package/src/forms/document-actions.tsx +0 -487
- package/src/forms/form-context.tsx +0 -704
- package/src/forms/form-renderer.module.css +0 -321
- package/src/forms/form-renderer.tsx +0 -888
- package/src/forms/navigation-guard.tsx +0 -98
- package/src/forms/path-widget.module.css +0 -41
- package/src/forms/path-widget.test.tsx +0 -217
- package/src/forms/path-widget.tsx +0 -181
- package/src/forms/upload-executor.ts +0 -190
- package/src/services/field-services-context.tsx +0 -35
- package/src/services/field-services-types.ts +0 -68
- package/src/widgets/diff-viewer/diff-modal.module.css +0 -79
- package/src/widgets/diff-viewer/diff-modal.tsx +0 -184
- package/src/widgets/status-badge/status-badge.module.css +0 -31
- package/src/widgets/status-badge/status-badge.tsx +0 -69
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { CollectionDefinition, ColumnDefinition } from '@byline/core'
|
|
10
|
-
import cx from 'classnames'
|
|
11
|
-
|
|
12
|
-
import styles from './relation-display.module.css'
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Shared render helpers used by both the relation picker modal rows and
|
|
16
|
-
// the relation-summary tile on the edit form. Kept in lock-step so a row
|
|
17
|
-
// rendered in the picker looks identical to the selected tile.
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Render a single row cell from a `ColumnDefinition`, reading the value
|
|
22
|
-
* from the document's `fields` (with a fallback to top-level metadata like
|
|
23
|
-
* `status`, `updated_at`, `path`). Honours both formatter shapes — plain
|
|
24
|
-
* function → its return value, `{ component }` → the component is rendered.
|
|
25
|
-
*/
|
|
26
|
-
export function PickerCell({
|
|
27
|
-
column,
|
|
28
|
-
record,
|
|
29
|
-
}: {
|
|
30
|
-
column: ColumnDefinition
|
|
31
|
-
record: Record<string, any>
|
|
32
|
-
}) {
|
|
33
|
-
const name = String(column.fieldName)
|
|
34
|
-
const value = record?.fields?.[name] ?? record?.[name]
|
|
35
|
-
|
|
36
|
-
let content: any
|
|
37
|
-
if (column.formatter) {
|
|
38
|
-
if (typeof column.formatter === 'function') {
|
|
39
|
-
content = column.formatter(value, record)
|
|
40
|
-
} else {
|
|
41
|
-
const Comp = column.formatter.component
|
|
42
|
-
content = <Comp value={value} record={record} />
|
|
43
|
-
}
|
|
44
|
-
} else if (value == null) {
|
|
45
|
-
content = null
|
|
46
|
-
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
47
|
-
content = String(value)
|
|
48
|
-
} else {
|
|
49
|
-
content = null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<div
|
|
54
|
-
className={cx(
|
|
55
|
-
'byline-relation-cell',
|
|
56
|
-
styles.cell,
|
|
57
|
-
column.align === 'center' && ['byline-relation-cell-center', styles['cell-center']],
|
|
58
|
-
column.align === 'right' && ['byline-relation-cell-right', styles['cell-right']],
|
|
59
|
-
column.className
|
|
60
|
-
)}
|
|
61
|
-
>
|
|
62
|
-
{content}
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** First top-level `text` field name on a collection, or null. */
|
|
68
|
-
export function resolveFallbackDisplayField(
|
|
69
|
-
def: CollectionDefinition | null | undefined
|
|
70
|
-
): string | null {
|
|
71
|
-
if (!def) return null
|
|
72
|
-
const textField = def.fields.find((f) => f.type === 'text')
|
|
73
|
-
return textField?.name ?? null
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Resolve the row's primary label text from the document. */
|
|
77
|
-
export function resolveRowLabel(
|
|
78
|
-
doc: Record<string, any> | null | undefined,
|
|
79
|
-
displayField: string | null
|
|
80
|
-
): string | null {
|
|
81
|
-
if (!doc) return null
|
|
82
|
-
if (displayField) {
|
|
83
|
-
const v = doc.fields?.[displayField]
|
|
84
|
-
if (typeof v === 'string' && v.length > 0) return v
|
|
85
|
-
}
|
|
86
|
-
if (typeof doc.fields?.title === 'string' && doc.fields.title.length > 0) {
|
|
87
|
-
return doc.fields.title as string
|
|
88
|
-
}
|
|
89
|
-
if (typeof doc.path === 'string' && doc.path.length > 0) return doc.path as string
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Build the `fields` projection for the picker listing. Unions:
|
|
95
|
-
* - caller-supplied `displayField`
|
|
96
|
-
* - target schema's `useAsTitle` (always included, even when not visible)
|
|
97
|
-
* - every `fieldName` declared in the admin config's `picker` columns
|
|
98
|
-
* - `title` (metadata fallback for rows with no explicit picker columns)
|
|
99
|
-
*
|
|
100
|
-
* Note that the document `path` is top-level metadata on every list response
|
|
101
|
-
* — it is always returned by `getCollectionDocuments` regardless of the
|
|
102
|
-
* `fields` projection. Callers that need `{ title, path }` for downstream
|
|
103
|
-
* normalisation (e.g. the rich-text link plugin) can rely on:
|
|
104
|
-
* - `record.fields[def.useAsTitle]` for the title
|
|
105
|
-
* - `record.path` for the path
|
|
106
|
-
*
|
|
107
|
-
* Returns `undefined` when no target definition is available, leaving the
|
|
108
|
-
* listing endpoint to decide its own default projection.
|
|
109
|
-
*/
|
|
110
|
-
export function resolveSelectFields(
|
|
111
|
-
def: CollectionDefinition | null | undefined,
|
|
112
|
-
displayField: string | undefined,
|
|
113
|
-
pickerColumns: ColumnDefinition[] | undefined
|
|
114
|
-
): string[] | undefined {
|
|
115
|
-
if (!def) return undefined
|
|
116
|
-
const out = new Set<string>()
|
|
117
|
-
if (displayField) out.add(displayField)
|
|
118
|
-
if (def.useAsTitle) out.add(def.useAsTitle)
|
|
119
|
-
const fallback = resolveFallbackDisplayField(def)
|
|
120
|
-
if (fallback) out.add(fallback)
|
|
121
|
-
if (pickerColumns) {
|
|
122
|
-
for (const col of pickerColumns) {
|
|
123
|
-
const name = String(col.fieldName)
|
|
124
|
-
if (def.fields.some((f) => f.name === name)) out.add(name)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (def.fields.some((f) => f.name === 'title')) out.add('title')
|
|
128
|
-
if (out.size === 0) return undefined
|
|
129
|
-
return Array.from(out)
|
|
130
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RelationField — widget for `type: 'relation'` fields.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-field-relation — outer wrapper
|
|
6
|
-
* .byline-field-relation-header — label row
|
|
7
|
-
* .byline-field-relation-help — help text below the label
|
|
8
|
-
* .byline-field-relation-error-tile — wrapper shown when target collection is unknown
|
|
9
|
-
* .byline-field-relation-error-text — secondary error message below the title
|
|
10
|
-
* .byline-field-relation-tile — bordered selected-value + actions container
|
|
11
|
-
* .byline-field-relation-actions — top-right icon-button group (edit + remove)
|
|
12
|
-
* .byline-field-relation-mono — monospace `<code>` for field/collection names
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
.header,
|
|
16
|
-
:global(.byline-field-relation-header) {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: baseline;
|
|
19
|
-
gap: var(--spacing-8);
|
|
20
|
-
margin-bottom: 0.25rem;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.help,
|
|
24
|
-
:global(.byline-field-relation-help) {
|
|
25
|
-
margin-bottom: 0.25rem;
|
|
26
|
-
color: var(--gray-400);
|
|
27
|
-
font-size: var(--font-size-xs);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.error-tile,
|
|
31
|
-
:global(.byline-field-relation-error-tile) {
|
|
32
|
-
display: flex;
|
|
33
|
-
flex-direction: column;
|
|
34
|
-
gap: 0.25rem;
|
|
35
|
-
margin-top: 0.25rem;
|
|
36
|
-
padding: var(--spacing-8);
|
|
37
|
-
border: var(--border-width-thin) var(--border-style-solid) var(--red-700);
|
|
38
|
-
background-color: oklch(from var(--red-900) l c h / 0.2);
|
|
39
|
-
border-radius: var(--border-radius-md);
|
|
40
|
-
color: var(--red-200);
|
|
41
|
-
font-size: var(--font-size-xs);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.error-text,
|
|
45
|
-
:global(.byline-field-relation-error-text) {
|
|
46
|
-
color: oklch(from var(--red-400) l c h / 0.8);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.tile,
|
|
50
|
-
:global(.byline-field-relation-tile) {
|
|
51
|
-
display: flex;
|
|
52
|
-
align-items: flex-start;
|
|
53
|
-
justify-content: space-between;
|
|
54
|
-
gap: var(--spacing-8);
|
|
55
|
-
margin-top: 0.25rem;
|
|
56
|
-
padding: var(--spacing-8);
|
|
57
|
-
border: var(--border-width-thin) var(--border-style-solid) var(--primary-500);
|
|
58
|
-
border-radius: var(--border-radius-md);
|
|
59
|
-
color: var(--gray-200);
|
|
60
|
-
font-size: var(--font-size-xs);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.actions,
|
|
64
|
-
:global(.byline-field-relation-actions) {
|
|
65
|
-
display: flex;
|
|
66
|
-
align-items: center;
|
|
67
|
-
gap: var(--spacing-4);
|
|
68
|
-
flex-shrink: 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
:global(.byline-field-relation-actions .byline-button) {
|
|
72
|
-
color: var(--gray-900);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
:global(.dark .byline-field-relation-actions .byline-button),
|
|
76
|
-
:global([data-theme="dark"] .byline-field-relation-actions .byline-button) {
|
|
77
|
-
color: var(--gray-200);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.mono,
|
|
81
|
-
:global(.byline-field-relation-mono) {
|
|
82
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
83
|
-
}
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useState } from 'react'
|
|
10
|
-
|
|
11
|
-
import type {
|
|
12
|
-
CollectionAdminConfig,
|
|
13
|
-
CollectionDefinition,
|
|
14
|
-
RelationField as FieldType,
|
|
15
|
-
RelatedDocumentValue,
|
|
16
|
-
} from '@byline/core'
|
|
17
|
-
import { getCollectionAdminConfig, getCollectionDefinition } from '@byline/core'
|
|
18
|
-
import cx from 'classnames'
|
|
19
|
-
|
|
20
|
-
import { useFieldError, useFieldValue } from '../../forms/form-context'
|
|
21
|
-
import { CloseIcon } from '../../icons/close-icon.js'
|
|
22
|
-
import { EditIcon } from '../../icons/edit-icon.js'
|
|
23
|
-
import { Button, ErrorText, IconButton, Label } from '../../uikit.js'
|
|
24
|
-
import styles from './relation-field.module.css'
|
|
25
|
-
import { RelationPicker } from './relation-picker'
|
|
26
|
-
import { RelationSummary } from './relation-summary'
|
|
27
|
-
|
|
28
|
-
// The raw form value for a relation field is `RelatedDocumentValue`, but
|
|
29
|
-
// when the edit loader runs server-side populate the value arrives as a
|
|
30
|
-
// `PopulatedRelationValue` (same base shape, plus `_resolved` / `document`
|
|
31
|
-
// discriminator keys). We accept both here and let `RelationSummary`
|
|
32
|
-
// narrow internally.
|
|
33
|
-
type IncomingRelationValue = RelatedDocumentValue & {
|
|
34
|
-
_resolved?: boolean
|
|
35
|
-
_cycle?: boolean
|
|
36
|
-
document?: Record<string, any>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// RelationField — widget for `type: 'relation'` fields
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
interface RelationFieldProps {
|
|
44
|
-
field: FieldType
|
|
45
|
-
value?: RelatedDocumentValue | null
|
|
46
|
-
defaultValue?: RelatedDocumentValue | null
|
|
47
|
-
onChange?: (value: RelatedDocumentValue | null) => void
|
|
48
|
-
id?: string
|
|
49
|
-
path?: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const RelationField = ({
|
|
53
|
-
field,
|
|
54
|
-
value,
|
|
55
|
-
defaultValue,
|
|
56
|
-
onChange,
|
|
57
|
-
id,
|
|
58
|
-
path,
|
|
59
|
-
}: RelationFieldProps) => {
|
|
60
|
-
const fieldPath = path ?? field.name
|
|
61
|
-
const htmlId = id ?? fieldPath
|
|
62
|
-
const fieldError = useFieldError(fieldPath)
|
|
63
|
-
const fieldValue = useFieldValue<IncomingRelationValue | null | undefined>(fieldPath)
|
|
64
|
-
|
|
65
|
-
const incomingValue: IncomingRelationValue | null =
|
|
66
|
-
fieldValue !== undefined
|
|
67
|
-
? ((fieldValue as IncomingRelationValue | null) ?? null)
|
|
68
|
-
: ((value as IncomingRelationValue | null) ??
|
|
69
|
-
(defaultValue as IncomingRelationValue | null) ??
|
|
70
|
-
null)
|
|
71
|
-
|
|
72
|
-
// Resolve the target collection definition + admin config. The admin
|
|
73
|
-
// config drives the picker-column rendering inside RelationSummary so
|
|
74
|
-
// the selected tile matches the picker row exactly. Missing target →
|
|
75
|
-
// render an inline error and disable the picker.
|
|
76
|
-
const targetDef: CollectionDefinition | null = getCollectionDefinition(field.targetCollection)
|
|
77
|
-
const targetAdminConfig: CollectionAdminConfig | null = getCollectionAdminConfig(
|
|
78
|
-
field.targetCollection
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
const [pickerOpen, setPickerOpen] = useState(false)
|
|
82
|
-
// Cached target document from the most recent picker selection. Lets the
|
|
83
|
-
// tile render real display data (name, thumbnail) immediately after a
|
|
84
|
-
// pick without a round trip. Cleared via the `targetDocumentId`
|
|
85
|
-
// comparison in the render path.
|
|
86
|
-
const [pickedRecord, setPickedRecord] = useState<{
|
|
87
|
-
id: string
|
|
88
|
-
record: Record<string, any>
|
|
89
|
-
} | null>(null)
|
|
90
|
-
|
|
91
|
-
const handleSelect = (selection: {
|
|
92
|
-
targetDocumentId: string
|
|
93
|
-
targetCollectionId: string
|
|
94
|
-
record?: Record<string, any>
|
|
95
|
-
}) => {
|
|
96
|
-
setPickerOpen(false)
|
|
97
|
-
if (selection.record) {
|
|
98
|
-
setPickedRecord({ id: selection.targetDocumentId, record: selection.record })
|
|
99
|
-
} else {
|
|
100
|
-
setPickedRecord(null)
|
|
101
|
-
}
|
|
102
|
-
onChange?.({
|
|
103
|
-
targetDocumentId: selection.targetDocumentId,
|
|
104
|
-
targetCollectionId: selection.targetCollectionId,
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const handleRemove = () => {
|
|
109
|
-
setPickedRecord(null)
|
|
110
|
-
onChange?.(null)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Only carry the cached picker record through to the summary when it
|
|
114
|
-
// still matches the current value — guards against a stale cache after
|
|
115
|
-
// an external value change (e.g. patch rollback).
|
|
116
|
-
const cachedRecord =
|
|
117
|
-
pickedRecord && incomingValue && pickedRecord.id === incomingValue.targetDocumentId
|
|
118
|
-
? pickedRecord.record
|
|
119
|
-
: null
|
|
120
|
-
|
|
121
|
-
const isUnknown = targetDef == null
|
|
122
|
-
const monoClass = cx('byline-field-relation-mono', styles.mono)
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<div className={`byline-field-relation ${field.name}`}>
|
|
126
|
-
<div className={cx('byline-field-relation-header', styles.header)}>
|
|
127
|
-
<Label
|
|
128
|
-
id={`${htmlId}-label`}
|
|
129
|
-
htmlFor={htmlId}
|
|
130
|
-
label={field.label ?? field.name}
|
|
131
|
-
required={!field.optional}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
{field.helpText && (
|
|
135
|
-
<div className={cx('byline-field-relation-help', styles.help)}>{field.helpText}</div>
|
|
136
|
-
)}
|
|
137
|
-
|
|
138
|
-
{isUnknown ? (
|
|
139
|
-
<div className={cx('byline-field-relation-error-tile', styles['error-tile'])}>
|
|
140
|
-
<span>
|
|
141
|
-
Relation field <code className={monoClass}>{field.name}</code> targets unknown
|
|
142
|
-
collection <code className={monoClass}>{field.targetCollection}</code>.
|
|
143
|
-
</span>
|
|
144
|
-
<span className={cx('byline-field-relation-error-text', styles['error-text'])}>
|
|
145
|
-
Register the collection in your Byline config or correct the target path.
|
|
146
|
-
</span>
|
|
147
|
-
</div>
|
|
148
|
-
) : incomingValue ? (
|
|
149
|
-
<div className={cx('byline-field-relation-tile', styles.tile)}>
|
|
150
|
-
<RelationSummary
|
|
151
|
-
targetDefinition={targetDef}
|
|
152
|
-
targetAdminConfig={targetAdminConfig}
|
|
153
|
-
displayField={field.displayField}
|
|
154
|
-
value={incomingValue}
|
|
155
|
-
cachedRecord={cachedRecord}
|
|
156
|
-
/>
|
|
157
|
-
<div className={cx('byline-field-relation-actions', styles.actions)}>
|
|
158
|
-
<IconButton
|
|
159
|
-
id={htmlId}
|
|
160
|
-
type="button"
|
|
161
|
-
intent="noeffect"
|
|
162
|
-
size="xs"
|
|
163
|
-
aria-label={`Change ${targetDef.labels.singular}`}
|
|
164
|
-
onClick={() => setPickerOpen(true)}
|
|
165
|
-
>
|
|
166
|
-
<EditIcon width="15px" height="15px" />
|
|
167
|
-
</IconButton>
|
|
168
|
-
<IconButton
|
|
169
|
-
type="button"
|
|
170
|
-
intent="noeffect"
|
|
171
|
-
size="xs"
|
|
172
|
-
aria-label={`Remove ${targetDef.labels.singular}`}
|
|
173
|
-
onClick={handleRemove}
|
|
174
|
-
>
|
|
175
|
-
<CloseIcon width="15px" height="15px" />
|
|
176
|
-
</IconButton>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
) : (
|
|
180
|
-
<Button
|
|
181
|
-
id={htmlId}
|
|
182
|
-
size="xs"
|
|
183
|
-
variant="outlined"
|
|
184
|
-
intent="noeffect"
|
|
185
|
-
type="button"
|
|
186
|
-
onClick={() => setPickerOpen(true)}
|
|
187
|
-
>
|
|
188
|
-
Select {targetDef.labels.singular}…
|
|
189
|
-
</Button>
|
|
190
|
-
)}
|
|
191
|
-
|
|
192
|
-
{fieldError && <ErrorText id={`${field.name}-error`} text={fieldError} />}
|
|
193
|
-
|
|
194
|
-
{!isUnknown && (
|
|
195
|
-
<RelationPicker
|
|
196
|
-
targetCollectionPath={field.targetCollection}
|
|
197
|
-
targetDefinition={targetDef}
|
|
198
|
-
displayField={field.displayField}
|
|
199
|
-
isOpen={pickerOpen}
|
|
200
|
-
onSelect={handleSelect}
|
|
201
|
-
onDismiss={() => setPickerOpen(false)}
|
|
202
|
-
/>
|
|
203
|
-
)}
|
|
204
|
-
</div>
|
|
205
|
-
)
|
|
206
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RelationPicker — modal listing for selecting a target document.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-field-relation-picker — picker chrome wrapper
|
|
6
|
-
* .byline-field-relation-picker-header — modal header (title)
|
|
7
|
-
* .byline-field-relation-picker-title — title text
|
|
8
|
-
* .byline-field-relation-picker-body — vertical stack of search + list + pager
|
|
9
|
-
* .byline-field-relation-picker-list — scrollable list container
|
|
10
|
-
* .byline-field-relation-picker-loading — centered spinner row
|
|
11
|
-
* .byline-field-relation-picker-error — error message row
|
|
12
|
-
* .byline-field-relation-picker-empty — "no results" message row
|
|
13
|
-
* .byline-field-relation-picker-rows — <ul> of rows
|
|
14
|
-
* .byline-field-relation-picker-row-button — clickable row button
|
|
15
|
-
* .byline-field-relation-picker-row-selected — added when row is selected
|
|
16
|
-
* .byline-field-relation-picker-row-cells — picker-columns layout inside a row
|
|
17
|
-
* .byline-field-relation-picker-row-stack — fallback label/path stack
|
|
18
|
-
* .byline-field-relation-picker-row-label — fallback row label text
|
|
19
|
-
* .byline-field-relation-picker-row-path — fallback row secondary path
|
|
20
|
-
* .byline-field-relation-picker-pager — pager row at the bottom
|
|
21
|
-
* .byline-field-relation-picker-action — Cancel / Select buttons
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
.header,
|
|
25
|
-
:global(.byline-field-relation-picker-header) {
|
|
26
|
-
padding-top: 1rem;
|
|
27
|
-
margin-bottom: var(--spacing-8);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.title,
|
|
31
|
-
:global(.byline-field-relation-picker-title) {
|
|
32
|
-
margin: 0 0 var(--spacing-8) 0;
|
|
33
|
-
font-size: var(--font-size-xl);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.body,
|
|
37
|
-
:global(.byline-field-relation-picker-body) {
|
|
38
|
-
display: flex;
|
|
39
|
-
flex-direction: column;
|
|
40
|
-
gap: 0.75rem;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.list,
|
|
44
|
-
:global(.byline-field-relation-picker-list) {
|
|
45
|
-
min-height: 320px;
|
|
46
|
-
max-height: 420px;
|
|
47
|
-
overflow-y: auto;
|
|
48
|
-
border: var(--border-width-thin) var(--border-style-solid) var(--gray-700);
|
|
49
|
-
border-radius: var(--border-radius-md);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.loading,
|
|
53
|
-
:global(.byline-field-relation-picker-loading) {
|
|
54
|
-
display: flex;
|
|
55
|
-
align-items: center;
|
|
56
|
-
justify-content: center;
|
|
57
|
-
padding: 2.5rem 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.error,
|
|
61
|
-
:global(.byline-field-relation-picker-error) {
|
|
62
|
-
padding: 2.5rem 1rem;
|
|
63
|
-
color: var(--red-500);
|
|
64
|
-
font-size: var(--font-size-sm);
|
|
65
|
-
text-align: center;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.empty,
|
|
69
|
-
:global(.byline-field-relation-picker-empty) {
|
|
70
|
-
padding: 2.5rem 1rem;
|
|
71
|
-
color: var(--gray-400);
|
|
72
|
-
font-size: var(--font-size-sm);
|
|
73
|
-
text-align: center;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.rows,
|
|
77
|
-
:global(.byline-field-relation-picker-rows) {
|
|
78
|
-
margin: 0;
|
|
79
|
-
padding: 0;
|
|
80
|
-
list-style: none;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.rows > li + li,
|
|
84
|
-
:global(.byline-field-relation-picker-rows) > li + li {
|
|
85
|
-
border-top: var(--border-width-thin) var(--border-style-solid) var(--gray-700);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.row-button,
|
|
89
|
-
:global(.byline-field-relation-picker-row-button) {
|
|
90
|
-
width: 100%;
|
|
91
|
-
padding: var(--spacing-8) 0.75rem;
|
|
92
|
-
border: none;
|
|
93
|
-
background: none;
|
|
94
|
-
text-align: left;
|
|
95
|
-
color: inherit;
|
|
96
|
-
cursor: pointer;
|
|
97
|
-
transition: background-color 150ms ease;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.row-button:hover,
|
|
101
|
-
:global(.byline-field-relation-picker-row-button):hover {
|
|
102
|
-
background-color: var(--gray-25);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.row-selected,
|
|
106
|
-
:global(.byline-field-relation-picker-row-selected) {
|
|
107
|
-
background-color: oklch(from var(--primary-200) l c h / 0.3);
|
|
108
|
-
border-left: 2px solid var(--primary-200);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.row-cells,
|
|
112
|
-
:global(.byline-field-relation-picker-row-cells) {
|
|
113
|
-
display: flex;
|
|
114
|
-
align-items: center;
|
|
115
|
-
gap: 0.75rem;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.row-stack,
|
|
119
|
-
:global(.byline-field-relation-picker-row-stack) {
|
|
120
|
-
display: flex;
|
|
121
|
-
flex-direction: column;
|
|
122
|
-
gap: 0.125rem;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.row-label,
|
|
126
|
-
:global(.byline-field-relation-picker-row-label) {
|
|
127
|
-
color: var(--gray-100);
|
|
128
|
-
font-size: var(--font-size-sm);
|
|
129
|
-
overflow: hidden;
|
|
130
|
-
text-overflow: ellipsis;
|
|
131
|
-
white-space: nowrap;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.row-path,
|
|
135
|
-
:global(.byline-field-relation-picker-row-path) {
|
|
136
|
-
color: var(--gray-500);
|
|
137
|
-
font-size: var(--font-size-xs);
|
|
138
|
-
overflow: hidden;
|
|
139
|
-
text-overflow: ellipsis;
|
|
140
|
-
white-space: nowrap;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.pager,
|
|
144
|
-
:global(.byline-field-relation-picker-pager) {
|
|
145
|
-
display: flex;
|
|
146
|
-
align-items: center;
|
|
147
|
-
justify-content: space-between;
|
|
148
|
-
color: var(--gray-400);
|
|
149
|
-
font-size: var(--font-size-xs);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.action,
|
|
153
|
-
:global(.byline-field-relation-picker-action) {
|
|
154
|
-
min-width: 70px;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
:is([data-theme="dark"], :global(.dark)) {
|
|
158
|
-
.row-button:hover,
|
|
159
|
-
:global(.byline-field-relation-picker-row-button):hover {
|
|
160
|
-
background-color: var(--gray-900);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.row-selected,
|
|
164
|
-
:global(.byline-field-relation-picker-row-selected) {
|
|
165
|
-
background-color: oklch(from var(--primary-900) l c h / 0.3);
|
|
166
|
-
border-left: 2px solid var(--primary-400);
|
|
167
|
-
}
|
|
168
|
-
}
|