"use client"; import * as React from "react"; import type { ToastProps } from "@radix-ui/react-toast"; const TOAST_LIMIT = 1; const TOAST_REMOVE_DELAY = 3000; type ToasterToast = ToastProps & { id: string; title?: React.ReactNode; }; type ActionType = { ADD_TOAST: "ADD_TOAST"; DISMISS_TOAST: "DISMISS_TOAST"; REMOVE_TOAST: "REMOVE_TOAST"; }; type Action = | { type: ActionType["ADD_TOAST"]; toast: ToasterToast } | { type: ActionType["DISMISS_TOAST"]; toastId?: string } | { type: ActionType["REMOVE_TOAST"]; toastId?: string }; interface State { toasts: ToasterToast[]; } const toastTimeouts = new Map>(); const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) return; const timeout = setTimeout(() => { toastTimeouts.delete(toastId); dispatch({ type: "REMOVE_TOAST", toastId }); }, TOAST_REMOVE_DELAY); toastTimeouts.set(toastId, timeout); }; const reducer = (state: State, action: Action): State => { switch (action.type) { case "ADD_TOAST": return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), }; case "DISMISS_TOAST": { const { toastId } = action; if (toastId) { addToRemoveQueue(toastId); } else { state.toasts.forEach((toast) => addToRemoveQueue(toast.id)); } return { ...state, toasts: state.toasts.map((toast) => toast.id === toastId || toastId === undefined ? { ...toast, open: false } : toast ), }; } case "REMOVE_TOAST": if (action.toastId === undefined) { return { ...state, toasts: [] }; } return { ...state, toasts: state.toasts.filter((toast) => toast.id !== action.toastId), }; default: return state; } }; const listeners: Array<(state: State) => void> = []; let memoryState: State = { toasts: [] }; let count = 0; const dispatch = (action: Action) => { memoryState = reducer(memoryState, action); listeners.forEach((listener) => listener(memoryState)); }; export function toast({ title, ...props }: Omit) { const id = String(++count); const update = (next: ToasterToast) => dispatch({ type: "ADD_TOAST", toast: { ...next, id }, }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); update({ id, title, open: true, onOpenChange: (open) => { if (!open) dismiss(); }, ...props, }); return { id, dismiss }; } export function useToast() { const [state, setState] = React.useState(memoryState); React.useEffect(() => { listeners.push(setState); return () => { const index = listeners.indexOf(setState); if (index > -1) listeners.splice(index, 1); }; }, []); return { ...state, toast, dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), }; }