@byline/ui 2.5.2 → 2.6.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/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/uikit.d.ts +1 -0
- package/dist/uikit.js +1 -0
- package/package.json +2 -8
- package/src/dnd/draggable-sortable/demo/draggable-list-demo.tsx +1 -1
- package/src/react.ts +20 -68
- 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,325 +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 { useCallback, useEffect, useState } from 'react'
|
|
10
|
-
|
|
11
|
-
import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
|
|
12
|
-
import { getCollectionAdminConfig } from '@byline/core'
|
|
13
|
-
import cx from 'classnames'
|
|
14
|
-
|
|
15
|
-
import { useBylineFieldServices } from '../../services/field-services-context'
|
|
16
|
-
import { Button, LoaderRing, Modal, Search } from '../../uikit.js'
|
|
17
|
-
import {
|
|
18
|
-
PickerCell,
|
|
19
|
-
resolveFallbackDisplayField,
|
|
20
|
-
resolveRowLabel,
|
|
21
|
-
resolveSelectFields,
|
|
22
|
-
} from './relation-display'
|
|
23
|
-
import styles from './relation-picker.module.css'
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// RelationPicker — modal listing for selecting a target document
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Row rendering strategy, in priority order:
|
|
31
|
-
* 1. `CollectionAdminConfig.picker` — a ColumnDefinition[] from the target
|
|
32
|
-
* admin config. Each row renders the declared columns side-by-side,
|
|
33
|
-
* reusing any column formatters (thumbnail, date, etc).
|
|
34
|
-
* 2. Explicit `displayField` prop on this component (forwarded from
|
|
35
|
-
* `RelationField.displayField`).
|
|
36
|
-
* 3. `CollectionDefinition.useAsTitle` on the target.
|
|
37
|
-
* 4. First top-level `text` field on the target.
|
|
38
|
-
*
|
|
39
|
-
* Paths 2–4 render a single-line label (primary) + `path` (secondary).
|
|
40
|
-
*/
|
|
41
|
-
interface RelationPickerProps {
|
|
42
|
-
/** The target collection path (e.g. `'media'`). */
|
|
43
|
-
targetCollectionPath: string
|
|
44
|
-
/** The target collection definition (used for labels + displayField fallback). */
|
|
45
|
-
targetDefinition?: CollectionDefinition | null
|
|
46
|
-
/** Explicit display field to render as row label. */
|
|
47
|
-
displayField?: string
|
|
48
|
-
/** Modal open/close state. */
|
|
49
|
-
isOpen: boolean
|
|
50
|
-
/**
|
|
51
|
-
* Called with the picked selection when the user confirms.
|
|
52
|
-
*
|
|
53
|
-
* `record` is the raw document the picker row rendered — the caller can
|
|
54
|
-
* use it to show the selected value in its own tile without a refetch.
|
|
55
|
-
* The fields available on `record` are whatever `resolveSelectFields`
|
|
56
|
-
* asked the listing endpoint for (picker columns + `useAsTitle` +
|
|
57
|
-
* `displayField`), so any display surface downstream of the picker that
|
|
58
|
-
* also renders from those same columns will find the data it needs.
|
|
59
|
-
*/
|
|
60
|
-
onSelect: (selection: {
|
|
61
|
-
targetDocumentId: string
|
|
62
|
-
targetCollectionId: string
|
|
63
|
-
record?: Record<string, any>
|
|
64
|
-
}) => void
|
|
65
|
-
/** Called when the user dismisses the modal. */
|
|
66
|
-
onDismiss: () => void
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const PAGE_SIZE = 15
|
|
70
|
-
|
|
71
|
-
export const RelationPicker = ({
|
|
72
|
-
targetCollectionPath,
|
|
73
|
-
targetDefinition,
|
|
74
|
-
displayField,
|
|
75
|
-
isOpen,
|
|
76
|
-
onSelect,
|
|
77
|
-
onDismiss,
|
|
78
|
-
}: RelationPickerProps) => {
|
|
79
|
-
const [query, setQuery] = useState<string>('')
|
|
80
|
-
const [page, setPage] = useState<number>(1)
|
|
81
|
-
const [selectedDocumentId, setSelectedDocumentId] = useState<string | null>(null)
|
|
82
|
-
const [loading, setLoading] = useState<boolean>(false)
|
|
83
|
-
const [error, setError] = useState<string | null>(null)
|
|
84
|
-
const [documents, setDocuments] = useState<any[]>([])
|
|
85
|
-
const [totalPages, setTotalPages] = useState<number>(1)
|
|
86
|
-
const [collectionId, setCollectionId] = useState<string | null>(null)
|
|
87
|
-
|
|
88
|
-
const { getCollectionDocuments } = useBylineFieldServices()
|
|
89
|
-
|
|
90
|
-
const targetAdminConfig: CollectionAdminConfig | null =
|
|
91
|
-
getCollectionAdminConfig(targetCollectionPath)
|
|
92
|
-
const pickerColumns = targetAdminConfig?.picker
|
|
93
|
-
|
|
94
|
-
// Reset local state each time the modal opens so prior queries don't leak.
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (isOpen) {
|
|
97
|
-
setQuery('')
|
|
98
|
-
setPage(1)
|
|
99
|
-
setSelectedDocumentId(null)
|
|
100
|
-
setError(null)
|
|
101
|
-
}
|
|
102
|
-
}, [isOpen])
|
|
103
|
-
|
|
104
|
-
// Fetch whenever the modal is open and the query / page changes.
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (!isOpen) return
|
|
107
|
-
let cancelled = false
|
|
108
|
-
|
|
109
|
-
const selectFields = resolveSelectFields(targetDefinition, displayField, pickerColumns)
|
|
110
|
-
|
|
111
|
-
setLoading(true)
|
|
112
|
-
setError(null)
|
|
113
|
-
getCollectionDocuments({
|
|
114
|
-
collection: targetCollectionPath,
|
|
115
|
-
params: {
|
|
116
|
-
page,
|
|
117
|
-
page_size: PAGE_SIZE,
|
|
118
|
-
query: query.length > 0 ? query : undefined,
|
|
119
|
-
fields: selectFields,
|
|
120
|
-
},
|
|
121
|
-
})
|
|
122
|
-
.then((response: any) => {
|
|
123
|
-
if (cancelled) return
|
|
124
|
-
setDocuments(response.docs)
|
|
125
|
-
setTotalPages(response.meta.totalPages ?? 1)
|
|
126
|
-
setCollectionId(response.included.collection.id as string)
|
|
127
|
-
})
|
|
128
|
-
.catch((err: any) => {
|
|
129
|
-
if (cancelled) return
|
|
130
|
-
setError(err instanceof Error ? err.message : 'Failed to load documents')
|
|
131
|
-
})
|
|
132
|
-
.finally(() => {
|
|
133
|
-
if (!cancelled) setLoading(false)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
return () => {
|
|
137
|
-
cancelled = true
|
|
138
|
-
}
|
|
139
|
-
}, [
|
|
140
|
-
isOpen,
|
|
141
|
-
targetCollectionPath,
|
|
142
|
-
query,
|
|
143
|
-
page,
|
|
144
|
-
displayField,
|
|
145
|
-
targetDefinition,
|
|
146
|
-
pickerColumns,
|
|
147
|
-
getCollectionDocuments,
|
|
148
|
-
])
|
|
149
|
-
|
|
150
|
-
const resolvedDisplayField =
|
|
151
|
-
displayField ??
|
|
152
|
-
targetDefinition?.useAsTitle ??
|
|
153
|
-
resolveFallbackDisplayField(targetDefinition) ??
|
|
154
|
-
null
|
|
155
|
-
|
|
156
|
-
const handleSelect = useCallback(() => {
|
|
157
|
-
if (!selectedDocumentId || !collectionId) return
|
|
158
|
-
const record = documents.find((d) => d?.id === selectedDocumentId)
|
|
159
|
-
onSelect({
|
|
160
|
-
targetDocumentId: selectedDocumentId,
|
|
161
|
-
targetCollectionId: collectionId,
|
|
162
|
-
record,
|
|
163
|
-
})
|
|
164
|
-
}, [selectedDocumentId, collectionId, documents, onSelect])
|
|
165
|
-
|
|
166
|
-
const title = targetDefinition
|
|
167
|
-
? `Select ${targetDefinition.labels.singular}`
|
|
168
|
-
: `Select ${targetCollectionPath}`
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<Modal isOpen={isOpen} onDismiss={onDismiss}>
|
|
172
|
-
<Modal.Container style={{ maxWidth: '600px', width: '100%' }}>
|
|
173
|
-
<Modal.Header className={cx('byline-field-relation-picker-header', styles.header)}>
|
|
174
|
-
<h3 className={cx('byline-field-relation-picker-title', styles.title)}>{title}</h3>
|
|
175
|
-
</Modal.Header>
|
|
176
|
-
<Modal.Content>
|
|
177
|
-
<div className={cx('byline-field-relation-picker-body', styles.body)}>
|
|
178
|
-
<Search
|
|
179
|
-
onSearch={(q) => {
|
|
180
|
-
setPage(1)
|
|
181
|
-
setQuery(q ?? '')
|
|
182
|
-
}}
|
|
183
|
-
onClear={() => {
|
|
184
|
-
setPage(1)
|
|
185
|
-
setQuery('')
|
|
186
|
-
}}
|
|
187
|
-
inputSize="sm"
|
|
188
|
-
placeholder="Search"
|
|
189
|
-
/>
|
|
190
|
-
|
|
191
|
-
<div className={cx('byline-field-relation-picker-list', styles.list)}>
|
|
192
|
-
{loading && documents.length === 0 && (
|
|
193
|
-
<div className={cx('byline-field-relation-picker-loading', styles.loading)}>
|
|
194
|
-
<LoaderRing size={24} color="#888888" />
|
|
195
|
-
</div>
|
|
196
|
-
)}
|
|
197
|
-
{!loading && error && (
|
|
198
|
-
<div className={cx('byline-field-relation-picker-error', styles.error)}>
|
|
199
|
-
{error}
|
|
200
|
-
</div>
|
|
201
|
-
)}
|
|
202
|
-
{!loading && !error && documents.length === 0 && (
|
|
203
|
-
<div className={cx('byline-field-relation-picker-empty', styles.empty)}>
|
|
204
|
-
No documents found
|
|
205
|
-
</div>
|
|
206
|
-
)}
|
|
207
|
-
{documents.length > 0 && (
|
|
208
|
-
<ul className={cx('byline-field-relation-picker-rows', styles.rows)}>
|
|
209
|
-
{documents.map((doc) => {
|
|
210
|
-
const id = doc.id as string
|
|
211
|
-
const selected = selectedDocumentId === id
|
|
212
|
-
return (
|
|
213
|
-
<li key={id}>
|
|
214
|
-
<button
|
|
215
|
-
type="button"
|
|
216
|
-
className={cx(
|
|
217
|
-
'byline-field-relation-picker-row-button',
|
|
218
|
-
styles['row-button'],
|
|
219
|
-
selected && [
|
|
220
|
-
'byline-field-relation-picker-row-selected',
|
|
221
|
-
styles['row-selected'],
|
|
222
|
-
]
|
|
223
|
-
)}
|
|
224
|
-
onClick={() => setSelectedDocumentId(id)}
|
|
225
|
-
>
|
|
226
|
-
{pickerColumns && pickerColumns.length > 0 ? (
|
|
227
|
-
<div
|
|
228
|
-
className={cx(
|
|
229
|
-
'byline-field-relation-picker-row-cells',
|
|
230
|
-
styles['row-cells']
|
|
231
|
-
)}
|
|
232
|
-
>
|
|
233
|
-
{pickerColumns.map((col) => (
|
|
234
|
-
<PickerCell key={String(col.fieldName)} column={col} record={doc} />
|
|
235
|
-
))}
|
|
236
|
-
</div>
|
|
237
|
-
) : (
|
|
238
|
-
<div
|
|
239
|
-
className={cx(
|
|
240
|
-
'byline-field-relation-picker-row-stack',
|
|
241
|
-
styles['row-stack']
|
|
242
|
-
)}
|
|
243
|
-
>
|
|
244
|
-
<span
|
|
245
|
-
className={cx(
|
|
246
|
-
'byline-field-relation-picker-row-label',
|
|
247
|
-
styles['row-label']
|
|
248
|
-
)}
|
|
249
|
-
>
|
|
250
|
-
{resolveRowLabel(doc, resolvedDisplayField) || id}
|
|
251
|
-
</span>
|
|
252
|
-
{typeof doc.path === 'string' && doc.path.length > 0 && (
|
|
253
|
-
<span
|
|
254
|
-
className={cx(
|
|
255
|
-
'byline-field-relation-picker-row-path',
|
|
256
|
-
styles['row-path']
|
|
257
|
-
)}
|
|
258
|
-
>
|
|
259
|
-
{doc.path}
|
|
260
|
-
</span>
|
|
261
|
-
)}
|
|
262
|
-
</div>
|
|
263
|
-
)}
|
|
264
|
-
</button>
|
|
265
|
-
</li>
|
|
266
|
-
)
|
|
267
|
-
})}
|
|
268
|
-
</ul>
|
|
269
|
-
)}
|
|
270
|
-
</div>
|
|
271
|
-
|
|
272
|
-
{totalPages > 1 && (
|
|
273
|
-
<div className={cx('byline-field-relation-picker-pager', styles.pager)}>
|
|
274
|
-
<Button
|
|
275
|
-
size="xs"
|
|
276
|
-
variant="outlined"
|
|
277
|
-
intent="noeffect"
|
|
278
|
-
type="button"
|
|
279
|
-
disabled={page <= 1 || loading}
|
|
280
|
-
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|
281
|
-
>
|
|
282
|
-
Previous
|
|
283
|
-
</Button>
|
|
284
|
-
<span>
|
|
285
|
-
Page {page} of {totalPages}
|
|
286
|
-
</span>
|
|
287
|
-
<Button
|
|
288
|
-
size="xs"
|
|
289
|
-
variant="outlined"
|
|
290
|
-
intent="noeffect"
|
|
291
|
-
type="button"
|
|
292
|
-
disabled={page >= totalPages || loading}
|
|
293
|
-
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
|
294
|
-
>
|
|
295
|
-
Next
|
|
296
|
-
</Button>
|
|
297
|
-
</div>
|
|
298
|
-
)}
|
|
299
|
-
</div>
|
|
300
|
-
</Modal.Content>
|
|
301
|
-
<Modal.Actions>
|
|
302
|
-
<Button
|
|
303
|
-
size="sm"
|
|
304
|
-
intent="noeffect"
|
|
305
|
-
type="button"
|
|
306
|
-
onClick={onDismiss}
|
|
307
|
-
className={cx('byline-field-relation-picker-action', styles.action)}
|
|
308
|
-
>
|
|
309
|
-
Cancel
|
|
310
|
-
</Button>
|
|
311
|
-
<Button
|
|
312
|
-
size="sm"
|
|
313
|
-
className={cx('byline-field-relation-picker-action', styles.action)}
|
|
314
|
-
intent="primary"
|
|
315
|
-
type="button"
|
|
316
|
-
disabled={!selectedDocumentId}
|
|
317
|
-
onClick={handleSelect}
|
|
318
|
-
>
|
|
319
|
-
Select
|
|
320
|
-
</Button>
|
|
321
|
-
</Modal.Actions>
|
|
322
|
-
</Modal.Container>
|
|
323
|
-
</Modal>
|
|
324
|
-
)
|
|
325
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RelationSummary — selected-value tile for the relation field widget.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-relation-summary — root container
|
|
6
|
-
* .byline-relation-summary-stack — vertical layout used for label + value
|
|
7
|
-
* .byline-relation-summary-row — horizontal layout used for picker columns
|
|
8
|
-
* .byline-relation-summary-kind — small "type" label above the value
|
|
9
|
-
* .byline-relation-summary-value — primary value text
|
|
10
|
-
* .byline-relation-summary-value-mono — monospace fallback (raw uuid / not-found)
|
|
11
|
-
* .byline-relation-summary-missing — added to the value when target is unresolved
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
.stack,
|
|
15
|
-
:global(.byline-relation-summary-stack) {
|
|
16
|
-
display: flex;
|
|
17
|
-
flex-direction: column;
|
|
18
|
-
gap: 0.125rem;
|
|
19
|
-
min-width: 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.row,
|
|
23
|
-
:global(.byline-relation-summary-row) {
|
|
24
|
-
display: flex;
|
|
25
|
-
align-items: center;
|
|
26
|
-
gap: 0.75rem;
|
|
27
|
-
min-width: 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.kind,
|
|
31
|
-
:global(.byline-relation-summary-kind) {
|
|
32
|
-
color: var(--gray-500);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.value,
|
|
36
|
-
:global(.byline-relation-summary-value) {
|
|
37
|
-
color: var(--gray-100);
|
|
38
|
-
overflow: hidden;
|
|
39
|
-
text-overflow: ellipsis;
|
|
40
|
-
white-space: nowrap;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.value-mono,
|
|
44
|
-
:global(.byline-relation-summary-value-mono) {
|
|
45
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
46
|
-
overflow: hidden;
|
|
47
|
-
text-overflow: ellipsis;
|
|
48
|
-
white-space: nowrap;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.missing,
|
|
52
|
-
:global(.byline-relation-summary-missing) {
|
|
53
|
-
font-size: var(--font-size-xs);
|
|
54
|
-
color: var(--red-400);
|
|
55
|
-
}
|
|
@@ -1,123 +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 { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
|
|
10
|
-
import cx from 'classnames'
|
|
11
|
-
|
|
12
|
-
import { PickerCell, resolveFallbackDisplayField, resolveRowLabel } from './relation-display'
|
|
13
|
-
import styles from './relation-summary.module.css'
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// RelationSummary — selected-value tile for the relation field widget.
|
|
17
|
-
//
|
|
18
|
-
// Rendering priority (mirrors RelationPicker so the tile and picker rows
|
|
19
|
-
// look identical):
|
|
20
|
-
// 1. target `CollectionAdminConfig.picker` columns (full fidelity, with
|
|
21
|
-
// formatters — e.g. MediaThumbnail + title)
|
|
22
|
-
// 2. explicit `displayField` prop (from source schema's RelationField)
|
|
23
|
-
// 3. `CollectionDefinition.useAsTitle`
|
|
24
|
-
// 4. first declared text field on the target
|
|
25
|
-
// 5. target UUID (only when nothing else is available — "resolved but
|
|
26
|
-
// naked" or unpopulated)
|
|
27
|
-
//
|
|
28
|
-
// Value source priority:
|
|
29
|
-
// 1. `populated.document` — a `PopulatedRelationValue` attached by the
|
|
30
|
-
// server-side populate pass on first page load.
|
|
31
|
-
// 2. `cachedRecord` — the raw document the picker just handed us after
|
|
32
|
-
// a fresh pick (no server round trip needed).
|
|
33
|
-
// 3. neither — we have only the stored ref; fall through to UUID.
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
interface RelationSummaryProps {
|
|
37
|
-
targetDefinition: CollectionDefinition
|
|
38
|
-
targetAdminConfig: CollectionAdminConfig | null
|
|
39
|
-
displayField?: string
|
|
40
|
-
/** The raw relation value from the form. May be a plain ref or a populated envelope. */
|
|
41
|
-
value: {
|
|
42
|
-
targetDocumentId: string
|
|
43
|
-
targetCollectionId: string
|
|
44
|
-
_resolved?: boolean
|
|
45
|
-
_cycle?: boolean
|
|
46
|
-
document?: Record<string, any>
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* A document record cached client-side from a recent picker selection.
|
|
50
|
-
* Used when `value` is a plain ref (post-pick state) but we still want
|
|
51
|
-
* the tile to render real display data without a refetch. Caller is
|
|
52
|
-
* responsible for clearing/replacing this when the value's
|
|
53
|
-
* `targetDocumentId` changes.
|
|
54
|
-
*/
|
|
55
|
-
cachedRecord?: Record<string, any> | null
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function RelationSummary({
|
|
59
|
-
targetDefinition,
|
|
60
|
-
targetAdminConfig,
|
|
61
|
-
displayField,
|
|
62
|
-
value,
|
|
63
|
-
cachedRecord,
|
|
64
|
-
}: RelationSummaryProps) {
|
|
65
|
-
const pickerColumns = targetAdminConfig?.picker
|
|
66
|
-
|
|
67
|
-
// Unresolved (deleted target).
|
|
68
|
-
if (value._resolved === false) {
|
|
69
|
-
return (
|
|
70
|
-
<div className={cx('byline-relation-summary-stack', styles.stack)}>
|
|
71
|
-
<span className={cx('byline-relation-summary-kind', styles.kind)}>
|
|
72
|
-
{targetDefinition.labels.singular}
|
|
73
|
-
</span>
|
|
74
|
-
<span
|
|
75
|
-
className={cx(
|
|
76
|
-
'byline-relation-summary-value-mono byline-relation-summary-missing',
|
|
77
|
-
styles['value-mono'],
|
|
78
|
-
styles.missing
|
|
79
|
-
)}
|
|
80
|
-
>
|
|
81
|
-
(target not found) {value.targetDocumentId}
|
|
82
|
-
</span>
|
|
83
|
-
</div>
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Prefer the populated envelope's document; fall back to the cached
|
|
88
|
-
// picker record; finally fall back to rendering just the raw ref.
|
|
89
|
-
const record: Record<string, any> | null =
|
|
90
|
-
(value._resolved === true && !value._cycle && value.document) || cachedRecord || null
|
|
91
|
-
|
|
92
|
-
if (record && pickerColumns && pickerColumns.length > 0) {
|
|
93
|
-
return (
|
|
94
|
-
<div className={cx('byline-relation-summary-row', styles.row)}>
|
|
95
|
-
{pickerColumns.map((col) => (
|
|
96
|
-
<PickerCell key={String(col.fieldName)} column={col} record={record} />
|
|
97
|
-
))}
|
|
98
|
-
</div>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const resolvedDisplayField =
|
|
103
|
-
displayField ??
|
|
104
|
-
targetDefinition.useAsTitle ??
|
|
105
|
-
resolveFallbackDisplayField(targetDefinition) ??
|
|
106
|
-
null
|
|
107
|
-
const label = record ? resolveRowLabel(record, resolvedDisplayField) : null
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div className={cx('byline-relation-summary-stack', styles.stack)}>
|
|
111
|
-
<span className={cx('byline-relation-summary-kind', styles.kind)}>
|
|
112
|
-
{targetDefinition.labels.singular}
|
|
113
|
-
</span>
|
|
114
|
-
{label ? (
|
|
115
|
-
<span className={cx('byline-relation-summary-value', styles.value)}>{label}</span>
|
|
116
|
-
) : (
|
|
117
|
-
<span className={cx('byline-relation-summary-value-mono', styles['value-mono'])}>
|
|
118
|
-
{value.targetDocumentId}
|
|
119
|
-
</span>
|
|
120
|
-
)}
|
|
121
|
-
</div>
|
|
122
|
-
)
|
|
123
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SelectField — dropdown form widget.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-field-select — the wrapper div
|
|
6
|
-
* .byline-field-select-dirty — added to the inner select when the
|
|
7
|
-
* field has unsaved local changes
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
.dirty,
|
|
11
|
-
:global(.byline-field-select-dirty) {
|
|
12
|
-
border-color: var(--blue-300);
|
|
13
|
-
}
|
|
@@ -1,61 +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 { SelectField as FieldType } from '@byline/core'
|
|
10
|
-
import cx from 'classnames'
|
|
11
|
-
|
|
12
|
-
import { useFieldError, useFieldValue, useIsDirty } from '../../forms/form-context'
|
|
13
|
-
import { ErrorText, Label, Select } from '../../uikit.js'
|
|
14
|
-
import styles from './select-field.module.css'
|
|
15
|
-
|
|
16
|
-
export const SelectField = ({
|
|
17
|
-
field,
|
|
18
|
-
value,
|
|
19
|
-
defaultValue,
|
|
20
|
-
onChange,
|
|
21
|
-
id,
|
|
22
|
-
path,
|
|
23
|
-
}: {
|
|
24
|
-
field: FieldType
|
|
25
|
-
value?: string
|
|
26
|
-
defaultValue?: string
|
|
27
|
-
onChange?: (value: string) => void
|
|
28
|
-
id?: string
|
|
29
|
-
path?: string
|
|
30
|
-
}) => {
|
|
31
|
-
const fieldPath = path ?? field.name
|
|
32
|
-
const fieldError = useFieldError(fieldPath)
|
|
33
|
-
const isDirty = useIsDirty(fieldPath)
|
|
34
|
-
const fieldValue = useFieldValue<string | undefined>(fieldPath)
|
|
35
|
-
const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
|
|
36
|
-
const htmlId = id ?? fieldPath
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div className={`byline-field-select ${field.name}`}>
|
|
40
|
-
{field.label && (
|
|
41
|
-
<Label id={htmlId} htmlFor={htmlId} label={field.label} required={!field.optional} />
|
|
42
|
-
)}
|
|
43
|
-
<Select<string>
|
|
44
|
-
size="sm"
|
|
45
|
-
id={htmlId}
|
|
46
|
-
name={field.name}
|
|
47
|
-
placeholder="Select an option"
|
|
48
|
-
required={!field.optional}
|
|
49
|
-
value={incomingValue}
|
|
50
|
-
ariaLabel={field.label}
|
|
51
|
-
helpText={field.helpText}
|
|
52
|
-
items={field.options.map((opt) => ({ value: opt.value, label: opt.label }))}
|
|
53
|
-
onValueChange={(value) => {
|
|
54
|
-
if (value != null) onChange?.(value)
|
|
55
|
-
}}
|
|
56
|
-
className={cx(isDirty && ['byline-field-select-dirty', styles.dirty])}
|
|
57
|
-
/>
|
|
58
|
-
{fieldError && <ErrorText id={`${field.name}-error`} text={fieldError} />}
|
|
59
|
-
</div>
|
|
60
|
-
)
|
|
61
|
-
}
|