{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/gauge",
  "title": "Gauge",
  "description": "Arc/speedometer gauge meter",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/terminal-symbols.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/gauge.tsx",
      "content": "import { Box, Text } from \"ink\";\n\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport { toAsciiComponentText } from \"@/lib/terminal-symbols\";\n\nexport type GaugeSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface GaugeProps {\n  value: number;\n  min?: number;\n  max?: number;\n  label?: string;\n  color?: string;\n  size?: GaugeSize;\n  \"aria-label\"?: string;\n}\n\nconst ARC_CHARS_FILL = \"█\";\nconst ARC_CHARS_EMPTY = \"░\";\n\nconst clamp = (value: number, min: number, max: number): number =>\n  Math.max(min, Math.min(max, value));\n\nconst renderSmGauge = (\n  pct: number,\n  color: string,\n  mutedColor: string,\n  unicode: boolean\n): React.ReactNode => {\n  const width = 10;\n  const filled = Math.round(pct * width);\n  const empty = width - filled;\n  const unicodeBar = `${ARC_CHARS_FILL.repeat(filled)}${ARC_CHARS_EMPTY.repeat(empty)}`;\n  const bar = unicode ? unicodeBar : toAsciiComponentText(unicodeBar);\n\n  return (\n    <Box flexDirection=\"row\" gap={1}>\n      <Text color={mutedColor}>[</Text>\n      <Text color={color}>{bar}</Text>\n      <Text color={mutedColor}>]</Text>\n      <Text color={color}>{Math.round(pct * 100)}%</Text>\n    </Box>\n  );\n};\n\nconst renderMdGauge = (\n  pct: number,\n  color: string,\n  mutedColor: string,\n  fgColor: string,\n  unicode: boolean\n): React.ReactNode => {\n  const arcWidth = 14;\n  const filled = Math.round(pct * arcWidth);\n  const empty = arcWidth - filled;\n  const unicodeBottomFill = `${ARC_CHARS_FILL.repeat(filled)}${ARC_CHARS_EMPTY.repeat(empty)}`;\n  const bottomFill = unicode\n    ? unicodeBottomFill\n    : toAsciiComponentText(unicodeBottomFill);\n  const visual = (value: string) =>\n    unicode ? value : toAsciiComponentText(value);\n  const pctStr = `${Math.round(pct * 100)}%`;\n\n  return (\n    <Box flexDirection=\"column\">\n      <Text color={mutedColor}>{visual(`╭${\"─\".repeat(arcWidth)}╮`)}</Text>\n      <Box flexDirection=\"row\">\n        <Text color={mutedColor}>{visual(\"│\")}</Text>\n        <Text color={fgColor}>{` ${pctStr} `.padEnd(arcWidth)}</Text>\n        <Text color={mutedColor}>{visual(\"│\")}</Text>\n      </Box>\n      <Box flexDirection=\"row\">\n        <Text color={mutedColor}>{visual(\"╰\")}</Text>\n        <Text color={color}>{bottomFill}</Text>\n        <Text color={mutedColor}>{visual(\"╯\")}</Text>\n      </Box>\n    </Box>\n  );\n};\n\nconst renderLgGauge = (\n  pct: number,\n  color: string,\n  mutedColor: string,\n  fgColor: string,\n  unicode: boolean\n): React.ReactNode => {\n  const arcWidth = 22;\n  const filled = Math.round(pct * arcWidth);\n  const empty = arcWidth - filled;\n  const unicodeBottomFill = `${ARC_CHARS_FILL.repeat(filled)}${ARC_CHARS_EMPTY.repeat(empty)}`;\n  const bottomFill = unicode\n    ? unicodeBottomFill\n    : toAsciiComponentText(unicodeBottomFill);\n  const visual = (value: string) =>\n    unicode ? value : toAsciiComponentText(value);\n  const pctStr = `${Math.round(pct * 100)}%`;\n  const centeredPct = pctStr\n    .padStart(Math.floor((arcWidth + pctStr.length) / 2))\n    .padEnd(arcWidth);\n\n  return (\n    <Box flexDirection=\"column\">\n      <Text color={mutedColor}>{visual(` ╭${\"─\".repeat(arcWidth)}╮`)}</Text>\n      <Text color={mutedColor}>{visual(`╱${\" \".repeat(arcWidth)}╲`)}</Text>\n      <Box flexDirection=\"row\">\n        <Text color={mutedColor}>{visual(\"│\")}</Text>\n        <Text color={fgColor} bold>\n          {centeredPct}\n        </Text>\n        <Text color={mutedColor}>{visual(\"│\")}</Text>\n      </Box>\n      <Text color={mutedColor}>{visual(`╲${\" \".repeat(arcWidth)}╱`)}</Text>\n      <Box flexDirection=\"row\">\n        <Text color={mutedColor}>{visual(\" ╰\")}</Text>\n        <Text color={color}>{bottomFill}</Text>\n        <Text color={mutedColor}>{visual(\"╯\")}</Text>\n      </Box>\n    </Box>\n  );\n};\n\nexport const Gauge = ({\n  value,\n  min = 0,\n  max = 100,\n  label,\n  color,\n  size = \"md\",\n  \"aria-label\": ariaLabel,\n}: GaugeProps) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const resolvedColor = color ?? theme.colors.primary;\n  const clamped = clamp(value, min, max);\n  const pct = max === min ? 0 : (clamped - min) / (max - min);\n\n  let gaugeNode: React.ReactNode;\n  if (size === \"sm\") {\n    gaugeNode = renderSmGauge(\n      pct,\n      resolvedColor,\n      theme.colors.mutedForeground,\n      unicode\n    );\n  } else if (size === \"lg\") {\n    gaugeNode = renderLgGauge(\n      pct,\n      resolvedColor,\n      theme.colors.mutedForeground,\n      theme.colors.foreground,\n      unicode\n    );\n  } else {\n    gaugeNode = renderMdGauge(\n      pct,\n      resolvedColor,\n      theme.colors.mutedForeground,\n      theme.colors.foreground,\n      unicode\n    );\n  }\n\n  return (\n    <Box\n      flexDirection=\"column\"\n      aria-role=\"progressbar\"\n      aria-label={\n        ariaLabel ??\n        `${label ?? \"Gauge\"}: ${Math.round(pct * 100)}%, ${clamped} of ${max}.`\n      }\n      aria-state={{ busy: pct < 1 }}\n    >\n      {gaugeNode}\n      {label && <Text color={theme.colors.mutedForeground}>{label}</Text>}\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "charts"
  ],
  "type": "registry:ui"
}