All files / components notification.tsx

86.95% Statements 20/23
81.25% Branches 13/16
85.71% Functions 6/7
86.95% Lines 20/23

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    12x                 12x 5x 5x   5x   1x     1x 1x       5x 4x       4x       4x 4x 4x         5x 5x   1x   2x   2x           5x                                                        
'use client';
 
import { useEffect, useCallback, useRef, useState } from 'react';
 
type Props = {
  message: string;
  type?: 'error' | 'success' | 'info';
  onClose: () => void;
  duration?: number;
}
 
export default function Notification({ message, type = 'error', onClose, duration = 1000 }: Props) {
  const timerRef = useRef<NodeJS.Timeout | null>(null);
  const [isExiting, setIsExiting] = useState(false);
 
  const handleClose = useCallback(() => {
    // Déclencher l'animation de sortie
    setIsExiting(true);
    
    // Attendre la fin de l'animation (300ms) puis fermer réellement
    setTimeout(() => {
      onClose();
    }, 300);
  }, [onClose]);
 
  useEffect(() => {
    Iif (timerRef.current) {
      clearTimeout(timerRef.current);
    }
 
    timerRef.current = setTimeout(() => {
      handleClose();
    }, duration);
 
    return () => {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
      }
    };
  }, [handleClose, duration]);
 
  const getTypeStyles = useCallback(() => {
    switch (type) {
      case 'error':
        return 'bg-red-500 border-red-600';
      case 'success':
        return 'bg-green-500 border-green-600';
      case 'info':
        return 'bg-blue-500 border-blue-600';
      default:
        return 'bg-red-500 border-red-600';
    }
  }, [type]);
 
  return (
    <div 
      className={`fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[60] ${getTypeStyles()} text-white px-4 py-3 rounded-lg shadow-lg border-l-4 min-w-[300px] max-w-[400px] transition-all duration-300 ease-in-out ${
        isExiting 
          ? 'animate-slide-out' 
          : 'animate-slide-in'
      }`}
    >
      <div className="flex items-center justify-between">
        <p className="text-sm font-medium">{message}</p>
        <button
          onClick={handleClose}
          className="ml-3 text-white hover:text-gray-200 font-bold text-lg"
          aria-label="Fermer"
        >
          ×
        </button>
      </div>
      <div 
        className={`absolute bottom-0 left-0 h-1 ${type === 'error' ? 'bg-red-300' : type === 'success' ? 'bg-green-300' : 'bg-blue-300'}`}
        style={{
          animation: `progress ${duration}ms linear forwards`,
          animationPlayState: isExiting ? 'paused' : 'running'
        }}
      ></div>
    </div>
  );
}