# All files / src/components ToastProvider.tsx

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

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
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103

     import React, { createContext, useContext, useState, useCallback } from 'react';

    type ToastType = 'success' | 'error' | 'warning' | 'info';

    interface Toast {
      id: string;
      type: ToastType;
      message: string;
      duration?: number;
    }

    interface ToastContextType {
      showToast: (message: string, type?: ToastType, duration?: number) => void;
      hideToast: (id: string) => void;
    }

    const ToastContext = createContext<ToastContextType | undefined>(undefined);

    export function useToast() {
      const context = useContext(ToastContext);
      if (!context) {
        throw new Error('useToast must be used within ToastProvider');
      }
      return context;
    }

    export function ToastProvider({ children }: { children: React.ReactNode }) {
      const [toasts, setToasts] = useState<Toast[]>([]);

      const showToast = useCallback((message: string, type: ToastType = 'info', duration: number = 5000) => {
        const id = Math.random().toString(36).substring(7);
        const toast: Toast = { id, type, message, duration };

        setToasts((prev) => [...prev, toast]);

        if (duration > 0) {
          setTimeout(() => {
            hideToast(id);
          }, duration);
        }
      }, []);

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

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

    function ToastContainer({ toasts, onClose }: { toasts: Toast[]; onClose: (id: string) => void }) {
      return (
        <div className="fixed top-4 right-4 z-50 space-y-2 pointer-events-none">
          {toasts.map((toast) => (
            <ToastItem key={toast.id} toast={toast} onClose={onClose} />
          ))}
        </div>
      );
    }

    function ToastItem({ toast, onClose }: { toast: Toast; onClose: (id: string) => void }) {
      const icons = {
        success: '✓',
        error: '✗',
        warning: '⚠',
        info: 'ℹ'
      };

      const styles = {
        success: 'bg-green-500 text-white',
        error: 'bg-red-500 text-white',
        warning: 'bg-yellow-500 text-white',
        info: 'bg-blue-500 text-white'
      };

      return (
        <div
          className={`
            ${styles[toast.type]}
            px-6 py-4 rounded-lg shadow-lg
            flex items-center gap-3 min-w-[300px] max-w-md
            pointer-events-auto
            animate-slide-in-right
          `}
        >
          <span className="text-xl font-bold">{icons[toast.type]}</span>
          <p className="flex-1 text-sm font-medium">{toast.message}</p>
          <button
            onClick={() => onClose(toast.id)}
            className="text-white/80 hover:text-white transition-colors"
          >
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
            </svg>
          </button>
        </div>
      );
    }