Glass Gauge

PreviousNext

A gauge component with glass morphism styling, suitable for displaying progress or performance metrics.

Docs
einuicomponent

Preview

Loading preview…
registry/innovative/glass-gauge.tsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

interface GlassGaugeProps extends React.HTMLAttributes<HTMLDivElement> {
  value: number
  max?: number
  size?: "sm" | "md" | "lg"
  showValue?: boolean
  label?: string
  colorScheme?: "cyan" | "purple" | "green" | "orange" | "gradient"
  animated?: boolean
}

const sizes = {
  sm: { size: 100, strokeWidth: 8, fontSize: "text-lg" },
  md: { size: 150, strokeWidth: 10, fontSize: "text-2xl" },
  lg: { size: 200, strokeWidth: 12, fontSize: "text-4xl" },
}

const colorSchemes = {
  cyan: { stroke: "stroke-cyan-500", glow: "from-cyan-500/40 to-blue-500/40" },
  purple: { stroke: "stroke-purple-500", glow: "from-purple-500/40 to-pink-500/40" },
  green: { stroke: "stroke-emerald-500", glow: "from-emerald-500/40 to-teal-500/40" },
  orange: { stroke: "stroke-orange-500", glow: "from-orange-500/40 to-amber-500/40" },
  gradient: { stroke: "", glow: "from-cyan-500/40 via-blue-500/40 to-purple-500/40" },
}

const GlassGauge = React.forwardRef<HTMLDivElement, GlassGaugeProps>(
  (
    {
      className,
      value,
      max = 100,
      size = "md",
      showValue = true,
      label,
      colorScheme = "cyan",
      animated = true,
      ...props
    },
    ref,
  ) => {
    const [displayValue, setDisplayValue] = React.useState(0)
    const config = sizes[size]
    const colors = colorSchemes[colorScheme]
    const percentage = Math.min(Math.max((value / max) * 100, 0), 100)

    const radius = (config.size - config.strokeWidth) / 2
    const circumference = radius * 2 * Math.PI
    const strokeDashoffset = circumference - (percentage / 100) * circumference

    React.useEffect(() => {
      if (!animated) {
        setDisplayValue(value)
        return
      }

      const duration = 1000
      const steps = 60
      const increment = value / steps
      let current = 0
      let step = 0

      const timer = setInterval(() => {
        step++
        current = Math.min(current + increment, value)
        setDisplayValue(Math.round(current))

        if (step >= steps) {
          clearInterval(timer)
          setDisplayValue(value)
        }
      }, duration / steps)

      return () => clearInterval(timer)
    }, [value, animated])

    const gradientId = React.useId()

    return (
      <div ref={ref} className={cn("relative inline-flex flex-col items-center", className)} {...props}>
        {/* Glow effect */}
        <div
          className={cn("absolute rounded-full bg-linear-to-r blur-xl opacity-60", colors.glow)}
          style={{
            width: config.size * 0.8,
            height: config.size * 0.8,
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
          }}
        />

        <div className="relative" style={{ width: config.size, height: config.size }}>
          <svg width={config.size} height={config.size} className="transform -rotate-90">
            {/* Gradient definition */}
            {colorScheme === "gradient" && (
              <defs>
                <linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%">
                  <stop offset="0%" stopColor="#06b6d4" />
                  <stop offset="50%" stopColor="#3b82f6" />
                  <stop offset="100%" stopColor="#a855f7" />
                </linearGradient>
              </defs>
            )}

            {/* Background track */}
            <circle
              cx={config.size / 2}
              cy={config.size / 2}
              r={radius}
              fill="none"
              stroke="currentColor"
              strokeWidth={config.strokeWidth}
              className="text-white/10"
            />

            {/* Glass reflection on track */}
            <circle
              cx={config.size / 2}
              cy={config.size / 2}
              r={radius - config.strokeWidth / 2}
              fill="none"
              stroke="currentColor"
              strokeWidth={1}
              className="text-white/5"
            />

            {/* Progress arc */}
            <circle
              cx={config.size / 2}
              cy={config.size / 2}
              r={radius}
              fill="none"
              stroke={colorScheme === "gradient" ? `url(#${gradientId})` : undefined}
              strokeWidth={config.strokeWidth}
              strokeLinecap="round"
              strokeDasharray={circumference}
              strokeDashoffset={strokeDashoffset}
              className={cn("transition-all duration-1000 ease-out", colorScheme !== "gradient" && colors.stroke)}
            />
          </svg>

          {/* Center content */}
          <div className="absolute inset-0 flex flex-col items-center justify-center">
            {showValue && (
              <span className={cn("font-bold text-white", config.fontSize)}>
                {displayValue}
                <span className="text-white/40 text-sm">%</span>
              </span>
            )}
            {label && <span className="text-white/60 text-sm mt-1">{label}</span>}
          </div>
        </div>
      </div>
    )
  },
)
GlassGauge.displayName = "GlassGauge"

export { GlassGauge }

Installation

npx shadcn@latest add @einui/glass-gauge

Usage

import { GlassGauge } from "@/components/glass-gauge"
<GlassGauge />