circular-progress

PreviousNext
Docs
wigggle-uiui

Preview

Loading preview…
registry/default/ui/circular-progress.tsx
"use client";

import React from "react";

type Props = {
  percentageComplete: number;
  taskType: string;
  ringColor?: string;
};

function CircularProgress({
  percentageComplete,
  taskType,
  ringColor = "stroke-blue-600",
  ...props
}: React.ComponentProps<"div"> & Props) {
  const radius = 16;
  const circumference = 2 * Math.PI * radius;

  // Start animation from 0
  const [offset, setOffset] = React.useState(circumference);

  React.useEffect(() => {
    const finalOffset =
      circumference - (percentageComplete / 100) * circumference;

    // small delay so the transition triggers
    requestAnimationFrame(() => setOffset(finalOffset));
  }, [percentageComplete]);

  return (
    <div className="flex size-full flex-col items-center justify-center gap-1">
      <div className="relative">
        <svg className="size-full scale-y-[-1] -rotate-90" viewBox="0 0 36 36">
          {/* Background */}
          <circle
            cx="18"
            cy="18"
            r={radius}
            fill="none"
            strokeWidth="2"
            className="stroke-secondary"
          />

          {/* Animated Progress */}
          <circle
            cx="18"
            cy="18"
            r={radius}
            fill="none"
            strokeWidth="2"
            strokeDasharray={circumference}
            strokeDashoffset={offset}
            strokeLinecap="round"
            className={`${ringColor} transition-[stroke-dashoffset] duration-700 ease-out`}
          />
        </svg>

        {/* Center Text */}
        <div className="absolute inset-0 flex flex-col items-center justify-center text-center">
          <p className="text-sm font-semibold">{percentageComplete}%</p>
        </div>
      </div>
      <p className="text-xs">{taskType}</p>
    </div>
  );
}

export { CircularProgress };

Installation

npx shadcn@latest add @wigggle-ui/circular-progress

Usage

import { CircularProgress } from "@/components/ui/circular-progress"
<CircularProgress />