import { useMemo, type CSSProperties, type ReactNode } from 'react'; export interface ProgressRingProps { /** Progress 0..1 (values outside are clamped). */ value: number; /** Outer diameter in px. Default 96. */ size?: number; /** Ring thickness in px. Default 8. */ thickness?: number; /** Track color. Default `var(--bl-border)`. */ track?: string; /** Active arc color. Default `var(--bl-accent)`. */ color?: string; /** Slot rendered in the center. */ children?: ReactNode; /** Aria label. */ ariaLabel?: string; className?: string; style?: CSSProperties; } /** * `` — circular progress with an inner content slot. * Perfect for onboarding checklists, usage meters, and goal trackers. * * Smoothly animates `stroke-dashoffset` so the ring sweeps when `value` * changes — no JS tween needed. */ export function ProgressRing({ value, size = 96, thickness = 8, track = 'var(--bl-border, rgba(0,0,0,0.08))', color = 'var(--bl-accent, #6366f1)', children, ariaLabel, className, style, }: ProgressRingProps) { const clamped = Math.max(0, Math.min(1, value)); const { radius, circumference, dashOffset } = useMemo(() => { const r = (size - thickness) / 2; const c = 2 * Math.PI * r; return { radius: r, circumference: c, dashOffset: c * (1 - clamped), }; }, [size, thickness, clamped]); return (
{children !== undefined && (
{children}
)}
); }