{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "description": "A virtualized, sortable table with resizable columns, a sticky header, and left or right pinned columns for scanning dozens of fields at once.",
  "dependencies": [
    "@base-ui/react@^1.8.0",
    "@solar-icons/react@^2.2.0",
    "cn@^0.3.0"
  ],
  "registryDependencies": [
    "https://pastaui.com/r/pastaui-styles.json"
  ],
  "files": [
    {
      "path": "src/components/ai/data-table.tsx",
      "content": "import { Menu } from '@base-ui/react/menu'\nimport { PinIcon } from '@solar-icons/react/bold/pin'\nimport { AltArrowDownIcon } from '@solar-icons/react/linear/alt-arrow-down'\nimport { ArrowDownIcon } from '@solar-icons/react/linear/arrow-down'\nimport { ArrowUpIcon } from '@solar-icons/react/linear/arrow-up'\nimport { TableIcon } from '@solar-icons/react/linear/table'\nimport * as React from 'react'\n\nimport { cn } from 'cn'\n\ntype DataTableValue = string | number | boolean | null | undefined\n\ninterface DataTableColumn<Row> {\n  key: keyof Row & string\n  header: string\n  align?: 'start' | 'end'\n  cell?: (row: Row) => React.ReactNode\n  icon?: React.ComponentType<{ className?: string }>\n  minWidth?: number\n  pin?: DataTablePin\n  pinnable?: boolean\n  resizable?: boolean\n  sortable?: boolean\n  width?: number\n}\n\ntype DataTablePin = 'left' | 'right'\n\ninterface DataTableSort {\n  key: string\n  direction: 'asc' | 'desc'\n}\n\ninterface DataTableProps<Row extends Record<string, DataTableValue>> extends Omit<\n  React.ComponentProps<'section'>,\n  'title'\n> {\n  columns: DataTableColumn<Row>[]\n  rows: Row[]\n  defaultSort?: DataTableSort | null\n  getRowId?: (row: Row, index: number) => string\n  onColumnPinChange?: (key: string, pin: DataTablePin | null) => void\n  onColumnResize?: (key: string, width: number) => void\n  onRowClick?: (row: Row) => void\n  onSortChange?: (sort: DataTableSort | null) => void\n  pinnable?: boolean\n  resizable?: boolean\n  rowHeight?: number\n  sort?: DataTableSort | null\n  title?: string\n}\n\nconst DEFAULT_WIDTH = 140\nconst MIN_WIDTH = 64\nconst KEYBOARD_STEP = 16\nconst OVERSCAN = 8\nconst COLUMN_OVERSCAN = 2\n\nconst collator = new Intl.Collator(undefined, { numeric: true })\n\nconst dividerClass = 'border-b-[0.5px] border-foreground/[0.09]'\n\nconst headerCellClass =\n  'sticky top-0 z-10 h-11 border-b-[0.5px] border-foreground/[0.09] bg-card px-3 text-[13px] font-medium whitespace-nowrap text-foreground/75'\n\nconst scrollbarClass =\n  '[&::-webkit-scrollbar]:size-[11px] [&::-webkit-scrollbar-corner]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border-[3px] [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-thumb]:bg-foreground/20 [&::-webkit-scrollbar-thumb]:bg-clip-padding [&::-webkit-scrollbar-thumb:hover]:bg-foreground/35 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-track:horizontal]:mx-2 [&::-webkit-scrollbar-track:vertical]:mt-11 [&::-webkit-scrollbar-track:vertical]:mb-2'\n\nconst bodyCellClass =\n  'bg-card px-3 whitespace-nowrap tabular-nums group-last:border-b-0 group-hover:bg-[color-mix(in_oklch,var(--card),var(--foreground)_3.5%)]'\n\nfunction compareValues(a: DataTableValue, b: DataTableValue) {\n  if (a == null) return b == null ? 0 : 1\n  if (b == null) return -1\n  if (typeof a === 'number' && typeof b === 'number') return a - b\n  return collator.compare(String(a), String(b))\n}\n\nfunction PinSideIcon({ side, className }: { side: DataTablePin; className?: string }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={cn(side === 'right' && '-scale-x-100', className)}\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"1.5\"\n      viewBox=\"0 0 16 16\"\n    >\n      <path d=\"M2.5 3v10M13.5 8H6M9 5 6 8l3 3\" />\n    </svg>\n  )\n}\n\nfunction MenuOption({\n  checked,\n  children,\n  icon,\n  onClick,\n}: {\n  checked: boolean\n  children: React.ReactNode\n  icon: React.ReactNode\n  onClick: () => void\n}) {\n  return (\n    <Menu.Item\n      className=\"flex cursor-pointer items-center gap-2.5 rounded-[10px] px-2.5 py-2 outline-none data-highlighted:bg-foreground/6 data-highlighted:text-foreground\"\n      onClick={onClick}\n    >\n      <span className=\"flex size-4 items-center justify-center text-muted-foreground [&>svg]:size-4\">\n        {icon}\n      </span>\n      <span className=\"flex-1\">{children}</span>\n      {checked && (\n        <svg\n          aria-hidden=\"true\"\n          className=\"size-4 text-foreground\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          strokeWidth=\"1.75\"\n          viewBox=\"0 0 16 16\"\n        >\n          <path d=\"m3.5 8.5 3 3 6-7\" />\n        </svg>\n      )}\n    </Menu.Item>\n  )\n}\n\ninterface DataTableLayout<Row> {\n  firstKey?: string\n  firstRightKey?: string\n  lastKey?: string\n  lastLeftKey?: string\n  left: DataTableColumn<Row>[]\n  middle: DataTableColumn<Row>[]\n  middleStarts: number[]\n  offsets: Record<string, number>\n  ordered: DataTableColumn<Row>[]\n  pinnedWidth: number\n  right: DataTableColumn<Row>[]\n  totalWidth: number\n  widths: Record<string, number>\n}\n\nfunction createLayout<Row>(\n  sourceColumns: DataTableColumn<Row>[],\n  resized: Record<string, number>,\n  pins: Record<string, DataTablePin | null>,\n): DataTableLayout<Row> {\n  const columns = sourceColumns.map((column) =>\n    column.key in pins ? { ...column, pin: pins[column.key] ?? undefined } : column,\n  )\n  const left = columns.filter((column) => column.pin === 'left')\n  const right = columns.filter((column) => column.pin === 'right')\n  const middle = columns.filter((column) => !column.pin)\n  const ordered = [...left, ...middle, ...right]\n  const widths: Record<string, number> = {}\n  for (const column of ordered)\n    widths[column.key] = resized[column.key] ?? column.width ?? DEFAULT_WIDTH\n\n  const offsets: Record<string, number> = {}\n  let leftWidth = 0\n  for (const column of left) {\n    offsets[column.key] = leftWidth\n    leftWidth += widths[column.key]\n  }\n  let rightWidth = 0\n  for (const column of [...right].reverse()) {\n    offsets[column.key] = rightWidth\n    rightWidth += widths[column.key]\n  }\n  const middleStarts: number[] = []\n  let middleWidth = 0\n  for (const column of middle) {\n    middleStarts.push(middleWidth)\n    middleWidth += widths[column.key]\n  }\n\n  return {\n    firstKey: ordered[0]?.key,\n    firstRightKey: right[0]?.key,\n    lastKey: ordered.at(-1)?.key,\n    lastLeftKey: left.at(-1)?.key,\n    left,\n    middle,\n    middleStarts,\n    offsets,\n    ordered,\n    pinnedWidth: leftWidth + rightWidth,\n    right,\n    totalWidth: leftWidth + middleWidth + rightWidth,\n    widths,\n  }\n}\n\nfunction visibleColumns<Row>(layout: DataTableLayout<Row>, scrollLeft: number, width: number) {\n  const { middle, middleStarts, widths } = layout\n  const visibleEnd = scrollLeft + Math.max(0, width - layout.pinnedWidth)\n  let start = 0\n  while (start < middle.length && middleStarts[start] + widths[middle[start].key] <= scrollLeft)\n    start++\n  let end = start\n  while (end < middle.length && middleStarts[end] < visibleEnd) end++\n  return [\n    Math.max(0, start - COLUMN_OVERSCAN),\n    Math.min(middle.length, end + COLUMN_OVERSCAN),\n  ] as const\n}\n\nfunction cellLayout<Row>(layout: DataTableLayout<Row>, column: DataTableColumn<Row>) {\n  return {\n    className: cn(\n      column.key === layout.firstKey && 'pl-5',\n      column.key === layout.lastKey && 'pr-5',\n      column.pin && 'sticky',\n      column.key === layout.lastLeftKey && 'border-r-[0.5px]',\n      column.key === layout.firstRightKey && 'border-l-[0.5px]',\n    ),\n    style: column.pin ? { [column.pin]: layout.offsets[column.key] } : undefined,\n  }\n}\n\nfunction SpacerRow({\n  colSpan,\n  height,\n  rowHeight,\n}: {\n  colSpan: number\n  height: number\n  rowHeight: number\n}) {\n  if (height <= 0) return null\n  return (\n    <tr aria-hidden=\"true\" style={{ height }}>\n      {/* oxlint-disable-next-line jsx-a11y/control-has-associated-label */}\n      <td\n        colSpan={colSpan}\n        style={{\n          backgroundImage: `repeating-linear-gradient(to bottom, transparent 0 ${rowHeight - 0.5}px, color-mix(in oklch, var(--foreground) 9%, transparent) ${rowHeight - 0.5}px ${rowHeight}px)`,\n        }}\n      />\n    </tr>\n  )\n}\n\nfunction GapCell({ colSpan, header }: { colSpan: number; header?: boolean }) {\n  if (colSpan <= 0) return null\n  return header ? (\n    <th aria-hidden=\"true\" className={headerCellClass} colSpan={colSpan} />\n  ) : (\n    // oxlint-disable-next-line jsx-a11y/control-has-associated-label\n    <td aria-hidden=\"true\" className={cn(bodyCellClass, dividerClass)} colSpan={colSpan} />\n  )\n}\n\ninterface DataTableRowProps<Row> {\n  columnEnd: number\n  columnStart: number\n  layout: DataTableLayout<Row>\n  onRowClick?: (row: Row) => void\n  row: Row\n  rowHeight: number\n  sortKey?: string\n}\n\nfunction DataTableRowContent<Row extends Record<string, DataTableValue>>({\n  columnEnd,\n  columnStart,\n  layout,\n  onRowClick,\n  row,\n  rowHeight,\n  sortKey,\n}: DataTableRowProps<Row>) {\n  function renderCell(column: DataTableColumn<Row>) {\n    const pin = cellLayout(layout, column)\n    const value = row[column.key]\n\n    return (\n      <td\n        className={cn(\n          bodyCellClass,\n          dividerClass,\n          sortKey === column.key && 'bg-[color-mix(in_oklch,var(--card),var(--foreground)_1.5%)]',\n          column.align === 'end' && 'text-right',\n          pin.className,\n          column.pin && 'z-[5]',\n        )}\n        key={column.key}\n        style={pin.style}\n      >\n        <div className=\"truncate\">{column.cell ? column.cell(row) : (value ?? '—')}</div>\n      </td>\n    )\n  }\n\n  return (\n    <tr\n      className={cn('group', onRowClick && 'cursor-pointer')}\n      onClick={onRowClick ? () => onRowClick(row) : undefined}\n      style={{ height: rowHeight }}\n    >\n      {layout.left.map(renderCell)}\n      <GapCell colSpan={columnStart} />\n      {layout.middle.slice(columnStart, columnEnd).map(renderCell)}\n      <GapCell colSpan={layout.middle.length - columnEnd} />\n      {layout.right.map(renderCell)}\n    </tr>\n  )\n}\n\nconst DataTableRow = React.memo(DataTableRowContent) as typeof DataTableRowContent\n\nfunction DataTable<Row extends Record<string, DataTableValue>>({\n  className,\n  columns,\n  defaultSort = null,\n  getRowId,\n  onColumnResize,\n  onRowClick,\n  onColumnPinChange,\n  onSortChange,\n  pinnable = true,\n  resizable = true,\n  rowHeight = 44,\n  rows,\n  sort: sortProp,\n  title = 'Data',\n  ...props\n}: DataTableProps<Row>) {\n  const [internalSort, setInternalSort] = React.useState(defaultSort)\n  const [viewport, setViewport] = React.useState({ top: 0, left: 0, height: 600, width: 1200 })\n  const [resized, setResized] = React.useState<Record<string, number>>({})\n  const [pins, setPins] = React.useState<Record<string, DataTablePin | null>>({})\n  const [resizingKey, setResizingKey] = React.useState<string | null>(null)\n  const scrollRef = React.useRef<HTMLDivElement>(null)\n  const sort = sortProp === undefined ? internalSort : sortProp\n\n  const sortedRows = React.useMemo(() => {\n    if (!sort) return rows\n    const sign = sort.direction === 'asc' ? 1 : -1\n    return [...rows].sort((a, b) => sign * compareValues(a[sort.key], b[sort.key]))\n  }, [rows, sort])\n\n  const layout = React.useMemo(() => createLayout(columns, resized, pins), [columns, resized, pins])\n\n  React.useEffect(() => {\n    const element = scrollRef.current\n    if (!element) return\n    const observer = new ResizeObserver(() =>\n      setViewport((current) => ({\n        ...current,\n        height: element.clientHeight,\n        width: element.clientWidth,\n      })),\n    )\n    observer.observe(element)\n    return () => observer.disconnect()\n  }, [])\n\n  const start = Math.max(0, Math.floor(viewport.top / rowHeight) - OVERSCAN)\n  const end = Math.min(\n    sortedRows.length,\n    Math.ceil((viewport.top + viewport.height) / rowHeight) + OVERSCAN,\n  )\n  const [columnStart, columnEnd] = visibleColumns(layout, viewport.left, viewport.width)\n  const visibleRows = sortedRows.slice(start, end)\n  const paddingTop = start * rowHeight\n  const paddingBottom = (sortedRows.length - end) * rowHeight\n\n  function handleScroll(event: React.UIEvent<HTMLDivElement>) {\n    const { scrollLeft, scrollTop } = event.currentTarget\n    setViewport((current) => {\n      const sameRows = Math.floor(current.top / rowHeight) === Math.floor(scrollTop / rowHeight)\n      const [currentStart, currentEnd] = visibleColumns(layout, current.left, current.width)\n      const [nextStart, nextEnd] = visibleColumns(layout, scrollLeft, current.width)\n      return sameRows && currentStart === nextStart && currentEnd === nextEnd\n        ? current\n        : { ...current, top: scrollTop, left: scrollLeft }\n    })\n  }\n\n  function resizeTo(column: DataTableColumn<Row>, width: number) {\n    const next = Math.max(column.minWidth ?? MIN_WIDTH, Math.round(width))\n    setResized((current) =>\n      current[column.key] === next ? current : { ...current, [column.key]: next },\n    )\n    return next\n  }\n\n  function startResize(event: React.PointerEvent<HTMLButtonElement>, column: DataTableColumn<Row>) {\n    if (event.button !== 0) return\n    event.preventDefault()\n    const handle = event.currentTarget\n    const startX = event.clientX\n    const startWidth = layout.widths[column.key]\n    const direction = column.pin === 'right' ? -1 : 1\n    let width = startWidth\n\n    handle.setPointerCapture(event.pointerId)\n    setResizingKey(column.key)\n    document.body.style.cursor = 'col-resize'\n    document.body.style.userSelect = 'none'\n\n    function move(moveEvent: PointerEvent) {\n      width = resizeTo(column, startWidth + (moveEvent.clientX - startX) * direction)\n    }\n\n    function stop() {\n      handle.removeEventListener('pointermove', move)\n      handle.removeEventListener('pointerup', stop)\n      handle.removeEventListener('pointercancel', stop)\n      document.body.style.cursor = ''\n      document.body.style.userSelect = ''\n      setResizingKey(null)\n      if (width !== startWidth) onColumnResize?.(column.key, width)\n    }\n\n    handle.addEventListener('pointermove', move)\n    handle.addEventListener('pointerup', stop)\n    handle.addEventListener('pointercancel', stop)\n  }\n\n  function resizeWithKeyboard(\n    event: React.KeyboardEvent<HTMLButtonElement>,\n    column: DataTableColumn<Row>,\n  ) {\n    if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return\n    event.preventDefault()\n    const grow = (event.key === 'ArrowRight') === (column.pin !== 'right')\n    const width = resizeTo(\n      column,\n      layout.widths[column.key] + (grow ? KEYBOARD_STEP : -KEYBOARD_STEP),\n    )\n    onColumnResize?.(column.key, width)\n  }\n\n  function resetWidth(column: DataTableColumn<Row>) {\n    setResized((current) => {\n      const next = { ...current }\n      delete next[column.key]\n      return next\n    })\n    onColumnResize?.(column.key, column.width ?? DEFAULT_WIDTH)\n  }\n\n  const headerColumns: (DataTableColumn<Row> | 'before' | 'after')[] = [\n    ...layout.left,\n    'before',\n    ...layout.middle.slice(columnStart, columnEnd),\n    'after',\n    ...layout.right,\n  ]\n\n  function sortBy(key: string, direction: DataTableSort['direction']) {\n    const next = sort?.key === key && sort.direction === direction ? null : { key, direction }\n    if (sortProp === undefined) setInternalSort(next)\n    onSortChange?.(next)\n  }\n\n  function pinTo(column: DataTableColumn<Row>, side: DataTablePin | null) {\n    const next = column.pin === side ? null : side\n    setPins((current) => ({ ...current, [column.key]: next }))\n    onColumnPinChange?.(column.key, next)\n  }\n\n  return (\n    <section\n      data-slot=\"data-table\"\n      className={cn(\n        'flex min-h-0 flex-col overflow-hidden rounded-[16px] bg-(--table-header) font-sans text-sm font-[450] tracking-[-0.05px] text-muted-foreground shadow-card [--table-header:color-mix(in_oklch,var(--card),var(--foreground)_5%)]',\n        className,\n      )}\n      {...props}\n    >\n      <header className=\"flex min-h-13 shrink-0 items-center gap-2 px-5 py-3\">\n        <span className=\"flex size-7 items-center justify-center rounded-lg bg-foreground/6 text-foreground/80\">\n          <TableIcon className=\"size-4\" />\n        </span>\n        <h3 className=\"card-heading text-foreground\">{title}</h3>\n      </header>\n      <div className=\"mx-1 mb-1 flex min-h-0 flex-1 flex-col overflow-hidden rounded-[12px] bg-card shadow-card\">\n        <div\n          className={cn(\n            'min-h-0 flex-1 overflow-auto overscroll-contain bg-card [overflow-anchor:none]',\n            scrollbarClass,\n          )}\n          onScroll={handleScroll}\n          ref={scrollRef}\n        >\n          <table\n            className=\"table-fixed border-separate border-spacing-0 text-[13px] leading-5\"\n            style={{ width: layout.totalWidth }}\n          >\n            <colgroup>\n              {layout.ordered.map((column) => (\n                <col key={column.key} style={{ width: layout.widths[column.key] }} />\n              ))}\n            </colgroup>\n            <thead>\n              <tr>\n                {headerColumns.map((column) => {\n                  if (column === 'before' || column === 'after')\n                    return (\n                      <GapCell\n                        colSpan={\n                          column === 'before' ? columnStart : layout.middle.length - columnEnd\n                        }\n                        header\n                        key={column}\n                      />\n                    )\n                  const pin = cellLayout(layout, column)\n                  const canResize = resizable && column.resizable !== false\n                  const resizing = resizingKey === column.key\n                  const sortable = column.sortable !== false\n                  const canPin = pinnable && column.pinnable !== false\n                  const direction = sort?.key === column.key ? sort.direction : null\n                  const end = column.align === 'end'\n                  const Icon = column.icon\n                  const label = (\n                    <>\n                      {Icon && <Icon className=\"size-3.5 shrink-0 opacity-70\" />}\n                      <span className=\"truncate\">{column.header}</span>\n                    </>\n                  )\n                  const arrow = direction && (\n                    <AltArrowDownIcon\n                      aria-hidden=\"true\"\n                      className={cn(\n                        'size-3.5 shrink-0 transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]',\n                        direction === 'asc' && 'rotate-180',\n                      )}\n                    />\n                  )\n\n                  return (\n                    <th\n                      aria-sort={\n                        direction === 'asc'\n                          ? 'ascending'\n                          : direction === 'desc'\n                            ? 'descending'\n                            : undefined\n                      }\n                      className={cn(\n                        'group/th',\n                        headerCellClass,\n                        end ? 'text-right' : 'text-left',\n                        pin.className,\n                        column.pin && 'z-20',\n                        column.pin && canPin && (end ? 'pl-9' : 'pr-9'),\n                      )}\n                      key={column.key}\n                      scope=\"col\"\n                      style={pin.style}\n                    >\n                      {sortable || canPin ? (\n                        <Menu.Root>\n                          <Menu.Trigger\n                            className={cn(\n                              '-mx-1.5 inline-flex max-w-[calc(100%+0.75rem)] cursor-pointer items-center gap-1.5 rounded-md px-1.5 py-0.5 transition-colors duration-150 ease-out outline-none hover:bg-foreground/6 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40 data-popup-open:bg-foreground/6 data-popup-open:text-foreground',\n                              direction && 'text-foreground',\n                            )}\n                          >\n                            {end && arrow}\n                            {label}\n                            {!end && arrow}\n                          </Menu.Trigger>\n                          <Menu.Portal>\n                            <Menu.Positioner\n                              align={end ? 'end' : 'start'}\n                              className=\"z-50\"\n                              side=\"bottom\"\n                              sideOffset={6}\n                            >\n                              <Menu.Popup className=\"min-w-44 origin-(--transform-origin) rounded-[14px] bg-card p-1 font-sans text-sm font-[450] tracking-[-0.05px] text-foreground/85 shadow-card transition-[opacity,scale] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] outline-none data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-opacity\">\n                                {sortable && (\n                                  <>\n                                    <MenuOption\n                                      checked={direction === 'asc'}\n                                      icon={<ArrowUpIcon />}\n                                      onClick={() => sortBy(column.key, 'asc')}\n                                    >\n                                      Asc\n                                    </MenuOption>\n                                    <MenuOption\n                                      checked={direction === 'desc'}\n                                      icon={<ArrowDownIcon />}\n                                      onClick={() => sortBy(column.key, 'desc')}\n                                    >\n                                      Desc\n                                    </MenuOption>\n                                  </>\n                                )}\n                                {sortable && canPin && (\n                                  <Menu.Separator className=\"mx-1 my-1 h-px bg-foreground/[0.08]\" />\n                                )}\n                                {canPin && (\n                                  <>\n                                    <MenuOption\n                                      checked={column.pin === 'left'}\n                                      icon={<PinSideIcon side=\"left\" />}\n                                      onClick={() => pinTo(column, 'left')}\n                                    >\n                                      Pin to left\n                                    </MenuOption>\n                                    <MenuOption\n                                      checked={column.pin === 'right'}\n                                      icon={<PinSideIcon side=\"right\" />}\n                                      onClick={() => pinTo(column, 'right')}\n                                    >\n                                      Pin to right\n                                    </MenuOption>\n                                  </>\n                                )}\n                              </Menu.Popup>\n                            </Menu.Positioner>\n                          </Menu.Portal>\n                        </Menu.Root>\n                      ) : (\n                        <span className=\"inline-flex max-w-full items-center gap-1.5\">{label}</span>\n                      )}\n                      {column.pin && canPin && (\n                        <button\n                          aria-label={`Unpin ${column.header}`}\n                          className={cn(\n                            'absolute top-1/2 flex size-6 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md text-muted-foreground/70 transition-colors duration-150 ease-out outline-none hover:bg-foreground/6 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40',\n                            end ? 'left-3' : 'right-3',\n                          )}\n                          onClick={() => pinTo(column, null)}\n                          type=\"button\"\n                        >\n                          <PinIcon className=\"size-3.5\" />\n                        </button>\n                      )}\n                      {canResize && (\n                        <button\n                          aria-label={`Resize ${column.header}`}\n                          className={cn(\n                            \"absolute inset-y-0 z-10 flex w-2.5 cursor-col-resize touch-none justify-center outline-none before:my-2.5 before:w-0.5 before:rounded-full before:bg-foreground/25 before:opacity-0 before:transition-opacity before:duration-150 before:content-[''] group-hover/th:before:opacity-60 hover:before:opacity-100 focus-visible:before:bg-ring focus-visible:before:opacity-100\",\n                            column.pin === 'right' ? '-left-px' : '-right-px',\n                            resizing && 'before:bg-foreground/50 before:opacity-100',\n                          )}\n                          onDoubleClick={() => resetWidth(column)}\n                          onKeyDown={(event) => resizeWithKeyboard(event, column)}\n                          onPointerDown={(event) => startResize(event, column)}\n                          type=\"button\"\n                        />\n                      )}\n                    </th>\n                  )\n                })}\n              </tr>\n            </thead>\n            <tbody>\n              <SpacerRow\n                colSpan={layout.ordered.length}\n                height={paddingTop}\n                rowHeight={rowHeight}\n              />\n              {visibleRows.map((row, offset) => (\n                <DataTableRow\n                  columnEnd={columnEnd}\n                  columnStart={columnStart}\n                  key={getRowId?.(row, start + offset) ?? start + offset}\n                  layout={layout}\n                  onRowClick={onRowClick}\n                  row={row}\n                  rowHeight={rowHeight}\n                  sortKey={sort?.key}\n                />\n              ))}\n              <SpacerRow\n                colSpan={layout.ordered.length}\n                height={paddingBottom}\n                rowHeight={rowHeight}\n              />\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </section>\n  )\n}\n\nexport { DataTable }\nexport type { DataTableColumn, DataTablePin, DataTableProps, DataTableSort, DataTableValue }\n",
      "type": "registry:component",
      "target": "@components/ai/data-table.tsx"
    }
  ],
  "type": "registry:component"
}