@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,190 +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
- /**
10
- * Upload Executor
11
- *
12
- * Handles batch execution of pending file uploads at form submission time.
13
- * This enables "deferred uploads" — files are selected/previewed immediately
14
- * but only uploaded when the user clicks Save.
15
- */
16
-
17
- import type { StoredFileValue } from '@byline/core'
18
-
19
- import type { UploadFieldFn } from '../services/field-services-types'
20
- import type { PendingUpload } from './form-context'
21
-
22
- export interface UploadResult {
23
- fieldPath: string
24
- success: boolean
25
- storedFile?: StoredFileValue
26
- error?: string
27
- }
28
-
29
- export interface ExecuteUploadsResult {
30
- /** All upload results (both successful and failed) */
31
- results: UploadResult[]
32
- /** Map of field path to StoredFileValue for successful uploads */
33
- successful: Map<string, StoredFileValue>
34
- /** Map of field path to error message for failed uploads */
35
- errors: Map<string, string>
36
- /** Whether all uploads succeeded */
37
- allSucceeded: boolean
38
- }
39
-
40
- /**
41
- * Execute all pending uploads sequentially.
42
- * Returns a result object with successful uploads and any errors.
43
- *
44
- * @param pendingUploads - Map of field path to PendingUpload
45
- * @param uploadField - Host-provided upload transport (resolved via
46
- * `useBylineFieldServices()` in the calling React tree)
47
- * @returns Promise resolving to ExecuteUploadsResult
48
- */
49
- export async function executeUploads(
50
- pendingUploads: Map<string, PendingUpload>,
51
- uploadField: UploadFieldFn
52
- ): Promise<ExecuteUploadsResult> {
53
- const results: UploadResult[] = []
54
- const successful = new Map<string, StoredFileValue>()
55
- const errors = new Map<string, string>()
56
-
57
- for (const [fieldPath, upload] of pendingUploads.entries()) {
58
- const formData = new FormData()
59
- formData.append('file', upload.file)
60
- // Tell the server which upload-capable field this file belongs to.
61
- // With per-field upload config a collection can have multiple
62
- // image/file fields, each with its own constraints; the server's
63
- // unique-default fallback covers the single-field case but rejects
64
- // multi-field collections without an explicit selector.
65
- formData.append('field', uploadFieldName(fieldPath))
66
-
67
- try {
68
- // Pass createDocument=false — we're uploading for an embedded field,
69
- // the form's save action handles document creation/update.
70
- const result = await uploadField(upload.collectionPath, formData, false)
71
-
72
- results.push({
73
- fieldPath,
74
- success: true,
75
- storedFile: result.storedFile,
76
- })
77
- successful.set(fieldPath, result.storedFile)
78
- } catch (err: unknown) {
79
- const message = err instanceof Error ? err.message : 'Upload failed'
80
- results.push({
81
- fieldPath,
82
- success: false,
83
- error: message,
84
- })
85
- errors.set(fieldPath, message)
86
- }
87
- }
88
-
89
- return {
90
- results,
91
- successful,
92
- errors,
93
- allSucceeded: errors.size === 0,
94
- }
95
- }
96
-
97
- /**
98
- * Extract the leaf field name from a `fieldPath`. Top-level upload
99
- * fields (`'image'`, `'avatar'`) pass through unchanged; nested paths
100
- * (`'profile.avatar'`) reduce to their last segment, since the
101
- * server-side resolver matches against top-level field names today.
102
- * Nested upload fields would need a richer transport selector when
103
- * they land — the host resolver is the natural place to extend.
104
- */
105
- function uploadFieldName(fieldPath: string): string {
106
- const dot = fieldPath.lastIndexOf('.')
107
- return dot === -1 ? fieldPath : fieldPath.slice(dot + 1)
108
- }
109
-
110
- /**
111
- * Progress callback type for upload execution with progress tracking.
112
- */
113
- export type UploadProgressCallback = (info: {
114
- current: number
115
- total: number
116
- fieldPath: string
117
- status: 'uploading' | 'done' | 'error'
118
- }) => void
119
-
120
- /**
121
- * Execute uploads with progress callbacks.
122
- * Useful for showing upload progress in the UI.
123
- */
124
- export async function executeUploadsWithProgress(
125
- pendingUploads: Map<string, PendingUpload>,
126
- uploadField: UploadFieldFn,
127
- onProgress?: UploadProgressCallback
128
- ): Promise<ExecuteUploadsResult> {
129
- const results: UploadResult[] = []
130
- const successful = new Map<string, StoredFileValue>()
131
- const errors = new Map<string, string>()
132
-
133
- const entries = Array.from(pendingUploads.entries())
134
- const total = entries.length
135
-
136
- for (let i = 0; i < entries.length; i++) {
137
- const [fieldPath, upload] = entries[i]
138
-
139
- onProgress?.({
140
- current: i + 1,
141
- total,
142
- fieldPath,
143
- status: 'uploading',
144
- })
145
-
146
- const formData = new FormData()
147
- formData.append('file', upload.file)
148
- formData.append('field', uploadFieldName(fieldPath))
149
-
150
- try {
151
- const result = await uploadField(upload.collectionPath, formData, false)
152
-
153
- results.push({
154
- fieldPath,
155
- success: true,
156
- storedFile: result.storedFile,
157
- })
158
- successful.set(fieldPath, result.storedFile)
159
-
160
- onProgress?.({
161
- current: i + 1,
162
- total,
163
- fieldPath,
164
- status: 'done',
165
- })
166
- } catch (err: unknown) {
167
- const message = err instanceof Error ? err.message : 'Upload failed'
168
- results.push({
169
- fieldPath,
170
- success: false,
171
- error: message,
172
- })
173
- errors.set(fieldPath, message)
174
-
175
- onProgress?.({
176
- current: i + 1,
177
- total,
178
- fieldPath,
179
- status: 'error',
180
- })
181
- }
182
- }
183
-
184
- return {
185
- results,
186
- successful,
187
- errors,
188
- allSucceeded: errors.size === 0,
189
- }
190
- }
@@ -1,35 +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 { createContext, type ReactNode, useContext } from 'react'
10
-
11
- import type { BylineFieldServices } from './field-services-types'
12
-
13
- const FieldServicesContext = createContext<BylineFieldServices | null>(null)
14
-
15
- interface BylineFieldServicesProviderProps {
16
- services: BylineFieldServices
17
- children: ReactNode
18
- }
19
-
20
- export const BylineFieldServicesProvider = ({
21
- services,
22
- children,
23
- }: BylineFieldServicesProviderProps) => (
24
- <FieldServicesContext.Provider value={services}>{children}</FieldServicesContext.Provider>
25
- )
26
-
27
- export const useBylineFieldServices = (): BylineFieldServices => {
28
- const ctx = useContext(FieldServicesContext)
29
- if (!ctx) {
30
- throw new Error(
31
- '@byline/ui: BylineFieldServicesProvider missing. Wrap your admin tree with <BylineFieldServicesProvider services={…} />.'
32
- )
33
- }
34
- return ctx
35
- }
@@ -1,68 +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
- /**
10
- * Framework-neutral function contracts that field/form components in
11
- * `@byline/ui` need from the host application. The host wires concrete
12
- * implementations via `BylineFieldServicesProvider` — typically thin
13
- * adapters around TanStack Start server functions, Next.js server
14
- * actions, or any other RPC-style transport.
15
- */
16
-
17
- import type { StoredFileValue } from '@byline/core'
18
-
19
- export interface CollectionListParams {
20
- page?: number
21
- page_size?: number
22
- order?: string
23
- desc?: boolean
24
- query?: string
25
- locale?: string
26
- status?: string
27
- fields?: string[]
28
- }
29
-
30
- export interface CollectionListDoc {
31
- id: string
32
- path?: string
33
- [field: string]: unknown
34
- }
35
-
36
- export interface CollectionListResponse {
37
- docs: CollectionListDoc[]
38
- meta: { totalPages?: number; [k: string]: unknown }
39
- included: { collection: { id: string; [k: string]: unknown } }
40
- }
41
-
42
- export type GetCollectionDocumentsFn = (input: {
43
- collection: string
44
- params: CollectionListParams
45
- }) => Promise<CollectionListResponse>
46
-
47
- export interface UploadedFileResult {
48
- documentId?: string
49
- documentVersionId?: string
50
- /**
51
- * The persisted file value, including the `variants` array with
52
- * `storagePath`, `storageUrl`, `width`, `height`, and `format` for each
53
- * generated derivative. Single source of truth — the legacy top-level
54
- * `variants: { name, url }[]` is gone.
55
- */
56
- storedFile: StoredFileValue
57
- }
58
-
59
- export type UploadFieldFn = (
60
- collection: string,
61
- formData: FormData,
62
- createDocument?: boolean
63
- ) => Promise<UploadedFileResult>
64
-
65
- export interface BylineFieldServices {
66
- getCollectionDocuments: GetCollectionDocumentsFn
67
- uploadField: UploadFieldFn
68
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * DiffModal — side-by-side document version comparison modal.
3
- *
4
- * Override handles:
5
- * .byline-diff-modal-header — modal header padding
6
- * .byline-diff-modal-title-stack — heading + subtitle stack
7
- * .byline-diff-modal-title — heading
8
- * .byline-diff-modal-subtitle — subtitle line
9
- * .byline-diff-modal-version — inline pill identifying the historical version
10
- * .byline-diff-modal-content — scrollable content
11
- * .byline-diff-modal-state — loading / error placeholder
12
- * .byline-diff-modal-error — error placeholder colour modifier
13
- * .byline-diff-modal-viewer — diff viewer wrapper
14
- */
15
-
16
- .header,
17
- :global(.byline-diff-modal-header) {
18
- padding-top: var(--spacing-16);
19
- margin-bottom: var(--spacing-8);
20
- flex-shrink: 0;
21
- }
22
-
23
- .title-stack,
24
- :global(.byline-diff-modal-title-stack) {
25
- display: flex;
26
- flex-direction: column;
27
- }
28
-
29
- .title,
30
- :global(.byline-diff-modal-title) {
31
- margin: 0;
32
- font-size: var(--font-size-xl);
33
- }
34
-
35
- .subtitle,
36
- :global(.byline-diff-modal-subtitle) {
37
- margin: 0;
38
- font-size: var(--font-size-sm);
39
- color: var(--gray-400);
40
- }
41
-
42
- .version,
43
- :global(.byline-diff-modal-version) {
44
- font-family: var(--font-family-mono);
45
- font-size: var(--font-size-xs);
46
- background-color: var(--canvas-700);
47
- padding: 0 var(--spacing-4);
48
- border-radius: var(--border-radius-sm);
49
- }
50
-
51
- .content,
52
- :global(.byline-diff-modal-content) {
53
- flex: 1 1 0;
54
- overflow: auto;
55
- padding: 0;
56
- min-height: 0;
57
- }
58
-
59
- .state,
60
- :global(.byline-diff-modal-state) {
61
- display: flex;
62
- align-items: center;
63
- justify-content: center;
64
- height: 100%;
65
- gap: var(--spacing-12);
66
- color: var(--gray-400);
67
- }
68
-
69
- .error,
70
- :global(.byline-diff-modal-error) {
71
- color: var(--red-400);
72
- gap: 0;
73
- }
74
-
75
- .viewer,
76
- :global(.byline-diff-modal-viewer) {
77
- font-family: var(--font-family-mono);
78
- font-size: var(--font-size-sm);
79
- }
@@ -1,184 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * This Source Code is subject to the terms of the Mozilla Public
5
- * License, v. 2.0. If a copy of the MPL was not distributed with this
6
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
- *
8
- * Copyright (c) Infonomic Company Limited
9
- */
10
-
11
- import { useEffect, useState } from 'react'
12
-
13
- import cx from 'classnames'
14
- import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued'
15
-
16
- import { CloseIcon, IconButton, LoaderRing, Modal } from '../../uikit.js'
17
- import styles from './diff-modal.module.css'
18
-
19
- // Keys that are per-version metadata rather than content — strip before diffing
20
- // so the diff focuses on meaningful content changes. ClientDocument-shape
21
- // metadata keys after the Phase 7 admin migration.
22
- const STRIP_KEYS = new Set([
23
- 'id',
24
- 'versionId',
25
- 'path',
26
- 'status',
27
- 'createdAt',
28
- 'updatedAt',
29
- 'hasPublishedVersion',
30
- '_publishedVersion',
31
- ])
32
-
33
- function stripMeta(doc: Record<string, unknown>): Record<string, unknown> {
34
- // With the nested document shape, extract just the fields for diffing.
35
- if (doc.fields && typeof doc.fields === 'object') {
36
- return doc.fields as Record<string, unknown>
37
- }
38
- return Object.fromEntries(Object.entries(doc).filter(([k]) => !STRIP_KEYS.has(k)))
39
- }
40
-
41
- export interface DiffModalProps {
42
- isOpen: boolean
43
- onDismiss: () => void
44
- collection: string
45
- documentId: string
46
- /** The `versionId` of the historical version to compare. */
47
- versionId: string
48
- /** A human-readable label for the historical version (e.g. a date string). */
49
- versionLabel: string
50
- /** The already-loaded current (latest) version of the document. */
51
- currentDocument: Record<string, unknown>
52
- /** Content locale to compare — undefined / 'all' shows all locales. */
53
- locale?: string
54
- /**
55
- * Host-provided loader for a historical document version. The diff
56
- * modal is framework-neutral; callers wire this to whatever transport
57
- * they use (e.g. `BylineAdminServices.getCollectionDocumentVersion` in
58
- * the admin shell).
59
- */
60
- loadHistoricalVersion: (
61
- collection: string,
62
- documentId: string,
63
- versionId: string,
64
- locale: string | undefined
65
- ) => Promise<Record<string, unknown>>
66
- }
67
-
68
- export function DiffModal({
69
- isOpen,
70
- onDismiss,
71
- collection,
72
- documentId,
73
- versionId,
74
- versionLabel,
75
- currentDocument,
76
- locale,
77
- loadHistoricalVersion,
78
- }: DiffModalProps) {
79
- const [historicalDoc, setHistoricalDoc] = useState<Record<string, unknown> | null>(null)
80
- const [loading, setLoading] = useState(false)
81
- const [error, setError] = useState<string | null>(null)
82
-
83
- useEffect(() => {
84
- if (!isOpen || !versionId) return
85
-
86
- let cancelled = false
87
- setLoading(true)
88
- setError(null)
89
- setHistoricalDoc(null)
90
-
91
- loadHistoricalVersion(collection, documentId, versionId, locale)
92
- .then((doc) => {
93
- if (cancelled) return
94
- setHistoricalDoc(doc)
95
- })
96
- .catch((err) => {
97
- if (cancelled) return
98
- setError(err instanceof Error ? err.message : 'Failed to load version')
99
- })
100
- .finally(() => {
101
- if (!cancelled) setLoading(false)
102
- })
103
-
104
- return () => {
105
- cancelled = true
106
- }
107
- }, [isOpen, collection, documentId, versionId, locale, loadHistoricalVersion])
108
-
109
- const currentStr = currentDocument ? JSON.stringify(stripMeta(currentDocument), null, 2) : ''
110
-
111
- const historicalStr = historicalDoc ? JSON.stringify(stripMeta(historicalDoc), null, 2) : ''
112
-
113
- return (
114
- <Modal isOpen={isOpen} closeOnOverlayClick={true} onDismiss={onDismiss}>
115
- <Modal.Container
116
- style={{
117
- width: '96vw',
118
- maxWidth: '96vw',
119
- height: '90vh',
120
- maxHeight: '90vh',
121
- display: 'flex',
122
- flexDirection: 'column',
123
- overflow: 'hidden',
124
- }}
125
- >
126
- <Modal.Header className={cx('byline-diff-modal-header', styles.header)}>
127
- <div className={cx('byline-diff-modal-title-stack', styles['title-stack'])}>
128
- <h3 className={cx('byline-diff-modal-title', styles.title)}>Version Comparison</h3>
129
- <p className={cx('byline-diff-modal-subtitle', styles.subtitle)}>
130
- Comparing{' '}
131
- <span className={cx('byline-diff-modal-version', styles.version)}>
132
- {versionLabel}
133
- </span>{' '}
134
- (left) against current version (right)
135
- </p>
136
- </div>
137
- <IconButton onClick={onDismiss} size="xs" aria-label="Close comparison">
138
- <CloseIcon width="15px" height="15px" />
139
- </IconButton>
140
- </Modal.Header>
141
-
142
- <Modal.Content
143
- className={cx('byline-diff-modal-content', styles.content)}
144
- style={{ minHeight: 0 }}
145
- >
146
- {loading && (
147
- <div className={cx('byline-diff-modal-state', styles.state)}>
148
- <LoaderRing size={28} color="#666666" />
149
- <span>Loading version…</span>
150
- </div>
151
- )}
152
-
153
- {error && (
154
- <div
155
- className={cx(
156
- 'byline-diff-modal-state',
157
- 'byline-diff-modal-error',
158
- styles.state,
159
- styles.error
160
- )}
161
- >
162
- {error}
163
- </div>
164
- )}
165
-
166
- {!loading && !error && historicalDoc && (
167
- <div className={cx('byline-diff-modal-viewer', styles.viewer)}>
168
- <ReactDiffViewer
169
- oldValue={historicalStr}
170
- newValue={currentStr}
171
- splitView={true}
172
- compareMethod={DiffMethod.LINES}
173
- useDarkTheme={true}
174
- leftTitle={versionLabel}
175
- rightTitle="Current version"
176
- hideLineNumbers={false}
177
- />
178
- </div>
179
- )}
180
- </Modal.Content>
181
- </Modal.Container>
182
- </Modal>
183
- )
184
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * StatusBadge — workflow-status pill, optionally preceded by a "live" dot.
3
- *
4
- * Override handles:
5
- * .byline-status-badge-wrap — the outer flex container
6
- * .byline-status-badge-dot — the green "published version live" dot
7
- * .byline-status-badge — the badge itself
8
- */
9
-
10
- .wrap,
11
- :global(.byline-status-badge-wrap) {
12
- display: inline-flex;
13
- align-items: center;
14
- gap: var(--spacing-4);
15
- }
16
-
17
- .dot,
18
- :global(.byline-status-badge-dot) {
19
- display: inline-block;
20
- width: 0.5rem;
21
- height: 0.5rem;
22
- border-radius: var(--border-radius-full);
23
- background-color: var(--green-500);
24
- }
25
-
26
- .badge,
27
- :global(.byline-status-badge) {
28
- padding: 0 0.375rem;
29
- font-size: 0.65rem;
30
- line-height: 1.5;
31
- }
@@ -1,69 +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 { WorkflowStatus } from '@byline/core'
10
- import {
11
- WORKFLOW_STATUS_ARCHIVED,
12
- WORKFLOW_STATUS_DRAFT,
13
- WORKFLOW_STATUS_PUBLISHED,
14
- } from '@byline/core'
15
- import cx from 'classnames'
16
-
17
- import { Badge } from '../../uikit.js'
18
- import styles from './status-badge.module.css'
19
-
20
- function statusIntent(status: string): 'success' | 'warning' | 'info' | 'noeffect' {
21
- switch (status) {
22
- case WORKFLOW_STATUS_PUBLISHED:
23
- return 'success'
24
- case WORKFLOW_STATUS_DRAFT:
25
- return 'warning'
26
- case WORKFLOW_STATUS_ARCHIVED:
27
- return 'info'
28
- default:
29
- return 'noeffect'
30
- }
31
- }
32
-
33
- /**
34
- * Compact badge for workflow status values. Maps the three built-in
35
- * statuses (draft, published, archived) to semantic intents and falls
36
- * back to `noeffect` for any custom workflow statuses.
37
- *
38
- * When `hasPublishedVersion` is true and the current status is not
39
- * `published`, a small green dot is rendered before the badge to
40
- * indicate that a published version is live.
41
- *
42
- * Stable override handles: `.byline-status-badge-wrap`,
43
- * `.byline-status-badge-dot`, `.byline-status-badge`.
44
- */
45
- export const StatusBadge = ({
46
- status,
47
- workflowStatuses,
48
- hasPublishedVersion,
49
- }: {
50
- status: string
51
- workflowStatuses: WorkflowStatus[]
52
- hasPublishedVersion?: boolean
53
- }) => {
54
- const label = workflowStatuses.find((s) => s.name === status)?.label ?? String(status ?? '')
55
-
56
- return (
57
- <span className={cx('byline-status-badge-wrap', styles.wrap)}>
58
- {hasPublishedVersion === true && status !== 'published' && (
59
- <span
60
- title="A published version is live"
61
- className={cx('byline-status-badge-dot', styles.dot)}
62
- />
63
- )}
64
- <Badge intent={statusIntent(status)} className={cx('byline-status-badge', styles.badge)}>
65
- {label}
66
- </Badge>
67
- </span>
68
- )
69
- }