{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table",
  "type": "registry:component",
  "title": "Table",
  "description": "Rows of records with the same columns, with numbers aligned and a header that stays pinned.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/panelui/table.tsx",
      "type": "registry:component",
      "target": "@components/panelui/table.tsx",
      "content": "'use client';\n\n/**\n * Table — rows of records with the same columns, to be read across and compared down.\n *\n * ```tsx\n * <Table>\n *   <Table.Header>\n *     <Table.Row>\n *       <Table.Head>Invoice</Table.Head>\n *       <Table.Head align=\"end\">Amount</Table.Head>\n *     </Table.Row>\n *   </Table.Header>\n *   <Table.Body>\n *     <Table.Row>\n *       <Table.Cell>INV-2041</Table.Cell>\n *       <Table.Cell align=\"end\">£1,280.00</Table.Cell>\n *     </Table.Row>\n *   </Table.Body>\n * </Table>\n * ```\n *\n * ## Numbers line up\n *\n * `align=\"end\"` on a column's head and cells right-aligns it and switches the\n * digits to equal widths, so a column of amounts lines up on its last digit\n * and can be compared at a glance. Text columns stay at the start.\n *\n * ## It scrolls inside itself\n *\n * A table wider than the page scrolls sideways inside its own box, so the\n * page around it does not. Give the table a `maxHeight` and the body scrolls\n * too, with the header row pinned above it.\n *\n * ## Density\n *\n * `density=\"compact\"` tightens the rows for a dense admin screen; the\n * default leaves room for a second line of muted text in a cell.\n * `striped` shades every other row, which helps a reader keep their place\n * across a wide row with nothing in the middle of it.\n *\n * Every part is the native table element, so a screen reader announces the\n * row and column of each cell. For data that is a list of things rather than\n * a grid of values, Frame's rows read better.\n */\nimport type { ComponentProps, CSSProperties } from 'react';\nimport { tv } from 'tailwind-variants';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport type { ElementType as RenderElementType } from 'react';\n\n/** Draws a part as another element, or composes it with a component: `render={<a href=\"/\" />}`. */\ntype RenderProp<Tag extends RenderElementType> = { render?: useRender.ComponentProps<Tag>['render'] };\n\nconst tableVariants = tv({\n  // The parts are rendered apart from the table, so the table's own settings\n  // (density, stripes, pinning, framing) reach them as data attributes on\n  // the container rather than as variants.\n  slots: {\n    container: 'relative w-full overflow-auto',\n    table: 'w-full caption-bottom border-collapse text-sm',\n    header: [\n      // The header's line is a shadow, so it stays drawn while the row is pinned and the body scrolls under it.\n      '[&_tr]:shadow-[inset_0_-1px_0_var(--border)] [&_tr]:hover:bg-transparent',\n    ],\n    body: '[&_tr:last-child]:border-0',\n    footer: 'border-t bg-muted/40 font-medium [&>tr]:last:border-b-0',\n    row: [\n      'border-b border-border transition-colors',\n      'hover:bg-muted/40 data-[state=selected]:bg-muted aria-selected:bg-muted',\n    ],\n    head: [\n      'h-10 whitespace-nowrap px-3 text-start align-middle text-xs font-medium text-muted-foreground',\n      'first:ps-0 last:pe-0 in-data-[framed]:first:ps-4 in-data-[framed]:last:pe-4',\n    ],\n    cell: 'px-3 align-middle first:ps-0 last:pe-0 in-data-[framed]:first:ps-4 in-data-[framed]:last:pe-4',\n    caption: 'mt-3 text-xs text-muted-foreground',\n  },\n  variants: {\n    align: {\n      start: {},\n      center: { head: 'text-center', cell: 'text-center' },\n      end: { head: 'text-end', cell: 'text-end tabular-nums' },\n    },\n  },\n  defaultVariants: { align: 'start' },\n});\n\nexport type TableDensity = 'comfortable' | 'compact';\nexport type TableAlign = 'start' | 'center' | 'end';\n\nexport interface TableProps extends ComponentProps<'table'> {\n  /** Row height. `compact` for a dense screen. Default `comfortable`. */\n  density?: TableDensity;\n  /** Shades every other body row. */\n  striped?: boolean;\n  /**\n   * Draws the cells clear of the edges, for a table set inside a Card or\n   * Frame panel, whose edge the first and last columns would otherwise touch.\n   */\n  framed?: boolean;\n  /**\n   * The tallest the table gets before its body scrolls, with the header\n   * pinned above. Any CSS length.\n   */\n  maxHeight?: CSSProperties['maxHeight'];\n  /** Classes for the scrolling box around the table, rather than the table. */\n  containerClassName?: string;\n}\n\n/** The table, in a box that scrolls when the table is wider or taller than it. */\nfunction TableRoot({\n  density = 'comfortable',\n  striped = false,\n  framed = false,\n  maxHeight,\n  containerClassName,\n  className,\n  render,\n  ...props\n}: TableProps & RenderProp<'table'>) {\n  const slots = tableVariants();\n  const element = useRender({\n    defaultTagName: 'table',\n    render,\n    props: mergeProps({ 'data-slot': 'table', className: slots.table({ className }) }, props),\n  });\n  return (\n    <div\n      data-slot=\"table-container\"\n      data-density={density}\n      data-striped={striped ? '' : undefined}\n      data-framed={framed ? '' : undefined}\n      data-sticky={maxHeight != null ? '' : undefined}\n      className={slots.container({ className: containerClassName })}\n      style={maxHeight != null ? { maxHeight } : undefined}\n    >\n      {element}\n    </div>\n  );\n}\nTableRoot.displayName = 'Table';\n\n/** The header row group. Pinned while the body scrolls, when the table has a `maxHeight`. */\nfunction TableHeader({ className, render, ...props }: ComponentProps<'thead'> & RenderProp<'thead'>) {\n  const slots = tableVariants();\n  return useRender({\n    defaultTagName: 'thead',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'table-header',\n        className: slots.header({\n          className: [\n            'in-data-[sticky]:sticky in-data-[sticky]:top-0 in-data-[sticky]:z-10 in-data-[sticky]:bg-background',\n            className,\n          ],\n        }),\n      },\n      props\n    ),\n  });\n}\nTableHeader.displayName = 'Table.Header';\n\n/** The rows of data. */\nfunction TableBody({ className, render, ...props }: ComponentProps<'tbody'> & RenderProp<'tbody'>) {\n  return useRender({\n    defaultTagName: 'tbody',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'table-body',\n        className: tableVariants().body({\n          className: ['in-data-[striped]:[&>tr:nth-child(even)]:bg-muted/40', className],\n        }),\n      },\n      props\n    ),\n  });\n}\nTableBody.displayName = 'Table.Body';\n\n/** Totals under the body. */\nfunction TableFooter({ className, render, ...props }: ComponentProps<'tfoot'> & RenderProp<'tfoot'>) {\n  return useRender({\n    defaultTagName: 'tfoot',\n    render,\n    props: mergeProps({ 'data-slot': 'table-footer', className: tableVariants().footer({ className }) }, props),\n  });\n}\nTableFooter.displayName = 'Table.Footer';\n\n/** One row. `aria-selected` shades it as chosen. */\nfunction TableRow({ className, render, ...props }: ComponentProps<'tr'> & RenderProp<'tr'>) {\n  return useRender({\n    defaultTagName: 'tr',\n    render,\n    props: mergeProps({ 'data-slot': 'table-row', className: tableVariants().row({ className }) }, props),\n  });\n}\nTableRow.displayName = 'Table.Row';\n\nexport interface TableHeadProps extends Omit<ComponentProps<'th'>, 'align'> {\n  /** Where the column's content sits. `end` for numbers. Default `start`. */\n  align?: TableAlign;\n}\n\n/** A column's heading. */\nfunction TableHead({ align = 'start', className, render, ...props }: TableHeadProps & RenderProp<'th'>) {\n  return useRender({\n    defaultTagName: 'th',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'table-head',\n        scope: 'col',\n        className: tableVariants({ align }).head({ className: ['in-data-[density=compact]:h-8', className] }),\n      },\n      props\n    ),\n  });\n}\nTableHead.displayName = 'Table.Head';\n\nexport interface TableCellProps extends Omit<ComponentProps<'td'>, 'align'> {\n  /** Where the content sits. Match the column's head. `end` also gives the digits equal widths. Default `start`. */\n  align?: TableAlign;\n}\n\n/** One cell. */\nfunction TableCell({ align = 'start', className, render, ...props }: TableCellProps & RenderProp<'td'>) {\n  return useRender({\n    defaultTagName: 'td',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'table-cell',\n        className: tableVariants({ align }).cell({\n          className: ['py-3 in-data-[density=compact]:py-1.5', className],\n        }),\n      },\n      props\n    ),\n  });\n}\nTableCell.displayName = 'Table.Cell';\n\n/** What the table is, under it. Read first by a screen reader. */\nfunction TableCaption({ className, render, ...props }: ComponentProps<'caption'> & RenderProp<'caption'>) {\n  return useRender({\n    defaultTagName: 'caption',\n    render,\n    props: mergeProps({ 'data-slot': 'table-caption', className: tableVariants().caption({ className }) }, props),\n  });\n}\nTableCaption.displayName = 'Table.Caption';\n\nexport const Table = Object.assign(TableRoot, {\n  Header: TableHeader,\n  Body: TableBody,\n  Footer: TableFooter,\n  Row: TableRow,\n  Head: TableHead,\n  Cell: TableCell,\n  Caption: TableCaption,\n});\n\nexport { TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption };\n"
    }
  ]
}
