@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,270 +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
- * ImageUploadField
11
- *
12
- * A drag-and-drop / click-to-browse file picker that prepares an image for
13
- * upload. The actual upload is deferred until form submission — this component
14
- * stores the file in the form context's pending uploads and emits a placeholder
15
- * StoredFileValue with a blob URL for immediate preview.
16
- *
17
- * Prototype: no chunk upload, no resumable uploads, single file only.
18
- */
19
-
20
- import type { ChangeEvent, DragEvent } from 'react'
21
- import { useCallback, useRef, useState } from 'react'
22
-
23
- import {
24
- createPendingStoredFileValue,
25
- type ImageField 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 './image-upload-field.module.css'
33
-
34
- // ---------------------------------------------------------------------------
35
- // Types
36
- // ---------------------------------------------------------------------------
37
-
38
- interface ImageUploadFieldProps {
39
- field: FieldType
40
- /** Collection path used to build the upload URL (e.g. `'media'`). */
41
- collectionPath: string
42
- /** Field path in the form (e.g. `'image'` or `'content.0.image'`). */
43
- fieldPath: string
44
- /** Called with the PendingStoredFileValue for immediate preview. */
45
- onUploaded: (value: StoredFileValue | PendingStoredFileValue) => void
46
- /** Optional accepted-file MIME types string for the native file input. */
47
- accept?: string
48
- }
49
-
50
- type SelectionStatus = 'idle' | 'processing' | 'error'
51
-
52
- // ---------------------------------------------------------------------------
53
- // Component
54
- // ---------------------------------------------------------------------------
55
-
56
- export const ImageUploadField = ({
57
- field: _field,
58
- collectionPath,
59
- fieldPath,
60
- onUploaded,
61
- accept = 'image/*',
62
- }: ImageUploadFieldProps) => {
63
- const inputRef = useRef<HTMLInputElement>(null)
64
- const [status, setStatus] = useState<SelectionStatus>('idle')
65
- const [errorMessage, setErrorMessage] = useState<string | null>(null)
66
- const [isDragOver, setIsDragOver] = useState(false)
67
- const { addPendingUpload } = useFormContext()
68
-
69
- // -------------------------------------------------------------------------
70
- // Core file selection logic (deferred upload)
71
- // -------------------------------------------------------------------------
72
-
73
- const handleFileSelected = useCallback(
74
- (file: File) => {
75
- setStatus('processing')
76
- setErrorMessage(null)
77
-
78
- // Basic client-side validation
79
- if (!file.type.startsWith('image/')) {
80
- setStatus('error')
81
- setErrorMessage('Please select an image file.')
82
- return
83
- }
84
-
85
- // Create a blob URL for immediate preview
86
- const previewUrl = URL.createObjectURL(file)
87
-
88
- // Extract image dimensions for the pending value
89
- const img = new Image()
90
- img.onload = () => {
91
- // SVGs without explicit width/height attrs (viewBox-only) report naturalWidth/Height = 0.
92
- // Skip dimensions when zero so they are stored as null (scalable, no fixed size).
93
- const w = img.naturalWidth
94
- const h = img.naturalHeight
95
- const dimensions = w > 0 && h > 0 ? { width: w, height: h } : undefined
96
-
97
- // Create the pending stored file value
98
- const pendingValue = createPendingStoredFileValue(file, previewUrl, dimensions)
99
-
100
- // Register the pending upload in form context
101
- addPendingUpload(fieldPath, {
102
- file,
103
- previewUrl,
104
- collectionPath,
105
- })
106
-
107
- setStatus('idle')
108
- onUploaded(pendingValue)
109
- }
110
-
111
- img.onerror = () => {
112
- URL.revokeObjectURL(previewUrl)
113
- setStatus('error')
114
- setErrorMessage('Could not read image. Please try a different file.')
115
- }
116
-
117
- img.src = previewUrl
118
- },
119
- [collectionPath, fieldPath, addPendingUpload, onUploaded]
120
- )
121
-
122
- // -------------------------------------------------------------------------
123
- // File input
124
- // -------------------------------------------------------------------------
125
-
126
- const handleFileChange = useCallback(
127
- (e: ChangeEvent<HTMLInputElement>) => {
128
- const file = e.target.files?.[0]
129
- if (file) handleFileSelected(file)
130
- // Reset so re-selecting the same file fires the event again.
131
- e.target.value = ''
132
- },
133
- [handleFileSelected]
134
- )
135
-
136
- const handleBrowseClick = useCallback(() => {
137
- inputRef.current?.click()
138
- }, [])
139
-
140
- // -------------------------------------------------------------------------
141
- // Drag and drop
142
- // -------------------------------------------------------------------------
143
-
144
- const handleDragOver = useCallback((e: DragEvent<HTMLDivElement>) => {
145
- e.preventDefault()
146
- setIsDragOver(true)
147
- }, [])
148
-
149
- const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
150
- e.preventDefault()
151
- setIsDragOver(false)
152
- }, [])
153
-
154
- const handleDrop = useCallback(
155
- (e: DragEvent<HTMLDivElement>) => {
156
- e.preventDefault()
157
- setIsDragOver(false)
158
- const file = e.dataTransfer.files?.[0]
159
- if (file) handleFileSelected(file)
160
- },
161
- [handleFileSelected]
162
- )
163
-
164
- // -------------------------------------------------------------------------
165
- // Render
166
- // -------------------------------------------------------------------------
167
-
168
- const isProcessing = status === 'processing'
169
-
170
- return (
171
- <div className={cx('byline-field-image-upload', styles.root)}>
172
- {/* Hidden native file input */}
173
- <input
174
- ref={inputRef}
175
- type="file"
176
- accept={accept}
177
- className={cx('byline-field-image-upload-input', styles.input)}
178
- onChange={handleFileChange}
179
- disabled={isProcessing}
180
- aria-hidden="true"
181
- tabIndex={-1}
182
- />
183
-
184
- {/* Drop zone */}
185
- <div
186
- role="button"
187
- tabIndex={0}
188
- aria-label="Upload image — drag and drop or click to browse"
189
- onDragOver={handleDragOver}
190
- onDragLeave={handleDragLeave}
191
- onDrop={handleDrop}
192
- onClick={handleBrowseClick}
193
- onKeyDown={(e) => {
194
- if (e.key === 'Enter' || e.key === ' ') {
195
- e.preventDefault()
196
- handleBrowseClick()
197
- }
198
- }}
199
- className={cx(
200
- 'byline-field-image-upload-zone',
201
- styles.zone,
202
- isDragOver &&
203
- !isProcessing && ['byline-field-image-upload-zone-active', styles['zone-active']],
204
- isProcessing && ['byline-field-image-upload-zone-busy', styles['zone-busy']]
205
- )}
206
- >
207
- {isProcessing ? (
208
- <>
209
- {/* Spinner */}
210
- <svg
211
- className={cx('byline-field-image-upload-spinner', styles.spinner)}
212
- xmlns="http://www.w3.org/2000/svg"
213
- fill="none"
214
- viewBox="0 0 24 24"
215
- aria-hidden="true"
216
- >
217
- <circle
218
- style={{ opacity: 0.25 }}
219
- cx="12"
220
- cy="12"
221
- r="10"
222
- stroke="currentColor"
223
- strokeWidth="4"
224
- />
225
- <path
226
- style={{ opacity: 0.75 }}
227
- fill="currentColor"
228
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
229
- />
230
- </svg>
231
- <span className={cx('byline-field-image-upload-label', styles.label)}>Processing…</span>
232
- </>
233
- ) : (
234
- <>
235
- {/* Upload icon */}
236
- <svg
237
- xmlns="http://www.w3.org/2000/svg"
238
- className={cx('byline-field-image-upload-icon', styles.icon)}
239
- fill="none"
240
- viewBox="0 0 24 24"
241
- stroke="currentColor"
242
- strokeWidth={1.5}
243
- aria-hidden="true"
244
- >
245
- <path
246
- strokeLinecap="round"
247
- strokeLinejoin="round"
248
- 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"
249
- />
250
- </svg>
251
- <span className={cx('byline-field-image-upload-label', styles.label)}>
252
- Drop image here or{' '}
253
- <span className={cx('byline-field-image-upload-action', styles.action)}>browse</span>
254
- </span>
255
- <span className={cx('byline-field-image-upload-hint', styles.hint)}>
256
- JPEG, PNG, WebP, GIF, SVG, AVIF
257
- </span>
258
- </>
259
- )}
260
- </div>
261
-
262
- {/* Error message */}
263
- {status === 'error' && errorMessage && (
264
- <p className={cx('byline-field-image-upload-error', styles.error)} role="alert">
265
- {errorMessage}
266
- </p>
267
- )}
268
- </div>
269
- )
270
- }
@@ -1,88 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useState } from 'react'
4
-
5
- // undefined will use the user's locale
6
- const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
7
- year: 'numeric',
8
- month: 'short', // <- short month text (locale-aware)
9
- day: '2-digit',
10
- hour: 'numeric',
11
- minute: 'numeric',
12
- })
13
-
14
- const dateFormatter = new Intl.DateTimeFormat(undefined, {
15
- year: 'numeric',
16
- month: 'short', // <- short month text (locale-aware)
17
- day: '2-digit',
18
- })
19
-
20
- interface LocalDateTimeProps {
21
- value: string | Date | null | undefined
22
- mode?: 'datetime' | 'date'
23
- className?: string
24
- fallback?: string
25
- }
26
-
27
- /**
28
- * Client-side date/time formatter that displays dates in the user's local timezone.
29
- *
30
- * This component avoids hydration mismatches by:
31
- * 1. Rendering a placeholder on the server/initial render
32
- * 2. Updating to the correctly formatted date after hydration
33
- *
34
- * Use this instead of formatDateTime when you need to display dates
35
- * in the user's local timezone rather than UTC.
36
- */
37
- export function LocalDateTime({
38
- value,
39
- mode = 'datetime',
40
- className,
41
- fallback = '—',
42
- }: LocalDateTimeProps): React.JSX.Element {
43
- const [formattedDate, setFormattedDate] = useState<string | null>(null)
44
-
45
- useEffect(() => {
46
- if (value == null) {
47
- setFormattedDate(null)
48
- return
49
- }
50
-
51
- const date = typeof value === 'string' ? Date.parse(value) : value.getTime()
52
- if (Number.isNaN(date)) {
53
- setFormattedDate('Error')
54
- return
55
- }
56
-
57
- const formatter = mode === 'datetime' ? dateTimeFormatter : dateFormatter
58
- setFormattedDate(formatter.format(date))
59
- }, [value, mode])
60
-
61
- // Show fallback during SSR and before useEffect runs
62
- if (formattedDate === null) {
63
- return <span className={className}>{value ? '...' : fallback}</span>
64
- }
65
-
66
- return <span className={className}>{formattedDate}</span>
67
- }
68
-
69
- /**
70
- * Formats a date string to the user's local timezone.
71
- *
72
- * WARNING: Only use this in client components where hydration
73
- * mismatches are acceptable or managed. For most cases, prefer
74
- * the <LocalDateTime /> component.
75
- */
76
- export function formatLocalDateTime(value: string | Date | null | undefined): string {
77
- if (value == null) return '—'
78
- const date = typeof value === 'string' ? Date.parse(value) : value.getTime()
79
- if (Number.isNaN(date)) return 'Error'
80
- return dateTimeFormatter.format(date)
81
- }
82
-
83
- export function formatLocalDate(value: string | Date | null | undefined): string {
84
- if (value == null) return '—'
85
- const date = typeof value === 'string' ? Date.parse(value) : value.getTime()
86
- if (Number.isNaN(date)) return 'Error'
87
- return dateFormatter.format(date)
88
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * LocaleBadge — small inline badge shown next to a localised field label.
3
- *
4
- * Override handle: `.byline-locale-badge`
5
- */
6
-
7
- .badge,
8
- :global(.byline-locale-badge) {
9
- display: inline-flex;
10
- align-items: center;
11
- gap: 0.125rem;
12
- margin-left: 0.375rem;
13
- padding: 0.125rem 0.375rem;
14
- border: var(--border-width-thin) var(--border-style-solid) var(--yellow-400);
15
- border-radius: var(--border-radius-sm);
16
- background-color: var(--yellow-100);
17
- color: var(--yellow-800);
18
- font-size: 0.6rem;
19
- font-weight: var(--font-weight-semibold);
20
- line-height: 1;
21
- letter-spacing: var(--letter-spacing-wide);
22
- text-transform: uppercase;
23
- pointer-events: none;
24
- user-select: none;
25
- vertical-align: middle;
26
- }
27
-
28
- /* ─── Dark theme variants ───────────────────────────────────── */
29
-
30
- :is([data-theme="dark"], :global(.dark)) {
31
- .badge,
32
- :global(.byline-locale-badge) {
33
- border-color: var(--yellow-700);
34
- background-color: oklch(from var(--yellow-900) l c h / 0.4);
35
- color: var(--yellow-400);
36
- }
37
- }
@@ -1,32 +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 cx from 'classnames'
10
-
11
- import styles from './locale-badge.module.css'
12
-
13
- export interface LocaleBadgeProps {
14
- locale: string
15
- }
16
-
17
- /**
18
- * Small inline badge shown next to the label of a field that has
19
- * `localized: true` in its schema definition. Indicates which locale
20
- * the editor is currently working in.
21
- *
22
- * Stable override handle: `.byline-locale-badge`.
23
- */
24
- export const LocaleBadge = ({ locale }: LocaleBadgeProps) => (
25
- <span
26
- aria-hidden="true"
27
- title={`Localised — editing ${locale.toUpperCase()} content`}
28
- className={cx('byline-locale-badge', styles.badge)}
29
- >
30
- {locale}
31
- </span>
32
- )
@@ -1,114 +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 {
10
- CounterField,
11
- DecimalField,
12
- Field,
13
- FieldComponentSlots,
14
- FloatField,
15
- IntegerField,
16
- } from '@byline/core'
17
-
18
- import { useFieldError, useFieldValue } from '../../forms/form-context'
19
- import { Input } from '../../uikit.js'
20
-
21
- export const NumericalField = ({
22
- field,
23
- value,
24
- defaultValue,
25
- onChange,
26
- id,
27
- path,
28
- components,
29
- }: {
30
- field: IntegerField | FloatField | DecimalField | CounterField
31
- value?: string | number | null
32
- defaultValue?: string | number | null
33
- onChange?: (value: string) => void
34
- id?: string
35
- path?: string
36
- /** Optional UI component slot overrides from the admin config. */
37
- components?: FieldComponentSlots
38
- }) => {
39
- const fieldPath = path ?? field.name
40
- const fieldError = useFieldError(fieldPath)
41
- const fieldValue = useFieldValue<string | number | undefined>(fieldPath)
42
- const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
43
- const htmlId = id ?? fieldPath
44
- const displayValue =
45
- incomingValue === undefined || incomingValue === null ? '' : String(incomingValue)
46
-
47
- // Custom component slots (from admin config)
48
- const slots = components
49
- const CustomLabel = slots?.Label
50
- const CustomHelpText = slots?.HelpText
51
- const CustomField = slots?.Field
52
- const BeforeField = slots?.beforeField
53
- const AfterField = slots?.afterField
54
-
55
- // Shared props available to every slot component
56
- const slotBaseProps = {
57
- field: field as Field,
58
- path: fieldPath,
59
- value: incomingValue,
60
- error: fieldError,
61
- id: htmlId,
62
- }
63
-
64
- const hasCustomLabel = !!CustomLabel
65
- const suppressInputLabel = hasCustomLabel
66
- const suppressInputHelpText = !!CustomHelpText
67
-
68
- // ── Label rendering ──────────────────────────────────────────
69
- const renderLabel = () => {
70
- if (hasCustomLabel) {
71
- return <CustomLabel {...slotBaseProps} label={field.label} required={!field.optional} />
72
- }
73
- return null
74
- }
75
-
76
- // ── Field input rendering ────────────────────────────────────
77
- const renderInput = () => {
78
- if (CustomField) {
79
- return (
80
- <CustomField
81
- {...slotBaseProps}
82
- onChange={(v: any) => onChange?.(v)}
83
- defaultValue={defaultValue}
84
- placeholder={field.placeholder}
85
- />
86
- )
87
- }
88
- return (
89
- <Input
90
- type="number"
91
- id={htmlId}
92
- name={field.name}
93
- label={suppressInputLabel ? undefined : field.label}
94
- required={!field.optional}
95
- readOnly={field.readOnly}
96
- helpText={suppressInputHelpText ? undefined : field.helpText}
97
- value={displayValue}
98
- onChange={(e) => onChange?.(e.target.value)}
99
- error={fieldError != null}
100
- errorText={fieldError}
101
- />
102
- )
103
- }
104
-
105
- return (
106
- <div className={`byline-field-${field.type} ${field.name}`}>
107
- {renderLabel()}
108
- {BeforeField && <BeforeField {...slotBaseProps} />}
109
- {renderInput()}
110
- {AfterField && <AfterField {...slotBaseProps} />}
111
- {CustomHelpText && <CustomHelpText {...slotBaseProps} helpText={field.helpText} />}
112
- </div>
113
- )
114
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * RelationDisplay — shared cell renderer for relation picker rows and the
3
- * relation-summary selected-value tile.
4
- *
5
- * Override handles:
6
- * .byline-relation-cell — single column cell
7
- * .byline-relation-cell-center — cell with align: center
8
- * .byline-relation-cell-right — cell with align: right
9
- */
10
-
11
- .cell,
12
- :global(.byline-relation-cell) {
13
- min-width: 0;
14
- color: var(--gray-700);
15
- font-size: var(--font-size-sm);
16
- overflow: hidden;
17
- text-overflow: ellipsis;
18
- white-space: nowrap;
19
- }
20
-
21
- .cell-center,
22
- :global(.byline-relation-cell-center) {
23
- text-align: center;
24
- }
25
-
26
- .cell-right,
27
- :global(.byline-relation-cell-right) {
28
- text-align: right;
29
- }
30
-
31
- :is([data-theme="dark"], :global(.dark)) {
32
- .cell,
33
- :global(.byline-relation-cell) {
34
- color: var(--gray-100);
35
- }
36
- }