{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "description": "A sortable, filterable, paginated table with column visibility, row selection, sticky and reorderable columns, and loading states, built on TanStack Table v9.",
  "dependencies": [
    "@tanstack/react-table",
    "lucide-react"
  ],
  "registryDependencies": [
    "table",
    "button",
    "input",
    "dropdown-menu",
    "checkbox",
    "skeleton"
  ],
  "files": [
    {
      "path": "registry/dashboardcn/ui/data-table.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  columnFilteringFeature,\n  columnOrderingFeature,\n  columnPinningFeature,\n  columnSizingFeature,\n  columnVisibilityFeature,\n  createColumnHelper,\n  createFilteredRowModel,\n  createPaginatedRowModel,\n  createSortedRowModel,\n  filterFn_includesString,\n  flexRender,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  sortFn_alphanumeric,\n  sortFn_basic,\n  sortFn_datetime,\n  sortFn_text,\n  tableFeatures,\n  useTable,\n  type Column,\n  type ColumnDef,\n  type ColumnFiltersState,\n  type ColumnOrderState,\n  type ColumnPinningState,\n  type ColumnVisibilityState,\n  type ReactTable,\n  type Row,\n  type RowData,\n  type RowSelectionState,\n  type SortingState,\n} from \"@tanstack/react-table\"\nimport {\n  ArrowDown,\n  ArrowUp,\n  ArrowUpDown,\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n  GripVertical,\n  Settings2,\n  X,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Input } from \"@/components/ui/input\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\"\n\n/**\n * TanStack Table v9 is feature-gated: only what is registered here ships in\n * the bundle. Sorting, filtering, pagination, visibility, ordering, pinning,\n * and row selection cover the usual dashboard table.\n */\nconst dataTableFeatures = tableFeatures({\n  columnFilteringFeature,\n  columnOrderingFeature,\n  columnPinningFeature,\n  columnSizingFeature,\n  columnVisibilityFeature,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  filteredRowModel: createFilteredRowModel(),\n  paginatedRowModel: createPaginatedRowModel(),\n  sortedRowModel: createSortedRowModel(),\n  filterFns: { includesString: filterFn_includesString },\n  sortFns: {\n    alphanumeric: sortFn_alphanumeric,\n    basic: sortFn_basic,\n    datetime: sortFn_datetime,\n    text: sortFn_text,\n  },\n})\n\ntype DataTableFeatures = typeof dataTableFeatures\n\ntype DataTableColumnDef<TData extends RowData> = ColumnDef<\n  DataTableFeatures,\n  TData,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  any\n>\n\n/** The table instance returned by `useDataTable`. */\ntype DataTableInstance<TData extends RowData> = ReactTable<\n  DataTableFeatures,\n  TData\n>\n\ntype DataTableColumn<TData extends RowData> = Column<\n  DataTableFeatures,\n  TData,\n  unknown\n>\n\ntype DataTableRow<TData extends RowData> = Row<DataTableFeatures, TData>\n\n/** Typed column helper bound to the data table's feature set. */\nfunction createDataTableColumnHelper<TData extends RowData>() {\n  return createColumnHelper<DataTableFeatures, TData>()\n}\n\n/** Row density presets. Only the cell padding changes. */\ntype DataTableDensity = \"compact\" | \"default\" | \"relaxed\"\n\nconst densityClasses: Record<DataTableDensity, string> = {\n  compact:\n    \"[&_[data-slot=table-head]]:h-8 [&_[data-slot=table-cell]]:py-1 [&_[data-slot=table-cell]]:text-[0.8125rem]\",\n  default: \"\",\n  relaxed: \"[&_[data-slot=table-head]]:h-12 [&_[data-slot=table-cell]]:py-3.5\",\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                    hook                                    */\n/* -------------------------------------------------------------------------- */\n\nexport interface UseDataTableOptions<TData extends RowData> {\n  columns: ReadonlyArray<DataTableColumnDef<TData>>\n  data: TData[]\n  /** Rows per page. Pass 0 to keep every row on a single page. */\n  pageSize?: number\n  initialSorting?: SortingState\n  initialColumnVisibility?: ColumnVisibilityState\n  /**\n   * Column ids held against the leading and trailing edge. Read once, on\n   * mount; use `table.setColumnPinning` to change it afterwards. Pinned\n   * columns should declare a `size` so the sticky offsets line up.\n   */\n  pinnedColumns?: Partial<ColumnPinningState>\n  /** Turn on checkbox selection, or decide per row. */\n  enableRowSelection?: boolean | ((row: DataTableRow<TData>) => boolean)\n  /** Stable row ids. Selection survives sorting and paging with this set. */\n  getRowId?: (row: TData, index: number) => string\n  onRowSelectionChange?: (selection: RowSelectionState) => void\n}\n\n/**\n * Wires the table state the components below read. Call it when you want to\n * lay the pieces out yourself; `DataTable` calls it for you.\n */\nfunction useDataTable<TData extends RowData>({\n  columns,\n  data,\n  pageSize = 10,\n  initialSorting,\n  initialColumnVisibility,\n  pinnedColumns,\n  enableRowSelection,\n  getRowId,\n  onRowSelectionChange,\n}: UseDataTableOptions<TData>): DataTableInstance<TData> {\n  const [sorting, setSorting] = React.useState<SortingState>(\n    initialSorting ?? []\n  )\n  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(\n    []\n  )\n  const [columnVisibility, setColumnVisibility] =\n    React.useState<ColumnVisibilityState>(initialColumnVisibility ?? {})\n  const [columnOrder, setColumnOrder] = React.useState<ColumnOrderState>([])\n  const [columnPinning, setColumnPinning] = React.useState<ColumnPinningState>(\n    () => ({ start: pinnedColumns?.start ?? [], end: pinnedColumns?.end ?? [] })\n  )\n  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})\n\n  // Report the selection from an effect, not from inside the state updater:\n  // the table can call the updater while it renders.\n  const notify = React.useRef(onRowSelectionChange)\n  const mounted = React.useRef(false)\n  React.useEffect(() => {\n    notify.current = onRowSelectionChange\n  })\n  React.useEffect(() => {\n    if (!mounted.current) {\n      mounted.current = true\n      return\n    }\n    notify.current?.(rowSelection)\n  }, [rowSelection])\n\n  return useTable({\n    features: dataTableFeatures,\n    data,\n    columns: columns as DataTableColumnDef<TData>[],\n    initialState: {\n      pagination: {\n        pageIndex: 0,\n        pageSize: pageSize > 0 ? pageSize : Number.MAX_SAFE_INTEGER,\n      },\n    },\n    getRowId,\n    enableRowSelection,\n    // Shift-click extends the selection from the last row you touched.\n    isRowRangeSelectionEvent: (event) =>\n      Boolean((event as { shiftKey?: boolean }).shiftKey),\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    onColumnVisibilityChange: setColumnVisibility,\n    onColumnOrderChange: setColumnOrder,\n    onColumnPinningChange: setColumnPinning,\n    onRowSelectionChange: setRowSelection,\n    state: {\n      sorting,\n      columnFilters,\n      columnVisibility,\n      columnOrder,\n      columnPinning,\n      rowSelection,\n    },\n  })\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                   preset                                   */\n/* -------------------------------------------------------------------------- */\n\nexport interface DataTableProps<TData extends RowData>\n  extends Omit<React.ComponentProps<\"div\">, \"onSelect\">,\n    UseDataTableOptions<TData> {\n  /** Column id to filter with the search input. Omit to hide the input. */\n  searchKey?: string\n  searchPlaceholder?: string\n  /** Extra toolbar content, rendered between the search input and view options. */\n  toolbar?: React.ReactNode\n  showViewOptions?: boolean\n  showPagination?: boolean\n  pageSizeOptions?: number[]\n  emptyMessage?: React.ReactNode\n  /** Swap the rows for a skeleton, e.g. on first load. */\n  loading?: boolean\n  /** Keep the rows but dim them, e.g. while a filter change is in flight. */\n  pending?: boolean\n  skeletonRows?: number\n  stickyHeader?: boolean\n  /** Caps the scroll area, e.g. 420 or \"60vh\". Pair it with stickyHeader. */\n  maxHeight?: number | string\n  /** Let columns be dragged into a new order. Pass ids to limit which ones. */\n  reorderable?: boolean | string[]\n  density?: DataTableDensity\n  onRowClick?: (row: DataTableRow<TData>) => void\n  rowClassName?: (row: DataTableRow<TData>) => string | undefined\n  /** Anything else to put on a row, e.g. a data attribute or a handler. */\n  rowProps?: (row: DataTableRow<TData>) => React.ComponentProps<\"tr\">\n  /** Wrap the row element, e.g. in a context menu trigger. */\n  renderRow?: (\n    row: DataTableRow<TData>,\n    element: React.ReactElement\n  ) => React.ReactNode\n  /** Actions shown in the bar that appears while rows are selected. */\n  selectionActions?: React.ReactNode\n}\n\n/**\n * The batteries-included table: toolbar, rows, and pagination. Every part is\n * exported on its own, so reach for `useDataTable` and compose them by hand\n * when this shape is not the one you want.\n */\nfunction DataTable<TData extends RowData>({\n  columns,\n  data,\n  pageSize = 10,\n  initialSorting,\n  initialColumnVisibility,\n  pinnedColumns,\n  enableRowSelection,\n  getRowId,\n  onRowSelectionChange,\n  searchKey,\n  searchPlaceholder = \"Filter...\",\n  toolbar,\n  showViewOptions = true,\n  showPagination = true,\n  pageSizeOptions,\n  emptyMessage = \"No results.\",\n  loading = false,\n  pending = false,\n  skeletonRows,\n  stickyHeader = false,\n  maxHeight,\n  reorderable = false,\n  density = \"default\",\n  onRowClick,\n  rowClassName,\n  rowProps,\n  renderRow,\n  selectionActions,\n  className,\n  ...props\n}: DataTableProps<TData>) {\n  const table = useDataTable({\n    columns,\n    data,\n    pageSize,\n    initialSorting,\n    initialColumnVisibility,\n    pinnedColumns,\n    enableRowSelection,\n    getRowId,\n    onRowSelectionChange,\n  })\n\n  const hasToolbar = Boolean(searchKey || toolbar || showViewOptions)\n  const selectedCount = table.getSelectedRowModel().rows.length\n\n  return (\n    <div\n      data-slot=\"data-table\"\n      className={cn(\"flex flex-col gap-4\", className)}\n      {...props}\n    >\n      {hasToolbar ? (\n        <DataTableToolbar>\n          {searchKey ? (\n            <DataTableSearch\n              table={table}\n              column={searchKey}\n              placeholder={searchPlaceholder}\n              disabled={loading}\n            />\n          ) : null}\n          {toolbar}\n          {showViewOptions ? (\n            <DataTableViewOptions\n              table={table}\n              reorderable={reorderable}\n              className=\"ml-auto\"\n              disabled={loading}\n            />\n          ) : null}\n        </DataTableToolbar>\n      ) : null}\n      {selectedCount > 0 ? (\n        <DataTableSelectionBar table={table}>\n          {selectionActions}\n        </DataTableSelectionBar>\n      ) : null}\n      <DataTableContent\n        table={table}\n        loading={loading}\n        pending={pending}\n        skeletonRows={skeletonRows ?? Math.min(pageSize || 10, 10)}\n        stickyHeader={stickyHeader}\n        maxHeight={maxHeight}\n        reorderable={reorderable}\n        density={density}\n        emptyMessage={emptyMessage}\n        onRowClick={onRowClick}\n        rowClassName={rowClassName}\n        rowProps={rowProps}\n        renderRow={renderRow}\n      />\n      {showPagination ? (\n        <DataTablePagination table={table} pageSizeOptions={pageSizeOptions} />\n      ) : null}\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                   toolbar                                  */\n/* -------------------------------------------------------------------------- */\n\n/** The row above the table. Anything can go in it. */\nfunction DataTableToolbar({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"data-table-toolbar\"\n      className={cn(\"flex flex-wrap items-center gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\ninterface DataTableSearchProps<TData extends RowData>\n  extends Omit<React.ComponentProps<typeof Input>, \"value\" | \"onChange\"> {\n  table: DataTableInstance<TData>\n  /** Column id to filter. */\n  column: string\n}\n\n/** An input bound to one column's filter. */\nfunction DataTableSearch<TData extends RowData>({\n  table,\n  column,\n  className,\n  ...props\n}: DataTableSearchProps<TData>) {\n  const target = table.getColumn(column)\n  if (!target) return null\n\n  return (\n    <Input\n      data-slot=\"data-table-search\"\n      value={(target.getFilterValue() as string) ?? \"\"}\n      onChange={(event) => target.setFilterValue(event.target.value)}\n      className={cn(\"h-8 max-w-sm\", className)}\n      {...props}\n    />\n  )\n}\n\ninterface DataTableViewOptionsProps<TData extends RowData>\n  extends React.ComponentProps<typeof Button> {\n  table: DataTableInstance<TData>\n  /** Adds \"move left / move right\" items for the reorderable columns. */\n  reorderable?: boolean | string[]\n}\n\n/** Column visibility, and column order when reordering is on. */\nfunction DataTableViewOptions<TData extends RowData>({\n  table,\n  reorderable = false,\n  className,\n  ...props\n}: DataTableViewOptionsProps<TData>) {\n  const columns = table.getAllColumns().filter((column) => column.getCanHide())\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          className={cn(\"h-8\", className)}\n          {...props}\n        >\n          <Settings2 />\n          View\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-44\">\n        <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>\n        <DropdownMenuSeparator />\n        {columns.map((column) => (\n          <DropdownMenuCheckboxItem\n            key={column.id}\n            className=\"capitalize\"\n            checked={column.getIsVisible()}\n            onCheckedChange={(value) => column.toggleVisibility(!!value)}\n          >\n            {column.id}\n          </DropdownMenuCheckboxItem>\n        ))}\n        {reorderable ? (\n          <>\n            <DropdownMenuSeparator />\n            <DropdownMenuLabel>Column order</DropdownMenuLabel>\n            <DropdownMenuItem onSelect={() => table.resetColumnOrder()}>\n              Reset to default\n            </DropdownMenuItem>\n          </>\n        ) : null}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface DataTableColumnHeaderProps<TData extends RowData, TValue>\n  extends React.ComponentProps<\"div\"> {\n  column: Column<DataTableFeatures, TData, TValue>\n  title: string\n  align?: \"left\" | \"right\"\n}\n\nfunction DataTableColumnHeader<TData extends RowData, TValue>({\n  column,\n  title,\n  align = \"left\",\n  className,\n  ...props\n}: DataTableColumnHeaderProps<TData, TValue>) {\n  if (!column.getCanSort()) {\n    return (\n      <div\n        className={cn(align === \"right\" && \"text-right\", className)}\n        {...props}\n      >\n        {title}\n      </div>\n    )\n  }\n\n  const sorted = column.getIsSorted()\n  const Icon =\n    sorted === \"asc\" ? ArrowUp : sorted === \"desc\" ? ArrowDown : ArrowUpDown\n\n  return (\n    <div\n      className={cn(\"flex\", align === \"right\" && \"justify-end\", className)}\n      {...props}\n    >\n      <Button\n        variant=\"ghost\"\n        size=\"sm\"\n        className=\"data-[sorted=true]:text-foreground -ml-2 h-8 gap-1.5 px-2\"\n        data-sorted={sorted !== false}\n        onClick={() => column.toggleSorting(sorted === \"asc\")}\n      >\n        {title}\n        <Icon className=\"text-muted-foreground size-3.5\" />\n      </Button>\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                  selection                                 */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A checkbox column. Put it first in your column list and give the table a\n * `getRowId` so the selection survives sorting and paging.\n */\nfunction createSelectionColumn<TData extends RowData>(\n  overrides?: Partial<DataTableColumnDef<TData>>\n): DataTableColumnDef<TData> {\n  return {\n    id: \"select\",\n    size: 36,\n    enableSorting: false,\n    enableHiding: false,\n    header: ({ table }) => (\n      <Checkbox\n        checked={\n          table.getIsAllPageRowsSelected()\n            ? true\n            : table.getIsSomePageRowsSelected()\n              ? \"indeterminate\"\n              : false\n        }\n        onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n        aria-label=\"Select all rows on this page\"\n      />\n    ),\n    cell: ({ row }) => (\n      <Checkbox\n        checked={row.getIsSelected()}\n        disabled={!row.getCanSelect()}\n        // The handler wants the checkbox's next value and the shift key, which\n        // is what turns a click into a range selection.\n        onClick={(event) =>\n          row.getToggleSelectedHandler()({\n            target: { checked: !row.getIsSelected() },\n            shiftKey: event.shiftKey,\n          })\n        }\n        aria-label=\"Select row\"\n      />\n    ),\n    ...overrides,\n  } as DataTableColumnDef<TData>\n}\n\ninterface DataTableSelectionBarProps<TData extends RowData>\n  extends React.ComponentProps<\"div\"> {\n  table: DataTableInstance<TData>\n}\n\n/** The bar that appears once rows are selected. Children are the actions. */\nfunction DataTableSelectionBar<TData extends RowData>({\n  table,\n  children,\n  className,\n  ...props\n}: DataTableSelectionBarProps<TData>) {\n  const count = table.getSelectedRowModel().rows.length\n\n  return (\n    <div\n      data-slot=\"data-table-selection-bar\"\n      className={cn(\n        \"bg-muted/50 flex flex-wrap items-center gap-2 rounded-md border px-3 py-2\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"text-sm tabular-nums\">\n        {count} {count === 1 ? \"row\" : \"rows\"} selected\n      </span>\n      <div className=\"ml-auto flex items-center gap-2\">\n        {children}\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"h-8\"\n          onClick={() => table.resetRowSelection()}\n        >\n          <X />\n          Clear\n        </Button>\n      </div>\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                   content                                  */\n/* -------------------------------------------------------------------------- */\n\n/** Moves `columnId` to the slot `targetId` currently occupies. */\nfunction moveColumn<TData extends RowData>(\n  table: DataTableInstance<TData>,\n  columnId: string,\n  targetId: string\n) {\n  const order = table.getAllLeafColumns().map((column) => column.id)\n  const from = order.indexOf(columnId)\n  const to = order.indexOf(targetId)\n  if (from < 0 || to < 0 || from === to) return\n  const next = order.slice()\n  next.splice(from, 1)\n  next.splice(to, 0, columnId)\n  table.setColumnOrder(next)\n}\n\n/** Nudges `columnId` one slot left (-1) or right (1). */\nfunction shiftColumn<TData extends RowData>(\n  table: DataTableInstance<TData>,\n  columnId: string,\n  delta: number\n) {\n  const order = table.getAllLeafColumns().map((column) => column.id)\n  const from = order.indexOf(columnId)\n  const to = from + delta\n  if (from < 0 || to < 0 || to >= order.length) return\n  const next = order.slice()\n  next.splice(from, 1)\n  next.splice(to, 0, columnId)\n  table.setColumnOrder(next)\n}\n\n/**\n * Sticky offsets and hairlines for a pinned cell. The feature computes the\n * regions; the renderer owns the CSS.\n */\nfunction getPinnedStyle<TData extends RowData>(\n  column: DataTableColumn<TData>,\n  { stickyHeader = false }: { stickyHeader?: boolean } = {}\n): React.CSSProperties | undefined {\n  const pinned = column.getIsPinned()\n  const shadows: string[] = []\n  if (stickyHeader) shadows.push(\"inset 0 -1px 0 0 var(--border)\")\n  if (pinned === \"start\" && column.getIsLastColumn(\"start\")) {\n    shadows.push(\"inset -1px 0 0 0 var(--border)\")\n  }\n  if (pinned === \"end\" && column.getIsFirstColumn(\"end\")) {\n    shadows.push(\"inset 1px 0 0 0 var(--border)\")\n  }\n  if (!pinned && !shadows.length) return undefined\n\n  // Auto table layout treats width as a suggestion, so pin all three: the\n  // sticky offsets are computed from `size` and have to match what renders.\n  const size = pinned ? column.getSize() : undefined\n\n  return {\n    insetInlineStart:\n      pinned === \"start\" ? `${column.getStart(\"start\")}px` : undefined,\n    insetInlineEnd: pinned === \"end\" ? `${column.getAfter(\"end\")}px` : undefined,\n    width: size,\n    minWidth: size,\n    maxWidth: size,\n    boxShadow: shadows.length ? shadows.join(\", \") : undefined,\n  }\n}\n\n/** Grip on a reorderable header. Drag it, or focus it and press an arrow. */\nfunction DataTableDragHandle<TData extends RowData>({\n  table,\n  column,\n}: {\n  table: DataTableInstance<TData>\n  column: DataTableColumn<TData>\n}) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={`Reorder ${column.id} column`}\n      className=\"text-muted-foreground/50 hover:text-foreground focus-visible:ring-ring/50 -ml-1 rounded-sm outline-none focus-visible:ring-[3px]\"\n      onKeyDown={(event) => {\n        if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return\n        event.preventDefault()\n        shiftColumn(table, column.id, event.key === \"ArrowLeft\" ? -1 : 1)\n      }}\n    >\n      <GripVertical className=\"size-3.5\" />\n    </button>\n  )\n}\n\nconst skeletonWidths = [72, 44, 58, 36, 64, 48, 52]\n\nexport interface DataTableContentProps<TData extends RowData>\n  extends React.ComponentProps<\"div\"> {\n  table: DataTableInstance<TData>\n  loading?: boolean\n  pending?: boolean\n  skeletonRows?: number\n  stickyHeader?: boolean\n  maxHeight?: number | string\n  reorderable?: boolean | string[]\n  density?: DataTableDensity\n  emptyMessage?: React.ReactNode\n  onRowClick?: (row: DataTableRow<TData>) => void\n  rowClassName?: (row: DataTableRow<TData>) => string | undefined\n  /**\n   * Anything else to put on a row: a handler, a data attribute, a title.\n   * Merged after the built-in props, so it wins.\n   */\n  rowProps?: (row: DataTableRow<TData>) => React.ComponentProps<\"tr\">\n  /**\n   * Wrap the row element, e.g. in a context menu trigger or a link. Render\n   * the element you are handed somewhere inside what you return.\n   */\n  renderRow?: (\n    row: DataTableRow<TData>,\n    element: React.ReactElement\n  ) => React.ReactNode\n}\n\n/** The bordered table itself: header, rows, and their loading states. */\nfunction DataTableContent<TData extends RowData>({\n  table,\n  loading = false,\n  pending = false,\n  skeletonRows = 8,\n  stickyHeader = false,\n  maxHeight,\n  reorderable = false,\n  density = \"default\",\n  emptyMessage = \"No results.\",\n  onRowClick,\n  rowClassName,\n  rowProps,\n  renderRow,\n  className,\n  style,\n  ...props\n}: DataTableContentProps<TData>) {\n  const [dragging, setDragging] = React.useState<string | null>(null)\n  const [dropTarget, setDropTarget] = React.useState<string | null>(null)\n\n  const { start, end } = table.state.columnPinning\n  const hasPinned = start.length > 0 || end.length > 0\n  const visibleColumns = table.getVisibleLeafColumns()\n  const rows = table.getRowModel().rows\n\n  const canReorder = (column: DataTableColumn<TData>) => {\n    if (!reorderable || column.getIsPinned()) return false\n    return Array.isArray(reorderable)\n      ? reorderable.includes(column.id)\n      : column.getCanHide()\n  }\n\n  /** Native drag and drop, so no drag library is pulled in for this. */\n  const dragProps = (columnId: string): React.ComponentProps<\"th\"> => ({\n    draggable: true,\n    onDragStart: (event) => {\n      setDragging(columnId)\n      event.dataTransfer.effectAllowed = \"move\"\n      event.dataTransfer.setData(\"text/plain\", columnId)\n    },\n    onDragOver: (event) => {\n      if (!dragging || dragging === columnId) return\n      event.preventDefault()\n      event.dataTransfer.dropEffect = \"move\"\n      setDropTarget(columnId)\n    },\n    onDragLeave: () =>\n      setDropTarget((current) => (current === columnId ? null : current)),\n    onDrop: (event) => {\n      event.preventDefault()\n      if (dragging) moveColumn(table, dragging, columnId)\n      setDragging(null)\n      setDropTarget(null)\n    },\n    onDragEnd: () => {\n      setDragging(null)\n      setDropTarget(null)\n    },\n  })\n\n  // Pinned cells are painted opaque so rows scroll under them, which would\n  // otherwise hide the row's own hover and selected colours. Move both onto\n  // the cells so pinned and unpinned columns stay in step. They have to stay\n  // opaque too: a translucent hover on a pinned cell lets the columns passing\n  // underneath show through, so the row hover is `bg-muted/50` pre-mixed over\n  // the background rather than blended with whatever is behind the cell.\n  const rowClasses = cn(\n    \"group/row\",\n    hasPinned && \"hover:bg-transparent data-[state=selected]:bg-transparent\"\n  )\n  const cellClasses = cn(\n    hasPinned && [\n      \"group-hover/row:bg-[color-mix(in_oklab,var(--muted)_50%,var(--background))]\",\n      \"group-data-[state=selected]/row:bg-muted\",\n    ]\n  )\n\n  return (\n    <div\n      data-slot=\"data-table-content\"\n      aria-busy={loading || pending || undefined}\n      className={cn(\n        \"relative overflow-hidden rounded-md border\",\n        \"[&>[data-slot=table-container]]:[scrollbar-width:thin] [&>[data-slot=table-container]]:[scrollbar-color:color-mix(in_oklab,var(--foreground)_20%,transparent)_transparent]\",\n        maxHeight !== undefined &&\n          \"[&>[data-slot=table-container]]:max-h-[var(--data-table-max-height)]\",\n        densityClasses[density],\n        className\n      )}\n      style={\n        maxHeight !== undefined\n          ? ({\n              ...style,\n              \"--data-table-max-height\":\n                typeof maxHeight === \"number\" ? `${maxHeight}px` : maxHeight,\n            } as React.CSSProperties)\n          : style\n      }\n      {...props}\n    >\n      {pending ? (\n        <div\n          aria-hidden\n          className=\"bg-primary/60 absolute inset-x-0 top-0 z-40 h-0.5 animate-pulse\"\n        />\n      ) : null}\n      <Table>\n        <TableHeader className={cn(stickyHeader && \"[&_tr]:border-b-0\")}>\n          {table.getHeaderGroups().map((headerGroup) => (\n            <TableRow\n              key={headerGroup.id}\n              className={cn(rowClasses, \"hover:bg-transparent\")}\n            >\n              {headerGroup.headers.map((header) => {\n                const column = header.column as DataTableColumn<TData>\n                const pinned = column.getIsPinned()\n                const draggable = canReorder(column)\n\n                return (\n                  <TableHead\n                    key={header.id}\n                    colSpan={header.colSpan}\n                    data-pinned={pinned || undefined}\n                    data-dragging={dragging === column.id || undefined}\n                    data-drop-target={dropTarget === column.id || undefined}\n                    {...(draggable ? dragProps(column.id) : null)}\n                    className={cn(\n                      (stickyHeader || pinned) && \"bg-background\",\n                      stickyHeader && \"sticky top-0 z-20\",\n                      pinned && \"sticky z-20 overflow-hidden\",\n                      pinned && stickyHeader && \"z-30\",\n                      draggable && \"cursor-grab data-[dragging]:opacity-40\",\n                      \"data-[drop-target]:bg-muted\"\n                    )}\n                    style={getPinnedStyle(column, { stickyHeader })}\n                  >\n                    {header.isPlaceholder ? null : draggable ? (\n                      <div className=\"flex items-center gap-1\">\n                        <DataTableDragHandle table={table} column={column} />\n                        {flexRender(\n                          column.columnDef.header,\n                          header.getContext()\n                        )}\n                      </div>\n                    ) : (\n                      flexRender(column.columnDef.header, header.getContext())\n                    )}\n                  </TableHead>\n                )\n              })}\n            </TableRow>\n          ))}\n        </TableHeader>\n        <TableBody\n          className={cn(\n            pending && \"pointer-events-none opacity-50\",\n            \"transition-opacity\"\n          )}\n        >\n          {loading ? (\n            <DataTableSkeletonRows\n              columns={visibleColumns.length}\n              rows={skeletonRows}\n            />\n          ) : rows.length ? (\n            rows.map((row) => {\n              const extra = rowProps?.(row)\n              const element = (\n                <TableRow\n                  data-state={row.getIsSelected() ? \"selected\" : undefined}\n                  onClick={onRowClick ? () => onRowClick(row) : undefined}\n                  {...extra}\n                  className={cn(\n                    rowClasses,\n                    onRowClick && \"cursor-pointer\",\n                    rowClassName?.(row),\n                    extra?.className\n                  )}\n                >\n                  {row.getVisibleCells().map((cell) => {\n                    const column = cell.column as DataTableColumn<TData>\n                    const pinned = column.getIsPinned()\n\n                    return (\n                      <TableCell\n                        key={cell.id}\n                        data-pinned={pinned || undefined}\n                        className={cn(\n                          cellClasses,\n                          pinned && \"bg-background sticky z-20 overflow-hidden\"\n                        )}\n                        style={getPinnedStyle(column)}\n                      >\n                        {flexRender(column.columnDef.cell, cell.getContext())}\n                      </TableCell>\n                    )\n                  })}\n                </TableRow>\n              )\n\n              return (\n                <React.Fragment key={row.id}>\n                  {renderRow ? renderRow(row, element) : element}\n                </React.Fragment>\n              )\n            })\n          ) : (\n            <TableRow className=\"hover:bg-transparent\">\n              <TableCell\n                colSpan={visibleColumns.length}\n                className=\"text-muted-foreground h-24 text-center\"\n              >\n                {emptyMessage}\n              </TableCell>\n            </TableRow>\n          )}\n        </TableBody>\n      </Table>\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                  skeletons                                 */\n/* -------------------------------------------------------------------------- */\n\nfunction DataTableSkeletonRows({\n  columns,\n  rows,\n}: {\n  columns: number\n  rows: number\n}) {\n  return (\n    <>\n      {Array.from({ length: rows }).map((_, rowIndex) => (\n        <TableRow key={rowIndex} className=\"hover:bg-transparent\">\n          {Array.from({ length: columns }).map((__, columnIndex) => (\n            <TableCell key={columnIndex}>\n              <Skeleton\n                className=\"h-4\"\n                style={{\n                  width: `${\n                    skeletonWidths[\n                      (rowIndex + columnIndex) % skeletonWidths.length\n                    ]\n                  }%`,\n                }}\n              />\n            </TableCell>\n          ))}\n        </TableRow>\n      ))}\n    </>\n  )\n}\n\nexport interface DataTableSkeletonProps extends React.ComponentProps<\"div\"> {\n  columns: number\n  rows?: number\n  showToolbar?: boolean\n  showPagination?: boolean\n}\n\n/**\n * A standalone placeholder for when the columns are not known yet, e.g. a\n * Suspense fallback. Once you have a table instance, `loading` on\n * `DataTableContent` renders the same rows under the real header.\n */\nfunction DataTableSkeleton({\n  columns,\n  rows = 8,\n  showToolbar = true,\n  showPagination = true,\n  className,\n  ...props\n}: DataTableSkeletonProps) {\n  return (\n    <div\n      data-slot=\"data-table-skeleton\"\n      aria-busy\n      className={cn(\"flex flex-col gap-4\", className)}\n      {...props}\n    >\n      {showToolbar ? (\n        <div className=\"flex items-center gap-2\">\n          <Skeleton className=\"h-8 w-full max-w-sm\" />\n          <Skeleton className=\"ml-auto h-8 w-20\" />\n        </div>\n      ) : null}\n      <div className=\"overflow-hidden rounded-md border\">\n        <Table>\n          <TableHeader>\n            <TableRow className=\"hover:bg-transparent\">\n              {Array.from({ length: columns }).map((_, index) => (\n                <TableHead key={index}>\n                  <Skeleton className=\"h-3.5 w-16\" />\n                </TableHead>\n              ))}\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <DataTableSkeletonRows columns={columns} rows={rows} />\n          </TableBody>\n        </Table>\n      </div>\n      {showPagination ? (\n        <div className=\"flex items-center justify-between gap-4\">\n          <Skeleton className=\"h-5 w-28\" />\n          <Skeleton className=\"h-8 w-48\" />\n        </div>\n      ) : null}\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                 pagination                                 */\n/* -------------------------------------------------------------------------- */\n\ninterface DataTablePaginationProps<TData extends RowData>\n  extends React.ComponentProps<\"div\"> {\n  table: DataTableInstance<TData>\n  /** Adds a rows-per-page menu. */\n  pageSizeOptions?: number[]\n}\n\nfunction DataTablePagination<TData extends RowData>({\n  table,\n  pageSizeOptions,\n  className,\n  ...props\n}: DataTablePaginationProps<TData>) {\n  const { pageIndex, pageSize } = table.state.pagination\n  const total = table.getFilteredRowModel().rows.length\n  const from = total === 0 ? 0 : pageIndex * pageSize + 1\n  const to = Math.min((pageIndex + 1) * pageSize, total)\n\n  return (\n    <div\n      data-slot=\"data-table-pagination\"\n      className={cn(\"flex flex-wrap items-center justify-between gap-4\", className)}\n      {...props}\n    >\n      <div className=\"flex items-center gap-3\">\n        <p className=\"text-muted-foreground text-sm tabular-nums\">\n          {from}–{to} of {total}\n        </p>\n        {pageSizeOptions?.length ? (\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button variant=\"ghost\" size=\"sm\" className=\"h-8 tabular-nums\">\n                {pageSize} per page\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"start\" className=\"w-36\">\n              {pageSizeOptions.map((option) => (\n                <DropdownMenuCheckboxItem\n                  key={option}\n                  checked={option === pageSize}\n                  onCheckedChange={() => table.setPageSize(option)}\n                  className=\"tabular-nums\"\n                >\n                  {option} per page\n                </DropdownMenuCheckboxItem>\n              ))}\n            </DropdownMenuContent>\n          </DropdownMenu>\n        ) : null}\n      </div>\n      <div className=\"flex items-center gap-1\">\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          className=\"size-8\"\n          onClick={() => table.firstPage()}\n          disabled={!table.getCanPreviousPage()}\n          aria-label=\"First page\"\n        >\n          <ChevronsLeft />\n        </Button>\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          className=\"size-8\"\n          onClick={() => table.previousPage()}\n          disabled={!table.getCanPreviousPage()}\n          aria-label=\"Previous page\"\n        >\n          <ChevronLeft />\n        </Button>\n        <span className=\"text-muted-foreground px-2 text-sm tabular-nums\">\n          Page {pageIndex + 1} of {Math.max(table.getPageCount(), 1)}\n        </span>\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          className=\"size-8\"\n          onClick={() => table.nextPage()}\n          disabled={!table.getCanNextPage()}\n          aria-label=\"Next page\"\n        >\n          <ChevronRight />\n        </Button>\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          className=\"size-8\"\n          onClick={() => table.lastPage()}\n          disabled={!table.getCanNextPage()}\n          aria-label=\"Last page\"\n        >\n          <ChevronsRight />\n        </Button>\n      </div>\n    </div>\n  )\n}\n\nexport {\n  DataTable,\n  DataTableColumnHeader,\n  DataTableContent,\n  DataTablePagination,\n  DataTableSearch,\n  DataTableSelectionBar,\n  DataTableSkeleton,\n  DataTableToolbar,\n  DataTableViewOptions,\n  createDataTableColumnHelper,\n  createSelectionColumn,\n  dataTableFeatures,\n  moveColumn,\n  shiftColumn,\n  useDataTable,\n  type DataTableColumnDef,\n  type DataTableDensity,\n  type DataTableFeatures,\n  type DataTableInstance,\n  type DataTableRow,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}