@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,155 +0,0 @@
1
- /**
2
- * ImageField — image preview + metadata + remove/upload affordances.
3
- *
4
- * Override handles:
5
- * .byline-field-image — wrapper div
6
- * .byline-field-image-header — label row
7
- * .byline-field-image-remove — remove-button (icon, top-right of tile)
8
- * .byline-field-image-empty — empty-state hint text
9
- * .byline-field-image-tile — bordered preview/metadata tile
10
- * .byline-field-image-preview-wrap — preview-image positioned wrapper
11
- * .byline-field-image-preview — the <img> preview
12
- * .byline-field-image-preview-svg — preview sized for SVG aspect
13
- * .byline-field-image-pending — yellow "pending upload" pill
14
- * .byline-field-image-meta — metadata list
15
- * .byline-field-image-meta-key — metadata field-name span
16
- * .byline-field-image-meta-pending — yellow text inside pending status
17
- */
18
-
19
- .header,
20
- :global(.byline-field-image-header) {
21
- display: flex;
22
- align-items: baseline;
23
- gap: var(--spacing-8);
24
- margin-bottom: 0.25rem;
25
- }
26
-
27
- .remove,
28
- :global(.byline-field-image-remove) {
29
- position: absolute;
30
- top: var(--spacing-6);
31
- right: var(--spacing-6);
32
- z-index: 1;
33
- }
34
-
35
- :global(.byline-field-image-remove .byline-button) {
36
- color: var(--gray-900);
37
- }
38
-
39
- :global(.dark .byline-field-image-remove .byline-button),
40
- :global([data-theme="dark"] .byline-field-image-remove .byline-button) {
41
- color: var(--gray-200);
42
- }
43
-
44
- .empty,
45
- :global(.byline-field-image-empty) {
46
- color: var(--gray-500);
47
- font-size: var(--font-size-xs);
48
- font-style: italic;
49
- }
50
-
51
- .tile,
52
- :global(.byline-field-image-tile) {
53
- position: relative;
54
- display: flex;
55
- gap: var(--spacing-16);
56
- margin-top: 0.25rem;
57
- padding: var(--spacing-8);
58
- border: var(--border-width-thin) var(--border-style-solid) var(--primary-500);
59
- border-radius: var(--border-radius-md);
60
- }
61
-
62
- .preview-wrap,
63
- :global(.byline-field-image-preview-wrap) {
64
- position: relative;
65
- }
66
-
67
- .preview-button,
68
- :global(.byline-field-image-preview-button) {
69
- appearance: none;
70
- background: none;
71
- border: none;
72
- padding: 0;
73
- margin: 0;
74
- display: block;
75
- cursor: pointer;
76
- border-radius: var(--border-radius-sm);
77
- }
78
-
79
- .preview-button:focus-visible,
80
- :global(.byline-field-image-preview-button):focus-visible {
81
- outline: 2px solid var(--primary-500);
82
- outline-offset: 2px;
83
- }
84
-
85
- .preview-button img,
86
- :global(.byline-field-image-preview-button) img {
87
- transition: opacity 120ms ease;
88
- }
89
-
90
- .preview-button:hover img,
91
- :global(.byline-field-image-preview-button):hover img {
92
- opacity: 0.85;
93
- }
94
-
95
- .preview,
96
- :global(.byline-field-image-preview) {
97
- max-height: 10rem;
98
- min-height: 4rem;
99
- min-width: 4rem;
100
- border: var(--border-width-thin) var(--border-style-solid) var(--gray-600);
101
- border-radius: var(--border-radius-sm);
102
- object-fit: contain;
103
- }
104
-
105
- .preview-svg,
106
- :global(.byline-field-image-preview-svg) {
107
- width: 271px;
108
- height: 159px;
109
- max-height: none;
110
- }
111
-
112
- .pending,
113
- :global(.byline-field-image-pending) {
114
- position: absolute;
115
- top: 0.25rem;
116
- left: 0.25rem;
117
- padding: 0.125rem 0.375rem;
118
- background-color: oklch(from var(--yellow-600) l c h / 0.9);
119
- color: var(--yellow-100);
120
- font-size: 0.6rem;
121
- font-weight: var(--font-weight-medium);
122
- border-radius: var(--border-radius-sm);
123
- }
124
-
125
- .uploading,
126
- :global(.byline-field-image-uploading) {
127
- position: absolute;
128
- inset: 0;
129
- z-index: 2;
130
- display: flex;
131
- align-items: center;
132
- justify-content: center;
133
- background-color: oklch(from var(--gray-950) l c h / 0.5);
134
- border-radius: var(--border-radius-md);
135
- }
136
-
137
- .meta,
138
- :global(.byline-field-image-meta) {
139
- display: flex;
140
- flex-direction: column;
141
- gap: 0.125rem;
142
- padding-right: var(--spacing-32);
143
- color: var(--gray-200);
144
- font-size: var(--font-size-xs);
145
- }
146
-
147
- .meta-key,
148
- :global(.byline-field-image-meta-key) {
149
- font-weight: var(--font-weight-semibold);
150
- }
151
-
152
- .meta-pending,
153
- :global(.byline-field-image-meta-pending) {
154
- color: var(--yellow-400);
155
- }
@@ -1,291 +0,0 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
-
9
- import { useState } from 'react'
10
-
11
- import {
12
- type ImageField as FieldType,
13
- isPendingStoredFileValue,
14
- type StoredFileValue,
15
- } from '@byline/core'
16
- import cx from 'classnames'
17
-
18
- import { IconButton } from '../../components/button/icon-button.js'
19
- import {
20
- useFieldError,
21
- useFieldValue,
22
- useFormContext,
23
- useIsDirty,
24
- useIsFieldUploading,
25
- } from '../../forms/form-context'
26
- import { CloseIcon } from '../../icons/close-icon.js'
27
- import { ErrorText, HelpText, Label, LoaderRing } from '../../uikit.js'
28
- import { ImageLightbox } from '../../widgets/image-lightbox/image-lightbox.js'
29
- import { useFieldChangeHandler } from '../use-field-change-handler'
30
- import styles from './image-field.module.css'
31
- import { ImageUploadField } from './image-upload-field'
32
-
33
- interface ImageFieldProps {
34
- field: FieldType
35
- /** Collection path required to call the /upload endpoint. */
36
- collectionPath?: string
37
- // Stored value is currently a plain object with file/image metadata
38
- // coming from the seed data / storage layer.
39
- value?: StoredFileValue | null
40
- defaultValue?: StoredFileValue | null
41
- onChange?: (value: StoredFileValue | null) => void
42
- path?: string
43
- }
44
-
45
- export const ImageField = ({
46
- field,
47
- collectionPath,
48
- value,
49
- defaultValue,
50
- onChange: _onChange,
51
- path,
52
- }: ImageFieldProps) => {
53
- const fieldPath = path ?? field.name
54
- const fieldError = useFieldError(fieldPath)
55
- const isDirty = useIsDirty(fieldPath)
56
- const fieldValue = useFieldValue<StoredFileValue | null | undefined>(fieldPath)
57
- const isUploading = useIsFieldUploading(fieldPath)
58
- const { removePendingUpload } = useFormContext()
59
-
60
- // Re-use the standard field change handler so patches are emitted correctly.
61
- const handleChange = useFieldChangeHandler(field, fieldPath)
62
-
63
- // When the field has been explicitly set (dirty), use the field value from
64
- // form state — even if it's null (user clicked Remove). Only fall back to
65
- // the prop / defaultValue when the field hasn't been touched yet.
66
- const incomingValue = isDirty
67
- ? (fieldValue ?? null)
68
- : (value ?? fieldValue ?? defaultValue ?? null)
69
-
70
- // Check if this is a pending upload (selected but not yet uploaded)
71
- const isPending = isPendingStoredFileValue(incomingValue)
72
-
73
- // Old placeholder check for backwards compatibility
74
- const isOldPlaceholder = (v: unknown): boolean => {
75
- if (!v || typeof v !== 'object') return false
76
- const maybe = v as Partial<StoredFileValue>
77
- return maybe.storageProvider === 'placeholder' && maybe.storagePath === 'pending'
78
- }
79
-
80
- // Show upload widget only if no value or old placeholder
81
- const showUploadWidget = incomingValue == null || isOldPlaceholder(incomingValue)
82
-
83
- // Prefer the generated thumbnail variant for the preview tile. SVGs and
84
- // other bypass types have no variants — fall back to the original.
85
- const thumbVariant =
86
- incomingValue && !isPendingStoredFileValue(incomingValue)
87
- ? incomingValue.variants?.find((v) => v.name === 'thumbnail')
88
- : undefined
89
- const previewUrl = thumbVariant?.storageUrl ?? incomingValue?.storageUrl
90
-
91
- // Handle remove, including cleanup of pending uploads
92
- const handleRemove = () => {
93
- if (isPending) {
94
- removePendingUpload(fieldPath)
95
- }
96
- handleChange(null)
97
- }
98
-
99
- // Lightbox state — only enabled for stored (non-pending) images that have a
100
- // resolvable original storageUrl.
101
- const [lightboxOpen, setLightboxOpen] = useState(false)
102
- const canOpenLightbox = !isPending && !!incomingValue?.storageUrl
103
-
104
- const htmlId = fieldPath
105
-
106
- return (
107
- <div className={`byline-field-image ${field.name}`}>
108
- <div className={cx('byline-field-image-header', styles.header)}>
109
- <Label
110
- id={htmlId}
111
- htmlFor={htmlId}
112
- label={field.label ?? field.name}
113
- required={!field.optional}
114
- />
115
- </div>
116
-
117
- {showUploadWidget ? (
118
- collectionPath ? (
119
- <ImageUploadField
120
- field={field}
121
- collectionPath={collectionPath}
122
- fieldPath={fieldPath}
123
- onUploaded={(uploaded) => {
124
- handleChange(uploaded)
125
- }}
126
- />
127
- ) : (
128
- <div className={cx('byline-field-image-empty', styles.empty)}>No image selected</div>
129
- )
130
- ) : (
131
- <div className={cx('byline-field-image-tile', styles.tile)}>
132
- {isUploading && (
133
- <div
134
- className={cx('byline-field-image-uploading', styles.uploading)}
135
- aria-live="polite"
136
- aria-busy="true"
137
- >
138
- <LoaderRing />
139
- </div>
140
- )}
141
- {/* Remove button — shown when an image is set (including pending) */}
142
- {collectionPath && (
143
- <div className={cx('byline-field-image-remove', styles.remove)}>
144
- <IconButton
145
- type="button"
146
- intent="noeffect"
147
- onClick={handleRemove}
148
- size="xs"
149
- disabled={isUploading}
150
- aria-label="Remove image"
151
- >
152
- <CloseIcon width="15px" height="15px" />
153
- </IconButton>
154
- </div>
155
- )}
156
- {/* Preview */}
157
- {previewUrl && (
158
- <div className={cx('byline-field-image-preview-wrap', styles['preview-wrap'])}>
159
- {canOpenLightbox ? (
160
- <button
161
- type="button"
162
- onClick={() => setLightboxOpen(true)}
163
- aria-label="Open full-size preview"
164
- className={cx('byline-field-image-preview-button', styles['preview-button'])}
165
- >
166
- <img
167
- src={previewUrl}
168
- alt={incomingValue.originalFilename ?? incomingValue.filename}
169
- className={cx(
170
- 'byline-field-image-preview',
171
- styles.preview,
172
- incomingValue.mimeType === 'image/svg+xml' && [
173
- 'byline-field-image-preview-svg',
174
- styles['preview-svg'],
175
- ]
176
- )}
177
- />
178
- </button>
179
- ) : (
180
- <img
181
- src={previewUrl}
182
- alt={incomingValue.originalFilename ?? incomingValue.filename}
183
- className={cx(
184
- 'byline-field-image-preview',
185
- styles.preview,
186
- incomingValue.mimeType === 'image/svg+xml' && [
187
- 'byline-field-image-preview-svg',
188
- styles['preview-svg'],
189
- ]
190
- )}
191
- />
192
- )}
193
- {/* Pending upload badge */}
194
- {isPending && (
195
- <div className={cx('byline-field-image-pending', styles.pending)}>
196
- Pending upload
197
- </div>
198
- )}
199
- </div>
200
- )}
201
- {/* Metadata */}
202
- <div className={cx('byline-field-image-meta', styles.meta)}>
203
- <div>
204
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
205
- Filename:
206
- </span>{' '}
207
- {incomingValue?.filename}
208
- </div>
209
- <div>
210
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
211
- Original:
212
- </span>{' '}
213
- {incomingValue?.originalFilename}
214
- </div>
215
- <div>
216
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>Type:</span>{' '}
217
- {incomingValue?.mimeType}
218
- </div>
219
- <div>
220
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>Size:</span>{' '}
221
- {incomingValue?.fileSize}
222
- </div>
223
- {isPending ? (
224
- <div>
225
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
226
- Status:
227
- </span>{' '}
228
- <span className={cx('byline-field-image-meta-pending', styles['meta-pending'])}>
229
- Will upload on save
230
- </span>
231
- </div>
232
- ) : (
233
- <>
234
- <div>
235
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
236
- Storage:
237
- </span>{' '}
238
- {incomingValue?.storageProvider}
239
- </div>
240
- {incomingValue?.imageWidth != null && (
241
- <div>
242
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
243
- Dimensions:
244
- </span>{' '}
245
- {incomingValue.imageWidth}
246
- {incomingValue.imageHeight != null ? `×${incomingValue.imageHeight}` : ''}
247
- </div>
248
- )}
249
- {incomingValue?.imageFormat != null && (
250
- <div>
251
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
252
- Format:
253
- </span>{' '}
254
- {incomingValue.imageFormat}
255
- </div>
256
- )}
257
- <div>
258
- <span className={cx('byline-field-image-meta-key', styles['meta-key'])}>
259
- Thumbnail:
260
- </span>{' '}
261
- {thumbVariant ? 'Generated' : 'Pending'}
262
- </div>
263
- </>
264
- )}
265
- </div>
266
- </div>
267
- )}
268
-
269
- {field.helpText && <HelpText text={field.helpText} />}
270
-
271
- {fieldError && <ErrorText id={`${field.name}-error`} text={fieldError} />}
272
-
273
- {canOpenLightbox && incomingValue?.storageUrl && (
274
- <ImageLightbox
275
- isOpen={lightboxOpen}
276
- onDismiss={() => setLightboxOpen(false)}
277
- src={incomingValue.storageUrl}
278
- alt={incomingValue.originalFilename ?? incomingValue.filename}
279
- downloadFilename={incomingValue.originalFilename ?? incomingValue.filename}
280
- title={incomingValue.originalFilename ?? incomingValue.filename}
281
- meta={{
282
- width: incomingValue.imageWidth,
283
- height: incomingValue.imageHeight,
284
- fileSize: incomingValue.fileSize,
285
- mimeType: incomingValue.mimeType,
286
- }}
287
- />
288
- )}
289
- </div>
290
- )
291
- }
@@ -1,123 +0,0 @@
1
- /**
2
- * ImageUploadField — drag-and-drop image picker that registers a deferred
3
- * upload in form context.
4
- *
5
- * Override handles:
6
- * .byline-field-image-upload — root wrapper
7
- * .byline-field-image-upload-input — visually-hidden file input
8
- * .byline-field-image-upload-zone — clickable / drop target
9
- * .byline-field-image-upload-zone-active — drag-hovered state
10
- * .byline-field-image-upload-zone-busy — processing state
11
- * .byline-field-image-upload-spinner — animated spinner svg
12
- * .byline-field-image-upload-icon — upload icon svg
13
- * .byline-field-image-upload-label — primary text inside the zone
14
- * .byline-field-image-upload-action — "browse" link inside the label
15
- * .byline-field-image-upload-hint — secondary "JPEG, PNG …" hint
16
- * .byline-field-image-upload-error — error message paragraph
17
- */
18
-
19
- .root,
20
- :global(.byline-field-image-upload) {
21
- margin-top: 0.25rem;
22
- }
23
-
24
- .input,
25
- :global(.byline-field-image-upload-input) {
26
- position: absolute;
27
- width: 1px;
28
- height: 1px;
29
- padding: 0;
30
- margin: -1px;
31
- overflow: hidden;
32
- clip: rect(0, 0, 0, 0);
33
- white-space: nowrap;
34
- border: 0;
35
- }
36
-
37
- .zone,
38
- :global(.byline-field-image-upload-zone) {
39
- display: flex;
40
- flex-direction: column;
41
- align-items: center;
42
- justify-content: center;
43
- gap: var(--spacing-8);
44
- padding: 1.5rem 1rem;
45
- border: 2px dashed var(--gray-600);
46
- border-radius: var(--border-radius-lg);
47
- color: var(--gray-400);
48
- text-align: center;
49
- cursor: pointer;
50
- user-select: none;
51
- transition:
52
- color 150ms ease,
53
- background-color 150ms ease,
54
- border-color 150ms ease;
55
- }
56
-
57
- .zone:hover,
58
- :global(.byline-field-image-upload-zone):hover {
59
- border-color: var(--primary-500);
60
- background-color: oklch(from var(--primary-900) l c h / 0.1);
61
- }
62
-
63
- .zone-active,
64
- :global(.byline-field-image-upload-zone-active) {
65
- border-color: var(--primary-400);
66
- background-color: oklch(from var(--primary-900) l c h / 0.2);
67
- color: var(--primary-300);
68
- }
69
-
70
- .zone-busy,
71
- :global(.byline-field-image-upload-zone-busy) {
72
- border-color: var(--gray-700);
73
- background-color: oklch(from var(--canvas-800) l c h / 0.5);
74
- color: var(--gray-600);
75
- cursor: not-allowed;
76
- }
77
-
78
- .spinner,
79
- :global(.byline-field-image-upload-spinner) {
80
- width: 1.5rem;
81
- height: 1.5rem;
82
- color: var(--primary-400);
83
- animation: byline-image-upload-spin 1s linear infinite;
84
- }
85
-
86
- @keyframes byline-image-upload-spin {
87
- to {
88
- transform: rotate(360deg);
89
- }
90
- }
91
-
92
- .icon,
93
- :global(.byline-field-image-upload-icon) {
94
- width: 1.75rem;
95
- height: 1.75rem;
96
- opacity: 0.6;
97
- }
98
-
99
- .label,
100
- :global(.byline-field-image-upload-label) {
101
- font-size: var(--font-size-xs);
102
- font-weight: var(--font-weight-medium);
103
- }
104
-
105
- .action,
106
- :global(.byline-field-image-upload-action) {
107
- color: var(--primary-400);
108
- text-decoration: underline;
109
- text-underline-offset: 2px;
110
- }
111
-
112
- .hint,
113
- :global(.byline-field-image-upload-hint) {
114
- color: var(--gray-500);
115
- font-size: 0.65rem;
116
- }
117
-
118
- .error,
119
- :global(.byline-field-image-upload-error) {
120
- margin-top: 0.375rem;
121
- color: var(--red-400);
122
- font-size: var(--font-size-xs);
123
- }