{
  "$schema": "https://blode.co/ui/schema/registry-item.json",
  "name": "bar-list",
  "title": "Bar List",
  "author": "Matthew Blode",
  "description": "A compact comparison list that visualises values as horizontal bars.",
  "files": [
    {
      "path": "ui/bar-list.tsx",
      "content": "import type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface BarListItemBase {\n  color?: string;\n  href?: string;\n  icon?: React.ComponentType<{ className?: string }>;\n  key?: string;\n  name: string;\n  target?: React.HTMLAttributeAnchorTarget;\n  value: number;\n}\n\ninterface BarListProps<\n  T extends object = Record<string, never>,\n> extends React.ComponentProps<\"div\"> {\n  color?: string;\n  data?: (T & BarListItemBase)[];\n  labelFormatter?: (label: string) => React.ReactNode;\n  showAnimation?: boolean;\n  sortOrder?: \"ascending\" | \"descending\";\n  valueFormatter?: (value: number) => React.ReactNode;\n}\n\nconst EMPTY_BAR_LIST_DATA: never[] = [];\n\nconst defaultValueFormatter = (value: number): string => value.toLocaleString();\n\nconst defaultLabelFormatter = (label: string): string => label;\n\nconst BarList = <T extends object = Record<string, never>>({\n  className,\n  color = \"hsl(var(--chart-1))\",\n  data = EMPTY_BAR_LIST_DATA,\n  labelFormatter = defaultLabelFormatter,\n  showAnimation = false,\n  sortOrder = \"descending\",\n  valueFormatter = defaultValueFormatter,\n  ...props\n}: BarListProps<T>) => {\n  const sortedData = [...data].toSorted((a, b) =>\n    sortOrder === \"ascending\" ? a.value - b.value : b.value - a.value,\n  );\n\n  const maxValue = Math.max(0, ...sortedData.map((item) => item.value));\n\n  const widths = sortedData.map((item) => {\n    if (item.value === 0 || maxValue === 0) {\n      return 0;\n    }\n\n    return Math.max((item.value / maxValue) * 100, 2);\n  });\n\n  const rowHeight = \"h-8\";\n\n  return (\n    <div\n      className={cn(\"flex justify-between space-x-6\", className)}\n      data-slot=\"bar-list\"\n      data-sort={sortOrder}\n      {...props}\n    >\n      <div className=\"relative w-full\">\n        {sortedData.map((item, index) => {\n          const Icon = item.icon;\n\n          return (\n            <div\n              className=\"group flex w-full items-center py-1\"\n              data-slot=\"bar-list-row\"\n              key={item.key ?? item.name}\n            >\n              <div\n                className={cn(\n                  \"relative flex items-center rounded-sm transition-[background-color,opacity]\",\n                  rowHeight,\n                  {\n                    \"duration-500\": showAnimation,\n                    \"mb-0\": index === sortedData.length - 1,\n                  },\n                )}\n                style={{\n                  transition: showAnimation ? \"all 1s\" : undefined,\n                  width: `${widths[index]}%`,\n                }}\n              >\n                <div\n                  className=\"absolute inset-0 rounded-sm opacity-30 transition-[width,opacity]\"\n                  style={{ background: item.color ?? color }}\n                />\n              </div>\n\n              <div className=\"absolute left-2 flex max-w-full pr-4\">\n                {Icon ? <Icon className=\"mr-2 h-5 w-5 flex-none text-muted-foreground\" /> : null}\n\n                {item.href ? (\n                  <a\n                    className=\"truncate whitespace-nowrap text-foreground text-sm hover:underline\"\n                    href={item.href}\n                    onClick={(event) => event.stopPropagation()}\n                    rel=\"noreferrer\"\n                    target={item.target ?? \"_blank\"}\n                  >\n                    {labelFormatter(item.name)}\n                  </a>\n                ) : (\n                  <p className=\"truncate whitespace-nowrap text-foreground text-sm\">\n                    {labelFormatter(item.name)}\n                  </p>\n                )}\n              </div>\n            </div>\n          );\n        })}\n      </div>\n\n      <div>\n        {sortedData.map((item, index) => (\n          <div\n            className={cn(\n              \"flex items-center justify-end\",\n              rowHeight,\n              index === 0 ? \"mt-1\" : \"my-2\",\n            )}\n            data-slot=\"bar-list-value\"\n            key={item.key ?? item.name}\n          >\n            <p className=\"truncate whitespace-nowrap text-foreground text-sm leading-none\">\n              {valueFormatter(item.value)}\n            </p>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport { BarList };\nexport type { BarListProps };\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}
