{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "metric-gauge",
  "type": "registry:component",
  "title": "Metric Gauge",
  "description": "Real-time arc and dial display for monitored percentages and utilization.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/metric-gauge/metric-gauge.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\nimport { Badge } from \"@vllnt/ui\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@vllnt/ui\";\n\nexport type MetricGaugeThreshold = {\n  colorClassName: string;\n  label: string;\n  value: number;\n};\n\nexport type MetricGaugeProps = React.ComponentPropsWithoutRef<\"div\"> & {\n  description?: string;\n  label: string;\n  max: number;\n  min?: number;\n  thresholds?: MetricGaugeThreshold[];\n  unit?: string;\n  value: number;\n};\n\ntype Point = {\n  x: number;\n  y: number;\n};\n\nconst DEFAULT_THRESHOLDS: MetricGaugeThreshold[] = [\n  { colorClassName: \"text-emerald-500\", label: \"Nominal\", value: 60 },\n  { colorClassName: \"text-amber-500\", label: \"Elevated\", value: 85 },\n  { colorClassName: \"text-destructive\", label: \"Critical\", value: 100 },\n];\nconst GAUGE_CENTER: Point = { x: 100, y: 100 };\n\nfunction clamp(value: number, min: number, max: number): number {\n  return Math.min(Math.max(value, min), max);\n}\n\nfunction polarToCartesian(radius: number, angle: number, center: Point): Point {\n  const radians = ((angle - 90) * Math.PI) / 180;\n\n  return {\n    x: center.x + radius * Math.cos(radians),\n    y: center.y + radius * Math.sin(radians),\n  };\n}\n\nfunction describeArc(\n  radius: number,\n  angles: { end: number; start: number },\n  center: Point,\n) {\n  const start = polarToCartesian(radius, angles.end, center);\n  const end = polarToCartesian(radius, angles.start, center);\n  const largeArcFlag = angles.end - angles.start <= 180 ? 0 : 1;\n\n  return `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 0 ${end.x} ${end.y}`;\n}\n\nfunction formatMetricValue(value: number, unit?: string): string {\n  const rounded = Number.isInteger(value) ? value.toString() : value.toFixed(1);\n\n  return unit ? `${rounded}${unit}` : rounded;\n}\n\nfunction getActiveThreshold(\n  percent: number,\n  thresholds: MetricGaugeThreshold[],\n) {\n  return (\n    thresholds.find((threshold) => percent <= threshold.value) ??\n    thresholds.at(-1)\n  );\n}\n\nfunction GaugeDialSvg({\n  activeThreshold,\n  endAngle,\n  label,\n}: {\n  activeThreshold: MetricGaugeThreshold;\n  endAngle: number;\n  label: string;\n}) {\n  const gaugePath = describeArc(72, { end: 90, start: -90 }, GAUGE_CENTER);\n  const activePath = describeArc(\n    72,\n    { end: endAngle, start: -90 },\n    GAUGE_CENTER,\n  );\n  const needlePoint = polarToCartesian(60, endAngle, GAUGE_CENTER);\n\n  return (\n    <svg aria-label={label} className=\"h-auto w-full\" viewBox=\"0 0 200 128\">\n      <path\n        d={gaugePath}\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeOpacity=\"0.12\"\n        strokeWidth=\"14\"\n      />\n      <path\n        className={cn(activeThreshold.colorClassName)}\n        d={activePath}\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"14\"\n      />\n      <line\n        className={cn(activeThreshold.colorClassName)}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"4\"\n        x1={GAUGE_CENTER.x}\n        x2={needlePoint.x}\n        y1={GAUGE_CENTER.y}\n        y2={needlePoint.y}\n      />\n      <circle\n        className=\"fill-background stroke-border\"\n        cx={GAUGE_CENTER.x}\n        cy={GAUGE_CENTER.y}\n        r=\"8\"\n      />\n    </svg>\n  );\n}\n\nfunction GaugeDial({\n  activeThreshold,\n  endAngle,\n  label,\n  max,\n  min,\n  percent,\n  safeValue,\n  unit,\n}: {\n  activeThreshold: MetricGaugeThreshold;\n  endAngle: number;\n  label: string;\n  max: number;\n  min: number;\n  percent: number;\n  safeValue: number;\n  unit?: string;\n}) {\n  return (\n    <>\n      <div className=\"relative mx-auto w-full max-w-[280px]\">\n        <GaugeDialSvg\n          activeThreshold={activeThreshold}\n          endAngle={endAngle}\n          label={label}\n        />\n        <div className=\"absolute inset-x-0 top-12 text-center\">\n          <div className=\"text-3xl font-semibold tracking-tight\">\n            {formatMetricValue(safeValue, unit)}\n          </div>\n          <div className=\"text-xs text-muted-foreground\">\n            Range {formatMetricValue(min, unit)}–{formatMetricValue(max, unit)}\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex items-center justify-between text-xs text-muted-foreground\">\n        <span>{formatMetricValue(min, unit)}</span>\n        <span>{Math.round(percent)}%</span>\n        <span>{formatMetricValue(max, unit)}</span>\n      </div>\n    </>\n  );\n}\n\nfunction GaugeLegend({ thresholds }: { thresholds: MetricGaugeThreshold[] }) {\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {thresholds.map((threshold) => (\n        <div\n          className=\"flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs\"\n          key={`${threshold.label}-${threshold.value}`}\n        >\n          <span\n            aria-hidden=\"true\"\n            className={cn(\n              \"size-2 rounded-full bg-current\",\n              threshold.colorClassName,\n            )}\n          />\n          <span>{threshold.label}</span>\n          <span className=\"text-muted-foreground\">≤ {threshold.value}%</span>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport const MetricGauge = React.forwardRef<HTMLDivElement, MetricGaugeProps>(\n  (\n    {\n      className,\n      description,\n      label,\n      max,\n      min = 0,\n      thresholds = DEFAULT_THRESHOLDS,\n      unit,\n      value,\n      ...props\n    },\n    ref,\n  ) => {\n    const safeValue = clamp(value, min, max);\n    const percent = max === min ? 0 : ((safeValue - min) / (max - min)) * 100;\n    const endAngle = -90 + 180 * (percent / 100);\n    const activeThreshold = getActiveThreshold(percent, thresholds);\n\n    return (\n      <Card className={cn(\"shadow-sm\", className)} ref={ref} {...props}>\n        <CardHeader className=\"space-y-2 pb-3\">\n          <div className=\"flex items-center justify-between gap-3\">\n            <div>\n              <CardTitle className=\"text-base\">{label}</CardTitle>\n              {description ? (\n                <CardDescription>{description}</CardDescription>\n              ) : null}\n            </div>\n            {activeThreshold ? (\n              <Badge variant=\"outline\">{activeThreshold.label}</Badge>\n            ) : null}\n          </div>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          {activeThreshold ? (\n            <GaugeDial\n              activeThreshold={activeThreshold}\n              endAngle={endAngle}\n              label={label}\n              max={max}\n              min={min}\n              percent={percent}\n              safeValue={safeValue}\n              unit={unit}\n            />\n          ) : null}\n          <GaugeLegend thresholds={thresholds} />\n        </CardContent>\n      </Card>\n    );\n  },\n);\n\nMetricGauge.displayName = \"MetricGauge\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
