{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "description": "Renders a TanStack Table as a Table, virtualized, with sorting, selection, an action bar, pagination, column visibility and TanStack Query states.",
  "dependencies": [
    "@hugeicons/core-free-icons",
    "@hugeicons/react",
    "@stylexjs/stylex",
    "@tanstack/react-query",
    "@tanstack/react-table",
    "@tanstack/react-virtual"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json",
    "https://constella.aayush.cv/r/button.json",
    "https://constella.aayush.cv/r/checkbox.json",
    "https://constella.aayush.cv/r/dropdown-menu.json",
    "https://constella.aayush.cv/r/select.json",
    "https://constella.aayush.cv/r/skeleton.json",
    "https://constella.aayush.cv/r/swap-icon.json",
    "https://constella.aayush.cv/r/table.json"
  ],
  "files": [
    {
      "path": "src/components/tanstack/data-table.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowDown01Icon,\n  ArrowUp01Icon,\n  UnfoldMoreIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { UseQueryResult } from \"@tanstack/react-query\";\nimport type {\n  ReactTable,\n  Row,\n  RowData,\n  TableFeatures,\n} from \"@tanstack/react-table\";\nimport { useRef } from \"react\";\nimport type { MouseEvent, ReactNode, RefObject } from \"react\";\n\nimport { Button } from \"@/components/primitives/button\";\nimport { Skeleton } from \"@/components/primitives/skeleton\";\nimport { SwapIcon, SwapIconItem } from \"@/components/primitives/swap-icon\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/primitives/table\";\nimport type { TableSize } from \"@/components/primitives/table\";\nimport {\n  isSelectable,\n  isSelecting,\n  useDragSelect,\n} from \"@/components/tanstack/data-table-selection\";\nimport { useDataTableKeys } from \"@/components/tanstack/use-data-table-keys\";\nimport { useDataTableVirtualizer } from \"@/components/tanstack/use-data-table-virtualizer\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport {\n  colors,\n  durations,\n  lineHeights,\n  opacities,\n  sizes,\n  space,\n  strokes,\n} from \"@/lib/constella/tokens.stylex\";\n\ntype DataTableQuery = Pick<\n  UseQueryResult,\n  \"isError\" | \"isFetching\" | \"isPending\" | \"isPlaceholderData\"\n>;\n\ninterface DataTableProps<\n  TFeatures extends TableFeatures,\n  TData extends RowData,\n  TSelected,\n> {\n  /** The row open elsewhere, like in a Side Panel: marked current and filled a step above a selected one. */\n  activeRowId?: string;\n  /** Keyed by column id: a column's width, on its header and cells. A column aligns to its end, like figures, with `meta: { align: \"end\" }`, and loads as a checkbox with `meta: { skeleton: \"checkbox\" }`. */\n  columnSx?: Readonly<Partial<Record<string, Sx>>>;\n  /** Shown in one full-width row when no row is left. */\n  empty: ReactNode;\n  /** Shown in place of `empty` when `query` failed and no row is left. */\n  error?: ReactNode;\n  /** Names the table for screen readers. */\n  label: string;\n  /** A click on the row, outside its controls; with Cmd, Ctrl or Shift it selects instead. Put a link or button in the row too, for the keyboard. */\n  onRowClick?: (row: Row<TFeatures, TData>) => void;\n  /** The `useQuery` result the data comes from: skeleton rows while pending, faded while a new page loads over the old one. */\n  query?: DataTableQuery;\n  /** A guess the virtualizer corrects by measuring each row it renders. */\n  rowHeight?: number;\n  /** The element the table scrolls in, when it isn't the window, like an app's main area. */\n  scrollRef?: RefObject<HTMLElement | null>;\n  size?: TableSize;\n  sx?: Sx;\n  table: ReactTable<TFeatures, TData, TSelected>;\n}\n\n// What the sorting feature adds to a column; typed by shape, so any feature set fits.\ninterface SortableColumn {\n  getCanSort: () => boolean;\n  getIsSorted: () => false | \"asc\" | \"desc\";\n  getToggleSortingHandler: () =>\n    | undefined\n    | ((event: MouseEvent<HTMLButtonElement>) => void);\n}\n\n// A column's `meta`, read by shape, as TanStack leaves its type to the table.\ninterface MetaColumn {\n  columnDef: { meta?: object };\n}\n\nconst alignsEnd = ({ columnDef: { meta } }: MetaColumn) =>\n  meta !== undefined && \"align\" in meta && meta.align === \"end\";\n\nconst loadsAsCheckbox = ({ columnDef: { meta } }: MetaColumn) =>\n  meta !== undefined && \"skeleton\" in meta && meta.skeleton === \"checkbox\";\n\n// What the column sizing feature adds to a column.\ninterface SizedColumn {\n  columnDef: { size?: number };\n  getSize: () => number;\n}\n\nconst isSized = <TColumn extends { id: string }>(\n  column: TColumn\n): column is TColumn & SizedColumn => \"getSize\" in column;\n\n// The column visibility feature's table; without it every column shows.\nconst hasVisibility = <TTable extends { getAllLeafColumns: () => unknown[] }>(\n  table: TTable\n): table is TTable & { getVisibleLeafColumns: TTable[\"getAllLeafColumns\"] } =>\n  \"getVisibleLeafColumns\" in table;\n\nconst styles = stylex.create({\n  // Fixed, as only the rows on screen render.\n  fixed: {\n    tableLayout: \"fixed\",\n  },\n  pad: (height: number) => ({ height }),\n  width: (width: number) => ({ width }),\n  body: {\n    opacity: 1,\n    transitionDuration: durations.hover,\n    transitionProperty: \"opacity\",\n    transitionTimingFunction: \"ease\",\n  },\n  busy: {\n    opacity: opacities.busy,\n  },\n  // Inline, so a column's alignment places it; its margins fill the text's line, so a loading row is as tall as a loaded one.\n  skeleton: {\n    display: \"inline-block\",\n    height: sizes.iconSm,\n    marginBlock: `calc((${lineHeights.textSm} - ${sizes.iconSm}) / 2)`,\n    verticalAlign: \"top\",\n    width: \"60%\",\n  },\n  skeletonCheckbox: {\n    height: sizes.icon,\n    marginBlock: `calc((${lineHeights.textSm} - ${sizes.icon}) / 2)`,\n    width: sizes.icon,\n  },\n  clickable: {\n    cursor: \"pointer\",\n  },\n  // Fixed columns don't grow to fit, so text that overflows one ends in an ellipsis; a checkbox keeps its hit area.\n  cell: {\n    overflow: { default: \"hidden\", \":has([data-slot='checkbox'])\": \"visible\" },\n    textOverflow: \"ellipsis\",\n  },\n  empty: {\n    color: colors.textSecondary,\n    height: sizes.media,\n    textAlign: \"center\",\n  },\n  // Lines its label up with the cells under it, despite the button's padding and border.\n  sort: {\n    marginInlineStart: `calc(-1 * (${space.popup} + ${strokes.border}))`,\n  },\n  sortEnd: {\n    marginInlineEnd: `calc(-1 * (${space.popup} + ${strokes.border}))`,\n  },\n  sortIcon: {\n    color: colors.textMuted,\n  },\n  end: {\n    textAlign: \"end\",\n  },\n});\n\nconst noColumns = {};\n\nconst loadingRows = Array.from({ length: 5 }, (_, index) => index);\n\n// Only a column that sets `size` gets a width; the rest share what's left.\nconst widthOf = (column: { id: string }) =>\n  isSized(column) && column.columnDef.size !== undefined\n    ? styles.width(column.getSize())\n    : null;\n\n/** Renders a TanStack Table you built with `useTable`, virtualizing its rows against the window or `scrollRef`. */\nfunction DataTable<\n  TFeatures extends TableFeatures,\n  TData extends RowData,\n  TSelected,\n>({\n  activeRowId,\n  columnSx = noColumns,\n  empty,\n  error = \"Couldn't load the data.\",\n  label,\n  onRowClick,\n  query,\n  rowHeight = 41,\n  scrollRef,\n  size,\n  sx,\n  table,\n}: DataTableProps<TFeatures, TData, TSelected>) {\n  const body = useRef<HTMLTableSectionElement>(null);\n  const { rows } = table.getRowModel();\n  const { handlePointerDown, handlePointerOver } = useDragSelect(rows);\n  const { items, padAfter, padBefore, virtualizer } = useDataTableVirtualizer({\n    body,\n    rowHeight,\n    rows,\n    scrollRef,\n  });\n  const { handleKeyDown, rowProps } = useDataTableKeys({\n    activeRowId,\n    body,\n    enabled: onRowClick !== undefined || isSelectable(table),\n    items,\n    onRowClick,\n    rows,\n    scrollToIndex: (index) => {\n      virtualizer.scrollToIndex(index);\n    },\n  });\n\n  const columns = hasVisibility(table)\n    ? table.getVisibleLeafColumns()\n    : table.getAllLeafColumns();\n  const pending = query?.isPending === true;\n  // The old page stays on screen while the next loads (`placeholderData: keepPreviousData`).\n  const stale = query?.isFetching === true && query.isPlaceholderData;\n\n  return (\n    <Table\n      aria-busy={pending || stale}\n      aria-label={label}\n      aria-rowcount={rows.length + 1}\n      data-selecting={isSelecting(table) ? \"\" : undefined}\n      data-slot=\"data-table\"\n      size={size}\n      sx={[styles.fixed, sx]}\n    >\n      <TableHeader>\n        {table.getHeaderGroups().map((group) => (\n          <TableRow key={group.id}>\n            {group.headers.map((header) => (\n              <TableHead\n                key={header.id}\n                sx={[\n                  styles.cell,\n                  widthOf(header.column),\n                  alignsEnd(header.column) && styles.end,\n                  columnSx[header.column.id],\n                ]}\n              >\n                {header.isPlaceholder ? null : (\n                  <table.FlexRender header={header} />\n                )}\n              </TableHead>\n            ))}\n          </TableRow>\n        ))}\n      </TableHeader>\n      <TableBody\n        onKeyDown={handleKeyDown}\n        onPointerDown={handlePointerDown}\n        onPointerOver={handlePointerOver}\n        ref={body}\n        sx={[styles.body, stale && styles.busy]}\n      >\n        {pending &&\n          loadingRows.map((index) => (\n            <TableRow key={index}>\n              {columns.map((column) => (\n                <TableCell\n                  key={column.id}\n                  sx={[alignsEnd(column) && styles.end, columnSx[column.id]]}\n                >\n                  <Skeleton\n                    sx={[\n                      styles.skeleton,\n                      loadsAsCheckbox(column) && styles.skeletonCheckbox,\n                    ]}\n                  />\n                </TableCell>\n              ))}\n            </TableRow>\n          ))}\n        {padBefore > 0 && (\n          <tr aria-hidden {...stylex.props(styles.pad(padBefore))} />\n        )}\n        {items.map((item) => {\n          const row = rows[item.index];\n          if (row === undefined) {\n            return null;\n          }\n          return (\n            <TableRow\n              aria-rowindex={item.index + 2}\n              data-index={item.index}\n              key={row.id}\n              ref={virtualizer.measureElement}\n              {...rowProps(item.index, row)}\n              sx={onRowClick && styles.clickable}\n            >\n              {(\"getVisibleCells\" in row\n                ? row.getVisibleCells()\n                : row.getAllCells()\n              ).map((cell) => (\n                <TableCell\n                  key={cell.id}\n                  sx={[\n                    styles.cell,\n                    alignsEnd(cell.column) && styles.end,\n                    columnSx[cell.column.id],\n                  ]}\n                >\n                  <table.FlexRender cell={cell} />\n                </TableCell>\n              ))}\n            </TableRow>\n          );\n        })}\n        {padAfter > 0 && (\n          <tr aria-hidden {...stylex.props(styles.pad(padAfter))} />\n        )}\n        {rows.length === 0 && !pending && (\n          <TableRow>\n            <TableCell colSpan={columns.length} sx={styles.empty}>\n              {query?.isError === true ? error : empty}\n            </TableCell>\n          </TableRow>\n        )}\n      </TableBody>\n    </Table>\n  );\n}\n\nconst sortIcons = {\n  asc: ArrowUp01Icon,\n  desc: ArrowDown01Icon,\n  none: UnfoldMoreIcon,\n};\n\n/** A header that sorts its column (needs `rowSortingFeature`); plain text if it can't sort. In a column aligned to its end, the icon leads, so the label ends over the cells. */\nfunction DataTableColumnHeader({\n  column,\n  sx,\n  title,\n}: {\n  column: MetaColumn & SortableColumn;\n  sx?: Sx;\n  title: string;\n}) {\n  if (!column.getCanSort()) {\n    return (\n      <span data-slot=\"data-table-column-header\" {...stylex.props(sx)}>\n        {title}\n      </span>\n    );\n  }\n  const sorted = column.getIsSorted();\n  const end = alignsEnd(column);\n  const sortIcon = (\n    <SwapIcon\n      aria-hidden\n      sx={styles.sortIcon}\n      value={sorted === false ? \"none\" : sorted}\n    >\n      {Object.entries(sortIcons).map(([value, icon]) => (\n        <SwapIconItem key={value} value={value}>\n          <HugeiconsIcon\n            icon={icon}\n            size={sizes.iconSm}\n            strokeWidth={Number(strokes.icon)}\n          />\n        </SwapIconItem>\n      ))}\n    </SwapIcon>\n  );\n  return (\n    <Button\n      data-slot=\"data-table-column-header\"\n      onClick={column.getToggleSortingHandler()}\n      size=\"sm\"\n      sx={[end ? styles.sortEnd : styles.sort, sx]}\n      variant=\"ghost\"\n    >\n      {end && sortIcon}\n      {title}\n      {!end && sortIcon}\n    </Button>\n  );\n}\n\nexport { DataTable, DataTableColumnHeader };\nexport { DataTableActionBar } from \"@/components/tanstack/data-table-action-bar\";\nexport { DataTablePagination } from \"@/components/tanstack/data-table-pagination\";\nexport {\n  DataTableSelectAll,\n  DataTableSelectRow,\n} from \"@/components/tanstack/data-table-selection\";\nexport { DataTableViewOptions } from \"@/components/tanstack/data-table-view-options\";\nexport type { DataTableProps };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/tanstack/data-table-action-bar.tsx",
      "content": "\"use client\";\n\nimport * as stylex from \"@stylexjs/stylex\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { Button } from \"@/components/primitives/button\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { typeStyles, visuallyHidden } from \"@/lib/constella/styles\";\nimport {\n  colors,\n  durations,\n  easings,\n  fontWeights,\n  layers,\n  media,\n  motion,\n  radii,\n  shadows,\n  space,\n} from \"@/lib/constella/tokens.stylex\";\n\n// What the row selection feature adds to a table.\ninterface ActionBarTable {\n  getSelectedRowIds: () => string[];\n  resetRowSelection: (defaultState?: boolean) => void;\n}\n\nconst closed = \":is([data-closed])\";\n// A key in the table, or in the bar itself, moved it: the keyboard never animates.\nconst byKeyboard =\n  \":is(:root:has([data-slot='data-table'] :focus-visible) *, :has(:focus-visible))\";\n\nconst styles = stylex.create({\n  // Centered by its auto margins between both edges, so the transform is free for the motion.\n  bar: {\n    alignItems: \"center\",\n    backgroundColor: colors.raised,\n    borderStartStartRadius: radii.md,\n    borderStartEndRadius: radii.md,\n    borderEndStartRadius: radii.md,\n    borderEndEndRadius: radii.md,\n    boxShadow: shadows.lg,\n    color: colors.textPrimary,\n    display: \"flex\",\n    flexWrap: \"wrap\",\n    gap: space.xxs,\n    insetBlockEnd: space.lg,\n    insetInlineEnd: 0,\n    insetInlineStart: 0,\n    justifyContent: \"center\",\n    marginInlineEnd: \"auto\",\n    marginInlineStart: \"auto\",\n    maxWidth: `calc(100% - 2 * ${space.md})`,\n    paddingBlockEnd: space.xxs,\n    paddingBlockStart: space.xxs,\n    paddingInlineEnd: space.xxs,\n    paddingInlineStart: space.xxs,\n    position: \"fixed\",\n    width: \"fit-content\",\n    zIndex: layers.bar,\n  },\n  motion: {\n    opacity: { default: 1, [closed]: 0 },\n    transform: {\n      default: \"none\",\n      [closed]: {\n        default: `translateY(${motion.exitOffset}) scale(${motion.popoverScale})`,\n        [media.reducedMotion]: \"none\",\n      },\n    },\n    transformOrigin: \"bottom center\",\n    transitionDuration: {\n      default: durations.popover,\n      [closed]: durations.popoverExit,\n      [byKeyboard]: \"0s\",\n    },\n    transitionProperty: \"opacity, transform, visibility\",\n    transitionTimingFunction: easings.out,\n    // Hidden once it has faded, so a bar that has left is out of the tab order.\n    visibility: { default: \"visible\", [closed]: \"hidden\" },\n  },\n  count: {\n    fontVariantNumeric: \"tabular-nums\",\n    fontWeight: fontWeights.medium,\n    paddingInlineEnd: space.popup,\n    paddingInlineStart: space.popup,\n    whiteSpace: \"nowrap\",\n  },\n});\n\n/** A bar of actions for the selected rows (needs `rowSelectionFeature`), floating at the bottom of the window while any row is selected: the count, `children`, then a Clear button. */\nfunction DataTableActionBar({\n  children,\n  label,\n  sx,\n  table,\n}: {\n  /** The actions, such as Buttons or a Dropdown Menu trigger. */\n  children?: ReactNode;\n  /** Names the bar for screen readers. */\n  label: string;\n  sx?: Sx;\n  table: ActionBarTable;\n}) {\n  const count = table.getSelectedRowIds().length;\n  // The last count stays on screen while the bar leaves.\n  const [held, setHeld] = useState(count);\n  if (count > 0 && count !== held) {\n    setHeld(count);\n  }\n  return (\n    <>\n      <div\n        aria-label={label}\n        data-closed={count === 0 ? \"\" : undefined}\n        data-slot=\"data-table-action-bar\"\n        role=\"toolbar\"\n        {...stylex.props([typeStyles.textSm, styles.bar], styles.motion, sx)}\n      >\n        <span {...stylex.props(styles.count)}>\n          {count > 0 ? count : held} selected\n        </span>\n        {children}\n        <Button\n          aria-label=\"Clear selection\"\n          corners=\"rounded\"\n          onClick={() => {\n            table.resetRowSelection(true);\n          }}\n          size=\"sm\"\n          variant=\"ghost\"\n        >\n          Clear\n        </Button>\n      </div>\n      {/* Outside the bar, which is hidden while empty: a hidden region isn't announced. */}\n      <output aria-live=\"polite\" {...stylex.props(visuallyHidden.base)}>\n        {count > 0 && `${count} selected`}\n      </output>\n    </>\n  );\n}\n\nexport { DataTableActionBar };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/tanstack/data-table-pagination.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowLeft01Icon,\n  ArrowLeftDoubleIcon,\n  ArrowRight01Icon,\n  ArrowRightDoubleIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { useId } from \"react\";\n\nimport { Button } from \"@/components/primitives/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/primitives/select\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { typeStyles } from \"@/lib/constella/styles\";\nimport { colors, sizes, space, strokes } from \"@/lib/constella/tokens.stylex\";\n\n// What the pagination feature adds to a table.\ninterface PaginatedTable {\n  getCanNextPage: () => boolean;\n  getCanPreviousPage: () => boolean;\n  getPageCount: () => number;\n  getRowCount: () => number;\n  nextPage: () => void;\n  previousPage: () => void;\n  setPageIndex: (index: number) => void;\n  setPageSize: (size: number) => void;\n  state: { pagination: { pageIndex: number; pageSize: number } };\n}\n\nconst defaultPageSizes = [10, 20, 50, 100];\n\nconst styles = stylex.create({\n  pagination: {\n    alignItems: \"center\",\n    columnGap: space.md,\n    display: \"flex\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n    rowGap: space.xs,\n  },\n  range: {\n    color: colors.textSecondary,\n    fontVariantNumeric: \"tabular-nums\",\n  },\n  pageControls: {\n    alignItems: \"center\",\n    columnGap: space.md,\n    display: \"flex\",\n    flexWrap: \"wrap\",\n    rowGap: space.xs,\n  },\n  pageSize: {\n    alignItems: \"center\",\n    color: colors.textSecondary,\n    display: \"flex\",\n    gap: space.xs,\n  },\n  pageButtons: {\n    display: \"flex\",\n    gap: space.xxs,\n  },\n});\n\nfunction PageButton({\n  disabled,\n  icon,\n  label,\n  onClick,\n}: {\n  disabled: boolean;\n  icon: typeof ArrowLeft01Icon;\n  label: string;\n  onClick: () => void;\n}) {\n  return (\n    <Button\n      aria-label={label}\n      disabled={disabled}\n      onClick={onClick}\n      size=\"icon-sm\"\n      variant=\"outline\"\n    >\n      <HugeiconsIcon\n        aria-hidden\n        data-rtl-flip\n        icon={icon}\n        size={sizes.icon}\n        strokeWidth={Number(strokes.icon)}\n      />\n    </Button>\n  );\n}\n\n/** Pages through the rows (needs `rowPaginationFeature`, and `paginatedRowModel` unless `manualPagination`): the range shown, rows per page, and first, previous, next and last. */\nfunction DataTablePagination({\n  pageSizes = defaultPageSizes,\n  sx,\n  table,\n}: {\n  /** The rows per page to offer. */\n  pageSizes?: readonly number[];\n  sx?: Sx;\n  table: PaginatedTable;\n}) {\n  const rowsLabel = useId();\n  const { pageIndex, pageSize } = table.state.pagination;\n  const count = table.getRowCount();\n  if (count === 0) {\n    return null;\n  }\n  const first = pageIndex * pageSize + 1;\n  const last = Math.min(first + pageSize - 1, count);\n  return (\n    <div\n      data-slot=\"data-table-pagination\"\n      {...stylex.props(styles.pagination, sx)}\n    >\n      <div\n        aria-live=\"polite\"\n        {...stylex.props([typeStyles.textSm, styles.range])}\n      >\n        {first}–{last} of {count}\n      </div>\n      <div {...stylex.props(styles.pageControls)}>\n        <div {...stylex.props([typeStyles.textSm, styles.pageSize])}>\n          <span id={rowsLabel}>Rows per page</span>\n          <Select\n            onValueChange={(value) => {\n              if (value !== null) {\n                table.setPageSize(value);\n              }\n            }}\n            value={pageSize}\n          >\n            <SelectTrigger aria-labelledby={rowsLabel} size=\"sm\">\n              <SelectValue />\n            </SelectTrigger>\n            <SelectContent>\n              {pageSizes.map((option) => (\n                <SelectItem key={option} value={option}>\n                  {option}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n        <div {...stylex.props(styles.pageButtons)}>\n          <PageButton\n            disabled={!table.getCanPreviousPage()}\n            icon={ArrowLeftDoubleIcon}\n            label=\"First page\"\n            onClick={() => {\n              table.setPageIndex(0);\n            }}\n          />\n          <PageButton\n            disabled={!table.getCanPreviousPage()}\n            icon={ArrowLeft01Icon}\n            label=\"Previous page\"\n            onClick={() => {\n              table.previousPage();\n            }}\n          />\n          <PageButton\n            disabled={!table.getCanNextPage()}\n            icon={ArrowRight01Icon}\n            label=\"Next page\"\n            onClick={() => {\n              table.nextPage();\n            }}\n          />\n          <PageButton\n            disabled={!table.getCanNextPage()}\n            icon={ArrowRightDoubleIcon}\n            label=\"Last page\"\n            onClick={() => {\n              table.setPageIndex(table.getPageCount() - 1);\n            }}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport { DataTablePagination };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/tanstack/data-table-selection.tsx",
      "content": "\"use client\";\n\nimport * as stylex from \"@stylexjs/stylex\";\nimport { useEffect, useRef } from \"react\";\nimport type { PointerEvent } from \"react\";\n\nimport { Checkbox } from \"@/components/primitives/checkbox\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { colors, durations, media } from \"@/lib/constella/tokens.stylex\";\n\n// What the row selection feature adds to a row and a table.\ninterface SelectableRow {\n  getCanSelect: () => boolean;\n  getDisplayIndex: () => number;\n  getIsSelected: () => boolean;\n  getToggleSelectedHandler: () => (event: ToggleEvent) => void;\n  toggleSelected: (value?: boolean) => void;\n}\n\n// What the selection handler reads: Shift picks a range.\ninterface ToggleEvent {\n  shiftKey: boolean;\n  target: { checked: boolean };\n}\n\ninterface SelectableTable {\n  getIsAllRowsSelected: () => boolean;\n  getIsSomeRowsSelected: () => boolean;\n  getRowModel: () => { rows: readonly unknown[] };\n  toggleAllRowsSelected: (value?: boolean) => void;\n}\n\nconst isSelectable = <TTable extends { getAllLeafColumns: () => unknown[] }>(\n  table: TTable\n): table is TTable & SelectableTable => \"getIsSomeRowsSelected\" in table;\n\nconst isSelecting = (table: { getAllLeafColumns: () => unknown[] }) =>\n  isSelectable(table) &&\n  (table.getIsSomeRowsSelected() || table.getIsAllRowsSelected());\n\nconst hasSelection = <TRow extends { id: string }>(\n  row: TRow | undefined\n): row is TRow & SelectableRow => row !== undefined && \"getIsSelected\" in row;\n\nconst rowIndexOf = (target: EventTarget) =>\n  target instanceof Element\n    ? Number(target.closest<HTMLElement>(\"tr[data-index]\")?.dataset.index)\n    : Number.NaN;\n\ninterface Drag {\n  from: number;\n  to: number;\n  value: boolean;\n  // Each row the drag has set, with the state to restore if it leaves the range.\n  was: Map<number, boolean>;\n}\n\n// Gives the rows from the range's start to `to` its value, and restores the rows it set before that fall outside.\nfunction paintRange(rows: readonly { id: string }[], range: Drag, to: number) {\n  range.to = to;\n  const low = Math.min(range.from, to);\n  const high = Math.max(range.from, to);\n  for (const [index, was] of range.was) {\n    const row = rows[index];\n    if ((index < low || index > high) && hasSelection(row)) {\n      row.toggleSelected(was);\n      range.was.delete(index);\n    }\n  }\n  for (let index = low; index <= high; index += 1) {\n    const row = rows[index];\n    if (hasSelection(row) && row.getCanSelect()) {\n      if (!range.was.has(index)) {\n        range.was.set(index, row.getIsSelected());\n      }\n      row.toggleSelected(range.value);\n    }\n  }\n  // Through the handler, so a later Shift-click ranges from where this ended.\n  const end = rows[to];\n  if (hasSelection(end) && end.getCanSelect()) {\n    end.getToggleSelectedHandler()({\n      shiftKey: false,\n      target: { checked: range.value },\n    });\n  }\n}\n\n// Drag from a row's checkbox across others to give them all its new state; dragging back restores them.\nfunction useDragSelect(rows: readonly { id: string }[]) {\n  const drag = useRef<Drag | null>(null);\n\n  useEffect(() => {\n    // The checkbox under the release would toggle again, so its click is dropped.\n    const swallow = (event: Event) => {\n      event.stopPropagation();\n      event.preventDefault();\n    };\n    const end = () => {\n      if (drag.current !== null && drag.current.was.size > 0) {\n        window.addEventListener(\"click\", swallow, { capture: true });\n        setTimeout(() => {\n          window.removeEventListener(\"click\", swallow, { capture: true });\n        }, 0);\n      }\n      drag.current = null;\n    };\n    window.addEventListener(\"pointerup\", end);\n    window.addEventListener(\"pointercancel\", end);\n    return () => {\n      window.removeEventListener(\"pointerup\", end);\n      window.removeEventListener(\"pointercancel\", end);\n    };\n  }, []);\n\n  const handlePointerDown = (event: PointerEvent<HTMLElement>) => {\n    const from = rowIndexOf(event.target);\n    const row = rows[from];\n    if (\n      event.pointerType !== \"mouse\" ||\n      event.button !== 0 ||\n      event.shiftKey ||\n      !(event.target instanceof Element) ||\n      event.target.closest(\"[data-row-select]\") === null ||\n      !hasSelection(row) ||\n      !row.getCanSelect()\n    ) {\n      return;\n    }\n    drag.current = {\n      from,\n      to: from,\n      value: !row.getIsSelected(),\n      was: new Map(),\n    };\n  };\n\n  const handlePointerOver = (event: PointerEvent<HTMLElement>) => {\n    const { current } = drag;\n    const to = rowIndexOf(event.target);\n    if (current === null || Number.isNaN(to) || to === current.to) {\n      return;\n    }\n    paintRange(rows, current, to);\n    getSelection()?.removeAllRanges();\n  };\n\n  return { handlePointerDown, handlePointerOver };\n}\n\nconst rowHovered = \":is([data-slot='table-row']:hover *)\";\nconst selecting = \":is([data-selecting] *)\";\n\nconst styles = stylex.create({\n  // The number and the checkbox share one spot; the checkbox sits on top, so a click on the number selects.\n  numbered: {\n    display: \"inline-grid\",\n    justifyItems: \"center\",\n    alignItems: \"center\",\n  },\n  // Fades only for the pointer; a swap the keyboard causes is instant.\n  stacked: {\n    gridArea: \"1 / 1\",\n    transitionDuration: {\n      default: 0,\n      [media.hover]: { default: 0, [rowHovered]: durations.hover },\n    },\n    transitionProperty: \"opacity\",\n    transitionTimingFunction: \"ease\",\n  },\n  // Touch has no hover to reveal the checkbox, so it always shows there.\n  number: {\n    color: colors.textMuted,\n    fontVariantNumeric: \"tabular-nums\",\n    opacity: {\n      default: 0,\n      [media.hover]: {\n        default: 1,\n        [rowHovered]: 0,\n        [selecting]: 0,\n        \":is([data-slot='data-table-select']:has(:focus-visible) *)\": 0,\n      },\n    },\n    pointerEvents: \"none\",\n  },\n  revealed: {\n    opacity: {\n      default: 1,\n      [media.hover]: {\n        default: 0,\n        [rowHovered]: 1,\n        [selecting]: 1,\n        \":focus-visible\": 1,\n      },\n    },\n  },\n});\n\n/** The header's checkbox: selects every row (needs `rowSelectionFeature`). */\nfunction DataTableSelectAll({\n  sx,\n  table,\n}: {\n  sx?: Sx;\n  table: SelectableTable;\n}) {\n  return (\n    <Checkbox\n      aria-label=\"Select all\"\n      checked={table.getIsAllRowsSelected()}\n      disabled={table.getRowModel().rows.length === 0}\n      indeterminate={\n        table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()\n      }\n      onCheckedChange={(checked) => {\n        table.toggleAllRowsSelected(checked);\n      }}\n      sx={sx}\n    />\n  );\n}\n\n/** A row's checkbox (needs `rowSelectionFeature`); Shift selects the range from the last one. `numbered` shows the row's number until the row is pointed at or any row is selected. */\nfunction DataTableSelectRow({\n  numbered = false,\n  row,\n  sx,\n}: {\n  numbered?: boolean;\n  row: SelectableRow;\n  sx?: Sx;\n}) {\n  const toggle = row.getToggleSelectedHandler();\n  const checkbox = (\n    <Checkbox\n      aria-label=\"Select row\"\n      checked={row.getIsSelected()}\n      data-row-select=\"\"\n      disabled={!row.getCanSelect()}\n      onCheckedChange={(checked, { event }) => {\n        toggle({\n          shiftKey:\n            (event instanceof globalThis.MouseEvent ||\n              event instanceof globalThis.KeyboardEvent) &&\n            event.shiftKey,\n          target: { checked },\n        });\n      }}\n      sx={numbered ? [styles.stacked, styles.revealed] : sx}\n    />\n  );\n  if (!numbered) {\n    return checkbox;\n  }\n  return (\n    <span data-slot=\"data-table-select\" {...stylex.props(styles.numbered, sx)}>\n      <span aria-hidden {...stylex.props(styles.stacked, styles.number)}>\n        {row.getDisplayIndex() + 1}\n      </span>\n      {checkbox}\n    </span>\n  );\n}\n\nexport {\n  DataTableSelectAll,\n  DataTableSelectRow,\n  hasSelection,\n  isSelectable,\n  isSelecting,\n  paintRange,\n  useDragSelect,\n};\nexport type { Drag };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/tanstack/data-table-view-options.tsx",
      "content": "\"use client\";\n\nimport { SlidersHorizontalIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\nimport { Button } from \"@/components/primitives/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuTrigger,\n} from \"@/components/primitives/dropdown-menu\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { sizes, strokes } from \"@/lib/constella/tokens.stylex\";\n\n// What the column visibility feature adds to a column and a table.\ninterface HidingColumn {\n  accessorFn?: unknown;\n  getCanHide: () => boolean;\n  getIsVisible: () => boolean;\n  id: string;\n  toggleVisibility: (value?: boolean) => void;\n}\n\ninterface HidingTable {\n  getAllLeafColumns: () => HidingColumn[];\n}\n\nconst noLabels = {};\n\n/** A menu that shows and hides columns (needs `columnVisibilityFeature`); lists the columns with data that can hide. */\nfunction DataTableViewOptions({\n  labels = noLabels,\n  sx,\n  table,\n}: {\n  /** Keyed by column id: the name to list it by; the id otherwise. */\n  labels?: Readonly<Partial<Record<string, string>>>;\n  sx?: Sx;\n  table: HidingTable;\n}) {\n  const columns = table\n    .getAllLeafColumns()\n    .filter((column) => column.accessorFn !== undefined && column.getCanHide());\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button sx={sx} variant=\"outline\" />}>\n        <HugeiconsIcon\n          aria-hidden\n          icon={SlidersHorizontalIcon}\n          size={sizes.icon}\n          strokeWidth={Number(strokes.icon)}\n        />\n        View\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>Columns</DropdownMenuLabel>\n          {columns.map((column) => (\n            <DropdownMenuCheckboxItem\n              checked={column.getIsVisible()}\n              key={column.id}\n              onCheckedChange={(checked) => {\n                column.toggleVisibility(checked);\n              }}\n            >\n              {labels[column.id] ?? column.id}\n            </DropdownMenuCheckboxItem>\n          ))}\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n\nexport { DataTableViewOptions };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/tanstack/use-data-table-keys.ts",
      "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type { FocusEvent, KeyboardEvent, MouseEvent, RefObject } from \"react\";\n\nimport {\n  hasSelection,\n  paintRange,\n} from \"@/components/tanstack/data-table-selection\";\nimport type { Drag } from \"@/components/tanstack/data-table-selection\";\n\n// A click on one of these is the control's, not the row's.\nconst controls =\n  \"a, button, input, label, select, textarea, [role='button'], [role='checkbox'], [role='switch']\";\n\n// Where a key moves the row focus from `index`, if it moves it.\nconst keyTarget = (key: string, index: number, last: number) => {\n  switch (key) {\n    case \"ArrowDown\": {\n      return Math.min(index + 1, last);\n    }\n    case \"ArrowUp\": {\n      return Math.max(index - 1, 0);\n    }\n    case \"End\": {\n      return last;\n    }\n    case \"Home\": {\n      return 0;\n    }\n    default: {\n      return null;\n    }\n  }\n};\n\n// Cmd, Ctrl or Shift on a row selects it (Shift through to the last one), as in a file list.\nfunction clickRow<TRow extends { id: string }>(\n  event: MouseEvent<HTMLTableRowElement>,\n  row: TRow,\n  onRowClick: (row: TRow) => void\n) {\n  const { target } = event;\n  if (\n    !(target instanceof Element) ||\n    // A click in a popup opened from the row reaches it through React, not the page.\n    !event.currentTarget.contains(target) ||\n    target.closest(controls) !== null\n  ) {\n    return;\n  }\n  if (\n    (event.metaKey || event.ctrlKey || event.shiftKey) &&\n    hasSelection(row) &&\n    row.getCanSelect()\n  ) {\n    // Shift-click also extends the page's text selection.\n    getSelection()?.removeAllRanges();\n    row.getToggleSelectedHandler()({\n      shiftKey: event.shiftKey,\n      target: { checked: !row.getIsSelected() },\n    });\n    return;\n  }\n  // The pointer was selecting text, not opening the row.\n  if (getSelection()?.isCollapsed === false) {\n    return;\n  }\n  onRowClick(row);\n}\n\n// One row takes Tab at a time and arrows move between them, as in a list. On a focused\n// row, x or Space selects it, Shift with an arrow extends the selection, and Enter opens it.\nfunction useDataTableKeys<TRow extends { id: string }>({\n  activeRowId,\n  body,\n  enabled,\n  items,\n  onRowClick,\n  rows,\n  scrollToIndex,\n}: {\n  activeRowId: string | undefined;\n  body: RefObject<HTMLTableSectionElement | null>;\n  enabled: boolean;\n  items: readonly { index: number }[];\n  onRowClick: ((row: TRow) => void) | undefined;\n  rows: readonly TRow[];\n  scrollToIndex: (index: number) => void;\n}) {\n  const [focusIndex, setFocusIndex] = useState(0);\n  const extending = useRef<Drag | null>(null);\n\n  const focusRow = (index: number) => {\n    setFocusIndex(index);\n    const find = () =>\n      body.current?.querySelector<HTMLElement>(`tr[data-index=\"${index}\"]`);\n    const mounted = find();\n    if (mounted) {\n      mounted.focus();\n      return;\n    }\n    // Far rows mount only once the virtualizer has scrolled to them, which can take a few frames.\n    scrollToIndex(index);\n    let frames = 0;\n    const retry = () => {\n      const target = find();\n      if (target) {\n        target.focus({ preventScroll: true });\n      } else if (frames < 10) {\n        frames += 1;\n        requestAnimationFrame(retry);\n      }\n    };\n    requestAnimationFrame(retry);\n  };\n\n  const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n    const { target } = event;\n    if (!(target instanceof HTMLTableRowElement)) {\n      return;\n    }\n    const index = Number(target.dataset.index);\n    const row = rows[index];\n    const next = keyTarget(event.key, index, rows.length - 1);\n    if (row === undefined) {\n      return;\n    }\n    if (next !== null) {\n      event.preventDefault();\n      const stepping = event.key === \"ArrowDown\" || event.key === \"ArrowUp\";\n      if (event.shiftKey && stepping) {\n        const range = extending.current ?? {\n          from: index,\n          to: index,\n          value: true,\n          was: new Map<number, boolean>(),\n        };\n        extending.current = range;\n        paintRange(rows, range, next);\n      } else {\n        extending.current = null;\n      }\n      focusRow(next);\n      return;\n    }\n    if (event.key !== \"Shift\") {\n      extending.current = null;\n    }\n    if (\n      (event.key === \"x\" || event.key === \" \") &&\n      hasSelection(row) &&\n      row.getCanSelect()\n    ) {\n      event.preventDefault();\n      row.getToggleSelectedHandler()({\n        shiftKey: false,\n        target: { checked: !row.getIsSelected() },\n      });\n    } else if (event.key === \"Enter\" && onRowClick !== undefined) {\n      event.preventDefault();\n      onRowClick(row);\n    }\n  };\n\n  const handleFocus = (event: FocusEvent<HTMLElement>) => {\n    if (event.target !== event.currentTarget) {\n      return;\n    }\n    const index = Number(event.currentTarget.dataset.index);\n    // Focus landing anywhere but where Shift and an arrow just went starts a new range.\n    if (index !== extending.current?.to) {\n      extending.current = null;\n    }\n    setFocusIndex(index);\n  };\n\n  // The row that takes Tab, kept among the rendered ones so scrolling never leaves none.\n  const first = items[0]?.index ?? 0;\n  const tabbable = Math.max(\n    first,\n    Math.min(focusIndex, items.at(-1)?.index ?? 0)\n  );\n\n  // A row's state, focus and click props; one row takes Tab, and none when rows can't be acted on.\n  const rowProps = (index: number, row: TRow) => {\n    const selected = hasSelection(row) ? row.getIsSelected() : undefined;\n    return {\n      \"aria-current\": row.id === activeRowId ? (\"true\" as const) : undefined,\n      \"data-state\": selected === true ? \"selected\" : undefined,\n      ...(enabled && {\n        onFocus: handleFocus,\n        tabIndex: index === tabbable ? 0 : -1,\n      }),\n      ...(onRowClick !== undefined && {\n        onClick: (event: MouseEvent<HTMLTableRowElement>) => {\n          clickRow(event, row, onRowClick);\n        },\n      }),\n    };\n  };\n\n  return { handleKeyDown, rowProps };\n}\n\nexport { useDataTableKeys };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/tanstack/use-data-table-virtualizer.ts",
      "content": "\"use client\";\n\nimport { useVirtualizer, useWindowVirtualizer } from \"@tanstack/react-virtual\";\nimport { useEffect, useLayoutEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\n// The server has no window, so it renders the rows that fit a typical one.\nconst serverRect = { height: 900, width: 1280 };\n\n// Measures now and again whenever one of the targets changes size; returns the cleanup.\nconst trackMargin = (measure: () => void, targets: readonly Element[]) => {\n  measure();\n  const observer = new ResizeObserver(measure);\n  for (const target of targets) {\n    observer.observe(target);\n  }\n  return () => {\n    observer.disconnect();\n  };\n};\n\n// The cleanup for an effect that tracks nothing, as the other mode does the measuring.\nconst keepMargin = () => null;\n\n// Virtualizes against the window, or the element `scrollRef` names. Both hooks always run,\n// as hooks can't be conditional, and only the one in use is enabled.\nfunction useDataTableVirtualizer({\n  body,\n  rowHeight,\n  rows,\n  scrollRef,\n}: {\n  body: RefObject<HTMLTableSectionElement | null>;\n  rowHeight: number;\n  rows: readonly { id: string }[];\n  scrollRef: RefObject<HTMLElement | null> | undefined;\n}) {\n  const [scrollMargin, setScrollMargin] = useState(0);\n\n  // The body's distance from the top of what scrolls, which the scroll offset is\n  // measured from; measured again whenever the content above it changes height.\n  useLayoutEffect(() => {\n    if (scrollRef !== undefined) {\n      return keepMargin;\n    }\n    return trackMargin(() => {\n      const top = body.current?.getBoundingClientRect().top ?? 0;\n      setScrollMargin(top + window.scrollY);\n    }, [document.body]);\n  }, [body, scrollRef]);\n\n  // Passive, as a parent's ref attaches only after its children's layout effects run.\n  useEffect(() => {\n    const scroller = scrollRef?.current;\n    if (scroller === undefined || scroller === null) {\n      return keepMargin;\n    }\n    return trackMargin(() => {\n      const top = body.current?.getBoundingClientRect().top ?? 0;\n      setScrollMargin(\n        // `scrollTop` counts from inside the border.\n        top -\n          scroller.getBoundingClientRect().top -\n          scroller.clientTop +\n          scroller.scrollTop\n      );\n    }, [scroller, ...scroller.children]);\n  }, [body, scrollRef]);\n\n  const options = {\n    count: rows.length,\n    estimateSize: () => rowHeight,\n    getItemKey: (index: number) => rows[index]?.id ?? index,\n    overscan: 8,\n    scrollMargin,\n  };\n  // oxlint-disable-next-line react/incompatible-library -- the virtualizer re-renders on scroll by design\n  const onWindow = useWindowVirtualizer({\n    ...options,\n    enabled: scrollRef === undefined,\n    initialRect: serverRect,\n  });\n  // oxlint-disable-next-line react/incompatible-library -- the virtualizer re-renders on scroll by design\n  const inElement = useVirtualizer({\n    ...options,\n    enabled: scrollRef !== undefined,\n    getScrollElement: () => scrollRef?.current ?? null,\n  });\n  const virtualizer = scrollRef === undefined ? onWindow : inElement;\n  const items = virtualizer.getVirtualItems();\n  const [first] = items;\n  const last = items.at(-1);\n  return {\n    items,\n    // The height of the rows not rendered above and below the ones that are.\n    padAfter:\n      last === undefined\n        ? 0\n        : virtualizer.getTotalSize() - (last.end - scrollMargin),\n    padBefore: first === undefined ? 0 : first.start - scrollMargin,\n    virtualizer,\n  };\n}\n\nexport { useDataTableVirtualizer };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/data-table/index.md",
  "type": "registry:ui"
}