95 lines
2.6 KiB
TypeScript
95 lines
2.6 KiB
TypeScript
import { cn } from "@/presentation/utils";
|
|
import type { FC, HTMLAttributes } from "react";
|
|
|
|
type Size = "xs" | "sm" | "md" | "lg" | "xl";
|
|
|
|
const sizes: Record<Size, { radius: number; strokeWidth: number }> = {
|
|
xs: { radius: 15, strokeWidth: 3 },
|
|
sm: { radius: 19, strokeWidth: 4 },
|
|
md: { radius: 32, strokeWidth: 6 },
|
|
lg: { radius: 52, strokeWidth: 8 },
|
|
xl: { radius: 80, strokeWidth: 10 },
|
|
};
|
|
|
|
interface ProgressCircleProps extends HTMLAttributes<HTMLDivElement> {
|
|
value?: number;
|
|
size?: Size;
|
|
radius?: number;
|
|
strokeWidth?: number;
|
|
showAnimation?: boolean;
|
|
}
|
|
|
|
const ProgressCircle: FC<ProgressCircleProps> = ({
|
|
className,
|
|
children,
|
|
value = 0,
|
|
size = "md",
|
|
radius,
|
|
strokeWidth,
|
|
showAnimation = true,
|
|
...props
|
|
}) => {
|
|
const currentValue = getLimitedValue(value);
|
|
const currentRadius = radius ?? sizes[size].radius;
|
|
const currentStrokeWidth = strokeWidth ?? sizes[size].strokeWidth;
|
|
const normalizedRadius = currentRadius - currentStrokeWidth / 2;
|
|
const circumference = normalizedRadius * 2 * Math.PI;
|
|
const strokeDashoffset = (currentValue / 100) * circumference;
|
|
const offset = circumference - strokeDashoffset;
|
|
|
|
return (
|
|
<div
|
|
className={cn("flex flex-col items-center justify-center", className)}
|
|
{...props}
|
|
>
|
|
<svg
|
|
width={currentRadius * 2}
|
|
height={currentRadius * 2}
|
|
viewBox={`0 0 ${currentRadius * 2} ${currentRadius * 2}`}
|
|
className="-rotate-90"
|
|
>
|
|
<circle
|
|
r={normalizedRadius}
|
|
cx={currentRadius}
|
|
cy={currentRadius}
|
|
strokeWidth={currentStrokeWidth}
|
|
fill="transparent"
|
|
stroke=""
|
|
strokeLinecap="round"
|
|
className={cn("stroke-secondary transition-colors ease-linear")}
|
|
/>
|
|
{currentValue >= 0 && (
|
|
<circle
|
|
r={normalizedRadius}
|
|
cx={currentRadius}
|
|
cy={currentRadius}
|
|
strokeWidth={currentStrokeWidth}
|
|
strokeDasharray={`${circumference} ${circumference}`}
|
|
strokeDashoffset={offset}
|
|
fill="transparent"
|
|
stroke=""
|
|
strokeLinecap="round"
|
|
className={cn(
|
|
"stroke-primary transition-colors ease-linear",
|
|
showAnimation ? "transition-all duration-300 ease-in-out" : ""
|
|
)}
|
|
/>
|
|
)}
|
|
</svg>
|
|
<div className={cn("absolute flex")}>{children}</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
function getLimitedValue(input: number | undefined): number {
|
|
if (input === undefined) {
|
|
return 0;
|
|
}
|
|
if (input > 100) {
|
|
return 100;
|
|
}
|
|
return input;
|
|
}
|
|
|
|
export { ProgressCircle };
|