modelforge-backend / apps /web /components /ui /grade-ring.tsx
ModelForge CI
deploy: 2026-06-19 19:24 UTC
6761f70
Raw
History Blame Contribute Delete
1.92 kB
import { cn } from "@/lib/utils"
type Grade = "A" | "B" | "C" | "D" | "F"
const gradeConfig: Record<Grade, { color: string; pct: number }> = {
A: { color: "var(--success)", pct: 0.95 },
B: { color: "var(--primary)", pct: 0.82 },
C: { color: "var(--pending)", pct: 0.68 },
D: { color: "#F59E0B", pct: 0.50 },
F: { color: "var(--error)", pct: 0.30 },
}
interface GradeRingProps {
grade: Grade
size?: number
className?: string
}
export function GradeRing({ grade, size = 80, className }: GradeRingProps) {
const { color, pct } = gradeConfig[grade] ?? gradeConfig.C
const r = (size - 10) / 2
const cx = size / 2
const circumference = 2 * Math.PI * r
const offset = circumference * (1 - pct)
return (
<div
className={cn("relative shrink-0", className)}
style={{ width: size, height: size }}
>
<svg
width={size}
height={size}
style={{ transform: "rotate(-90deg)" }}
aria-label={`Grade ${grade}`}
>
{/* Track */}
<circle
cx={cx} cy={cx} r={r}
fill="none"
stroke="var(--border-c)"
strokeWidth={6}
/>
{/* Fill arc */}
<circle
cx={cx} cy={cx} r={r}
fill="none"
stroke={color}
strokeWidth={6}
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={offset}
style={{
filter: `drop-shadow(0 0 6px ${color})`,
transition: "stroke-dashoffset 1s cubic-bezier(0.16, 1, 0.3, 1)",
}}
/>
</svg>
{/* Grade letter centered */}
<div
className="absolute inset-0 flex items-center justify-center font-bold"
style={{
fontSize: size * 0.38,
color,
lineHeight: 1,
}}
>
{grade}
</div>
</div>
)
}