@byline/ui 2.5.2 → 2.6.0

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