@byline/admin 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 (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 +61 -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 +59 -0
  72. package/dist/fields/relation/relation-picker.js +237 -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 +138 -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 +343 -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,160 @@
1
+ /**
2
+ * DocumentActions — overflow menu (Unpublish / Delete) on the form chrome.
3
+ *
4
+ * Override handles:
5
+ * .byline-form-actions-icon — the rotated ellipsis trigger icon
6
+ * .byline-form-actions-menu — dropdown content panel
7
+ * .byline-form-actions-item — a row inside the dropdown
8
+ * .byline-form-actions-item-icon — fixed-width icon column
9
+ * .byline-form-actions-item-text — label column
10
+ * .byline-form-actions-delete — destructive label colour
11
+ * .byline-form-actions-modal-head — modal header layout
12
+ * .byline-form-actions-modal-title — modal heading text
13
+ * .byline-form-actions-sr-only — visually-hidden autofocus shim
14
+ */
15
+
16
+ .icon,
17
+ :global(.byline-form-actions-icon) {
18
+ transform: rotate(90deg);
19
+ color: var(--primary-500);
20
+ }
21
+
22
+ .menu,
23
+ :global(.byline-form-actions-menu) {
24
+ min-width: 140px;
25
+ }
26
+
27
+ .item,
28
+ :global(.byline-form-actions-item) {
29
+ display: flex;
30
+ align-items: center;
31
+ margin-left: var(--spacing-8);
32
+ }
33
+
34
+ .item-icon,
35
+ :global(.byline-form-actions-item-icon) {
36
+ display: inline-block;
37
+ width: 28px;
38
+ }
39
+
40
+ .item-text,
41
+ :global(.byline-form-actions-item-text) {
42
+ display: inline-block;
43
+ width: 100%;
44
+ flex: 1;
45
+ font-size: var(--font-size-sm);
46
+ text-align: left;
47
+ }
48
+
49
+ .delete,
50
+ :global(.byline-form-actions-delete) {
51
+ flex: 1;
52
+ width: 100%;
53
+ border: none;
54
+ background: none;
55
+ padding: 0;
56
+ color: var(--red-600);
57
+ line-height: 1;
58
+ text-align: left;
59
+ cursor: pointer;
60
+ }
61
+
62
+ .modal-head,
63
+ :global(.byline-form-actions-modal-head) {
64
+ padding-top: 1rem;
65
+ margin-bottom: var(--spacing-8);
66
+ }
67
+
68
+ .modal-title,
69
+ :global(.byline-form-actions-modal-title) {
70
+ margin: 0 0 var(--spacing-8) 0;
71
+ font-size: var(--font-size-2xl);
72
+ }
73
+
74
+ .sr-only,
75
+ :global(.byline-form-actions-sr-only) {
76
+ position: absolute;
77
+ width: 1px;
78
+ height: 1px;
79
+ padding: 0;
80
+ margin: -1px;
81
+ overflow: hidden;
82
+ clip: rect(0, 0, 0, 0);
83
+ white-space: nowrap;
84
+ border: 0;
85
+ }
86
+
87
+ .list,
88
+ :global(.byline-form-actions-list) {
89
+ margin: var(--spacing-8) 0;
90
+ padding-left: 1.25rem;
91
+ font-size: var(--font-size-sm);
92
+ }
93
+
94
+ .preview,
95
+ :global(.byline-form-actions-preview) {
96
+ margin-top: var(--spacing-16);
97
+ padding: var(--spacing-8) var(--spacing-12);
98
+ background: var(--surface-2, rgba(0, 0, 0, 0.03));
99
+ border: 1px solid var(--border-subtle, rgba(0, 0, 0, 0.08));
100
+ border-radius: var(--radius-md, 4px);
101
+ font-size: var(--font-size-sm);
102
+ }
103
+
104
+ .preview-label,
105
+ :global(.byline-form-actions-preview-label) {
106
+ font-weight: 500;
107
+ margin-bottom: var(--spacing-4);
108
+ opacity: 0.75;
109
+ }
110
+
111
+ .preview-row,
112
+ :global(.byline-form-actions-preview-row) {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: var(--spacing-8);
116
+ flex-wrap: wrap;
117
+ }
118
+
119
+ .preview-before,
120
+ :global(.byline-form-actions-preview-before) {
121
+ font-family: var(--font-mono, monospace);
122
+ opacity: 0.7;
123
+ }
124
+
125
+ .preview-arrow,
126
+ :global(.byline-form-actions-preview-arrow) {
127
+ opacity: 0.5;
128
+ }
129
+
130
+ .preview-after,
131
+ :global(.byline-form-actions-preview-after) {
132
+ font-family: var(--font-mono, monospace);
133
+ font-weight: 500;
134
+ }
135
+
136
+ .copy-row,
137
+ :global(.byline-form-actions-copy-row) {
138
+ display: flex;
139
+ align-items: center;
140
+ flex-wrap: wrap;
141
+ }
142
+
143
+ .copy-label,
144
+ :global(.byline-form-actions-copy-label) {
145
+ /* Layout-only handle; spacing is set inline alongside the field. */
146
+ }
147
+
148
+ .copy-source,
149
+ :global(.byline-form-actions-copy-source) {
150
+ font-family: var(--font-mono, monospace);
151
+ }
152
+
153
+ /* ─── Dark theme variants ───────────────────────────────────── */
154
+
155
+ :is([data-theme="dark"], :global(.dark)) {
156
+ .delete,
157
+ :global(.byline-form-actions-delete) {
158
+ color: var(--red-400);
159
+ }
160
+ }
@@ -0,0 +1,482 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * This Source Code is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ *
8
+ * Copyright (c) Infonomic Company Limited
9
+ */
10
+
11
+ import { useState } from 'react'
12
+
13
+ import { useTranslation } from '@byline/i18n/react'
14
+ import {
15
+ Button,
16
+ Checkbox,
17
+ CloseIcon,
18
+ DeleteIcon,
19
+ Dropdown as DropdownComponent,
20
+ EllipsisIcon,
21
+ IconButton,
22
+ Modal,
23
+ Select,
24
+ } from '@byline/ui/react'
25
+ import cx from 'classnames'
26
+
27
+ import styles from './document-actions.module.css'
28
+ import type { PublishedVersionInfo } from './form-renderer'
29
+
30
+ const DUPLICATE_TITLE_SUFFIX = ' (copy)'
31
+
32
+ /**
33
+ * Shape of a content-locale option as consumed by the Copy-to-Locale
34
+ * modal. Matches the host adapter's `ContentLocaleOption`; declared
35
+ * locally so this package does not take a dependency on host code.
36
+ */
37
+ export interface DocumentActionsLocaleOption {
38
+ code: string
39
+ label: string
40
+ }
41
+
42
+ export function DocumentActions({
43
+ publishedVersion,
44
+ onUnpublish,
45
+ onDelete,
46
+ onDuplicate,
47
+ sourceTitle,
48
+ onCopyToLocale,
49
+ sourceLocale,
50
+ contentLocales,
51
+ }: {
52
+ publishedVersion?: PublishedVersionInfo | null
53
+ onUnpublish?: () => Promise<void>
54
+ onDelete?: () => Promise<void>
55
+ /**
56
+ * Called when the editor confirms the duplicate modal. The parent runs
57
+ * the server fn, surfaces a toast, and navigates to the new document.
58
+ */
59
+ onDuplicate?: () => Promise<void>
60
+ /**
61
+ * The current (saved) value of the source document's `useAsTitle`
62
+ * field, used to render the suffix preview inside the duplicate modal.
63
+ * Sourced from the form's `initialData`, not live form state, so the
64
+ * preview reflects what will actually be duplicated.
65
+ */
66
+ sourceTitle?: string | null
67
+ /**
68
+ * Called when the editor confirms the Copy-to-Locale modal. The
69
+ * parent runs the server fn, surfaces a toast, and navigates to the
70
+ * target locale view. Menu item is hidden when omitted, or when fewer
71
+ * than two content locales are configured.
72
+ */
73
+ onCopyToLocale?: (args: { targetLocale: string; overwrite: boolean }) => Promise<void>
74
+ /**
75
+ * The locale the form is currently displaying. Used as the read-only
76
+ * "From" label in the Copy-to-Locale modal and excluded from the
77
+ * target Select.
78
+ */
79
+ sourceLocale?: string
80
+ /**
81
+ * All configured content locales (code + display label). The
82
+ * Copy-to-Locale Select lists every locale except `sourceLocale`.
83
+ */
84
+ contentLocales?: ReadonlyArray<DocumentActionsLocaleOption>
85
+ }) {
86
+ const { t } = useTranslation('byline-admin')
87
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
88
+ const [showDuplicateConfirm, setShowDuplicateConfirm] = useState(false)
89
+ const [duplicateBusy, setDuplicateBusy] = useState(false)
90
+
91
+ // Copy-to-Locale modal state. The menu item is hidden entirely unless
92
+ // the host has supplied a handler AND there is at least one *other*
93
+ // locale to copy into.
94
+ const availableTargetLocales = (contentLocales ?? []).filter((loc) => loc.code !== sourceLocale)
95
+ const copyToLocaleAvailable = onCopyToLocale != null && availableTargetLocales.length > 0
96
+ const [showCopyToLocaleConfirm, setShowCopyToLocaleConfirm] = useState(false)
97
+ const [copyToLocaleBusy, setCopyToLocaleBusy] = useState(false)
98
+ const [copyTargetLocale, setCopyTargetLocale] = useState<string>(
99
+ availableTargetLocales[0]?.code ?? ''
100
+ )
101
+ const [copyOverwrite, setCopyOverwrite] = useState(false)
102
+
103
+ const handleOnDelete = () => {
104
+ setShowDeleteConfirm(false)
105
+ if (onDelete) {
106
+ onDelete()
107
+ }
108
+ }
109
+
110
+ const handleOnDuplicate = async () => {
111
+ if (!onDuplicate) return
112
+ setDuplicateBusy(true)
113
+ try {
114
+ await onDuplicate()
115
+ setShowDuplicateConfirm(false)
116
+ } finally {
117
+ setDuplicateBusy(false)
118
+ }
119
+ }
120
+
121
+ const handleOpenCopyToLocale = () => {
122
+ // Reset on open: pick the first available target and clear the
123
+ // overwrite checkbox so a previous-session "overwrite=true" choice
124
+ // is not silently sticky.
125
+ setCopyTargetLocale(availableTargetLocales[0]?.code ?? '')
126
+ setCopyOverwrite(false)
127
+ setShowCopyToLocaleConfirm(true)
128
+ }
129
+
130
+ const handleOnCopyToLocale = async () => {
131
+ if (!onCopyToLocale || !copyTargetLocale) return
132
+ setCopyToLocaleBusy(true)
133
+ try {
134
+ await onCopyToLocale({ targetLocale: copyTargetLocale, overwrite: copyOverwrite })
135
+ setShowCopyToLocaleConfirm(false)
136
+ } finally {
137
+ setCopyToLocaleBusy(false)
138
+ }
139
+ }
140
+
141
+ // Preview text shown inside the modal. Falls back to the literal suffix
142
+ // when no source title is supplied (collections without `useAsTitle`).
143
+ const duplicatePreviewBefore = sourceTitle ?? ''
144
+ const duplicatePreviewAfter = (sourceTitle ?? '') + DUPLICATE_TITLE_SUFFIX
145
+
146
+ const sourceLocaleLabel =
147
+ contentLocales?.find((loc) => loc.code === sourceLocale)?.label ?? sourceLocale ?? ''
148
+
149
+ return (
150
+ <>
151
+ <DropdownComponent.Root>
152
+ <DropdownComponent.Trigger
153
+ render={<IconButton variant="text" intent="noeffect" size="sm" />}
154
+ >
155
+ <EllipsisIcon
156
+ className={cx('byline-form-actions-icon', styles.icon)}
157
+ width="15px"
158
+ height="15px"
159
+ />
160
+ </DropdownComponent.Trigger>
161
+
162
+ <DropdownComponent.Portal>
163
+ <DropdownComponent.Content
164
+ className={cx('byline-form-actions-menu', styles.menu)}
165
+ align="end"
166
+ data-side="top"
167
+ sideOffset={10}
168
+ >
169
+ {/*{publishedVersion && (
170
+ <>
171
+ <DropdownComponent.Item onClick={onUnpublish}>
172
+ <div className={cx('byline-form-actions-item', styles.item)}>
173
+ <span className={cx('byline-form-actions-item-icon', styles['item-icon'])} />
174
+ <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
175
+ Unpublish
176
+ </span>
177
+ </div>
178
+ </DropdownComponent.Item>
179
+ <DropdownComponent.Separator />
180
+ </>
181
+ )}*/}
182
+ {copyToLocaleAvailable && (
183
+ <DropdownComponent.Item onClick={handleOpenCopyToLocale}>
184
+ <div className={cx('byline-form-actions-item', styles.item)}>
185
+ <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
186
+ <button type="button">{t('documentActions.copyToLocaleMenuItem')}</button>
187
+ </span>
188
+ </div>
189
+ </DropdownComponent.Item>
190
+ )}
191
+ {onDuplicate && (
192
+ <DropdownComponent.Item
193
+ onClick={() => {
194
+ setShowDuplicateConfirm(true)
195
+ }}
196
+ >
197
+ <div className={cx('byline-form-actions-item', styles.item)}>
198
+ <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
199
+ <button type="button">{t('common.actions.duplicate')}</button>
200
+ </span>
201
+ </div>
202
+ </DropdownComponent.Item>
203
+ )}
204
+ <DropdownComponent.Separator />
205
+ <DropdownComponent.Item
206
+ onClick={() => {
207
+ setShowDeleteConfirm(true)
208
+ }}
209
+ >
210
+ <div className={cx('byline-form-actions-item', styles.item)}>
211
+ <span className={cx('byline-form-actions-item-icon', styles['item-icon'])}>
212
+ <DeleteIcon width="16px" height="16px" />
213
+ </span>
214
+ <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
215
+ <button type="button" className={cx('byline-form-actions-delete', styles.delete)}>
216
+ {t('common.actions.delete')}
217
+ </button>
218
+ </span>
219
+ </div>
220
+ </DropdownComponent.Item>
221
+ </DropdownComponent.Content>
222
+ </DropdownComponent.Portal>
223
+ </DropdownComponent.Root>
224
+
225
+ <Modal
226
+ isOpen={showDeleteConfirm}
227
+ closeOnOverlayClick={true}
228
+ onDismiss={() => {
229
+ setShowDeleteConfirm(false)
230
+ }}
231
+ >
232
+ <Modal.Container style={{ maxWidth: '500px' }}>
233
+ <Modal.Header className={cx('byline-form-actions-modal-head', styles['modal-head'])}>
234
+ <h3 className={cx('byline-form-actions-modal-title', styles['modal-title'])}>
235
+ {t('documentActions.delete.title')}
236
+ </h3>
237
+ <IconButton
238
+ arial-label={t('common.actions.close')}
239
+ size="xs"
240
+ onClick={() => {
241
+ setShowDeleteConfirm(false)
242
+ }}
243
+ >
244
+ <CloseIcon width="16px" height="16px" svgClassName="white-icon" />
245
+ </IconButton>
246
+ </Modal.Header>
247
+ <Modal.Content>
248
+ <p>{t('documentActions.delete.warning')}</p>
249
+ </Modal.Content>
250
+ <Modal.Actions>
251
+ <button
252
+ data-autofocus
253
+ type="button"
254
+ tabIndex={0}
255
+ className={cx('byline-form-actions-sr-only', styles['sr-only'])}
256
+ >
257
+ no action
258
+ </button>
259
+ <Button
260
+ size="sm"
261
+ style={{ minWidth: '80px' }}
262
+ intent="noeffect"
263
+ onClick={() => {
264
+ setShowDeleteConfirm(false)
265
+ }}
266
+ >
267
+ {t('common.actions.cancel')}
268
+ </Button>
269
+ <Button size="sm" style={{ minWidth: '80px' }} intent="danger" onClick={handleOnDelete}>
270
+ {t('common.actions.delete')}
271
+ </Button>
272
+ </Modal.Actions>
273
+ </Modal.Container>
274
+ </Modal>
275
+
276
+ <Modal
277
+ isOpen={showDuplicateConfirm}
278
+ closeOnOverlayClick={!duplicateBusy}
279
+ onDismiss={() => {
280
+ if (!duplicateBusy) setShowDuplicateConfirm(false)
281
+ }}
282
+ >
283
+ <Modal.Container style={{ maxWidth: '560px' }}>
284
+ <Modal.Header className={cx('byline-form-actions-modal-head', styles['modal-head'])}>
285
+ <h3 className={cx('byline-form-actions-modal-title', styles['modal-title'])}>
286
+ {t('documentActions.duplicate.title')}
287
+ </h3>
288
+ <IconButton
289
+ arial-label={t('common.actions.close')}
290
+ size="xs"
291
+ onClick={() => {
292
+ if (!duplicateBusy) setShowDuplicateConfirm(false)
293
+ }}
294
+ >
295
+ <CloseIcon width="16px" height="16px" svgClassName="white-icon" />
296
+ </IconButton>
297
+ </Modal.Header>
298
+ <Modal.Content className="prose">
299
+ <p className="m-0">{t('documentActions.duplicate.intro')}</p>
300
+ <ul className={cx('byline-form-actions-list', styles.list)}>
301
+ <li>
302
+ {t('documentActions.duplicate.bulletTitle')}{' '}
303
+ <code>{DUPLICATE_TITLE_SUFFIX.trim()}</code>.
304
+ </li>
305
+ <li>{t('documentActions.duplicate.bulletPath')}</li>
306
+ </ul>
307
+ {sourceTitle != null && sourceTitle.length > 0 && (
308
+ <div className={cx('byline-form-actions-preview', styles.preview)}>
309
+ <div className={cx('byline-form-actions-preview-label', styles['preview-label'])}>
310
+ {t('documentActions.duplicate.previewLabel')}
311
+ </div>
312
+ <div className={cx('byline-form-actions-preview-row', styles['preview-row'])}>
313
+ <span
314
+ className={cx('byline-form-actions-preview-before', styles['preview-before'])}
315
+ >
316
+ {duplicatePreviewBefore}
317
+ </span>
318
+ <span
319
+ className={cx('byline-form-actions-preview-arrow', styles['preview-arrow'])}
320
+ >
321
+
322
+ </span>
323
+ <span
324
+ className={cx('byline-form-actions-preview-after', styles['preview-after'])}
325
+ >
326
+ {duplicatePreviewAfter}
327
+ </span>
328
+ </div>
329
+ </div>
330
+ )}
331
+ </Modal.Content>
332
+ <Modal.Actions>
333
+ <button
334
+ data-autofocus
335
+ type="button"
336
+ tabIndex={0}
337
+ className={cx('byline-form-actions-sr-only', styles['sr-only'])}
338
+ >
339
+ no action
340
+ </button>
341
+ <Button
342
+ size="sm"
343
+ style={{ minWidth: '80px' }}
344
+ intent="noeffect"
345
+ onClick={() => {
346
+ if (!duplicateBusy) setShowDuplicateConfirm(false)
347
+ }}
348
+ disabled={duplicateBusy}
349
+ >
350
+ {t('common.actions.cancel')}
351
+ </Button>
352
+ <Button
353
+ size="sm"
354
+ style={{ minWidth: '80px' }}
355
+ intent="primary"
356
+ onClick={handleOnDuplicate}
357
+ disabled={duplicateBusy}
358
+ >
359
+ {duplicateBusy
360
+ ? t('documentActions.duplicate.busyButton')
361
+ : t('common.actions.duplicate')}
362
+ </Button>
363
+ </Modal.Actions>
364
+ </Modal.Container>
365
+ </Modal>
366
+
367
+ <Modal
368
+ isOpen={showCopyToLocaleConfirm}
369
+ closeOnOverlayClick={!copyToLocaleBusy}
370
+ onDismiss={() => {
371
+ if (!copyToLocaleBusy) setShowCopyToLocaleConfirm(false)
372
+ }}
373
+ >
374
+ <Modal.Container style={{ maxWidth: '560px' }}>
375
+ <Modal.Header className={cx('byline-form-actions-modal-head', styles['modal-head'])}>
376
+ <h3 className={cx('byline-form-actions-modal-title', styles['modal-title'])}>
377
+ {t('documentActions.copyToLocale.title')}
378
+ </h3>
379
+ <IconButton
380
+ arial-label={t('common.actions.close')}
381
+ size="xs"
382
+ onClick={() => {
383
+ if (!copyToLocaleBusy) setShowCopyToLocaleConfirm(false)
384
+ }}
385
+ >
386
+ <CloseIcon width="16px" height="16px" svgClassName="white-icon" />
387
+ </IconButton>
388
+ </Modal.Header>
389
+ <Modal.Content>
390
+ <p>{t('documentActions.copyToLocale.intro')}</p>
391
+ <div
392
+ className={cx('byline-form-actions-copy-row', styles['copy-row'])}
393
+ style={{ marginTop: 'var(--spacing-12)' }}
394
+ >
395
+ <span
396
+ className={cx('byline-form-actions-copy-label', styles['copy-label'])}
397
+ style={{ fontWeight: 500 }}
398
+ >
399
+ {t('documentActions.copyToLocale.fromLabel')}&nbsp;
400
+ </span>
401
+ <span className={cx('byline-form-actions-copy-source', styles['copy-source'])}>
402
+ {sourceLocaleLabel}
403
+ </span>
404
+ </div>
405
+ <div
406
+ className={cx('byline-form-actions-copy-row', styles['copy-row'])}
407
+ style={{ marginTop: 'var(--spacing-12)' }}
408
+ >
409
+ <span
410
+ className={cx('byline-form-actions-copy-label', styles['copy-label'])}
411
+ style={{ fontWeight: 500, marginRight: 'var(--spacing-8)' }}
412
+ >
413
+ {t('documentActions.copyToLocale.toLabel')}
414
+ </span>
415
+ <Select<string>
416
+ size="sm"
417
+ ariaLabel={t('documentActions.copyToLocale.targetAriaLabel')}
418
+ value={copyTargetLocale}
419
+ items={availableTargetLocales.map((loc) => ({
420
+ value: loc.code,
421
+ label: loc.label,
422
+ }))}
423
+ onValueChange={(value) => {
424
+ if (value != null) setCopyTargetLocale(value)
425
+ }}
426
+ disabled={copyToLocaleBusy}
427
+ />
428
+ </div>
429
+ <div
430
+ className={cx('byline-form-actions-copy-row', styles['copy-row'])}
431
+ style={{ marginTop: 'var(--spacing-16)' }}
432
+ >
433
+ <Checkbox
434
+ id="copy-to-locale-overwrite"
435
+ name="overwrite"
436
+ label={t('documentActions.copyToLocale.overwriteLabel')}
437
+ checked={copyOverwrite}
438
+ disabled={copyToLocaleBusy}
439
+ helpText={t('documentActions.copyToLocale.overwriteHelp')}
440
+ onCheckedChange={(value) => {
441
+ setCopyOverwrite(value === true)
442
+ }}
443
+ />
444
+ </div>
445
+ </Modal.Content>
446
+ <Modal.Actions>
447
+ <button
448
+ data-autofocus
449
+ type="button"
450
+ tabIndex={0}
451
+ className={cx('byline-form-actions-sr-only', styles['sr-only'])}
452
+ >
453
+ no action
454
+ </button>
455
+ <Button
456
+ size="sm"
457
+ style={{ minWidth: '80px' }}
458
+ intent="noeffect"
459
+ onClick={() => {
460
+ if (!copyToLocaleBusy) setShowCopyToLocaleConfirm(false)
461
+ }}
462
+ disabled={copyToLocaleBusy}
463
+ >
464
+ {t('common.actions.cancel')}
465
+ </Button>
466
+ <Button
467
+ size="sm"
468
+ style={{ minWidth: '80px' }}
469
+ intent="primary"
470
+ onClick={handleOnCopyToLocale}
471
+ disabled={copyToLocaleBusy || !copyTargetLocale}
472
+ >
473
+ {copyToLocaleBusy
474
+ ? t('documentActions.copyToLocale.busyButton')
475
+ : t('documentActions.copyToLocale.confirmButton')}
476
+ </Button>
477
+ </Modal.Actions>
478
+ </Modal.Container>
479
+ </Modal>
480
+ </>
481
+ )
482
+ }