@byline/admin 4.19.0 → 5.0.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.
@@ -1,5 +1,6 @@
1
1
  import type { SlugifierFn } from '@byline/core';
2
2
  export interface PathWidgetProps {
3
+ disabled?: boolean;
3
4
  /** The collection's `useAsPath` source field name, when configured. */
4
5
  useAsPath: string | undefined;
5
6
  /** Collection path, forwarded to the slugifier as context. */
@@ -51,4 +52,4 @@ export interface PathWidgetProps {
51
52
  * Stable override handles: `.byline-form-path`, `.byline-form-path-header`,
52
53
  * `.byline-form-path-regenerate`.
53
54
  */
54
- export declare const PathWidget: ({ useAsPath, collectionPath, defaultLocale, activeLocale, mode, slugifier, sourceLocked, }: PathWidgetProps) => import("react").JSX.Element;
55
+ export declare const PathWidget: ({ useAsPath, collectionPath, defaultLocale, activeLocale, mode, slugifier, sourceLocked, disabled, }: PathWidgetProps) => import("react").JSX.Element;
@@ -12,7 +12,7 @@ function coerceToString(value) {
12
12
  if (value instanceof Date) return value.toISOString();
13
13
  return String(value);
14
14
  }
15
- const PathWidget = ({ useAsPath, collectionPath, defaultLocale, activeLocale, mode, slugifier, sourceLocked = false })=>{
15
+ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, activeLocale, mode, slugifier, sourceLocked = false, disabled = false })=>{
16
16
  const { setSystemPath } = useFormContext();
17
17
  const { t } = useTranslation('byline-admin');
18
18
  const systemPath = useSystemPath();
@@ -37,13 +37,16 @@ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, activeLocale, mo
37
37
  ]);
38
38
  const inputValue = systemPath ?? '';
39
39
  const handleChange = useCallback((next)=>{
40
+ if (disabled) return;
40
41
  setSystemPath(0 === next.length ? null : next);
41
42
  }, [
43
+ disabled,
42
44
  setSystemPath
43
45
  ]);
44
46
  const handleRegenerate = useCallback(()=>{
45
- if (livePreview.length > 0) setSystemPath(livePreview);
47
+ if (!disabled && livePreview.length > 0) setSystemPath(livePreview);
46
48
  }, [
49
+ disabled,
47
50
  livePreview,
48
51
  setSystemPath
49
52
  ]);
@@ -90,6 +93,7 @@ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, activeLocale, mo
90
93
  type: "button",
91
94
  onClick: handleRegenerate,
92
95
  className: clsx('byline-form-path-regenerate', path_widget_module.regenerate),
96
+ disabled: disabled,
93
97
  "aria-label": t('pathWidget.regenerateAriaLabel', {
94
98
  field: useAsPath
95
99
  }),
@@ -100,6 +104,7 @@ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, activeLocale, mo
100
104
  ]
101
105
  }),
102
106
  /*#__PURE__*/ jsx(Input, {
107
+ disabled: disabled,
103
108
  id: "system-path",
104
109
  name: "__systemPath__",
105
110
  value: inputValue,
@@ -5,6 +5,7 @@ export interface SchedulePublicationInput {
5
5
  publishAt: string;
6
6
  }
7
7
  export interface UseScheduledPublicationArgs {
8
+ disabled?: boolean;
8
9
  schedule: ScheduledPublicationInfo | null;
9
10
  onSchedule?: (input: SchedulePublicationInput) => Promise<void>;
10
11
  onConfirm?: () => Promise<void>;
@@ -24,7 +25,7 @@ export interface UseScheduledPublicationReturn {
24
25
  /** The schedule / reschedule modal. Render once, anywhere in the form. */
25
26
  modal: React.ReactNode;
26
27
  }
27
- export declare function useScheduledPublication({ schedule, onSchedule, onConfirm, onCancel, hasUnsavedChanges, onUnsavedChanges, }: UseScheduledPublicationArgs): UseScheduledPublicationReturn;
28
+ export declare function useScheduledPublication({ disabled, schedule, onSchedule, onConfirm, onCancel, hasUnsavedChanges, onUnsavedChanges, }: UseScheduledPublicationArgs): UseScheduledPublicationReturn;
28
29
  /**
29
30
  * One metadata cell for the form's status bar, matching the Status /
30
31
  * Last modified / Created cells in scale, sitting immediately after the status
@@ -28,7 +28,7 @@ function seedScheduleInstant(schedule) {
28
28
  seed.setSeconds(0, 0);
29
29
  return seed;
30
30
  }
31
- function useScheduledPublication({ schedule, onSchedule, onConfirm, onCancel, hasUnsavedChanges, onUnsavedChanges }) {
31
+ function useScheduledPublication({ disabled = false, schedule, onSchedule, onConfirm, onCancel, hasUnsavedChanges, onUnsavedChanges }) {
32
32
  const timeZone = useMemo(browserTimeZone, []);
33
33
  const [now, setNow] = useState(()=>Date.now());
34
34
  const [showSchedule, setShowSchedule] = useState(false);
@@ -55,52 +55,56 @@ function useScheduledPublication({ schedule, onSchedule, onConfirm, onCancel, ha
55
55
  now
56
56
  ]);
57
57
  const openSchedule = useCallback(()=>{
58
+ if (disabled) return;
58
59
  if (hasUnsavedChanges) return void onUnsavedChanges();
59
60
  setShowSchedule(true);
60
61
  }, [
62
+ disabled,
61
63
  hasUnsavedChanges,
62
64
  onUnsavedChanges
63
65
  ]);
64
66
  const confirm = useCallback(async ()=>{
65
- if (null == onConfirm) return;
67
+ if (disabled || null == onConfirm) return;
66
68
  if (hasUnsavedChanges) return void onUnsavedChanges();
67
69
  setBusy(true);
68
70
  try {
69
71
  await onConfirm();
70
- } finally{
72
+ } catch {} finally{
71
73
  setBusy(false);
72
74
  }
73
75
  }, [
76
+ disabled,
74
77
  onConfirm,
75
78
  hasUnsavedChanges,
76
79
  onUnsavedChanges
77
80
  ]);
78
81
  const cancel = useCallback(async ()=>{
79
- if (null == onCancel) return;
82
+ if (disabled || null == onCancel) return;
80
83
  setBusy(true);
81
84
  try {
82
85
  await onCancel();
83
- } finally{
86
+ } catch {} finally{
84
87
  setBusy(false);
85
88
  }
86
89
  }, [
90
+ disabled,
87
91
  onCancel
88
92
  ]);
89
93
  const modal = showSchedule ? /*#__PURE__*/ jsx(ScheduleModal, {
90
94
  schedule: schedule,
91
95
  timeZone: timeZone,
92
96
  onSubmit: async (input)=>{
93
- if (null == onSchedule) return;
97
+ if (disabled || null == onSchedule) return;
94
98
  setBusy(true);
95
99
  try {
96
100
  await onSchedule(input);
97
101
  setShowSchedule(false);
98
- } finally{
102
+ } catch {} finally{
99
103
  setBusy(false);
100
104
  }
101
105
  },
102
106
  onDismiss: ()=>setShowSchedule(false),
103
- busy: busy
107
+ busy: disabled || busy
104
108
  }) : null;
105
109
  return {
106
110
  state,
@@ -1,4 +1,8 @@
1
1
  export interface TreePlacementWidgetProps {
2
+ disabled?: boolean;
3
+ onMutationError?: (error: unknown) => 'blocked' | 'committed' | null | void;
4
+ onCommitted?: (receipt: import('@byline/core').StructuralMutationReceipt) => void;
5
+ expectedRevision: number;
2
6
  /** The collection path (`tree: true`). */
3
7
  collectionPath: string;
4
8
  /** The logical id of the document being edited. */
@@ -20,4 +24,4 @@ export interface TreePlacementWidgetProps {
20
24
  * Renders only in edit mode (placement needs a persisted document) and only when
21
25
  * the host wires the tree services. Stable override handle: `.byline-form-tree`.
22
26
  */
23
- export declare const TreePlacementWidget: ({ collectionPath, documentId, useAsTitle, }: TreePlacementWidgetProps) => import("react").JSX.Element | null;
27
+ export declare const TreePlacementWidget: ({ collectionPath, disabled, onMutationError, onCommitted, expectedRevision, documentId, useAsTitle, }: TreePlacementWidgetProps) => import("react").JSX.Element | null;
@@ -8,7 +8,7 @@ import clsx from "clsx";
8
8
  import { useBylineFieldServices } from "../fields/field-services-context.js";
9
9
  import { RelationPicker } from "../fields/relation/relation-picker.js";
10
10
  import tree_placement_widget_module from "./tree-placement-widget.module.js";
11
- const TreePlacementWidget = ({ collectionPath, documentId, useAsTitle })=>{
11
+ const TreePlacementWidget = ({ collectionPath, disabled = false, onMutationError, onCommitted, expectedRevision, documentId, useAsTitle })=>{
12
12
  const { t } = useTranslation('byline-admin');
13
13
  const { getTreeAncestors, getTreeParent, placeTreeNode } = useBylineFieldServices();
14
14
  const definition = getCollectionDefinition(collectionPath);
@@ -60,7 +60,7 @@ const TreePlacementWidget = ({ collectionPath, documentId, useAsTitle })=>{
60
60
  t
61
61
  ]);
62
62
  const place = useCallback(async (parentDocumentId, optimistic)=>{
63
- if (null == placeTreeNode || busy) return;
63
+ if (disabled || null == placeTreeNode || busy) return;
64
64
  const previousParent = parent;
65
65
  const previousPlaced = placed;
66
66
  setError(null);
@@ -68,12 +68,15 @@ const TreePlacementWidget = ({ collectionPath, documentId, useAsTitle })=>{
68
68
  setParent(optimistic);
69
69
  setPlaced(true);
70
70
  try {
71
- await placeTreeNode({
71
+ const result = await placeTreeNode({
72
+ expectedRevision,
72
73
  collection: collectionPath,
73
74
  documentId,
74
75
  parentDocumentId
75
76
  });
76
- } catch {
77
+ onCommitted?.(result);
78
+ } catch (error) {
79
+ if (onMutationError?.(error) === 'committed') return;
77
80
  setParent(previousParent);
78
81
  setPlaced(previousPlaced);
79
82
  setError(t('treeWidget.error'));
@@ -81,6 +84,10 @@ const TreePlacementWidget = ({ collectionPath, documentId, useAsTitle })=>{
81
84
  setBusy(false);
82
85
  }
83
86
  }, [
87
+ disabled,
88
+ onMutationError,
89
+ onCommitted,
90
+ expectedRevision,
84
91
  placeTreeNode,
85
92
  busy,
86
93
  parent,
@@ -136,20 +143,20 @@ const TreePlacementWidget = ({ collectionPath, documentId, useAsTitle })=>{
136
143
  size: "xs",
137
144
  variant: "outlined",
138
145
  intent: "noeffect",
139
- disabled: loading || busy,
146
+ disabled: disabled || loading || busy,
140
147
  onClick: ()=>setPickerOpen(true),
141
148
  children: t('treeWidget.choose')
142
149
  }),
143
150
  placed ? null != parent && /*#__PURE__*/ jsx("button", {
144
151
  type: "button",
145
152
  className: clsx('byline-form-tree-link', tree_placement_widget_module.link),
146
- disabled: busy,
153
+ disabled: disabled || busy,
147
154
  onClick: ()=>place(null, null),
148
155
  children: t('treeWidget.makeRoot')
149
156
  }) : /*#__PURE__*/ jsx("button", {
150
157
  type: "button",
151
158
  className: clsx('byline-form-tree-link', tree_placement_widget_module.link),
152
- disabled: loading || busy,
159
+ disabled: disabled || loading || busy,
153
160
  onClick: ()=>place(null, null),
154
161
  children: t('treeWidget.addToTree')
155
162
  })
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/admin",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "4.19.0",
5
+ "version": "5.0.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -183,12 +183,12 @@
183
183
  "react-diff-viewer-continued": "^4.4.0",
184
184
  "uuid": "^14.0.2",
185
185
  "zod": "^4.4.3",
186
- "@byline/analytics": "4.19.0",
187
- "@byline/analytics-agent": "4.19.0",
188
- "@byline/auth": "4.19.0",
189
- "@byline/ui": "4.19.0",
190
- "@byline/i18n": "4.19.0",
191
- "@byline/core": "4.19.0"
186
+ "@byline/core": "5.0.0",
187
+ "@byline/analytics": "5.0.0",
188
+ "@byline/i18n": "5.0.0",
189
+ "@byline/ui": "5.0.0",
190
+ "@byline/auth": "5.0.0",
191
+ "@byline/analytics-agent": "5.0.0"
192
192
  },
193
193
  "peerDependencies": {
194
194
  "react": "^19.0.0",
@@ -1,3 +1,4 @@
1
+ import type { StructuralMutationReceipt } from '@byline/core'
1
2
  /**
2
3
  * This Source Code is subject to the terms of the Mozilla Public
3
4
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -72,6 +73,7 @@ export interface TreeAncestor {
72
73
  }
73
74
 
74
75
  export interface PlaceTreeNodeInput {
76
+ expectedRevision: number
75
77
  collection: string
76
78
  documentId: string
77
79
  /** The new parent; `null` makes the document a root node. */
@@ -82,10 +84,16 @@ export interface PlaceTreeNodeInput {
82
84
  }
83
85
 
84
86
  /** Place / move a document within its collection's tree. */
85
- export type PlaceTreeNodeFn = (input: PlaceTreeNodeInput) => Promise<{ orderKey: string }>
87
+ export type PlaceTreeNodeFn = (
88
+ input: PlaceTreeNodeInput
89
+ ) => Promise<{ orderKey: string } & StructuralMutationReceipt>
86
90
 
87
91
  /** Remove a document from the tree (back to the unplaced state). */
88
- export type RemoveFromTreeFn = (input: { collection: string; documentId: string }) => Promise<void>
92
+ export type RemoveFromTreeFn = (input: {
93
+ collection: string
94
+ documentId: string
95
+ expectedRevision: number
96
+ }) => Promise<StructuralMutationReceipt>
89
97
 
90
98
  /** Resolve a document's ancestor chain, root-first, hydrated with titles. */
91
99
  export type GetTreeAncestorsFn = (input: {
@@ -27,6 +27,7 @@ export interface AvailableLocalesWidgetLocale {
27
27
  }
28
28
 
29
29
  export interface AvailableLocalesWidgetProps {
30
+ disabled?: boolean
30
31
  /** All configured content locales — one checkbox each (code + display label). */
31
32
  contentLocales: ReadonlyArray<AvailableLocalesWidgetLocale>
32
33
  /**
@@ -50,6 +51,7 @@ export interface AvailableLocalesWidgetProps {
50
51
  * `.byline-form-available-locales-list`.
51
52
  */
52
53
  export const AvailableLocalesWidget = ({
54
+ disabled: mutationsBlocked = false,
53
55
  contentLocales,
54
56
  availableVersionLocales,
55
57
  }: AvailableLocalesWidgetProps) => {
@@ -62,6 +64,7 @@ export const AvailableLocalesWidget = ({
62
64
 
63
65
  const toggle = useCallback(
64
66
  (code: string, checked: boolean) => {
67
+ if (mutationsBlocked) return
65
68
  const next = new Set(advertised)
66
69
  if (checked) {
67
70
  next.add(code)
@@ -70,7 +73,7 @@ export const AvailableLocalesWidget = ({
70
73
  }
71
74
  setSystemAvailableLocales([...next])
72
75
  },
73
- [advertised, setSystemAvailableLocales]
76
+ [mutationsBlocked, advertised, setSystemAvailableLocales]
74
77
  )
75
78
 
76
79
  if (contentLocales.length === 0) {
@@ -102,7 +105,7 @@ export const AvailableLocalesWidget = ({
102
105
  label={label}
103
106
  intent={intent}
104
107
  checked={checked}
105
- disabled={disabled}
108
+ disabled={mutationsBlocked || disabled}
106
109
  onCheckedChange={(value) => toggle(code, value === true)}
107
110
  />
108
111
  )
@@ -41,6 +41,7 @@ export interface DocumentActionsLocaleOption {
41
41
  }
42
42
 
43
43
  export function DocumentActions({
44
+ disabled = false,
44
45
  publishedVersion,
45
46
  onUnpublish,
46
47
  onDelete,
@@ -59,6 +60,7 @@ export function DocumentActions({
59
60
  onConfirmScheduledPublication,
60
61
  onCancelScheduledPublication,
61
62
  }: {
63
+ disabled?: boolean
62
64
  publishedVersion?: PublishedVersionInfo | null
63
65
  onUnpublish?: () => Promise<void>
64
66
  onDelete?: () => Promise<void>
@@ -221,24 +223,29 @@ export function DocumentActions({
221
223
  onDelete != null
222
224
 
223
225
  const handleOnDelete = () => {
226
+ if (disabled) return
224
227
  setShowDeleteConfirm(false)
225
228
  if (onDelete) {
226
- onDelete()
229
+ void onDelete().catch(() => {})
227
230
  }
228
231
  }
229
232
 
230
233
  const handleOnDuplicate = async () => {
234
+ if (disabled) return
231
235
  if (!onDuplicate) return
232
236
  setDuplicateBusy(true)
233
237
  try {
234
238
  await onDuplicate()
235
239
  setShowDuplicateConfirm(false)
240
+ } catch {
241
+ // The host reports the failure and retains the editor observation.
236
242
  } finally {
237
243
  setDuplicateBusy(false)
238
244
  }
239
245
  }
240
246
 
241
247
  const handleOpenDuplicate = () => {
248
+ if (disabled) return
242
249
  // Duplicate copies the saved version — block when the form is dirty so
243
250
  // unsaved edits are not silently dropped from the copy.
244
251
  if (hasUnsavedChanges) {
@@ -249,6 +256,7 @@ export function DocumentActions({
249
256
  }
250
257
 
251
258
  const handleOpenCopyToLocale = () => {
259
+ if (disabled) return
252
260
  // Copy-to-Locale reads the saved version — block when the form is dirty.
253
261
  if (hasUnsavedChanges) {
254
262
  onUnsavedChanges?.()
@@ -263,17 +271,21 @@ export function DocumentActions({
263
271
  }
264
272
 
265
273
  const handleOnCopyToLocale = async () => {
274
+ if (disabled) return
266
275
  if (!onCopyToLocale || !copyTargetLocale) return
267
276
  setCopyToLocaleBusy(true)
268
277
  try {
269
278
  await onCopyToLocale({ targetLocale: copyTargetLocale, overwrite: copyOverwrite })
270
279
  setShowCopyToLocaleConfirm(false)
280
+ } catch {
281
+ // The host reports the failure and retains the editor observation.
271
282
  } finally {
272
283
  setCopyToLocaleBusy(false)
273
284
  }
274
285
  }
275
286
 
276
287
  const handleOpenDeleteLocale = () => {
288
+ if (disabled) return
277
289
  // Delete-Locale removes the saved version's locale content — block when
278
290
  // the form is dirty so the editor saves (or discards) first.
279
291
  if (hasUnsavedChanges) {
@@ -288,11 +300,14 @@ export function DocumentActions({
288
300
  }
289
301
 
290
302
  const handleOnDeleteLocale = async () => {
303
+ if (disabled) return
291
304
  if (!onDeleteLocale || !deleteTargetLocale) return
292
305
  setDeleteLocaleBusy(true)
293
306
  try {
294
307
  await onDeleteLocale({ targetLocale: deleteTargetLocale })
295
308
  setShowDeleteLocaleConfirm(false)
309
+ } catch {
310
+ // The host reports the failure and retains the editor observation.
296
311
  } finally {
297
312
  setDeleteLocaleBusy(false)
298
313
  }
@@ -311,6 +326,7 @@ export function DocumentActions({
311
326
  {hasAnyAction && (
312
327
  <DropdownComponent.Root>
313
328
  <DropdownComponent.Trigger
329
+ disabled={disabled}
314
330
  render={<IconButton variant="text" intent="noeffect" size="sm" />}
315
331
  >
316
332
  <EllipsisIcon
@@ -329,7 +345,7 @@ export function DocumentActions({
329
345
  >
330
346
  {/*{publishedVersion && (
331
347
  <>
332
- <DropdownComponent.Item onClick={onUnpublish}>
348
+ <DropdownComponent.Item disabled={disabled} onClick={onUnpublish}>
333
349
  <div className={cx('byline-form-actions-item', styles.item)}>
334
350
  <span className={cx('byline-form-actions-item-icon', styles['item-icon'])} />
335
351
  <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
@@ -343,10 +359,16 @@ export function DocumentActions({
343
359
  {schedulingActions.length > 0 && (
344
360
  <>
345
361
  {schedulingActions.map((action) => (
346
- <DropdownComponent.Item key={action.key} onClick={action.onSelect}>
362
+ <DropdownComponent.Item
363
+ disabled={disabled}
364
+ key={action.key}
365
+ onClick={action.onSelect}
366
+ >
347
367
  <div className={cx('byline-form-actions-item', styles.item)}>
348
368
  <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
349
- <button type="button">{action.label}</button>
369
+ <button type="button" disabled={disabled}>
370
+ {action.label}
371
+ </button>
350
372
  </span>
351
373
  </div>
352
374
  </DropdownComponent.Item>
@@ -355,28 +377,34 @@ export function DocumentActions({
355
377
  </>
356
378
  )}
357
379
  {copyToLocaleAvailable && (
358
- <DropdownComponent.Item onClick={handleOpenCopyToLocale}>
380
+ <DropdownComponent.Item disabled={disabled} onClick={handleOpenCopyToLocale}>
359
381
  <div className={cx('byline-form-actions-item', styles.item)}>
360
382
  <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
361
- <button type="button">{t('documentActions.copyToLocaleMenuItem')}</button>
383
+ <button type="button" disabled={disabled}>
384
+ {t('documentActions.copyToLocaleMenuItem')}
385
+ </button>
362
386
  </span>
363
387
  </div>
364
388
  </DropdownComponent.Item>
365
389
  )}
366
390
  {deleteLocaleAvailable && (
367
- <DropdownComponent.Item onClick={handleOpenDeleteLocale}>
391
+ <DropdownComponent.Item disabled={disabled} onClick={handleOpenDeleteLocale}>
368
392
  <div className={cx('byline-form-actions-item', styles.item)}>
369
393
  <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
370
- <button type="button">{t('documentActions.deleteLocale.menuItem')}</button>
394
+ <button type="button" disabled={disabled}>
395
+ {t('documentActions.deleteLocale.menuItem')}
396
+ </button>
371
397
  </span>
372
398
  </div>
373
399
  </DropdownComponent.Item>
374
400
  )}
375
401
  {onDuplicate && (
376
- <DropdownComponent.Item onClick={handleOpenDuplicate}>
402
+ <DropdownComponent.Item disabled={disabled} onClick={handleOpenDuplicate}>
377
403
  <div className={cx('byline-form-actions-item', styles.item)}>
378
404
  <span className={cx('byline-form-actions-item-text', styles['item-text'])}>
379
- <button type="button">{t('common.actions.duplicate')}</button>
405
+ <button type="button" disabled={disabled}>
406
+ {t('common.actions.duplicate')}
407
+ </button>
380
408
  </span>
381
409
  </div>
382
410
  </DropdownComponent.Item>
@@ -385,6 +413,7 @@ export function DocumentActions({
385
413
  <>
386
414
  <DropdownComponent.Separator />
387
415
  <DropdownComponent.Item
416
+ disabled={disabled}
388
417
  onClick={() => {
389
418
  setShowDeleteConfirm(true)
390
419
  }}
@@ -454,7 +483,13 @@ export function DocumentActions({
454
483
  >
455
484
  {t('common.actions.cancel')}
456
485
  </Button>
457
- <Button size="sm" style={{ minWidth: '80px' }} intent="danger" onClick={handleOnDelete}>
486
+ <Button
487
+ size="sm"
488
+ style={{ minWidth: '80px' }}
489
+ intent="danger"
490
+ disabled={disabled}
491
+ onClick={handleOnDelete}
492
+ >
458
493
  {t('common.actions.delete')}
459
494
  </Button>
460
495
  </Modal.Actions>
@@ -533,7 +568,7 @@ export function DocumentActions({
533
568
  onClick={() => {
534
569
  if (!duplicateBusy) setShowDuplicateConfirm(false)
535
570
  }}
536
- disabled={duplicateBusy}
571
+ disabled={disabled || duplicateBusy}
537
572
  >
538
573
  {t('common.actions.cancel')}
539
574
  </Button>
@@ -542,7 +577,7 @@ export function DocumentActions({
542
577
  style={{ minWidth: '80px' }}
543
578
  intent="primary"
544
579
  onClick={handleOnDuplicate}
545
- disabled={duplicateBusy}
580
+ disabled={disabled || duplicateBusy}
546
581
  >
547
582
  {duplicateBusy
548
583
  ? t('documentActions.duplicate.busyButton')
@@ -611,7 +646,7 @@ export function DocumentActions({
611
646
  onValueChange={(value) => {
612
647
  if (value != null) setCopyTargetLocale(value)
613
648
  }}
614
- disabled={copyToLocaleBusy}
649
+ disabled={disabled || copyToLocaleBusy}
615
650
  />
616
651
  </div>
617
652
  <div
@@ -623,7 +658,7 @@ export function DocumentActions({
623
658
  name="overwrite"
624
659
  label={t('documentActions.copyToLocale.overwriteLabel')}
625
660
  checked={copyOverwrite}
626
- disabled={copyToLocaleBusy}
661
+ disabled={disabled || copyToLocaleBusy}
627
662
  helpText={t('documentActions.copyToLocale.overwriteHelp')}
628
663
  onCheckedChange={(value) => {
629
664
  setCopyOverwrite(value === true)
@@ -647,7 +682,7 @@ export function DocumentActions({
647
682
  onClick={() => {
648
683
  if (!copyToLocaleBusy) setShowCopyToLocaleConfirm(false)
649
684
  }}
650
- disabled={copyToLocaleBusy}
685
+ disabled={disabled || copyToLocaleBusy}
651
686
  >
652
687
  {t('common.actions.cancel')}
653
688
  </Button>
@@ -656,7 +691,7 @@ export function DocumentActions({
656
691
  style={{ minWidth: '80px' }}
657
692
  intent="primary"
658
693
  onClick={handleOnCopyToLocale}
659
- disabled={copyToLocaleBusy || !copyTargetLocale}
694
+ disabled={disabled || copyToLocaleBusy || !copyTargetLocale}
660
695
  >
661
696
  {copyToLocaleBusy
662
697
  ? t('documentActions.copyToLocale.busyButton')
@@ -711,7 +746,7 @@ export function DocumentActions({
711
746
  onValueChange={(value) => {
712
747
  if (value != null) setDeleteTargetLocale(value)
713
748
  }}
714
- disabled={deleteLocaleBusy}
749
+ disabled={disabled || deleteLocaleBusy}
715
750
  />
716
751
  </div>
717
752
  <p style={{ marginTop: 'var(--spacing-12)' }}>
@@ -734,7 +769,7 @@ export function DocumentActions({
734
769
  onClick={() => {
735
770
  if (!deleteLocaleBusy) setShowDeleteLocaleConfirm(false)
736
771
  }}
737
- disabled={deleteLocaleBusy}
772
+ disabled={disabled || deleteLocaleBusy}
738
773
  >
739
774
  {t('common.actions.cancel')}
740
775
  </Button>
@@ -743,7 +778,7 @@ export function DocumentActions({
743
778
  style={{ minWidth: '80px' }}
744
779
  intent="danger"
745
780
  onClick={handleOnDeleteLocale}
746
- disabled={deleteLocaleBusy || !deleteTargetLocale}
781
+ disabled={disabled || deleteLocaleBusy || !deleteTargetLocale}
747
782
  >
748
783
  {deleteLocaleBusy
749
784
  ? t('documentActions.deleteLocale.busyButton')