Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import type { ShowNotification, CloseNotification } from './useNotifications';
import type { ShowNotification, CloseNotification, RemoveNotification } from './useNotifications';

/**
* @ignore - internal component.
Expand All @@ -9,6 +9,7 @@ import type { ShowNotification, CloseNotification } from './useNotifications';
export interface NotificationsContextValue {
show: ShowNotification;
close: CloseNotification;
remove: RemoveNotification;
}

export const NotificationsContext = React.createContext<NotificationsContextValue | null>(null);
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
CloseNotification,
ShowNotification,
ShowNotificationOptions,
RemoveNotification,
} from './useNotifications';
import { useLocaleText, type LocaleText } from '../AppProvider/LocalizationProvider';

Expand Down Expand Up @@ -55,8 +56,7 @@ interface NotificationProps {
function Notification({ notificationKey, open, message, options, badge }: NotificationProps) {
const globalLocaleText = useLocaleText();
const localeText = { ...defaultLocaleText, ...globalLocaleText };
const { close } = useNonNullableContext(NotificationsContext);

const { close, remove } = useNonNullableContext(NotificationsContext);
const { severity, actionText, onAction, autoHideDuration } = options;

const handleClose = React.useCallback(
Expand All @@ -69,6 +69,10 @@ function Notification({ notificationKey, open, message, options, badge }: Notifi
[notificationKey, close],
);

const handleExited = React.useCallback(() => {
remove(notificationKey);
}, [notificationKey, remove]);

const action = (
<React.Fragment>
{onAction ? (
Expand All @@ -90,10 +94,25 @@ function Notification({ notificationKey, open, message, options, badge }: Notifi

const props = React.useContext(RootPropsContext);
const SnackbarComponent = props?.slots?.snackbar ?? Snackbar;

// Passing `onExited` through `externalSlotProps` here.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Janpot This is not ideal, but I couldn't think of a better way that didn't involve rewriting mergeSlotProps. Let me know if I'm missing something - I'm fairly new to React and mui.

// Passing it through `additionalProps` causes it to be overwritten when
// transition slotProps are specified in RootPropsContext.
const externalSnackbarSlotProps = props?.slotProps?.snackbar?.slotProps;
const externalTransitionProps = externalSnackbarSlotProps?.transition;
const snackbarSlotProps = useSlotProps({
elementType: SnackbarComponent,
ownerState: props,
externalSlotProps: props?.slotProps?.snackbar,
externalSlotProps: {
...props?.slotProps?.snackbar,
slotProps: {
...externalSnackbarSlotProps,
transition: {
...externalTransitionProps,
onExited: handleExited,
},
},
},
additionalProps: {
open,
autoHideDuration,
Expand Down Expand Up @@ -193,11 +212,18 @@ function NotificationsProvider(props: NotificationsProviderProps) {
const close = React.useCallback<CloseNotification>((key) => {
setState((prev) => ({
...prev,
queue: prev.queue.filter((n) => n.notificationKey !== key),
queue: prev.queue.map((n) => (n.notificationKey === key ? { ...n, open: false } : n)),
}));
}, []);

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

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

return (
<RootPropsContext.Provider value={props}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import * as React from 'react';
import { describe, test, expect } from 'vitest';
import { renderHook, within, screen } from '@testing-library/react';
import { renderHook, within, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { useNotifications } from './useNotifications';
import { NotificationsProvider } from './NotificationsProvider';
Expand Down Expand Up @@ -35,6 +35,8 @@ describe('useNotifications', () => {

rerender();

expect(screen.queryByRole('alert')).toBeNull();
await waitFor(() => {
expect(screen.queryByRole('alert')).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ export interface CloseNotification {
(key: string): void;
}

export interface RemoveNotification {
/**
* Remove a snackbar from the application state (after it has been closed).
*
* @param key The key of the notification to remove.
*/
(key: string): void;
}

interface UseNotifications {
show: ShowNotification;
close: CloseNotification;
Expand Down