Circular progress indicator.
<div class="ring" role="progressbar" aria-label="Storage used" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68" style="--pct:68;--c:#BFD9F2;">
<svg width="88" height="88" viewBox="0 0 88 88" aria-hidden="true">
<circle cx="44" cy="44" r="38" fill="none" stroke="#24143018" stroke-width="8"></circle>
<circle class="ring__arc" cx="44" cy="44" r="38" fill="none" stroke="#BFD9F2" stroke-width="8" stroke-linecap="round"></circle>
</svg>
<div class="ring__center">
<span class="ring__value">68%</span>
<span class="ring__label">Storage used</span>
</div>
</div>
<style>
.ring { position: relative; width: 88px; height: 88px; font: 11px system-ui, sans-serif; color: #241430; }
.ring__arc { stroke-dasharray: 238.76; stroke-dashoffset: calc(238.76 - (238.76 * var(--pct) / 100)); transform: rotate(-90deg); transform-origin: 50% 50%; }
.ring--indeterminate .ring__arc { stroke-dasharray: 60 178.76; }
@keyframes ring-spin { to { transform: rotate(270deg); } }
@media (prefers-reduced-motion: no-preference) {
.ring__arc { transition: stroke-dashoffset .2s ease; }
.ring--indeterminate .ring__arc { animation: ring-spin .2s linear infinite; }
}
.ring__center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 2px; }
.ring__value { font-weight: 700; font-size: 15px; }
.ring__label { opacity: .7; max-width: 70px; }
</style><div role="progressbar" aria-label="Storage used" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68" class="ring-tw relative w-[88px] h-[88px] font-sans text-[11px] text-[#241430]" style="--pct:68;--c:#BFD9F2;">
<svg width="88" height="88" viewBox="0 0 88 88" aria-hidden="true">
<circle cx="44" cy="44" r="38" fill="none" stroke="#24143018" stroke-width="8"></circle>
<circle class="ring-tw__arc" cx="44" cy="44" r="38" fill="none" stroke="#BFD9F2" stroke-width="8" stroke-linecap="round"></circle>
</svg>
<div class="absolute inset-0 flex flex-col items-center justify-center text-center gap-0.5">
<span class="font-bold text-[15px]">68%</span>
<span class="opacity-70 max-w-[70px]">Storage used</span>
</div>
</div>
<style>
.ring-tw__arc { stroke-dasharray: 238.76; stroke-dashoffset: calc(238.76 - (238.76 * var(--pct) / 100)); transform: rotate(-90deg); transform-origin: 50% 50%; }
.ring-tw--indeterminate .ring-tw__arc { stroke-dasharray: 60 178.76; }
@keyframes ring-tw-spin { to { transform: rotate(270deg); } }
@media (prefers-reduced-motion: no-preference) {
.ring-tw__arc { transition: stroke-dashoffset .2s ease; }
.ring-tw--indeterminate .ring-tw__arc { animation: ring-tw-spin .2s linear infinite; }
}
</style>export function ProgressRing({
label = "Storage used",
value = 68,
color = "#BFD9F2",
indeterminate = false,
}) {
const circumference = 238.76;
const offset = circumference - (circumference * value) / 100;
return (
<div role="progressbar" aria-label={label} aria-valuemin={0} aria-valuemax={100}
aria-valuenow={indeterminate ? undefined : value}
style={{ position: "relative", width: 88, height: 88, font: "11px system-ui, sans-serif", color: "#241430" }}>
<svg width="88" height="88" viewBox="0 0 88 88" aria-hidden="true">
<circle cx="44" cy="44" r="38" fill="none" stroke="#24143018" strokeWidth="8" />
<circle cx="44" cy="44" r="38" fill="none" stroke={color} strokeWidth="8" strokeLinecap="round"
className={indeterminate ? "pr-circle pr-circle--indeterminate" : "pr-circle"}
style={{ strokeDasharray: indeterminate ? "60 178.76" : circumference,
strokeDashoffset: indeterminate ? 0 : offset, transform: "rotate(-90deg)", transformOrigin: "50% 50%" }} />
</svg>
<div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center",
justifyContent: "center", textAlign: "center", gap: 2 }}>
{!indeterminate && <span style={{ fontWeight: 700, fontSize: 15 }}>{value}%</span>}
<span style={{ opacity: 0.7, maxWidth: 70 }}>{label}</span>
</div>
<style>{`
@media (prefers-reduced-motion: no-preference) {
.pr-circle { transition: stroke-dashoffset .2s ease; }
.pr-circle--indeterminate { animation: ring-react-spin 1s linear infinite; }
@keyframes ring-react-spin { to { transform: rotate(270deg); } }
}
`}</style>
</div>
);
}