All files / Rindu/components/CountDown CountDown.tsx

7.14% Statements 3/42
0% Branches 0/13
0% Functions 0/6
7.69% Lines 3/39

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 104 105 106 107 108 10919x   19x                                                                                                                                                                                                                 19x  
import React, { ReactElement, useEffect, useRef } from "react";
 
export const CountDown = ({
  startTime,
  currentProgress,
  isPlaying,
  size = 32,
  strokeWidth = 3,
  color = "#ffffff",
}: {
  startTime: number;
  currentProgress: number;
  isPlaying: boolean;
  size?: number;
  strokeWidth?: number;
  color?: string;
}): ReactElement | null => {
  const radius = size / 2 - strokeWidth / 2;
  const circumference = 2 * Math.PI * radius;
  const circleRef = useRef<SVGCircleElement>(null);
  const animationRef = useRef<number>(null);
  const progressRef = useRef(currentProgress);
  const lastTimeRef = useRef<number>(null);
 
  const updateCircle = () => {
    Iif (!circleRef.current) return;
 
    const remainingTime = startTime - progressRef.current;
    const totalDuration = startTime;
 
    const progress = Math.max(0, Math.min(1, remainingTime / totalDuration));
    const dashOffset = circumference * (1 - progress);
 
    circleRef.current.style.strokeDashoffset = dashOffset.toString();
 
    if (progress < 0.1) {
      circleRef.current.style.opacity = (progress * 10).toString(); // Fade out in last 10%
    } else {
      circleRef.current.style.opacity = "1";
    }
 
    Iif (progress <= 0) {
      return false;
    }
 
    return true;
  };
 
  useEffect(() => {
    progressRef.current = currentProgress;
 
    updateCircle();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentProgress, startTime]);
 
  useEffect(() => {
    const animate = (time: number) => {
      Iif (lastTimeRef.current && isPlaying) {
        const deltaTime = time - lastTimeRef.current;
 
        progressRef.current += deltaTime;
      }
 
      lastTimeRef.current = time;
 
      Iif (updateCircle()) {
        animationRef.current = requestAnimationFrame(animate);
      }
    };
 
    Iif (isPlaying) {
      lastTimeRef.current = performance.now();
      animationRef.current = requestAnimationFrame(animate);
    }
 
    return () => {
      Iif (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isPlaying]);
 
  return (
    <div className="countdown-container" style={{ width: size, height: size }}>
      <svg
        style={{ width: "100%", height: "100%" }}
        viewBox={`0 0 ${size} ${size}`}
      >
        <circle
          ref={circleRef}
          cx={size / 2}
          cy={size / 2}
          r={radius}
          fill="none"
          stroke={color}
          strokeWidth={strokeWidth}
          strokeDasharray={circumference}
          strokeDashoffset="0"
          transform={`rotate(-90 ${size / 2} ${size / 2})`}
          strokeLinecap="round"
        />
      </svg>
    </div>
  );
};
 
export default CountDown;