import * as React from 'react'; import Alert from '@mui/material/Alert'; import Badge from '@mui/material/Badge'; import Button from '@mui/material/Button'; import IconButton from '@mui/material/IconButton'; import Snackbar from '@mui/material/Snackbar'; import SnackbarContent from '@mui/material/SnackbarContent'; import type { SnackbarCloseReason } from '@mui/material/Snackbar'; import type { CloseReason } from '@mui/material/SpeedDial'; import CloseIcon from '@mui/icons-material/Close'; import useSlotProps from '@mui/utils/useSlotProps'; import NotificationsContext from './NotificationsContext'; import type { CloseNotification, ShowNotification, ShowNotificationOptions, } from './useNotifications'; const RootPropsContext = React.createContext( null, ); interface NotificationProps { notificationKey: string; badge: string | null; open: boolean; message: React.ReactNode; options: ShowNotificationOptions; } function Notification({ notificationKey, open, message, options, badge, }: NotificationProps) { const notificationsContext = React.useContext(NotificationsContext); if (!notificationsContext) { throw new Error('Notifications context was used without a provider.'); } const { close } = notificationsContext; const { severity, actionText, onAction, autoHideDuration } = options; const handleClose = React.useCallback( (event: unknown, reason?: CloseReason | SnackbarCloseReason) => { if (reason === 'clickaway') { return; } close(notificationKey); }, [notificationKey, close], ); const action = ( {onAction ? ( ) : null} ); const props = React.useContext(RootPropsContext); const snackbarSlotProps = useSlotProps({ elementType: Snackbar, ownerState: props, externalSlotProps: {}, additionalProps: { open, autoHideDuration, onClose: handleClose, action, }, }); return ( {severity ? ( {message} ) : ( )} ); } interface NotificationQueueEntry { notificationKey: string; options: ShowNotificationOptions; open: boolean; message: React.ReactNode; } interface NotificationsState { queue: NotificationQueueEntry[]; } interface NotificationsProps { state: NotificationsState; } function Notifications({ state }: NotificationsProps) { const currentNotification = state.queue[0] ?? null; return currentNotification ? ( 1 ? String(state.queue.length) : null} /> ) : null; } export interface NotificationsProviderProps { children?: React.ReactNode; } let nextId = 0; const generateId = () => { const id = nextId; nextId += 1; 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. */ export default function NotificationsProvider(props: NotificationsProviderProps) { const { children } = props; const [state, setState] = React.useState({ queue: [] }); const show = React.useCallback((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 }], }; }); return notificationKey; }, []); const close = React.useCallback((key) => { setState((prev) => ({ ...prev, queue: prev.queue.filter((n) => n.notificationKey !== key), })); }, []); const contextValue = React.useMemo(() => ({ show, close }), [show, close]); return ( {children} ); }