69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
'use client';
|
|
|
|
interface CountdownRingProps {
|
|
progress: number; // 0-1, where 1 = full, 0 = empty
|
|
size?: number;
|
|
strokeWidth?: number;
|
|
color?: string;
|
|
trackColor?: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export function CountdownRing({
|
|
progress,
|
|
size = 200,
|
|
strokeWidth = 8,
|
|
color,
|
|
trackColor = 'var(--cm-surface-muted)',
|
|
children,
|
|
}: CountdownRingProps) {
|
|
const radius = (size - strokeWidth) / 2;
|
|
const circumference = 2 * Math.PI * radius;
|
|
const offset = circumference * (1 - Math.min(1, Math.max(0, progress)));
|
|
|
|
// Color transitions: green → yellow → orange → red
|
|
const dynamicColor = color ?? getProgressColor(progress);
|
|
|
|
return (
|
|
<div className="relative inline-flex items-center justify-center" style={{ width: size, height: size }}>
|
|
<svg width={size} height={size} className="transform -rotate-90">
|
|
{/* Track */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke={trackColor}
|
|
strokeWidth={strokeWidth}
|
|
/>
|
|
{/* Progress */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke={dynamicColor}
|
|
strokeWidth={strokeWidth}
|
|
strokeLinecap="round"
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={offset}
|
|
className="transition-all duration-300"
|
|
/>
|
|
</svg>
|
|
{/* Center content */}
|
|
{children && (
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
{children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function getProgressColor(progress: number): string {
|
|
if (progress > 0.6) return 'var(--cm-gentle)'; // green
|
|
if (progress > 0.3) return 'var(--cm-standard)'; // yellow
|
|
if (progress > 0.1) return 'var(--cm-important)'; // orange
|
|
return 'var(--cm-critical)'; // red
|
|
}
|