dev_arc_aws/src/components/ProgressRing.tsx

102 lines
3.1 KiB
TypeScript
Raw Normal View History

2026-06-18 08:14:00 -04:00
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>
)
}
interface RingSegment {
color: string
value: number
}
interface MultiSegmentRingProps {
segments: RingSegment[]
total: number
size?: number
strokeWidth?: number
centerLabel?: string
}
export function MultiSegmentRing({ segments, total, size = 64, strokeWidth = 10, centerLabel }: MultiSegmentRingProps) {
const radius = (size - strokeWidth) / 2
const circumference = 2 * Math.PI * radius
let cumulative = 0
const drawn = segments.filter((s) => s.value > 0)
return (
<div className="relative flex items-center justify-center" style={{ width: size, height: size }}>
<svg width={size} height={size} className="-rotate-90">
<circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="#1E2025" strokeWidth={strokeWidth} />
{total > 0 &&
drawn.map((seg, i) => {
const segLen = (seg.value / total) * circumference
const offset = circumference - cumulative
cumulative += segLen
return (
<circle
key={i}
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={seg.color}
strokeWidth={strokeWidth}
strokeDasharray={`${segLen} ${circumference - segLen}`}
strokeDashoffset={offset}
className="transition-all duration-1000 ease-out"
/>
)
})}
</svg>
{centerLabel && <span className="absolute text-sm font-bold text-text-primary">{centerLabel}</span>}
</div>
)
}