first
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* Donut-style progress ring.
|
||||
*
|
||||
* Used in the Overview dashboard for things like implementation coverage,
|
||||
* test coverage, project health score, traceability score.
|
||||
*/
|
||||
export function ProgressRing({
|
||||
value,
|
||||
size = 80,
|
||||
stroke = 8,
|
||||
tone = "brand",
|
||||
label,
|
||||
sublabel,
|
||||
className,
|
||||
}: {
|
||||
value: number; // 0..100
|
||||
size?: number;
|
||||
stroke?: number;
|
||||
tone?: "brand" | "green" | "amber" | "red" | "violet";
|
||||
label?: string;
|
||||
sublabel?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const r = (size - stroke) / 2;
|
||||
const c = 2 * Math.PI * r;
|
||||
const clamped = Math.max(0, Math.min(100, Number(value) || 0));
|
||||
const offset = c - (clamped / 100) * c;
|
||||
|
||||
const toneMap: Record<string, string> = {
|
||||
brand: "text-brand-600",
|
||||
green: "text-emerald-600",
|
||||
amber: "text-amber-600",
|
||||
red: "text-red-600",
|
||||
violet: "text-violet-600",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center gap-4", className)}>
|
||||
<div className="relative shrink-0" style={{ width: size, height: size }}>
|
||||
<svg width={size} height={size} className={cn("-rotate-90", toneMap[tone])}>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={r}
|
||||
stroke="currentColor"
|
||||
strokeOpacity={0.12}
|
||||
fill="transparent"
|
||||
strokeWidth={stroke}
|
||||
/>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={r}
|
||||
stroke="currentColor"
|
||||
fill="transparent"
|
||||
strokeWidth={stroke}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={c}
|
||||
strokeDashoffset={offset}
|
||||
className="transition-all duration-500 ease-out"
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 grid place-items-center">
|
||||
<div className="text-sm font-semibold tabular-nums text-slate-900">
|
||||
{clamped.toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(label || sublabel) && (
|
||||
<div className="min-w-0">
|
||||
{label && <div className="text-sm font-medium text-slate-800">{label}</div>}
|
||||
{sublabel && <div className="text-xs text-slate-500 mt-0.5">{sublabel}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user