@rebasepro/app 0.0.1-canary.4829d6e

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/auth/api.d.ts +66 -0
  4. package/dist/auth/index.d.ts +13 -0
  5. package/dist/auth/types.d.ts +127 -0
  6. package/dist/auth/useRebaseAuthController.d.ts +13 -0
  7. package/dist/components/AIIcon.d.ts +17 -0
  8. package/dist/components/AuthSimulationMenu.d.ts +0 -0
  9. package/dist/components/ConfirmationDialog.d.ts +9 -0
  10. package/dist/components/Debug/UIReferenceView.d.ts +13 -0
  11. package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
  12. package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
  13. package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
  14. package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
  15. package/dist/components/Debug/collection-views/index.d.ts +3 -0
  16. package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
  17. package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
  18. package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
  19. package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
  20. package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
  21. package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
  22. package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
  23. package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
  24. package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
  25. package/dist/components/ErrorTooltip.d.ts +3 -0
  26. package/dist/components/ErrorView.d.ts +21 -0
  27. package/dist/components/LanguageToggle.d.ts +2 -0
  28. package/dist/components/LoginView/LoginView.d.ts +113 -0
  29. package/dist/components/LoginView/index.d.ts +2 -0
  30. package/dist/components/NotFoundPage.d.ts +2 -0
  31. package/dist/components/RebaseAuth.d.ts +10 -0
  32. package/dist/components/RebaseLogo.d.ts +7 -0
  33. package/dist/components/SchemaDriftBanner.d.ts +27 -0
  34. package/dist/components/UnsavedChangesDialog.d.ts +9 -0
  35. package/dist/components/UserDisplay.d.ts +7 -0
  36. package/dist/components/UserSelectPopover.d.ts +63 -0
  37. package/dist/components/UserSettingsView.d.ts +2 -0
  38. package/dist/components/common/index.d.ts +6 -0
  39. package/dist/components/common/table_height.d.ts +5 -0
  40. package/dist/components/common/types.d.ts +66 -0
  41. package/dist/components/common/useColumnsIds.d.ts +9 -0
  42. package/dist/components/common/useDataTableController.d.ts +45 -0
  43. package/dist/components/common/useDebouncedData.d.ts +9 -0
  44. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  45. package/dist/components/index.d.ts +18 -0
  46. package/dist/contexts/AdminModeController.d.ts +4 -0
  47. package/dist/contexts/AnalyticsContext.d.ts +3 -0
  48. package/dist/contexts/AuthControllerContext.d.ts +3 -0
  49. package/dist/contexts/CollectionScopeContext.d.ts +50 -0
  50. package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
  51. package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
  52. package/dist/contexts/DataDriverContext.d.ts +3 -0
  53. package/dist/contexts/DataSourcesContext.d.ts +31 -0
  54. package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
  55. package/dist/contexts/DialogsProvider.d.ts +4 -0
  56. package/dist/contexts/EffectiveRoleController.d.ts +4 -0
  57. package/dist/contexts/ModeController.d.ts +4 -0
  58. package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
  59. package/dist/contexts/RebaseDataContext.d.ts +3 -0
  60. package/dist/contexts/SnackbarProvider.d.ts +2 -0
  61. package/dist/contexts/StorageSourceContext.d.ts +3 -0
  62. package/dist/contexts/StorageSourcesContext.d.ts +25 -0
  63. package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
  64. package/dist/contexts/index.d.ts +17 -0
  65. package/dist/core/PluginLifecycleManager.d.ts +17 -0
  66. package/dist/core/PluginProviderStack.d.ts +21 -0
  67. package/dist/core/Rebase.d.ts +15 -0
  68. package/dist/core/RebaseProps.d.ts +291 -0
  69. package/dist/core/RebaseRouter.d.ts +4 -0
  70. package/dist/core/RebaseRoutes.d.ts +17 -0
  71. package/dist/core/index.d.ts +4 -0
  72. package/dist/hooks/ApiConfigContext.d.ts +24 -0
  73. package/dist/hooks/data/delete.d.ts +33 -0
  74. package/dist/hooks/data/save.d.ts +40 -0
  75. package/dist/hooks/data/useCollection.d.ts +62 -0
  76. package/dist/hooks/data/useData.d.ts +13 -0
  77. package/dist/hooks/data/useDataOrder.d.ts +12 -0
  78. package/dist/hooks/data/useFetch.d.ts +43 -0
  79. package/dist/hooks/data/useRelationSelector.d.ts +52 -0
  80. package/dist/hooks/index.d.ts +36 -0
  81. package/dist/hooks/useAdminModeController.d.ts +19 -0
  82. package/dist/hooks/useAnalyticsController.d.ts +5 -0
  83. package/dist/hooks/useAuthController.d.ts +11 -0
  84. package/dist/hooks/useAuthSubscription.d.ts +2 -0
  85. package/dist/hooks/useBackendStorageSource.d.ts +30 -0
  86. package/dist/hooks/useBridgeRegistration.d.ts +18 -0
  87. package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
  88. package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
  89. package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
  90. package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
  91. package/dist/hooks/useBuildModeController.d.ts +6 -0
  92. package/dist/hooks/useClipboard.d.ts +57 -0
  93. package/dist/hooks/useCollapsedGroups.d.ts +27 -0
  94. package/dist/hooks/useComponentOverride.d.ts +32 -0
  95. package/dist/hooks/useCustomizationController.d.ts +11 -0
  96. package/dist/hooks/useDialogsController.d.ts +11 -0
  97. package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
  98. package/dist/hooks/useLargeLayout.d.ts +1 -0
  99. package/dist/hooks/useModeController.d.ts +19 -0
  100. package/dist/hooks/usePermissions.d.ts +12 -0
  101. package/dist/hooks/useRebaseClient.d.ts +5 -0
  102. package/dist/hooks/useRebaseContext.d.ts +11 -0
  103. package/dist/hooks/useRebaseRegistry.d.ts +34 -0
  104. package/dist/hooks/useResolvedComponent.d.ts +47 -0
  105. package/dist/hooks/useSlot.d.ts +18 -0
  106. package/dist/hooks/useSnackbarController.d.ts +20 -0
  107. package/dist/hooks/useStorageSource.d.ts +6 -0
  108. package/dist/hooks/useStudioBridge.d.ts +79 -0
  109. package/dist/hooks/useTranslation.d.ts +17 -0
  110. package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
  111. package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
  112. package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
  113. package/dist/index.d.ts +16 -0
  114. package/dist/index.es.js +16843 -0
  115. package/dist/index.es.js.map +1 -0
  116. package/dist/index.umd.js +17021 -0
  117. package/dist/index.umd.js.map +1 -0
  118. package/dist/internal/common.d.ts +14 -0
  119. package/dist/internal/useRestoreScroll.d.ts +15 -0
  120. package/dist/locales/de.d.ts +2 -0
  121. package/dist/locales/en.d.ts +10 -0
  122. package/dist/locales/es.d.ts +10 -0
  123. package/dist/locales/fr.d.ts +2 -0
  124. package/dist/locales/hi.d.ts +2 -0
  125. package/dist/locales/it.d.ts +2 -0
  126. package/dist/locales/pt.d.ts +7 -0
  127. package/dist/util/constants.d.ts +1 -0
  128. package/dist/util/createFormexStub.d.ts +2 -0
  129. package/dist/util/entity_cache.d.ts +22 -0
  130. package/dist/util/enums.d.ts +5 -0
  131. package/dist/util/icon_list.d.ts +5 -0
  132. package/dist/util/icons.d.ts +20 -0
  133. package/dist/util/index.d.ts +8 -0
  134. package/dist/util/previews.d.ts +4 -0
  135. package/dist/util/useStorageUploadController.d.ts +44 -0
  136. package/dist/vitePlugin.d.ts +60 -0
  137. package/dist/vitePlugin.js +113 -0
  138. package/package.json +145 -0
  139. package/src/auth/api.ts +148 -0
  140. package/src/auth/index.ts +25 -0
  141. package/src/auth/types.ts +115 -0
  142. package/src/auth/useRebaseAuthController.ts +328 -0
  143. package/src/components/AIIcon.tsx +47 -0
  144. package/src/components/AuthSimulationMenu.tsx +0 -0
  145. package/src/components/ConfirmationDialog.tsx +48 -0
  146. package/src/components/Debug/UIReferenceView.tsx +958 -0
  147. package/src/components/Debug/UIStyleGuide.tsx +156 -0
  148. package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
  149. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
  150. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
  151. package/src/components/Debug/collection-views/index.ts +3 -0
  152. package/src/components/Debug/collection-views/sample_data.ts +42 -0
  153. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
  154. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
  155. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
  156. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
  157. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
  158. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
  159. package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
  160. package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
  161. package/src/components/ErrorTooltip.tsx +12 -0
  162. package/src/components/ErrorView.tsx +94 -0
  163. package/src/components/LanguageToggle.tsx +59 -0
  164. package/src/components/LoginView/LoginView.tsx +932 -0
  165. package/src/components/LoginView/index.ts +2 -0
  166. package/src/components/NotFoundPage.tsx +27 -0
  167. package/src/components/RebaseAuth.tsx +27 -0
  168. package/src/components/RebaseLogo.tsx +29 -0
  169. package/src/components/SchemaDriftBanner.tsx +102 -0
  170. package/src/components/UnsavedChangesDialog.tsx +46 -0
  171. package/src/components/UserDisplay.tsx +54 -0
  172. package/src/components/UserSelectPopover.tsx +399 -0
  173. package/src/components/UserSettingsView.tsx +318 -0
  174. package/src/components/common/index.ts +6 -0
  175. package/src/components/common/table_height.tsx +21 -0
  176. package/src/components/common/types.tsx +66 -0
  177. package/src/components/common/useColumnsIds.tsx +222 -0
  178. package/src/components/common/useDataTableController.tsx +514 -0
  179. package/src/components/common/useDebouncedData.ts +49 -0
  180. package/src/components/common/useScrollRestoration.tsx +68 -0
  181. package/src/components/index.tsx +26 -0
  182. package/src/contexts/AdminModeController.tsx +11 -0
  183. package/src/contexts/AnalyticsContext.tsx +4 -0
  184. package/src/contexts/AuthControllerContext.tsx +4 -0
  185. package/src/contexts/CollectionScopeContext.tsx +67 -0
  186. package/src/contexts/ComponentOverrideContext.tsx +81 -0
  187. package/src/contexts/CustomizationControllerContext.tsx +4 -0
  188. package/src/contexts/DataDriverContext.tsx +4 -0
  189. package/src/contexts/DataSourcesContext.tsx +36 -0
  190. package/src/contexts/DatabaseAdminContext.tsx +4 -0
  191. package/src/contexts/DialogsProvider.tsx +56 -0
  192. package/src/contexts/EffectiveRoleController.tsx +12 -0
  193. package/src/contexts/ModeController.tsx +11 -0
  194. package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
  195. package/src/contexts/RebaseDataContext.tsx +4 -0
  196. package/src/contexts/SnackbarProvider.tsx +15 -0
  197. package/src/contexts/StorageSourceContext.tsx +4 -0
  198. package/src/contexts/StorageSourcesContext.tsx +30 -0
  199. package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
  200. package/src/contexts/index.ts +17 -0
  201. package/src/core/PluginLifecycleManager.tsx +95 -0
  202. package/src/core/PluginProviderStack.tsx +40 -0
  203. package/src/core/Rebase.tsx +439 -0
  204. package/src/core/RebaseProps.tsx +320 -0
  205. package/src/core/RebaseRouter.tsx +17 -0
  206. package/src/core/RebaseRoutes.tsx +24 -0
  207. package/src/core/index.tsx +5 -0
  208. package/src/hooks/ApiConfigContext.tsx +41 -0
  209. package/src/hooks/data/delete.ts +55 -0
  210. package/src/hooks/data/save.ts +77 -0
  211. package/src/hooks/data/useCollection.tsx +176 -0
  212. package/src/hooks/data/useData.tsx +18 -0
  213. package/src/hooks/data/useDataOrder.ts +26 -0
  214. package/src/hooks/data/useFetch.tsx +140 -0
  215. package/src/hooks/data/useRelationSelector.tsx +234 -0
  216. package/src/hooks/index.tsx +45 -0
  217. package/src/hooks/useAdminModeController.tsx +23 -0
  218. package/src/hooks/useAnalyticsController.tsx +8 -0
  219. package/src/hooks/useAuthController.tsx +14 -0
  220. package/src/hooks/useAuthSubscription.ts +87 -0
  221. package/src/hooks/useBackendStorageSource.ts +288 -0
  222. package/src/hooks/useBridgeRegistration.tsx +32 -0
  223. package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
  224. package/src/hooks/useBuildAdminModeController.tsx +24 -0
  225. package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
  226. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
  227. package/src/hooks/useBuildModeController.tsx +71 -0
  228. package/src/hooks/useClipboard.tsx +158 -0
  229. package/src/hooks/useCollapsedGroups.ts +116 -0
  230. package/src/hooks/useComponentOverride.tsx +59 -0
  231. package/src/hooks/useCustomizationController.tsx +14 -0
  232. package/src/hooks/useDialogsController.tsx +15 -0
  233. package/src/hooks/useEffectiveRoleController.tsx +10 -0
  234. package/src/hooks/useLargeLayout.tsx +65 -0
  235. package/src/hooks/useModeController.tsx +23 -0
  236. package/src/hooks/usePermissions.ts +44 -0
  237. package/src/hooks/useRebaseClient.tsx +10 -0
  238. package/src/hooks/useRebaseContext.tsx +83 -0
  239. package/src/hooks/useRebaseRegistry.tsx +91 -0
  240. package/src/hooks/useResolvedComponent.tsx +159 -0
  241. package/src/hooks/useSlot.tsx +64 -0
  242. package/src/hooks/useSnackbarController.tsx +52 -0
  243. package/src/hooks/useStorageSource.tsx +11 -0
  244. package/src/hooks/useStudioBridge.tsx +209 -0
  245. package/src/hooks/useTranslation.ts +33 -0
  246. package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
  247. package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
  248. package/src/i18n/RebaseI18nProvider.tsx +160 -0
  249. package/src/index.ts +25 -0
  250. package/src/internal/common.tsx +16 -0
  251. package/src/internal/useRestoreScroll.tsx +69 -0
  252. package/src/locales/de.ts +902 -0
  253. package/src/locales/en.ts +980 -0
  254. package/src/locales/es.ts +922 -0
  255. package/src/locales/fr.ts +901 -0
  256. package/src/locales/hi.ts +901 -0
  257. package/src/locales/it.ts +901 -0
  258. package/src/locales/pt.ts +909 -0
  259. package/src/util/constants.ts +7 -0
  260. package/src/util/createFormexStub.tsx +66 -0
  261. package/src/util/entity_cache.ts +239 -0
  262. package/src/util/enums.ts +32 -0
  263. package/src/util/icon_list.ts +22 -0
  264. package/src/util/icons.tsx +149 -0
  265. package/src/util/index.ts +12 -0
  266. package/src/util/previews.ts +105 -0
  267. package/src/util/useStorageUploadController.tsx +350 -0
  268. package/src/vitePlugin.ts +196 -0
@@ -0,0 +1,91 @@
1
+ import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from "react";
2
+ import type { RebaseRegistryController, RebaseAdminConfig, RebaseStudioConfig, RebaseAuthConfig } from "@rebasepro/types";
3
+
4
+ /**
5
+ * Split into two contexts to prevent infinite re-render loops:
6
+ * - DispatchContext: stable register/unregister functions (never changes identity)
7
+ * - StateContext: the current config values (changes when modules register)
8
+ *
9
+ * Feature components (RebaseAuth, RebaseAdmin, RebaseStudio) only consume
10
+ * the dispatch context, so registering does NOT cause them to re-render.
11
+ * RebaseShell consumes state context to read the collected configs.
12
+ */
13
+
14
+ interface RegistryDispatch {
15
+ registerCMS: (config: RebaseAdminConfig) => void;
16
+ unregisterCMS: () => void;
17
+ registerStudio: (config: RebaseStudioConfig) => void;
18
+ unregisterStudio: () => void;
19
+ registerAuth: (config: RebaseAuthConfig) => void;
20
+ unregisterAuth: () => void;
21
+ }
22
+
23
+ interface RegistryState {
24
+ cmsConfig: RebaseAdminConfig | null;
25
+ studioConfig: RebaseStudioConfig | null;
26
+ authConfig: RebaseAuthConfig | null;
27
+ }
28
+
29
+ const RegistryDispatchContext = createContext<RegistryDispatch | undefined>(undefined);
30
+ const RegistryStateContext = createContext<RegistryState>({
31
+ cmsConfig: null,
32
+ studioConfig: null,
33
+ authConfig: null
34
+ });
35
+
36
+ export function RebaseRegistryProvider({ children }: { children: React.ReactNode }) {
37
+ const [cmsConfig, setCmsConfig] = useState<RebaseAdminConfig | null>(null);
38
+ const [studioConfig, setStudioConfig] = useState<RebaseStudioConfig | null>(null);
39
+ const [authConfig, setAuthConfig] = useState<RebaseAuthConfig | null>(null);
40
+
41
+ // Dispatch functions are stable — never change identity
42
+ const dispatch = useMemo<RegistryDispatch>(() => ({
43
+ registerCMS: (config: RebaseAdminConfig) => setCmsConfig(config),
44
+ unregisterCMS: () => setCmsConfig(null),
45
+ registerStudio: (config: RebaseStudioConfig) => setStudioConfig(config),
46
+ unregisterStudio: () => setStudioConfig(null),
47
+ registerAuth: (config: RebaseAuthConfig) => setAuthConfig(config),
48
+ unregisterAuth: () => setAuthConfig(null)
49
+ }), []);
50
+
51
+ const state = useMemo<RegistryState>(() => ({
52
+ cmsConfig,
53
+ studioConfig,
54
+ authConfig
55
+ }), [cmsConfig, studioConfig, authConfig]);
56
+
57
+ return (
58
+ <RegistryDispatchContext.Provider value={dispatch}>
59
+ <RegistryStateContext.Provider value={state}>
60
+ {children}
61
+ </RegistryStateContext.Provider>
62
+ </RegistryDispatchContext.Provider>
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Returns the full registry (state + dispatch).
68
+ * Use this in RebaseShell where you need to read configs.
69
+ */
70
+ export function useRebaseRegistry(): RebaseRegistryController {
71
+ const dispatch = useContext(RegistryDispatchContext);
72
+ const state = useContext(RegistryStateContext);
73
+ if (!dispatch) {
74
+ throw new Error("useRebaseRegistry must be used within RebaseRegistryProvider");
75
+ }
76
+ return useMemo(() => ({ ...state,
77
+ ...dispatch }), [state, dispatch]);
78
+ }
79
+
80
+ /**
81
+ * Returns only the stable dispatch functions.
82
+ * Use this in feature components (RebaseAuth, RebaseAdmin, RebaseStudio)
83
+ * to avoid re-render loops.
84
+ */
85
+ export function useRebaseRegistryDispatch(): RegistryDispatch {
86
+ const dispatch = useContext(RegistryDispatchContext);
87
+ if (!dispatch) {
88
+ throw new Error("useRebaseRegistryDispatch must be used within RebaseRegistryProvider");
89
+ }
90
+ return dispatch;
91
+ }
@@ -0,0 +1,159 @@
1
+ import React, { lazy, useMemo } from "react";
2
+ import type { ComponentRef, LazyComponentRef } from "@rebasepro/types";
3
+ import { isLazyComponentRef } from "@rebasepro/types";
4
+
5
+ /**
6
+ * Internal cache for resolved lazy components.
7
+ *
8
+ * `React.lazy()` must return the SAME wrapper across renders — if we call
9
+ * `lazy(loader)` on every render, React will treat each result as a new
10
+ * component type and unmount/remount on each render cycle.
11
+ *
12
+ * This WeakMap keeps a stable mapping from a `ComponentRef` (object/function)
13
+ * to the `React.lazy()` wrapper it produced. Strings are keyed by a separate
14
+ * plain Map since they can't be WeakMap keys.
15
+ */
16
+
17
+ const lazyCache = new WeakMap<object | ((...args: any[]) => any), React.ComponentType<any>>();
18
+
19
+ /**
20
+ * Resolves a `ComponentRef` into a renderable `React.ComponentType`.
21
+ *
22
+ * This hook handles all three forms of `ComponentRef`:
23
+ *
24
+ * 1. **`LazyComponentRef`** (produced by the Vite plugin from string paths):
25
+ * Wraps the lazy loader with `React.lazy()` for automatic code-splitting.
26
+ *
27
+ * 2. **`() => Promise<{ default: ComponentType }>`** (manual lazy import):
28
+ * Also wraps with `React.lazy()`. Distinguished from regular components
29
+ * by checking that the function has no parameters and is not a known
30
+ * React internal (no `$$typeof`).
31
+ *
32
+ * 3. **Direct `React.ComponentType`**:
33
+ * Returned as-is.
34
+ *
35
+ * If the ref is `undefined` or a raw string (which should never happen at
36
+ * runtime in the browser since the Vite plugin transforms strings), returns `undefined`.
37
+ *
38
+ * The returned component is stable across re-renders as long as the `ref`
39
+ * is referentially stable.
40
+ *
41
+ * **Usage:** Wrap the rendered component in `<Suspense>` to handle the
42
+ * loading state of lazy components.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * const ResolvedField = useResolvedComponent(property.ui?.Field);
47
+ * if (!ResolvedField) return null;
48
+ * return (
49
+ * <Suspense fallback={<CircularProgress />}>
50
+ * <ResolvedField {...fieldProps} />
51
+ * </Suspense>
52
+ * );
53
+ * ```
54
+ * */
55
+ export function useResolvedComponent<P = unknown>(
56
+ ref: ComponentRef<P> | undefined
57
+ ): React.ComponentType<P> | undefined {
58
+ return useMemo(() => resolveComponentRef(ref), [ref]);
59
+ }
60
+
61
+ /**
62
+ * Wraps a loader function with `React.lazy()`, caching the result so the
63
+ * same loader always returns the same lazy component identity.
64
+ */
65
+ function getOrCreateLazy<P>(
66
+ key: object | ((...args: any[]) => any),
67
+ loader: () => Promise<{ default: React.ComponentType<P> }>
68
+ ): React.ComponentType<P> {
69
+ const cached = lazyCache.get(key);
70
+ if (cached) return cached as React.ComponentType<P>;
71
+
72
+ // SAFETY: React.lazy returns LazyExoticComponent which is structurally compatible with ComponentType<P>
73
+ const LazyComponent = lazy(loader) as React.ComponentType<P>;
74
+ lazyCache.set(key, LazyComponent);
75
+ return LazyComponent;
76
+ }
77
+
78
+ /**
79
+ * Pure function version of the resolver, for use outside React components.
80
+ * Same resolution logic as `useResolvedComponent`.
81
+ *
82
+ * Results are cached per reference identity — calling this multiple times
83
+ * with the same `ref` object returns the same `React.ComponentType`.
84
+ */
85
+ export function resolveComponentRef<P = unknown>(
86
+ ref: ComponentRef<P> | undefined
87
+ ): React.ComponentType<P> | undefined {
88
+ if (ref == null) return undefined;
89
+
90
+ // 1. String — should not happen at runtime (Vite transforms them).
91
+ // Log a warning and bail out.
92
+ if (typeof ref === "string") {
93
+ console.warn(
94
+ `[Rebase] Encountered a raw string ComponentRef ("${ref}") at runtime. ` +
95
+ "This usually means the Vite transform plugin did not process this file. " +
96
+ "Ensure the file is inside the configured collectionsDir."
97
+ );
98
+ return undefined;
99
+ }
100
+
101
+ // 2. LazyComponentRef — produced by the Vite plugin from string paths.
102
+ // The object has { __rebaseLazy: true, load: () => import(...) }.
103
+ if (isLazyComponentRef(ref)) {
104
+ return getOrCreateLazy<P>(
105
+ ref as object,
106
+ () => (ref as LazyComponentRef<P>).load()
107
+ );
108
+ }
109
+
110
+ // 3. Function — either a React component or a lazy import loader.
111
+ if (typeof ref === "function") {
112
+ const fn = ref as (...args: any[]) => any;
113
+
114
+ // Class components (React.Component / PureComponent) have this flag
115
+ if (fn.prototype?.isReactComponent) {
116
+ return ref as React.ComponentType<P>;
117
+ }
118
+
119
+ // React internals (forwardRef, memo, etc.) carry $$typeof
120
+ if ("$$typeof" in fn) {
121
+ return ref as React.ComponentType<P>;
122
+ }
123
+
124
+ // A function with declared parameters (fn.length > 0) is a regular
125
+ // component that accepts props — return as-is.
126
+ if (fn.length > 0) {
127
+ return ref as React.ComponentType<P>;
128
+ }
129
+
130
+ // Zero-parameter function — could be a React component with no props
131
+ // OR a lazy loader `() => import(...)`. We distinguish by checking
132
+ // if `fn.name` looks like a component (starts with uppercase) or is
133
+ // an anonymous arrow.
134
+ //
135
+ // Convention: named function components always start with an
136
+ // uppercase letter. Dynamic import wrappers are typically anonymous
137
+ // arrows or have lowercase names.
138
+ const name = fn.name;
139
+ if (name && /^[A-Z]/.test(name)) {
140
+ // Named component (e.g. `function MyField() { ... }`) — return as-is
141
+ return ref as React.ComponentType<P>;
142
+ }
143
+
144
+ // Treat as a lazy loader — wrap with React.lazy, cached by identity.
145
+ return getOrCreateLazy<P>(
146
+ fn,
147
+ ref as () => Promise<{ default: React.ComponentType<P> }>
148
+ );
149
+ }
150
+
151
+ // 4. React wrapper objects (React.forwardRef, React.memo) —
152
+ // These are objects with $$typeof but are not functions.
153
+ if (typeof ref === "object" && "$$typeof" in (ref as object)) {
154
+ return ref as unknown as React.ComponentType<P>;
155
+ }
156
+
157
+ // 5. Unknown shape — return undefined to be safe
158
+ return undefined;
159
+ }
@@ -0,0 +1,64 @@
1
+ import type { SlotName, SlotRegistry } from "@rebasepro/types";
2
+ import React, { useMemo } from "react";
3
+ ;
4
+ import { useCustomizationController } from "./useCustomizationController";
5
+ import { ErrorBoundary } from "@rebasepro/ui";
6
+
7
+ /**
8
+ * Hook that retrieves and renders all slot contributions for a given slot name.
9
+ *
10
+ * @param slot - The slot name to render contributions for.
11
+ * @param props - Props passed to each slot component.
12
+ * @returns An array of rendered React nodes, each wrapped in an ErrorBoundary.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * const actions = useSlot("home.actions", { context });
17
+ * return <div>{actions}</div>;
18
+ * ```
19
+ *
20
+ * @group Hooks
21
+ */
22
+ export function useSlot<K extends SlotName>(
23
+ slot: K,
24
+ props: SlotRegistry[K]
25
+ ): React.ReactNode[] {
26
+ const { resolvedSlots } = useCustomizationController();
27
+
28
+ const propsRef = React.useRef(props);
29
+ const currentProps = props as unknown as Record<string, unknown>;
30
+ const prevProps = propsRef.current as unknown as Record<string, unknown>;
31
+
32
+ let changed = false;
33
+ if (currentProps !== prevProps) {
34
+ const keys = Object.keys(currentProps);
35
+ const prevKeys = Object.keys(prevProps);
36
+ if (keys.length !== prevKeys.length) {
37
+ changed = true;
38
+ } else {
39
+ for (let i = 0; i < keys.length; i++) {
40
+ if (currentProps[keys[i]] !== prevProps[keys[i]]) {
41
+ changed = true;
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ if (changed) {
49
+ propsRef.current = props;
50
+ }
51
+
52
+ const stableProps = propsRef.current;
53
+
54
+ return useMemo(() => {
55
+ return resolvedSlots
56
+ .filter(s => s.slot === slot)
57
+ .sort((a, b) => (a.order ?? 50) - (b.order ?? 50))
58
+ .map((s, i) => (
59
+ <ErrorBoundary key={`${slot}_${i}`}>
60
+ <s.Component {...(stableProps as unknown as Record<string, unknown>)} {...(s.props ?? {})}/>
61
+ </ErrorBoundary>
62
+ ));
63
+ }, [resolvedSlots, slot, stableProps]);
64
+ }
@@ -0,0 +1,52 @@
1
+ import type { SnackbarMessageType } from "@rebasepro/types";
2
+ import React, { useCallback, useMemo } from "react";
3
+ import { useSnackbar } from "notistack";
4
+ ;
5
+
6
+ /**
7
+ * Hook to retrieve the SnackbarContext.
8
+ *
9
+ * Consider that in order to use this hook you need to have a parent
10
+ * `Rebase`
11
+ *
12
+ * @see SnackbarController
13
+ * @group Hooks and utilities
14
+ */
15
+ export const useSnackbarController = () => {
16
+
17
+ const {
18
+ enqueueSnackbar,
19
+ closeSnackbar
20
+ } = useSnackbar();
21
+
22
+ const open = useCallback((props: {
23
+ type: SnackbarMessageType;
24
+ title?: string;
25
+ message: React.ReactNode;
26
+ autoHideDuration?: number;
27
+ }) => {
28
+ const {
29
+ type,
30
+ message,
31
+ autoHideDuration
32
+ } = props;
33
+ enqueueSnackbar({
34
+ message: props.title ? <div className={"flex flex-col"}>
35
+ <strong>{props.title}</strong>
36
+ {message}
37
+ </div> : message,
38
+ variant: type,
39
+ autoHideDuration
40
+ })
41
+ }, []);
42
+
43
+ const close = useCallback(() => {
44
+ closeSnackbar();
45
+ }, []);
46
+
47
+ return useMemo(() => ({
48
+ open,
49
+ close
50
+ }), [open, close]);
51
+
52
+ };
@@ -0,0 +1,11 @@
1
+ import { StorageSource } from "@rebasepro/types";
2
+ import { StorageSourceContext } from "../contexts/StorageSourceContext";
3
+ import { useContext } from "react";
4
+
5
+ /**
6
+ * Use this hook to get the storage source being used
7
+ * @group Hooks and utilities
8
+ */
9
+ export const useStorageSource = (): StorageSource => {
10
+ return useContext(StorageSourceContext);
11
+ };
@@ -0,0 +1,209 @@
1
+ import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
2
+ import type {
3
+ CollectionRegistryController,
4
+ SidePanelController,
5
+ UrlController,
6
+ NavigationStateController,
7
+ BreadcrumbEntry,
8
+ BreadcrumbsController
9
+ } from "@rebasepro/types";
10
+
11
+ export type { BreadcrumbEntry, BreadcrumbsController };
12
+
13
+ // ─── Breadcrumbs ──────────────────────────────────────────────────
14
+
15
+
16
+ // ─── Bridge interface ───────────────────────────────────────────────
17
+
18
+ /**
19
+ * StudioBridge provides optional CMS capabilities to Studio components.
20
+ * When CMS is present, a bridge provider injects real implementations.
21
+ * When CMS is absent, noop defaults ensure Studio works standalone.
22
+ */
23
+ export interface StudioBridge {
24
+ collectionRegistry: CollectionRegistryController;
25
+ sidePanelController: SidePanelController;
26
+ urlController: UrlController;
27
+ navigationState: NavigationStateController;
28
+ breadcrumbs: BreadcrumbsController;
29
+ }
30
+
31
+ // ─── Noop defaults ──────────────────────────────────────────────────
32
+
33
+ const NOOP_COLLECTION_REGISTRY: CollectionRegistryController = {
34
+ getCollection: () => undefined,
35
+ getRawCollection: () => undefined,
36
+ getParentReferencesFromPath: () => [],
37
+ getParentCollectionSlugs: () => [],
38
+ getParentEntityIds: () => [],
39
+ convertIdsToPaths: () => [],
40
+ initialised: false
41
+ };
42
+
43
+ const NOOP_SIDE_PANEL: SidePanelController = {
44
+ open: () => {},
45
+ replace: () => {},
46
+ close: () => {}
47
+ };
48
+
49
+ const NOOP_URL_CONTROLLER: UrlController = {
50
+ basePath: "/",
51
+ baseCollectionPath: "/c",
52
+ urlPathToDataPath: () => "",
53
+ homeUrl: "/",
54
+ isUrlCollectionPath: () => false,
55
+ buildUrlCollectionPath: () => "",
56
+ buildAppUrlPath: () => "",
57
+ resolveDatabasePathsFrom: () => "",
58
+ navigate: () => {}
59
+ };
60
+
61
+ const NOOP_NAVIGATION_STATE: NavigationStateController = {
62
+ loading: false,
63
+ refreshNavigation: () => {}
64
+ };
65
+
66
+ const NOOP_BREADCRUMBS: BreadcrumbsController = {
67
+ breadcrumbs: [],
68
+ set: () => {},
69
+ updateCount: () => {}
70
+ };
71
+
72
+ const NOOP_BRIDGE: StudioBridge = {
73
+ collectionRegistry: NOOP_COLLECTION_REGISTRY,
74
+ sidePanelController: NOOP_SIDE_PANEL,
75
+ urlController: NOOP_URL_CONTROLLER,
76
+ navigationState: NOOP_NAVIGATION_STATE,
77
+ breadcrumbs: NOOP_BREADCRUMBS
78
+ };
79
+
80
+ // ─── Context & Provider ─────────────────────────────────────────────
81
+
82
+ export const StudioBridgeContext = createContext<StudioBridge>(NOOP_BRIDGE);
83
+
84
+ /**
85
+ * Provider that injects CMS capabilities into Studio.
86
+ * Accepts partial overrides — any field not provided falls back to noop.
87
+ *
88
+ * Usage (in app wiring, when CMS is present):
89
+ * ```tsx
90
+ * <StudioBridgeProvider value={{
91
+ * collectionRegistry: useCollectionRegistryController(),
92
+ * sidePanelController: useSidePanel(),
93
+ * urlController: useUrlController(),
94
+ * navigationState: useNavigationStateController(),
95
+ * breadcrumbs: useBreadcrumbsController(),
96
+ * }}>
97
+ * <RebaseStudio ... />
98
+ * </StudioBridgeProvider>
99
+ * ```
100
+ */
101
+ export function StudioBridgeProvider({
102
+ value,
103
+ children
104
+ }: {
105
+ value: Partial<StudioBridge>;
106
+ children: React.ReactNode;
107
+ }) {
108
+ const merged = React.useMemo(
109
+ () => ({ ...NOOP_BRIDGE,
110
+ ...value }),
111
+ [value]
112
+ );
113
+ return (
114
+ <StudioBridgeContext.Provider value={merged}>
115
+ {children}
116
+ </StudioBridgeContext.Provider>
117
+ );
118
+ }
119
+
120
+ // ─── Convenience hooks ──────────────────────────────────────────────
121
+
122
+ /** Collection registry — returns noop if CMS is not present. */
123
+ export function useStudioCollectionRegistry(): CollectionRegistryController {
124
+ return useContext(StudioBridgeContext).collectionRegistry;
125
+ }
126
+
127
+ /** Side panel controller — returns noop if CMS is not present. */
128
+ export function useStudioSidePanelController(): SidePanelController {
129
+ return useContext(StudioBridgeContext).sidePanelController;
130
+ }
131
+
132
+ /** URL controller — returns noop if CMS is not present. */
133
+ export function useStudioUrlController(): UrlController {
134
+ return useContext(StudioBridgeContext).urlController;
135
+ }
136
+
137
+ /** Navigation state — returns noop if CMS is not present. */
138
+ export function useStudioNavigationState(): NavigationStateController {
139
+ return useContext(StudioBridgeContext).navigationState;
140
+ }
141
+
142
+ /** Breadcrumbs controller — returns noop if CMS is not present. */
143
+ export function useStudioBreadcrumbs(): BreadcrumbsController {
144
+ return useContext(StudioBridgeContext).breadcrumbs;
145
+ }
146
+
147
+ // ─── Self-Assembling Bridge Registry ────────────────────────────────
148
+
149
+ /**
150
+ * Registry that controllers use to self-register their implementations
151
+ * into the Studio bridge. Each controller calls `register(key, value)`
152
+ * on mount and `unregister(key)` on unmount.
153
+ */
154
+ export interface StudioBridgeRegistry {
155
+ register: <K extends keyof StudioBridge>(key: K, value: StudioBridge[K]) => void;
156
+ unregister: (key: keyof StudioBridge) => void;
157
+ }
158
+
159
+ export const StudioBridgeRegistryContext = createContext<StudioBridgeRegistry | null>(null);
160
+
161
+ /**
162
+ * Provider that creates a self-assembling bridge.
163
+ *
164
+ * Mount this above the controller providers. Each controller calls
165
+ * `useBridgeRegistration(key, value)` to inject its implementation.
166
+ * The bridge context value is automatically kept in sync.
167
+ *
168
+ * ```tsx
169
+ * <StudioBridgeRegistryProvider>
170
+ * <CollectionRegistryProvider> // auto-registers
171
+ * <SidePanelProvider> // auto-registers
172
+ * <UrlProvider> // auto-registers
173
+ * <RebaseStudio /> // consumes bridge
174
+ * </UrlProvider>
175
+ * </SidePanelProvider>
176
+ * </CollectionRegistryProvider>
177
+ * </StudioBridgeRegistryProvider>
178
+ * ```
179
+ */
180
+ export function StudioBridgeRegistryProvider({ children }: { children: React.ReactNode }) {
181
+ const [version, setVersion] = useState(0);
182
+ const slicesRef = useRef<Partial<StudioBridge>>({});
183
+
184
+ const register = useCallback(<K extends keyof StudioBridge>(key: K, value: StudioBridge[K]) => {
185
+ slicesRef.current[key] = value;
186
+ setVersion(v => v + 1);
187
+ }, []);
188
+
189
+ const unregister = useCallback((key: keyof StudioBridge) => {
190
+ delete slicesRef.current[key];
191
+ setVersion(v => v + 1);
192
+ }, []);
193
+
194
+ const registry = useMemo<StudioBridgeRegistry>(() => ({ register,
195
+ unregister }), [register, unregister]);
196
+
197
+ const bridgeValue = useMemo<StudioBridge>(() => ({
198
+ ...NOOP_BRIDGE,
199
+ ...slicesRef.current
200
+ }), [version]);
201
+
202
+ return (
203
+ <StudioBridgeRegistryContext.Provider value={registry}>
204
+ <StudioBridgeContext.Provider value={bridgeValue}>
205
+ {children}
206
+ </StudioBridgeContext.Provider>
207
+ </StudioBridgeRegistryContext.Provider>
208
+ );
209
+ }
@@ -0,0 +1,33 @@
1
+ import { useTranslation as useI18nTranslation } from "react-i18next";
2
+ import { useCallback, useMemo } from "react";
3
+
4
+ const REBASE_NS = "rebase_core";
5
+
6
+ /**
7
+ * Internal hook for translating Rebase UI strings.
8
+ *
9
+ * Uses the `rebase_core` i18next namespace that is initialised by
10
+ * `RebaseI18nProvider`. Do NOT use `react-i18next` directly in internal
11
+ * components — always go through this hook so the namespace is consistent.
12
+ *
13
+ * @example
14
+ * const { t } = useTranslation();
15
+ * <Button>{t("save")}</Button>
16
+ *
17
+ * @internal
18
+ */
19
+ export function useTranslation() {
20
+ const { t, i18n } = useI18nTranslation(REBASE_NS);
21
+
22
+ /**
23
+ * Typed translation function scoped to RebaseTranslations keys.
24
+ * Also supports i18next interpolation variables, e.g.
25
+ * t("add_to_field", { fieldName: "Tags" })
26
+ * t("error_deleting", { message: err.message })
27
+ */
28
+ const typedT = useCallback((key: string, vars?: Record<string, string>): string =>
29
+ t(key, vars) as string, [t]);
30
+
31
+ return useMemo(() => ({ t: typedT,
32
+ i18n }), [typedT, i18n]);
33
+ }
@@ -0,0 +1,64 @@
1
+ import { useCallback, useEffect, useState, useMemo } from "react";
2
+ import { useBlocker } from "react-router-dom";
3
+ import { UnsavedChangesDialogProps } from "../components/UnsavedChangesDialog";
4
+
5
+ /**
6
+ * A single, unified hook to prevent navigation when there are unsaved changes.
7
+ *
8
+ * It automatically handles:
9
+ * 1. Internal React Router navigation using `useBlocker`.
10
+ * 2. External browser navigation (page refresh, tab close) using `beforeunload`.
11
+ */
12
+ export function useUnsavedChangesDialog(
13
+ when: boolean,
14
+ onOk: () => void
15
+ ): {
16
+ dialogProps: UnsavedChangesDialogProps;
17
+ triggerDialog: () => void;
18
+ } {
19
+ const [manualDialogOpen, setManualDialogOpen] = useState(false);
20
+
21
+ const blocker = useBlocker(
22
+ ({ currentLocation, nextLocation }) =>
23
+ when && currentLocation.pathname !== nextLocation.pathname
24
+ );
25
+
26
+ useEffect(() => {
27
+ if (!when) return;
28
+
29
+ const handleBeforeUnload = (e: BeforeUnloadEvent) => {
30
+ e.preventDefault();
31
+ e.returnValue = "";
32
+ };
33
+
34
+ window.addEventListener("beforeunload", handleBeforeUnload);
35
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
36
+ }, [when]);
37
+
38
+ const handleOk = useCallback(() => {
39
+ onOk();
40
+ if (blocker.state === "blocked") {
41
+ blocker.proceed();
42
+ }
43
+ setManualDialogOpen(false);
44
+ }, [blocker, onOk]);
45
+
46
+ const handleCancel = useCallback(() => {
47
+ if (blocker.state === "blocked") {
48
+ blocker.reset();
49
+ }
50
+ setManualDialogOpen(false);
51
+ }, [blocker]);
52
+
53
+ const triggerDialog = useCallback(() => setManualDialogOpen(true), []);
54
+
55
+ return useMemo(() => ({
56
+ dialogProps: {
57
+ open: manualDialogOpen || blocker.state === "blocked",
58
+ handleOk,
59
+ handleCancel,
60
+ body: "There are unsaved changes in this collection"
61
+ },
62
+ triggerDialog
63
+ }), [manualDialogOpen, blocker.state, handleOk, handleCancel, triggerDialog]);
64
+ }