@byline/admin 2.5.1 → 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 (260) hide show
  1. package/dist/fields/array/array-field.d.ts +14 -0
  2. package/dist/fields/array/array-field.js +177 -0
  3. package/dist/fields/array/array-field.module.js +11 -0
  4. package/dist/fields/array/array-field_module.css +32 -0
  5. package/dist/fields/blocks/blocks-field.d.ts +13 -0
  6. package/dist/fields/blocks/blocks-field.js +245 -0
  7. package/dist/fields/blocks/blocks-field.module.js +26 -0
  8. package/dist/fields/blocks/blocks-field_module.css +107 -0
  9. package/dist/fields/checkbox/checkbox-field.d.ts +16 -0
  10. package/dist/fields/checkbox/checkbox-field.js +28 -0
  11. package/dist/fields/checkbox/checkbox-field.module.js +6 -0
  12. package/dist/fields/checkbox/checkbox-field_module.css +4 -0
  13. package/dist/fields/column-formatter.d.ts +20 -0
  14. package/dist/fields/column-formatter.js +15 -0
  15. package/dist/fields/date-time-formatter.d.ts +16 -0
  16. package/dist/fields/date-time-formatter.js +8 -0
  17. package/dist/fields/datetime/datetime-field.d.ts +16 -0
  18. package/dist/fields/datetime/datetime-field.js +37 -0
  19. package/dist/fields/datetime/datetime-field.module.js +5 -0
  20. package/dist/fields/datetime/datetime-field_module.css +4 -0
  21. package/dist/fields/draggable-context-menu.d.ts +6 -0
  22. package/dist/fields/draggable-context-menu.js +85 -0
  23. package/dist/fields/draggable-context-menu.module.js +15 -0
  24. package/dist/fields/draggable-context-menu_module.css +91 -0
  25. package/dist/fields/field-helpers.d.ts +26 -0
  26. package/dist/fields/field-helpers.js +50 -0
  27. package/dist/fields/field-renderer.d.ts +37 -0
  28. package/dist/fields/field-renderer.js +206 -0
  29. package/dist/fields/field-renderer.module.js +8 -0
  30. package/dist/fields/field-renderer_module.css +11 -0
  31. package/dist/fields/field-services-context.d.ts +16 -0
  32. package/dist/fields/field-services-context.js +13 -0
  33. package/dist/fields/field-services-types.d.ts +63 -0
  34. package/dist/fields/field-services-types.js +1 -0
  35. package/dist/fields/file/file-field.d.ts +19 -0
  36. package/dist/fields/file/file-field.js +225 -0
  37. package/dist/fields/file/file-field.module.js +18 -0
  38. package/dist/fields/file/file-field_module.css +131 -0
  39. package/dist/fields/file/file-upload-field.d.ts +21 -0
  40. package/dist/fields/file/file-upload-field.js +130 -0
  41. package/dist/fields/file/file-upload-field.module.js +15 -0
  42. package/dist/fields/file/file-upload-field_module.css +74 -0
  43. package/dist/fields/group/group-field.d.ts +15 -0
  44. package/dist/fields/group/group-field.js +59 -0
  45. package/dist/fields/group/group-field.module.js +9 -0
  46. package/dist/fields/group/group-field_module.css +27 -0
  47. package/dist/fields/image/image-field.d.ts +19 -0
  48. package/dist/fields/image/image-field.js +241 -0
  49. package/dist/fields/image/image-field.module.js +22 -0
  50. package/dist/fields/image/image-field_module.css +121 -0
  51. package/dist/fields/image/image-upload-field.d.ts +21 -0
  52. package/dist/fields/image/image-upload-field.js +190 -0
  53. package/dist/fields/image/image-upload-field.module.js +19 -0
  54. package/dist/fields/image/image-upload-field_module.css +92 -0
  55. package/dist/fields/local-date-time.d.ts +27 -0
  56. package/dist/fields/local-date-time.js +49 -0
  57. package/dist/fields/locale-badge.d.ts +18 -0
  58. package/dist/fields/locale-badge.js +10 -0
  59. package/dist/fields/locale-badge.module.js +5 -0
  60. package/dist/fields/locale-badge_module.css +27 -0
  61. package/dist/fields/numerical/numerical-field.d.ts +18 -0
  62. package/dist/fields/numerical/numerical-field.js +74 -0
  63. package/dist/fields/relation/relation-display.d.ts +40 -0
  64. package/dist/fields/relation/relation-display.js +58 -0
  65. package/dist/fields/relation/relation-display.module.js +9 -0
  66. package/dist/fields/relation/relation-display_module.css +21 -0
  67. package/dist/fields/relation/relation-field.d.ts +18 -0
  68. package/dist/fields/relation/relation-field.js +138 -0
  69. package/dist/fields/relation/relation-field.module.js +13 -0
  70. package/dist/fields/relation/relation-field_module.css +62 -0
  71. package/dist/fields/relation/relation-picker.d.ts +49 -0
  72. package/dist/fields/relation/relation-picker.js +236 -0
  73. package/dist/fields/relation/relation-picker.module.js +26 -0
  74. package/dist/fields/relation/relation-picker_module.css +124 -0
  75. package/dist/fields/relation/relation-summary.d.ts +31 -0
  76. package/dist/fields/relation/relation-summary.js +50 -0
  77. package/dist/fields/relation/relation-summary.module.js +11 -0
  78. package/dist/fields/relation/relation-summary_module.css +37 -0
  79. package/dist/fields/select/select-field.d.ts +16 -0
  80. package/dist/fields/select/select-field.js +50 -0
  81. package/dist/fields/select/select-field.module.js +5 -0
  82. package/dist/fields/select/select-field_module.css +4 -0
  83. package/dist/fields/sortable-item.d.ts +15 -0
  84. package/dist/fields/sortable-item.js +81 -0
  85. package/dist/fields/sortable-item.module.js +22 -0
  86. package/dist/fields/sortable-item_module.css +124 -0
  87. package/dist/fields/text/text-field.d.ts +20 -0
  88. package/dist/fields/text/text-field.js +104 -0
  89. package/dist/fields/text/text-field.module.js +6 -0
  90. package/dist/fields/text/text-field_module.css +5 -0
  91. package/dist/fields/text-area/text-area-field.d.ts +20 -0
  92. package/dist/fields/text-area/text-area-field.js +105 -0
  93. package/dist/fields/text-area/text-area-field.module.js +6 -0
  94. package/dist/fields/text-area/text-area-field_module.css +5 -0
  95. package/dist/fields/use-field-change-handler.d.ts +23 -0
  96. package/dist/fields/use-field-change-handler.js +52 -0
  97. package/dist/forms/document-actions.d.ts +48 -0
  98. package/dist/forms/document-actions.js +475 -0
  99. package/dist/forms/document-actions.module.js +34 -0
  100. package/dist/forms/document-actions_module.css +118 -0
  101. package/dist/forms/form-context.d.ts +89 -0
  102. package/dist/forms/form-context.js +466 -0
  103. package/dist/forms/form-renderer.d.ts +98 -0
  104. package/dist/forms/form-renderer.js +597 -0
  105. package/dist/forms/form-renderer.module.js +46 -0
  106. package/dist/forms/form-renderer_module.css +245 -0
  107. package/dist/forms/navigation-guard.d.ts +54 -0
  108. package/dist/forms/navigation-guard.js +22 -0
  109. package/dist/forms/path-widget.d.ts +36 -0
  110. package/dist/forms/path-widget.js +116 -0
  111. package/dist/forms/path-widget.module.js +8 -0
  112. package/dist/forms/path-widget_module.css +29 -0
  113. package/dist/forms/upload-executor.d.ts +57 -0
  114. package/dist/forms/upload-executor.js +94 -0
  115. package/dist/lib/translate-validation-error.d.ts +36 -0
  116. package/dist/lib/translate-validation-error.js +11 -0
  117. package/dist/modules/admin-account/commands.d.ts +2 -1
  118. package/dist/modules/admin-account/commands.js +13 -2
  119. package/dist/modules/admin-account/components/change-password.js +45 -36
  120. package/dist/modules/admin-account/components/container.js +185 -134
  121. package/dist/modules/admin-account/components/preferences.d.ts +8 -0
  122. package/dist/modules/admin-account/components/preferences.js +152 -0
  123. package/dist/modules/admin-account/components/preferences.module.js +11 -0
  124. package/dist/modules/admin-account/components/preferences_module.css +41 -0
  125. package/dist/modules/admin-account/components/update.js +50 -31
  126. package/dist/modules/admin-account/index.d.ts +3 -3
  127. package/dist/modules/admin-account/index.js +2 -2
  128. package/dist/modules/admin-account/schemas.d.ts +4 -0
  129. package/dist/modules/admin-account/schemas.js +4 -1
  130. package/dist/modules/admin-account/service.d.ts +1 -0
  131. package/dist/modules/admin-account/service.js +8 -0
  132. package/dist/modules/admin-permissions/components/inspector.js +31 -41
  133. package/dist/modules/admin-roles/components/create.js +43 -26
  134. package/dist/modules/admin-roles/components/permissions.js +26 -35
  135. package/dist/modules/admin-roles/components/update.js +26 -16
  136. package/dist/modules/admin-users/components/create.js +60 -40
  137. package/dist/modules/admin-users/components/roles.js +9 -15
  138. package/dist/modules/admin-users/components/set-password.js +30 -31
  139. package/dist/modules/admin-users/components/update.js +58 -39
  140. package/dist/modules/admin-users/dto.js +1 -0
  141. package/dist/modules/admin-users/repository.d.ts +17 -0
  142. package/dist/modules/admin-users/schemas.d.ts +4 -0
  143. package/dist/modules/admin-users/schemas.js +6 -2
  144. package/dist/modules/auth/components/sign-in-form.js +10 -8
  145. package/dist/presentation/group.d.ts +27 -0
  146. package/dist/presentation/group.js +14 -0
  147. package/dist/presentation/group.module.js +6 -0
  148. package/dist/presentation/group_module.css +19 -0
  149. package/dist/presentation/row.d.ts +25 -0
  150. package/dist/presentation/row.js +8 -0
  151. package/dist/presentation/row.module.js +5 -0
  152. package/dist/presentation/row_module.css +18 -0
  153. package/dist/presentation/tabs.d.ts +25 -0
  154. package/dist/presentation/tabs.js +39 -0
  155. package/dist/presentation/tabs.module.js +10 -0
  156. package/dist/presentation/tabs_module.css +68 -0
  157. package/dist/react.d.ts +66 -0
  158. package/dist/react.js +36 -0
  159. package/dist/services/admin-services-types.d.ts +16 -0
  160. package/dist/widgets/diff-viewer/diff-modal.d.ts +22 -0
  161. package/dist/widgets/diff-viewer/diff-modal.js +149 -0
  162. package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
  163. package/dist/widgets/diff-viewer/diff-modal_module.css +56 -0
  164. package/dist/widgets/status-badge/status-badge.d.ts +25 -0
  165. package/dist/widgets/status-badge/status-badge.js +37 -0
  166. package/dist/widgets/status-badge/status-badge.module.js +7 -0
  167. package/dist/widgets/status-badge/status-badge_module.css +20 -0
  168. package/package.json +14 -4
  169. package/src/fields/array/array-field.module.css +48 -0
  170. package/src/fields/array/array-field.tsx +267 -0
  171. package/src/fields/blocks/blocks-field.module.css +148 -0
  172. package/src/fields/blocks/blocks-field.tsx +323 -0
  173. package/src/fields/checkbox/checkbox-field.module.css +4 -0
  174. package/src/fields/checkbox/checkbox-field.tsx +54 -0
  175. package/src/fields/column-formatter.tsx +31 -0
  176. package/src/fields/date-time-formatter.tsx +22 -0
  177. package/src/fields/datetime/datetime-field.module.css +13 -0
  178. package/src/fields/datetime/datetime-field.tsx +54 -0
  179. package/src/fields/draggable-context-menu.module.css +127 -0
  180. package/src/fields/draggable-context-menu.tsx +87 -0
  181. package/src/fields/field-helpers.ts +69 -0
  182. package/src/fields/field-renderer.module.css +22 -0
  183. package/src/fields/field-renderer.tsx +288 -0
  184. package/src/fields/field-services-context.tsx +35 -0
  185. package/src/fields/field-services-types.ts +68 -0
  186. package/src/fields/file/file-field.module.css +153 -0
  187. package/src/fields/file/file-field.tsx +286 -0
  188. package/src/fields/file/file-upload-field.module.css +101 -0
  189. package/src/fields/file/file-upload-field.tsx +187 -0
  190. package/src/fields/group/group-field.module.css +43 -0
  191. package/src/fields/group/group-field.tsx +84 -0
  192. package/src/fields/image/image-field.module.css +155 -0
  193. package/src/fields/image/image-field.tsx +306 -0
  194. package/src/fields/image/image-upload-field.module.css +123 -0
  195. package/src/fields/image/image-upload-field.tsx +276 -0
  196. package/src/fields/local-date-time.tsx +88 -0
  197. package/src/fields/locale-badge.module.css +37 -0
  198. package/src/fields/locale-badge.tsx +32 -0
  199. package/src/fields/numerical/numerical-field.tsx +114 -0
  200. package/src/fields/relation/relation-display.module.css +36 -0
  201. package/src/fields/relation/relation-display.tsx +130 -0
  202. package/src/fields/relation/relation-field.module.css +83 -0
  203. package/src/fields/relation/relation-field.tsx +211 -0
  204. package/src/fields/relation/relation-picker.module.css +168 -0
  205. package/src/fields/relation/relation-picker.tsx +326 -0
  206. package/src/fields/relation/relation-summary.module.css +55 -0
  207. package/src/fields/relation/relation-summary.tsx +123 -0
  208. package/src/fields/select/select-field.module.css +13 -0
  209. package/src/fields/select/select-field.tsx +61 -0
  210. package/src/fields/sortable-item.module.css +167 -0
  211. package/src/fields/sortable-item.tsx +106 -0
  212. package/src/fields/text/text-field.module.css +13 -0
  213. package/src/fields/text/text-field.tsx +146 -0
  214. package/src/fields/text-area/text-area-field.module.css +13 -0
  215. package/src/fields/text-area/text-area-field.tsx +147 -0
  216. package/src/fields/use-field-change-handler.ts +112 -0
  217. package/src/forms/document-actions.module.css +160 -0
  218. package/src/forms/document-actions.tsx +482 -0
  219. package/src/forms/form-context.tsx +704 -0
  220. package/src/forms/form-renderer.module.css +321 -0
  221. package/src/forms/form-renderer.tsx +891 -0
  222. package/src/forms/navigation-guard.tsx +98 -0
  223. package/src/forms/path-widget.module.css +41 -0
  224. package/src/forms/path-widget.test.tsx +217 -0
  225. package/src/forms/path-widget.tsx +183 -0
  226. package/src/forms/upload-executor.ts +192 -0
  227. package/src/lib/translate-validation-error.ts +56 -0
  228. package/src/modules/admin-account/commands.ts +13 -0
  229. package/src/modules/admin-account/components/change-password.tsx +46 -31
  230. package/src/modules/admin-account/components/container.tsx +83 -38
  231. package/src/modules/admin-account/components/preferences.module.css +60 -0
  232. package/src/modules/admin-account/components/preferences.tsx +203 -0
  233. package/src/modules/admin-account/components/update.tsx +53 -27
  234. package/src/modules/admin-account/index.ts +3 -0
  235. package/src/modules/admin-account/schemas.ts +13 -0
  236. package/src/modules/admin-account/service.ts +12 -0
  237. package/src/modules/admin-permissions/components/inspector.tsx +22 -14
  238. package/src/modules/admin-roles/components/create.tsx +51 -23
  239. package/src/modules/admin-roles/components/permissions.tsx +25 -21
  240. package/src/modules/admin-roles/components/update.tsx +37 -19
  241. package/src/modules/admin-users/components/create.tsx +63 -34
  242. package/src/modules/admin-users/components/roles.tsx +9 -8
  243. package/src/modules/admin-users/components/set-password.tsx +34 -28
  244. package/src/modules/admin-users/components/update.tsx +58 -36
  245. package/src/modules/admin-users/dto.ts +1 -0
  246. package/src/modules/admin-users/repository.ts +17 -0
  247. package/src/modules/admin-users/schemas.ts +12 -0
  248. package/src/modules/auth/components/sign-in-form.tsx +14 -8
  249. package/src/presentation/group.module.css +41 -0
  250. package/src/presentation/group.tsx +40 -0
  251. package/src/presentation/row.module.css +32 -0
  252. package/src/presentation/row.tsx +33 -0
  253. package/src/presentation/tabs.module.css +107 -0
  254. package/src/presentation/tabs.tsx +84 -0
  255. package/src/react.ts +84 -0
  256. package/src/services/admin-services-types.ts +18 -0
  257. package/src/widgets/diff-viewer/diff-modal.module.css +79 -0
  258. package/src/widgets/diff-viewer/diff-modal.tsx +186 -0
  259. package/src/widgets/status-badge/status-badge.module.css +31 -0
  260. package/src/widgets/status-badge/status-badge.tsx +71 -0
@@ -0,0 +1,88 @@
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
+ }
@@ -0,0 +1,37 @@
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
+ }
@@ -0,0 +1,32 @@
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
+ )
@@ -0,0 +1,114 @@
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
+ import { Input } from '@byline/ui/react'
18
+
19
+ import { useFieldError, useFieldValue } from '../../forms/form-context'
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
+ }
@@ -0,0 +1,36 @@
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
+ }
@@ -0,0 +1,130 @@
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 { CollectionDefinition, ColumnDefinition } from '@byline/core'
10
+ import cx from 'classnames'
11
+
12
+ import styles from './relation-display.module.css'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Shared render helpers used by both the relation picker modal rows and
16
+ // the relation-summary tile on the edit form. Kept in lock-step so a row
17
+ // rendered in the picker looks identical to the selected tile.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * Render a single row cell from a `ColumnDefinition`, reading the value
22
+ * from the document's `fields` (with a fallback to top-level metadata like
23
+ * `status`, `updated_at`, `path`). Honours both formatter shapes — plain
24
+ * function → its return value, `{ component }` → the component is rendered.
25
+ */
26
+ export function PickerCell({
27
+ column,
28
+ record,
29
+ }: {
30
+ column: ColumnDefinition
31
+ record: Record<string, any>
32
+ }) {
33
+ const name = String(column.fieldName)
34
+ const value = record?.fields?.[name] ?? record?.[name]
35
+
36
+ let content: any
37
+ if (column.formatter) {
38
+ if (typeof column.formatter === 'function') {
39
+ content = column.formatter(value, record)
40
+ } else {
41
+ const Comp = column.formatter.component
42
+ content = <Comp value={value} record={record} />
43
+ }
44
+ } else if (value == null) {
45
+ content = null
46
+ } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
47
+ content = String(value)
48
+ } else {
49
+ content = null
50
+ }
51
+
52
+ return (
53
+ <div
54
+ className={cx(
55
+ 'byline-relation-cell',
56
+ styles.cell,
57
+ column.align === 'center' && ['byline-relation-cell-center', styles['cell-center']],
58
+ column.align === 'right' && ['byline-relation-cell-right', styles['cell-right']],
59
+ column.className
60
+ )}
61
+ >
62
+ {content}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ /** First top-level `text` field name on a collection, or null. */
68
+ export function resolveFallbackDisplayField(
69
+ def: CollectionDefinition | null | undefined
70
+ ): string | null {
71
+ if (!def) return null
72
+ const textField = def.fields.find((f) => f.type === 'text')
73
+ return textField?.name ?? null
74
+ }
75
+
76
+ /** Resolve the row's primary label text from the document. */
77
+ export function resolveRowLabel(
78
+ doc: Record<string, any> | null | undefined,
79
+ displayField: string | null
80
+ ): string | null {
81
+ if (!doc) return null
82
+ if (displayField) {
83
+ const v = doc.fields?.[displayField]
84
+ if (typeof v === 'string' && v.length > 0) return v
85
+ }
86
+ if (typeof doc.fields?.title === 'string' && doc.fields.title.length > 0) {
87
+ return doc.fields.title as string
88
+ }
89
+ if (typeof doc.path === 'string' && doc.path.length > 0) return doc.path as string
90
+ return null
91
+ }
92
+
93
+ /**
94
+ * Build the `fields` projection for the picker listing. Unions:
95
+ * - caller-supplied `displayField`
96
+ * - target schema's `useAsTitle` (always included, even when not visible)
97
+ * - every `fieldName` declared in the admin config's `picker` columns
98
+ * - `title` (metadata fallback for rows with no explicit picker columns)
99
+ *
100
+ * Note that the document `path` is top-level metadata on every list response
101
+ * — it is always returned by `getCollectionDocuments` regardless of the
102
+ * `fields` projection. Callers that need `{ title, path }` for downstream
103
+ * normalisation (e.g. the rich-text link plugin) can rely on:
104
+ * - `record.fields[def.useAsTitle]` for the title
105
+ * - `record.path` for the path
106
+ *
107
+ * Returns `undefined` when no target definition is available, leaving the
108
+ * listing endpoint to decide its own default projection.
109
+ */
110
+ export function resolveSelectFields(
111
+ def: CollectionDefinition | null | undefined,
112
+ displayField: string | undefined,
113
+ pickerColumns: ColumnDefinition[] | undefined
114
+ ): string[] | undefined {
115
+ if (!def) return undefined
116
+ const out = new Set<string>()
117
+ if (displayField) out.add(displayField)
118
+ if (def.useAsTitle) out.add(def.useAsTitle)
119
+ const fallback = resolveFallbackDisplayField(def)
120
+ if (fallback) out.add(fallback)
121
+ if (pickerColumns) {
122
+ for (const col of pickerColumns) {
123
+ const name = String(col.fieldName)
124
+ if (def.fields.some((f) => f.name === name)) out.add(name)
125
+ }
126
+ }
127
+ if (def.fields.some((f) => f.name === 'title')) out.add('title')
128
+ if (out.size === 0) return undefined
129
+ return Array.from(out)
130
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * RelationField — widget for `type: 'relation'` fields.
3
+ *
4
+ * Override handles:
5
+ * .byline-field-relation — outer wrapper
6
+ * .byline-field-relation-header — label row
7
+ * .byline-field-relation-help — help text below the label
8
+ * .byline-field-relation-error-tile — wrapper shown when target collection is unknown
9
+ * .byline-field-relation-error-text — secondary error message below the title
10
+ * .byline-field-relation-tile — bordered selected-value + actions container
11
+ * .byline-field-relation-actions — top-right icon-button group (edit + remove)
12
+ * .byline-field-relation-mono — monospace `<code>` for field/collection names
13
+ */
14
+
15
+ .header,
16
+ :global(.byline-field-relation-header) {
17
+ display: flex;
18
+ align-items: baseline;
19
+ gap: var(--spacing-8);
20
+ margin-bottom: 0.25rem;
21
+ }
22
+
23
+ .help,
24
+ :global(.byline-field-relation-help) {
25
+ margin-bottom: 0.25rem;
26
+ color: var(--gray-400);
27
+ font-size: var(--font-size-xs);
28
+ }
29
+
30
+ .error-tile,
31
+ :global(.byline-field-relation-error-tile) {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: 0.25rem;
35
+ margin-top: 0.25rem;
36
+ padding: var(--spacing-8);
37
+ border: var(--border-width-thin) var(--border-style-solid) var(--red-700);
38
+ background-color: oklch(from var(--red-900) l c h / 0.2);
39
+ border-radius: var(--border-radius-md);
40
+ color: var(--red-200);
41
+ font-size: var(--font-size-xs);
42
+ }
43
+
44
+ .error-text,
45
+ :global(.byline-field-relation-error-text) {
46
+ color: oklch(from var(--red-400) l c h / 0.8);
47
+ }
48
+
49
+ .tile,
50
+ :global(.byline-field-relation-tile) {
51
+ display: flex;
52
+ align-items: flex-start;
53
+ justify-content: space-between;
54
+ gap: var(--spacing-8);
55
+ margin-top: 0.25rem;
56
+ padding: var(--spacing-8);
57
+ border: var(--border-width-thin) var(--border-style-solid) var(--primary-500);
58
+ border-radius: var(--border-radius-md);
59
+ color: var(--gray-200);
60
+ font-size: var(--font-size-xs);
61
+ }
62
+
63
+ .actions,
64
+ :global(.byline-field-relation-actions) {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--spacing-4);
68
+ flex-shrink: 0;
69
+ }
70
+
71
+ :global(.byline-field-relation-actions .byline-button) {
72
+ color: var(--gray-900);
73
+ }
74
+
75
+ :global(.dark .byline-field-relation-actions .byline-button),
76
+ :global([data-theme="dark"] .byline-field-relation-actions .byline-button) {
77
+ color: var(--gray-200);
78
+ }
79
+
80
+ .mono,
81
+ :global(.byline-field-relation-mono) {
82
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
83
+ }