"use client"; import * as React from "react"; import * as ToastPrimitives from "@radix-ui/react-toast"; import { cva, type VariantProps } from "class-variance-authority"; import { X } from "lucide-react"; import { cn } from "../../lib/utils"; const ToastProvider = ToastPrimitives.Provider; const ToastViewport = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ToastViewport.displayName = ToastPrimitives.Viewport.displayName; const toastVariants = cva( "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-8 shadow-sm transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { default: "border bg-background text-foreground", destructive: "destructive group border-destructive bg-destructive text-destructive-foreground", success: "border-green-200 bg-green-50 text-green-900 dark:border-green-800 dark:bg-green-900/20 dark:text-green-100", warning: "border-orange-200 bg-orange-50 text-orange-900 dark:border-orange-800 dark:bg-orange-900/20 dark:text-orange-100", info: "border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-800 dark:bg-blue-900/20 dark:text-blue-100", }, }, defaultVariants: { variant: "default", }, } ); type ToastProps = React.ComponentPropsWithoutRef & VariantProps; type ToastActionElement = React.ReactElement; const Toast = React.forwardRef< React.ElementRef, ToastProps >(({ className, variant, ...props }, ref) => { return ( ); }); Toast.displayName = ToastPrimitives.Root.displayName; const ToastAction = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ToastAction.displayName = ToastPrimitives.Action.displayName; const ToastClose = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ToastClose.displayName = ToastPrimitives.Close.displayName; const ToastTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ToastTitle.displayName = ToastPrimitives.Title.displayName; const ToastDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ToastDescription.displayName = ToastPrimitives.Description.displayName; type ToastData = { id: string; title?: React.ReactNode; description?: React.ReactNode; action?: ToastActionElement; variant?: ToastProps["variant"]; duration?: number; open?: boolean; onOpenChange?: (open: boolean) => void; }; const TOAST_LIMIT = 5; const TOAST_REMOVE_DELAY = 1000000; type State = { toasts: ToastData[]; }; const toastTimeouts = new Map>(); const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { return; } const timeout = setTimeout(() => { toastTimeouts.delete(toastId); dispatch({ type: "REMOVE_TOAST", toastId: toastId, }); }, TOAST_REMOVE_DELAY); toastTimeouts.set(toastId, timeout); }; const reducer = (state: State, action: any): State => { 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((t) => t.id === action.toast.id ? { ...t, ...action.toast } : t ), }; case "DISMISS_TOAST": { const { toastId } = action; if (toastId) { addToRemoveQueue(toastId); } else { state.toasts.forEach((toast) => { addToRemoveQueue(toast.id); }); } return { ...state, toasts: state.toasts.map((t) => t.id === toastId || toastId === undefined ? { ...t, open: false, } : t ), }; } case "REMOVE_TOAST": if (action.toastId === undefined) { return { ...state, toasts: [], }; } return { ...state, toasts: state.toasts.filter((t) => t.id !== action.toastId), }; default: return state; } }; const listeners: Array<(state: State) => void> = []; let memoryState: State = { toasts: [] }; let toastCounter = 0; function dispatch(action: any) { memoryState = reducer(memoryState, action); listeners.forEach((listener) => { listener(memoryState); }); } type Toast = Omit; function toast({ ...props }: Toast) { const id = `toast-${Date.now()}-${++toastCounter}`; const update = (props: ToastData) => dispatch({ type: "UPDATE_TOAST", toast: { ...props, id }, }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); dispatch({ type: "ADD_TOAST", toast: { ...props, id, open: true, onOpenChange: (open: boolean) => { if (!open) dismiss(); }, }, }); return { id: id, dismiss, update, }; } 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); } }; }, [state]); return { ...state, toast, dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), }; } function Toaster() { const { toasts } = useToast(); return ( {toasts.map(function ({ id, title, description, action, variant, open, onOpenChange, ...props }) { return (
{title && {title}} {description && ( {description} )}
{action}
); })}
); } export { type ToastProps, type ToastActionElement, ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastAction, ToastClose, Toaster, toast, useToast, };