@rebasepro/admin 0.12.1-canary.gf5f1d39 → 0.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 (178) hide show
  1. package/README.md +1 -1
  2. package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog-CjXkWZ64.js} +75 -62
  3. package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog-CjXkWZ64.js.map} +1 -1
  4. package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-W8I3YV89.js} +36 -16
  5. package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-W8I3YV89.js.map} +1 -1
  6. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-BsLTNoqX.js} +5 -5
  7. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-BsLTNoqX.js.map} +1 -1
  8. package/dist/collection_editor/serializable_types.d.ts +4 -1
  9. package/dist/collection_editor/ui/collection_editor/CollectionRelationsTab.d.ts +1 -0
  10. package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +8 -0
  11. package/dist/collection_editor_ui.js +5 -5
  12. package/dist/collection_editor_ui.js.map +1 -1
  13. package/dist/components/CollectionTableBinding/column_utils.d.ts +17 -0
  14. package/dist/components/CollectionViewBinding/CollectionViewStartActions.d.ts +7 -2
  15. package/dist/components/CollectionViewBinding/SortButton.d.ts +24 -0
  16. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +10 -0
  17. package/dist/components/CollectionViewBinding/SplitViewContext.d.ts +23 -0
  18. package/dist/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.d.ts +26 -0
  19. package/dist/components/DetailViewBinding.d.ts +5 -0
  20. package/dist/components/EditFormActions.d.ts +12 -1
  21. package/dist/components/EditViewBinding.d.ts +6 -1
  22. package/dist/components/EntityIdentityBar.d.ts +53 -0
  23. package/dist/components/EntityInspector.d.ts +35 -0
  24. package/dist/components/EntityPreviewNesting.d.ts +25 -0
  25. package/dist/components/EntityViewBinding.d.ts +9 -0
  26. package/dist/components/InlineEntityPreview.d.ts +47 -0
  27. package/dist/components/history/EntityHistoryView.d.ts +4 -1
  28. package/dist/components/index.d.ts +2 -0
  29. package/dist/editor.js +18 -10
  30. package/dist/editor.js.map +1 -1
  31. package/dist/{export-BzIpUdSK.js → export-J79rMoNj.js} +2 -2
  32. package/dist/{export-BzIpUdSK.js.map → export-J79rMoNj.js.map} +1 -1
  33. package/dist/form/EntityForm.d.ts +1 -1
  34. package/dist/form/EntityFormActions.d.ts +11 -1
  35. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  36. package/dist/form/components/FieldBlock.d.ts +30 -0
  37. package/dist/form/components/FormRail.d.ts +18 -0
  38. package/dist/form/components/FormSections.d.ts +16 -0
  39. package/dist/form/components/index.d.ts +3 -2
  40. package/dist/form/components/useRailVisible.d.ts +24 -0
  41. package/dist/form/field_bindings/BinaryFieldBinding.d.ts +1 -1
  42. package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +1 -1
  43. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +1 -1
  44. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  45. package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +1 -1
  46. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  47. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  48. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
  49. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -1
  50. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  51. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +1 -1
  52. package/dist/form/field_bindings/VectorFieldBinding.d.ts +1 -1
  53. package/dist/{history-g9688IaK.js → history-D7L_JlLO.js} +35 -33
  54. package/dist/history-D7L_JlLO.js.map +1 -0
  55. package/dist/hooks/navigation/utils.d.ts +16 -0
  56. package/dist/hooks/useBuildSidePanel.d.ts +14 -0
  57. package/dist/hooks/useEntityDisplayTitle.d.ts +18 -0
  58. package/dist/hooks/useHistory.d.ts +7 -0
  59. package/dist/hooks/useRecordActions.d.ts +17 -0
  60. package/dist/{import-BjUG-cIY.js → import-BvYl--U4.js} +2 -2
  61. package/dist/{import-BjUG-cIY.js.map → import-BvYl--U4.js.map} +1 -1
  62. package/dist/index.js +106 -35
  63. package/dist/index.js.map +1 -1
  64. package/dist/markdown-CklalUUk.js.map +1 -1
  65. package/dist/preview/components/InlineEntityListPreview.d.ts +15 -0
  66. package/dist/preview/index.d.ts +1 -0
  67. package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
  68. package/dist/preview/property_previews/ArrayOfRelationsPreview.d.ts +1 -1
  69. package/dist/types/components/EntityFormActionsProps.d.ts +0 -1
  70. package/dist/types/components/EntityFormProps.d.ts +0 -5
  71. package/dist/types/fields.d.ts +24 -0
  72. package/dist/{util-B5fJm1FA.js → util-GiwPgxnL.js} +2106 -1117
  73. package/dist/util-GiwPgxnL.js.map +1 -0
  74. package/package.json +32 -34
  75. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  76. package/src/collection_editor/serializable_types.ts +4 -3
  77. package/src/collection_editor/serializable_utils.ts +51 -17
  78. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +65 -22
  79. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +38 -30
  80. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -1
  81. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
  82. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +1 -1
  83. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +16 -0
  84. package/src/collection_editor/useLocalCollectionsConfigController.tsx +127 -15
  85. package/src/collection_editor/validateCollectionJson.ts +3 -3
  86. package/src/components/AdminModeSyncer.tsx +1 -1
  87. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +10 -2
  88. package/src/components/CollectionTableBinding/column_utils.tsx +59 -2
  89. package/src/components/CollectionTableBinding/table_bindings.tsx +2 -1
  90. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +23 -16
  91. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +7 -1
  92. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +9 -2
  93. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +47 -12
  94. package/src/components/CollectionViewBinding/SortButton.tsx +189 -0
  95. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +33 -0
  96. package/src/components/CollectionViewBinding/SplitListView.tsx +18 -3
  97. package/src/components/CollectionViewBinding/SplitViewContext.tsx +34 -0
  98. package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +48 -9
  99. package/src/components/CollectionViewBinding/useBoardDataController.tsx +80 -25
  100. package/src/components/CollectionViewBinding/usePreviewSlots.ts +18 -6
  101. package/src/components/DefaultAppBar.tsx +1 -1
  102. package/src/components/DefaultDrawer.tsx +1 -1
  103. package/src/components/DetailViewBinding.tsx +112 -148
  104. package/src/components/DrawerNavigationItem.tsx +1 -1
  105. package/src/components/EditFormActions.tsx +54 -129
  106. package/src/components/EditViewBinding.tsx +202 -99
  107. package/src/components/EntityIdentityBar.tsx +288 -0
  108. package/src/components/EntityInspector.tsx +137 -0
  109. package/src/components/EntityPreviewBinding.tsx +6 -2
  110. package/src/components/EntityPreviewNesting.tsx +50 -0
  111. package/src/components/EntityViewBinding.tsx +35 -32
  112. package/src/components/HomePage/FavouritesView.tsx +1 -1
  113. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  114. package/src/components/HomePage/SmallNavigationCard.tsx +1 -1
  115. package/src/components/InlineEntityPreview.tsx +149 -0
  116. package/src/components/RebaseLayout.tsx +1 -1
  117. package/src/components/RebaseNavigation.tsx +45 -5
  118. package/src/components/RebaseRouteDefs.tsx +1 -1
  119. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  120. package/src/components/SidePanelBinding.tsx +19 -13
  121. package/src/components/UserSelector.tsx +12 -6
  122. package/src/components/common/default_entity_actions.tsx +4 -3
  123. package/src/components/history/EntityHistoryEntry.tsx +28 -7
  124. package/src/components/history/EntityHistoryView.tsx +29 -24
  125. package/src/components/history/LastEditedByIndicator.tsx +4 -3
  126. package/src/components/index.ts +2 -0
  127. package/src/data_import/utils/get_import_inference_type.ts +6 -0
  128. package/src/form/EntityForm.tsx +267 -179
  129. package/src/form/EntityFormActions.tsx +50 -99
  130. package/src/form/EntityFormBinding.tsx +4 -2
  131. package/src/form/PropertyFieldBinding.tsx +4 -0
  132. package/src/form/components/FieldBlock.tsx +119 -0
  133. package/src/form/components/FormRail.tsx +125 -0
  134. package/src/form/components/FormSections.tsx +118 -0
  135. package/src/form/components/index.tsx +3 -2
  136. package/src/form/components/useRailVisible.tsx +46 -0
  137. package/src/form/field_bindings/BinaryFieldBinding.tsx +3 -2
  138. package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
  139. package/src/form/field_bindings/GeopointFieldBinding.tsx +3 -2
  140. package/src/form/field_bindings/MapFieldBinding.tsx +1 -2
  141. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -2
  142. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
  143. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
  144. package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -1
  145. package/src/form/field_bindings/RelationFieldBinding.tsx +6 -4
  146. package/src/form/field_bindings/RepeatFieldBinding.tsx +4 -1
  147. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
  148. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +31 -8
  149. package/src/form/field_bindings/SwitchFieldBinding.tsx +7 -3
  150. package/src/form/field_bindings/TextFieldBinding.tsx +18 -16
  151. package/src/form/field_bindings/UserSelectFieldBinding.tsx +3 -2
  152. package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
  153. package/src/hooks/navigation/useBuildUrlController.tsx +1 -1
  154. package/src/hooks/navigation/useTopLevelNavigation.ts +18 -7
  155. package/src/hooks/navigation/utils.ts +20 -0
  156. package/src/hooks/useBuildSideDialogsController.tsx +2 -2
  157. package/src/hooks/useBuildSidePanel.tsx +22 -61
  158. package/src/hooks/useEntityDisplayTitle.tsx +60 -0
  159. package/src/hooks/useHistory.ts +20 -10
  160. package/src/hooks/useRecordActions.tsx +69 -0
  161. package/src/hooks/useResolvedUsers.ts +10 -5
  162. package/src/preview/PropertyPreview.tsx +48 -22
  163. package/src/preview/components/InlineEntityListPreview.tsx +42 -0
  164. package/src/preview/components/ReferencePreview.tsx +34 -14
  165. package/src/preview/components/RelationPreview.tsx +34 -14
  166. package/src/preview/index.ts +1 -0
  167. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +24 -1
  168. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +25 -1
  169. package/src/routes/RebaseRoute.tsx +2 -2
  170. package/src/types/components/EntityFormActionsProps.tsx +0 -1
  171. package/src/types/components/EntityFormProps.tsx +0 -6
  172. package/src/types/fields.tsx +27 -0
  173. package/dist/form/components/FormEntry.d.ts +0 -6
  174. package/dist/form/components/FormLayout.d.ts +0 -5
  175. package/dist/history-g9688IaK.js.map +0 -1
  176. package/dist/util-B5fJm1FA.js.map +0 -1
  177. package/src/form/components/FormEntry.tsx +0 -22
  178. package/src/form/components/FormLayout.tsx +0 -16
@@ -8,6 +8,9 @@ import { PluginProviderStack, resolveComponentRef, useComponentOverride, Collect
8
8
 
9
9
  import { CollectionViewBinding } from "./CollectionViewBinding/CollectionViewBinding";
10
10
  import { EntityViewBinding } from "./EntityViewBinding";
11
+ import { EntityIdentityBar } from "./EntityIdentityBar";
12
+ import { SplitListCloseButton } from "./CollectionViewBinding/SplitListCloseButton";
13
+ import { EntityInspector, InspectorTab } from "./EntityInspector";
11
14
  import { CircularProgressCenter, iconSize } from "@rebasepro/ui";
12
15
  import {
13
16
  Alert,
@@ -41,13 +44,11 @@ import {
41
44
  } from "@rebasepro/app";
42
45
  import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
43
46
  import { useCollectionRegistryController } from "../hooks/navigation/contexts/CollectionRegistryContext";
44
- import { useNavigate } from "react-router-dom";
47
+ import { useNavigate } from "react-router";
45
48
  import { getValueInPath } from "@rebasepro/utils";
46
49
  import { getEntityTitlePropertyKeyForEntity, isUserSelectProperty, resolveTitleToString } from "../util/previews";
47
50
  import { getUserLabel, useResolvedUser } from "../hooks/useResolvedUsers";
48
- import { JsonPreviewBinding } from "../components/JsonPreviewBinding";
49
51
 
50
- const EntityHistoryView = lazy(() => import("../components/history").then(m => ({ default: m.EntityHistoryView })));
51
52
 
52
53
  import { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE } from "../util/view_constants";
53
54
 
@@ -76,6 +77,11 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
76
77
  onEditClick?: () => void;
77
78
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
78
79
  barActions?: (params: BarActionsParams) => React.ReactNode;
80
+ /**
81
+ * Controls the owning panel puts at the identity bar's leading edge, ahead
82
+ * of the breadcrumb — the side panel's close button.
83
+ */
84
+ barActionsStart?: React.ReactNode;
79
85
  }
80
86
 
81
87
  export function DetailViewBinding<M extends Record<string, unknown>>({
@@ -134,7 +140,8 @@ function DetailViewBindingInner<M extends Record<string, unknown>>({
134
140
  entity,
135
141
  dataLoading,
136
142
  layout = "full_screen",
137
- barActions
143
+ barActions,
144
+ barActionsStart
138
145
  }: DetailViewBindingProps<M> & {
139
146
  entity?: Entity<M>,
140
147
  dataLoading: boolean,
@@ -189,7 +196,18 @@ entityId }
189
196
  const customViewsCount = customViews?.length ?? 0;
190
197
  const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
191
198
  const includeHistoryView = Boolean(collection.history);
192
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView || includeHistoryView;
199
+ // Same split as the edit view: the strip is for destinations, the inspector
200
+ // for the developer tools that used to sit in front of them.
201
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
202
+
203
+ const [inspectorTab, setInspectorTab] = useState<InspectorTab | null>(null);
204
+
205
+ const legacyInspectorTab: InspectorTab | undefined = selectedTab === JSON_TAB_VALUE
206
+ ? "json"
207
+ : selectedTab === HISTORY_TAB_VALUE ? "history" : undefined;
208
+ useEffect(() => {
209
+ if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
210
+ }, [legacyInspectorTab]);
193
211
 
194
212
  const {
195
213
  resolvedEntityViews
@@ -198,13 +216,11 @@ entityId }
198
216
  const validTabValues = useMemo(() => {
199
217
  const set = new Set<string>([
200
218
  MAIN_TAB_VALUE,
201
- ...(includeJsonView ? [JSON_TAB_VALUE] : []),
202
- ...(includeHistoryView ? [HISTORY_TAB_VALUE] : []),
203
219
  ...resolvedEntityViews.map(v => v.key),
204
220
  ...subcollections.map(s => s.slug)
205
221
  ]);
206
222
  return set;
207
- }, [includeJsonView, includeHistoryView, resolvedEntityViews, subcollections]);
223
+ }, [resolvedEntityViews, subcollections]);
208
224
 
209
225
  const activeTab = validTabValues.has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
210
226
 
@@ -321,35 +337,6 @@ entityId }
321
337
 
322
338
  const globalLoading = dataLoading && !usedEntity;
323
339
 
324
- // JSON view
325
- const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
326
- const jsonView = (activeTab === JSON_TAB_VALUE || jsonTabMounted) ? <div
327
- className={cls("relative flex-1 h-full overflow-auto w-full",
328
- { "hidden": activeTab !== JSON_TAB_VALUE })}
329
- key={"json_view"}
330
- role="tabpanel">
331
- <ErrorBoundary>
332
- <JsonPreviewBinding values={usedEntity?.values ?? {}} />
333
- </ErrorBoundary>
334
- </div> : null;
335
-
336
- // History view
337
- const historyView = includeHistoryView && activeTab === HISTORY_TAB_VALUE ? <div
338
- className={"relative flex-1 h-full overflow-auto w-full"}
339
- key={"history_view"}
340
- role="tabpanel">
341
- <ErrorBoundary>
342
- <Suspense fallback={<CircularProgressCenter />}>
343
- <EntityHistoryView
344
- collection={collection}
345
- entity={usedEntity}
346
- formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
347
- modifiedValues={usedEntity?.values}
348
- />
349
- </Suspense>
350
- </ErrorBoundary>
351
- </div> : null;
352
-
353
340
  // Subcollection views
354
341
  const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
355
342
  const subcollectionId = subcollection.slug;
@@ -490,7 +477,9 @@ entityId }
490
477
 
491
478
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog" || Boolean(backToCollectionButton);
492
479
 
493
- const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? (
480
+ const fullScreenButton = !barActions && // Not in split: the list panel beside it carries the close control,
481
+ // and an expand button there competes with it.
482
+ (layout === "side_panel" || layout === "dialog") && entityId ? (
494
483
  <Tooltip title={"Open full screen"}>
495
484
  <IconButton
496
485
  size="small"
@@ -516,130 +505,105 @@ entityId }
516
505
  </Button>
517
506
  ) : null;
518
507
 
519
- // Main content view with title and properties
508
+ // Main content view. The title, the `path/id` chip and the empty band under
509
+ // them have moved into the identity bar, and the `w-80 2xl:w-96` rail that
510
+ // held a single Edit button is gone — the bar holds it now.
520
511
  const mainView = <div
521
512
  className={cls(
522
513
  "flex-1 flex flex-row w-full overflow-y-auto justify-center",
523
514
  !mainViewVisible ? "hidden" : ""
524
515
  )}>
525
- <div
526
- className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
527
- <div className={cls(
528
- "flex flex-col w-full",
529
- layout === "dialog"
530
- ? "pt-4 pb-12 px-6 sm:px-8"
531
- : "pt-12 pb-16 px-4 sm:px-8 md:px-10"
532
- )}>
533
- {/* Title and entity path */}
534
- <div className={"w-full flex flex-col items-start my-4 lg:my-6"}>
535
- <div className="flex items-center justify-between w-full">
536
- <Typography
537
- className={cls("my-4 grow line-clamp-1", collection.hideIdFromForm ? "mb-6" : "")}
538
- variant={"h4"}>
539
- {title}
540
- </Typography>
541
- {editButton}
542
- </div>
543
-
544
- <Alert color={"base"} outerClassName={"w-full"} size={"small"}>
545
- <code
546
- className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>
547
- {usedEntity?.path ?? path}/{entityId}
548
- </code>
549
- </Alert>
550
- </div>
551
-
552
- {/* Property detail display */}
553
- <div className="mt-12 flex flex-col gap-8">
554
- {propertyDetailView()}
555
- </div>
556
-
557
- <div className="h-16" />
558
- </div>
559
-
560
- {/* Side action bar for large screens */}
561
- {canEdit && onEditClick && layout === "full_screen" && <div
562
- className={cls(
563
- "overflow-auto h-full hidden @6xl:flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l",
564
- defaultBorderMixin
565
- )}>
566
- <Button
567
- fullWidth={true}
568
- variant="filled"
569
- color="primary"
570
- startIcon={<PencilIcon size={iconSize.small} />}
571
- onClick={onEditClick}>
572
- {t("edit_entity")}
573
- </Button>
574
- </div>}
516
+ <div className={cls(
517
+ "w-full max-w-3xl 2xl:max-w-4xl flex flex-col",
518
+ layout === "dialog"
519
+ ? "pt-5 pb-12 px-6 sm:px-8"
520
+ : "pt-6 pb-16 px-5 sm:px-8"
521
+ )}>
522
+ {propertyDetailView()}
575
523
  </div>
576
524
  </div>;
577
525
 
578
526
  let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-800">
579
527
 
580
- {shouldShowTopBar && <div
581
- className={cls("h-[52px] items-center flex overflow-hidden w-full border-b pl-2 pr-2 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
582
-
583
- {backToCollectionButton}
584
-
585
- {fullScreenButton}
586
-
587
- {barActions?.({
588
- path,
589
- entityId,
590
- values: usedEntity?.values ?? {},
591
- status: "existing"
592
- })}
593
-
594
- {pluginActionsTop}
595
-
596
- {hasAdditionalViews && <div className={"flex-1 flex justify-end min-w-0 shrink-0"}>
597
- <Tabs
598
- className={"!w-fit max-w-full"}
599
- value={activeTab}
600
- onValueChange={(value) => {
601
- onSideTabClick(value);
602
- }}>
603
-
604
- {includeJsonView && <Tab
605
- disabled={!hasAdditionalViews}
606
- value={JSON_TAB_VALUE}
607
- className={"text-sm"}>
608
- <CodeIcon size={iconSize.smallest} />
609
- </Tab>}
610
-
611
- {includeHistoryView && <Tab
612
- disabled={!hasAdditionalViews}
613
- value={HISTORY_TAB_VALUE}
614
- className={"text-sm"}>
615
- <HistoryIcon size={iconSize.smallest} />
616
- </Tab>}
617
-
618
- <Tab
619
- disabled={!hasAdditionalViews}
620
- value={MAIN_TAB_VALUE}
621
- className={"text-sm min-w-[90px]"}>
622
- <span className="flex items-center gap-1.5">
623
- {getIcon(collection.icon, undefined, undefined, "smallest")}
624
- {collection.singularName ?? collection.name}
625
- </span>
626
- </Tab>
528
+ <EntityIdentityBar
529
+ collection={collection as AdminCollection}
530
+ title={title}
531
+ entityId={entityId}
532
+ status={"existing"}
533
+ dirty={false}
534
+ saving={false}
535
+ onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
536
+ onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
537
+ leading={<>
538
+ {barActionsStart}
539
+ {/* Split view: closing the list is opening this record
540
+ full screen, and the control for it belongs at this
541
+ bar's leading edge. */}
542
+ {layout === "split" && <SplitListCloseButton/>}
543
+ </>}
544
+ trailing={<>
545
+ {editButton}
546
+ {pluginActionsTop}
547
+ {backToCollectionButton}
548
+ {fullScreenButton}
549
+ {barActions?.({
550
+ path,
551
+ entityId,
552
+ values: usedEntity?.values ?? {},
553
+ status: "existing"
554
+ })}
555
+ </>}
556
+ />
557
+
558
+ {hasAdditionalViews && <div className={cls(
559
+ "h-10 shrink-0 flex items-stretch border-b px-2 min-w-0",
560
+ defaultBorderMixin
561
+ )}>
562
+ <Tabs
563
+ className={"!w-full"}
564
+ innerClassName={"h-full"}
565
+ variant={"boxy"}
566
+ value={activeTab}
567
+ onValueChange={(value) => {
568
+ onSideTabClick(value);
569
+ }}>
570
+
571
+ <Tab value={MAIN_TAB_VALUE}>
572
+ <span className="flex items-center gap-1.5">
573
+ {getIcon(collection.icon, undefined, undefined, "smallest")}
574
+ {collection.singularName ?? collection.name}
575
+ </span>
576
+ </Tab>
627
577
 
628
- {customViewTabsStart}
629
- {customViewTabsEnd}
630
- {subcollectionTabs}
631
- </Tabs>
632
- </div>}
578
+ {customViewTabsStart}
579
+ {customViewTabsEnd}
580
+ {subcollectionTabs}
581
+ </Tabs>
633
582
  </div>}
634
583
 
635
- {globalLoading
636
- ? <DetailViewSkeleton collection={collection} />
637
- : mainView}
584
+ {/* See EditViewBinding: the inspector docks beside the record rather
585
+ than over it, so the record stays readable and scrollable. */}
586
+ <div className={"flex-1 min-h-0 flex"}>
587
+
588
+ <div className={"flex-1 min-w-0 flex flex-col"}>
589
+ {globalLoading
590
+ ? <DetailViewSkeleton collection={collection} />
591
+ : mainView}
638
592
 
639
- {jsonView}
640
- {historyView}
641
- {customViewsView}
642
- {subCollectionsViews}
593
+ {customViewsView}
594
+ {subCollectionsViews}
595
+ </div>
596
+
597
+ <EntityInspector
598
+ tab={inspectorTab}
599
+ onTabChange={setInspectorTab}
600
+ onClose={() => setInspectorTab(null)}
601
+ collection={collection as AdminCollection}
602
+ entity={usedEntity as Entity<Record<string, unknown>> | undefined}
603
+ formContext={readOnlyFormContext as FormContext<Record<string, unknown>> | undefined}
604
+ values={usedEntity?.values}
605
+ includeHistory={includeHistoryView}/>
606
+ </div>
643
607
 
644
608
  </div>;
645
609
 
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { NavLink } from "react-router-dom";
3
+ import { NavLink } from "react-router";
4
4
  import { cls, Tooltip } from "@rebasepro/ui";
5
5
 
6
6
  export function DrawerNavigationItem({
@@ -24,11 +24,21 @@ import { getIcon } from "@rebasepro/app";
24
24
  import { useAdminContext } from "../hooks/useAdminContext";
25
25
  import { useChildViewSource } from "../hooks/useChildViewSource";
26
26
 
27
+ /**
28
+ * The entity form's footer.
29
+ *
30
+ * It used to render in two shapes: this footer, and a `w-80 2xl:w-96` sticky
31
+ * side rail carrying the same Save and Discard above several hundred pixels of
32
+ * nothing. The rail is gone, and Save/Discard have moved to the identity bar
33
+ * where they stay visible while the form scrolls — so with
34
+ * `showDefaultActions={false}` this renders only what the bar cannot host:
35
+ * record actions (copy, delete, custom), plugin actions, the saving error, and
36
+ * the side-panel-only "save and close".
37
+ */
27
38
  export function EditFormActions({
28
39
  collection,
29
40
  path,
30
41
  entity,
31
- layout,
32
42
  savingError,
33
43
  formex,
34
44
  disabled,
@@ -69,10 +79,11 @@ export function EditFormActions({
69
79
  return actions;
70
80
  }, [canCreate, canDelete, collection, path, customizationController.entityActions?.length, entity, collection.disableDefaultActions, isLinkedChildView]);
71
81
 
72
- const formActions = showDefaultActions ? entityActions.filter(a => a.includeInForm === undefined || a.includeInForm) : [];
82
+ // Record actions stay in the footer whether or not the bar carries Save;
83
+ // they are things you do *to* the record, not ways to leave the form.
84
+ const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
73
85
 
74
- // HMR reload trigger
75
- const bottomActions = buildBottomActions({
86
+ return buildBottomActions({
76
87
  savingError,
77
88
  entity,
78
89
  formActions,
@@ -88,36 +99,8 @@ export function EditFormActions({
88
99
  formContext,
89
100
  formex,
90
101
  t,
91
- className: layout === "responsive" ? "@6xl:hidden" : undefined
102
+ showDefaultActions
92
103
  });
93
-
94
- const sideActions = buildSideActions({
95
- savingError,
96
- entity,
97
- formActions,
98
- collection,
99
- context,
100
- sidePanelController,
101
- sideDialogContext,
102
- disabled,
103
- status,
104
- pluginActions,
105
- openEntityMode,
106
- navigateBack,
107
- formContext,
108
- formex,
109
- t,
110
- className: layout === "responsive" ? "hidden @6xl:flex" : undefined
111
- });
112
-
113
- if (layout === "responsive") {
114
- return <>
115
- {bottomActions}
116
- {sideActions}
117
- </>;
118
- }
119
-
120
- return layout === "bottom" ? bottomActions : sideActions;
121
104
  }
122
105
 
123
106
  type ActionsViewProps<M extends Record<string, unknown>> = {
@@ -136,6 +119,7 @@ type ActionsViewProps<M extends Record<string, unknown>> = {
136
119
  formContext: FormContext,
137
120
  formex: FormexController<Record<string, unknown>>;
138
121
  t: (key: string, vars?: Record<string, string>) => string;
122
+ showDefaultActions: boolean;
139
123
  className?: string;
140
124
  };
141
125
 
@@ -155,14 +139,28 @@ function buildBottomActions<M extends Record<string, unknown>>({
155
139
  formContext,
156
140
  formex,
157
141
  t,
142
+ showDefaultActions,
158
143
  className
159
144
  }: ActionsViewProps<M>) {
160
145
 
161
146
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
147
+ // "Save and close" only means something where there is something to close.
162
148
  const canClose = openEntityMode === "side_panel" || openEntityMode === "dialog";
149
+ const showBackToDetail = collection.defaultEntityAction === "view" && status === "existing";
150
+
151
+ // When the container carries the actions — the entity view's identity bar
152
+ // does — this renders nothing at all. A full-width bar holding a copy icon
153
+ // and a delete icon is the same waste as the side rail it replaced: the
154
+ // record actions moved into the bar's overflow menu, and Save, Discard and
155
+ // "save and close" are buttons up there too.
156
+ const hasContent = showDefaultActions || Boolean(savingError);
157
+
158
+ if (!hasContent) return null;
159
+
163
160
  return <DialogActions
164
161
  className={className}
165
- position={"absolute"}>
162
+ // In normal flow at the foot of the form column, not overlaying it.
163
+ position={"sticky"}>
166
164
  {savingError &&
167
165
  <div className="text-right">
168
166
  <Typography color={"error"}>{savingError.message}</Typography>
@@ -202,13 +200,8 @@ function buildBottomActions<M extends Record<string, unknown>>({
202
200
  <ErrorTooltip title={"This form has errors"}>
203
201
  <AlertCircleIcon className="ml-4 text-red-500" size={iconSize.smallest}/>
204
202
  </ErrorTooltip> : null}
205
- <Button variant="text"
206
- color="primary"
207
- disabled={disabled || formex.isSubmitting}
208
- type="reset">
209
- {status === "existing" ? t("discard") : t("clear")}
210
- </Button>
211
- {collection.defaultEntityAction === "view" && status === "existing" && (
203
+
204
+ {showBackToDetail && (
212
205
  <Button variant="text"
213
206
  color="primary"
214
207
  disabled={disabled}
@@ -216,19 +209,29 @@ function buildBottomActions<M extends Record<string, unknown>>({
216
209
  {t("back_to_detail") ?? "Back to details"}
217
210
  </Button>
218
211
  )}
219
- <Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
220
- <Button variant={canClose ? "text" : "filled"}
212
+
213
+ {showDefaultActions && <>
214
+ <Button variant="text"
221
215
  color="primary"
222
- type="submit"
223
216
  disabled={disabled || formex.isSubmitting}
224
- onClick={() => {
225
- sideDialogContext.setPendingClose(false);
226
- }}>
227
- {status === "existing" && t("save")}
228
- {status === "copy" && t("create_copy")}
229
- {status === "new" && t("create")}
217
+ type="reset">
218
+ {status === "existing" ? t("discard") : t("clear")}
230
219
  </Button>
231
- </Tooltip>
220
+ <Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
221
+ <Button variant={canClose ? "text" : "filled"}
222
+ color="primary"
223
+ type="submit"
224
+ disabled={disabled || formex.isSubmitting}
225
+ onClick={() => {
226
+ sideDialogContext.setPendingClose(false);
227
+ }}>
228
+ {status === "existing" && t("save")}
229
+ {status === "copy" && t("create_copy")}
230
+ {status === "new" && t("create")}
231
+ </Button>
232
+ </Tooltip>
233
+ </>}
234
+
232
235
  {canClose && <LoadingButton variant="filled"
233
236
  color="primary"
234
237
  type="submit"
@@ -244,84 +247,6 @@ function buildBottomActions<M extends Record<string, unknown>>({
244
247
  </DialogActions>;
245
248
  }
246
249
 
247
- function buildSideActions<M extends Record<string, unknown>>({
248
- savingError,
249
- entity,
250
- formActions,
251
- collection,
252
- context,
253
- sidePanelController,
254
- disabled,
255
- status,
256
- sideDialogContext,
257
- pluginActions,
258
- openEntityMode,
259
- navigateBack,
260
- formContext,
261
- formex,
262
- t,
263
- className
264
- }: ActionsViewProps<M>) {
265
-
266
- const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
267
- return <div
268
- className={cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className)}>
269
- <Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
270
- <LoadingButton fullWidth={true}
271
- variant="filled"
272
- color="primary"
273
- type="submit"
274
- loading={formex.isSubmitting}
275
- startIcon={hasErrors ? <AlertCircleIcon/> : undefined}
276
- disabled={disabled || formex.isSubmitting}
277
- onClick={() => {
278
- sideDialogContext.setPendingClose?.(false);
279
- }}>
280
- {status === "existing" && t("save")}
281
- {status === "copy" && t("create_copy")}
282
- {status === "new" && t("create")}
283
- </LoadingButton>
284
- </Tooltip>
285
-
286
- <Button fullWidth={true} variant="text" disabled={disabled || formex.isSubmitting} type="reset">
287
- {status === "existing" ? t("discard") : t("clear")}
288
- </Button>
289
- {collection.defaultEntityAction === "view" && status === "existing" && (
290
- <Button fullWidth={true} variant="text" disabled={disabled} onClick={navigateBack}>
291
- {t("back_to_detail") ?? "Back to details"}
292
- </Button>
293
- )}
294
-
295
- {pluginActions}
296
-
297
- {formActions.length > 0 && <div className="flex flex-row flex-wrap mt-2">
298
- {formActions.map((action, index) => {
299
- const props = {
300
- view: "form",
301
- entity,
302
- path: getCollectionDataPath(collection),
303
- collection: collection,
304
- context,
305
- sidePanelController,
306
- openEntityMode,
307
- navigateBack,
308
- formContext
309
- } satisfies EntityActionClickProps<Record<string, unknown>>;
310
- const isEnabled = !action.isEnabled || action.isEnabled(props);
311
- return (
312
- <EntityActionButton key={action.key ?? action.name ?? index} action={action} enabled={isEnabled} props={props}/>
313
- );
314
- })}
315
- </div>}
316
-
317
- {savingError &&
318
- <div className="text-right">
319
- <Typography color={"error"}>{savingError.message}</Typography>
320
- </div>
321
- }
322
- </div>;
323
- }
324
-
325
250
  function EntityActionButton({
326
251
  action,
327
252
  enabled,