52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
|
|
import { useEffect, useState } from 'react'
|
||
|
|
|
||
|
|
interface ProgressRingProps {
|
||
|
|
percentage: number
|
||
|
|
size?: number
|
||
|
|
strokeWidth?: number
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function ProgressRing({ percentage, size = 56, strokeWidth = 4 }: ProgressRingProps) {
|
||
|
|
const [animatedPercentage, setAnimatedPercentage] = useState(0)
|
||
|
|
const radius = (size - strokeWidth) / 2
|
||
|
|
const circumference = 2 * Math.PI * radius
|
||
|
|
const offset = circumference - (animatedPercentage / 100) * circumference
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const timer = setTimeout(() => setAnimatedPercentage(percentage), 100)
|
||
|
|
return () => clearTimeout(timer)
|
||
|
|
}, [percentage])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="relative flex items-center justify-center" style={{ width: size, height: size }}>
|
||
|
|
<svg width={size} height={size} className="-rotate-90">
|
||
|
|
{/* Background circle */}
|
||
|
|
<circle
|
||
|
|
cx={size / 2}
|
||
|
|
cy={size / 2}
|
||
|
|
r={radius}
|
||
|
|
fill="none"
|
||
|
|
stroke="#1E2025"
|
||
|
|
strokeWidth={strokeWidth}
|
||
|
|
/>
|
||
|
|
{/* Progress circle */}
|
||
|
|
<circle
|
||
|
|
cx={size / 2}
|
||
|
|
cy={size / 2}
|
||
|
|
r={radius}
|
||
|
|
fill="none"
|
||
|
|
stroke="#C8A434"
|
||
|
|
strokeWidth={strokeWidth}
|
||
|
|
strokeDasharray={circumference}
|
||
|
|
strokeDashoffset={offset}
|
||
|
|
strokeLinecap="round"
|
||
|
|
className="transition-all duration-1000 ease-out"
|
||
|
|
/>
|
||
|
|
</svg>
|
||
|
|
<span className="absolute text-xs font-bold text-text-primary">
|
||
|
|
{percentage}%
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|