# All files / src/components/notifications Toast.tsx

75% Statements 6/8
100% Branches 0/0
60% Functions 3/5
83.33% Lines 5/6

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

    7x

    15x
    15x
    15x

    15x

     import { useEffect, type ReactNode, type JSX } from "react";
    import { AlertTriangle, Check, Info, X } from "lucide-react";
    import { motion } from "motion/react";
    import type { ToastItem } from "../../context/ToastContext.js";
    import styles from "./Toast.module.scss";
    import { ICON_LG, ICON_MD } from "../../utils/iconSize.js";

    const VARIANT_ICONS: Record<ToastItem["variant"], ReactNode> = {
      success: <Check size={ICON_LG} />,
      error: <X size={ICON_LG} />,
      warning: <AlertTriangle size={ICON_LG} />,
      info: <Info size={ICON_LG} />,
    };

    interface ToastProps {
      toast: ToastItem;
      onDismiss: (id: string) => void;
    }

    /** Animated individual toast notification. Auto-dismisses after toast.duration ms. */
    export function Toast({ toast, onDismiss }: ToastProps): JSX.Element {
      useEffect(() => {
        const timer = setTimeout(() => onDismiss(toast.id), toast.duration);
        return () => clearTimeout(timer);
      }, [toast.id, toast.duration, onDismiss]);

      return (
        <motion.div
          className={`${styles.toast} ${styles[toast.variant]}`}
          role="status"
          initial={{ opacity: 0, y: -16, scale: 0.94 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: -8, scale: 0.94 }}
          transition={{ duration: 0.2, ease: "easeOut" }}
          layout
        >
          <span className={styles.icon} aria-hidden="true">
            {VARIANT_ICONS[toast.variant]}
          </span>
          <span className={styles.message}>{toast.message}</span>
          <button
            type="button"
            className={styles.close}
            onClick={() => onDismiss(toast.id)}
            aria-label="Dismiss notification"
          >
            <X size={ICON_MD} aria-hidden="true" />
          </button>
        </motion.div>
      );
    }