# All files / src/context ToastContext.tsx

0% Statements 0/27
0% Branches 0/1
0% Functions 0/1
0% Lines 0/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67

     import React, {
      createContext,
      useContext,
      useState,
      useRef,
      useCallback,
      type ReactNode,
      type JSX,
    } from "react";

    /** Visual style variant for a toast notification. */
    export type ToastVariant = "success" | "error" | "warning" | "info";

    /** A single toast notification item. */
    export interface ToastItem {
      id: string;
      message: string;
      variant: ToastVariant;
      /** Auto-dismiss delay in milliseconds. */
      duration: number;
    }

    interface ToastContextValue {
      toasts: ToastItem[];
      /** Display a new toast notification. */
      showToast: (message: string, variant?: ToastVariant, duration?: number) => void;
      /** Programmatically remove a toast by id. */
      dismissToast: (id: string) => void;
    }

    const ToastContext: React.Context<ToastContextValue | undefined> = createContext<
      ToastContextValue | undefined
    >(undefined);

    const DEFAULT_DURATION_MS: number = 4_000;

    /** Provides toast notification state to the component tree. Mount <ToastContainer> as a sibling to receive rendered output. */
    export function ToastProvider({ children }: { children: ReactNode }): JSX.Element {
      const [toasts, setToasts] = useState<ToastItem[]>([]);
      const toastCounterRef = useRef<number>(0);

      const dismissToast = useCallback((id: string) => {
        setToasts((prev) => prev.filter((t) => t.id !== id));
      }, []);

      const showToast = useCallback(
        (message: string, variant: ToastVariant = "info", duration: number = DEFAULT_DURATION_MS) => {
          const id = `toast-${++toastCounterRef.current}`;
          setToasts((prev) => [...prev, { id, message, variant, duration }]);
        },
        [],
      );

      return (
        <ToastContext.Provider value={{ toasts, showToast, dismissToast }}>
          {children}
        </ToastContext.Provider>
      );
    }

    /** Consumes the toast context; must be called within a ToastProvider. */
    export function useToast(): ToastContextValue {
      const ctx = useContext(ToastContext);
      if (!ctx) throw new Error("useToast must be used within ToastProvider");
      return ctx;
    }