@byline/ui 2.5.1 → 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,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
- }