@rebasepro/admin 0.11.0 → 0.11.1-canary.g8caabf3

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 (89) hide show
  1. package/README.md +1 -1
  2. package/dist/{CollectionEditorDialog-o7F_zbGY.js → CollectionEditorDialog-id__fOqM.js} +4 -4
  3. package/dist/CollectionEditorDialog-id__fOqM.js.map +1 -0
  4. package/dist/{PropertyEditView-DgB2b22q.js → PropertyEditView-BLPT_e-N.js} +2 -2
  5. package/dist/{PropertyEditView-DgB2b22q.js.map → PropertyEditView-BLPT_e-N.js.map} +1 -1
  6. package/dist/{RouterCollectionsStudioView-DaLRgPkj.js → RouterCollectionsStudioView-DwkGdgyN.js} +4 -4
  7. package/dist/{RouterCollectionsStudioView-DaLRgPkj.js.map → RouterCollectionsStudioView-DwkGdgyN.js.map} +1 -1
  8. package/dist/collection_editor/CollectionEditorDialogsContext.d.ts +1 -1
  9. package/dist/collection_editor/_cms_internals.d.ts +1 -1
  10. package/dist/collection_editor/extensibility_types.d.ts +1 -1
  11. package/dist/collection_editor_ui.js +4 -4
  12. package/dist/components/CollectionEditorDialogs.d.ts +2 -2
  13. package/dist/components/CollectionViewBinding/CollectionListViewBinding.d.ts +2 -2
  14. package/dist/components/DefaultDrawer.d.ts +3 -3
  15. package/dist/components/DrawerNavigationGroup.d.ts +8 -1
  16. package/dist/components/DrawerNavigationItem.d.ts +7 -1
  17. package/dist/components/EditViewBinding.d.ts +1 -1
  18. package/dist/components/RebaseAdmin.d.ts +1 -1
  19. package/dist/components/RebaseAuthGate.d.ts +1 -1
  20. package/dist/components/RebaseLayout.d.ts +5 -1
  21. package/dist/components/RebaseNavigation.d.ts +2 -2
  22. package/dist/components/RebaseRouteDefs.d.ts +1 -1
  23. package/dist/components/RebaseShell.d.ts +6 -1
  24. package/dist/components/SidePanelProvider.d.ts +2 -2
  25. package/dist/components/app/Scaffold.d.ts +11 -0
  26. package/dist/{export-BvqhjdEb.js → export-lZhpezjk.js} +3 -3
  27. package/dist/export-lZhpezjk.js.map +1 -0
  28. package/dist/form/EntityForm.d.ts +2 -2
  29. package/dist/form/EntityFormBinding.d.ts +5 -5
  30. package/dist/{history-Dz20hH38.js → history-DkP1tce0.js} +2 -2
  31. package/dist/{history-Dz20hH38.js.map → history-DkP1tce0.js.map} +1 -1
  32. package/dist/hooks/index.d.ts +1 -1
  33. package/dist/hooks/useAdminContext.d.ts +29 -0
  34. package/dist/{import-DINSB-Vu.js → import-DQPGsiIw.js} +2 -2
  35. package/dist/{import-DINSB-Vu.js.map → import-DQPGsiIw.js.map} +1 -1
  36. package/dist/index.js +60 -38
  37. package/dist/index.js.map +1 -1
  38. package/dist/types/components/EntityFormProps.d.ts +1 -1
  39. package/dist/types/fields.d.ts +1 -1
  40. package/dist/{util-BhhfHY1M.js → util-DBxSfWpl.js} +45 -35
  41. package/dist/util-DBxSfWpl.js.map +1 -0
  42. package/package.json +9 -9
  43. package/src/collection_editor/CollectionEditorDialogsContext.tsx +1 -1
  44. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  45. package/src/collection_editor/_cms_internals.ts +1 -1
  46. package/src/collection_editor/extensibility_types.ts +1 -1
  47. package/src/collection_editor/index.ts +2 -2
  48. package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +1 -1
  49. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -1
  50. package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +2 -2
  51. package/src/components/CollectionEditorDialogs.tsx +2 -2
  52. package/src/components/CollectionTableBinding/CollectionRowActions.tsx +5 -4
  53. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +8 -7
  54. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +2 -2
  55. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +2 -2
  56. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +2 -2
  57. package/src/components/DefaultDrawer.tsx +19 -8
  58. package/src/components/DeleteEntityDialog.tsx +2 -2
  59. package/src/components/DetailViewBinding.tsx +21 -1
  60. package/src/components/DrawerNavigationGroup.tsx +28 -4
  61. package/src/components/DrawerNavigationItem.tsx +19 -5
  62. package/src/components/EditFormActions.tsx +4 -3
  63. package/src/components/EditViewBinding.tsx +1 -1
  64. package/src/components/HomePage/ContentHomePage.tsx +4 -4
  65. package/src/components/RebaseAdmin.tsx +3 -3
  66. package/src/components/RebaseAuthGate.tsx +1 -1
  67. package/src/components/RebaseLayout.tsx +8 -2
  68. package/src/components/RebaseNavigation.tsx +9 -9
  69. package/src/components/RebaseRouteDefs.tsx +2 -2
  70. package/src/components/RebaseShell.tsx +10 -1
  71. package/src/components/RelationSelector.tsx +2 -2
  72. package/src/components/SidePanelProvider.tsx +2 -2
  73. package/src/components/app/Scaffold.tsx +16 -1
  74. package/src/data_export/export/ExportCollectionAction.tsx +2 -2
  75. package/src/form/EntityForm.tsx +2 -2
  76. package/src/form/EntityFormActions.tsx +2 -1
  77. package/src/form/EntityFormBinding.tsx +7 -7
  78. package/src/hooks/index.ts +1 -1
  79. package/src/hooks/navigation/useNavigationResolution.ts +2 -2
  80. package/src/hooks/{useCMSContext.tsx → useAdminContext.tsx} +10 -10
  81. package/src/hooks/useResolvedNavigationFrom.tsx +2 -2
  82. package/src/index.ts +4 -4
  83. package/src/preview/components/RelationPreview.tsx +1 -1
  84. package/src/types/components/EntityFormProps.tsx +2 -2
  85. package/src/types/fields.tsx +1 -1
  86. package/dist/CollectionEditorDialog-o7F_zbGY.js.map +0 -1
  87. package/dist/export-BvqhjdEb.js.map +0 -1
  88. package/dist/hooks/useCMSContext.d.ts +0 -29
  89. package/dist/util-BhhfHY1M.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/admin",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.11.1-canary.g8caabf3",
5
5
  "description": "Rebase CMS — content management views, forms, and routing",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -83,14 +83,14 @@
83
83
  "react-dropzone": "^15.0.0",
84
84
  "react-use-measure": "^2.1.7",
85
85
  "zod": "^4.4.3",
86
- "@rebasepro/admin-types": "0.11.0",
87
- "@rebasepro/common": "0.11.0",
88
- "@rebasepro/forms": "0.11.0",
89
- "@rebasepro/app": "0.11.0",
90
- "@rebasepro/inference": "0.11.0",
91
- "@rebasepro/types": "0.11.0",
92
- "@rebasepro/ui": "0.11.0",
93
- "@rebasepro/utils": "0.11.0"
86
+ "@rebasepro/admin-types": "0.11.1-canary.g8caabf3",
87
+ "@rebasepro/app": "0.11.1-canary.g8caabf3",
88
+ "@rebasepro/forms": "0.11.1-canary.g8caabf3",
89
+ "@rebasepro/common": "0.11.1-canary.g8caabf3",
90
+ "@rebasepro/types": "0.11.1-canary.g8caabf3",
91
+ "@rebasepro/ui": "0.11.1-canary.g8caabf3",
92
+ "@rebasepro/utils": "0.11.1-canary.g8caabf3",
93
+ "@rebasepro/inference": "0.11.1-canary.g8caabf3"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "react": ">=19.0.0",
@@ -5,7 +5,7 @@ import React, { createContext, useContext } from "react";
5
5
  * sets and CollectionEditorDialogs reads.
6
6
  *
7
7
  * This indirection exists because ConfigControllerProvider is a root-scope
8
- * plugin provider that wraps <RebaseShell>, therefore living ABOVE the CMS
8
+ * plugin provider that wraps <RebaseShell>, therefore living ABOVE the admin
9
9
  * context providers (CollectionRegistryContext, NavigationStateContext,
10
10
  * UrlContext). The actual dialog components need those contexts, so they
11
11
  * must render inside the shell tree.
@@ -245,7 +245,7 @@ parentEntityIds,
245
245
  }, [onAnalyticsEvent, pathSuggestions]);
246
246
 
247
247
  // Build the dialog props objects that will be consumed by
248
- // CollectionEditorDialogs (rendered inside RebaseShell where CMS
248
+ // CollectionEditorDialogs (rendered inside RebaseShell where admin
249
249
  // contexts are available).
250
250
  const collectionDialogProps = useMemo(() => {
251
251
  if (!currentDialog) return undefined;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Internal re-exports for the collection editor module.
3
3
  * This avoids circular imports by pointing directly at source files
4
- * rather than going through the CMS barrel (index.ts).
4
+ * rather than going through the admin barrel (index.ts).
5
5
  */
6
6
 
7
7
  // Hooks
@@ -160,7 +160,7 @@ export interface CollectionEditorExtensionProps {
160
160
  * reading from React contexts.
161
161
  *
162
162
  * Use this when embedding the collection editor outside of the Rebase
163
- * CMS, where the Rebase context providers are not available.
163
+ * admin, where the Rebase context providers are not available.
164
164
  *
165
165
  * Default: false (standard Rebase mode).
166
166
  */
@@ -1,9 +1,9 @@
1
1
  // Collection Editor — moved from @rebasepro/studio
2
- // This module provides the visual schema/collection editor for the CMS.
2
+ // This module provides the visual schema/collection editor for the admin.
3
3
  //
4
4
  // IMPORTANT: Heavy UI components (CollectionEditorDialog, CollectionStudioView,
5
5
  // CollectionsStudioView, PropertyForm, PropertyFormDialog) are NOT re-exported
6
- // here to keep the main CMS bundle lean. Import them from
6
+ // here to keep the main admin bundle lean. Import them from
7
7
  // "@rebasepro/admin/collection_editor_ui" instead — they live in a separate chunk
8
8
  // that is loaded on demand.
9
9
 
@@ -145,7 +145,7 @@ export function EntityActionsEditTab({
145
145
  title={<>Remove this action?</>}
146
146
  body={<>This will <b>not
147
147
  delete any data</b>, only
148
- the action in the CMS</>}/>}
148
+ the action in the admin</>}/>}
149
149
 
150
150
  <EntityActionsSelectDialog
151
151
  open={addEntityActionDialogOpen}
@@ -27,7 +27,7 @@ export function GetCodeDialog({
27
27
  <DialogContent>
28
28
 
29
29
  <Typography variant={"body2"} className={"my-4 mb-8"}>
30
- If you want to customise the collection in code, you can add this collection code to your CMS
30
+ If you want to customise the collection in code, you can add this collection code to your admin
31
31
  app configuration.
32
32
  More info in the <a
33
33
  rel="noopener noreferrer"
@@ -214,7 +214,7 @@ export function SubcollectionsEditTab({
214
214
  title={<>Delete this subcollection?</>}
215
215
  body={<> This will <b>not
216
216
  delete any data</b>, only
217
- the collection in the CMS</>}/>}
217
+ the collection in the admin</>}/>}
218
218
  {viewToDelete &&
219
219
  <ConfirmationDialog open={Boolean(viewToDelete)}
220
220
  onAccept={() => {
@@ -225,7 +225,7 @@ export function SubcollectionsEditTab({
225
225
  title={<>Remove this view?</>}
226
226
  body={<>This will <b>not
227
227
  delete any data</b>, only
228
- the view in the CMS</>}/>}
228
+ the view in the admin</>}/>}
229
229
 
230
230
  <CollectionEditorDialog
231
231
  open={Boolean(currentDialog)}
@@ -16,12 +16,12 @@ const PropertyFormDialog = lazy(() =>
16
16
 
17
17
  /**
18
18
  * Renders the CollectionEditorDialog and PropertyFormDialog inside
19
- * the RebaseShell tree where CMS-internal contexts
19
+ * the RebaseShell tree where admin-internal contexts
20
20
  * (CollectionRegistryContext, NavigationStateContext, UrlContext)
21
21
  * are available.
22
22
  *
23
23
  * Both dialog components are loaded dynamically via React.lazy so that
24
- * production CMS bundles that never open the collection editor don't pay
24
+ * production admin bundles that never open the collection editor don't pay
25
25
  * the download/parse cost up-front.
26
26
  *
27
27
  * The dialog state (open/close, props) is managed by ConfigControllerProvider
@@ -16,9 +16,10 @@ import {
16
16
  Tooltip
17
17
  } from "@rebasepro/ui";
18
18
  import { useTranslation } from "@rebasepro/app";
19
+ import { getIcon } from "@rebasepro/app";
19
20
  import { getEntityFromCache } from "@rebasepro/app";
20
21
  import { getLocalChangesBackup } from "@rebasepro/app";
21
- import { useCMSContext } from "../../hooks/useCMSContext";
22
+ import { useAdminContext } from "../../hooks/useAdminContext";
22
23
 
23
24
  /**
24
25
  *
@@ -79,7 +80,7 @@ export const CollectionRowActions = function CollectionRowActions({
79
80
  isDraggable?: boolean;
80
81
  }) {
81
82
 
82
- const context = useCMSContext();
83
+ const context = useAdminContext();
83
84
  const sidePanelCtrl = context.sidePanelController;
84
85
  const { t } = useTranslation();
85
86
 
@@ -140,7 +141,7 @@ export const CollectionRowActions = function CollectionRowActions({
140
141
  });
141
142
  }}
142
143
  size={iconSize}>
143
- {action.icon}
144
+ {getIcon(action.icon, undefined, undefined, "smallest")}
144
145
  </IconButton>;
145
146
  if (isEditAction && hasDraft) {
146
147
  iconButton = (
@@ -183,7 +184,7 @@ export const CollectionRowActions = function CollectionRowActions({
183
184
  openEntityMode: openEntityMode ?? collection?.openEntityMode
184
185
  });
185
186
  }}>
186
- {action.icon}
187
+ {getIcon(action.icon, undefined, undefined, "smallest")}
187
188
  {action.name}
188
189
  </MenuItem>
189
190
  ))}
@@ -22,9 +22,10 @@ import {
22
22
  import { useAnalyticsController } from "@rebasepro/app";
23
23
  import { getEntityPreviewKeys } from "../../util/previews";
24
24
  import { IconForView } from "@rebasepro/app";
25
+ import { getIcon } from "@rebasepro/app";
25
26
  import { getValueInPath } from "@rebasepro/utils";
26
27
  import { useCollectionSlotKeys, resolveEntitySlots, type CollectionSlotKeys } from "./usePreviewSlots";
27
- import { useCMSContext } from "../../hooks/useCMSContext";
28
+ import { useAdminContext } from "../../hooks/useAdminContext";
28
29
  import { resolveEntityAction } from "../../util/resolutions";
29
30
 
30
31
  export type CollectionListViewBindingProps<M extends Record<string, unknown> = Record<string, unknown>> = {
@@ -265,8 +266,8 @@ year: diffDays > 365 ? "numeric" : undefined });
265
266
  }
266
267
 
267
268
  /**
268
- * Classic CMS list view for displaying entities.
269
- * Designed to be the most familiar, stereotypical CMS content management view:
269
+ * Classic admin list view for displaying entities.
270
+ * Designed to be the most familiar, stereotypical admin content management view:
270
271
  * - Clean rows with checkbox, icon/avatar, title, metadata, and actions
271
272
  * - Column-sortable headers
272
273
  * - Infinite scroll
@@ -289,7 +290,7 @@ export function CollectionListViewBinding<M extends Record<string, unknown> = Re
289
290
  const authController = useAuthController();
290
291
  const customizationController = useCustomizationController();
291
292
  const analyticsController = useAnalyticsController();
292
- const context = useCMSContext();
293
+ const context = useAdminContext();
293
294
 
294
295
  const containerRef = useRef<HTMLDivElement>(null);
295
296
  const [containerWidth, setContainerWidth] = useState(1200);
@@ -622,7 +623,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
622
623
  isLast: boolean;
623
624
  isActive?: boolean;
624
625
  listViewActions?: EntityAction[];
625
- context?: ReturnType<typeof useCMSContext>;
626
+ context?: ReturnType<typeof useAdminContext>;
626
627
  path?: string;
627
628
  selectionController?: SelectionController<M>;
628
629
  openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
@@ -864,7 +865,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
864
865
  openEntityMode: openEntityMode ?? collection?.openEntityMode ?? "full_screen"
865
866
  });
866
867
  }}>
867
- {action.icon}
868
+ {getIcon(action.icon, undefined, undefined, "smallest")}
868
869
  </IconButton>
869
870
  </Tooltip>
870
871
  ))}
@@ -888,7 +889,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
888
889
  isLast: boolean;
889
890
  isActive?: boolean;
890
891
  listViewActions?: EntityAction[];
891
- context?: ReturnType<typeof useCMSContext>;
892
+ context?: ReturnType<typeof useAdminContext>;
892
893
  path?: string;
893
894
  selectionController?: SelectionController<M>;
894
895
  openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
@@ -13,7 +13,7 @@ const ImportCollectionAction = lazy(() => import("../../data_import/import").the
13
13
  const ExportCollectionAction = lazy(() => import("../../data_export/export").then(m => ({ default: m.ExportCollectionAction })));
14
14
  import { EditorCollectionAction } from "../../collection_editor/ui/EditorCollectionAction";
15
15
  import { useCollectionEditorController } from "../../collection_editor/useCollectionEditorController";
16
- import { useCMSContext } from "../../hooks/useCMSContext";
16
+ import { useAdminContext } from "../../hooks/useAdminContext";
17
17
 
18
18
  export type CollectionViewActionsProps<M extends Record<string, unknown>> = {
19
19
  collection: AdminCollection<M>;
@@ -52,7 +52,7 @@ export function CollectionViewActions<M extends Record<string, unknown>>({
52
52
  children,
53
53
  openNewDocument
54
54
  }: CollectionViewActionsProps<M>) {
55
- const context = useCMSContext();
55
+ const context = useAdminContext();
56
56
 
57
57
  const { canCreate, canDelete } = usePermissions();
58
58
 
@@ -75,7 +75,7 @@ import { addRecentId, getRecentIds } from "./utils";
75
75
  import { isViewMode, OpenEntityMode, resolveOpenEntityMode, VIEW_MODE_PARAM } from "../../util/view_mode";
76
76
  import { mergeDeep } from "@rebasepro/utils";
77
77
  import { useBreadcrumbsController } from "../../hooks/useBreadcrumbsController";
78
- import { useCMSContext } from "../../hooks/useCMSContext";
78
+ import { useAdminContext } from "../../hooks/useAdminContext";
79
79
  import { useCollectionRegistryController } from "../../hooks/navigation/contexts/CollectionRegistryContext";
80
80
  import { useSidePanel } from "../../hooks/useSidePanel";
81
81
  import { useUrlController } from "../../hooks/navigation/contexts/UrlContext";
@@ -167,7 +167,7 @@ const CollectionViewBindingInner = React.memo(
167
167
  ) {
168
168
 
169
169
  const { t } = useTranslation();
170
- const context = useCMSContext();
170
+ const context = useAdminContext();
171
171
  const collectionRegistry = useCollectionRegistryController();
172
172
  const urlController = useUrlController();
173
173
  const breadcrumbs = useBreadcrumbsController();
@@ -11,7 +11,7 @@ import { FilterPresetsButton } from "./FilterPresetsButton";
11
11
  import { toArray } from "@rebasepro/utils";
12
12
  import { useNavigate } from "react-router-dom";
13
13
  import { useUrlController } from "../../hooks/navigation/contexts/UrlContext";
14
- import { useCMSContext } from "../../hooks/useCMSContext";
14
+ import { useAdminContext } from "../../hooks/useAdminContext";
15
15
  import { withViewMode } from "../../util/view_mode";
16
16
 
17
17
  export type CollectionViewStartActionsProps<M extends Record<string, unknown>> = {
@@ -43,7 +43,7 @@ export function CollectionViewStartActions<M extends Record<string, unknown>>({
43
43
  openNewDocument
44
44
  }: CollectionViewStartActionsProps<M>) {
45
45
 
46
- const context = useCMSContext();
46
+ const context = useAdminContext();
47
47
  const largeLayout = useLargeLayout();
48
48
  const { t } = useTranslation();
49
49
 
@@ -33,9 +33,9 @@ import { LanguageToggle, RebaseLogo } from "@rebasepro/app";
33
33
  import { useApp } from "./app/useApp";
34
34
 
35
35
  /**
36
- * Default drawer used in the CMS.
36
+ * Default drawer used in the admin.
37
37
  *
38
- * When no `children` are provided, renders the full CMS navigation
38
+ * When no `children` are provided, renders the full admin navigation
39
39
  * (collection groups, mode switch, plugin slots).
40
40
  *
41
41
  * When `children` **are** provided, renders the shared drawer shell
@@ -60,7 +60,7 @@ export function DefaultDrawer({
60
60
  logoDestination?: string;
61
61
  /**
62
62
  * Custom navigation content for the drawer.
63
- * When provided, replaces the default CMS navigation (collection groups,
63
+ * When provided, replaces the default admin navigation (collection groups,
64
64
  * mode switch, slots). The shared shell (logo, scroll area, footer
65
65
  * actions, collapse toggle) is still rendered around it.
66
66
  */
@@ -115,7 +115,7 @@ export function DefaultDrawer({
115
115
  ? "linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%)"
116
116
  : "linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%)"
117
117
  }}>
118
- {children ?? <CMSNavigationContent />}
118
+ {children ?? <AdminNavigationContent />}
119
119
  </div>
120
120
 
121
121
  {footerActions !== null && (
@@ -138,11 +138,11 @@ export function DefaultDrawer({
138
138
  }
139
139
 
140
140
  /**
141
- * Default CMS navigation content — rendered inside DefaultDrawer when no
141
+ * Default admin navigation content — rendered inside DefaultDrawer when no
142
142
  * custom `children` are provided. Contains the mode switch, navigation
143
143
  * groups, and header / footer plugin slots.
144
144
  */
145
- function CMSNavigationContent() {
145
+ function AdminNavigationContent() {
146
146
 
147
147
  const {
148
148
  drawerHovered,
@@ -166,7 +166,7 @@ function CMSNavigationContent() {
166
166
  const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
167
167
 
168
168
  // Build a set of Studio-only dev view slugs so we can distinguish them from
169
- // CMS custom views added via <RebaseAdmin views={...}> or plugins.
169
+ // admin custom views added via <RebaseAdmin views={...}> or plugins.
170
170
  const studioViewSlugs = useMemo(() => {
171
171
  const slugs = new Set<string>();
172
172
  (registry.studioConfig?.devViews ?? []).forEach(v => slugs.add(v.slug));
@@ -176,7 +176,7 @@ function CMSNavigationContent() {
176
176
  }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
177
177
 
178
178
  // Studio mode shows Studio dev views + admin entries (Users/Roles).
179
- // Content mode shows collections, CMS custom views, and admin entries — but not Studio dev views.
179
+ // Content mode shows collections, admin custom views, and admin entries — but not Studio dev views.
180
180
  const filteredEntries = adminModeController.mode === "studio"
181
181
  ? allNavigationEntries.filter(e => (e.type === "view" && studioViewSlugs.has(e.slug)) || e.type === "admin")
182
182
  : allNavigationEntries.filter(e => e.type !== "view" || !studioViewSlugs.has(e.slug));
@@ -189,6 +189,16 @@ function CMSNavigationContent() {
189
189
  ...[...entryGroups].filter(g => !orderedGroups.includes(g))
190
190
  ];
191
191
 
192
+ // Group header icons, declared on `navigationGroupMappings`. Keyed by name
193
+ // because that is what a navigation entry's `group` holds.
194
+ const groupIcons = useMemo(() => {
195
+ const icons = new Map<string, string>();
196
+ (registry.cmsConfig?.navigationGroupMappings ?? []).forEach(mapping => {
197
+ if (mapping.icon) icons.set(mapping.name, mapping.icon);
198
+ });
199
+ return icons;
200
+ }, [registry.cmsConfig?.navigationGroupMappings]);
201
+
192
202
  // Collapsible groups state - using "drawer" namespace for independent state from home page
193
203
  const collapsedDefaults = useMemo(
194
204
  () => buildCollapsedDefaults(registry.cmsConfig?.navigationGroupMappings, "drawer"),
@@ -244,6 +254,7 @@ context });
244
254
  tooltipsOpen={tooltipsOpen}
245
255
  adminMenuOpen={adminMenuOpen}
246
256
  onItemClick={onItemClick}
257
+ icon={groupIcons.get(group)}
247
258
  />
248
259
  );
249
260
  })}
@@ -10,7 +10,7 @@ import {
10
10
  useSnackbarController,
11
11
  useTranslation
12
12
  } from "@rebasepro/app";
13
- import { useCMSContext } from "../hooks";
13
+ import { useAdminContext } from "../hooks";
14
14
  import { EntityViewBinding } from "./EntityViewBinding";
15
15
  import type { AdminCollection } from "@rebasepro/admin-types";
16
16
 
@@ -54,7 +54,7 @@ export function DeleteEntityDialog<M extends Record<string, unknown>>({
54
54
  const { t } = useTranslation();
55
55
  const [loading, setLoading] = useState(false);
56
56
 
57
- const context = useCMSContext();
57
+ const context = useAdminContext();
58
58
  const entityOrEntities = Array.isArray(entityOrEntitiesToDelete) && entityOrEntitiesToDelete.length === 1
59
59
  ? entityOrEntitiesToDelete[0]
60
60
  : entityOrEntitiesToDelete;
@@ -11,6 +11,7 @@ import { EntityViewBinding } from "./EntityViewBinding";
11
11
  import { CircularProgressCenter, iconSize } from "@rebasepro/ui";
12
12
  import {
13
13
  Alert,
14
+ ArrowLeftIcon,
14
15
  Button,
15
16
  CenteredView,
16
17
  cls,
@@ -470,7 +471,24 @@ entityId }
470
471
  );
471
472
  });
472
473
 
473
- const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog";
474
+ // Full screen is the one layout with no way out of its own: a side panel and a
475
+ // dialog can be dismissed, and a split keeps the list beside it, but opening an
476
+ // entity full screen replaces the collection entirely and left browser Back as
477
+ // the only route back to it — which is not an affordance the page shows, and is
478
+ // wrong anyway once the user has moved between tabs inside the entity.
479
+ const backToCollectionButton = layout === "full_screen" ? (
480
+ <Tooltip title={t("back")}>
481
+ <IconButton
482
+ size="small"
483
+ aria-label={t("back")}
484
+ onClick={() => navigate(urlController.buildUrlCollectionPath(path))}
485
+ >
486
+ <ArrowLeftIcon size={iconSize.smallest} />
487
+ </IconButton>
488
+ </Tooltip>
489
+ ) : null;
490
+
491
+ const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog" || Boolean(backToCollectionButton);
474
492
 
475
493
  const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? (
476
494
  <Tooltip title={"Open full screen"}>
@@ -562,6 +580,8 @@ entityId }
562
580
  {shouldShowTopBar && <div
563
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)}>
564
582
 
583
+ {backToCollectionButton}
584
+
565
585
  {fullScreenButton}
566
586
 
567
587
  {barActions?.({
@@ -2,7 +2,7 @@ import type { NavigationEntry } from "@rebasepro/admin-types";
2
2
  import React from "react";
3
3
  import { ChevronDownIcon, cls, iconSize, Typography } from "@rebasepro/ui";
4
4
 
5
- import { IconForView } from "@rebasepro/app";
5
+ import { IconForView, getIcon } from "@rebasepro/app";
6
6
  import { DrawerNavigationItem } from "./DrawerNavigationItem";
7
7
  import { useTranslation, useComponentOverride } from "@rebasepro/app";
8
8
 
@@ -47,6 +47,13 @@ export interface DrawerNavigationGroupProps {
47
47
  * Hide the group header (title and expandable panel)
48
48
  */
49
49
  hideHeader?: boolean;
50
+ /**
51
+ * Lucide icon name for the group header, from `NavigationGroupMapping.icon`.
52
+ *
53
+ * Supplying one moves the visual anchor from the rows to the group: the header
54
+ * gets the icon, and the entries below trade theirs for an indent.
55
+ */
56
+ icon?: string;
50
57
  }
51
58
 
52
59
  /**
@@ -63,10 +70,17 @@ export function DrawerNavigationGroup({
63
70
  adminMenuOpen,
64
71
  headerActions,
65
72
  onItemClick,
66
- hideHeader
73
+ hideHeader,
74
+ icon
67
75
  }: DrawerNavigationGroupProps) {
68
76
  const { t } = useTranslation();
69
77
  const ResolvedDrawerNavigationItem = useComponentOverride("Shell.DrawerNavigationItem", DrawerNavigationItem);
78
+
79
+ // Indent the entries only when the group itself is carrying the icon. Without a
80
+ // header there is nothing to indent under, and collapsed to a rail the entry
81
+ // icons are the only affordance left, so both cases keep the original rows.
82
+ const groupIcon = !hideHeader && drawerOpen ? getIcon(icon, undefined, undefined, "small") : undefined;
83
+ const indentEntries = Boolean(groupIcon);
70
84
  return (
71
85
  <div
72
86
  className={"my-2 mx-2 flex flex-col"}
@@ -83,14 +97,23 @@ export function DrawerNavigationGroup({
83
97
  <ChevronDownIcon
84
98
  size={iconSize.small}
85
99
  className={cls(
86
- "text-surface-400 dark:text-surface-400 transition-transform duration-200 mr-1",
100
+ "text-surface-500 dark:text-surface-400 transition-transform duration-200 mr-1",
87
101
  collapsed ? "-rotate-90" : "rotate-0"
88
102
  )}
89
103
  />
104
+ {groupIcon && (
105
+ <span className="shrink-0 mr-2 flex items-center text-surface-600 dark:text-surface-300 [&>svg]:size-4">
106
+ {groupIcon}
107
+ </span>
108
+ )}
90
109
  <Typography
91
110
  variant={"caption"}
92
111
  color={"secondary"}
93
- className="font-semibold text-[11px] uppercase tracking-wider flex-grow line-clamp-1 text-surface-400 dark:text-surface-400"
112
+ // A category label is the thing you scan to find anything else in
113
+ // the drawer, so it is read far more often than it is read *past*.
114
+ // At 11px uppercase in surface-400 it sat below the contrast of the
115
+ // rows it labels, which inverted that.
116
+ className="font-semibold text-[12px] uppercase tracking-wide flex-grow line-clamp-1 text-surface-600 dark:text-surface-300"
94
117
  >
95
118
  {(group || t("views_group"))}
96
119
  </Typography>
@@ -122,6 +145,7 @@ export function DrawerNavigationGroup({
122
145
  onClick={() => onItemClick?.(entry)}
123
146
  url={entry.url}
124
147
  name={entry.name}
148
+ indented={indentEntries}
125
149
  />
126
150
  ))}
127
151
  </div>
@@ -10,7 +10,8 @@ export function DrawerNavigationItem({
10
10
  adminMenuOpen,
11
11
  tooltipsOpen,
12
12
  url,
13
- onClick
13
+ onClick,
14
+ indented = false
14
15
  }: {
15
16
  icon: React.ReactElement,
16
17
  name: string,
@@ -19,12 +20,25 @@ export function DrawerNavigationItem({
19
20
  adminMenuOpen?: boolean,
20
21
  url: string,
21
22
  onClick?: () => void,
23
+ /**
24
+ * Render as a child of an icon-bearing group: no icon of its own, indented to
25
+ * sit under the group's label. Set by {@link DrawerNavigationGroup} only when
26
+ * the group actually has an icon and the drawer is expanded.
27
+ */
28
+ indented?: boolean,
22
29
  }) {
23
30
 
24
- const iconWrap = <div
25
- className={"shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4 group-hover/nav:text-primary transition-colors duration-150"}>
26
- {icon}
27
- </div>;
31
+ // Indented rows give up the icon rather than keeping it *and* indenting: a
32
+ // per-row icon repeated down a group is what flattens the hierarchy, since
33
+ // every row then reads at the weight of the category above it. The indent is
34
+ // the same 44px the icon occupied, so labels stay on the original grid and the
35
+ // rail width does not change.
36
+ const iconWrap = indented
37
+ ? <div className={"shrink-0 w-[44px] h-[30px]"} aria-hidden={true}/>
38
+ : <div
39
+ className={"shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4 group-hover/nav:text-primary transition-colors duration-150"}>
40
+ {icon}
41
+ </div>;
28
42
 
29
43
  const listItem = <div>
30
44
  <NavLink
@@ -20,7 +20,8 @@ import { SideDialogController, useSideDialogContext } from "./SideDialogs";
20
20
  import { FormexController } from "@rebasepro/forms";
21
21
  import { ErrorTooltip } from "@rebasepro/app";
22
22
  import { usePermissions } from "@rebasepro/app";
23
- import { useCMSContext } from "../hooks/useCMSContext";
23
+ import { getIcon } from "@rebasepro/app";
24
+ import { useAdminContext } from "../hooks/useAdminContext";
24
25
  import { useChildViewSource } from "../hooks/useChildViewSource";
25
26
 
26
27
  export function EditFormActions({
@@ -40,7 +41,7 @@ export function EditFormActions({
40
41
  }: EntityFormActionsProps) {
41
42
 
42
43
  const { canCreate, canDelete } = usePermissions();
43
- const context = useCMSContext();
44
+ const context = useAdminContext();
44
45
  const sidePanelController = context.sidePanelController;
45
46
  const sideDialogContext = useSideDialogContext();
46
47
  const customizationController = useCustomizationController();
@@ -371,7 +372,7 @@ function EntityActionButton({
371
372
  });
372
373
  }
373
374
  }}>
374
- {loading ? <CircularProgress size={"smallest"}/> : action.icon}
375
+ {loading ? <CircularProgress size={"smallest"}/> : getIcon(action.icon, undefined, undefined, "smallest")}
375
376
  </IconButton>
376
377
  </Tooltip>;
377
378
  }
@@ -70,7 +70,7 @@ export type OnTabChangeParams<M extends Record<string, unknown>> = {
70
70
 
71
71
  export interface EditViewBindingProps<M extends Record<string, unknown> = Record<string, unknown>> {
72
72
  /**
73
- * The CMS path of the entity, e.g. "users" or "products".
73
+ * The admin path of the entity, e.g. "users" or "products".
74
74
  */
75
75
  path: string;
76
76
  collection: AdminCollection<M>;
@@ -24,7 +24,7 @@ import { useRestoreScroll } from "@rebasepro/app";
24
24
 
25
25
  import { SchemaDriftBanner } from "@rebasepro/app";
26
26
  import { useBreadcrumbsController } from "../../hooks/useBreadcrumbsController";
27
- import { useCMSContext } from "../../hooks/useCMSContext";
27
+ import { useAdminContext } from "../../hooks/useAdminContext";
28
28
 
29
29
  export const DEFAULT_GROUP_NAME = "Views";
30
30
  export const ADMIN_GROUP_NAME = "Admin";
@@ -43,7 +43,7 @@ export function ContentHomePage({
43
43
  hiddenGroups?: string[];
44
44
  }) {
45
45
 
46
- const context = useCMSContext();
46
+ const context = useAdminContext();
47
47
  const { navigationStateController } = context;
48
48
  const customizationController = useCustomizationController();
49
49
  const adminModeController = useAdminModeController();
@@ -65,7 +65,7 @@ export function ContentHomePage({
65
65
  } = navigationStateController.topLevelNavigation || {};
66
66
 
67
67
  // Build a set of Studio-only dev view slugs so we can distinguish them from
68
- // CMS custom views added via <RebaseAdmin views={...}> or plugins.
68
+ // admin custom views added via <RebaseAdmin views={...}> or plugins.
69
69
  const studioViewSlugs = useMemo(() => {
70
70
  const slugs = new Set<string>();
71
71
  (registry.studioConfig?.devViews ?? []).forEach(v => slugs.add(v.slug));
@@ -74,7 +74,7 @@ export function ContentHomePage({
74
74
  }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
75
75
 
76
76
  // Studio mode shows Studio dev views + admin entries (Users/Roles).
77
- // Content mode shows collections, CMS custom views, and admin entries — but not Studio dev views.
77
+ // Content mode shows collections, admin custom views, and admin entries — but not Studio dev views.
78
78
  const rawNavigationEntries = useMemo(() => {
79
79
  if (adminModeController.mode === "studio") {
80
80
  return unFilteredNavigationEntries.filter(e => (e.type === "view" && studioViewSlugs.has(e.slug)) || e.type === "admin");