@rebasepro/admin 0.2.3 → 0.3.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 (210) hide show
  1. package/dist/{CollectionEditorDialog-CmGXXSY9.js → CollectionEditorDialog-D0VqpLPO.js} +70 -77
  2. package/dist/CollectionEditorDialog-D0VqpLPO.js.map +1 -0
  3. package/dist/{CollectionsStudioView-DcLHT5bU.js → CollectionsStudioView-Bc3Rxxc2.js} +5 -4
  4. package/dist/{CollectionsStudioView-DcLHT5bU.js.map → CollectionsStudioView-Bc3Rxxc2.js.map} +1 -1
  5. package/dist/{ExportCollectionAction-BfN34eWX.js → ExportCollectionAction-Ckc-09BQ.js} +4 -3
  6. package/dist/ExportCollectionAction-Ckc-09BQ.js.map +1 -0
  7. package/dist/{ImportCollectionAction-SZrInjhx.js → ImportCollectionAction-BqjIrC3Z.js} +3 -2
  8. package/dist/{ImportCollectionAction-SZrInjhx.js.map → ImportCollectionAction-BqjIrC3Z.js.map} +1 -1
  9. package/dist/{PropertyEditView-Cvryrb3B.js → PropertyEditView-CvRSV-A2.js} +128 -121
  10. package/dist/PropertyEditView-CvRSV-A2.js.map +1 -0
  11. package/dist/collection_editor/ConfigControllerProvider.d.ts +0 -5
  12. package/dist/collection_editor/index.d.ts +0 -1
  13. package/dist/collection_editor/types/collection_editor_controller.d.ts +0 -2
  14. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +3 -3
  15. package/dist/collection_editor_ui.js +3 -3
  16. package/dist/components/ArrayContainer.d.ts +2 -2
  17. package/dist/components/DefaultAppBar.d.ts +18 -1
  18. package/dist/components/DefaultDrawer.d.ts +51 -3
  19. package/dist/components/EntityCollectionTable/fields/TableStorageUpload.d.ts +2 -2
  20. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +2 -2
  21. package/dist/components/EntityCollectionTable/table_bindings.d.ts +4 -3
  22. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +4 -3
  23. package/dist/components/EntityEditView.d.ts +2 -1
  24. package/dist/components/HomePage/HomePageDnD.d.ts +3 -3
  25. package/dist/components/PropertyCollectionView.d.ts +1 -1
  26. package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
  27. package/dist/components/SelectableTable/SelectionStore.d.ts +4 -1
  28. package/dist/components/SelectableTable/filters/BooleanFilterField.d.ts +2 -2
  29. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -2
  30. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -2
  31. package/dist/components/SelectableTable/filters/StringNumberFilterField.d.ts +2 -2
  32. package/dist/components/admin/RolesView.d.ts +3 -3
  33. package/dist/components/admin/UsersView.d.ts +3 -3
  34. package/dist/components/app/Drawer.d.ts +8 -1
  35. package/dist/data_export/export/export.d.ts +3 -3
  36. package/dist/editor/components/editor-bubble.d.ts +5 -1
  37. package/dist/editor/components/image-bubble.d.ts +5 -1
  38. package/dist/editor/components/index.d.ts +3 -3
  39. package/dist/editor/components/table-bubble.d.ts +5 -1
  40. package/dist/editor/nodeViews/ReactNodeView.d.ts +4 -1
  41. package/dist/editor/useProseMirror.d.ts +2 -2
  42. package/dist/editor/utils/remove_classes.d.ts +1 -1
  43. package/dist/editor.js +15 -14
  44. package/dist/editor.js.map +1 -1
  45. package/dist/form/EntityForm.d.ts +2 -2
  46. package/dist/form/components/StorageUploadProgress.d.ts +2 -2
  47. package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +1 -1
  48. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
  49. package/dist/form/validation.d.ts +3 -3
  50. package/dist/hooks/navigation/useResolvedCollections.d.ts +6 -0
  51. package/dist/hooks/navigation/useResolvedViews.d.ts +3 -4
  52. package/dist/{index-PLIQXpTt.js → index-BCcLwgfe.js} +3 -2
  53. package/dist/{index-PLIQXpTt.js.map → index-BCcLwgfe.js.map} +1 -1
  54. package/dist/{index-DjduZG1T.js → index-DY2k5TtG.js} +3 -3
  55. package/dist/index-DY2k5TtG.js.map +1 -0
  56. package/dist/{index-MKPc70-v.js → index-UQOMHwt1.js} +3 -3
  57. package/dist/index-UQOMHwt1.js.map +1 -0
  58. package/dist/index.d.ts +1 -1
  59. package/dist/index.js +2813 -372
  60. package/dist/index.js.map +1 -1
  61. package/dist/{markdown-z2Ir7Cgo.js → markdown-DD2JDU1X.js} +2 -2
  62. package/dist/markdown-DD2JDU1X.js.map +1 -0
  63. package/dist/preview/components/UrlComponentPreview.d.ts +1 -0
  64. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  65. package/dist/types/components/EntityFormProps.d.ts +2 -2
  66. package/dist/types/fields.d.ts +1 -1
  67. package/dist/{util-DbWax_sV.js → util-ZM9gQuCv.js} +2031 -2078
  68. package/dist/util-ZM9gQuCv.js.map +1 -0
  69. package/package.json +8 -8
  70. package/src/collection_editor/ConfigControllerProvider.tsx +3 -13
  71. package/src/collection_editor/index.ts +1 -3
  72. package/src/collection_editor/types/collection_editor_controller.tsx +0 -3
  73. package/src/collection_editor/ui/EditorCollectionAction.tsx +1 -6
  74. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +1 -6
  75. package/src/collection_editor/ui/EditorEntityAction.tsx +1 -6
  76. package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +7 -14
  77. package/src/collection_editor/ui/NewCollectionCard.tsx +1 -5
  78. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +3 -8
  79. package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +8 -12
  80. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +21 -21
  81. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +4 -4
  82. package/src/collection_editor/ui/collection_editor/EnumForm.tsx +1 -1
  83. package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +3 -3
  84. package/src/collection_editor/ui/collection_editor/properties/CommonPropertyFields.tsx +3 -3
  85. package/src/collection_editor/ui/collection_editor/properties/DateTimePropertyField.tsx +8 -8
  86. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +5 -5
  87. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  88. package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +5 -5
  89. package/src/collection_editor/ui/collection_editor/properties/NumberPropertyField.tsx +5 -5
  90. package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -2
  91. package/src/collection_editor/ui/collection_editor/properties/RepeatPropertyField.tsx +2 -2
  92. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +8 -8
  93. package/src/collection_editor/ui/collection_editor/properties/StringPropertyField.tsx +5 -5
  94. package/src/collection_editor/ui/collection_editor/properties/UrlPropertyField.tsx +3 -2
  95. package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +2 -2
  96. package/src/collection_editor/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
  97. package/src/collection_editor/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +1 -1
  98. package/src/collection_editor/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +4 -7
  99. package/src/collection_editor/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +4 -4
  100. package/src/components/ArrayContainer.tsx +3 -3
  101. package/src/components/DefaultAppBar.tsx +52 -31
  102. package/src/components/DefaultDrawer.tsx +279 -66
  103. package/src/components/DrawerNavigationItem.tsx +1 -1
  104. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +6 -5
  105. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +9 -7
  106. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +5 -5
  107. package/src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx +12 -9
  108. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +2 -2
  109. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +1 -1
  110. package/src/components/EntityCollectionTable/table_bindings.tsx +5 -4
  111. package/src/components/EntityCollectionView/EntityCollectionView.tsx +6 -4
  112. package/src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts +1 -1
  113. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +7 -6
  114. package/src/components/EntityDetailView.tsx +46 -24
  115. package/src/components/EntityEditView.tsx +51 -28
  116. package/src/components/EntityEditViewFormActions.tsx +4 -4
  117. package/src/components/EntityPreview.tsx +9 -4
  118. package/src/components/HomePage/HomePageDnD.tsx +3 -2
  119. package/src/components/PropertyCollectionView.tsx +1 -1
  120. package/src/components/PropertyIdCopyTooltip.tsx +1 -1
  121. package/src/components/RebaseLayout.tsx +5 -1
  122. package/src/components/RebaseNavigation.tsx +2 -2
  123. package/src/components/RebaseRouteDefs.tsx +4 -7
  124. package/src/components/RebaseShell.tsx +16 -13
  125. package/src/components/SearchIconsView.tsx +1 -8
  126. package/src/components/SelectableTable/SelectableTable.tsx +8 -11
  127. package/src/components/SelectableTable/SelectionStore.ts +1 -1
  128. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +3 -3
  129. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +3 -3
  130. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +5 -5
  131. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +3 -3
  132. package/src/components/SideEntityProvider.tsx +2 -1
  133. package/src/components/admin/RolesView.tsx +7 -2
  134. package/src/components/admin/UsersView.tsx +12 -6
  135. package/src/components/app/Drawer.tsx +9 -1
  136. package/src/components/app/Scaffold.tsx +5 -1
  137. package/src/data_export/export/export.ts +17 -17
  138. package/src/data_import/components/DataNewPropertiesMapping.tsx +1 -1
  139. package/src/editor/components/editor-bubble.tsx +32 -9
  140. package/src/editor/components/image-bubble.tsx +27 -11
  141. package/src/editor/components/index.ts +3 -3
  142. package/src/editor/components/table-bubble.tsx +79 -17
  143. package/src/editor/extensions/HighlightDecorationExtension.ts +3 -2
  144. package/src/editor/nodeViews/ReactNodeView.tsx +1 -1
  145. package/src/editor/nodeViews/TaskItemComponent.tsx +9 -8
  146. package/src/editor/schema.ts +135 -59
  147. package/src/editor/selectors/link-selector.tsx +8 -5
  148. package/src/editor/useProseMirror.ts +2 -2
  149. package/src/editor/utils/remove_classes.ts +6 -5
  150. package/src/form/EntityForm.tsx +15 -15
  151. package/src/form/EntityFormActions.tsx +2 -2
  152. package/src/form/PropertyFieldBinding.tsx +64 -64
  153. package/src/form/components/FieldHelperText.tsx +4 -4
  154. package/src/form/components/StorageUploadProgress.tsx +2 -2
  155. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +1 -1
  156. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
  157. package/src/form/field_bindings/BlockFieldBinding.tsx +54 -53
  158. package/src/form/field_bindings/KeyValueFieldBinding.tsx +290 -289
  159. package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
  160. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -2
  161. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +1 -1
  162. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +1 -1
  163. package/src/form/field_bindings/ReferenceFieldBinding.tsx +8 -6
  164. package/src/form/field_bindings/RelationFieldBinding.tsx +4 -4
  165. package/src/form/field_bindings/RepeatFieldBinding.tsx +1 -1
  166. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  167. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +84 -84
  168. package/src/form/field_bindings/SwitchFieldBinding.tsx +16 -16
  169. package/src/form/field_bindings/TextFieldBinding.tsx +77 -73
  170. package/src/form/field_bindings/UserSelectFieldBinding.tsx +17 -17
  171. package/src/form/validation.ts +43 -43
  172. package/src/hooks/navigation/useBuildNavigationStateController.tsx +3 -5
  173. package/src/hooks/navigation/useResolvedCollections.ts +27 -7
  174. package/src/hooks/navigation/useResolvedViews.tsx +24 -44
  175. package/src/index.ts +2 -0
  176. package/src/preview/PropertyPreview.tsx +2 -2
  177. package/src/preview/components/ImagePreview.tsx +2 -1
  178. package/src/preview/components/UrlComponentPreview.tsx +11 -2
  179. package/src/preview/components/UserPreview.tsx +1 -1
  180. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +2 -2
  181. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -4
  182. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +3 -3
  183. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +3 -3
  184. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +3 -2
  185. package/src/preview/property_previews/ArrayOneOfPreview.tsx +6 -8
  186. package/src/preview/property_previews/ArrayPropertyEnumPreview.tsx +1 -1
  187. package/src/preview/property_previews/ArrayPropertyPreview.tsx +3 -3
  188. package/src/preview/property_previews/MapPropertyPreview.tsx +4 -3
  189. package/src/preview/property_previews/NumberPropertyPreview.tsx +5 -3
  190. package/src/preview/property_previews/StringPropertyPreview.tsx +10 -8
  191. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  192. package/src/types/components/EntityFormProps.tsx +2 -2
  193. package/src/types/fields.tsx +2 -2
  194. package/dist/CollectionEditorDialog-CmGXXSY9.js.map +0 -1
  195. package/dist/ContentHomePage-C7vFqKSe.js +0 -1784
  196. package/dist/ContentHomePage-C7vFqKSe.js.map +0 -1
  197. package/dist/ExportCollectionAction-BfN34eWX.js.map +0 -1
  198. package/dist/PropertyEditView-Cvryrb3B.js.map +0 -1
  199. package/dist/RoleChip-QtUFXeTp.js +0 -67
  200. package/dist/RoleChip-QtUFXeTp.js.map +0 -1
  201. package/dist/RolesView-BCb7qwWs.js +0 -437
  202. package/dist/RolesView-BCb7qwWs.js.map +0 -1
  203. package/dist/UsersView-Cex24r8H.js +0 -408
  204. package/dist/UsersView-Cex24r8H.js.map +0 -1
  205. package/dist/collection_editor/types/config_permissions.d.ts +0 -19
  206. package/dist/index-DjduZG1T.js.map +0 -1
  207. package/dist/index-MKPc70-v.js.map +0 -1
  208. package/dist/markdown-z2Ir7Cgo.js.map +0 -1
  209. package/dist/util-DbWax_sV.js.map +0 -1
  210. package/src/collection_editor/types/config_permissions.ts +0 -20
@@ -1,32 +1,77 @@
1
1
  import type { NavigationEntry, NavigationResult } from "@rebasepro/types";
2
2
  import React, { useMemo } from "react";
3
3
 
4
- import { useCollapsedGroups, buildCollapsedDefaults, useLargeLayout, useAdminModeController, useTranslation, useSlot, useRebaseContext, useAnalyticsController, useRebaseRegistry } from "@rebasepro/core";
4
+ import { useCollapsedGroups, buildCollapsedDefaults, useLargeLayout, useAdminModeController, useAuthController, useModeController, useTranslation, useSlot, useRebaseContext, useAnalyticsController, useRebaseRegistry } from "@rebasepro/core";
5
5
  import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
6
6
  import { useNavigationStateController } from "../hooks/navigation/contexts/NavigationStateContext";
7
7
 
8
8
 
9
9
  import { Link, useNavigate } from "react-router-dom";
10
10
  import { AnalyticsEvent } from "@rebasepro/types";
11
- import { ChevronsLeftIcon, ChevronsRightIcon, cls, iconSize, Tooltip, Typography } from "@rebasepro/ui";
11
+ import {
12
+ Avatar,
13
+ ChevronsLeftIcon,
14
+ ChevronsRightIcon,
15
+ cls,
16
+ iconSize,
17
+ IconButton,
18
+ LanguagesIcon,
19
+ LogOutIcon,
20
+ Menu,
21
+ MenuItem,
22
+ MoonIcon,
23
+ Separator,
24
+ SettingsIcon,
25
+ Skeleton,
26
+ SunIcon,
27
+ SunMoonIcon,
28
+ Tooltip,
29
+ Typography
30
+ } from "@rebasepro/ui";
12
31
  import { DrawerNavigationGroup } from "./DrawerNavigationGroup";
13
- import { RebaseLogo } from "@rebasepro/core";
32
+ import { LanguageToggle, RebaseLogo } from "@rebasepro/core";
14
33
  import { useApp } from "./app/useApp";
15
34
 
16
35
  /**
17
- * Default drawer used in the CMS
36
+ * Default drawer used in the CMS.
37
+ *
38
+ * When no `children` are provided, renders the full CMS navigation
39
+ * (collection groups, mode switch, plugin slots).
40
+ *
41
+ * When `children` **are** provided, renders the shared drawer shell
42
+ * (logo, scrollable area, footer actions, collapse toggle) with the
43
+ * custom content injected in the scrollable area. This lets consumers
44
+ * like the SaaS dashboard reuse the identical structural layout while
45
+ * supplying their own navigation items.
46
+ *
18
47
  * @group Core
19
48
  */
20
49
  export function DefaultDrawer({
21
50
  title,
22
51
  logo,
23
52
  logoDestination,
53
+ children,
54
+ footerActions,
24
55
  className,
25
56
  style
26
57
  }: {
27
58
  title?: React.ReactNode;
28
59
  logo?: string;
29
60
  logoDestination?: string;
61
+ /**
62
+ * Custom navigation content for the drawer.
63
+ * When provided, replaces the default CMS navigation (collection groups,
64
+ * mode switch, slots). The shared shell (logo, scroll area, footer
65
+ * actions, collapse toggle) is still rendered around it.
66
+ */
67
+ children?: React.ReactNode;
68
+ /**
69
+ * Custom content for the drawer footer actions area (language, theme, user menu).
70
+ * - `undefined` — renders the default `DrawerFooterActions`.
71
+ * - `null` — renders nothing (hides the footer actions).
72
+ * - `ReactNode` — renders the provided custom content.
73
+ */
74
+ footerActions?: React.ReactNode | null;
30
75
  className?: string;
31
76
  style?: React.CSSProperties;
32
77
  }) {
@@ -36,13 +81,11 @@ export function DefaultDrawer({
36
81
  drawerOpen,
37
82
  openDrawer,
38
83
  closeDrawer,
39
- closeHover,
40
84
  logo: appLogo
41
85
  } = useApp();
42
86
 
43
87
  const resolvedLogo = logo ?? appLogo;
44
88
 
45
- const [adminMenuOpen, setAdminMenuOpen] = React.useState(false);
46
89
  const scrollRef = React.useRef<HTMLDivElement>(null);
47
90
  const [scrolled, setScrolled] = React.useState(false);
48
91
 
@@ -52,6 +95,64 @@ export function DefaultDrawer({
52
95
  }
53
96
  };
54
97
 
98
+ return (
99
+ <div role="navigation" aria-label="Main navigation" className={cls("flex flex-col h-full relative grow w-full", className)} style={style}>
100
+
101
+ <DrawerLogo
102
+ logo={resolvedLogo}
103
+ title={title}
104
+ logoDestination={logoDestination}
105
+ drawerOpen={drawerOpen}
106
+ drawerHovered={drawerHovered}
107
+ />
108
+
109
+ <div
110
+ ref={scrollRef}
111
+ onScroll={handleScroll}
112
+ className={"flex-grow min-h-0 overflow-y-auto overflow-x-hidden no-scrollbar px-2"}
113
+ style={{
114
+ maskImage: scrolled
115
+ ? "linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%)"
116
+ : "linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%)"
117
+ }}>
118
+ {children ?? <CMSNavigationContent />}
119
+ </div>
120
+
121
+ {footerActions !== null && (
122
+ footerActions !== undefined
123
+ ? footerActions
124
+ : <DrawerFooterActions
125
+ drawerOpen={drawerOpen}
126
+ drawerHovered={drawerHovered}
127
+ />
128
+ )}
129
+
130
+ <DrawerToggle
131
+ drawerOpen={drawerOpen}
132
+ drawerHovered={drawerHovered}
133
+ openDrawer={openDrawer}
134
+ closeDrawer={closeDrawer}
135
+ />
136
+ </div>
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Default CMS navigation content — rendered inside DefaultDrawer when no
142
+ * custom `children` are provided. Contains the mode switch, navigation
143
+ * groups, and header / footer plugin slots.
144
+ */
145
+ function CMSNavigationContent() {
146
+
147
+ const {
148
+ drawerHovered,
149
+ drawerOpen,
150
+ closeDrawer,
151
+ closeHover
152
+ } = useApp();
153
+
154
+ const [adminMenuOpen, setAdminMenuOpen] = React.useState(false);
155
+
55
156
  const analyticsController = useAnalyticsController();
56
157
  const navigationState = useNavigationStateController();
57
158
  const context = useRebaseContext();
@@ -94,8 +195,6 @@ context });
94
195
  if (!navigationState.topLevelNavigation)
95
196
  return null;
96
197
 
97
- const groups = navigationState.topLevelNavigation.groups;
98
-
99
198
  const onItemClick = (view: NavigationEntry) => {
100
199
  const eventName: AnalyticsEvent = view.type === "collection"
101
200
  ? "drawer_navigate_to_collection"
@@ -108,69 +207,37 @@ context });
108
207
  }
109
208
  };
110
209
 
111
- const isStudioDark = adminModeController.mode === "studio";
112
210
  const drawerVisuallyOpen = drawerOpen || drawerHovered;
113
211
 
114
212
  return (
115
213
  <>
116
- <div role="navigation" aria-label="Main navigation" className={cls("flex flex-col h-full relative grow w-full", className)} style={style}>
117
-
118
- <DrawerLogo
119
- logo={resolvedLogo}
120
- title={title}
121
- logoDestination={logoDestination}
214
+ {registry.studioConfig && (
215
+ <DrawerModeSwitch
122
216
  drawerOpen={drawerOpen}
123
217
  drawerHovered={drawerHovered}
124
218
  />
219
+ )}
125
220
 
126
- {registry.studioConfig && (
127
- <DrawerModeSwitch
128
- drawerOpen={drawerOpen}
129
- drawerHovered={drawerHovered}
221
+ {headerSlot}
222
+
223
+ {groupsToRender.map((group) => {
224
+ const entriesInGroup = filteredEntries.filter(e => e.group === group);
225
+ return (
226
+ <DrawerNavigationGroup
227
+ key={`drawer_group_${group}`}
228
+ group={group}
229
+ entries={entriesInGroup}
230
+ collapsed={isGroupCollapsed(group)}
231
+ onToggleCollapsed={() => toggleGroupCollapsed(group)}
232
+ drawerOpen={drawerVisuallyOpen}
233
+ tooltipsOpen={tooltipsOpen}
234
+ adminMenuOpen={adminMenuOpen}
235
+ onItemClick={onItemClick}
130
236
  />
131
- )}
132
-
133
- {headerSlot}
134
-
135
- <div
136
- ref={scrollRef}
137
- onScroll={handleScroll}
138
- className={"flex-grow min-h-0 overflow-y-auto overflow-x-hidden no-scrollbar"}
139
- style={{
140
- maskImage: scrolled
141
- ? "linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%)"
142
- : "linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%)"
143
- }}>
144
-
145
- {groupsToRender.map((group) => {
146
- const entriesInGroup = filteredEntries.filter(e => e.group === group);
147
- return (
148
- <DrawerNavigationGroup
149
- key={`drawer_group_${group}`}
150
- group={group}
151
- entries={entriesInGroup}
152
- collapsed={isGroupCollapsed(group)}
153
- onToggleCollapsed={() => toggleGroupCollapsed(group)}
154
- drawerOpen={drawerVisuallyOpen}
155
- tooltipsOpen={tooltipsOpen}
156
- adminMenuOpen={adminMenuOpen}
157
- onItemClick={onItemClick}
158
- />
159
- );
160
- })}
161
-
162
- </div>
163
-
164
- {footerSlot}
165
-
166
- <DrawerToggle
167
- drawerOpen={drawerOpen}
168
- drawerHovered={drawerHovered}
169
- openDrawer={openDrawer}
170
- closeDrawer={closeDrawer}
171
- />
172
- </div>
237
+ );
238
+ })}
173
239
 
240
+ {footerSlot}
174
241
  </>
175
242
  );
176
243
  }
@@ -178,7 +245,7 @@ context });
178
245
  export function DrawerLogo({
179
246
  logo,
180
247
  title,
181
- logoDestination,
248
+ logoDestination = "/",
182
249
  drawerOpen,
183
250
  drawerHovered
184
251
  }: {
@@ -189,7 +256,6 @@ export function DrawerLogo({
189
256
  drawerHovered: boolean;
190
257
  }) {
191
258
 
192
- const urlController = useUrlController();
193
259
  const showFullContent = drawerOpen || (drawerHovered && !drawerOpen);
194
260
 
195
261
  return (
@@ -197,7 +263,7 @@ export function DrawerLogo({
197
263
  {/* Logo — always visible */}
198
264
  <Link
199
265
  className="shrink-0 flex items-center justify-center w-[56px] h-[40px]"
200
- to={logoDestination || urlController.basePath}
266
+ to={logoDestination}
201
267
  >
202
268
  {logo
203
269
  ? <img src={logo} alt="Logo" className="w-[28px] h-[28px] object-contain"/>
@@ -215,7 +281,7 @@ export function DrawerLogo({
215
281
  {title && (
216
282
  <Link
217
283
  className="visited:text-inherit dark:visited:text-inherit block truncate"
218
- to={logoDestination || urlController.basePath}
284
+ to={logoDestination}
219
285
  >
220
286
  {typeof title === "string"
221
287
  ? <Typography variant="subtitle1" noWrap className="truncate">{title}</Typography>
@@ -271,8 +337,18 @@ export function DrawerToggle({
271
337
  aria-expanded={isExpanded}
272
338
  aria-label={isExpanded ? t("collapse") : t("expand")}
273
339
  onClick={() => isExpanded ? closeDrawer() : openDrawer()}
340
+ onKeyDown={(e) => {
341
+ if (e.key === "Enter" || e.key === " ") {
342
+ e.preventDefault();
343
+ if (isExpanded) {
344
+ closeDrawer();
345
+ } else {
346
+ openDrawer();
347
+ }
348
+ }
349
+ }}
274
350
  >
275
- <div className="shrink-0 flex items-center justify-center w-[56px] h-[24px] text-surface-500 dark:text-surface-400">
351
+ <div className="shrink-0 flex items-center justify-center w-[44px] h-[24px] text-surface-500 dark:text-surface-400">
276
352
  {isExpanded
277
353
  ? <ChevronsLeftIcon size={iconSize.small}/>
278
354
  : <ChevronsRightIcon size={iconSize.small}/>
@@ -353,3 +429,140 @@ function DrawerModeSwitch({
353
429
  </div>
354
430
  );
355
431
  }
432
+
433
+ /**
434
+ * Footer actions rendered at the bottom of the drawer, above the collapse/expand toggle.
435
+ * Replaces the app bar icons (language, theme, user avatar) with a drawer-native layout
436
+ * that adapts between collapsed (icons only) and expanded (full labels + user bar) states.
437
+ *
438
+ * @group Core
439
+ */
440
+ export function DrawerFooterActions({
441
+ drawerOpen,
442
+ drawerHovered,
443
+ /**
444
+ * Custom content to render inside the user dropdown menu.
445
+ * When provided, these items are appended after the user info header
446
+ * and before the default Sign Out item.
447
+ */
448
+ dropDownActions,
449
+ /**
450
+ * Override the user object displayed in the footer.
451
+ * When omitted, falls back to `authController.user`.
452
+ */
453
+ user: userProp
454
+ }: {
455
+ drawerOpen: boolean;
456
+ drawerHovered: boolean;
457
+ dropDownActions?: React.ReactNode;
458
+ user?: import("@rebasepro/types").User;
459
+ }) {
460
+ const authController = useAuthController();
461
+ const {
462
+ mode,
463
+ setMode
464
+ } = useModeController();
465
+ const navigate = useNavigate();
466
+ const { t } = useTranslation();
467
+
468
+ const user = userProp ?? authController.user;
469
+ const showFullContent = drawerOpen || drawerHovered;
470
+ const isFloating = drawerHovered && !drawerOpen;
471
+ const portalRef = React.useRef<HTMLDivElement>(null);
472
+
473
+ // User avatar
474
+ let avatarComponent: React.ReactElement | null;
475
+ if (user) {
476
+ const initial = user?.displayName
477
+ ? user.displayName[0].toUpperCase()
478
+ : (user?.email ? user.email[0].toUpperCase() : "A");
479
+ avatarComponent = <Avatar src={user.photoURL ?? undefined} className="w-8 h-8 text-xs">
480
+ {initial}
481
+ </Avatar>;
482
+ } else if (user === undefined || authController.initialLoading) {
483
+ avatarComponent = <Skeleton className="w-8 h-8 rounded-full"/>;
484
+ } else {
485
+ avatarComponent = null;
486
+ }
487
+
488
+ return (
489
+ <div className="shrink-0 pb-1" ref={portalRef}>
490
+ {avatarComponent && (
491
+ <div className="flex items-center px-[16px] py-1">
492
+ <Menu
493
+ trigger={
494
+ <div
495
+ className={cls(
496
+ "shrink-0 flex items-center justify-center w-[44px] cursor-pointer",
497
+ "rounded-md py-1",
498
+ "hover:bg-surface-accent-100 dark:hover:bg-surface-800",
499
+ "transition-colors duration-150"
500
+ )}
501
+ role="button"
502
+ tabIndex={0}
503
+ aria-label={t("user_menu") || "User menu"}
504
+ >
505
+ {avatarComponent}
506
+ </div>
507
+ }
508
+ side="top"
509
+ align="start"
510
+ >
511
+ {user && <div className="px-4 py-2 mb-1">
512
+ {user.displayName && <Typography variant="body1" color="secondary">
513
+ {user.displayName}
514
+ </Typography>}
515
+ {user.email && <Typography variant="body2" color="secondary">
516
+ {user.email}
517
+ </Typography>}
518
+ </div>}
519
+
520
+ {dropDownActions}
521
+
522
+ {!dropDownActions && <>
523
+ <MenuItem onClick={() => navigate("/settings")}>
524
+ <SettingsIcon/>
525
+ {t("account_settings")}
526
+ </MenuItem>
527
+ <MenuItem onClick={async () => {
528
+ await authController.signOut();
529
+ navigate("/");
530
+ }}>
531
+ <LogOutIcon/>
532
+ {t("log_out")}
533
+ </MenuItem>
534
+ </>}
535
+ </Menu>
536
+
537
+ {/* Language + Theme — only when expanded */}
538
+ {showFullContent && (
539
+ <div className="flex items-center gap-0">
540
+ <LanguageToggle/>
541
+ <Menu
542
+ trigger={
543
+ <IconButton
544
+ color="inherit"
545
+ aria-label={t("toggle_theme") || "Toggle theme"}
546
+ className="text-surface-500 dark:text-surface-400"
547
+ >
548
+ {mode === "dark"
549
+ ? <MoonIcon size={iconSize.small}/>
550
+ : mode === "light"
551
+ ? <SunIcon size={iconSize.small}/>
552
+ : <SunMoonIcon size={iconSize.small}/>}
553
+ </IconButton>
554
+ }
555
+ portalContainer={portalRef.current}
556
+ side="top"
557
+ >
558
+ <MenuItem onClick={() => setMode("dark")}><MoonIcon size={iconSize.smallest}/> {t("dark_mode")}</MenuItem>
559
+ <MenuItem onClick={() => setMode("light")}><SunIcon size={iconSize.smallest}/> {t("light_mode")}</MenuItem>
560
+ <MenuItem onClick={() => setMode("system")}><SunMoonIcon size={iconSize.smallest}/> {t("system_mode")}</MenuItem>
561
+ </Menu>
562
+ </div>
563
+ )}
564
+ </div>
565
+ )}
566
+ </div>
567
+ );
568
+ }
@@ -33,7 +33,7 @@ export function DrawerNavigationItem({
33
33
  width: "100%",
34
34
  transition: drawerOpen ? "width 150ms ease-in" : undefined
35
35
  }}
36
- className={({ isActive }: any) => cls("rounded-md truncate",
36
+ className={({ isActive }: { isActive: boolean }) => cls("rounded-md truncate",
37
37
  "hover:bg-surface-100 dark:hover:bg-surface-800/60 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white",
38
38
  "flex flex-row items-center",
39
39
  drawerOpen ? "pr-4 h-[30px]" : "h-[30px]",
@@ -1,6 +1,6 @@
1
1
  import type { AdditionalFieldDelegate } from "@rebasepro/types";
2
2
  import React, { useCallback, useMemo, useRef } from "react";
3
- import { CollectionSize, Entity, RebaseContext, User } from "@rebasepro/types";
3
+ import { CollectionSize, Entity, RebaseContext, User, Property } from "@rebasepro/types";
4
4
  import { PropertyTableCell } from "./PropertyTableCell";
5
5
  import { ErrorBoundary } from "@rebasepro/ui";
6
6
  import { useRebaseContext, useLargeLayout } from "@rebasepro/core";
@@ -138,11 +138,12 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
138
138
 
139
139
  const propertyKey = column.key;
140
140
 
141
- let disabled = column.custom?.disabled;
141
+ const columnCustom = column.custom as { disabled?: boolean; resolvedProperty?: Property } | undefined;
142
+ let disabled: boolean = columnCustom?.disabled ?? false;
142
143
  const property = getPropertyFor?.({
143
144
  propertyKey,
144
145
  entity
145
- }) ?? column.custom.resolvedProperty;
146
+ }) ?? columnCustom?.resolvedProperty;
146
147
  if (!property?.ui?.disabled) {
147
148
  disabled = false;
148
149
  }
@@ -231,7 +232,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
231
232
  disabledTooltip={"This column can't be edited directly"}
232
233
  sortableNodeRef={sortableNodeRef}
233
234
  sortableStyle={sortableStyle}
234
- sortableAttributes={sortableAttributes}
235
+ sortableAttributes={sortableAttributes as Record<string, string | number | undefined>}
235
236
  isDragging={isDragging}
236
237
  isDraggable={isDraggable}
237
238
  frozen={frozen}
@@ -337,7 +338,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
337
338
  disabled={true}
338
339
  sortableNodeRef={props.sortableNodeRef}
339
340
  sortableStyle={props.sortableStyle}
340
- sortableAttributes={props.sortableAttributes}
341
+ sortableAttributes={props.sortableAttributes as Record<string, string | number | undefined>}
341
342
  isDragging={props.isDragging}
342
343
  isDraggable={props.isDraggable}
343
344
  frozen={props.frozen}>
@@ -1,4 +1,4 @@
1
- import { useCellSelected } from "../SelectableTable/SelectionStore";
1
+ import { useCellSelected, createSelectionStore } from "../SelectableTable/SelectionStore";
2
2
  import type { ArrayProperty, NumberProperty, Property, ReferenceProperty, StringProperty } from "@rebasepro/types";
3
3
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { deepEqual as equal } from "fast-equals"
@@ -76,7 +76,9 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
76
76
  setPopupCell
77
77
  } = useSelectableTableController();
78
78
 
79
- const selected = useCellSelected(selectionStore, propertyKey, entity.path, entity.id);
79
+ const dummySelectionStore = useMemo(() => createSelectionStore(), []);
80
+ const activeSelectionStore = selectionStore || dummySelectionStore;
81
+ const selected = useCellSelected(activeSelectionStore, propertyKey, entity.path, entity.id);
80
82
 
81
83
  const [internalValue, setInternalValue] = useState<any | null>(value);
82
84
  const internalValueRef = useRef(value);
@@ -114,13 +116,13 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
114
116
  [onValueUpdated, value]
115
117
  );
116
118
 
117
- const saveValues = async (value: any) => {
119
+ const saveValues = async (value: unknown) => {
118
120
  if (equal(value, internalValueRef.current))
119
121
  return;
120
122
  const result = await validation.safeParseAsync(value);
121
123
  if (result.success) {
122
124
  setValidationError(undefined);
123
- internalValueRef.current = value;
125
+ internalValueRef.current = value as T;
124
126
  if (onValueChange) {
125
127
  try {
126
128
  onValueChange({
@@ -153,9 +155,9 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
153
155
  });
154
156
  }, [internalValue, validation, propertyKey, property, entity]);
155
157
 
156
- const updateValue = (newValue: any | null) => {
158
+ const updateValue = (newValue: unknown | null) => {
157
159
 
158
- let updatedValue: any;
160
+ let updatedValue: unknown;
159
161
  if (newValue === undefined) {
160
162
  updatedValue = null;
161
163
  } else {
@@ -165,7 +167,7 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
165
167
  saveValues(updatedValue);
166
168
  };
167
169
 
168
- useClearRestoreValue<any>({
170
+ useClearRestoreValue<unknown>({
169
171
  property,
170
172
  value: internalValue,
171
173
  setValue: updateValue
@@ -34,7 +34,7 @@ export function TableStorageUpload(props: {
34
34
  selected: boolean;
35
35
  focused: boolean;
36
36
  property: StringProperty | ArrayProperty;
37
- entity: Entity<any>;
37
+ entity: Entity<Record<string, unknown>>;
38
38
  path: string;
39
39
  previewSize: PreviewSize;
40
40
  openPopup?: (cellRect?: DOMRect) => void;
@@ -112,13 +112,13 @@ interface StorageUploadProps {
112
112
  autoFocus: boolean;
113
113
  selected: boolean;
114
114
  disabled: boolean;
115
- entity: Entity<any>;
115
+ entity: Entity<Record<string, unknown>>;
116
116
  previewSize: PreviewSize;
117
117
  storage: StorageConfig;
118
118
  onFilesAdded: (acceptedFiles: File[]) => void;
119
119
  storagePathBuilder: (file: File) => string;
120
120
  openPopup?: (cellRect?: DOMRect) => void;
121
- onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: any) => Promise<void>;
121
+ onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: Record<string, unknown>) => Promise<void>;
122
122
  }
123
123
 
124
124
  function StorageUpload({
@@ -156,7 +156,7 @@ function StorageUpload({
156
156
  }
157
157
  }
158
158
 
159
- const metadata: any | undefined = storage?.metadata;
159
+ const metadata: Record<string, unknown> | undefined = storage?.metadata;
160
160
  const hasValue = Boolean(internalValue);
161
161
 
162
162
  const snackbarContext = useSnackbarController();
@@ -288,7 +288,7 @@ interface TableStorageItemPreviewProps {
288
288
  property: StringProperty;
289
289
  value: string,
290
290
  size: PreviewSize;
291
- entity: Entity<any>;
291
+ entity: Entity<Record<string, unknown>>;
292
292
  }
293
293
 
294
294
  export function TableStorageItemPreview({
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
- import { useDebouncedCallback, cls, focusedDisabled } from "@rebasepro/ui";
2
+ import { useDebouncedCallback, cls, focusedDisabled, TextField } from "@rebasepro/ui";
3
3
 
4
4
  export function VirtualTableNumberInput(props: {
5
5
  error: Error | undefined;
@@ -50,21 +50,24 @@ export function VirtualTableNumberInput(props: {
50
50
  [value, focused]
51
51
  );
52
52
 
53
- const ref = React.useRef<HTMLInputElement>(null);
53
+ const inputRef = React.useRef<HTMLInputElement>(null);
54
54
 
55
55
  useEffect(() => {
56
- if (ref.current && focused) {
57
- ref.current.focus({ preventScroll: true });
56
+ if (inputRef.current && focused) {
57
+ inputRef.current.focus({ preventScroll: true });
58
58
  }
59
- }, [focused, ref]);
59
+ }, [focused, inputRef]);
60
60
 
61
61
  const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
62
62
 
63
63
  return (
64
- <input
65
- ref={ref}
66
- className={cls("w-full text-right p-0 m-0 bg-transparent border-none resize-none outline-hidden font-normal leading-normal text-unset", focusedDisabled)}
67
- style={{
64
+ <TextField
65
+ inputRef={inputRef}
66
+ invisible={true}
67
+ size="small"
68
+ className="w-full"
69
+ inputClassName={cls("p-0 m-0 bg-transparent border-none outline-hidden font-normal leading-normal text-unset", focusedDisabled)}
70
+ inputStyle={{
68
71
  textAlign: align
69
72
  }}
70
73
  value={internalValue ?? ""}
@@ -13,7 +13,7 @@ interface EntityTableCellProps {
13
13
  /**
14
14
  * The value is used only to check changes and force re-renders
15
15
  */
16
- value?: any;
16
+ value?: unknown;
17
17
  disabled: boolean;
18
18
  savedTimestamp?: number;
19
19
  error?: Error;
@@ -31,7 +31,7 @@ interface EntityTableCellProps {
31
31
  // Sortable props for dnd-kit integration
32
32
  sortableNodeRef?: (node: HTMLElement | null) => void;
33
33
  sortableStyle?: React.CSSProperties;
34
- sortableAttributes?: Record<string, any>;
34
+ sortableAttributes?: Record<string, string | number | undefined>;
35
35
  isDragging?: boolean;
36
36
  isDraggable?: boolean;
37
37
  frozen?: boolean;
@@ -248,7 +248,7 @@ export function PopupFormFieldInternal<M extends Record<string, unknown>>({
248
248
  if (!validationSchema) return {};
249
249
  const result = await validationSchema.safeParseAsync(values);
250
250
  if (result.success) return {};
251
- return zodToFormErrors(result.error);
251
+ return zodToFormErrors(result.error) as Record<string, string>;
252
252
  },
253
253
  validateOnInitialRender: true,
254
254
  onSubmit: (values, actions) => {