@byline/ui 2.5.2 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/components/shimmer/shimmer.d.ts +13 -1
  2. package/dist/components/shimmer/shimmer.js +29 -20
  3. package/dist/components/shimmer/shimmer_module.css +4 -4
  4. package/dist/dnd/draggable-sortable/demo/draggable-list-demo.js +1 -1
  5. package/dist/react.d.ts +18 -54
  6. package/dist/react.js +0 -35
  7. package/dist/styles/styles.css +3 -0
  8. package/dist/uikit.d.ts +1 -0
  9. package/dist/uikit.js +1 -0
  10. package/package.json +2 -8
  11. package/src/components/shimmer/shimmer.module.css +8 -4
  12. package/src/components/shimmer/shimmer.tsx +34 -9
  13. package/src/dnd/draggable-sortable/demo/draggable-list-demo.tsx +1 -1
  14. package/src/react.ts +20 -68
  15. package/src/styles/functional/surfaces.css +13 -1
  16. package/src/uikit.ts +1 -0
  17. package/dist/admin/group.d.ts +0 -27
  18. package/dist/admin/group.js +0 -14
  19. package/dist/admin/group.module.js +0 -6
  20. package/dist/admin/group_module.css +0 -19
  21. package/dist/admin/row.d.ts +0 -25
  22. package/dist/admin/row.js +0 -8
  23. package/dist/admin/row.module.js +0 -5
  24. package/dist/admin/row_module.css +0 -18
  25. package/dist/admin/tabs.d.ts +0 -25
  26. package/dist/admin/tabs.js +0 -35
  27. package/dist/admin/tabs.module.js +0 -10
  28. package/dist/admin/tabs_module.css +0 -68
  29. package/dist/fields/array/array-field.d.ts +0 -14
  30. package/dist/fields/array/array-field.js +0 -176
  31. package/dist/fields/array/array-field.module.js +0 -11
  32. package/dist/fields/array/array-field_module.css +0 -32
  33. package/dist/fields/blocks/blocks-field.d.ts +0 -13
  34. package/dist/fields/blocks/blocks-field.js +0 -244
  35. package/dist/fields/blocks/blocks-field.module.js +0 -26
  36. package/dist/fields/blocks/blocks-field_module.css +0 -107
  37. package/dist/fields/checkbox/checkbox-field.d.ts +0 -16
  38. package/dist/fields/checkbox/checkbox-field.js +0 -28
  39. package/dist/fields/checkbox/checkbox-field.module.js +0 -6
  40. package/dist/fields/checkbox/checkbox-field_module.css +0 -4
  41. package/dist/fields/column-formatter.d.ts +0 -20
  42. package/dist/fields/column-formatter.js +0 -15
  43. package/dist/fields/date-time-formatter.d.ts +0 -16
  44. package/dist/fields/date-time-formatter.js +0 -8
  45. package/dist/fields/datetime/datetime-field.d.ts +0 -16
  46. package/dist/fields/datetime/datetime-field.js +0 -37
  47. package/dist/fields/datetime/datetime-field.module.js +0 -5
  48. package/dist/fields/datetime/datetime-field_module.css +0 -4
  49. package/dist/fields/draggable-context-menu.d.ts +0 -6
  50. package/dist/fields/draggable-context-menu.js +0 -83
  51. package/dist/fields/draggable-context-menu.module.js +0 -15
  52. package/dist/fields/draggable-context-menu_module.css +0 -91
  53. package/dist/fields/field-helpers.d.ts +0 -26
  54. package/dist/fields/field-helpers.js +0 -50
  55. package/dist/fields/field-renderer.d.ts +0 -37
  56. package/dist/fields/field-renderer.js +0 -206
  57. package/dist/fields/field-renderer.module.js +0 -8
  58. package/dist/fields/field-renderer_module.css +0 -11
  59. package/dist/fields/file/file-field.d.ts +0 -19
  60. package/dist/fields/file/file-field.js +0 -226
  61. package/dist/fields/file/file-field.module.js +0 -18
  62. package/dist/fields/file/file-field_module.css +0 -131
  63. package/dist/fields/file/file-upload-field.d.ts +0 -21
  64. package/dist/fields/file/file-upload-field.js +0 -128
  65. package/dist/fields/file/file-upload-field.module.js +0 -15
  66. package/dist/fields/file/file-upload-field_module.css +0 -74
  67. package/dist/fields/group/group-field.d.ts +0 -15
  68. package/dist/fields/group/group-field.js +0 -59
  69. package/dist/fields/group/group-field.module.js +0 -9
  70. package/dist/fields/group/group-field_module.css +0 -27
  71. package/dist/fields/image/image-field.d.ts +0 -19
  72. package/dist/fields/image/image-field.js +0 -242
  73. package/dist/fields/image/image-field.module.js +0 -22
  74. package/dist/fields/image/image-field_module.css +0 -121
  75. package/dist/fields/image/image-upload-field.d.ts +0 -21
  76. package/dist/fields/image/image-upload-field.js +0 -187
  77. package/dist/fields/image/image-upload-field.module.js +0 -19
  78. package/dist/fields/image/image-upload-field_module.css +0 -92
  79. package/dist/fields/local-date-time.d.ts +0 -27
  80. package/dist/fields/local-date-time.js +0 -49
  81. package/dist/fields/locale-badge.d.ts +0 -18
  82. package/dist/fields/locale-badge.js +0 -10
  83. package/dist/fields/locale-badge.module.js +0 -5
  84. package/dist/fields/locale-badge_module.css +0 -27
  85. package/dist/fields/numerical/numerical-field.d.ts +0 -18
  86. package/dist/fields/numerical/numerical-field.js +0 -74
  87. package/dist/fields/relation/relation-display.d.ts +0 -40
  88. package/dist/fields/relation/relation-display.js +0 -58
  89. package/dist/fields/relation/relation-display.module.js +0 -9
  90. package/dist/fields/relation/relation-display_module.css +0 -21
  91. package/dist/fields/relation/relation-field.d.ts +0 -18
  92. package/dist/fields/relation/relation-field.js +0 -146
  93. package/dist/fields/relation/relation-field.module.js +0 -13
  94. package/dist/fields/relation/relation-field_module.css +0 -62
  95. package/dist/fields/relation/relation-picker.d.ts +0 -49
  96. package/dist/fields/relation/relation-picker.js +0 -233
  97. package/dist/fields/relation/relation-picker.module.js +0 -26
  98. package/dist/fields/relation/relation-picker_module.css +0 -124
  99. package/dist/fields/relation/relation-summary.d.ts +0 -31
  100. package/dist/fields/relation/relation-summary.js +0 -50
  101. package/dist/fields/relation/relation-summary.module.js +0 -11
  102. package/dist/fields/relation/relation-summary_module.css +0 -37
  103. package/dist/fields/select/select-field.d.ts +0 -16
  104. package/dist/fields/select/select-field.js +0 -50
  105. package/dist/fields/select/select-field.module.js +0 -5
  106. package/dist/fields/select/select-field_module.css +0 -4
  107. package/dist/fields/sortable-item.d.ts +0 -15
  108. package/dist/fields/sortable-item.js +0 -80
  109. package/dist/fields/sortable-item.module.js +0 -22
  110. package/dist/fields/sortable-item_module.css +0 -124
  111. package/dist/fields/text/text-field.d.ts +0 -20
  112. package/dist/fields/text/text-field.js +0 -104
  113. package/dist/fields/text/text-field.module.js +0 -6
  114. package/dist/fields/text/text-field_module.css +0 -5
  115. package/dist/fields/text-area/text-area-field.d.ts +0 -20
  116. package/dist/fields/text-area/text-area-field.js +0 -105
  117. package/dist/fields/text-area/text-area-field.module.js +0 -6
  118. package/dist/fields/text-area/text-area-field_module.css +0 -5
  119. package/dist/fields/use-field-change-handler.d.ts +0 -23
  120. package/dist/fields/use-field-change-handler.js +0 -52
  121. package/dist/forms/document-actions.d.ts +0 -48
  122. package/dist/forms/document-actions.js +0 -469
  123. package/dist/forms/document-actions.module.js +0 -34
  124. package/dist/forms/document-actions_module.css +0 -118
  125. package/dist/forms/form-context.d.ts +0 -89
  126. package/dist/forms/form-context.js +0 -466
  127. package/dist/forms/form-renderer.d.ts +0 -98
  128. package/dist/forms/form-renderer.js +0 -591
  129. package/dist/forms/form-renderer.module.js +0 -46
  130. package/dist/forms/form-renderer_module.css +0 -245
  131. package/dist/forms/navigation-guard.d.ts +0 -54
  132. package/dist/forms/navigation-guard.js +0 -22
  133. package/dist/forms/path-widget.d.ts +0 -36
  134. package/dist/forms/path-widget.js +0 -107
  135. package/dist/forms/path-widget.module.js +0 -8
  136. package/dist/forms/path-widget_module.css +0 -29
  137. package/dist/forms/upload-executor.d.ts +0 -57
  138. package/dist/forms/upload-executor.js +0 -92
  139. package/dist/services/field-services-context.d.ts +0 -16
  140. package/dist/services/field-services-context.js +0 -13
  141. package/dist/services/field-services-types.d.ts +0 -63
  142. package/dist/services/field-services-types.js +0 -1
  143. package/dist/widgets/diff-viewer/diff-modal.d.ts +0 -22
  144. package/dist/widgets/diff-viewer/diff-modal.js +0 -146
  145. package/dist/widgets/diff-viewer/diff-modal.module.js +0 -14
  146. package/dist/widgets/diff-viewer/diff-modal_module.css +0 -56
  147. package/dist/widgets/status-badge/status-badge.d.ts +0 -25
  148. package/dist/widgets/status-badge/status-badge.js +0 -35
  149. package/dist/widgets/status-badge/status-badge.module.js +0 -7
  150. package/dist/widgets/status-badge/status-badge_module.css +0 -20
  151. package/src/admin/group.module.css +0 -41
  152. package/src/admin/group.tsx +0 -40
  153. package/src/admin/row.module.css +0 -32
  154. package/src/admin/row.tsx +0 -33
  155. package/src/admin/tabs.module.css +0 -107
  156. package/src/admin/tabs.tsx +0 -82
  157. package/src/fields/array/array-field.module.css +0 -48
  158. package/src/fields/array/array-field.tsx +0 -266
  159. package/src/fields/blocks/blocks-field.module.css +0 -148
  160. package/src/fields/blocks/blocks-field.tsx +0 -312
  161. package/src/fields/checkbox/checkbox-field.module.css +0 -4
  162. package/src/fields/checkbox/checkbox-field.tsx +0 -54
  163. package/src/fields/column-formatter.tsx +0 -31
  164. package/src/fields/date-time-formatter.tsx +0 -22
  165. package/src/fields/datetime/datetime-field.module.css +0 -13
  166. package/src/fields/datetime/datetime-field.tsx +0 -54
  167. package/src/fields/draggable-context-menu.module.css +0 -127
  168. package/src/fields/draggable-context-menu.tsx +0 -85
  169. package/src/fields/field-helpers.ts +0 -69
  170. package/src/fields/field-renderer.module.css +0 -22
  171. package/src/fields/field-renderer.tsx +0 -288
  172. package/src/fields/file/file-field.module.css +0 -153
  173. package/src/fields/file/file-field.tsx +0 -271
  174. package/src/fields/file/file-upload-field.module.css +0 -101
  175. package/src/fields/file/file-upload-field.tsx +0 -183
  176. package/src/fields/group/group-field.module.css +0 -43
  177. package/src/fields/group/group-field.tsx +0 -84
  178. package/src/fields/image/image-field.module.css +0 -155
  179. package/src/fields/image/image-field.tsx +0 -291
  180. package/src/fields/image/image-upload-field.module.css +0 -123
  181. package/src/fields/image/image-upload-field.tsx +0 -270
  182. package/src/fields/local-date-time.tsx +0 -88
  183. package/src/fields/locale-badge.module.css +0 -37
  184. package/src/fields/locale-badge.tsx +0 -32
  185. package/src/fields/numerical/numerical-field.tsx +0 -114
  186. package/src/fields/relation/relation-display.module.css +0 -36
  187. package/src/fields/relation/relation-display.tsx +0 -130
  188. package/src/fields/relation/relation-field.module.css +0 -83
  189. package/src/fields/relation/relation-field.tsx +0 -206
  190. package/src/fields/relation/relation-picker.module.css +0 -168
  191. package/src/fields/relation/relation-picker.tsx +0 -325
  192. package/src/fields/relation/relation-summary.module.css +0 -55
  193. package/src/fields/relation/relation-summary.tsx +0 -123
  194. package/src/fields/select/select-field.module.css +0 -13
  195. package/src/fields/select/select-field.tsx +0 -61
  196. package/src/fields/sortable-item.module.css +0 -167
  197. package/src/fields/sortable-item.tsx +0 -101
  198. package/src/fields/text/text-field.module.css +0 -13
  199. package/src/fields/text/text-field.tsx +0 -146
  200. package/src/fields/text-area/text-area-field.module.css +0 -13
  201. package/src/fields/text-area/text-area-field.tsx +0 -147
  202. package/src/fields/use-field-change-handler.ts +0 -112
  203. package/src/forms/document-actions.module.css +0 -160
  204. package/src/forms/document-actions.tsx +0 -487
  205. package/src/forms/form-context.tsx +0 -704
  206. package/src/forms/form-renderer.module.css +0 -321
  207. package/src/forms/form-renderer.tsx +0 -888
  208. package/src/forms/navigation-guard.tsx +0 -98
  209. package/src/forms/path-widget.module.css +0 -41
  210. package/src/forms/path-widget.test.tsx +0 -217
  211. package/src/forms/path-widget.tsx +0 -181
  212. package/src/forms/upload-executor.ts +0 -190
  213. package/src/services/field-services-context.tsx +0 -35
  214. package/src/services/field-services-types.ts +0 -68
  215. package/src/widgets/diff-viewer/diff-modal.module.css +0 -79
  216. package/src/widgets/diff-viewer/diff-modal.tsx +0 -184
  217. package/src/widgets/status-badge/status-badge.module.css +0 -31
  218. package/src/widgets/status-badge/status-badge.tsx +0 -69
@@ -1,167 +0,0 @@
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
- }
@@ -1,101 +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 ReactNode, useState } from 'react'
10
-
11
- import cx from 'classnames'
12
-
13
- import { useSortable } from '../dnd/draggable-sortable'
14
- import { ChevronDownIcon, GripperVerticalIcon } from '../uikit.js'
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 { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
32
- id,
33
- transition: {
34
- duration: 250,
35
- easing: 'cubic-bezier(0, 0.2, 0.2, 1)',
36
- },
37
- })
38
-
39
- const [collapsed, setCollapsed] = useState(false)
40
-
41
- const style = {
42
- transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined,
43
- transition,
44
- zIndex: isDragging ? 10 : 'auto',
45
- }
46
-
47
- return (
48
- <div
49
- ref={setNodeRef}
50
- style={style}
51
- className={cx(
52
- 'byline-sortable',
53
- styles.root,
54
- isDragging && ['byline-sortable-dragging', styles.dragging],
55
- collapsed && ['byline-sortable-collapsed', styles.collapsed]
56
- )}
57
- >
58
- <div
59
- className={cx(
60
- 'byline-sortable-header',
61
- styles.header,
62
- !collapsed && ['byline-sortable-header-expanded', styles['header-expanded']]
63
- )}
64
- >
65
- <button
66
- type="button"
67
- className={cx('byline-sortable-grip', styles.grip)}
68
- {...attributes}
69
- {...listeners}
70
- >
71
- <GripperVerticalIcon className={cx('byline-sortable-grip-icon', styles['grip-icon'])} />
72
- </button>
73
- <div className={cx('byline-sortable-label', styles.label)}>{label}</div>
74
- <DraggableContextMenu onAddBelow={onAddBelow} onRemove={onRemove} />
75
- <button
76
- type="button"
77
- className={cx('byline-sortable-toggle', styles.toggle)}
78
- onClick={() => setCollapsed((prev) => !prev)}
79
- aria-label={collapsed ? 'Expand item' : 'Collapse item'}
80
- >
81
- <ChevronDownIcon
82
- className={cx(
83
- 'byline-sortable-toggle-icon',
84
- styles['toggle-icon'],
85
- collapsed && ['byline-sortable-toggle-icon-rotated', styles['toggle-icon-rotated']]
86
- )}
87
- />
88
- </button>
89
- </div>
90
- <div
91
- className={cx(
92
- 'byline-sortable-content',
93
- styles.content,
94
- collapsed && ['byline-sortable-content-hidden', styles['content-hidden']]
95
- )}
96
- >
97
- {children}
98
- </div>
99
- </div>
100
- )
101
- }
@@ -1,13 +0,0 @@
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
- }
@@ -1,146 +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 { useCallback } from 'react'
10
-
11
- import type { Field, FieldComponentSlots, TextField as FieldType } from '@byline/core'
12
- import cx from 'classnames'
13
-
14
- import { useFieldError, useFieldValue } from '../../forms/form-context'
15
- import { Input, Label } from '../../uikit.js'
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
- }
@@ -1,13 +0,0 @@
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
- }
@@ -1,147 +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 { useCallback } from 'react'
10
-
11
- import type { Field, FieldComponentSlots, TextAreaField as FieldType } from '@byline/core'
12
- import cx from 'classnames'
13
-
14
- import { useFieldError, useFieldValue } from '../../forms/form-context'
15
- import { Label, TextArea } from '../../uikit.js'
16
- import { LocaleBadge } from '../locale-badge'
17
- import styles from './text-area-field.module.css'
18
-
19
- export const TextAreaField = ({
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
- // TextArea'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 TextArea 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-area-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
- <TextArea
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: React.ChangeEvent<HTMLTextAreaElement>) => handleChange(e.target.value)}
131
- error={fieldError != null}
132
- errorText={fieldError}
133
- rows={4}
134
- />
135
- )
136
- }
137
-
138
- return (
139
- <div className={`byline-field-text-area ${field.name}`}>
140
- {renderLabel()}
141
- {BeforeField && <BeforeField {...slotBaseProps} />}
142
- {renderInput()}
143
- {AfterField && <AfterField {...slotBaseProps} />}
144
- {CustomHelpText && <CustomHelpText {...slotBaseProps} helpText={field.helpText} />}
145
- </div>
146
- )
147
- }
@@ -1,112 +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 { useCallback } from 'react'
10
-
11
- import type { Field, FieldBeforeChangeResult, FieldHookContext } from '@byline/core'
12
- import { normalizeHooks } from '@byline/core'
13
-
14
- import { useFormContext } from '../forms/form-context'
15
-
16
- /**
17
- * Returns a change handler for the given field that runs through the
18
- * field-hook pipeline before committing the value:
19
- *
20
- * 1. `clearFieldError(path)`
21
- * 2. `field.hooks.beforeValidate(ctx)` — advisory: may set an error on
22
- * the field but the value is **always** committed (user can keep typing)
23
- * 3. `field.hooks.beforeChange(ctx)` — may return `{ value }` to replace
24
- * or `{ error }` to block the change entirely
25
- * 4. `setFieldValue(path, finalValue)`
26
- *
27
- * When the field has no hooks the function is a zero-overhead pass-through
28
- * to `setFieldValue` (no promises, no extra allocations).
29
- */
30
- export function useFieldChangeHandler(field: Field, path: string) {
31
- const { setFieldValue, getFieldValue, getFieldValues, setFieldError, clearFieldError } =
32
- useFormContext()
33
-
34
- return useCallback(
35
- (value: any) => {
36
- const hooks = field.hooks
37
-
38
- // ── fast path: no hooks defined ────────────────────────────
39
- const validateFns = normalizeHooks(hooks?.beforeValidate)
40
- const changeFns = normalizeHooks(hooks?.beforeChange)
41
-
42
- if (validateFns.length === 0 && changeFns.length === 0) {
43
- setFieldValue(path, value)
44
- return
45
- }
46
-
47
- // ── slow path: run async hook pipeline ─────────────────────
48
- const previousValue = getFieldValue(path)
49
- const ctx: FieldHookContext = {
50
- value,
51
- previousValue,
52
- data: getFieldValues(),
53
- path,
54
- field,
55
- operation: 'change',
56
- }
57
-
58
- clearFieldError(path)
59
-
60
- // When there are only advisory beforeValidate hooks (no beforeChange that can
61
- // block or transform the value), commit synchronously so that controlled inputs
62
- // (e.g. <input value={...}>) receive the updated value before React's next
63
- // render. Deferring even one microtask tick (via await) causes React to
64
- // reconcile the stale value prop against the DOM and reset cursor position.
65
- if (changeFns.length === 0) {
66
- setFieldValue(path, value)
67
- }
68
-
69
- void (async () => {
70
- try {
71
- // 1. beforeValidate (advisory — value is always committed)
72
- let advisoryError: string | undefined
73
- for (const fn of validateFns) {
74
- const result = (await fn(ctx)) as FieldBeforeChangeResult | undefined
75
- if (result?.error) {
76
- advisoryError = result.error
77
- }
78
- if (result?.value !== undefined) {
79
- ctx.value = result.value
80
- }
81
- }
82
-
83
- // 2. beforeChange
84
- for (const fn of changeFns) {
85
- const result = (await fn(ctx)) as FieldBeforeChangeResult | undefined
86
- if (result?.error) {
87
- setFieldError(path, result.error)
88
- return // block the change
89
- }
90
- if (result?.value !== undefined) {
91
- ctx.value = result.value
92
- }
93
- }
94
-
95
- // 3. commit the (possibly transformed) value and surface any advisory error.
96
- // If there were no beforeChange hooks we already committed synchronously above;
97
- // this call is a no-op if the value hasn't been altered by a hook.
98
- setFieldValue(path, ctx.value)
99
- if (advisoryError) {
100
- setFieldError(path, advisoryError)
101
- }
102
- } catch (err) {
103
- // Surface unexpected hook errors as field errors rather than crashing
104
- const message = err instanceof Error ? err.message : 'Unexpected hook error'
105
- setFieldError(path, message)
106
- }
107
- })()
108
- },
109
- // field reference is stable per render cycle; path is derived from props
110
- [field, path, setFieldValue, getFieldValue, getFieldValues, setFieldError, clearFieldError]
111
- )
112
- }