Skip to content

Instantly share code, notes, and snippets.

@lumotroph
Forked from maxotuteye/RadialProgressBar.tsx
Last active February 5, 2025 13:19
Show Gist options
  • Save lumotroph/8de1d6e05c4c1dd74c9dacf3731ce1a4 to your computer and use it in GitHub Desktop.
Save lumotroph/8de1d6e05c4c1dd74c9dacf3731ce1a4 to your computer and use it in GitHub Desktop.
Tailwind CSS Radial Progress Bar
/* FORKED FROM maxotuteye on github https://gist.github.com/maxotuteye/86dc828106847c471a0abbb11a631550
* IN TURN created from a non-react version by eYinka at https://gist.github.com/eYinka/873be69fae3ef27b103681b8a9f5e379
*/
export const RadialProgressBar = ({
size = 40,
progress = 67,
ringThickness = 10,
descriptionText = "Progress",
descriptionTextClass = "text-sm",
descriptionValue = progress.toFixed(0) + "%",
descriptionValueClass = "text-lg font-bold",
colorClassName = "text-primary-500",
}: {
size: number;
progress: number;
ringThickness: number;
descriptionText?: string;
descriptionTextClass?: string;
descriptionValue?: string;
descriptionValueClass?: string;
colorClassName?: string;
}) => {
const width = `w-${size}`;
const height = `h-${size}`;
const radius = Number((size * 0.94).toFixed(0));
const circumference = Number((2 * Math.PI * radius).toFixed(1));
const strokeDashOffset = circumference - (circumference * progress) / 100;
return (
<div className={`relative ${width} ${height}`}>
<svg className="size-full" viewBox="0 0 100 100">
<circle
className="stroke-current text-gray-200"
strokeWidth={ringThickness}
cx="50"
cy="50"
r={radius}
fill="transparent"
/>
<circle
className={`stroke-current ${colorClassName}`}
strokeWidth={ringThickness}
strokeLinecap="round"
cx="50"
cy="50"
r={radius}
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={`${strokeDashOffset}px`}
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center -space-y-1">
<p className={`${descriptionTextClass} ${colorClassName}`}>{descriptionText}</p>
<h5 className={`${descriptionValueClass} ${colorClassName}`}>
{descriptionValue}
</h5>
</div>
</div>
);
};
import React from "react";
import { RadialProgressBar } from "@/components/ui/radial-progress-bar"; // ?? or whatever
const UsageExample = () => {
return (
<>
<RadialProgressBar
size={40}
ringThickness={10}
colorClassName="text-dark-600"
descriptionValueClass="text-sm font-bold"
progress="50"
descriptionText="km left"
descriptionValue="20 / 40"
/>
</>
);
};
export default UsageExample;
@lumotroph
Copy link
Author

Thank you for pointing that out @firxworx !

I've actually made some improvements since posting this, so here are the updates.

It looks like this new code (perhaps incorrectly) sets width and height directly rather than using those variables - I guess I must have run into the exact problem you were talking about!

/* FORKED FROM maxotuteye on github https://gist.github.com/maxotuteye/86dc828106847c471a0abbb11a631550
 * IN TURN created from a non-react version by eYinka at https://gist.github.com/eYinka/873be69fae3ef27b103681b8a9f5e379
 *   - see https://gist.github.com/lumotroph/8de1d6e05c4c1dd74c9dacf3731ce1a4 for open source audit trail 
*/


export const RadialProgressBar = ({
  size = 40,
  progress = 67,
  ringThickness = 10,
  descriptionText = "Progress",
  descriptionTextClass = "text-sm",
  descriptionValue = progress.toFixed(0) + "%",
  descriptionValueClass = "text-lg font-bold",
  colorClassName = "text-primary-500",
}: {
  size: number;
  progress: number;
  ringThickness: number;
  descriptionText?: string;
  descriptionTextClass?: string;
  descriptionValue?: string;
  descriptionValueClass?: string;
  colorClassName?: string;
}) => {
  const radius = Number((size * 0.25).toFixed(0));
  const circumference = Number((2 * Math.PI * radius).toFixed(1));
  const strokeDashOffset = circumference - (circumference * progress) / 100;
  // Calculate the dash offset for 90% fill
  const backgroundStrokeDashOffset = circumference - (circumference * 94) / 100;

  return (
    <div
      className={`relative`}
      style={{ width: `${size}px`, height: `${size}px` }}
    >
      <svg
        className="size-full"
        viewBox="0 0 100 100"
        style={{ width: `${size}px`, height: `${size}px` }}
      >
        {/* Background circle - now 90% filled */}
        <circle
          className="stroke-current text-gray-200"
          strokeWidth={ringThickness}
          strokeDasharray={circumference}
          strokeDashoffset={`${backgroundStrokeDashOffset}px`}
          strokeLinecap="round"
          cx="50"
          cy="50"
          r={radius}
          fill="transparent"
          transform="rotate(180 50 50)"
        />
        {/* Percentage fill */}
        <circle
          className={`stroke-current ${colorClassName}`}
          strokeWidth={ringThickness}
          strokeLinecap="round"
          cx="50"
          cy="50"
          r={radius}
          fill="transparent"
          strokeDasharray={circumference}
          strokeDashoffset={`${strokeDashOffset}px`}
          transform="rotate(180 50 50)" // Changed rotation
        />
      </svg>
      <div className="absolute inset-0 flex flex-col items-center justify-center -space-y-1">
        <p className={`${descriptionTextClass} ${colorClassName}`}>
          {descriptionText}
        </p>
        <h5 className={`${descriptionValueClass} ${colorClassName}`}>
          {descriptionValue}
        </h5>
      </div>
    </div>
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment