Skip to content

Commit

Permalink
[notifications] Implement global notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot committed Jan 22, 2025
1 parent eee95b6 commit bd59031
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 67 deletions.

This file was deleted.

2 changes: 1 addition & 1 deletion packages/toolpad-core/src/useNotifications/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './useNotifications';
export * from './NotificationsProvider';
export * from './notifications';
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,12 @@ interface NotificationsState {
}

interface NotificationsProps {
state: NotificationsState;
subscribe: (cb: () => void) => () => void;
getState: () => NotificationsState;
}

function Notifications({ state }: NotificationsProps) {
function Notifications({ subscribe, getState }: NotificationsProps) {
const state = React.useSyncExternalStore(subscribe, getState, getState);
const currentNotification = state.queue[0] ?? null;

return currentNotification ? (
Expand All @@ -148,55 +150,83 @@ const generateId = () => {
return id;
};

/**
* Provider for Notifications. The subtree of this component can use the `useNotifications` hook to
* access the notifications API. The notifications are shown in the same order they are requested.
*
* Demos:
*
* - [Sign-in Page](https://mui.com/toolpad/core/react-sign-in-page/)
* - [useNotifications](https://mui.com/toolpad/core/react-use-notifications/)
*
* API:
*
* - [NotificationsProvider API](https://mui.com/toolpad/core/api/notifications-provider)
*/
function NotificationsProvider(props: NotificationsProviderProps) {
const { children } = props;
const [state, setState] = React.useState<NotificationsState>({ queue: [] });

const show = React.useCallback<ShowNotification>((message, options = {}) => {
interface NotificationsApi {
show: ShowNotification;
close: CloseNotification;
Provider: React.ComponentType<NotificationsProviderProps>;
}

export function createNotifications(): NotificationsApi {
let state: NotificationsState = { queue: [] };
const listeners = new Set<() => void>();

const getState = () => state;
const subscribeState = (listener: () => void) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
};
const fireUpdateEvent = () => {
for (const listener of listeners) {
listener();
}
};

const show: ShowNotification = (message, options = {}) => {
const notificationKey = options.key ?? `::toolpad-internal::notification::${generateId()}`;
setState((prev) => {
if (prev.queue.some((n) => n.notificationKey === notificationKey)) {
// deduplicate by key
return prev;
}
return {
...prev,
queue: [...prev.queue, { message, options, notificationKey, open: true }],
if (!state.queue.some((n) => n.notificationKey === notificationKey)) {
state = {
...state,
queue: [...state.queue, { message, options, notificationKey, open: true }],
};
});
fireUpdateEvent();
}

return notificationKey;
}, []);
};

const close = React.useCallback<CloseNotification>((key) => {
setState((prev) => ({
...prev,
queue: prev.queue.filter((n) => n.notificationKey !== key),
}));
}, []);
const close: CloseNotification = (key) => {
state = {
...state,
queue: state.queue.filter((n) => n.notificationKey !== key),
};
fireUpdateEvent();
};

const contextValue = React.useMemo(() => ({ show, close }), [show, close]);
const contextValue = { show, close };

return (
<RootPropsContext.Provider value={props}>
<NotificationsContext.Provider value={contextValue}>
{children}
<Notifications state={state} />
</NotificationsContext.Provider>
</RootPropsContext.Provider>
);
/**
* Provider for Notifications. The subtree of this component can use the `useNotifications` hook to
* access the notifications API. The notifications are shown in the same order they are requested.
*
* Demos:
*
* - [Sign-in Page](https://mui.com/toolpad/core/react-sign-in-page/)
* - [useNotifications](https://mui.com/toolpad/core/react-use-notifications/)
*
* API:
*
* - [NotificationsProvider API](https://mui.com/toolpad/core/api/notifications-provider)
*/
function Provider(props: NotificationsProviderProps) {
return (
<RootPropsContext.Provider value={props}>
<NotificationsContext.Provider value={contextValue}>
{props.children}
<Notifications getState={getState} subscribe={subscribeState} />
</NotificationsContext.Provider>
</RootPropsContext.Provider>
);
}

return {
show,
close,
Provider,
};
}

export { NotificationsProvider };
const { show, close, Provider } = createNotifications();

export { show, close, Provider as NotificationsProvider };
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest';
import { renderHook, within, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { useNotifications } from './useNotifications';
import { NotificationsProvider } from './NotificationsProvider';
import { NotificationsProvider } from './notifications';

interface TestWrapperProps {
children: React.ReactNode;
Expand Down

0 comments on commit bd59031

Please sign in to comment.