{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ledger-table",
  "title": "Ledger Table",
  "description": "Full-bleed rows with the title inside the grid, a numbered rail and sortable headers.",
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/ledger-table.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst MIN_COLUMN_WIDTH = 110\nconst CELL_RULE = \"border-l border-[color-mix(in_srgb,var(--ink)_18%,transparent)]\"\nconst RAIL = \"border-ink w-[2.6rem] border-r-(length:--rule) pr-2 text-right font-(family-name:--f-mono) text-[11px] font-normal\"\n\nexport type LedgerColumn<Row> = {\n  key: keyof Row & string\n  label: string\n  /** Right-aligns the column with tabular digits. */\n  numeric?: boolean\n  /** Sets the column in the heavier display type. */\n  emphasis?: boolean\n  /** Custom cell content. Pass columns from a client component when you use it. */\n  render?: (row: Row) => React.ReactNode\n}\n\nexport type LedgerSort<Row> = { key: keyof Row & string; dir: \"asc\" | \"desc\" }\n\ntype LedgerTableProps<Row> = {\n  /** Printed inside the table, and names it for screen readers. */\n  title: string\n  rows: Row[]\n  columns: LedgerColumn<Row>[]\n  /** The field that uniquely identifies a row. */\n  rowKey: keyof Row & string\n  defaultSort?: LedgerSort<Row>\n  className?: string\n}\n\nexport function sortRows<Row>(rows: Row[], sort: LedgerSort<Row> | undefined) {\n  if (!sort) return rows\n  const direction = sort.dir === \"asc\" ? 1 : -1\n  return rows.toSorted((a, b) => {\n    const left = a[sort.key]\n    const right = b[sort.key]\n    if (left === right) return 0\n    return (left > right ? 1 : -1) * direction\n  })\n}\n\nexport function LedgerTable<Row>({ title, rows, columns, rowKey, defaultSort, className }: LedgerTableProps<Row>) {\n  const [sort, setSort] = React.useState(defaultSort)\n  const sorted = sortRows(rows, sort)\n\n  const toggle = (key: keyof Row & string) => {\n    setSort((current) => ({ key, dir: current?.key === key && current.dir === \"desc\" ? \"asc\" : \"desc\" }))\n  }\n\n  return (\n    <div tabIndex={0} role=\"region\" aria-label={title} className={cn(\"spotcheck-focus overflow-x-auto font-(family-name:--f-body)\", className)}>\n      <table className=\"w-full border-collapse tabular-nums\" style={{ minWidth: columns.length * MIN_COLUMN_WIDTH }}>\n        <caption className=\"sr-only\">{title}</caption>\n        <thead>\n          <tr aria-hidden>\n            <td className={cn(RAIL, \"text-mute pt-6.5 pb-2.5 align-bottom\")}>00</td>\n            <td colSpan={columns.length} className=\"spotcheck-display px-3 pt-6.5 pb-2.5 align-bottom text-[42px] leading-[.9]\">\n              {title}\n            </td>\n          </tr>\n          <tr>\n            <th scope=\"col\" className={cn(RAIL, \"spotcheck-label border-b-(length:--rule) pt-2.5 pb-1.5 align-bottom\")}>\n              #\n            </th>\n            {columns.map((column) => (\n              <th\n                key={column.key}\n                scope=\"col\"\n                aria-sort={sort?.key === column.key ? (sort.dir === \"asc\" ? \"ascending\" : \"descending\") : undefined}\n                className={cn(\n                  \"spotcheck-label border-ink border-b-(length:--rule) px-3 pt-2.5 pb-1.5 text-left align-bottom font-medium\",\n                  CELL_RULE,\n                  sort?.key === column.key && \"text-ink\",\n                  column.numeric && \"text-right\",\n                )}\n              >\n                <button type=\"button\" onClick={() => toggle(column.key)} className=\"spotcheck-focus cursor-pointer uppercase\">\n                  {column.label}\n                  {sort?.key === column.key && <span aria-hidden>{sort.dir === \"asc\" ? \" ↑\" : \" ↓\"}</span>}\n                </button>\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody>\n          {sorted.map((row, index) => (\n            <tr\n              key={String(row[rowKey])}\n              className=\"hover:bg-ink hover:text-paper group transition-colors duration-100 motion-reduce:transition-none\"\n            >\n              <td className={cn(RAIL, \"text-mute group-hover:text-paper pt-5.5 pb-1.5 align-bottom\")}>\n                {String(index + 1).padStart(2, \"0\")}\n              </td>\n              {columns.map((column) => (\n                <td\n                  key={column.key}\n                  className={cn(\n                    \"px-3 pt-5.5 pb-1.5 align-bottom\",\n                    CELL_RULE,\n                    column.numeric && \"text-right\",\n                    column.emphasis && \"spotcheck-heading text-xl leading-none\",\n                  )}\n                >\n                  {column.render ? column.render(row) : String(row[column.key])}\n                </td>\n              ))}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "core"
  ],
  "type": "registry:component"
}