Description
The ProgressCircle
displays the completion percentage in circular form; similar to a progress bar, but as an SVG circle.
It accepts a progress
prop that should be a number between 0 and 100.
Upon 100% completion the background (circle’s fill
) color will change to the progressColor
.
🏎️ Example
Default
With Center SVG Icon
You can pass a SVG Icon as a child to the ProgressCircle
component. The icon will be centered in the circle.
The getCenterIconHeightWidth
helper fn, will help calculate the height and width of the icon and properly center it in the circle.
With Progress Completed Fill
When the progress circle is 100% completed then the progress circle’s background is filled with the progress color.
🦾 Usage
import { useState } from "react";
import { ProgressCircle, getCenterIconHeightWidth } from "./ProgressCircle";
const [progress, setProgress] = useState(50);
const iconHeightWidth = getCenterIconHeightWidth(180);
const App = () => (
<ProgressCircle progress={progress} circleHeightWidth={180} strokeWidth={10}>
<svg
xmlns="http://www.w3.org/2000/svg"
height={iconHeightWidth}
width={iconHeightWidth}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
className="lucide lucide-bone"
>
<path d="M17 10c.7-.7 1.69 0 2.5 0a2.5 2.5 0 1 0 0-5 .5.5 0 0 1-.5-.5 2.5 2.5 0 1 0-5 0c0 .81.7 1.8 0 2.5l-7 7c-.7.7-1.69 0-2.5 0a2.5 2.5 0 0 0 0 5c.28 0 .5.22.5.5a2.5 2.5 0 1 0 5 0c0-.81-.7-1.8 0-2.5Z" />
</svg>
</ProgressCircle>
);
🍎 Code Byte
ProgressCircle.tsx
import React from "react";
import { motion } from "framer-motion";
export interface ProgressCircleProps {
progress: number;
circleHeightWidth: number;
progressColor?: string;
gaugeColor?: string;
strokeWidth?: number;
children?: React.ReactNode;
}
// When you pass a svg icon as child to the ProgressCircle component, this will calculate the height and width of that svg.
export const getCenterIconHeightWidth = (circleWidthHeight: number) =>
(circleWidthHeight / 2 - 2) * 0.8;
const transition = {
type: "tween",
duration: 1,
ease: "easeInOut",
};
export const ProgressCircle = ({
progress,
circleHeightWidth = 80,
progressColor = "#16a34a",
gaugeColor = "#E0E0DE",
strokeWidth = 3,
children,
}: ProgressCircleProps) => {
let circleCXY: number;
const parentRadius = (circleCXY = circleHeightWidth / 2 - 2);
const circumference = 2 * Math.PI * parentRadius;
const strokeDashoffset = circumference - (progress / 100) * circumference;
// multiply by 1.1 to give the svg 10% padding from its container
const diameter = 2 * parentRadius * 1.1;
let minX: number;
// push from the top and push from the left of the svg container
const minY = (minX = parentRadius * 0.1 * -1);
const pathLength = progress >= 100 ? (progress + 1) / 100 : progress / 100;
return (
<svg
width={circleHeightWidth}
height={circleHeightWidth}
viewBox={`${minX} ${minY} ${diameter} ${diameter}`}
>
{/* Animate the fill (background) color when progress reaches 100% by changing the opacity */}
<motion.circle
cx={circleCXY}
cy={circleCXY}
r={parentRadius}
stroke="none"
strokeWidth={1}
fill={progressColor}
initial={{ opacity: 0 }}
animate={{ opacity: progress >= 100 ? 1 : 0 }}
exit={{ opacity: 0 }}
transition={transition}
/>
{/* Once the progress reaches 100%, we change the stroke of the gauge ring to the progress color;
otherwise, we get the slightest sliver of the gauge color between the progress ring
and the inner background color fill. */}
<circle
cx={circleCXY}
cy={circleCXY}
r={parentRadius}
stroke={progress >= 100 ? progressColor : gaugeColor}
strokeWidth={strokeWidth}
fill="none"
/>
<motion.circle
cx={circleCXY}
cy={circleCXY}
r={parentRadius}
stroke={progressColor}
strokeWidth={strokeWidth}
fill="none"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
transform={`rotate(-90,${parentRadius},${parentRadius})`}
initial={{ pathLength: 0 }}
animate={{ pathLength: pathLength }}
transition={transition}
/>
<svg
x={0.6 * parentRadius}
y={0.6 * parentRadius}
width={0.8 * parentRadius}
height={0.8 * parentRadius}
aria-hidden={true}
>
{children}
</svg>
</svg>
);
};
🪑 Props
Prop | Type | Description |
---|---|---|
progress | number | The percentage of completion represented by the progress circle, ranging from 0 - 100. |
circleHeightWidth | number | The height and width of progress circle. |
progressColor | string | The color applied to the progress portion of the circle. |
gaugeColor | string | The “background” color of the non-progress portion of the circle. |
strokeWidth | number | The thickness of the circle stroke. |