@rebasepro/app 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/contexts/AuthControllerContext.d.ts +2 -1
- package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
- package/dist/contexts/DialogsProvider.d.ts +2 -1
- package/dist/contexts/RebaseDataContext.d.ts +10 -1
- package/dist/contexts/StorageSourceContext.d.ts +11 -1
- package/dist/core/RebaseProps.d.ts +2 -12
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug.js +3631 -0
- package/dist/hooks/data/delete.d.ts +16 -7
- package/dist/hooks/data/save.d.ts +18 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAdminModeController.d.ts +5 -4
- package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
- package/dist/hooks/useStudioBridge.d.ts +33 -0
- package/dist/index.es.js +2599 -6980
- package/dist/index.es.js.map +1 -1
- package/dist/vitePlugin.d.ts +22 -0
- package/dist/vitePlugin.js +40 -1
- package/package.json +42 -31
- package/src/auth/api.ts +0 -134
- package/src/auth/index.ts +0 -26
- package/src/auth/types.ts +0 -115
- package/src/auth/useRebaseAuthController.ts +0 -345
- package/src/collections/collection_view_config.ts +0 -76
- package/src/collections/entity-display-cache.ts +0 -195
- package/src/collections/entity-display.ts +0 -77
- package/src/collections/entity_image_preview.ts +0 -61
- package/src/collections/filter-operator-resolution.ts +0 -119
- package/src/collections/form-layout.ts +0 -394
- package/src/collections/index.ts +0 -13
- package/src/collections/navigation_from_path.ts +0 -123
- package/src/collections/navigation_utils.ts +0 -171
- package/src/collections/parent_references_from_path.ts +0 -59
- package/src/collections/property-path.ts +0 -30
- package/src/collections/property_presentation.ts +0 -264
- package/src/collections/summary-property.ts +0 -114
- package/src/collections/title-property.ts +0 -312
- package/src/components/AIIcon.tsx +0 -47
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +0 -48
- package/src/components/Debug/UIReferenceView.tsx +0 -1053
- package/src/components/Debug/UIStyleGuide.tsx +0 -159
- package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
- package/src/components/Debug/collection-views/index.ts +0 -3
- package/src/components/Debug/collection-views/sample_data.ts +0 -44
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
- package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
- package/src/components/ErrorTooltip.tsx +0 -12
- package/src/components/ErrorView.tsx +0 -94
- package/src/components/LanguageToggle.tsx +0 -59
- package/src/components/LoginView/LoginView.tsx +0 -1235
- package/src/components/LoginView/index.ts +0 -2
- package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
- package/src/components/NotFoundPage.tsx +0 -27
- package/src/components/RebaseAuth.tsx +0 -27
- package/src/components/RebaseLogo.tsx +0 -88
- package/src/components/SchemaDriftBanner.tsx +0 -102
- package/src/components/UnsavedChangesDialog.tsx +0 -46
- package/src/components/UserDisplay.tsx +0 -54
- package/src/components/UserSelectPopover.tsx +0 -399
- package/src/components/UserSettingsView.tsx +0 -318
- package/src/components/common/index.ts +0 -6
- package/src/components/common/table_height.tsx +0 -21
- package/src/components/common/types.tsx +0 -66
- package/src/components/common/useColumnsIds.tsx +0 -261
- package/src/components/common/useDataTableController.tsx +0 -571
- package/src/components/common/useDebouncedData.ts +0 -49
- package/src/components/common/useScrollRestoration.tsx +0 -68
- package/src/components/index.tsx +0 -26
- package/src/contexts/AdminModeController.tsx +0 -11
- package/src/contexts/AnalyticsContext.tsx +0 -4
- package/src/contexts/AuthControllerContext.tsx +0 -4
- package/src/contexts/CollectionResolverContext.tsx +0 -24
- package/src/contexts/CollectionScopeContext.tsx +0 -68
- package/src/contexts/ComponentOverrideContext.tsx +0 -81
- package/src/contexts/CustomizationControllerContext.tsx +0 -4
- package/src/contexts/DataDriverContext.tsx +0 -4
- package/src/contexts/DataSourcesContext.tsx +0 -36
- package/src/contexts/DatabaseAdminContext.tsx +0 -4
- package/src/contexts/DialogsProvider.tsx +0 -56
- package/src/contexts/EffectiveRoleController.tsx +0 -12
- package/src/contexts/ModeController.tsx +0 -11
- package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
- package/src/contexts/RebaseDataContext.tsx +0 -4
- package/src/contexts/SnackbarProvider.tsx +0 -15
- package/src/contexts/StorageSourceContext.tsx +0 -4
- package/src/contexts/StorageSourcesContext.tsx +0 -30
- package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
- package/src/contexts/index.ts +0 -18
- package/src/core/PluginLifecycleManager.tsx +0 -96
- package/src/core/PluginProviderStack.tsx +0 -40
- package/src/core/Rebase.tsx +0 -488
- package/src/core/RebaseProps.tsx +0 -333
- package/src/core/RebaseRouter.tsx +0 -18
- package/src/core/RebaseRoutes.tsx +0 -24
- package/src/core/index.tsx +0 -5
- package/src/hooks/ApiConfigContext.tsx +0 -83
- package/src/hooks/data/collectionQuery.ts +0 -55
- package/src/hooks/data/delete.ts +0 -56
- package/src/hooks/data/save.ts +0 -78
- package/src/hooks/data/useCollection.tsx +0 -183
- package/src/hooks/data/useData.tsx +0 -18
- package/src/hooks/data/useDataOrder.ts +0 -26
- package/src/hooks/data/useFetch.tsx +0 -156
- package/src/hooks/data/useRelationSelector.tsx +0 -280
- package/src/hooks/index.tsx +0 -45
- package/src/hooks/useAdminModeController.tsx +0 -23
- package/src/hooks/useAnalyticsController.tsx +0 -8
- package/src/hooks/useAuthController.tsx +0 -15
- package/src/hooks/useAuthSubscription.ts +0 -130
- package/src/hooks/useBackendStorageSource.ts +0 -295
- package/src/hooks/useBridgeRegistration.tsx +0 -32
- package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
- package/src/hooks/useBuildAdminModeController.tsx +0 -29
- package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
- package/src/hooks/useBuildModeController.tsx +0 -73
- package/src/hooks/useClipboard.tsx +0 -158
- package/src/hooks/useCollapsedGroups.ts +0 -116
- package/src/hooks/useComponentOverride.tsx +0 -59
- package/src/hooks/useCustomizationController.tsx +0 -14
- package/src/hooks/useDialogsController.tsx +0 -15
- package/src/hooks/useEffectiveRoleController.tsx +0 -10
- package/src/hooks/useLargeLayout.tsx +0 -65
- package/src/hooks/useModeController.tsx +0 -23
- package/src/hooks/useNavigationBlocker.tsx +0 -108
- package/src/hooks/usePermissions.ts +0 -45
- package/src/hooks/useRebaseClient.tsx +0 -10
- package/src/hooks/useRebaseContext.tsx +0 -84
- package/src/hooks/useRebaseRegistry.tsx +0 -91
- package/src/hooks/useResolvedComponent.tsx +0 -195
- package/src/hooks/useSlot.tsx +0 -64
- package/src/hooks/useSnackbarController.tsx +0 -80
- package/src/hooks/useStorageSource.tsx +0 -11
- package/src/hooks/useStudioBridge.tsx +0 -258
- package/src/hooks/useTranslation.ts +0 -38
- package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
- package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
- package/src/i18n/RebaseI18nProvider.tsx +0 -159
- package/src/index.ts +0 -32
- package/src/internal/common.tsx +0 -28
- package/src/internal/useRestoreScroll.tsx +0 -69
- package/src/locales/de.ts +0 -941
- package/src/locales/en.ts +0 -1021
- package/src/locales/es.ts +0 -962
- package/src/locales/fr.ts +0 -941
- package/src/locales/hi.ts +0 -941
- package/src/locales/it.ts +0 -941
- package/src/locales/pt.ts +0 -949
- package/src/util/constants.ts +0 -7
- package/src/util/createFormexStub.tsx +0 -66
- package/src/util/entity_cache.ts +0 -242
- package/src/util/enums.ts +0 -34
- package/src/util/icon_list.ts +0 -22
- package/src/util/icons.tsx +0 -129
- package/src/util/index.ts +0 -13
- package/src/util/local_storage.ts +0 -66
- package/src/util/previews.ts +0 -127
- package/src/util/useStorageUploadController.tsx +0 -354
- package/src/vitePlugin.ts +0 -215
- /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
- /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Blocker, BlockerFunction, useBlocker } from "react-router";
|
|
3
|
-
|
|
4
|
-
const IDLE_BLOCKER: Blocker = {
|
|
5
|
-
state: "unblocked",
|
|
6
|
-
proceed: undefined,
|
|
7
|
-
reset: undefined,
|
|
8
|
-
location: undefined
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type NavigationBlockerContextValue = {
|
|
12
|
-
register: (id: string, predicate: BlockerFunction) => void;
|
|
13
|
-
unregister: (id: string) => void;
|
|
14
|
-
blocker: Blocker;
|
|
15
|
-
/** Id of the registration that caused the current block, if any. */
|
|
16
|
-
blockedBy: string | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const NavigationBlockerContext = createContext<NavigationBlockerContextValue | null>(null);
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Owns the single React Router blocker for the whole app.
|
|
23
|
-
*
|
|
24
|
-
* React Router only honours **one** blocker at a time: `shouldBlockNavigation`
|
|
25
|
-
* picks the last-registered blocker function and silently ignores every other
|
|
26
|
-
* one (it only logs a dev warning). Because blockers register in a `useEffect`,
|
|
27
|
-
* the winner is whichever surface mounted most recently — so an unsaved-changes
|
|
28
|
-
* guard could be disabled by an unrelated component mounting after it.
|
|
29
|
-
*
|
|
30
|
-
* This provider registers the only `useBlocker` in the tree and multiplexes it,
|
|
31
|
-
* so every surface that needs to guard navigation gets a say regardless of
|
|
32
|
-
* mount order. Surfaces register through {@link useNavigationBlocker}.
|
|
33
|
-
*/
|
|
34
|
-
export function NavigationBlockerProvider({ children }: { children: React.ReactNode }) {
|
|
35
|
-
|
|
36
|
-
const predicates = useRef(new Map<string, BlockerFunction>());
|
|
37
|
-
|
|
38
|
-
// State, not a ref: the owning registration only renders its dialog once
|
|
39
|
-
// this has propagated, so it has to go through React.
|
|
40
|
-
const [blockedBy, setBlockedBy] = useState<string | null>(null);
|
|
41
|
-
|
|
42
|
-
const shouldBlock = useCallback<BlockerFunction>((args) => {
|
|
43
|
-
for (const [id, predicate] of predicates.current) {
|
|
44
|
-
if (predicate(args)) {
|
|
45
|
-
setBlockedBy(id);
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
setBlockedBy(null);
|
|
50
|
-
return false;
|
|
51
|
-
}, []);
|
|
52
|
-
|
|
53
|
-
const blocker = useBlocker(shouldBlock);
|
|
54
|
-
|
|
55
|
-
const register = useCallback((id: string, predicate: BlockerFunction) => {
|
|
56
|
-
predicates.current.set(id, predicate);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
const unregister = useCallback((id: string) => {
|
|
60
|
-
predicates.current.delete(id);
|
|
61
|
-
setBlockedBy((current) => (current === id ? null : current));
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
const value = useMemo<NavigationBlockerContextValue>(() => ({
|
|
65
|
-
register,
|
|
66
|
-
unregister,
|
|
67
|
-
blocker,
|
|
68
|
-
blockedBy
|
|
69
|
-
}), [register, unregister, blocker, blockedBy]);
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<NavigationBlockerContext.Provider value={value}>
|
|
73
|
-
{children}
|
|
74
|
-
</NavigationBlockerContext.Provider>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Guard navigation with `predicate`, sharing the app-wide blocker.
|
|
80
|
-
*
|
|
81
|
-
* Returns a {@link Blocker} that is only ever in the `blocked` state when *this*
|
|
82
|
-
* registration is what blocked the navigation — so several guards can coexist
|
|
83
|
-
* without each of them popping a dialog.
|
|
84
|
-
*
|
|
85
|
-
* Returns an idle blocker when no {@link NavigationBlockerProvider} is mounted
|
|
86
|
-
* above, rather than competing for React Router's single blocker slot.
|
|
87
|
-
*/
|
|
88
|
-
export function useNavigationBlocker(predicate: BlockerFunction): Blocker {
|
|
89
|
-
|
|
90
|
-
const context = useContext(NavigationBlockerContext);
|
|
91
|
-
const id = useId();
|
|
92
|
-
|
|
93
|
-
// Keep the latest predicate without re-registering on every render.
|
|
94
|
-
const predicateRef = useRef(predicate);
|
|
95
|
-
predicateRef.current = predicate;
|
|
96
|
-
|
|
97
|
-
const register = context?.register;
|
|
98
|
-
const unregister = context?.unregister;
|
|
99
|
-
|
|
100
|
-
useEffect(() => {
|
|
101
|
-
if (!register || !unregister) return;
|
|
102
|
-
register(id, (args) => predicateRef.current(args));
|
|
103
|
-
return () => unregister(id);
|
|
104
|
-
}, [register, unregister, id]);
|
|
105
|
-
|
|
106
|
-
if (!context) return IDLE_BLOCKER;
|
|
107
|
-
return context.blockedBy === id ? context.blocker : IDLE_BLOCKER;
|
|
108
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { useAuthController } from "./useAuthController";
|
|
3
|
-
import { Entity } from "@rebasepro/types";
|
|
4
|
-
import { canCreateEntity, canEditEntity, canDeleteEntity, canReadCollection } from "@rebasepro/common";
|
|
5
|
-
import { useCallback, useMemo } from "react";
|
|
6
|
-
import type { AdminCollection } from "@rebasepro/cms-types";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Hook to evaluate roles and permissions for the current user.
|
|
10
|
-
* It abstracts away the need to pass `authController` to permission evaluation functions.
|
|
11
|
-
*/
|
|
12
|
-
export function usePermissions() {
|
|
13
|
-
const authController = useAuthController();
|
|
14
|
-
|
|
15
|
-
const canCreate = useCallback(
|
|
16
|
-
(collection: AdminCollection<any>, path: string) =>
|
|
17
|
-
canCreateEntity(collection, authController, path, null),
|
|
18
|
-
[authController]
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
const canEdit = useCallback(
|
|
22
|
-
(collection: AdminCollection<any>, path: string, entity: Entity<any> | null) =>
|
|
23
|
-
canEditEntity(collection, authController, path, entity),
|
|
24
|
-
[authController]
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const canDelete = useCallback(
|
|
28
|
-
(collection: AdminCollection<any>, path: string, entity: Entity<any> | null) =>
|
|
29
|
-
canDeleteEntity(collection, authController, path, entity),
|
|
30
|
-
[authController]
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const canRead = useCallback(
|
|
34
|
-
(collection: AdminCollection<any>) =>
|
|
35
|
-
canReadCollection(collection, authController),
|
|
36
|
-
[authController]
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return useMemo(() => ({
|
|
40
|
-
canCreate,
|
|
41
|
-
canEdit,
|
|
42
|
-
canDelete,
|
|
43
|
-
canRead
|
|
44
|
-
}), [canCreate, canEdit, canDelete, canRead]);
|
|
45
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { useContext } from "react";
|
|
2
|
-
import { RebaseClientInstanceContext } from "../contexts/RebaseClientInstanceContext";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook to retrieve the full RebaseClient instance from context.
|
|
6
|
-
* Returns `undefined` if no client was provided to `<Rebase>`.
|
|
7
|
-
*/
|
|
8
|
-
export function useRebaseClient<T = any>(): T | undefined {
|
|
9
|
-
return useContext(RebaseClientInstanceContext) as T | undefined;
|
|
10
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { User } from "@rebasepro/types";
|
|
2
|
-
import { AuthController, RebaseContext } from "@rebasepro/cms-types";
|
|
3
|
-
import { wrapAsSdkData } from "@rebasepro/common";
|
|
4
|
-
import { useAuthController } from "./useAuthController";
|
|
5
|
-
import { useRebaseClient } from "./useRebaseClient";
|
|
6
|
-
import { useData } from "./data/useData";
|
|
7
|
-
import { useStorageSource } from "./useStorageSource";
|
|
8
|
-
import { useSnackbarController } from "./useSnackbarController";
|
|
9
|
-
import { useUserConfigurationPersistence } from "./useUserConfigurationPersistence";
|
|
10
|
-
import { useDialogsController } from "./useDialogsController";
|
|
11
|
-
import { useCustomizationController } from "./useCustomizationController";
|
|
12
|
-
import { useAnalyticsController } from "./useAnalyticsController";
|
|
13
|
-
import { useEffectiveRoleController } from "./useEffectiveRoleController";
|
|
14
|
-
import React, { useEffect, useContext } from "react";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// DatabaseAdmin is provided by <Rebase> via DatabaseAdminContext.
|
|
18
|
-
import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Hook to retrieve the {@link RebaseContext}.
|
|
22
|
-
*
|
|
23
|
-
* Consider that in order to use this hook you need to have a parent
|
|
24
|
-
* `Rebase` component.
|
|
25
|
-
*
|
|
26
|
-
* @see RebaseContext
|
|
27
|
-
* @group Hooks and utilities
|
|
28
|
-
*/
|
|
29
|
-
export const useRebaseContext = <USER extends User = User, AuthControllerType extends AuthController<USER> = AuthController<USER>>(): RebaseContext<USER, AuthControllerType> => {
|
|
30
|
-
|
|
31
|
-
const client = useRebaseClient<any>();
|
|
32
|
-
|
|
33
|
-
const authController = useAuthController<USER, AuthControllerType>();
|
|
34
|
-
// `context.data` is the flat SDK view — identical in shape to the backend
|
|
35
|
-
// `context.data` and the frontend SDK client, so callbacks behave the same
|
|
36
|
-
// everywhere. The admin reads Entities via `useData()` directly.
|
|
37
|
-
const entityData = useData();
|
|
38
|
-
const data = React.useMemo(() => wrapAsSdkData(entityData), [entityData]);
|
|
39
|
-
const storageSource = useStorageSource();
|
|
40
|
-
const snackbarController = useSnackbarController();
|
|
41
|
-
const userConfigPersistence = useUserConfigurationPersistence();
|
|
42
|
-
const dialogsController = useDialogsController();
|
|
43
|
-
const customizationController = useCustomizationController();
|
|
44
|
-
const analyticsController = useAnalyticsController();
|
|
45
|
-
const effectiveRoleController = useEffectiveRoleController();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Will get `databaseAdmin` from context
|
|
49
|
-
const databaseAdmin = useContext(DatabaseAdminContext);
|
|
50
|
-
|
|
51
|
-
const rebaseContextRef = React.useRef<RebaseContext<USER, AuthControllerType>>({
|
|
52
|
-
authController,
|
|
53
|
-
data,
|
|
54
|
-
storageSource,
|
|
55
|
-
snackbarController,
|
|
56
|
-
userConfigPersistence,
|
|
57
|
-
dialogsController,
|
|
58
|
-
customizationController,
|
|
59
|
-
analyticsController,
|
|
60
|
-
effectiveRoleController,
|
|
61
|
-
databaseAdmin,
|
|
62
|
-
client: client! // Client should be provided
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
React.useEffect(() => {
|
|
66
|
-
rebaseContextRef.current = {
|
|
67
|
-
authController,
|
|
68
|
-
data,
|
|
69
|
-
storageSource,
|
|
70
|
-
snackbarController,
|
|
71
|
-
userConfigPersistence,
|
|
72
|
-
dialogsController,
|
|
73
|
-
customizationController,
|
|
74
|
-
analyticsController,
|
|
75
|
-
effectiveRoleController,
|
|
76
|
-
databaseAdmin,
|
|
77
|
-
client: client!
|
|
78
|
-
};
|
|
79
|
-
}, [authController, data, storageSource, snackbarController, userConfigPersistence,
|
|
80
|
-
dialogsController, customizationController, analyticsController,
|
|
81
|
-
effectiveRoleController, databaseAdmin, client]);
|
|
82
|
-
|
|
83
|
-
return rebaseContextRef.current;
|
|
84
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import type { RebaseRegistryController, RebaseCMSConfig, RebaseStudioConfig, RebaseAuthViewConfig } from "@rebasepro/cms-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, RebaseCMS, 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
|
-
registerAdmin: (config: RebaseCMSConfig) => void;
|
|
16
|
-
unregisterAdmin: () => void;
|
|
17
|
-
registerStudio: (config: RebaseStudioConfig) => void;
|
|
18
|
-
unregisterStudio: () => void;
|
|
19
|
-
registerAuth: (config: RebaseAuthViewConfig) => void;
|
|
20
|
-
unregisterAuth: () => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface RegistryState {
|
|
24
|
-
cmsConfig: RebaseCMSConfig | null;
|
|
25
|
-
studioConfig: RebaseStudioConfig | null;
|
|
26
|
-
authConfig: RebaseAuthViewConfig | 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<RebaseCMSConfig | null>(null);
|
|
38
|
-
const [studioConfig, setStudioConfig] = useState<RebaseStudioConfig | null>(null);
|
|
39
|
-
const [authConfig, setAuthConfig] = useState<RebaseAuthViewConfig | null>(null);
|
|
40
|
-
|
|
41
|
-
// Dispatch functions are stable — never change identity
|
|
42
|
-
const dispatch = useMemo<RegistryDispatch>(() => ({
|
|
43
|
-
registerAdmin: (config: RebaseCMSConfig) => setCmsConfig(config),
|
|
44
|
-
unregisterAdmin: () => setCmsConfig(null),
|
|
45
|
-
registerStudio: (config: RebaseStudioConfig) => setStudioConfig(config),
|
|
46
|
-
unregisterStudio: () => setStudioConfig(null),
|
|
47
|
-
registerAuth: (config: RebaseAuthViewConfig) => 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, RebaseCMS, 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
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
2
|
-
import { lazyChunk } from "@rebasepro/ui";
|
|
3
|
-
import type { ComponentRef, LazyComponentRef } from "@rebasepro/types";
|
|
4
|
-
import { isLazyComponentRef } from "@rebasepro/types";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Internal cache for resolved lazy components.
|
|
8
|
-
*
|
|
9
|
-
* `React.lazy()` must return the SAME wrapper across renders — if we call
|
|
10
|
-
* `lazy(loader)` on every render, React will treat each result as a new
|
|
11
|
-
* component type and unmount/remount on each render cycle.
|
|
12
|
-
*
|
|
13
|
-
* This WeakMap keeps a stable mapping from a `ComponentRef` (object/function)
|
|
14
|
-
* to the `React.lazy()` wrapper it produced. Strings are keyed by a separate
|
|
15
|
-
* plain Map since they can't be WeakMap keys.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const lazyCache = new WeakMap<object | ((...args: any[]) => any), React.ComponentType<any>>();
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Resolves a `ComponentRef` into a renderable `React.ComponentType`.
|
|
22
|
-
*
|
|
23
|
-
* This hook handles all three forms of `ComponentRef`:
|
|
24
|
-
*
|
|
25
|
-
* 1. **`LazyComponentRef`** (produced by the Vite plugin from string paths):
|
|
26
|
-
* Wraps the lazy loader with `React.lazy()` for automatic code-splitting.
|
|
27
|
-
*
|
|
28
|
-
* 2. **`() => Promise<{ default: ComponentType }>`** (manual lazy import):
|
|
29
|
-
* Also wraps with `React.lazy()`. Distinguished from regular components
|
|
30
|
-
* by checking that the function has no parameters and is not a known
|
|
31
|
-
* React internal (no `$$typeof`).
|
|
32
|
-
*
|
|
33
|
-
* 3. **Direct `React.ComponentType`**:
|
|
34
|
-
* Returned as-is.
|
|
35
|
-
*
|
|
36
|
-
* If the ref is `undefined` or a raw string (which should never happen at
|
|
37
|
-
* runtime in the browser since the Vite plugin transforms strings), returns `undefined`.
|
|
38
|
-
*
|
|
39
|
-
* The returned component is stable across re-renders as long as the `ref`
|
|
40
|
-
* is referentially stable.
|
|
41
|
-
*
|
|
42
|
-
* **Usage:** Wrap the rendered component in `<Suspense>` to handle the
|
|
43
|
-
* loading state of lazy components.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```tsx
|
|
47
|
-
* const ResolvedField = useResolvedComponent(property.ui?.Field);
|
|
48
|
-
* if (!ResolvedField) return null;
|
|
49
|
-
* return (
|
|
50
|
-
* <Suspense fallback={<CircularProgress />}>
|
|
51
|
-
* <ResolvedField {...fieldProps} />
|
|
52
|
-
* </Suspense>
|
|
53
|
-
* );
|
|
54
|
-
* ```
|
|
55
|
-
* */
|
|
56
|
-
export function useResolvedComponent<P = unknown>(
|
|
57
|
-
ref: ComponentRef<P> | undefined
|
|
58
|
-
): React.ComponentType<P> | undefined {
|
|
59
|
-
return useMemo(() => resolveComponentRef(ref), [ref]);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Wraps a loader function with `React.lazy()`, caching the result so the
|
|
64
|
-
* same loader always returns the same lazy component identity.
|
|
65
|
-
*/
|
|
66
|
-
function getOrCreateLazy<P>(
|
|
67
|
-
key: object | ((...args: any[]) => any),
|
|
68
|
-
loader: () => Promise<{ default: React.ComponentType<P> }>
|
|
69
|
-
): React.ComponentType<P> {
|
|
70
|
-
const cached = lazyCache.get(key);
|
|
71
|
-
if (cached) return cached as React.ComponentType<P>;
|
|
72
|
-
|
|
73
|
-
// SAFETY: React.lazy returns LazyExoticComponent which is structurally compatible with ComponentType<P>
|
|
74
|
-
// `lazyChunk`, not `lazy`: a custom view is a chunk like any other, and a
|
|
75
|
-
// tab left open across a deploy cannot fetch it either.
|
|
76
|
-
const LazyComponent = lazyChunk(loader) as React.ComponentType<P>;
|
|
77
|
-
lazyCache.set(key, LazyComponent);
|
|
78
|
-
return LazyComponent;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Pure function version of the resolver, for use outside React components.
|
|
83
|
-
* Same resolution logic as `useResolvedComponent`.
|
|
84
|
-
*
|
|
85
|
-
* Results are cached per reference identity — calling this multiple times
|
|
86
|
-
* with the same `ref` object returns the same `React.ComponentType`.
|
|
87
|
-
*/
|
|
88
|
-
export function resolveComponentRef<P = unknown>(
|
|
89
|
-
ref: ComponentRef<P> | undefined
|
|
90
|
-
): React.ComponentType<P> | undefined {
|
|
91
|
-
if (ref == null) return undefined;
|
|
92
|
-
|
|
93
|
-
// 1. String — should not happen at runtime (Vite transforms them).
|
|
94
|
-
// Log a warning and bail out.
|
|
95
|
-
if (typeof ref === "string") {
|
|
96
|
-
console.warn(
|
|
97
|
-
`[Rebase] Encountered a raw string ComponentRef ("${ref}") at runtime. ` +
|
|
98
|
-
"This usually means the Vite transform plugin did not process this file. " +
|
|
99
|
-
"Ensure the file is inside the configured collectionsDir."
|
|
100
|
-
);
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 2. LazyComponentRef — produced by the Vite plugin from string paths.
|
|
105
|
-
// The object has { __rebaseLazy: true, load: () => import(...) }.
|
|
106
|
-
if (isLazyComponentRef(ref)) {
|
|
107
|
-
return getOrCreateLazy<P>(
|
|
108
|
-
ref as object,
|
|
109
|
-
// `load()` resolves to `ComponentLike<P>` — the structural stand-in
|
|
110
|
-
// that lets `ComponentRef` live in the React-free core. This function
|
|
111
|
-
// is the one place that boundary is crossed, so it is the one place
|
|
112
|
-
// the widening is asserted: `ComponentLike` accepts a return type of
|
|
113
|
-
// `unknown` where React wants `ReactNode`. What actually arrives is
|
|
114
|
-
// whatever the collection file pointed at, and rendering it is
|
|
115
|
-
// React's problem either way.
|
|
116
|
-
() => (ref as LazyComponentRef<P>).load() as Promise<{ default: React.ComponentType<P> }>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 3. Function — either a React component or a lazy import loader.
|
|
121
|
-
if (typeof ref === "function") {
|
|
122
|
-
const fn = ref as (...args: any[]) => any;
|
|
123
|
-
|
|
124
|
-
// Class components (React.Component / PureComponent) have this flag
|
|
125
|
-
if (fn.prototype?.isReactComponent) {
|
|
126
|
-
return ref as React.ComponentType<P>;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// React internals (forwardRef, memo, etc.) carry $$typeof
|
|
130
|
-
if ("$$typeof" in fn) {
|
|
131
|
-
return ref as React.ComponentType<P>;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// A function with declared parameters (fn.length > 0) is a regular
|
|
135
|
-
// component that accepts props — return as-is.
|
|
136
|
-
if (fn.length > 0) {
|
|
137
|
-
return ref as React.ComponentType<P>;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Zero-parameter function — either a React component that takes no props,
|
|
141
|
-
// or a lazy loader `() => import(...)`.
|
|
142
|
-
//
|
|
143
|
-
// Nothing here can be decided with certainty from the outside, so the
|
|
144
|
-
// signals are ordered by how much they can be trusted, and the strongest
|
|
145
|
-
// one is what the function *does*, not what it is called.
|
|
146
|
-
//
|
|
147
|
-
// The name alone is not usable evidence, which is what this used to rely on.
|
|
148
|
-
// `admin: { Preview: () => import("./BodyPartsPreview") }` gives the arrow
|
|
149
|
-
// the *inferred* name "Preview" — JS names an anonymous function after the
|
|
150
|
-
// property key it is assigned to — so the "starts with an uppercase letter,
|
|
151
|
-
// therefore a component" rule matched every lazy loader written in the
|
|
152
|
-
// documented way. The thunk was then handed to React as a component, React
|
|
153
|
-
// called it, got a Promise back, and rendered nothing. That is what a blank
|
|
154
|
-
// custom preview column in the demo was.
|
|
155
|
-
const source = (() => {
|
|
156
|
-
try {
|
|
157
|
-
return Function.prototype.toString.call(fn);
|
|
158
|
-
} catch {
|
|
159
|
-
return "";
|
|
160
|
-
}
|
|
161
|
-
})();
|
|
162
|
-
|
|
163
|
-
// A dynamic module load in the body is the strongest signal, and it outranks
|
|
164
|
-
// the name: a toolchain cannot rename it away without breaking the fetch,
|
|
165
|
-
// whereas the name is often not the author's at all.
|
|
166
|
-
//
|
|
167
|
-
// Both spellings are checked because the body reaching us depends on the
|
|
168
|
-
// transform. Vite and any ESM build keep `import(...)`; ts-jest and other
|
|
169
|
-
// CommonJS transforms rewrite it to `require(...)`. Matching only the first
|
|
170
|
-
// passes in the browser and fails in the test suite, which is precisely how
|
|
171
|
-
// this went unnoticed. A zero-argument *component* containing either call is
|
|
172
|
-
// not a thing — components import at module scope.
|
|
173
|
-
const loadsAChunk = /\b(?:import|require)\s*\(/.test(source);
|
|
174
|
-
|
|
175
|
-
if (!loadsAChunk && fn.name && /^[A-Z]/.test(fn.name)) {
|
|
176
|
-
// Named like a component and does not load a chunk: a component.
|
|
177
|
-
return ref as React.ComponentType<P>;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Treat as a lazy loader — wrap with React.lazy, cached by identity.
|
|
181
|
-
return getOrCreateLazy<P>(
|
|
182
|
-
fn,
|
|
183
|
-
ref as () => Promise<{ default: React.ComponentType<P> }>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 4. React wrapper objects (React.forwardRef, React.memo) —
|
|
188
|
-
// These are objects with $$typeof but are not functions.
|
|
189
|
-
if (typeof ref === "object" && "$$typeof" in (ref as object)) {
|
|
190
|
-
return ref as unknown as React.ComponentType<P>;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// 5. Unknown shape — return undefined to be safe
|
|
194
|
-
return undefined;
|
|
195
|
-
}
|
package/src/hooks/useSlot.tsx
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { SlotName, SlotRegistry } from "@rebasepro/cms-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
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { SnackbarAction, SnackbarMessageType } from "@rebasepro/cms-types";
|
|
2
|
-
import React, { useCallback, useMemo } from "react";
|
|
3
|
-
import { useSnackbar } from "notistack";
|
|
4
|
-
import { Button } from "@rebasepro/ui";
|
|
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
|
-
action?: SnackbarAction;
|
|
28
|
-
}) => {
|
|
29
|
-
const {
|
|
30
|
-
type,
|
|
31
|
-
message,
|
|
32
|
-
autoHideDuration,
|
|
33
|
-
action
|
|
34
|
-
} = props;
|
|
35
|
-
enqueueSnackbar({
|
|
36
|
-
message: props.title ? <div className={"flex flex-col"}>
|
|
37
|
-
<strong>{props.title}</strong>
|
|
38
|
-
{message}
|
|
39
|
-
</div> : message,
|
|
40
|
-
variant: type,
|
|
41
|
-
autoHideDuration,
|
|
42
|
-
// The provider sets `preventDuplicate`, which collapses two
|
|
43
|
-
// snackbars carrying the same message into one. That is right for a
|
|
44
|
-
// confirmation and wrong for an action: deleting two rows in a row
|
|
45
|
-
// produces the same "Deleted" text twice, and swallowing the second
|
|
46
|
-
// would leave the second deletion with no way to undo it. A key
|
|
47
|
-
// makes each one distinct.
|
|
48
|
-
...(action ? { key: nextActionKey() } : {}),
|
|
49
|
-
action: action
|
|
50
|
-
? (key) => (
|
|
51
|
-
<Button variant={"text"}
|
|
52
|
-
size={"small"}
|
|
53
|
-
className={"text-inherit"}
|
|
54
|
-
onClick={() => {
|
|
55
|
-
action.onClick();
|
|
56
|
-
// The offer has been taken; leaving it up
|
|
57
|
-
// invites a second click that undoes nothing,
|
|
58
|
-
// or worse, undoes something else.
|
|
59
|
-
closeSnackbar(key);
|
|
60
|
-
}}>
|
|
61
|
-
{action.label}
|
|
62
|
-
</Button>
|
|
63
|
-
)
|
|
64
|
-
: undefined
|
|
65
|
-
})
|
|
66
|
-
}, [enqueueSnackbar, closeSnackbar]);
|
|
67
|
-
|
|
68
|
-
const close = useCallback(() => {
|
|
69
|
-
closeSnackbar();
|
|
70
|
-
}, [closeSnackbar]);
|
|
71
|
-
|
|
72
|
-
return useMemo(() => ({
|
|
73
|
-
open,
|
|
74
|
-
close
|
|
75
|
-
}), [open, close]);
|
|
76
|
-
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
let actionKeySeq = 0;
|
|
80
|
-
const nextActionKey = () => `rebase-snackbar-action-${actionKeySeq++}`;
|