@byline/ui 2.5.2 → 2.6.1

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