@rebasepro/admin 0.13.1-canary.gef9608c → 0.14.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.
- package/dist/{CollectionEditorDialog-7b-kSLno.js → CollectionEditorDialog-CSheud_E.js} +19 -9
- package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
- package/dist/{PropertyEditView-C-31uvpG.js → PropertyEditView-CWZlC1gq.js} +5 -5
- package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-BJylABq3.js → RouterCollectionsStudioView-CwTpdzWF.js} +9 -13
- package/dist/RouterCollectionsStudioView-CwTpdzWF.js.map +1 -0
- package/dist/collection_editor/serializable_types.d.ts +1 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/ClearFilterSortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
- package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
- package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +101 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +26 -0
- package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -6
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +1 -1
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +26 -1
- package/dist/components/DetailViewBinding.d.ts +7 -0
- package/dist/components/DrawerNavigationGroup.d.ts +4 -2
- package/dist/components/DrawerNavigationItem.d.ts +13 -5
- package/dist/components/EditViewBinding.d.ts +22 -7
- package/dist/components/EntityIdentityBar.d.ts +24 -1
- package/dist/components/RelationSelector.d.ts +13 -0
- package/dist/data_export/export/export.d.ts +7 -0
- package/dist/data_export/export/fetch_export_data.d.ts +48 -0
- package/dist/data_export/export/index.d.ts +1 -0
- package/dist/data_import/utils/csv.d.ts +30 -0
- package/dist/data_import/utils/index.d.ts +2 -0
- package/dist/data_import/utils/save_entities.d.ts +38 -0
- package/dist/data_import/utils/transforms.d.ts +9 -0
- package/dist/editor.js +3 -0
- package/dist/editor.js.map +1 -1
- package/dist/{export-C-XG3aRh.js → export-L53WektO.js} +175 -39
- package/dist/export-L53WektO.js.map +1 -0
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-DfMuvPfr.js → history-D5SKygpJ.js} +2 -2
- package/dist/{history-DfMuvPfr.js.map → history-D5SKygpJ.js.map} +1 -1
- package/dist/{import-B0GyllB0.js → import-C_4edkoD.js} +5 -4
- package/dist/import-C_4edkoD.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +96 -80
- package/dist/index.js.map +1 -1
- package/dist/preview/components/date_fns_locales.d.ts +9 -0
- package/dist/util/view_constants.d.ts +24 -0
- package/dist/util/view_mode.d.ts +18 -0
- package/dist/{util-DHNXodBo.js → util-C-D5yD2n.js} +2173 -673
- package/dist/util-C-D5yD2n.js.map +1 -0
- package/package.json +10 -9
- package/src/collection_editor/serializable_types.ts +1 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +8 -3
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +9 -8
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -0
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +92 -84
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
- package/src/components/ClearFilterSortButton.tsx +16 -7
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +2 -2
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
- package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +18 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +816 -335
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +26 -6
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +45 -10
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +311 -0
- package/src/components/CollectionViewBinding/SlotValue.tsx +96 -0
- package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -9
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +3 -3
- package/src/components/CollectionViewBinding/SplitListView.tsx +36 -9
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +11 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +76 -24
- package/src/components/CollectionViewBinding/utils.ts +7 -4
- package/src/components/DefaultDrawer.tsx +7 -0
- package/src/components/DetailViewBinding.tsx +33 -9
- package/src/components/DrawerNavigationGroup.tsx +12 -32
- package/src/components/DrawerNavigationItem.tsx +33 -12
- package/src/components/EditViewBinding.tsx +175 -28
- package/src/components/EntityIdentityBar.tsx +109 -17
- package/src/components/EntityInspector.tsx +24 -6
- package/src/components/EntityViewBinding.tsx +4 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +61 -11
- package/src/components/SearchIconsView.tsx +14 -33
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
- package/src/components/SidePanelBinding.tsx +36 -25
- package/src/components/UserSelector.tsx +52 -9
- package/src/components/history/LastEditedByIndicator.tsx +7 -12
- package/src/data_export/export/ExportCollectionAction.tsx +101 -48
- package/src/data_export/export/export.ts +63 -16
- package/src/data_export/export/fetch_export_data.ts +90 -0
- package/src/data_export/export/index.ts +1 -0
- package/src/data_import/components/DataNewPropertiesMapping.tsx +20 -3
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -0
- package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
- package/src/data_import/import/ImportCollectionAction.tsx +13 -2
- package/src/data_import/utils/csv.ts +153 -0
- package/src/data_import/utils/data.ts +16 -2
- package/src/data_import/utils/file_to_json.ts +77 -6
- package/src/data_import/utils/index.ts +2 -0
- package/src/data_import/utils/save_entities.ts +138 -0
- package/src/data_import/utils/transforms.ts +23 -0
- package/src/editor/components/image-bubble.tsx +2 -0
- package/src/editor/selectors/link-selector.tsx +1 -0
- package/src/form/EntityForm.tsx +12 -3
- package/src/form/EntityFormBinding.tsx +8 -12
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +1 -0
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +15 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
- package/src/form/field_bindings/TextFieldBinding.tsx +18 -0
- package/src/form/field_bindings/VectorFieldBinding.tsx +1 -0
- package/src/form/form_utils.ts +78 -0
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +7 -3
- package/src/preview/components/DatePreview.tsx +15 -2
- package/src/preview/components/ReferencePreview.tsx +5 -8
- package/src/preview/components/RelationPreview.tsx +8 -7
- package/src/preview/components/date_fns_locales.ts +125 -0
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/util/navigation_utils.ts +4 -2
- package/src/util/property_utils.tsx +6 -1
- package/src/util/view_constants.ts +27 -0
- package/src/util/view_mode.ts +36 -0
- package/dist/CollectionEditorDialog-7b-kSLno.js.map +0 -1
- package/dist/PropertyEditView-C-31uvpG.js.map +0 -1
- package/dist/RouterCollectionsStudioView-BJylABq3.js.map +0 -1
- package/dist/export-C-XG3aRh.js.map +0 -1
- package/dist/import-B0GyllB0.js.map +0 -1
- package/dist/util-DHNXodBo.js.map +0 -1
|
@@ -49,7 +49,14 @@ import { useNavigate } from "react-router";
|
|
|
49
49
|
import { useEntityDisplayTitle } from "../hooks/useEntityDisplayTitle";
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
MAIN_TAB_VALUE,
|
|
54
|
+
JSON_TAB_VALUE,
|
|
55
|
+
HISTORY_TAB_VALUE,
|
|
56
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
57
|
+
ENTITY_VIEW_TAB_CLASS,
|
|
58
|
+
ENTITY_VIEW_TAB_LABEL_CLASS
|
|
59
|
+
} from "../util/view_constants";
|
|
53
60
|
|
|
54
61
|
export type BarActionsParams = {
|
|
55
62
|
values: object,
|
|
@@ -76,6 +83,13 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
|
|
|
76
83
|
parentEntityIds: string[];
|
|
77
84
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
78
85
|
onEditClick?: () => void;
|
|
86
|
+
/**
|
|
87
|
+
* Dismiss this record — the ✕ at the identity bar's trailing edge.
|
|
88
|
+
*
|
|
89
|
+
* Set by the layouts that own the record's place on screen but are not side
|
|
90
|
+
* dialogs: the split, where dismissing is navigating back to the collection.
|
|
91
|
+
*/
|
|
92
|
+
onCloseRequest?: () => void;
|
|
79
93
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
80
94
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
81
95
|
/**
|
|
@@ -144,6 +158,7 @@ function DetailViewBindingInner<M extends Record<string, unknown>>({
|
|
|
144
158
|
parentEntityIds,
|
|
145
159
|
onTabChange,
|
|
146
160
|
onEditClick,
|
|
161
|
+
onCloseRequest,
|
|
147
162
|
entity,
|
|
148
163
|
dataLoading,
|
|
149
164
|
layout = "full_screen",
|
|
@@ -197,7 +212,12 @@ entityId }
|
|
|
197
212
|
}
|
|
198
213
|
}, [selectedTabProp, defaultSelectedView]);
|
|
199
214
|
|
|
200
|
-
|
|
215
|
+
// `hideFromEntityViews`, not `hideFromNavigation`: the latter governs the
|
|
216
|
+
// drawer. Filtering tabs on it defeated the introspection generator, which
|
|
217
|
+
// sets `hideFromNavigation` on owned-child tables precisely *because* "every
|
|
218
|
+
// inbound foreign key renders as a tab on the parent" — so the rows it
|
|
219
|
+
// deliberately kept out of the drawer became reachable from nowhere.
|
|
220
|
+
const childViews = getAdminEntityChildViews(collection).filter(v => !v.collection.hideFromEntityViews);
|
|
201
221
|
const subcollections = childViews.map(v => v.collection);
|
|
202
222
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
203
223
|
const customViews = collection.entityViews ?? [];
|
|
@@ -387,9 +407,10 @@ entityId }
|
|
|
387
407
|
// centred column the record used to be given here; the default display
|
|
388
408
|
// now lays itself out, rail included.
|
|
389
409
|
if (FormViewBuilder && usedEntity) {
|
|
390
|
-
return <div className={"flex-1 min-w-0 overflow-y-auto flex justify-center items-start"}>
|
|
410
|
+
return <div className={"@container/form flex-1 min-w-0 overflow-y-auto flex justify-center items-start"}>
|
|
391
411
|
<div className={cls(
|
|
392
|
-
|
|
412
|
+
// Same widths as the default form (see EntityForm).
|
|
413
|
+
"w-full max-w-3xl @7xl/form:max-w-4xl 2xl:max-w-4xl flex flex-col",
|
|
393
414
|
layout === "dialog"
|
|
394
415
|
? "pt-5 pb-12 px-6 sm:px-8"
|
|
395
416
|
: "pt-6 pb-16 px-5 sm:px-8"
|
|
@@ -429,7 +450,7 @@ entityId }
|
|
|
429
450
|
const icon = getIcon(subcollection.icon, undefined, undefined, "smallest");
|
|
430
451
|
return (
|
|
431
452
|
<Tab
|
|
432
|
-
className=
|
|
453
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
433
454
|
value={subcollection.slug}
|
|
434
455
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
435
456
|
<span className="flex items-center gap-1.5">
|
|
@@ -445,7 +466,7 @@ entityId }
|
|
|
445
466
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
446
467
|
return (
|
|
447
468
|
<Tab
|
|
448
|
-
className={!view.tabComponent
|
|
469
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
449
470
|
value={view.key}
|
|
450
471
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
451
472
|
{view.tabComponent ?? (
|
|
@@ -462,7 +483,7 @@ entityId }
|
|
|
462
483
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
463
484
|
return (
|
|
464
485
|
<Tab
|
|
465
|
-
className={!view.tabComponent
|
|
486
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
466
487
|
value={view.key}
|
|
467
488
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
468
489
|
{view.tabComponent ?? (
|
|
@@ -551,6 +572,7 @@ entityId }
|
|
|
551
572
|
dirty={false}
|
|
552
573
|
saving={false}
|
|
553
574
|
onBack={backToCollection}
|
|
575
|
+
onClose={onCloseRequest}
|
|
554
576
|
onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
|
|
555
577
|
onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
|
|
556
578
|
externalLink={usedEntity
|
|
@@ -580,10 +602,11 @@ entityId }
|
|
|
580
602
|
|
|
581
603
|
{hasAdditionalViews && <div className={cls(
|
|
582
604
|
"h-10 shrink-0 flex items-stretch border-b px-2 min-w-0",
|
|
605
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
583
606
|
defaultBorderMixin
|
|
584
607
|
)}>
|
|
585
608
|
<Tabs
|
|
586
|
-
className={"!w-full"}
|
|
609
|
+
className={"!w-full items-stretch"}
|
|
587
610
|
innerClassName={"h-full"}
|
|
588
611
|
variant={"boxy"}
|
|
589
612
|
value={activeTab}
|
|
@@ -591,7 +614,8 @@ entityId }
|
|
|
591
614
|
onSideTabClick(value);
|
|
592
615
|
}}>
|
|
593
616
|
|
|
594
|
-
<Tab value={MAIN_TAB_VALUE}
|
|
617
|
+
<Tab value={MAIN_TAB_VALUE}
|
|
618
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}>
|
|
595
619
|
<span className="flex items-center gap-1.5">
|
|
596
620
|
{getIcon(collection.icon, undefined, undefined, "smallest")}
|
|
597
621
|
{collection.singularName ?? collection.name}
|
|
@@ -51,8 +51,10 @@ export interface DrawerNavigationGroupProps {
|
|
|
51
51
|
/**
|
|
52
52
|
* Lucide icon name for the group header, from `NavigationGroupMapping.icon`.
|
|
53
53
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* It decorates the header and nothing else: the entries below are untouched and
|
|
55
|
+
* keep their own icons. An app that wants the categorised look — rows giving up
|
|
56
|
+
* their icons to indent under the group — builds it in its own drawer, by
|
|
57
|
+
* overriding `Shell.DrawerNavigationItem` and passing `indented`.
|
|
56
58
|
*/
|
|
57
59
|
icon?: string;
|
|
58
60
|
}
|
|
@@ -75,17 +77,13 @@ export function DrawerNavigationGroup({
|
|
|
75
77
|
const { t } = useTranslation();
|
|
76
78
|
const ResolvedDrawerNavigationItem = useComponentOverride("Shell.DrawerNavigationItem", DrawerNavigationItem);
|
|
77
79
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// nothing to indent under, and collapsed to a rail the entry icons are the only
|
|
85
|
-
// affordance left to click.
|
|
80
|
+
// The icon decorates the header, and stops there. It used to also strip the
|
|
81
|
+
// entries of theirs and indent them, which made a per-app styling choice into
|
|
82
|
+
// framework behaviour: every project that labelled a group with an icon lost the
|
|
83
|
+
// icons on its rows, with nothing to turn it off. An app that wants that look
|
|
84
|
+
// now opts into it in its own drawer — see `indented` on
|
|
85
|
+
// {@link DrawerNavigationItem}.
|
|
86
86
|
const groupIcon = !hideHeader && drawerOpen ? getIcon(icon, undefined, undefined, "small") : undefined;
|
|
87
|
-
const categorised = Boolean(groupIcon);
|
|
88
|
-
const indentEntries = categorised;
|
|
89
87
|
return (
|
|
90
88
|
<div
|
|
91
89
|
className={"my-2 mx-2 flex flex-col"}
|
|
@@ -102,10 +100,7 @@ export function DrawerNavigationGroup({
|
|
|
102
100
|
<ChevronDownIcon
|
|
103
101
|
size={iconSize.small}
|
|
104
102
|
className={cls(
|
|
105
|
-
|
|
106
|
-
? "text-surface-500 dark:text-surface-400"
|
|
107
|
-
: "text-surface-400 dark:text-surface-400",
|
|
108
|
-
"transition-transform duration-200 mr-1",
|
|
103
|
+
"text-surface-400 dark:text-surface-400 transition-transform duration-200 mr-1",
|
|
109
104
|
collapsed ? "-rotate-90" : "rotate-0"
|
|
110
105
|
)}
|
|
111
106
|
/>
|
|
@@ -117,21 +112,7 @@ export function DrawerNavigationGroup({
|
|
|
117
112
|
<Typography
|
|
118
113
|
variant={"caption"}
|
|
119
114
|
color={"secondary"}
|
|
120
|
-
|
|
121
|
-
// itself. Without an icon this is byte-for-byte the original: the
|
|
122
|
-
// drawer is a flat list of same-weight rows, and a louder header
|
|
123
|
-
// would just compete with them.
|
|
124
|
-
//
|
|
125
|
-
// With one, the group becomes the thing you scan — its rows have
|
|
126
|
-
// given up their icons to indent beneath it — so the label steps up
|
|
127
|
-
// to match. At 11px in surface-400 it sat *below* the contrast of
|
|
128
|
-
// the rows it labels, which is backwards once it is the anchor.
|
|
129
|
-
className={cls(
|
|
130
|
-
"font-semibold uppercase flex-grow line-clamp-1",
|
|
131
|
-
categorised
|
|
132
|
-
? "text-[12px] tracking-wide text-surface-600 dark:text-surface-300"
|
|
133
|
-
: "text-[11px] tracking-wider text-surface-400 dark:text-surface-400"
|
|
134
|
-
)}
|
|
115
|
+
className="font-semibold text-[11px] uppercase tracking-wider flex-grow line-clamp-1 text-surface-400 dark:text-surface-400"
|
|
135
116
|
>
|
|
136
117
|
{(group || t("views_group"))}
|
|
137
118
|
</Typography>
|
|
@@ -161,7 +142,6 @@ export function DrawerNavigationGroup({
|
|
|
161
142
|
onClick={() => onItemClick?.(entry)}
|
|
162
143
|
url={entry.url}
|
|
163
144
|
name={entry.name}
|
|
164
|
-
indented={indentEntries}
|
|
165
145
|
/>
|
|
166
146
|
))}
|
|
167
147
|
</div>
|
|
@@ -3,14 +3,11 @@ import React from "react";
|
|
|
3
3
|
import { NavLink } from "react-router";
|
|
4
4
|
import { cls, Tooltip } from "@rebasepro/ui";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
onClick,
|
|
12
|
-
indented = false
|
|
13
|
-
}: {
|
|
6
|
+
/**
|
|
7
|
+
* Props of a drawer navigation row, named so an app overriding
|
|
8
|
+
* `Shell.DrawerNavigationItem` can type its wrapper against them.
|
|
9
|
+
*/
|
|
10
|
+
export type DrawerNavigationItemProps = {
|
|
14
11
|
icon: React.ReactElement,
|
|
15
12
|
name: string,
|
|
16
13
|
/** @deprecated No longer read: the tooltip is uncontrolled. */
|
|
@@ -21,12 +18,24 @@ export function DrawerNavigationItem({
|
|
|
21
18
|
url: string,
|
|
22
19
|
onClick?: () => void,
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Drop the row's icon and indent the label into the space it occupied, so the
|
|
22
|
+
* row reads as a child of the group header above it.
|
|
23
|
+
*
|
|
24
|
+
* Nothing in the framework sets this: the stock drawer always passes `false`,
|
|
25
|
+
* and an app that wants the categorised look opts into it by overriding
|
|
26
|
+
* `Shell.DrawerNavigationItem` and deciding for itself which rows get it.
|
|
27
27
|
*/
|
|
28
28
|
indented?: boolean,
|
|
29
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function DrawerNavigationItem({
|
|
32
|
+
name,
|
|
33
|
+
icon,
|
|
34
|
+
drawerOpen,
|
|
35
|
+
url,
|
|
36
|
+
onClick,
|
|
37
|
+
indented = false
|
|
38
|
+
}: DrawerNavigationItemProps) {
|
|
30
39
|
|
|
31
40
|
// Indented rows give up the icon rather than keeping it *and* indenting: a
|
|
32
41
|
// per-row icon repeated down a group is what flattens the hierarchy, since
|
|
@@ -52,6 +61,18 @@ export function DrawerNavigationItem({
|
|
|
52
61
|
const listItem = <div>
|
|
53
62
|
<NavLink
|
|
54
63
|
onClick={onClick}
|
|
64
|
+
// The label below is `hidden` (display:none) while the rail is
|
|
65
|
+
// collapsed, which takes it out of the accessibility tree along with
|
|
66
|
+
// the row's only text — a collapsed rail was a column of links with no
|
|
67
|
+
// accessible name at all. The tooltip does not cover this: Radix wires
|
|
68
|
+
// a trigger to its content with `aria-describedby`, which is a
|
|
69
|
+
// description rather than a name, and only while the tooltip is open —
|
|
70
|
+
// and this one is masked shut whenever the drawer is open.
|
|
71
|
+
//
|
|
72
|
+
// Named unconditionally rather than only when collapsed: expanded, this
|
|
73
|
+
// is the same string the row already displays, so the accessible name
|
|
74
|
+
// stays stable across the transition instead of appearing with it.
|
|
75
|
+
aria-label={name}
|
|
55
76
|
style={{
|
|
56
77
|
width: "100%",
|
|
57
78
|
transition: drawerOpen ? "width 150ms ease-in" : undefined
|
|
@@ -38,8 +38,9 @@ import {
|
|
|
38
38
|
useSlot,
|
|
39
39
|
getIcon
|
|
40
40
|
} from "@rebasepro/app";
|
|
41
|
-
import { getEntityFromMemoryCache } from "@rebasepro/app";
|
|
41
|
+
import { getEntityFromMemoryCache, removeEntityFromMemoryCache, saveEntityToMemoryCache } from "@rebasepro/app";
|
|
42
42
|
import { EntityFormBinding } from "../form";
|
|
43
|
+
import { getEditHandoffValues } from "../form/form_utils";
|
|
43
44
|
import type { EntityFormBindingProps } from "../form";
|
|
44
45
|
import type { OnUpdateParams } from "../types/components/EntityFormProps";
|
|
45
46
|
import { EditFormActions } from "./EditFormActions";
|
|
@@ -54,10 +55,17 @@ import { useEntityDisplayTitle } from "../hooks/useEntityDisplayTitle";
|
|
|
54
55
|
import { createFormexStub, getEntityFromCache } from "@rebasepro/app";
|
|
55
56
|
import { usePermissions } from "@rebasepro/app";
|
|
56
57
|
import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
|
|
57
|
-
import {
|
|
58
|
+
import { withListState } from "../util/view_mode";
|
|
58
59
|
import { useNavigate } from "react-router";
|
|
59
60
|
|
|
60
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
MAIN_TAB_VALUE,
|
|
63
|
+
JSON_TAB_VALUE,
|
|
64
|
+
HISTORY_TAB_VALUE,
|
|
65
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
66
|
+
ENTITY_VIEW_TAB_CLASS,
|
|
67
|
+
ENTITY_VIEW_TAB_LABEL_CLASS
|
|
68
|
+
} from "../util/view_constants";
|
|
61
69
|
export { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE };
|
|
62
70
|
|
|
63
71
|
export type BarActionsParams = {
|
|
@@ -65,14 +73,19 @@ export type BarActionsParams = {
|
|
|
65
73
|
status: EntityStatus,
|
|
66
74
|
path: string,
|
|
67
75
|
entityId?: string | number;
|
|
76
|
+
/** Whether `values` differ from what is stored. */
|
|
77
|
+
dirty: boolean;
|
|
68
78
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
79
|
+
* Hand the edit in progress to the layout this action is about to open, so
|
|
80
|
+
* the record arrives showing — and still holding — what the user typed here.
|
|
81
|
+
* Call it from any action that changes layout without leaving the record;
|
|
82
|
+
* it decides on its own whether there is anything worth carrying, so a
|
|
83
|
+
* record nobody edited still arrives clean.
|
|
84
|
+
*
|
|
85
|
+
* Only supplied where there is a form to carry: the detail view passes no
|
|
86
|
+
* such thing.
|
|
74
87
|
*/
|
|
75
|
-
|
|
88
|
+
carryEdit?: () => void;
|
|
76
89
|
};
|
|
77
90
|
|
|
78
91
|
export type OnTabChangeParams<M extends Record<string, unknown>> = {
|
|
@@ -98,6 +111,16 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
98
111
|
onSaved?: (params: OnUpdateParams) => void;
|
|
99
112
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
100
113
|
navigateBack?: () => void;
|
|
114
|
+
/**
|
|
115
|
+
* Dismiss this record — the ✕ at the identity bar's trailing edge, and what
|
|
116
|
+
* "save and close" reaches once the save lands.
|
|
117
|
+
*
|
|
118
|
+
* Set by the layouts that own the record's place on screen but are not side
|
|
119
|
+
* dialogs: the split, where dismissing is navigating back to the collection.
|
|
120
|
+
* The side panel and the dialog close through {@link useSideDialogContext}
|
|
121
|
+
* instead, which is what runs their unsaved-changes prompt.
|
|
122
|
+
*/
|
|
123
|
+
onCloseRequest?: () => void;
|
|
101
124
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
102
125
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
103
126
|
/**
|
|
@@ -142,9 +165,39 @@ export function EditViewBinding<M extends Record<string, unknown>>({
|
|
|
142
165
|
useCache: false
|
|
143
166
|
});
|
|
144
167
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
168
|
+
// The edit handed over by another layout of this same record — see
|
|
169
|
+
// {@link carryEdit} below for the sending half.
|
|
170
|
+
//
|
|
171
|
+
// Read once per record and consumed: this is a handoff, and an edit left in
|
|
172
|
+
// the channel after it has been picked up is one that reopens the record
|
|
173
|
+
// dirty on a later, unrelated visit, carrying values the user walked away
|
|
174
|
+
// from. Held in a ref rather than re-read each render, because the read has
|
|
175
|
+
// to survive the clear — {@link EntityFormBinding} measures the
|
|
176
|
+
// local-changes banner against what the form opens showing, and a prop that
|
|
177
|
+
// reverted to `undefined` once the cache was cleared would raise the banner
|
|
178
|
+
// over the very edit that was just carried in.
|
|
179
|
+
//
|
|
180
|
+
// A copy is keyed with the new records, not under the record it was copied
|
|
181
|
+
// from: it is going to be stored as its own row, and both the handoff and
|
|
182
|
+
// the local-changes backup have always keyed it that way from the writing
|
|
183
|
+
// side. Reading it under `path/id` meant a copy in progress was written to
|
|
184
|
+
// one key and looked for under another.
|
|
185
|
+
const carriesAsNew = props.copy || entityId === undefined;
|
|
186
|
+
const handoffKey = carriesAsNew ? props.path + "#new" : props.path + "/" + entityId;
|
|
187
|
+
const handoff = useRef<{ key: string, values: object | undefined } | null>(null);
|
|
188
|
+
if (handoff.current?.key !== handoffKey) {
|
|
189
|
+
handoff.current = {
|
|
190
|
+
key: handoffKey,
|
|
191
|
+
values: getEntityFromMemoryCache(handoffKey)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
removeEntityFromMemoryCache(handoffKey);
|
|
196
|
+
}, [handoffKey]);
|
|
197
|
+
|
|
198
|
+
const initialDirtyValues = carriesAsNew
|
|
199
|
+
? (props.defaultValues ?? handoff.current.values)
|
|
200
|
+
: handoff.current.values;
|
|
148
201
|
|
|
149
202
|
const { canEdit: canEditHook } = usePermissions();
|
|
150
203
|
|
|
@@ -201,6 +254,7 @@ export function EditViewBindingInner<M extends Record<string, unknown>>({
|
|
|
201
254
|
onSaved,
|
|
202
255
|
onTabChange,
|
|
203
256
|
navigateBack,
|
|
257
|
+
onCloseRequest,
|
|
204
258
|
entity,
|
|
205
259
|
initialDirtyValues,
|
|
206
260
|
dataLoading,
|
|
@@ -287,7 +341,9 @@ parentEntityIds,
|
|
|
287
341
|
}
|
|
288
342
|
}, [selectedTabProp, defaultSelectedView]);
|
|
289
343
|
|
|
290
|
-
|
|
344
|
+
// See DetailViewBinding: tabs are governed by `hideFromEntityViews`, the
|
|
345
|
+
// drawer by `hideFromNavigation`. They are separate roles.
|
|
346
|
+
const childViews = getAdminEntityChildViews(collection).filter(v => !v.collection.hideFromEntityViews);
|
|
291
347
|
const subcollections = childViews.map(v => v.collection);
|
|
292
348
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
293
349
|
const customViews = collection.entityViews ?? [];
|
|
@@ -436,6 +492,35 @@ parentEntityIds,
|
|
|
436
492
|
|| (status === "existing" && !formex?.dirty)
|
|
437
493
|
);
|
|
438
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Hand this record's edit in progress to the layout about to replace this
|
|
497
|
+
* one — the split's "hide list", full screen's "show list", the side panel's
|
|
498
|
+
* "open full screen". None of them is a navigation *away* from the record:
|
|
499
|
+
* the same record is about to be shown by a different form, and what the
|
|
500
|
+
* user has typed belongs to the record, not to the pane it was typed in.
|
|
501
|
+
*
|
|
502
|
+
* Every control that changes layout has to call this, and none of them is
|
|
503
|
+
* blocked by the unsaved-changes prompt — the prompt asks about leaving, and
|
|
504
|
+
* these do not leave. What used to happen instead is that the edit reached
|
|
505
|
+
* the next layout only as its local-changes backup, which is the channel for
|
|
506
|
+
* a draft left behind by a closed tab: the record reopened *clean*, with a
|
|
507
|
+
* banner offering to apply changes the user had never walked away from.
|
|
508
|
+
*/
|
|
509
|
+
const carryEdit = useCallback(() => {
|
|
510
|
+
const carried = formex
|
|
511
|
+
? getEditHandoffValues<M>({
|
|
512
|
+
status,
|
|
513
|
+
dirty: formex.dirty,
|
|
514
|
+
values: formex.values,
|
|
515
|
+
touched: formex.touched,
|
|
516
|
+
storedValues: usedEntity?.values
|
|
517
|
+
})
|
|
518
|
+
: undefined;
|
|
519
|
+
if (!carried) return;
|
|
520
|
+
const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
|
|
521
|
+
saveEntityToMemoryCache(key, carried);
|
|
522
|
+
}, [formex, status, path, entityId, usedEntity]);
|
|
523
|
+
|
|
439
524
|
const [inspectorTab, setInspectorTab] = useState<InspectorTab | null>(null);
|
|
440
525
|
// A save adds a revision, and the inspector can be open while it happens:
|
|
441
526
|
// Save lives in the identity bar, above the panel. Counting saves gives the
|
|
@@ -445,7 +530,26 @@ parentEntityIds,
|
|
|
445
530
|
/* ---- record actions, in the bar's overflow menu ---------------------- */
|
|
446
531
|
|
|
447
532
|
const sideDialogContext = useSideDialogContext();
|
|
448
|
-
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Whether the save in flight was asked to dismiss the record afterwards.
|
|
536
|
+
*
|
|
537
|
+
* A ref, and for the reason {@link SideDialogs} keeps its own as one: the
|
|
538
|
+
* flag is raised and the form submitted in a single click handler, and the
|
|
539
|
+
* `onSaved` that has to act on it was captured before React could re-render.
|
|
540
|
+
* As state it read one save late — the first "save and close" saved and
|
|
541
|
+
* stayed open, and the *next* save closed the record.
|
|
542
|
+
*
|
|
543
|
+
* The side panel and the dialog use the side-dialog controller's flag,
|
|
544
|
+
* which their own close path consumes; this one covers the layouts that
|
|
545
|
+
* close by calling {@link onCloseRequest} instead.
|
|
546
|
+
*/
|
|
547
|
+
const pendingCloseRef = useRef(false);
|
|
548
|
+
|
|
549
|
+
// The split can close after a save the same way the overlays do — through
|
|
550
|
+
// the callback its layout supplies. Without one there is nothing to close.
|
|
551
|
+
const canCloseAfterSave = layout === "side_panel" || layout === "dialog"
|
|
552
|
+
|| (layout === "split" && Boolean(onCloseRequest));
|
|
449
553
|
|
|
450
554
|
const recordActions = useRecordActions({
|
|
451
555
|
collection,
|
|
@@ -547,10 +651,11 @@ parentEntityIds,
|
|
|
547
651
|
const entityReadOnlyView = !canEdit && entity ? (
|
|
548
652
|
FormViewBuilder && readOnlyFormContext
|
|
549
653
|
? <div className={cls(
|
|
550
|
-
"flex-1 flex flex-row w-full overflow-y-auto justify-center",
|
|
654
|
+
"@container/form flex-1 flex flex-row w-full overflow-y-auto justify-center",
|
|
551
655
|
(canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : ""
|
|
552
656
|
)}>
|
|
553
|
-
|
|
657
|
+
{/* Same widths as the default form (see EntityForm). */}
|
|
658
|
+
<div className={"w-full max-w-3xl @7xl/form:max-w-4xl 2xl:max-w-4xl flex flex-col pt-6 pb-16 px-5 sm:px-8"}>
|
|
554
659
|
<ErrorBoundary>
|
|
555
660
|
<Suspense fallback={<CircularProgressCenter />}>
|
|
556
661
|
<FormViewBuilder
|
|
@@ -629,8 +734,32 @@ parentEntityIds,
|
|
|
629
734
|
...params,
|
|
630
735
|
selectedTab: MAIN_TAB_VALUE === activeTab ? undefined : activeTab
|
|
631
736
|
};
|
|
737
|
+
// Two caller-supplied callbacks for one save, and either may
|
|
738
|
+
// navigate — the layouts' `onSaved` all do. Where both do, the
|
|
739
|
+
// second one's destination is what survives (docs/bug-classes.md
|
|
740
|
+
// #28), so `formProps.onSaved` is deliberately last: it belongs
|
|
741
|
+
// to whoever embedded this view, which outranks the layout.
|
|
632
742
|
onSaved?.(res);
|
|
633
743
|
formProps?.onSaved?.(res);
|
|
744
|
+
|
|
745
|
+
// After `onSaved`, never instead of it: the layout's own handler
|
|
746
|
+
// is what lowers its dirty flag, and closing is a navigation the
|
|
747
|
+
// unsaved-changes blocker would otherwise stop on values that
|
|
748
|
+
// are already stored.
|
|
749
|
+
//
|
|
750
|
+
// And after this handler returns, not inside it. The layouts
|
|
751
|
+
// that close this way close by navigating, and they have just
|
|
752
|
+
// navigated themselves — the split leaves `…/edit` for the
|
|
753
|
+
// record's own URL. Deferring keeps the close unambiguously
|
|
754
|
+
// last, which is what decides it: of two navigations raised for
|
|
755
|
+
// one action the final one survives, measured in
|
|
756
|
+
// `router_two_navigations_one_handler.test.tsx`. It also lets
|
|
757
|
+
// the state those handlers just set settle before the blocker
|
|
758
|
+
// reads it. See docs/bug-classes.md #28.
|
|
759
|
+
if (pendingCloseRef.current) {
|
|
760
|
+
pendingCloseRef.current = false;
|
|
761
|
+
setTimeout(() => onCloseRequest?.(), 0);
|
|
762
|
+
}
|
|
634
763
|
}}
|
|
635
764
|
Builder={resolveComponentRef(selectedSecondaryForm?.Builder as ComponentRef<EntityCustomViewParams<M>> | undefined) as React.ComponentType<EntityCustomViewParams<M>> | undefined}
|
|
636
765
|
/>
|
|
@@ -640,7 +769,7 @@ parentEntityIds,
|
|
|
640
769
|
const icon = getIcon(subcollection.icon, undefined, undefined, "smallest");
|
|
641
770
|
return (
|
|
642
771
|
<Tab
|
|
643
|
-
className=
|
|
772
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
644
773
|
value={subcollection.slug}
|
|
645
774
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
646
775
|
<span className="flex items-center gap-1.5">
|
|
@@ -656,7 +785,7 @@ parentEntityIds,
|
|
|
656
785
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
657
786
|
return (
|
|
658
787
|
<Tab
|
|
659
|
-
className={!view.tabComponent
|
|
788
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
660
789
|
value={view.key}
|
|
661
790
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
662
791
|
{view.tabComponent ?? (
|
|
@@ -673,7 +802,7 @@ parentEntityIds,
|
|
|
673
802
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
674
803
|
return (
|
|
675
804
|
<Tab
|
|
676
|
-
className={!view.tabComponent
|
|
805
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
677
806
|
value={view.key}
|
|
678
807
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
679
808
|
{view.tabComponent ?? (
|
|
@@ -693,6 +822,7 @@ parentEntityIds,
|
|
|
693
822
|
<IconButton
|
|
694
823
|
size="small"
|
|
695
824
|
onClick={() => {
|
|
825
|
+
carryEdit();
|
|
696
826
|
const editSuffix = collection.defaultEntityAction === "view" ? "/edit" : "";
|
|
697
827
|
const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}${editSuffix}`);
|
|
698
828
|
navigate(`${entityUrl}#full`);
|
|
@@ -708,7 +838,7 @@ parentEntityIds,
|
|
|
708
838
|
<EntityIdentityBar
|
|
709
839
|
collection={collection as AdminCollection}
|
|
710
840
|
collectionUrl={layout === "full_screen" || layout === "split"
|
|
711
|
-
?
|
|
841
|
+
? withListState(urlController.buildUrlCollectionPath(path))
|
|
712
842
|
: undefined}
|
|
713
843
|
title={displayTitle}
|
|
714
844
|
entityId={status === "existing" ? entityId : undefined}
|
|
@@ -723,22 +853,32 @@ parentEntityIds,
|
|
|
723
853
|
// list can be unfolded back beside the record, that chevron reaches
|
|
724
854
|
// the same collection and the arrow beside it is noise.
|
|
725
855
|
onBack={layout === "full_screen" && !onShowList
|
|
726
|
-
? () => navigate(
|
|
856
|
+
? () => navigate(withListState(urlController.buildUrlCollectionPath(path)))
|
|
727
857
|
: undefined}
|
|
728
858
|
onSave={canEdit && formContext ? () => {
|
|
729
859
|
sideDialogContext.setPendingClose?.(false);
|
|
860
|
+
pendingCloseRef.current = false;
|
|
730
861
|
formContext.submit();
|
|
731
862
|
} : undefined}
|
|
732
863
|
onSaveAndClose={canEdit && formContext && canCloseAfterSave ? () => {
|
|
733
|
-
sideDialogContext.setPendingClose?.(true);
|
|
734
864
|
// Lowered again once the submit settles. A submit the form
|
|
735
865
|
// rejects never reaches `onSaved`, so nothing would consume the
|
|
736
866
|
// flag and the *next* save — a keyboard ⌘S, say — would close
|
|
737
867
|
// the panel out from under an edit nobody asked to finish. A
|
|
738
868
|
// save that succeeds has already closed by the time this runs.
|
|
869
|
+
if (layout === "split") {
|
|
870
|
+
pendingCloseRef.current = true;
|
|
871
|
+
Promise.resolve(formContext.submit())
|
|
872
|
+
.finally(() => { pendingCloseRef.current = false; });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
sideDialogContext.setPendingClose?.(true);
|
|
739
876
|
Promise.resolve(formContext.submit())
|
|
740
877
|
.finally(() => sideDialogContext.setPendingClose?.(false));
|
|
741
878
|
} : undefined}
|
|
879
|
+
// Welded to Save in the split, its own button in the overlays.
|
|
880
|
+
saveAndClosePlacement={layout === "split" ? "menu" : "button"}
|
|
881
|
+
onClose={onCloseRequest}
|
|
742
882
|
onDiscard={canEdit && formContext ? () => formContext.formex.resetForm() : undefined}
|
|
743
883
|
onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
|
|
744
884
|
onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
|
|
@@ -751,9 +891,13 @@ parentEntityIds,
|
|
|
751
891
|
{barActionsStart}
|
|
752
892
|
{/* Split view: closing the list is opening this record
|
|
753
893
|
full screen, and the control for it belongs at this
|
|
754
|
-
bar's leading edge. Full screen carries its mirror.
|
|
755
|
-
|
|
756
|
-
{layout === "
|
|
894
|
+
bar's leading edge. Full screen carries its mirror.
|
|
895
|
+
Both change layout, so both hand the edit over. */}
|
|
896
|
+
{layout === "split" && <SplitListCloseButton onBeforeHide={carryEdit}/>}
|
|
897
|
+
{layout === "full_screen" && onShowList && <SplitListShowButton onClick={() => {
|
|
898
|
+
carryEdit();
|
|
899
|
+
onShowList();
|
|
900
|
+
}}/>}
|
|
757
901
|
</>}
|
|
758
902
|
trailing={<>
|
|
759
903
|
{pluginActionsTop}
|
|
@@ -763,7 +907,8 @@ parentEntityIds,
|
|
|
763
907
|
entityId,
|
|
764
908
|
values: formContext?.values ?? usedEntity?.values ?? {},
|
|
765
909
|
status,
|
|
766
|
-
dirty: Boolean(formContext?.formex?.dirty)
|
|
910
|
+
dirty: Boolean(formContext?.formex?.dirty),
|
|
911
|
+
carryEdit
|
|
767
912
|
})}
|
|
768
913
|
</>}
|
|
769
914
|
/>
|
|
@@ -773,10 +918,11 @@ parentEntityIds,
|
|
|
773
918
|
disappears entirely when there is nowhere else to go. */}
|
|
774
919
|
{hasAdditionalViews && <div className={cls(
|
|
775
920
|
"h-10 shrink-0 flex items-stretch border-b px-2 min-w-0",
|
|
921
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
776
922
|
defaultBorderMixin
|
|
777
923
|
)}>
|
|
778
924
|
<Tabs
|
|
779
|
-
className={"!w-full"}
|
|
925
|
+
className={"!w-full items-stretch"}
|
|
780
926
|
innerClassName={"h-full"}
|
|
781
927
|
variant={"boxy"}
|
|
782
928
|
value={activeTab}
|
|
@@ -784,7 +930,8 @@ parentEntityIds,
|
|
|
784
930
|
onSideTabClick(value);
|
|
785
931
|
}}>
|
|
786
932
|
|
|
787
|
-
<Tab value={MAIN_TAB_VALUE}
|
|
933
|
+
<Tab value={MAIN_TAB_VALUE}
|
|
934
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}>
|
|
788
935
|
<span className="flex items-center gap-1.5">
|
|
789
936
|
{getIcon(collection.icon, undefined, undefined, "smallest")}
|
|
790
937
|
{collection.singularName ?? collection.name}
|