@byline/admin 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 (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,55 @@
1
+ /**
2
+ * RelationSummary — selected-value tile for the relation field widget.
3
+ *
4
+ * Override handles:
5
+ * .byline-relation-summary — root container
6
+ * .byline-relation-summary-stack — vertical layout used for label + value
7
+ * .byline-relation-summary-row — horizontal layout used for picker columns
8
+ * .byline-relation-summary-kind — small "type" label above the value
9
+ * .byline-relation-summary-value — primary value text
10
+ * .byline-relation-summary-value-mono — monospace fallback (raw uuid / not-found)
11
+ * .byline-relation-summary-missing — added to the value when target is unresolved
12
+ */
13
+
14
+ .stack,
15
+ :global(.byline-relation-summary-stack) {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: 0.125rem;
19
+ min-width: 0;
20
+ }
21
+
22
+ .row,
23
+ :global(.byline-relation-summary-row) {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 0.75rem;
27
+ min-width: 0;
28
+ }
29
+
30
+ .kind,
31
+ :global(.byline-relation-summary-kind) {
32
+ color: var(--gray-500);
33
+ }
34
+
35
+ .value,
36
+ :global(.byline-relation-summary-value) {
37
+ color: var(--gray-100);
38
+ overflow: hidden;
39
+ text-overflow: ellipsis;
40
+ white-space: nowrap;
41
+ }
42
+
43
+ .value-mono,
44
+ :global(.byline-relation-summary-value-mono) {
45
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
46
+ overflow: hidden;
47
+ text-overflow: ellipsis;
48
+ white-space: nowrap;
49
+ }
50
+
51
+ .missing,
52
+ :global(.byline-relation-summary-missing) {
53
+ font-size: var(--font-size-xs);
54
+ color: var(--red-400);
55
+ }
@@ -0,0 +1,123 @@
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 { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
10
+ import cx from 'classnames'
11
+
12
+ import { PickerCell, resolveFallbackDisplayField, resolveRowLabel } from './relation-display'
13
+ import styles from './relation-summary.module.css'
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // RelationSummary — selected-value tile for the relation field widget.
17
+ //
18
+ // Rendering priority (mirrors RelationPicker so the tile and picker rows
19
+ // look identical):
20
+ // 1. target `CollectionAdminConfig.picker` columns (full fidelity, with
21
+ // formatters — e.g. MediaThumbnail + title)
22
+ // 2. explicit `displayField` prop (from source schema's RelationField)
23
+ // 3. `CollectionDefinition.useAsTitle`
24
+ // 4. first declared text field on the target
25
+ // 5. target UUID (only when nothing else is available — "resolved but
26
+ // naked" or unpopulated)
27
+ //
28
+ // Value source priority:
29
+ // 1. `populated.document` — a `PopulatedRelationValue` attached by the
30
+ // server-side populate pass on first page load.
31
+ // 2. `cachedRecord` — the raw document the picker just handed us after
32
+ // a fresh pick (no server round trip needed).
33
+ // 3. neither — we have only the stored ref; fall through to UUID.
34
+ // ---------------------------------------------------------------------------
35
+
36
+ interface RelationSummaryProps {
37
+ targetDefinition: CollectionDefinition
38
+ targetAdminConfig: CollectionAdminConfig | null
39
+ displayField?: string
40
+ /** The raw relation value from the form. May be a plain ref or a populated envelope. */
41
+ value: {
42
+ targetDocumentId: string
43
+ targetCollectionId: string
44
+ _resolved?: boolean
45
+ _cycle?: boolean
46
+ document?: Record<string, any>
47
+ }
48
+ /**
49
+ * A document record cached client-side from a recent picker selection.
50
+ * Used when `value` is a plain ref (post-pick state) but we still want
51
+ * the tile to render real display data without a refetch. Caller is
52
+ * responsible for clearing/replacing this when the value's
53
+ * `targetDocumentId` changes.
54
+ */
55
+ cachedRecord?: Record<string, any> | null
56
+ }
57
+
58
+ export function RelationSummary({
59
+ targetDefinition,
60
+ targetAdminConfig,
61
+ displayField,
62
+ value,
63
+ cachedRecord,
64
+ }: RelationSummaryProps) {
65
+ const pickerColumns = targetAdminConfig?.picker
66
+
67
+ // Unresolved (deleted target).
68
+ if (value._resolved === false) {
69
+ return (
70
+ <div className={cx('byline-relation-summary-stack', styles.stack)}>
71
+ <span className={cx('byline-relation-summary-kind', styles.kind)}>
72
+ {targetDefinition.labels.singular}
73
+ </span>
74
+ <span
75
+ className={cx(
76
+ 'byline-relation-summary-value-mono byline-relation-summary-missing',
77
+ styles['value-mono'],
78
+ styles.missing
79
+ )}
80
+ >
81
+ (target not found) {value.targetDocumentId}
82
+ </span>
83
+ </div>
84
+ )
85
+ }
86
+
87
+ // Prefer the populated envelope's document; fall back to the cached
88
+ // picker record; finally fall back to rendering just the raw ref.
89
+ const record: Record<string, any> | null =
90
+ (value._resolved === true && !value._cycle && value.document) || cachedRecord || null
91
+
92
+ if (record && pickerColumns && pickerColumns.length > 0) {
93
+ return (
94
+ <div className={cx('byline-relation-summary-row', styles.row)}>
95
+ {pickerColumns.map((col) => (
96
+ <PickerCell key={String(col.fieldName)} column={col} record={record} />
97
+ ))}
98
+ </div>
99
+ )
100
+ }
101
+
102
+ const resolvedDisplayField =
103
+ displayField ??
104
+ targetDefinition.useAsTitle ??
105
+ resolveFallbackDisplayField(targetDefinition) ??
106
+ null
107
+ const label = record ? resolveRowLabel(record, resolvedDisplayField) : null
108
+
109
+ return (
110
+ <div className={cx('byline-relation-summary-stack', styles.stack)}>
111
+ <span className={cx('byline-relation-summary-kind', styles.kind)}>
112
+ {targetDefinition.labels.singular}
113
+ </span>
114
+ {label ? (
115
+ <span className={cx('byline-relation-summary-value', styles.value)}>{label}</span>
116
+ ) : (
117
+ <span className={cx('byline-relation-summary-value-mono', styles['value-mono'])}>
118
+ {value.targetDocumentId}
119
+ </span>
120
+ )}
121
+ </div>
122
+ )
123
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SelectField — dropdown form widget.
3
+ *
4
+ * Override handles:
5
+ * .byline-field-select — the wrapper div
6
+ * .byline-field-select-dirty — added to the inner select when the
7
+ * field has unsaved local changes
8
+ */
9
+
10
+ .dirty,
11
+ :global(.byline-field-select-dirty) {
12
+ border-color: var(--blue-300);
13
+ }
@@ -0,0 +1,61 @@
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 { SelectField as FieldType } from '@byline/core'
10
+ import { ErrorText, Label, Select } from '@byline/ui/react'
11
+ import cx from 'classnames'
12
+
13
+ import { useFieldError, useFieldValue, useIsDirty } from '../../forms/form-context'
14
+ import styles from './select-field.module.css'
15
+
16
+ export const SelectField = ({
17
+ field,
18
+ value,
19
+ defaultValue,
20
+ onChange,
21
+ id,
22
+ path,
23
+ }: {
24
+ field: FieldType
25
+ value?: string
26
+ defaultValue?: string
27
+ onChange?: (value: string) => void
28
+ id?: string
29
+ path?: string
30
+ }) => {
31
+ const fieldPath = path ?? field.name
32
+ const fieldError = useFieldError(fieldPath)
33
+ const isDirty = useIsDirty(fieldPath)
34
+ const fieldValue = useFieldValue<string | undefined>(fieldPath)
35
+ const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
36
+ const htmlId = id ?? fieldPath
37
+
38
+ return (
39
+ <div className={`byline-field-select ${field.name}`}>
40
+ {field.label && (
41
+ <Label id={htmlId} htmlFor={htmlId} label={field.label} required={!field.optional} />
42
+ )}
43
+ <Select<string>
44
+ size="sm"
45
+ id={htmlId}
46
+ name={field.name}
47
+ placeholder="Select an option"
48
+ required={!field.optional}
49
+ value={incomingValue}
50
+ ariaLabel={field.label}
51
+ helpText={field.helpText}
52
+ items={field.options.map((opt) => ({ value: opt.value, label: opt.label }))}
53
+ onValueChange={(value) => {
54
+ if (value != null) onChange?.(value)
55
+ }}
56
+ className={cx(isDirty && ['byline-field-select-dirty', styles.dirty])}
57
+ />
58
+ {fieldError && <ErrorText id={`${field.name}-error`} text={fieldError} />}
59
+ </div>
60
+ )
61
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * SortableItem — drag-card wrapping array/blocks list rows.
3
+ *
4
+ * Override handles:
5
+ * .byline-sortable — root card
6
+ * .byline-sortable-dragging — added while the item is mid-drag
7
+ * .byline-sortable-collapsed — added when content is collapsed
8
+ * .byline-sortable-header — top row (gripper + label + actions)
9
+ * .byline-sortable-header-collapsed — added to header when collapsed
10
+ * .byline-sortable-grip — drag handle button
11
+ * .byline-sortable-grip-icon — drag handle icon
12
+ * .byline-sortable-label — header label text
13
+ * .byline-sortable-toggle — collapse/expand button
14
+ * .byline-sortable-toggle-icon — chevron icon (rotates when collapsed)
15
+ * .byline-sortable-content — body container (children)
16
+ * .byline-sortable-content-hidden — collapsed body state
17
+ */
18
+
19
+ .root,
20
+ :global(.byline-sortable) {
21
+ padding: var(--spacing-16);
22
+ padding-top: var(--spacing-8);
23
+ border: var(--border-width-thin) dashed var(--gray-600);
24
+ border-radius: var(--border-radius-md);
25
+ background-color: oklch(from var(--canvas-50) l c h / 0.5);
26
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
27
+ }
28
+
29
+ .dragging,
30
+ :global(.byline-sortable-dragging) {
31
+ background-color: oklch(from var(--canvas-50) l c h / 0.8);
32
+ box-shadow:
33
+ 0 4px 6px -1px rgb(0 0 0 / 0.1),
34
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
35
+ }
36
+
37
+ .collapsed,
38
+ :global(.byline-sortable-collapsed) {
39
+ padding-top: var(--spacing-8);
40
+ padding-bottom: var(--spacing-8);
41
+ }
42
+
43
+ .header,
44
+ :global(.byline-sortable-header) {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: var(--spacing-8);
48
+ margin-bottom: 0;
49
+ margin-left: -0.75rem;
50
+ }
51
+
52
+ .header-expanded,
53
+ :global(.byline-sortable-header-expanded) {
54
+ margin-bottom: var(--spacing-8);
55
+ }
56
+
57
+ .grip,
58
+ :global(.byline-sortable-grip) {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ padding: 0.25rem;
63
+ border: none;
64
+ background: none;
65
+ border-radius: var(--border-radius-sm);
66
+ color: var(--gray-400);
67
+ cursor: grab;
68
+ }
69
+
70
+ .grip:hover,
71
+ :global(.byline-sortable-grip):hover {
72
+ background-color: var(--gray-100);
73
+ }
74
+
75
+ .grip:active,
76
+ :global(.byline-sortable-grip):active {
77
+ cursor: grabbing;
78
+ }
79
+
80
+ .grip-icon,
81
+ :global(.byline-sortable-grip-icon) {
82
+ width: 1rem;
83
+ height: 1rem;
84
+ color: var(--primary-500);
85
+ }
86
+
87
+ .label,
88
+ :global(.byline-sortable-label) {
89
+ flex: 1;
90
+ min-width: 0;
91
+ font-size: 1rem;
92
+ font-weight: var(--font-weight-medium);
93
+ overflow: hidden;
94
+ text-overflow: ellipsis;
95
+ white-space: nowrap;
96
+ }
97
+
98
+ .toggle,
99
+ :global(.byline-sortable-toggle) {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ padding: 0.25rem;
104
+ border: none;
105
+ background: none;
106
+ border-radius: var(--border-radius-sm);
107
+ color: var(--gray-400);
108
+ cursor: pointer;
109
+ }
110
+
111
+ .toggle:hover,
112
+ :global(.byline-sortable-toggle):hover {
113
+ background-color: var(--gray-800);
114
+ }
115
+
116
+ .toggle-icon,
117
+ :global(.byline-sortable-toggle-icon) {
118
+ width: 1rem;
119
+ height: 1rem;
120
+ transition: transform 150ms ease;
121
+ }
122
+
123
+ .toggle-icon-rotated,
124
+ :global(.byline-sortable-toggle-icon-rotated) {
125
+ transform: rotate(180deg);
126
+ }
127
+
128
+ .content,
129
+ :global(.byline-sortable-content) {
130
+ position: relative;
131
+ display: flex;
132
+ flex-direction: column;
133
+ gap: var(--spacing-16);
134
+ transition: all 200ms ease;
135
+ opacity: 1;
136
+ }
137
+
138
+ .content-hidden,
139
+ :global(.byline-sortable-content-hidden) {
140
+ max-height: 0;
141
+ opacity: 0;
142
+ z-index: -10;
143
+ }
144
+
145
+ /* ─── Dark theme variants ───────────────────────────────────── */
146
+
147
+ :is([data-theme="dark"], :global(.dark)) {
148
+ .root,
149
+ :global(.byline-sortable) {
150
+ background-color: var(--canvas-800);
151
+ }
152
+
153
+ .dragging,
154
+ :global(.byline-sortable-dragging) {
155
+ background-color: oklch(from var(--canvas-700) l c h / 0.3);
156
+ }
157
+
158
+ .grip:hover,
159
+ :global(.byline-sortable-grip):hover {
160
+ background-color: var(--gray-800);
161
+ }
162
+
163
+ .grip-icon,
164
+ :global(.byline-sortable-grip-icon) {
165
+ color: var(--primary-200);
166
+ }
167
+ }
@@ -0,0 +1,106 @@
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 ReactNode, useState } from 'react'
10
+
11
+ import { useTranslation } from '@byline/i18n/react'
12
+ import { ChevronDownIcon, GripperVerticalIcon, useSortable } from '@byline/ui/react'
13
+ import cx from 'classnames'
14
+
15
+ import { DraggableContextMenu } from './draggable-context-menu'
16
+ import styles from './sortable-item.module.css'
17
+
18
+ export const SortableItem = ({
19
+ id,
20
+ label,
21
+ children,
22
+ onAddBelow,
23
+ onRemove,
24
+ }: {
25
+ id: string
26
+ label: ReactNode
27
+ children: ReactNode
28
+ onAddBelow?: () => void
29
+ onRemove?: () => void
30
+ }) => {
31
+ const { t } = useTranslation('byline-admin')
32
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
33
+ id,
34
+ transition: {
35
+ duration: 250,
36
+ easing: 'cubic-bezier(0, 0.2, 0.2, 1)',
37
+ },
38
+ })
39
+
40
+ const [collapsed, setCollapsed] = useState(false)
41
+
42
+ const style = {
43
+ transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined,
44
+ transition,
45
+ zIndex: isDragging ? 10 : 'auto',
46
+ }
47
+
48
+ return (
49
+ <div
50
+ ref={setNodeRef}
51
+ style={style}
52
+ className={cx(
53
+ 'byline-sortable',
54
+ styles.root,
55
+ isDragging && ['byline-sortable-dragging', styles.dragging],
56
+ collapsed && ['byline-sortable-collapsed', styles.collapsed]
57
+ )}
58
+ >
59
+ <div
60
+ className={cx(
61
+ 'byline-sortable-header',
62
+ styles.header,
63
+ !collapsed && ['byline-sortable-header-expanded', styles['header-expanded']]
64
+ )}
65
+ >
66
+ <button
67
+ type="button"
68
+ className={cx('byline-sortable-grip', styles.grip)}
69
+ {...attributes}
70
+ {...listeners}
71
+ >
72
+ <GripperVerticalIcon className={cx('byline-sortable-grip-icon', styles['grip-icon'])} />
73
+ </button>
74
+ <div className={cx('byline-sortable-label', styles.label)}>{label}</div>
75
+ <DraggableContextMenu onAddBelow={onAddBelow} onRemove={onRemove} />
76
+ <button
77
+ type="button"
78
+ className={cx('byline-sortable-toggle', styles.toggle)}
79
+ onClick={() => setCollapsed((prev) => !prev)}
80
+ aria-label={
81
+ collapsed
82
+ ? t('fields.sortable.expandAriaLabel')
83
+ : t('fields.sortable.collapseAriaLabel')
84
+ }
85
+ >
86
+ <ChevronDownIcon
87
+ className={cx(
88
+ 'byline-sortable-toggle-icon',
89
+ styles['toggle-icon'],
90
+ collapsed && ['byline-sortable-toggle-icon-rotated', styles['toggle-icon-rotated']]
91
+ )}
92
+ />
93
+ </button>
94
+ </div>
95
+ <div
96
+ className={cx(
97
+ 'byline-sortable-content',
98
+ styles.content,
99
+ collapsed && ['byline-sortable-content-hidden', styles['content-hidden']]
100
+ )}
101
+ >
102
+ {children}
103
+ </div>
104
+ </div>
105
+ )
106
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * TextField — single-line text input wrapper.
3
+ *
4
+ * Override handles:
5
+ * .byline-field-text — the wrapper div
6
+ * .byline-field-text-label-row — the label + locale-badge row
7
+ */
8
+
9
+ .label-row,
10
+ :global(.byline-field-text-label-row) {
11
+ display: flex;
12
+ align-items: center;
13
+ }
@@ -0,0 +1,146 @@
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 { useCallback } from 'react'
10
+
11
+ import type { Field, FieldComponentSlots, TextField as FieldType } from '@byline/core'
12
+ import { Input, Label } from '@byline/ui/react'
13
+ import cx from 'classnames'
14
+
15
+ import { useFieldError, useFieldValue } from '../../forms/form-context'
16
+ import { LocaleBadge } from '../locale-badge'
17
+ import styles from './text-field.module.css'
18
+
19
+ export const TextField = ({
20
+ field,
21
+ value,
22
+ defaultValue,
23
+ onChange,
24
+ id,
25
+ path,
26
+ locale,
27
+ components,
28
+ }: {
29
+ field: FieldType
30
+ value?: string
31
+ defaultValue?: string
32
+ onChange?: (value: string) => void
33
+ id?: string
34
+ path?: string
35
+ /** When provided, renders a LocaleBadge next to the field label. */
36
+ locale?: string
37
+ /** Optional UI component slot overrides from the admin config. */
38
+ components?: FieldComponentSlots
39
+ }) => {
40
+ const fieldPath = path ?? field.name
41
+ const fieldError = useFieldError(fieldPath)
42
+ const fieldValue = useFieldValue<string | undefined>(fieldPath)
43
+ const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
44
+ const htmlId = id ?? fieldPath
45
+
46
+ const handleChange = useCallback(
47
+ (value: string) => {
48
+ if (onChange) {
49
+ onChange(value)
50
+ }
51
+ },
52
+ [onChange]
53
+ )
54
+
55
+ // Custom component slots (from admin config)
56
+ const slots = components
57
+ const CustomLabel = slots?.Label
58
+ const CustomHelpText = slots?.HelpText
59
+ const CustomField = slots?.Field
60
+ const BeforeField = slots?.beforeField
61
+ const AfterField = slots?.afterField
62
+
63
+ // Shared props available to every slot component
64
+ const slotBaseProps = {
65
+ field: field as Field,
66
+ path: fieldPath,
67
+ value: incomingValue,
68
+ error: fieldError,
69
+ id: htmlId,
70
+ }
71
+
72
+ // When a locale is active, render a custom Label+badge and suppress the
73
+ // Input's own label so the locale indicator appears in the label row.
74
+ const showBadge = !!locale && !!field.label
75
+
76
+ // Determine whether the label is handled externally (by a custom slot or
77
+ // the locale badge row) so Input doesn't render its own.
78
+ const hasCustomLabel = !!CustomLabel
79
+ const suppressInputLabel = showBadge || hasCustomLabel
80
+ const suppressInputHelpText = !!CustomHelpText
81
+
82
+ const labelRowClass = cx('byline-field-text-label-row', styles['label-row'])
83
+
84
+ // ── Label rendering ──────────────────────────────────────────
85
+ const renderLabel = () => {
86
+ if (hasCustomLabel) {
87
+ return (
88
+ <div className={labelRowClass}>
89
+ <CustomLabel {...slotBaseProps} label={field.label} required={!field.optional} />
90
+ {showBadge && <LocaleBadge locale={locale!} />}
91
+ </div>
92
+ )
93
+ }
94
+ if (showBadge) {
95
+ return (
96
+ <div className={labelRowClass}>
97
+ <Label
98
+ id={`${htmlId}-label`}
99
+ htmlFor={htmlId}
100
+ label={field.label!}
101
+ required={!field.optional}
102
+ />
103
+ <LocaleBadge locale={locale!} />
104
+ </div>
105
+ )
106
+ }
107
+ return null
108
+ }
109
+
110
+ // ── Field input rendering ────────────────────────────────────
111
+ const renderInput = () => {
112
+ if (CustomField) {
113
+ return (
114
+ <CustomField
115
+ {...slotBaseProps}
116
+ onChange={handleChange}
117
+ defaultValue={defaultValue}
118
+ placeholder={field.placeholder}
119
+ />
120
+ )
121
+ }
122
+ return (
123
+ <Input
124
+ id={htmlId}
125
+ name={field.name}
126
+ label={suppressInputLabel ? undefined : field.label}
127
+ required={!field.optional}
128
+ helpText={suppressInputHelpText ? undefined : field.helpText}
129
+ value={incomingValue}
130
+ onChange={(e) => handleChange(e.target.value)}
131
+ error={fieldError != null}
132
+ errorText={fieldError}
133
+ />
134
+ )
135
+ }
136
+
137
+ return (
138
+ <div className={`byline-field-text ${field.name}`}>
139
+ {renderLabel()}
140
+ {BeforeField && <BeforeField {...slotBaseProps} />}
141
+ {renderInput()}
142
+ {AfterField && <AfterField {...slotBaseProps} />}
143
+ {CustomHelpText && <CustomHelpText {...slotBaseProps} helpText={field.helpText} />}
144
+ </div>
145
+ )
146
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * TextAreaField — multi-line text input wrapper.
3
+ *
4
+ * Override handles:
5
+ * .byline-field-text-area — the wrapper div
6
+ * .byline-field-text-area-label-row — the label + locale-badge row
7
+ */
8
+
9
+ .label-row,
10
+ :global(.byline-field-text-area-label-row) {
11
+ display: flex;
12
+ align-items: center;
13
+ }