@byline/admin 3.12.1 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/fields/field-services-types.d.ts +50 -0
  2. package/dist/forms/form-context.js +30 -60
  3. package/dist/forms/form-modals.d.ts +30 -0
  4. package/dist/forms/form-modals.js +189 -0
  5. package/dist/forms/form-renderer.d.ts +8 -1
  6. package/dist/forms/form-renderer.js +31 -365
  7. package/dist/forms/form-status-display.d.ts +21 -0
  8. package/dist/forms/form-status-display.js +94 -0
  9. package/dist/forms/status-transitions.d.ts +23 -0
  10. package/dist/forms/status-transitions.js +37 -0
  11. package/dist/forms/status-transitions.test.node.d.ts +8 -0
  12. package/dist/forms/tree-placement-widget.d.ts +23 -0
  13. package/dist/forms/tree-placement-widget.js +176 -0
  14. package/dist/forms/tree-placement-widget.module.js +15 -0
  15. package/dist/forms/tree-placement-widget_module.css +69 -0
  16. package/dist/forms/use-form-layout.d.ts +35 -0
  17. package/dist/forms/use-form-layout.js +77 -0
  18. package/dist/forms/use-form-layout.test.node.d.ts +8 -0
  19. package/dist/forms/use-tracked-slot.d.ts +45 -0
  20. package/dist/forms/use-tracked-slot.js +46 -0
  21. package/dist/react.d.ts +1 -1
  22. package/package.json +5 -5
  23. package/src/fields/field-services-types.ts +50 -0
  24. package/src/forms/form-context.tsx +36 -92
  25. package/src/forms/form-modals.tsx +186 -0
  26. package/src/forms/form-renderer.tsx +42 -387
  27. package/src/forms/form-status-display.tsx +108 -0
  28. package/src/forms/status-transitions.test.node.ts +87 -0
  29. package/src/forms/status-transitions.ts +86 -0
  30. package/src/forms/tree-placement-widget.module.css +87 -0
  31. package/src/forms/tree-placement-widget.tsx +202 -0
  32. package/src/forms/use-form-layout.test.node.ts +82 -0
  33. package/src/forms/use-form-layout.ts +134 -0
  34. package/src/forms/use-tracked-slot.ts +101 -0
  35. package/src/react.ts +6 -0
@@ -1,146 +1,27 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
- import { Alert, Button, CloseIcon, ComboButton, IconButton, Modal } from "@byline/ui/react";
5
+ import { Alert, Button, ComboButton } from "@byline/ui/react";
6
6
  import classnames from "classnames";
7
7
  import { FieldRenderer } from "../fields/field-renderer.js";
8
8
  import { useBylineFieldServices } from "../fields/field-services-context.js";
9
- import { LocalDateTime } from "../fields/local-date-time.js";
10
9
  import { AdminGroup } from "../presentation/group.js";
11
10
  import { AdminRow } from "../presentation/row.js";
12
11
  import { AdminTabs } from "../presentation/tabs.js";
13
12
  import { AvailableLocalesWidget } from "./available-locales-widget.js";
14
13
  import { DocumentActions } from "./document-actions.js";
15
14
  import { FormProvider, useFieldValue, useFormContext } from "./form-context.js";
15
+ import { NavigationGuardModal, SystemFieldsConfirmModal, UnsavedChangesModal } from "./form-modals.js";
16
16
  import form_renderer_module from "./form-renderer.module.js";
17
+ import { FormStatusDisplay } from "./form-status-display.js";
17
18
  import { useNavigationGuardAdapter } from "./navigation-guard.js";
18
19
  import { PathWidget } from "./path-widget.js";
20
+ import { computeStatusTransitions } from "./status-transitions.js";
21
+ import { TreePlacementWidget } from "./tree-placement-widget.js";
19
22
  import { executeUploadsWithProgress } from "./upload-executor.js";
20
- const FormStatusDisplay = ({ initialData, workflowStatuses, publishedVersion, onUnpublish })=>{
21
- const { t } = useTranslation('byline-admin');
22
- const statusCode = initialData?.status;
23
- const statusLabel = workflowStatuses?.find((s)=>s.name === statusCode)?.label ?? statusCode;
24
- const showStatusCell = (workflowStatuses?.length ?? 0) > 1;
25
- return /*#__PURE__*/ jsxs("div", {
26
- className: classnames('byline-form-status', form_renderer_module.status),
27
- children: [
28
- /*#__PURE__*/ jsxs("div", {
29
- className: classnames('byline-form-status-meta', form_renderer_module["status-meta"]),
30
- children: [
31
- showStatusCell && /*#__PURE__*/ jsxs("div", {
32
- className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
33
- children: [
34
- /*#__PURE__*/ jsx("span", {
35
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
36
- children: t('forms.status.label')
37
- }),
38
- /*#__PURE__*/ jsx("span", {
39
- className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
40
- children: statusLabel
41
- })
42
- ]
43
- }),
44
- initialData?.updatedAt != null && /*#__PURE__*/ jsxs("div", {
45
- className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
46
- children: [
47
- /*#__PURE__*/ jsx("span", {
48
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
49
- children: t('forms.status.lastModified')
50
- }),
51
- /*#__PURE__*/ jsx("span", {
52
- className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
53
- children: /*#__PURE__*/ jsx(LocalDateTime, {
54
- value: initialData.updatedAt
55
- })
56
- })
57
- ]
58
- }),
59
- initialData?.createdAt != null && /*#__PURE__*/ jsxs("div", {
60
- className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
61
- children: [
62
- /*#__PURE__*/ jsx("span", {
63
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
64
- children: t('forms.status.created')
65
- }),
66
- /*#__PURE__*/ jsx("span", {
67
- className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
68
- children: /*#__PURE__*/ jsx(LocalDateTime, {
69
- value: initialData.createdAt
70
- })
71
- })
72
- ]
73
- })
74
- ]
75
- }),
76
- null != publishedVersion && /*#__PURE__*/ jsxs("div", {
77
- className: classnames('byline-form-status-published', form_renderer_module["status-published"]),
78
- children: [
79
- /*#__PURE__*/ jsxs("span", {
80
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
81
- children: [
82
- t('forms.status.publishedLive'),
83
- ' ',
84
- publishedVersion.updatedAt ? /*#__PURE__*/ jsx("span", {
85
- children: t('forms.status.publishedOn', {
86
- date: new Date(publishedVersion.updatedAt)
87
- })
88
- }) : ''
89
- ]
90
- }),
91
- onUnpublish && /*#__PURE__*/ jsxs(Fragment, {
92
- children: [
93
- ' ',
94
- /*#__PURE__*/ jsx("button", {
95
- type: "button",
96
- onClick: onUnpublish,
97
- className: classnames('byline-form-status-unpublish', form_renderer_module["status-unpublish"]),
98
- children: t('common.actions.unpublish')
99
- })
100
- ]
101
- })
102
- ]
103
- })
104
- ]
105
- });
106
- };
107
- function computeStatusTransitions(currentStatus, workflowStatuses, nextStatus) {
108
- if (!workflowStatuses || 0 === workflowStatuses.length || !currentStatus) return {
109
- primaryStatus: nextStatus,
110
- secondaryStatuses: [],
111
- isTerminal: false
112
- };
113
- if (workflowStatuses.length <= 1) return {
114
- primaryStatus: void 0,
115
- secondaryStatuses: [],
116
- isTerminal: false
117
- };
118
- const currentIndex = workflowStatuses.findIndex((s)=>s.name === currentStatus);
119
- if (-1 === currentIndex) return {
120
- primaryStatus: nextStatus,
121
- secondaryStatuses: [],
122
- isTerminal: false
123
- };
124
- const isAtEnd = currentIndex === workflowStatuses.length - 1;
125
- const isAtStart = 0 === currentIndex;
126
- const availableTargets = [];
127
- if (!isAtStart && workflowStatuses[0]) availableTargets.push(workflowStatuses[0]);
128
- const prev = workflowStatuses[currentIndex - 1];
129
- if (currentIndex > 1 && prev) availableTargets.push(prev);
130
- const next = workflowStatuses[currentIndex + 1];
131
- if (!isAtEnd && next) availableTargets.push(next);
132
- if (isAtEnd) return {
133
- primaryStatus: workflowStatuses[currentIndex],
134
- secondaryStatuses: availableTargets,
135
- isTerminal: true
136
- };
137
- return {
138
- primaryStatus: nextStatus,
139
- secondaryStatuses: availableTargets.filter((s)=>s.name !== nextStatus?.name),
140
- isTerminal: false
141
- };
142
- }
143
- const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, advertiseLocales, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
23
+ import { useFormLayout } from "./use-form-layout.js";
24
+ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
144
25
  const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getDirtyBreakdown, getSystemPath, getSystemAvailableLocales, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads, setFieldUploading } = useFormContext();
145
26
  const { t } = useTranslation('byline-admin');
146
27
  const [errors, setErrors] = useState(initialErrors);
@@ -156,69 +37,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
156
37
  }, [
157
38
  initialLocale
158
39
  ]);
159
- const fieldByName = useMemo(()=>{
160
- const map = new Map();
161
- for (const field of fields)if ('name' in field) map.set(field.name, field);
162
- return map;
163
- }, [
164
- fields
165
- ]);
166
- const tabSetByName = useMemo(()=>{
167
- const map = new Map();
168
- for (const set of adminConfig?.tabSets ?? [])map.set(set.name, set);
169
- return map;
170
- }, [
171
- adminConfig
172
- ]);
173
- const rowByName = useMemo(()=>{
174
- const map = new Map();
175
- for (const row of adminConfig?.rows ?? [])map.set(row.name, row);
176
- return map;
177
- }, [
178
- adminConfig
179
- ]);
180
- const groupByName = useMemo(()=>{
181
- const map = new Map();
182
- for (const group of adminConfig?.groups ?? [])map.set(group.name, group);
183
- return map;
184
- }, [
185
- adminConfig
186
- ]);
187
- const layout = useMemo(()=>{
188
- if (adminConfig?.layout) return adminConfig.layout;
189
- return {
190
- main: fields.filter((f)=>'name' in f).map((f)=>f.name)
191
- };
192
- }, [
193
- adminConfig,
194
- fields
195
- ]);
196
- const fieldToTabPath = useMemo(()=>{
197
- const map = new Map();
198
- const visit = (names, tabSetName, tabName, seen)=>{
199
- for (const name of names)if (fieldByName.has(name)) map.set(name, {
200
- tabSetName,
201
- tabName
202
- });
203
- else if (seen.has(name)) ;
204
- else if (rowByName.has(name)) {
205
- const row = rowByName.get(name);
206
- const next = new Set(seen).add(name);
207
- visit(row.fields, tabSetName, tabName, next);
208
- } else if (groupByName.has(name)) {
209
- const group = groupByName.get(name);
210
- const next = new Set(seen).add(name);
211
- visit(group.fields, tabSetName, tabName, next);
212
- }
213
- };
214
- for (const set of adminConfig?.tabSets ?? [])for (const tab of set.tabs)visit(tab.fields, set.name, tab.name, new Set());
215
- return map;
216
- }, [
217
- adminConfig,
218
- fieldByName,
219
- rowByName,
220
- groupByName
221
- ]);
40
+ const { fieldByName, tabSetByName, rowByName, groupByName, layout, fieldToTabPath } = useFormLayout(adminConfig, fields);
222
41
  const tabSets = adminConfig?.tabSets ?? [];
223
42
  const initialActiveTabBySet = useMemo(()=>{
224
43
  const result = {};
@@ -528,6 +347,11 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
528
347
  activeLocale: contentLocale,
529
348
  mode: mode
530
349
  }),
350
+ tree && 'edit' === mode && 'string' == typeof initialData?.id && /*#__PURE__*/ jsx(TreePlacementWidget, {
351
+ collectionPath: collectionPath ?? '',
352
+ documentId: initialData.id,
353
+ useAsTitle: useAsTitle
354
+ }),
531
355
  advertiseLocales && /*#__PURE__*/ jsx(AvailableLocalesWidget, {
532
356
  contentLocales: contentLocales ?? [],
533
357
  availableVersionLocales: initialData?._availableVersionLocales ?? []
@@ -537,187 +361,28 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
537
361
  })
538
362
  ]
539
363
  }),
540
- showUnsavedModal && /*#__PURE__*/ jsx(Modal, {
541
- isOpen: true,
542
- closeOnOverlayClick: true,
543
- onDismiss: ()=>setShowUnsavedModal(false),
544
- children: /*#__PURE__*/ jsxs(Modal.Container, {
545
- style: {
546
- maxWidth: '460px'
547
- },
548
- children: [
549
- /*#__PURE__*/ jsx(Modal.Header, {
550
- className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
551
- children: /*#__PURE__*/ jsx("h3", {
552
- className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
553
- children: t('forms.unsavedChanges.title')
554
- })
555
- }),
556
- /*#__PURE__*/ jsx(Modal.Content, {
557
- children: /*#__PURE__*/ jsx("p", {
558
- className: classnames('byline-form-guard-modal-text', form_renderer_module["guard-modal-text"]),
559
- children: t('forms.unsavedChanges.message')
560
- })
561
- }),
562
- /*#__PURE__*/ jsx(Modal.Actions, {
563
- children: /*#__PURE__*/ jsx(Button, {
564
- size: "sm",
565
- style: {
566
- minWidth: '60px'
567
- },
568
- intent: "primary",
569
- type: "button",
570
- onClick: ()=>setShowUnsavedModal(false),
571
- children: t('forms.unsavedChanges.okButton')
572
- })
573
- })
574
- ]
575
- })
364
+ showUnsavedModal && /*#__PURE__*/ jsx(UnsavedChangesModal, {
365
+ onClose: ()=>setShowUnsavedModal(false)
576
366
  }),
577
- null != pendingSystemFieldsSubmit && /*#__PURE__*/ jsx(Modal, {
578
- isOpen: true,
579
- closeOnOverlayClick: true,
580
- onDismiss: ()=>setPendingSystemFieldsSubmit(null),
581
- children: /*#__PURE__*/ jsxs(Modal.Container, {
582
- style: {
583
- maxWidth: '520px'
584
- },
585
- children: [
586
- /*#__PURE__*/ jsxs(Modal.Header, {
587
- className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
588
- children: [
589
- /*#__PURE__*/ jsx("h3", {
590
- className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
591
- children: pendingSystemFieldsSubmit.contentDirty ? t('forms.systemFieldsConfirm.bothTitle') : t('forms.systemFieldsConfirm.title')
592
- }),
593
- /*#__PURE__*/ jsx(IconButton, {
594
- "aria-label": t('common.actions.close'),
595
- size: "xs",
596
- onClick: ()=>setPendingSystemFieldsSubmit(null),
597
- children: /*#__PURE__*/ jsx(CloseIcon, {
598
- width: "16px",
599
- height: "16px",
600
- svgClassName: "white-icon"
601
- })
602
- })
603
- ]
604
- }),
605
- /*#__PURE__*/ jsxs(Modal.Content, {
606
- className: "prose",
607
- children: [
608
- pendingSystemFieldsSubmit.contentDirty && /*#__PURE__*/ jsx("p", {
609
- className: classnames('byline-form-system-fields-content-note', 'm-0 mt-2'),
610
- children: t('forms.systemFieldsConfirm.contentNote')
611
- }),
612
- /*#__PURE__*/ jsx("p", {
613
- className: "m-0 mt-2",
614
- style: pendingSystemFieldsSubmit.contentDirty ? {
615
- marginTop: 'var(--spacing-8)',
616
- paddingTop: 'var(--spacing-12)',
617
- borderTop: '1px solid var(--border-color)'
618
- } : void 0,
619
- children: t('forms.systemFieldsConfirm.intro')
620
- }),
621
- /*#__PURE__*/ jsxs("ul", {
622
- className: classnames('byline-form-system-fields-list', form_renderer_module["guard-modal-text"], 'm-0'),
623
- children: [
624
- pendingSystemFieldsSubmit.pathDirty && /*#__PURE__*/ jsx("li", {
625
- children: t('forms.systemFieldsConfirm.bulletPath')
626
- }),
627
- pendingSystemFieldsSubmit.availableLocalesDirty && /*#__PURE__*/ jsx("li", {
628
- children: t('forms.systemFieldsConfirm.bulletLocales')
629
- })
630
- ]
631
- }),
632
- /*#__PURE__*/ jsx("p", {
633
- className: classnames('byline-form-system-fields-effect', form_renderer_module["guard-modal-text"]),
634
- style: {
635
- marginTop: 'var(--spacing-4)',
636
- marginBottom: 0,
637
- color: 'var(--text-subtle)'
638
- },
639
- children: t('forms.systemFieldsConfirm.effectLine')
640
- })
641
- ]
642
- }),
643
- /*#__PURE__*/ jsxs(Modal.Actions, {
644
- children: [
645
- /*#__PURE__*/ jsx(Button, {
646
- size: "sm",
647
- style: {
648
- minWidth: '80px'
649
- },
650
- intent: "noeffect",
651
- type: "button",
652
- onClick: ()=>setPendingSystemFieldsSubmit(null),
653
- children: t('common.actions.cancel')
654
- }),
655
- /*#__PURE__*/ jsx(Button, {
656
- size: "sm",
657
- style: {
658
- minWidth: '80px'
659
- },
660
- intent: "primary",
661
- type: "button",
662
- onClick: ()=>{
663
- const payload = pendingSystemFieldsSubmit;
664
- setPendingSystemFieldsSubmit(null);
665
- submitPayload(payload);
666
- },
667
- children: t('forms.systemFieldsConfirm.confirmButton')
668
- })
669
- ]
670
- })
671
- ]
672
- })
367
+ null != pendingSystemFieldsSubmit && /*#__PURE__*/ jsx(SystemFieldsConfirmModal, {
368
+ contentDirty: pendingSystemFieldsSubmit.contentDirty,
369
+ pathDirty: pendingSystemFieldsSubmit.pathDirty,
370
+ availableLocalesDirty: pendingSystemFieldsSubmit.availableLocalesDirty,
371
+ onCancel: ()=>setPendingSystemFieldsSubmit(null),
372
+ onConfirm: ()=>{
373
+ const payload = pendingSystemFieldsSubmit;
374
+ setPendingSystemFieldsSubmit(null);
375
+ submitPayload(payload);
376
+ }
673
377
  }),
674
- guard.isBlocked && /*#__PURE__*/ jsx(Modal, {
675
- isOpen: true,
676
- closeOnOverlayClick: false,
677
- onDismiss: guard.stay,
678
- children: /*#__PURE__*/ jsxs(Modal.Container, {
679
- style: {
680
- maxWidth: '460px'
681
- },
682
- children: [
683
- /*#__PURE__*/ jsx(Modal.Header, {
684
- className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
685
- children: /*#__PURE__*/ jsx("h3", {
686
- className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
687
- children: t('forms.navigationGuard.title')
688
- })
689
- }),
690
- /*#__PURE__*/ jsx(Modal.Content, {
691
- children: /*#__PURE__*/ jsx("p", {
692
- className: classnames('byline-form-guard-modal-text', form_renderer_module["guard-modal-text"]),
693
- children: t('forms.navigationGuard.message')
694
- })
695
- }),
696
- /*#__PURE__*/ jsxs(Modal.Actions, {
697
- children: [
698
- /*#__PURE__*/ jsx(Button, {
699
- size: "sm",
700
- intent: "noeffect",
701
- type: "button",
702
- onClick: guard.stay,
703
- children: t('forms.navigationGuard.stayButton')
704
- }),
705
- /*#__PURE__*/ jsx(Button, {
706
- size: "sm",
707
- intent: "danger",
708
- type: "button",
709
- onClick: guard.proceed,
710
- children: t('forms.navigationGuard.leaveButton')
711
- })
712
- ]
713
- })
714
- ]
715
- })
378
+ guard.isBlocked && /*#__PURE__*/ jsx(NavigationGuardModal, {
379
+ onStay: guard.stay,
380
+ onProceed: guard.proceed
716
381
  })
717
382
  ]
718
383
  });
719
384
  };
720
- const FormRenderer = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, advertiseLocales, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard, restoreWarnings })=>{
385
+ const FormRenderer = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard, restoreWarnings })=>{
721
386
  const savedTabsRef = useRef({});
722
387
  return /*#__PURE__*/ jsx(FormProvider, {
723
388
  initialData: initialData,
@@ -741,6 +406,7 @@ const FormRenderer = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpu
741
406
  useAsTitle: useAsTitle,
742
407
  useAsPath: useAsPath,
743
408
  advertiseLocales: advertiseLocales,
409
+ tree: tree,
744
410
  headingLabel: headingLabel,
745
411
  headerSlot: headerSlot,
746
412
  collectionPath: collectionPath,
@@ -0,0 +1,21 @@
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
+ import type { WorkflowStatus } from '@byline/core';
9
+ import type { PublishedVersionInfo } from './form-renderer';
10
+ /**
11
+ * The status / metadata strip at the top of a document form — current
12
+ * workflow status (suppressed for single-status workflows), last-modified and
13
+ * created timestamps, and a "published live" indicator with an inline
14
+ * Unpublish action when a previously-published version is still live.
15
+ */
16
+ export declare const FormStatusDisplay: ({ initialData, workflowStatuses, publishedVersion, onUnpublish, }: {
17
+ initialData?: Record<string, any>;
18
+ workflowStatuses?: WorkflowStatus[];
19
+ publishedVersion?: PublishedVersionInfo | null;
20
+ onUnpublish?: () => Promise<void>;
21
+ }) => import("react").JSX.Element;
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "@byline/i18n/react";
4
+ import classnames from "classnames";
5
+ import { LocalDateTime } from "../fields/local-date-time.js";
6
+ import form_renderer_module from "./form-renderer.module.js";
7
+ const FormStatusDisplay = ({ initialData, workflowStatuses, publishedVersion, onUnpublish })=>{
8
+ const { t } = useTranslation('byline-admin');
9
+ const statusCode = initialData?.status;
10
+ const statusLabel = workflowStatuses?.find((s)=>s.name === statusCode)?.label ?? statusCode;
11
+ const showStatusCell = (workflowStatuses?.length ?? 0) > 1;
12
+ return /*#__PURE__*/ jsxs("div", {
13
+ className: classnames('byline-form-status', form_renderer_module.status),
14
+ children: [
15
+ /*#__PURE__*/ jsxs("div", {
16
+ className: classnames('byline-form-status-meta', form_renderer_module["status-meta"]),
17
+ children: [
18
+ showStatusCell && /*#__PURE__*/ jsxs("div", {
19
+ className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
20
+ children: [
21
+ /*#__PURE__*/ jsx("span", {
22
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
23
+ children: t('forms.status.label')
24
+ }),
25
+ /*#__PURE__*/ jsx("span", {
26
+ className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
27
+ children: statusLabel
28
+ })
29
+ ]
30
+ }),
31
+ initialData?.updatedAt != null && /*#__PURE__*/ jsxs("div", {
32
+ className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
33
+ children: [
34
+ /*#__PURE__*/ jsx("span", {
35
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
36
+ children: t('forms.status.lastModified')
37
+ }),
38
+ /*#__PURE__*/ jsx("span", {
39
+ className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
40
+ children: /*#__PURE__*/ jsx(LocalDateTime, {
41
+ value: initialData.updatedAt
42
+ })
43
+ })
44
+ ]
45
+ }),
46
+ initialData?.createdAt != null && /*#__PURE__*/ jsxs("div", {
47
+ className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
48
+ children: [
49
+ /*#__PURE__*/ jsx("span", {
50
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
51
+ children: t('forms.status.created')
52
+ }),
53
+ /*#__PURE__*/ jsx("span", {
54
+ className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
55
+ children: /*#__PURE__*/ jsx(LocalDateTime, {
56
+ value: initialData.createdAt
57
+ })
58
+ })
59
+ ]
60
+ })
61
+ ]
62
+ }),
63
+ null != publishedVersion && /*#__PURE__*/ jsxs("div", {
64
+ className: classnames('byline-form-status-published', form_renderer_module["status-published"]),
65
+ children: [
66
+ /*#__PURE__*/ jsxs("span", {
67
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
68
+ children: [
69
+ t('forms.status.publishedLive'),
70
+ ' ',
71
+ publishedVersion.updatedAt ? /*#__PURE__*/ jsx("span", {
72
+ children: t('forms.status.publishedOn', {
73
+ date: new Date(publishedVersion.updatedAt)
74
+ })
75
+ }) : ''
76
+ ]
77
+ }),
78
+ onUnpublish && /*#__PURE__*/ jsxs(Fragment, {
79
+ children: [
80
+ ' ',
81
+ /*#__PURE__*/ jsx("button", {
82
+ type: "button",
83
+ onClick: onUnpublish,
84
+ className: classnames('byline-form-status-unpublish', form_renderer_module["status-unpublish"]),
85
+ children: t('common.actions.unpublish')
86
+ })
87
+ ]
88
+ })
89
+ ]
90
+ })
91
+ ]
92
+ });
93
+ };
94
+ export { FormStatusDisplay };
@@ -0,0 +1,23 @@
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
+ import type { WorkflowStatus } from '@byline/core';
9
+ export interface StatusTransitions {
10
+ primaryStatus: WorkflowStatus | undefined;
11
+ secondaryStatuses: WorkflowStatus[];
12
+ isTerminal: boolean;
13
+ }
14
+ /**
15
+ * Compute the primary and secondary status transitions for the ComboButton.
16
+ * - Primary: the main action (forward step), or the current status itself
17
+ * when the document has reached the final workflow step (terminal state).
18
+ * - Secondary: other available transitions to show as dropdown options.
19
+ * - isTerminal: true when the document is at the final workflow status —
20
+ * the primary button renders as a non-actionable indicator and all
21
+ * back-steps move into the dropdown.
22
+ */
23
+ export declare function computeStatusTransitions(currentStatus: string | undefined, workflowStatuses: WorkflowStatus[] | undefined, nextStatus: WorkflowStatus | undefined): StatusTransitions;
@@ -0,0 +1,37 @@
1
+ function computeStatusTransitions(currentStatus, workflowStatuses, nextStatus) {
2
+ if (!workflowStatuses || 0 === workflowStatuses.length || !currentStatus) return {
3
+ primaryStatus: nextStatus,
4
+ secondaryStatuses: [],
5
+ isTerminal: false
6
+ };
7
+ if (workflowStatuses.length <= 1) return {
8
+ primaryStatus: void 0,
9
+ secondaryStatuses: [],
10
+ isTerminal: false
11
+ };
12
+ const currentIndex = workflowStatuses.findIndex((s)=>s.name === currentStatus);
13
+ if (-1 === currentIndex) return {
14
+ primaryStatus: nextStatus,
15
+ secondaryStatuses: [],
16
+ isTerminal: false
17
+ };
18
+ const isAtEnd = currentIndex === workflowStatuses.length - 1;
19
+ const isAtStart = 0 === currentIndex;
20
+ const availableTargets = [];
21
+ if (!isAtStart && workflowStatuses[0]) availableTargets.push(workflowStatuses[0]);
22
+ const prev = workflowStatuses[currentIndex - 1];
23
+ if (currentIndex > 1 && prev) availableTargets.push(prev);
24
+ const next = workflowStatuses[currentIndex + 1];
25
+ if (!isAtEnd && next) availableTargets.push(next);
26
+ if (isAtEnd) return {
27
+ primaryStatus: workflowStatuses[currentIndex],
28
+ secondaryStatuses: availableTargets,
29
+ isTerminal: true
30
+ };
31
+ return {
32
+ primaryStatus: nextStatus,
33
+ secondaryStatuses: availableTargets.filter((s)=>s.name !== nextStatus?.name),
34
+ isTerminal: false
35
+ };
36
+ }
37
+ export { computeStatusTransitions };
@@ -0,0 +1,8 @@
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
+ export {};