{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table",
  "title": "Table",
  "description": "Hairline rows in tabular figures; the column you sort by is set in ink, or set loud; or the row and column you point at cross in ink.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/table.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { useComposedRefs } from \"@/lib/0nlytype/refs\"\nimport { cn } from \"@/lib/utils\"\n\ntype TableProps = React.ComponentProps<\"table\"> & {\n  /**\n   * ink: the column you sort by is set in ink. forte: it is also set loud, in large thin figures,\n   * against the small print of the rest. cross: as on a road atlas's distance chart, the row and\n   * column of the cell you point at cross in ink, the cell itself in the accent, the rest in pencil.\n   */\n  variant?: \"ink\" | \"forte\" | \"cross\"\n}\n\n/** Hairline rows in tabular figures. The column you sort by is set in ink. */\nfunction Table({ className, variant = \"ink\", ...props }: TableProps) {\n  return <table data-slot=\"table\" data-variant={variant === \"ink\" ? undefined : variant} className={cn(\"ot-table\", className)} {...props} />\n}\n\nfunction TableHeader(props: React.ComponentProps<\"thead\">) {\n  return <thead data-slot=\"table-header\" {...props} />\n}\n\n/** Re-sorted, each row glides from where it was to where it is now (FLIP), andante. Still under reduced motion. */\nfunction TableBody({ ref, ...props }: React.ComponentProps<\"tbody\">) {\n  const own = React.useRef<HTMLTableSectionElement>(null)\n  const was = React.useRef(new WeakMap<Element, number>())\n  React.useLayoutEffect(() => {\n    const body = own.current\n    if (!body) return\n    const still = matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n    const cs = getComputedStyle(body)\n    const tempo = { duration: parseFloat(cs.getPropertyValue(\"--ot-andante\")) || 640, easing: cs.getPropertyValue(\"--ot-breath\").trim() || \"ease\" }\n    for (const row of body.rows) {\n      const from = was.current.get(row)\n      was.current.set(row, row.offsetTop) // offsetTop, not the viewport: scrolling between renders isn't a move\n      if (still || from === undefined || from === row.offsetTop) continue\n      row.animate([{ translate: `0 ${from - row.offsetTop}px` }, { translate: \"0 0\" }], tempo)\n    }\n  })\n  const composedRef = useComposedRefs(own, ref)\n  return (\n    <tbody\n      ref={composedRef}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  )\n}\n\nfunction TableFooter(props: React.ComponentProps<\"tfoot\">) {\n  return <tfoot data-slot=\"table-footer\" {...props} />\n}\n\ntype TableRowProps = React.ComponentProps<\"tr\"> & {\n  /** A chosen row: its name turns italic. */\n  picked?: boolean\n}\n\nfunction TableRow({ picked, ...props }: TableRowProps) {\n  return <tr data-slot=\"table-row\" data-picked={picked || undefined} {...props} />\n}\n\ntype TableHeadProps = React.ComponentProps<\"th\"> & {\n  /** Right-align a column of figures. */\n  numeric?: boolean\n  /** The column the rows are sorted by. Put a button inside to make it sortable. */\n  sort?: \"ascending\" | \"descending\"\n}\n\nfunction TableHead({ numeric, sort, scope = \"col\", ...props }: TableHeadProps) {\n  return <th data-slot=\"table-head\" data-num={numeric || undefined} aria-sort={sort} scope={scope} {...props} />\n}\n\ntype TableCellProps = React.ComponentProps<\"td\"> & {\n  numeric?: boolean\n  /** The cell sits in the sorted column, so it is set in ink. */\n  sorted?: boolean\n  /** The row's name: it turns italic when the row is picked. */\n  primary?: boolean\n}\n\nfunction TableCell({ numeric, sorted, primary, className, ...props }: TableCellProps) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      data-num={numeric || undefined}\n      data-sorted={sorted || undefined}\n      className={cn(primary && \"ot-table-name\", className)}\n      {...props}\n    />\n  )\n}\n\n/** Names the table for screen readers; visually hidden. */\nfunction TableCaption({ className, ...props }: React.ComponentProps<\"caption\">) {\n  return <caption data-slot=\"table-caption\" className={cn(\"ot-sr\", className)} {...props} />\n}\n\n/** A ring that fills when the row is chosen. Give it an aria-label naming the row. */\nfunction TablePick({ className, ...props }: Omit<React.ComponentProps<\"input\">, \"type\">) {\n  return <input type=\"checkbox\" data-slot=\"table-pick\" className={cn(\"ot-table-pick\", className)} {...props} />\n}\n\nexport { type TableProps, Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption, TablePick }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/table.css",
      "content": "@layer components {\n  /* ── ot-table ── hairline rows; the column you sort by is set in ink */\n  .ot-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums lining-nums; }\n  .ot-table :is(th, td) { padding: var(--ot-space-3) var(--ot-space-4) var(--ot-space-3) 0; text-align: start; vertical-align: baseline; border-bottom: var(--ot-hairline) solid var(--ot-rule); }\n  .ot-table :is(th, td):last-child { padding-inline-end: 0; } /* figures end flush with the rule */\n  .ot-table thead th { padding-top: 0; font-size: var(--ot-pp); font-weight: 400; color: var(--ot-pencil); border-bottom-color: var(--ot-ink); white-space: nowrap; }\n  .ot-table :is(tbody, tfoot) th { font-weight: 400; color: var(--ot-graphite); }\n  .ot-table tfoot :is(th, td) { color: var(--ot-ink); border-bottom: 0; }\n  .ot-table td { color: var(--ot-graphite); transition: color var(--ot-moderato); }\n  .ot-table :is(th, td)[data-num] { text-align: end; }\n  .ot-table :is(th, td):first-child:has(.ot-table-pick) { width: 1.6em; }\n  .ot-table th button { appearance: none; border: 0; background: none; margin: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; position: relative; }\n  /* The arrow hangs outside the word (after it; before it in a figures column), so headings keep their column's edge. */\n  .ot-table th button::after { content: \"↓\" / \"\"; position: absolute; inset-inline-start: calc(100% + 0.25em); opacity: 0; transition: rotate var(--ot-moderato) var(--ot-spiccato), opacity var(--ot-allegro); }\n  .ot-table th[data-num] button::after { inset-inline: auto calc(100% + 0.25em); }\n  .ot-table th button:hover::after { opacity: 0.5; }\n  .ot-table th[aria-sort] { color: var(--ot-ink); }\n  .ot-table th[aria-sort] button::after { opacity: 1; }\n  .ot-table th[aria-sort=\"ascending\"] button::after { rotate: 180deg; }\n  .ot-table td[data-sorted] { color: var(--ot-ink); }\n  .ot-table tr[data-picked] .ot-table-name { font-family: var(--ot-expression); font-style: italic; font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1; color: var(--ot-ink); }\n  /* Choosing is a ring that fills; some chosen is half a moon. */\n  @media (max-width: 30rem) { .ot-table :is(th, td) { padding-inline-end: var(--ot-space-3); } }\n  .ot-table-pick {\n    appearance: none; margin: 0; width: 10px; height: 10px; border-radius: 50%; vertical-align: 0.05em; cursor: pointer;\n    box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-rule-strong);\n    transition: background-color var(--ot-allegro), box-shadow var(--ot-allegro);\n  }\n  .ot-table-pick:hover { box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-ink); }\n  .ot-table-pick:checked { background: var(--ot-ink); box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-ink); animation: ot-land var(--ot-moderato) var(--ot-spiccato); }\n  .ot-table-pick:indeterminate { background: linear-gradient(90deg, var(--ot-ink) 50%, transparent 50%); box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-ink); }\n\n  /* ── forte ── SPECTRA's giant type against tiny data: the column you sort by is also set loud, in large thin figures, and the\n     rest stays small print on the same baselines. Re-sorted, the loudness passes to the new column as the rows glide. */\n  .ot-table[data-variant=\"forte\"] td { transition: color var(--ot-moderato), font-size var(--ot-moderato) var(--ot-breath); }\n  .ot-table[data-variant=\"forte\"] td[data-sorted] { font-size: var(--ot-mf); line-height: var(--ot-mf-lh); letter-spacing: var(--ot-mf-tr); font-weight: 250; }\n  .ot-table[data-variant=\"forte\"] tr[data-picked] td[data-sorted].ot-table-name { font-size: calc(var(--ot-expression-scale) * var(--ot-mf)); }\n  @media (max-width: 40rem) { .ot-table[data-variant=\"forte\"] td[data-sorted] { font-size: var(--ot-mp); line-height: var(--ot-mp-lh); letter-spacing: var(--ot-mp-tr); } }\n\n  /* ── cross ── the road atlas's distance chart: point at a cell (or focus inside it) and its row and its column cross in ink,\n     their headings too; the cell where they meet is the accent; everything else steps back to pencil. A reading aid only: every\n     figure reads in full without it. ponytail: one rule per column, to eight columns; add rules if a chart grows wider. */\n  .ot-table[data-variant=\"cross\"] :is(th, td) { transition: color var(--ot-allegro); }\n  @media (max-width: 30rem) { .ot-table[data-variant=\"cross\"] { font-size: var(--ot-pp); line-height: var(--ot-pp-lh); } } /* a distance chart is small print; on a phone it keeps its columns */\n  .ot-table[data-variant=\"cross\"]:has(tbody td:is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td) { color: var(--ot-pencil); }\n  .ot-table[data-variant=\"cross\"]:has(tbody td:is(:hover, :focus-within, [data-force~=\"hover\"])) tbody tr:has(> td:is(:hover, :focus-within, [data-force~=\"hover\"])) > *,\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(1):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(1),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(2):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(2),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(3):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(3),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(4):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(4),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(5):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(5),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(6):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(6),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(7):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(7),\n  .ot-table[data-variant=\"cross\"]:has(tbody td:nth-child(8):is(:hover, :focus-within, [data-force~=\"hover\"])) :is(th, td):nth-child(8) { color: var(--ot-ink); }\n  .ot-table[data-variant=\"cross\"]:has(tbody td:is(:hover, :focus-within, [data-force~=\"hover\"])) tbody td:is(:hover, :focus-within, [data-force~=\"hover\"]):is(:hover, :focus-within, [data-force~=\"hover\"]) { color: var(--ot-accent); }\n  @media (forced-colors: active) { .ot-table[data-variant=\"cross\"]:has(tbody td:is(:hover, :focus-within, [data-force~=\"hover\"])) tbody td:is(:hover, :focus-within, [data-force~=\"hover\"]):is(:hover, :focus-within, [data-force~=\"hover\"]) { color: Highlight; } }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/table.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/table.css\"": {}
  },
  "meta": {
    "movement": "X",
    "underneath": "native",
    "contract": "ot-table"
  },
  "type": "registry:ui"
}
