@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,271 +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 {
10
- type FileField as FieldType,
11
- isPendingStoredFileValue,
12
- type StoredFileValue,
13
- } from '@byline/core'
14
- import cx from 'classnames'
15
-
16
- import { IconButton } from '../../components/button/icon-button.js'
17
- import {
18
- useFieldError,
19
- useFieldValue,
20
- useFormContext,
21
- useIsDirty,
22
- useIsFieldUploading,
23
- } from '../../forms/form-context'
24
- import { CloseIcon } from '../../icons/close-icon.js'
25
- import { DocumentIcon } from '../../icons/document-icon.js'
26
- import { DownloadIcon } from '../../icons/download-icon.js'
27
- import { VideoIcon } from '../../icons/video-icon.js'
28
- import { ErrorText, HelpText, Label, LoaderRing } from '../../uikit.js'
29
- import { useFieldChangeHandler } from '../use-field-change-handler'
30
- import styles from './file-field.module.css'
31
- import { FileUploadField } from './file-upload-field'
32
-
33
- /**
34
- * Trigger a download via a temporary anchor. Mirrors the helper in
35
- * `image-lightbox.tsx`: same-origin URLs respect the `download` attribute and
36
- * save with the suggested filename; cross-origin URLs without CORS headers
37
- * fall through to navigation in a new tab, where the user can right-click
38
- * Save As.
39
- */
40
- function triggerDownload(url: string, filename?: string) {
41
- if (typeof document === 'undefined') return
42
- const a = document.createElement('a')
43
- a.href = url
44
- if (filename) a.download = filename
45
- a.target = '_blank'
46
- a.rel = 'noreferrer'
47
- document.body.appendChild(a)
48
- a.click()
49
- document.body.removeChild(a)
50
- }
51
-
52
- interface FileFieldProps {
53
- field: FieldType
54
- /** Collection path required to call the /upload endpoint. */
55
- collectionPath?: string
56
- value?: StoredFileValue | null
57
- defaultValue?: StoredFileValue | null
58
- onChange?: (value: StoredFileValue | null) => void
59
- path?: string
60
- }
61
-
62
- export const FileField = ({
63
- field,
64
- collectionPath,
65
- value,
66
- defaultValue,
67
- onChange: _onChange,
68
- path,
69
- }: FileFieldProps) => {
70
- const fieldPath = path ?? field.name
71
- const fieldError = useFieldError(fieldPath)
72
- const isDirty = useIsDirty(fieldPath)
73
- const fieldValue = useFieldValue<StoredFileValue | null | undefined>(fieldPath)
74
- const isUploading = useIsFieldUploading(fieldPath)
75
- const { removePendingUpload } = useFormContext()
76
-
77
- const handleChange = useFieldChangeHandler(field, fieldPath)
78
-
79
- // Mirror the image-field rule: once the field has been touched, the form
80
- // value is authoritative (even when null, so a click-to-remove sticks);
81
- // otherwise fall back to props.
82
- const incomingValue = isDirty
83
- ? (fieldValue ?? null)
84
- : (value ?? fieldValue ?? defaultValue ?? null)
85
-
86
- const isPending = isPendingStoredFileValue(incomingValue)
87
-
88
- // Legacy placeholder shape — kept for backwards compatibility with older
89
- // seed data, matching the image-field check.
90
- const isOldPlaceholder = (v: unknown): boolean => {
91
- if (!v || typeof v !== 'object') return false
92
- const maybe = v as Partial<StoredFileValue>
93
- return maybe.storageProvider === 'placeholder' && maybe.storagePath === 'pending'
94
- }
95
-
96
- const showUploadWidget = incomingValue == null || isOldPlaceholder(incomingValue)
97
-
98
- const handleRemove = () => {
99
- if (isPending) {
100
- removePendingUpload(fieldPath)
101
- }
102
- handleChange(null)
103
- }
104
-
105
- // MIME-driven glyph dispatch. Until a dedicated VideoField primitive lands,
106
- // the FileField is the canonical home for video uploads — the schema's
107
- // `upload.allowedMimeTypes` decides what gets in, and we swap the glyph
108
- // here based on the resolved MIME so the tile reads as "video" rather
109
- // than "generic document".
110
- const isVideo = incomingValue?.mimeType?.startsWith('video/') === true
111
- const FileGlyph = isVideo ? VideoIcon : DocumentIcon
112
-
113
- const htmlId = fieldPath
114
-
115
- return (
116
- <div className={`byline-field-file ${field.name}`}>
117
- <div className={cx('byline-field-file-header', styles.header)}>
118
- <Label
119
- id={htmlId}
120
- htmlFor={htmlId}
121
- label={field.label ?? field.name}
122
- required={!field.optional}
123
- />
124
- </div>
125
-
126
- {showUploadWidget ? (
127
- collectionPath ? (
128
- <FileUploadField
129
- field={field}
130
- collectionPath={collectionPath}
131
- fieldPath={fieldPath}
132
- onUploaded={(uploaded) => {
133
- handleChange(uploaded)
134
- }}
135
- />
136
- ) : (
137
- <div className={cx('byline-field-file-empty', styles.empty)}>No file selected</div>
138
- )
139
- ) : (
140
- <div className={cx('byline-field-file-tile', styles.tile)}>
141
- {isUploading && (
142
- <div
143
- className={cx('byline-field-file-uploading', styles.uploading)}
144
- aria-live="polite"
145
- aria-busy="true"
146
- >
147
- <LoaderRing />
148
- </div>
149
- )}
150
- {collectionPath && (
151
- <div className={cx('byline-field-file-actions', styles.actions)}>
152
- {!isPending && incomingValue?.storageUrl && (
153
- <IconButton
154
- type="button"
155
- intent="noeffect"
156
- onClick={() =>
157
- triggerDownload(
158
- incomingValue.storageUrl as string,
159
- incomingValue.originalFilename ?? incomingValue.filename
160
- )
161
- }
162
- size="xs"
163
- disabled={isUploading}
164
- aria-label="Download file"
165
- >
166
- <DownloadIcon width="15px" height="15px" />
167
- </IconButton>
168
- )}
169
- <IconButton
170
- type="button"
171
- intent="noeffect"
172
- onClick={handleRemove}
173
- size="xs"
174
- disabled={isUploading}
175
- aria-label="Remove file"
176
- >
177
- <CloseIcon width="15px" height="15px" />
178
- </IconButton>
179
- </div>
180
- )}
181
- {/* Document icon + (optional) pending badge — mirrors the
182
- image-field's preview-wrap so the file tile has the same
183
- visual hierarchy: glyph on the left, metadata on the right.
184
- When the file is stored (non-pending and resolvable storageUrl),
185
- the wrap is rendered as an anchor that opens the asset in a new
186
- tab — browser-native viewer dispatch (PDFs render inline,
187
- non-renderable types fall through to download). */}
188
- {!isPending && incomingValue?.storageUrl ? (
189
- <a
190
- href={incomingValue.storageUrl}
191
- target="_blank"
192
- rel="noreferrer"
193
- aria-label={`Open ${incomingValue.originalFilename ?? incomingValue.filename} in a new tab`}
194
- className={cx('byline-field-file-icon-wrap', styles['icon-wrap'])}
195
- >
196
- <FileGlyph
197
- width="48px"
198
- height="48px"
199
- className={cx('byline-field-file-icon', styles.icon)}
200
- />
201
- </a>
202
- ) : (
203
- <div className={cx('byline-field-file-icon-wrap', styles['icon-wrap'])}>
204
- <FileGlyph
205
- width="48px"
206
- height="48px"
207
- className={cx('byline-field-file-icon', styles.icon)}
208
- />
209
- {isPending && (
210
- <div className={cx('byline-field-file-pending', styles.pending)}>
211
- Pending upload
212
- </div>
213
- )}
214
- </div>
215
- )}
216
- <div className={cx('byline-field-file-meta', styles.meta)}>
217
- <div>
218
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>
219
- Filename:
220
- </span>{' '}
221
- {incomingValue?.filename}
222
- </div>
223
- <div>
224
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>
225
- Original:
226
- </span>{' '}
227
- {incomingValue?.originalFilename}
228
- </div>
229
- <div>
230
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>Type:</span>{' '}
231
- {incomingValue?.mimeType}
232
- </div>
233
- <div>
234
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>Size:</span>{' '}
235
- {incomingValue?.fileSize}
236
- </div>
237
- {isPending ? (
238
- <div>
239
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>
240
- Status:
241
- </span>{' '}
242
- <span className={cx('byline-field-file-meta-pending', styles['meta-pending'])}>
243
- Will upload on save
244
- </span>
245
- </div>
246
- ) : (
247
- <>
248
- <div>
249
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>
250
- Storage:
251
- </span>{' '}
252
- {incomingValue?.storageProvider}
253
- </div>
254
- <div>
255
- <span className={cx('byline-field-file-meta-key', styles['meta-key'])}>
256
- Path:
257
- </span>{' '}
258
- {incomingValue?.storagePath}
259
- </div>
260
- </>
261
- )}
262
- </div>
263
- </div>
264
- )}
265
-
266
- {field.helpText && <HelpText text={field.helpText} />}
267
-
268
- {fieldError && <ErrorText id={`${field.name}-error`} text={fieldError} />}
269
- </div>
270
- )
271
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * FileUploadField — drag-and-drop file picker that registers a deferred
3
- * upload in form context.
4
- *
5
- * Override handles:
6
- * .byline-field-file-upload — root wrapper
7
- * .byline-field-file-upload-input — visually-hidden file input
8
- * .byline-field-file-upload-zone — clickable / drop target
9
- * .byline-field-file-upload-zone-active — drag-hovered state
10
- * .byline-field-file-upload-zone-busy — processing state
11
- * .byline-field-file-upload-icon — upload icon svg
12
- * .byline-field-file-upload-label — primary text inside the zone
13
- * .byline-field-file-upload-action — "browse" link inside the label
14
- * .byline-field-file-upload-error — error message paragraph
15
- */
16
-
17
- .root,
18
- :global(.byline-field-file-upload) {
19
- margin-top: 0.25rem;
20
- }
21
-
22
- .input,
23
- :global(.byline-field-file-upload-input) {
24
- position: absolute;
25
- width: 1px;
26
- height: 1px;
27
- padding: 0;
28
- margin: -1px;
29
- overflow: hidden;
30
- clip: rect(0, 0, 0, 0);
31
- white-space: nowrap;
32
- border: 0;
33
- }
34
-
35
- .zone,
36
- :global(.byline-field-file-upload-zone) {
37
- display: flex;
38
- flex-direction: column;
39
- align-items: center;
40
- justify-content: center;
41
- gap: var(--spacing-8);
42
- padding: 1.5rem 1rem;
43
- border: 2px dashed var(--gray-600);
44
- border-radius: var(--border-radius-lg);
45
- color: var(--gray-400);
46
- text-align: center;
47
- cursor: pointer;
48
- user-select: none;
49
- transition:
50
- color 150ms ease,
51
- background-color 150ms ease,
52
- border-color 150ms ease;
53
- }
54
-
55
- .zone:hover,
56
- :global(.byline-field-file-upload-zone):hover {
57
- border-color: var(--primary-500);
58
- background-color: oklch(from var(--primary-900) l c h / 0.1);
59
- }
60
-
61
- .zone-active,
62
- :global(.byline-field-file-upload-zone-active) {
63
- border-color: var(--primary-400);
64
- background-color: oklch(from var(--primary-900) l c h / 0.2);
65
- color: var(--primary-300);
66
- }
67
-
68
- .zone-busy,
69
- :global(.byline-field-file-upload-zone-busy) {
70
- border-color: var(--gray-700);
71
- background-color: oklch(from var(--canvas-800) l c h / 0.5);
72
- color: var(--gray-600);
73
- cursor: not-allowed;
74
- }
75
-
76
- .icon,
77
- :global(.byline-field-file-upload-icon) {
78
- width: 1.75rem;
79
- height: 1.75rem;
80
- opacity: 0.6;
81
- }
82
-
83
- .label,
84
- :global(.byline-field-file-upload-label) {
85
- font-size: var(--font-size-xs);
86
- font-weight: var(--font-weight-medium);
87
- }
88
-
89
- .action,
90
- :global(.byline-field-file-upload-action) {
91
- color: var(--primary-400);
92
- text-decoration: underline;
93
- text-underline-offset: 2px;
94
- }
95
-
96
- .error,
97
- :global(.byline-field-file-upload-error) {
98
- margin-top: 0.375rem;
99
- color: var(--red-400);
100
- font-size: var(--font-size-xs);
101
- }
@@ -1,183 +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
- * FileUploadField
11
- *
12
- * Generic drag-and-drop / click-to-browse file picker that prepares a file for
13
- * upload. Mirrors `ImageUploadField` but without image-specific validation or
14
- * dimension extraction. The actual upload is deferred until form submission —
15
- * this component stores the file in the form context's pending uploads and
16
- * emits a placeholder StoredFileValue with a blob URL (used by the form
17
- * orchestrator for cleanup; not shown to the user).
18
- */
19
-
20
- import type { ChangeEvent, DragEvent } from 'react'
21
- import { useCallback, useRef, useState } from 'react'
22
-
23
- import {
24
- createPendingStoredFileValue,
25
- type FileField as FieldType,
26
- type PendingStoredFileValue,
27
- type StoredFileValue,
28
- } from '@byline/core'
29
- import cx from 'classnames'
30
-
31
- import { useFormContext } from '../../forms/form-context'
32
- import styles from './file-upload-field.module.css'
33
-
34
- interface FileUploadFieldProps {
35
- field: FieldType
36
- /** Collection path used to build the upload URL (e.g. `'media'`). */
37
- collectionPath: string
38
- /** Field path in the form (e.g. `'attachment'` or `'content.0.attachment'`). */
39
- fieldPath: string
40
- /** Called with the PendingStoredFileValue for immediate UI update. */
41
- onUploaded: (value: StoredFileValue | PendingStoredFileValue) => void
42
- /** Optional `accept` MIME-type / extension string for the native file input. */
43
- accept?: string
44
- }
45
-
46
- type SelectionStatus = 'idle' | 'processing' | 'error'
47
-
48
- export const FileUploadField = ({
49
- field: _field,
50
- collectionPath,
51
- fieldPath,
52
- onUploaded,
53
- accept,
54
- }: FileUploadFieldProps) => {
55
- const inputRef = useRef<HTMLInputElement>(null)
56
- const [status, setStatus] = useState<SelectionStatus>('idle')
57
- const [errorMessage, setErrorMessage] = useState<string | null>(null)
58
- const [isDragOver, setIsDragOver] = useState(false)
59
- const { addPendingUpload } = useFormContext()
60
-
61
- const handleFileSelected = useCallback(
62
- (file: File) => {
63
- setStatus('processing')
64
- setErrorMessage(null)
65
-
66
- // Blob URL is created so the form orchestrator can revoke it on cleanup
67
- // alongside image-field uploads; it isn't surfaced in the UI here.
68
- const previewUrl = URL.createObjectURL(file)
69
-
70
- const pendingValue = createPendingStoredFileValue(file, previewUrl)
71
-
72
- addPendingUpload(fieldPath, {
73
- file,
74
- previewUrl,
75
- collectionPath,
76
- })
77
-
78
- setStatus('idle')
79
- onUploaded(pendingValue)
80
- },
81
- [collectionPath, fieldPath, addPendingUpload, onUploaded]
82
- )
83
-
84
- const handleFileChange = useCallback(
85
- (e: ChangeEvent<HTMLInputElement>) => {
86
- const file = e.target.files?.[0]
87
- if (file) handleFileSelected(file)
88
- // Reset so re-selecting the same file fires the event again.
89
- e.target.value = ''
90
- },
91
- [handleFileSelected]
92
- )
93
-
94
- const handleBrowseClick = useCallback(() => {
95
- inputRef.current?.click()
96
- }, [])
97
-
98
- const handleDragOver = useCallback((e: DragEvent<HTMLDivElement>) => {
99
- e.preventDefault()
100
- setIsDragOver(true)
101
- }, [])
102
-
103
- const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
104
- e.preventDefault()
105
- setIsDragOver(false)
106
- }, [])
107
-
108
- const handleDrop = useCallback(
109
- (e: DragEvent<HTMLDivElement>) => {
110
- e.preventDefault()
111
- setIsDragOver(false)
112
- const file = e.dataTransfer.files?.[0]
113
- if (file) handleFileSelected(file)
114
- },
115
- [handleFileSelected]
116
- )
117
-
118
- const isProcessing = status === 'processing'
119
-
120
- return (
121
- <div className={cx('byline-field-file-upload', styles.root)}>
122
- <input
123
- ref={inputRef}
124
- type="file"
125
- accept={accept}
126
- className={cx('byline-field-file-upload-input', styles.input)}
127
- onChange={handleFileChange}
128
- disabled={isProcessing}
129
- aria-hidden="true"
130
- tabIndex={-1}
131
- />
132
-
133
- <div
134
- role="button"
135
- tabIndex={0}
136
- aria-label="Upload file — drag and drop or click to browse"
137
- onDragOver={handleDragOver}
138
- onDragLeave={handleDragLeave}
139
- onDrop={handleDrop}
140
- onClick={handleBrowseClick}
141
- onKeyDown={(e) => {
142
- if (e.key === 'Enter' || e.key === ' ') {
143
- e.preventDefault()
144
- handleBrowseClick()
145
- }
146
- }}
147
- className={cx(
148
- 'byline-field-file-upload-zone',
149
- styles.zone,
150
- isDragOver &&
151
- !isProcessing && ['byline-field-file-upload-zone-active', styles['zone-active']],
152
- isProcessing && ['byline-field-file-upload-zone-busy', styles['zone-busy']]
153
- )}
154
- >
155
- <svg
156
- xmlns="http://www.w3.org/2000/svg"
157
- className={cx('byline-field-file-upload-icon', styles.icon)}
158
- fill="none"
159
- viewBox="0 0 24 24"
160
- stroke="currentColor"
161
- strokeWidth={1.5}
162
- aria-hidden="true"
163
- >
164
- <path
165
- strokeLinecap="round"
166
- strokeLinejoin="round"
167
- d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
168
- />
169
- </svg>
170
- <span className={cx('byline-field-file-upload-label', styles.label)}>
171
- Drop file here or{' '}
172
- <span className={cx('byline-field-file-upload-action', styles.action)}>browse</span>
173
- </span>
174
- </div>
175
-
176
- {status === 'error' && errorMessage && (
177
- <p className={cx('byline-field-file-upload-error', styles.error)} role="alert">
178
- {errorMessage}
179
- </p>
180
- )}
181
- </div>
182
- )
183
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * GroupField — fixed-order group of child fields.
3
- *
4
- * Override handles:
5
- * .byline-field-group — outer wrapper
6
- * .byline-field-group-header — label/help text block
7
- * .byline-field-group-title — title heading
8
- * .byline-field-group-required — required asterisk
9
- * .byline-field-group-help — help text below title
10
- * .byline-field-group-body — vertical stack of children
11
- */
12
-
13
- .header,
14
- :global(.byline-field-group-header) {
15
- display: flex;
16
- flex-direction: column;
17
- gap: 0.125rem;
18
- margin-bottom: var(--spacing-8);
19
- }
20
-
21
- .title,
22
- :global(.byline-field-group-title) {
23
- font-size: 1rem;
24
- font-weight: var(--font-weight-medium);
25
- }
26
-
27
- .required,
28
- :global(.byline-field-group-required) {
29
- color: var(--red-500);
30
- }
31
-
32
- .help,
33
- :global(.byline-field-group-help) {
34
- color: var(--gray-500);
35
- font-size: var(--font-size-xs);
36
- }
37
-
38
- .body,
39
- :global(.byline-field-group-body) {
40
- display: flex;
41
- flex-direction: column;
42
- gap: var(--spacing-8);
43
- }
@@ -1,84 +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 { useMemo } from 'react'
10
-
11
- import type { Field, GroupField as GroupFieldType } from '@byline/core'
12
- import cx from 'classnames'
13
-
14
- import { placeholderForField } from '../../fields/field-helpers'
15
- import { FieldRenderer } from '../../fields/field-renderer'
16
- import { useFieldError } from '../../forms/form-context'
17
- import { ErrorText } from '../../uikit.js'
18
- import styles from './group-field.module.css'
19
-
20
- // ---------------------------------------------------------------------------
21
- // GroupField — renders a fixed-order group of child fields wrapped in a
22
- // single div. No drag-and-drop. No add/remove.
23
- // The outer div carries the field type ('group') and field name as classes
24
- // so consumers can target individual groups via CSS.
25
- //
26
- // Stable override handles: `.byline-field-group`, `.byline-field-group-header`,
27
- // `.byline-field-group-title`, `.byline-field-group-help`,
28
- // `.byline-field-group-body`.
29
- // ---------------------------------------------------------------------------
30
-
31
- interface GroupFieldProps {
32
- field: GroupFieldType
33
- defaultValue: any
34
- path: string
35
- }
36
-
37
- export const GroupField = ({ field, defaultValue, path }: GroupFieldProps) => {
38
- const fieldError = useFieldError(field.name)
39
- // Default value for a group field is a plain object: { rating: 5, comment: '...' }
40
- // Normalize to a plain object if not already one.
41
- const groupData = useMemo(() => {
42
- if (defaultValue && typeof defaultValue === 'object' && !Array.isArray(defaultValue)) {
43
- return defaultValue
44
- }
45
- // Fallback: build a placeholder object from child field definitions
46
- const placeholder: Record<string, any> = {}
47
- for (const childField of field.fields as Field[]) {
48
- placeholder[childField.name] = placeholderForField(childField)
49
- }
50
- return placeholder
51
- }, [defaultValue, field.fields])
52
-
53
- return (
54
- <div className={`byline-field-group ${field.name}`}>
55
- {field.label && (
56
- <div className={cx('byline-field-group-header', styles.header)}>
57
- <h3 className={cx('byline-field-group-title', styles.title)}>
58
- {field.label}{' '}
59
- {!field.optional && (
60
- <span className={cx('byline-field-group-required', styles.required)}>*</span>
61
- )}
62
- </h3>
63
- {field.helpText && (
64
- <p className={cx('byline-field-group-help', styles.help)}>{field.helpText}</p>
65
- )}
66
- </div>
67
- )}
68
- <div className={cx('byline-field-group-body', styles.body)}>
69
- {(field.fields as Field[]).map((innerField) => {
70
- return (
71
- <FieldRenderer
72
- key={innerField.name}
73
- field={innerField}
74
- defaultValue={groupData[innerField.name]}
75
- basePath={path}
76
- disableSorting={true}
77
- />
78
- )
79
- })}
80
- </div>
81
- {fieldError && <ErrorText id={`${field.name}-error`} text={fieldError} />}
82
- </div>
83
- )
84
- }