oc tung

Circle Progress

Component 29

About

CircleProgress displays progress, completion status, or usage metrics in a circular format. It provides visual feedback about processes, tasks completion, or resource utilization. ## When to use - To show completion percentage of a task or process - To display resource usage (disk space, memory, CPU) - To create countdown or elapsed time indicators - For data visualization that needs to show parts of a whole ## Accessibility - Progress indicator includes ARIA attributes for screen readers - Value text provides detailed information about current progress value - Ensure sufficient color contrast for the progress indicator - Consider providing text alternatives that describe the progress value

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Circle Progress to your project and fits it to your theme.

Code

How Circle Progress is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import * as React from "react";
import { CircleProgress } from "@/components/ui/circle-progress";
import { Button } from "@/components/ui/button";

export function CountdownTimer() {
  const [totalSeconds] = React.useState(60);
  const [secondsLeft, setSecondsLeft] = React.useState(totalSeconds);
  const [isRunning, setIsRunning] = React.useState(false);
  const intervalRef = React.useRef<NodeJS.Timeout | null>(null);

  const formatTime = (seconds: number) => {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins}:${secs.toString().padStart(2, '0')}`;
  };

  const startTimer = React.useCallback(() => {
    if (secondsLeft <= 0) {
      setSecondsLeft(totalSeconds);
    }
    setIsRunning(true);
  }, [secondsLeft, totalSeconds]);

  const pauseTimer = () => {
    setIsRunning(false);
  };

  const resetTimer = () => {
    setIsRunning(false);
    setSecondsLeft(totalSeconds);
  };

  React.useEffect(() => {
    if (isRunning) {
      intervalRef.current = setInterval(() => {
        setSecondsLeft((prev) => {
          if (prev <= 1) {
            setIsRunning(false);
            clearInterval(intervalRef.current as NodeJS.Timeout);
            return 0;
          }
          return prev - 1;
        });
      }, 1000);
    }

    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, [isRunning]);

  return (
    <div className="flex flex-col items-center gap-6">
      <div className="relative">
        <CircleProgress
          value={secondsLeft}
          maxValue={totalSeconds}
          size={160}
          strokeWidth={8}
          counterClockwise={true}
          getColor={(percentage) => {
            if (percentage > 0.66) return "stroke-emerald-500";
            if (percentage > 0.33) return "stroke-amber-500";
            return "stroke-rose-500";
          }}
        />
        <div className="absolute inset-0 flex items-center justify-center">
          <div className="text-center">
            <div className="text-3xl font-bold">{formatTime(secondsLeft)}</div>
            <div className="text-sm text-gray-500">
              {isRunning ? "Counting down..." : secondsLeft === 0 ? "Time's up!" : "Ready"}
            </div>
          </div>
        </div>
      </div>

      <div className="flex gap-3">
        {!isRunning ? (
          <Button onClick={startTimer} disabled={isRunning}>
            {secondsLeft === 0 ? "Restart" : secondsLeft < totalSeconds ? "Resume" : "Start"}
          </Button>
        ) : (
          <Button variant="outline" onClick={pauseTimer}>
            Pause
          </Button>
        )}
        <Button variant="outline" onClick={resetTimer}>
          Reset
        </Button>
      </div>
    </div>
  );
}

More by oc tung

View all
  • useItemOverflow

    useItemOverflow

    68