@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.
Files changed (211) hide show
  1. package/dist/dnd/draggable-sortable/demo/draggable-list-demo.js +1 -1
  2. package/dist/react.d.ts +18 -54
  3. package/dist/react.js +0 -35
  4. package/dist/uikit.d.ts +1 -0
  5. package/dist/uikit.js +1 -0
  6. package/package.json +2 -8
  7. package/src/dnd/draggable-sortable/demo/draggable-list-demo.tsx +1 -1
  8. package/src/react.ts +20 -68
  9. package/src/uikit.ts +1 -0
  10. package/dist/admin/group.d.ts +0 -27
  11. package/dist/admin/group.js +0 -14
  12. package/dist/admin/group.module.js +0 -6
  13. package/dist/admin/group_module.css +0 -19
  14. package/dist/admin/row.d.ts +0 -25
  15. package/dist/admin/row.js +0 -8
  16. package/dist/admin/row.module.js +0 -5
  17. package/dist/admin/row_module.css +0 -18
  18. package/dist/admin/tabs.d.ts +0 -25
  19. package/dist/admin/tabs.js +0 -35
  20. package/dist/admin/tabs.module.js +0 -10
  21. package/dist/admin/tabs_module.css +0 -68
  22. package/dist/fields/array/array-field.d.ts +0 -14
  23. package/dist/fields/array/array-field.js +0 -176
  24. package/dist/fields/array/array-field.module.js +0 -11
  25. package/dist/fields/array/array-field_module.css +0 -32
  26. package/dist/fields/blocks/blocks-field.d.ts +0 -13
  27. package/dist/fields/blocks/blocks-field.js +0 -244
  28. package/dist/fields/blocks/blocks-field.module.js +0 -26
  29. package/dist/fields/blocks/blocks-field_module.css +0 -107
  30. package/dist/fields/checkbox/checkbox-field.d.ts +0 -16
  31. package/dist/fields/checkbox/checkbox-field.js +0 -28
  32. package/dist/fields/checkbox/checkbox-field.module.js +0 -6
  33. package/dist/fields/checkbox/checkbox-field_module.css +0 -4
  34. package/dist/fields/column-formatter.d.ts +0 -20
  35. package/dist/fields/column-formatter.js +0 -15
  36. package/dist/fields/date-time-formatter.d.ts +0 -16
  37. package/dist/fields/date-time-formatter.js +0 -8
  38. package/dist/fields/datetime/datetime-field.d.ts +0 -16
  39. package/dist/fields/datetime/datetime-field.js +0 -37
  40. package/dist/fields/datetime/datetime-field.module.js +0 -5
  41. package/dist/fields/datetime/datetime-field_module.css +0 -4
  42. package/dist/fields/draggable-context-menu.d.ts +0 -6
  43. package/dist/fields/draggable-context-menu.js +0 -83
  44. package/dist/fields/draggable-context-menu.module.js +0 -15
  45. package/dist/fields/draggable-context-menu_module.css +0 -91
  46. package/dist/fields/field-helpers.d.ts +0 -26
  47. package/dist/fields/field-helpers.js +0 -50
  48. package/dist/fields/field-renderer.d.ts +0 -37
  49. package/dist/fields/field-renderer.js +0 -206
  50. package/dist/fields/field-renderer.module.js +0 -8
  51. package/dist/fields/field-renderer_module.css +0 -11
  52. package/dist/fields/file/file-field.d.ts +0 -19
  53. package/dist/fields/file/file-field.js +0 -226
  54. package/dist/fields/file/file-field.module.js +0 -18
  55. package/dist/fields/file/file-field_module.css +0 -131
  56. package/dist/fields/file/file-upload-field.d.ts +0 -21
  57. package/dist/fields/file/file-upload-field.js +0 -128
  58. package/dist/fields/file/file-upload-field.module.js +0 -15
  59. package/dist/fields/file/file-upload-field_module.css +0 -74
  60. package/dist/fields/group/group-field.d.ts +0 -15
  61. package/dist/fields/group/group-field.js +0 -59
  62. package/dist/fields/group/group-field.module.js +0 -9
  63. package/dist/fields/group/group-field_module.css +0 -27
  64. package/dist/fields/image/image-field.d.ts +0 -19
  65. package/dist/fields/image/image-field.js +0 -242
  66. package/dist/fields/image/image-field.module.js +0 -22
  67. package/dist/fields/image/image-field_module.css +0 -121
  68. package/dist/fields/image/image-upload-field.d.ts +0 -21
  69. package/dist/fields/image/image-upload-field.js +0 -187
  70. package/dist/fields/image/image-upload-field.module.js +0 -19
  71. package/dist/fields/image/image-upload-field_module.css +0 -92
  72. package/dist/fields/local-date-time.d.ts +0 -27
  73. package/dist/fields/local-date-time.js +0 -49
  74. package/dist/fields/locale-badge.d.ts +0 -18
  75. package/dist/fields/locale-badge.js +0 -10
  76. package/dist/fields/locale-badge.module.js +0 -5
  77. package/dist/fields/locale-badge_module.css +0 -27
  78. package/dist/fields/numerical/numerical-field.d.ts +0 -18
  79. package/dist/fields/numerical/numerical-field.js +0 -74
  80. package/dist/fields/relation/relation-display.d.ts +0 -40
  81. package/dist/fields/relation/relation-display.js +0 -58
  82. package/dist/fields/relation/relation-display.module.js +0 -9
  83. package/dist/fields/relation/relation-display_module.css +0 -21
  84. package/dist/fields/relation/relation-field.d.ts +0 -18
  85. package/dist/fields/relation/relation-field.js +0 -146
  86. package/dist/fields/relation/relation-field.module.js +0 -13
  87. package/dist/fields/relation/relation-field_module.css +0 -62
  88. package/dist/fields/relation/relation-picker.d.ts +0 -49
  89. package/dist/fields/relation/relation-picker.js +0 -233
  90. package/dist/fields/relation/relation-picker.module.js +0 -26
  91. package/dist/fields/relation/relation-picker_module.css +0 -124
  92. package/dist/fields/relation/relation-summary.d.ts +0 -31
  93. package/dist/fields/relation/relation-summary.js +0 -50
  94. package/dist/fields/relation/relation-summary.module.js +0 -11
  95. package/dist/fields/relation/relation-summary_module.css +0 -37
  96. package/dist/fields/select/select-field.d.ts +0 -16
  97. package/dist/fields/select/select-field.js +0 -50
  98. package/dist/fields/select/select-field.module.js +0 -5
  99. package/dist/fields/select/select-field_module.css +0 -4
  100. package/dist/fields/sortable-item.d.ts +0 -15
  101. package/dist/fields/sortable-item.js +0 -80
  102. package/dist/fields/sortable-item.module.js +0 -22
  103. package/dist/fields/sortable-item_module.css +0 -124
  104. package/dist/fields/text/text-field.d.ts +0 -20
  105. package/dist/fields/text/text-field.js +0 -104
  106. package/dist/fields/text/text-field.module.js +0 -6
  107. package/dist/fields/text/text-field_module.css +0 -5
  108. package/dist/fields/text-area/text-area-field.d.ts +0 -20
  109. package/dist/fields/text-area/text-area-field.js +0 -105
  110. package/dist/fields/text-area/text-area-field.module.js +0 -6
  111. package/dist/fields/text-area/text-area-field_module.css +0 -5
  112. package/dist/fields/use-field-change-handler.d.ts +0 -23
  113. package/dist/fields/use-field-change-handler.js +0 -52
  114. package/dist/forms/document-actions.d.ts +0 -48
  115. package/dist/forms/document-actions.js +0 -469
  116. package/dist/forms/document-actions.module.js +0 -34
  117. package/dist/forms/document-actions_module.css +0 -118
  118. package/dist/forms/form-context.d.ts +0 -89
  119. package/dist/forms/form-context.js +0 -466
  120. package/dist/forms/form-renderer.d.ts +0 -98
  121. package/dist/forms/form-renderer.js +0 -591
  122. package/dist/forms/form-renderer.module.js +0 -46
  123. package/dist/forms/form-renderer_module.css +0 -245
  124. package/dist/forms/navigation-guard.d.ts +0 -54
  125. package/dist/forms/navigation-guard.js +0 -22
  126. package/dist/forms/path-widget.d.ts +0 -36
  127. package/dist/forms/path-widget.js +0 -107
  128. package/dist/forms/path-widget.module.js +0 -8
  129. package/dist/forms/path-widget_module.css +0 -29
  130. package/dist/forms/upload-executor.d.ts +0 -57
  131. package/dist/forms/upload-executor.js +0 -92
  132. package/dist/services/field-services-context.d.ts +0 -16
  133. package/dist/services/field-services-context.js +0 -13
  134. package/dist/services/field-services-types.d.ts +0 -63
  135. package/dist/services/field-services-types.js +0 -1
  136. package/dist/widgets/diff-viewer/diff-modal.d.ts +0 -22
  137. package/dist/widgets/diff-viewer/diff-modal.js +0 -146
  138. package/dist/widgets/diff-viewer/diff-modal.module.js +0 -14
  139. package/dist/widgets/diff-viewer/diff-modal_module.css +0 -56
  140. package/dist/widgets/status-badge/status-badge.d.ts +0 -25
  141. package/dist/widgets/status-badge/status-badge.js +0 -35
  142. package/dist/widgets/status-badge/status-badge.module.js +0 -7
  143. package/dist/widgets/status-badge/status-badge_module.css +0 -20
  144. package/src/admin/group.module.css +0 -41
  145. package/src/admin/group.tsx +0 -40
  146. package/src/admin/row.module.css +0 -32
  147. package/src/admin/row.tsx +0 -33
  148. package/src/admin/tabs.module.css +0 -107
  149. package/src/admin/tabs.tsx +0 -82
  150. package/src/fields/array/array-field.module.css +0 -48
  151. package/src/fields/array/array-field.tsx +0 -266
  152. package/src/fields/blocks/blocks-field.module.css +0 -148
  153. package/src/fields/blocks/blocks-field.tsx +0 -312
  154. package/src/fields/checkbox/checkbox-field.module.css +0 -4
  155. package/src/fields/checkbox/checkbox-field.tsx +0 -54
  156. package/src/fields/column-formatter.tsx +0 -31
  157. package/src/fields/date-time-formatter.tsx +0 -22
  158. package/src/fields/datetime/datetime-field.module.css +0 -13
  159. package/src/fields/datetime/datetime-field.tsx +0 -54
  160. package/src/fields/draggable-context-menu.module.css +0 -127
  161. package/src/fields/draggable-context-menu.tsx +0 -85
  162. package/src/fields/field-helpers.ts +0 -69
  163. package/src/fields/field-renderer.module.css +0 -22
  164. package/src/fields/field-renderer.tsx +0 -288
  165. package/src/fields/file/file-field.module.css +0 -153
  166. package/src/fields/file/file-field.tsx +0 -271
  167. package/src/fields/file/file-upload-field.module.css +0 -101
  168. package/src/fields/file/file-upload-field.tsx +0 -183
  169. package/src/fields/group/group-field.module.css +0 -43
  170. package/src/fields/group/group-field.tsx +0 -84
  171. package/src/fields/image/image-field.module.css +0 -155
  172. package/src/fields/image/image-field.tsx +0 -291
  173. package/src/fields/image/image-upload-field.module.css +0 -123
  174. package/src/fields/image/image-upload-field.tsx +0 -270
  175. package/src/fields/local-date-time.tsx +0 -88
  176. package/src/fields/locale-badge.module.css +0 -37
  177. package/src/fields/locale-badge.tsx +0 -32
  178. package/src/fields/numerical/numerical-field.tsx +0 -114
  179. package/src/fields/relation/relation-display.module.css +0 -36
  180. package/src/fields/relation/relation-display.tsx +0 -130
  181. package/src/fields/relation/relation-field.module.css +0 -83
  182. package/src/fields/relation/relation-field.tsx +0 -206
  183. package/src/fields/relation/relation-picker.module.css +0 -168
  184. package/src/fields/relation/relation-picker.tsx +0 -325
  185. package/src/fields/relation/relation-summary.module.css +0 -55
  186. package/src/fields/relation/relation-summary.tsx +0 -123
  187. package/src/fields/select/select-field.module.css +0 -13
  188. package/src/fields/select/select-field.tsx +0 -61
  189. package/src/fields/sortable-item.module.css +0 -167
  190. package/src/fields/sortable-item.tsx +0 -101
  191. package/src/fields/text/text-field.module.css +0 -13
  192. package/src/fields/text/text-field.tsx +0 -146
  193. package/src/fields/text-area/text-area-field.module.css +0 -13
  194. package/src/fields/text-area/text-area-field.tsx +0 -147
  195. package/src/fields/use-field-change-handler.ts +0 -112
  196. package/src/forms/document-actions.module.css +0 -160
  197. package/src/forms/document-actions.tsx +0 -487
  198. package/src/forms/form-context.tsx +0 -704
  199. package/src/forms/form-renderer.module.css +0 -321
  200. package/src/forms/form-renderer.tsx +0 -888
  201. package/src/forms/navigation-guard.tsx +0 -98
  202. package/src/forms/path-widget.module.css +0 -41
  203. package/src/forms/path-widget.test.tsx +0 -217
  204. package/src/forms/path-widget.tsx +0 -181
  205. package/src/forms/upload-executor.ts +0 -190
  206. package/src/services/field-services-context.tsx +0 -35
  207. package/src/services/field-services-types.ts +0 -68
  208. package/src/widgets/diff-viewer/diff-modal.module.css +0 -79
  209. package/src/widgets/diff-viewer/diff-modal.tsx +0 -184
  210. package/src/widgets/status-badge/status-badge.module.css +0 -31
  211. 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
- }