import { Icon } from "@thorium/ui/Icon"; import { Transition } from "@thorium/ui/Transition"; import uniqid from "@thorium/utils/uniqid"; import { type ReactNode, useEffect, useReducer, useRef, useState } from "react"; import { createPortal } from "react-dom"; // TODO September 25, 2025 - Replace this with React Aria Components const Toast = ({ title, body = null, color, dismiss, visible, action, pause, resume, }: Notification & { dismiss: () => void; }) => { return ( { action?.(); dismiss(); }} onMouseEnter={() => pause()} onMouseLeave={() => resume()} afterLeave={() => dismiss()} >
{title}

{body}

); }; interface Notification { id: string; title: string; body?: ReactNode; visible: boolean; duration?: number; action?: () => any; color?: "info" | "success" | "warning" | "error" | "notice"; pause: () => void; resume: () => void; } function toastReducer(state: Notification[], action: Notification | string) { if (typeof action === "string") { const item = state.find((item) => item.id === action); if (!item?.visible) { return state.filter((notification) => notification.id !== action); } return state.map((notification) => { if (notification.id === action) { return { ...notification, visible: false }; } return notification; }); } return [...state, action]; } export let toast: ( notification: Omit, ) => void = () => {}; export default function ToastContainer() { const [toasts, dispatch] = useReducer(toastReducer, []); const toastsRef = useRef(toasts); toastsRef.current = toasts; useEffect(() => { const timeouts: Record> = {}; toast = (notification: Omit) => { if (toastsRef.current.some((t) => t.visible && t.title === notification.title)) { return; } const id = uniqid("tst-"); const { duration = 5000 } = notification; dispatch({ ...notification, id, visible: true, pause: () => clearTimeout(timeouts[id]), resume: () => setTimeout(() => dispatch(id), duration), }); timeouts[id] = setTimeout(() => { dispatch(id); delete timeouts[id]; timeouts[uniqid()] = setTimeout(() => dispatch(id), 1000); }, duration); }; return () => { for (const timeout in timeouts) { clearTimeout(timeouts[timeout]); } }; }, []); const [portalRef, setPortalRef] = useState(null); useEffect(() => { if (!portalRef) { const div = document.createElement("div"); div.className = "toast-container theme-container"; document.body.appendChild(div); setPortalRef(div); } }, [portalRef]); if (!portalRef) { return null; } return createPortal(
{toasts.map((toast) => ( dispatch(toast.id)} /> ))}
, portalRef, ); }