Files
ok_r/components/progress-ring.tsx
2026-01-25 21:55:29 +08:00

70 lines
1.8 KiB
TypeScript

'use client';
import { cn } from '@/lib/utils';
import type { ProjectStatus } from '@/types/database';
interface ProgressRingProps {
progress: number;
status: ProjectStatus;
size?: number;
strokeWidth?: number;
showPercentage?: boolean;
className?: string;
}
export function ProgressRing({
progress,
status,
size = 64,
strokeWidth = 4,
showPercentage = true,
className,
}: ProgressRingProps) {
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const normalizedProgress = Math.min(Math.max(progress, 0), 1);
const strokeDashoffset = circumference - normalizedProgress * circumference;
const strokeColor =
status === 'green' ? 'stroke-emerald-500' :
status === 'yellow' ? 'stroke-amber-500' :
'stroke-rose-500';
const textColor =
status === 'green' ? 'text-emerald-600' :
status === 'yellow' ? 'text-amber-600' :
'text-rose-600';
return (
<div className={cn('relative inline-flex items-center justify-center', className)}>
<svg width={size} height={size} className="-rotate-90">
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="currentColor"
strokeWidth={strokeWidth}
className="text-slate-200"
/>
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className={cn('transition-all duration-500', strokeColor)}
/>
</svg>
{showPercentage && (
<span className={cn('absolute text-sm font-semibold', textColor)}>
{Math.round(normalizedProgress * 100)}%
</span>
)}
</div>
);
}