import { useEffect, useState } from 'react' import classNames from '~/lib/classNames' interface CircularGaugeProps { value: number // percentage label: string icon?: React.ReactNode size?: 'sm' | 'md' | 'lg' variant?: 'cpu' | 'memory' | 'disk' | 'default' subtext?: string animated?: boolean } export default function CircularGauge({ value, label, icon, size = 'md', variant = 'default', subtext, animated = true, }: CircularGaugeProps) { const [animatedValue, setAnimatedValue] = useState(animated ? 0 : value) useEffect(() => { if (animated) { const timeout = setTimeout(() => setAnimatedValue(value), 100) return () => clearTimeout(timeout) } }, [value, animated]) const displayValue = animated ? animatedValue : value // Size configs: container size must match SVG size (2 * (radius + strokeWidth)) const sizes = { sm: { container: 'w-28 h-28', // 112px = 2 * (48 + 8) strokeWidth: 8, radius: 48, fontSize: 'text-xl', labelSize: 'text-xs', }, md: { container: 'w-[140px] h-[140px]', // 140px = 2 * (60 + 10) strokeWidth: 10, radius: 60, fontSize: 'text-2xl', labelSize: 'text-sm', }, lg: { container: 'w-[244px] h-[244px]', // 244px = 2 * (110 + 12) strokeWidth: 12, radius: 110, fontSize: 'text-4xl', labelSize: 'text-base', }, } const config = sizes[size] const circumference = 2 * Math.PI * config.radius const offset = circumference - (displayValue / 100) * circumference const getColor = () => { // For benchmarks: higher scores = better = green if (value >= 75) return 'desert-green' if (value >= 50) return 'desert-olive' if (value >= 25) return 'desert-orange' return 'desert-red' } const color = getColor() const center = config.radius + config.strokeWidth return (
{/* Background circle */} {/* Progress circle */} {/* Tick marks */} {Array.from({ length: 12 }).map((_, i) => { const angle = (i * 30 * Math.PI) / 180 const ringGap = 8 const tickLength = 6 const innerRadius = config.radius - config.strokeWidth - ringGap const outerRadius = config.radius - config.strokeWidth - ringGap - tickLength const x1 = center + innerRadius * Math.cos(angle) const y1 = center + innerRadius * Math.sin(angle) const x2 = center + outerRadius * Math.cos(angle) const y2 = center + outerRadius * Math.sin(angle) return ( ) })}
{icon &&
{icon}
}
{Math.round(displayValue)}%
{subtext && (
{subtext}
)}
{label}
) }