{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/bar-chart",
  "title": "Bar Chart",
  "description": "Horizontal and vertical bar chart with labels",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/accessibility.json",
    "https://termcn.dev/r/ink/terminal-symbols.json",
    "https://termcn.dev/r/ink/terminal-text.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/bar-chart.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport React from \"react\";\n\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport { summarizeSeries } from \"@/lib/accessibility\";\nimport { resolveTerminalSymbol } from \"@/lib/terminal-symbols\";\nimport {\n  padToTerminalWidth,\n  terminalWidth,\n  truncateToTerminalWidth,\n} from \"@/lib/terminal-text\";\n\nexport interface BarChartItem {\n  label: string;\n  value: number;\n  color?: string;\n}\n\nexport interface BarChartProps {\n  data: BarChartItem[];\n  direction?: \"horizontal\" | \"vertical\";\n  width?: number;\n  height?: number;\n  showValues?: boolean;\n  title?: string;\n  accessibleSummary?: string;\n  \"aria-label\"?: string;\n}\n\nconst BAR_CHAR = \"█\";\nconst EMPTY_CHAR = \"░\";\n\nconst pad = (str: string, length: number): string =>\n  padToTerminalWidth(truncateToTerminalWidth(str, length), length);\n\nconst padStart = (str: string, length: number): string => {\n  const value = truncateToTerminalWidth(str, length);\n  return \" \".repeat(Math.max(0, length - terminalWidth(value))) + value;\n};\n\nexport const BarChart = ({\n  data,\n  direction = \"horizontal\",\n  width = 30,\n  height = 10,\n  showValues = true,\n  title,\n  accessibleSummary,\n  \"aria-label\": ariaLabel,\n}: BarChartProps) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const barChar = resolveTerminalSymbol(unicode, BAR_CHAR, \"#\");\n  const emptyChar = resolveTerminalSymbol(unicode, EMPTY_CHAR, \".\");\n\n  if (data.length === 0) {\n    return <Text color={theme.colors.mutedForeground}>No data</Text>;\n  }\n\n  const maxValue = Math.max(...data.map((d) => d.value));\n  const chartSummary =\n    accessibleSummary ??\n    summarizeSeries(\n      title ?? \"Bar chart\",\n      data.map((item) => ({ label: item.label, value: item.value })),\n      12\n    );\n\n  if (direction === \"horizontal\") {\n    const maxLabelLen = Math.max(...data.map((d) => terminalWidth(d.label)));\n    const maxValLen = Math.max(\n      ...data.map((d) => terminalWidth(String(d.value)))\n    );\n    const barWidth = width - maxLabelLen - maxValLen - 3;\n\n    return (\n      <Box flexDirection=\"column\" aria-label={ariaLabel ?? chartSummary}>\n        {title && (\n          <Text bold color={theme.colors.primary}>\n            {title}\n          </Text>\n        )}\n        {data.map((item, idx) => {\n          const filled =\n            maxValue === 0\n              ? 0\n              : Math.round((item.value / maxValue) * Math.max(1, barWidth));\n          const empty = Math.max(0, barWidth - filled);\n          const barStr = barChar.repeat(filled) + emptyChar.repeat(empty);\n          const resolvedColor = item.color ?? theme.colors.primary;\n\n          return (\n            <Box key={idx} flexDirection=\"row\" gap={1}>\n              <Text color={theme.colors.foreground}>\n                {pad(item.label, maxLabelLen)}\n              </Text>\n              <Text color={resolvedColor}>{barStr}</Text>\n              {showValues && (\n                <Text color={theme.colors.mutedForeground}>\n                  {padStart(String(item.value), maxValLen)}\n                </Text>\n              )}\n            </Box>\n          );\n        })}\n      </Box>\n    );\n  }\n\n  const barW = Math.max(3, Math.floor(width / data.length));\n  const rows: string[][] = [];\n\n  for (let row = height - 1; row >= 0; row -= 1) {\n    const threshold = (row / (height - 1)) * maxValue;\n    const cells = data.map((item) => {\n      const filled = item.value >= threshold;\n      return filled ? barChar.repeat(barW) : \" \".repeat(barW);\n    });\n    rows.push(cells);\n  }\n\n  return (\n    <Box flexDirection=\"column\" aria-label={ariaLabel ?? chartSummary}>\n      {title && (\n        <Text bold color={theme.colors.primary}>\n          {title}\n        </Text>\n      )}\n      {rows.map((row, rowIdx) => {\n        const threshold = ((height - 1 - rowIdx) / (height - 1)) * maxValue;\n        return (\n          <Box key={rowIdx} flexDirection=\"row\">\n            {row.map((cell, colIdx) => {\n              const item = data[colIdx];\n              const resolvedColor = item.color ?? theme.colors.primary;\n              const isFilled = item.value >= threshold;\n              return (\n                <Text\n                  key={colIdx}\n                  color={isFilled ? resolvedColor : theme.colors.muted}\n                >\n                  {cell}\n                </Text>\n              );\n            })}\n          </Box>\n        );\n      })}\n      {showValues && (\n        <Box flexDirection=\"row\">\n          {data.map((item, idx) => {\n            const resolvedColor = item.color ?? theme.colors.primary;\n            return (\n              <Text key={idx} color={resolvedColor}>\n                {pad(String(item.value), barW)}\n              </Text>\n            );\n          })}\n        </Box>\n      )}\n      <Box flexDirection=\"row\">\n        {data.map((item, idx) => (\n          <Text key={idx} color={theme.colors.mutedForeground}>\n            {pad(item.label, barW)}\n          </Text>\n        ))}\n      </Box>\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "charts"
  ],
  "type": "registry:ui"
}