import { CloseButtonProps, cssTransition, toast as toastDefault, ToastContentProps, ToastOptions as ToastOptionsDefault, } from 'react-toastify'; import React, { useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import styles from './Toast.scss'; import Close from '../../buttons/Close/Close'; import ProgressRing from '../../loaders/ProgressRing/ProgressRing'; import { CheckmarkIcon, ExclamationIcon } from '../../icons/Icons'; const CloseButton = ({ closeToast }: CloseButtonProps) => { return ; }; enum ToastType { success = 'success', error = 'error', cta = 'cta', } export interface ToastOptions extends Pick { /** * Content for toast. Can be simple string or valid React node. * E.g. Component or JSX for rendering icons, text buttons, external links, etc. */ content: React.ReactChild; /** Toast type, can be either "success", "error", or "cta" */ type?: keyof typeof ToastType | ToastType; } type ContentProps = Pick & Partial; const Content = (props: ContentProps) => { const { content, autoClose, type, closeToast, pauseOnHover } = props; const [progress, setProgress] = useState(1); // This needs to be a ref and not state to prevent stale data in the setInterval callback const hovered = useRef(false); const contentRef = useRef(null); const setHoveredTrue = () => { hovered.current = true; }; const setHoveredFalse = () => { hovered.current = false; }; useEffect(() => { if (pauseOnHover) { const toastElement = contentRef.current?.closest('.Toastify__toast'); toastElement?.addEventListener('mouseover', setHoveredTrue); toastElement?.addEventListener('mouseout', setHoveredFalse); return () => { toastElement?.removeEventListener('mouseover', setHoveredTrue); toastElement?.removeEventListener('mouseout', setHoveredFalse); }; } }, [contentRef.current]); useEffect(() => { if (autoClose) { const interval = 100; const intervalID = setInterval(() => { setProgress((current) => { if (pauseOnHover && hovered.current) { return current; } if (current <= 0) { closeToast?.(); clearInterval(intervalID); return 0; } return current - 1 / (autoClose / interval); }); }, interval); } }, []); return (
{autoClose && (
{(type === ToastType.success || type === ToastType.cta) && ( )} {type === ToastType.error && ( )}
)} {content}
); }; const toast = (options: ToastOptions) => { const { content, autoClose = 6000, type = 'success', pauseOnHover = false, ...restOptions } = options; const toastOptions: ToastOptionsDefault = { closeButton: CloseButton, hideProgressBar: true, className: type === ToastType.cta ? 'Theme__Inverted' : undefined, autoClose: false, transition: cssTransition({ enter: styles['Toast__slide-enter'], exit: styles['Toast__slide-exit'], }), pauseOnHover: false, ...restOptions, }; return toastDefault(, toastOptions); }; export const { dismiss: dismissToast } = toastDefault; export default toast;