import * as React from "react"; import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; type ToasterToast = ToastProps & { id: string; title?: React.ReactNode; description?: React.ReactNode; action?: ToastActionElement; }; type ToastState = { toasts: ToasterToast[]; }; type ToastAction = | { type: "ADD_TOAST"; toast: ToasterToast } | { type: "UPDATE_TOAST"; toast: Partial & { id: string } } | { type: "DISMISS_TOAST"; toastId?: string } | { type: "REMOVE_TOAST"; toastId?: string }; const TOAST_LIMIT = 5; const TOAST_REMOVE_DELAY = 1000; const toastTimeouts = new Map>(); const listeners = new Set<(state: ToastState) => void>(); let memoryState: ToastState = { toasts: [] }; function dispatch(action: ToastAction) { memoryState = reducer(memoryState, action); listeners.forEach(listener => { listener(memoryState); }); } function reducer(state: ToastState, action: ToastAction): ToastState { switch (action.type) { case "ADD_TOAST": { return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), }; } case "UPDATE_TOAST": { return { ...state, toasts: state.toasts.map(toast => (toast.id === action.toast.id ? { ...toast, ...action.toast } : toast)), }; } 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; } } function 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); } function genId() { return Math.random().toString(36).slice(2, 10); } export function useToast() { const [state, setState] = React.useState(memoryState); React.useEffect(() => { listeners.add(setState); return () => { listeners.delete(setState); }; }, []); return { ...state, toast: (props: Omit) => { const id = genId(); dispatch({ type: "ADD_TOAST", toast: { ...props, id, open: true } }); return id; }, dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), }; } export const toast = (props: Omit) => { const id = genId(); dispatch({ type: "ADD_TOAST", toast: { ...props, id, open: true } }); return id; };