{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data table",
  "description": "A table for real data, with sorting, search, row selection, pinned columns, a sticky header and pagination.",
  "dependencies": [
    "@base-ui/react@^1.8.0",
    "@tabler/icons-react",
    "@tanstack/react-table@^9.2.6",
    "cn"
  ],
  "registryDependencies": [
    "https://hextaui.com/r/theme.json",
    "https://hextaui.com/r/button.json",
    "https://hextaui.com/r/checkbox.json",
    "https://hextaui.com/r/skeleton.json",
    "https://hextaui.com/r/table.json"
  ],
  "files": [
    {
      "path": "components/ui/data-table.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\"\nimport {\n  IconAdjustmentsHorizontal,\n  IconArrowDown,\n  IconArrowUp,\n  IconCheck,\n  IconChevronDown,\n  IconChevronLeft,\n  IconChevronRight,\n  IconChevronsLeft,\n  IconChevronsRight,\n  IconSearch,\n  IconSelector,\n  IconX,\n} from \"@tabler/icons-react\"\nimport {\n  columnFilteringFeature,\n  columnVisibilityFeature,\n  createFilteredRowModel,\n  createPaginatedRowModel,\n  createSortedRowModel,\n  createTableHook,\n  createTableHookContexts,\n  filterFn_includesString,\n  globalFilteringFeature,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  sortFn_alphanumeric,\n  sortFn_basic,\n  sortFn_datetime,\n  sortFn_text,\n  Subscribe,\n  tableFeatures,\n  type Column,\n  type Row,\n  type RowData,\n  type RowSelectionState,\n  type Table as CoreTable,\n} from \"@tanstack/react-table\"\nimport { cn } from \"cn\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\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\nconst dataTableFeatures = tableFeatures({\n  rowSortingFeature,\n  sortedRowModel: createSortedRowModel(),\n  sortFns: {\n    alphanumeric: sortFn_alphanumeric,\n    text: sortFn_text,\n    datetime: sortFn_datetime,\n    basic: sortFn_basic,\n  },\n  columnFilteringFeature,\n  globalFilteringFeature,\n  filteredRowModel: createFilteredRowModel(),\n  filterFns: { includesString: filterFn_includesString },\n  rowPaginationFeature,\n  paginatedRowModel: createPaginatedRowModel(),\n  rowSelectionFeature,\n  columnVisibilityFeature,\n})\n\ntype DataTableFeatures = typeof dataTableFeatures\n\nconst dataTableContexts = createTableHookContexts<DataTableFeatures>()\n\nconst dataTableHook = createTableHook({\n  features: dataTableFeatures,\n  ...dataTableContexts,\n  globalFilterFn: \"includesString\",\n  getColumnCanGlobalFilter: (column) => column.id !== \"select\",\n})\n\ntype UseDataTableOptions<TData extends RowData> = Parameters<\n  typeof dataTableHook.useAppTable<TData>\n>[0]\n\ntype DataTableInstance<TData extends RowData = RowData> = ReturnType<\n  typeof dataTableHook.useAppTable<TData>\n>\n\ntype AnyColumn = Column<DataTableFeatures, RowData, unknown>\ntype AnyRow = Row<DataTableFeatures, RowData>\ntype AnyCoreTable = CoreTable<DataTableFeatures, RowData>\n\nfunction useDataTable<TData extends RowData>(\n  options: UseDataTableOptions<TData>\n) {\n  return dataTableHook.useAppTable<TData>({\n    ...options,\n    initialState: {\n      pagination: { pageIndex: 0, pageSize: 10 },\n      ...options.initialState,\n    },\n  })\n}\n\nconst createDataTableColumns = dataTableHook.createAppColumnHelper\n\nfunction useDataTableContext() {\n  return dataTableHook.useTableContext()\n}\n\nfunction columnLabel(column: AnyColumn) {\n  const header = column.columnDef.header\n  if (typeof header === \"string\") {\n    return header\n  }\n  return column.id.charAt(0).toUpperCase() + column.id.slice(1)\n}\n\nfunction DataTableAnnouncer() {\n  const table = useDataTableContext()\n  const sorting = table.state.sorting\n  const search = table.state.globalFilter as string | undefined\n  const count = table.getFilteredRowModel().rows.length\n  const sortKey = JSON.stringify(sorting)\n  const [message, setMessage] = React.useState(\"\")\n  const [previousSort, setPreviousSort] = React.useState(sortKey)\n\n  const first = sorting[0]\n  const sortedColumn = first ? table.getColumn(first.id) : undefined\n  const sortMessage = first\n    ? `Sorted by ${sortedColumn ? columnLabel(sortedColumn as AnyColumn) : first.id}, ${first.desc ? \"descending\" : \"ascending\"}`\n    : \"Sorting removed\"\n\n  if (sortKey !== previousSort) {\n    setPreviousSort(sortKey)\n    setMessage(sortMessage)\n  }\n\n  React.useEffect(() => {\n    if (!search) {\n      return\n    }\n    const timer = setTimeout(\n      () => setMessage(count === 1 ? \"1 result\" : `${count} results`),\n      500\n    )\n    return () => clearTimeout(timer)\n  }, [count, search])\n\n  return (\n    <span\n      data-slot=\"data-table-announcer\"\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-atomic\n      className=\"sr-only\"\n    >\n      {message}\n    </span>\n  )\n}\n\ntype DataTableProps<TData extends RowData> = React.ComponentProps<\"div\"> & {\n  table: DataTableInstance<TData>\n}\n\nfunction DataTable<TData extends RowData>({\n  table,\n  className,\n  children,\n  ...props\n}: DataTableProps<TData>) {\n  return (\n    <table.AppTable>\n      <div\n        data-slot=\"data-table\"\n        className={cn(\"flex w-full min-w-0 flex-col gap-3\", className)}\n        {...props}\n      >\n        {children}\n        <DataTableAnnouncer />\n      </div>\n    </table.AppTable>\n  )\n}\n\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\nconst fieldClassName =\n  \"h-8 w-full min-w-0 rounded-md bg-background text-sm inset-ring-(length:--hairline) forced-colors:border inset-ring-input transition-[box-shadow] duration-150 ease-out-quint outline-none focus-visible:outline-hidden placeholder:text-muted-foreground focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:inset-ring-ring pointer-coarse:h-11 pointer-coarse:text-[max(16px,1rem)] dark:bg-input/30\"\n\ntype DataTableSearchProps = Omit<\n  React.ComponentProps<\"input\">,\n  \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n  clearLabel?: string\n}\n\nfunction DataTableSearch({\n  className,\n  placeholder = \"Search…\",\n  clearLabel = \"Clear search\",\n  onKeyDown,\n  \"aria-label\": ariaLabel = \"Search table\",\n  ...props\n}: DataTableSearchProps) {\n  const table = useDataTableContext()\n  const value = (table.state.globalFilter as string | undefined) ?? \"\"\n  const inputRef = React.useRef<HTMLInputElement>(null)\n\n  const setSearch = (next: string) => {\n    table.setGlobalFilter(next)\n    table.setPageIndex(0)\n  }\n\n  return (\n    <div\n      data-slot=\"data-table-search\"\n      className={cn(\n        \"relative flex min-w-0 flex-1 items-center sm:max-w-xs\",\n        className\n      )}\n    >\n      <IconSearch\n        aria-hidden\n        className=\"pointer-events-none absolute start-2.5 size-4 text-muted-foreground\"\n      />\n      <input\n        ref={inputRef}\n        type=\"search\"\n        value={value}\n        placeholder={placeholder}\n        aria-label={ariaLabel}\n        onChange={(event) => setSearch(event.target.value)}\n        onKeyDown={(event) => {\n          onKeyDown?.(event)\n          if (\n            !event.defaultPrevented &&\n            event.key === \"Escape\" &&\n            value !== \"\"\n          ) {\n            event.preventDefault()\n            setSearch(\"\")\n          }\n        }}\n        className={cn(\n          fieldClassName,\n          \"ps-8 pe-8 [&::-webkit-search-cancel-button]:hidden\"\n        )}\n        {...props}\n      />\n      <Button\n        type=\"button\"\n        variant=\"ghost\"\n        size=\"icon-xs\"\n        tabIndex={-1}\n        aria-label={clearLabel}\n        aria-hidden={value === \"\" || undefined}\n        onClick={() => {\n          setSearch(\"\")\n          inputRef.current?.focus()\n        }}\n        className={cn(\n          \"absolute end-1 rounded-full text-muted-foreground transition-[opacity,scale,color,background-color] duration-150 hover:text-foreground\",\n          value === \"\"\n            ? \"pointer-events-none opacity-0 motion-safe:scale-75\"\n            : \"opacity-100\"\n        )}\n      >\n        <IconX />\n      </Button>\n    </div>\n  )\n}\n\ntype DataTableColumnHeaderProps<\n  TData extends RowData,\n  TValue,\n> = React.ComponentProps<\"div\"> & {\n  column: Column<DataTableFeatures, TData, TValue>\n  title: string\n}\n\nfunction DataTableColumnHeader<TData extends RowData, TValue>({\n  column,\n  title,\n  className,\n  ...props\n}: DataTableColumnHeaderProps<TData, TValue>) {\n  const target = column as unknown as AnyColumn\n\n  if (!target.getCanSort()) {\n    return (\n      <div\n        data-slot=\"data-table-column-header\"\n        className={className}\n        {...props}\n      >\n        {title}\n      </div>\n    )\n  }\n\n  return (\n    <Subscribe\n      source={target.table.store}\n      selector={(state) =>\n        state.sorting.find((entry) => entry.id === target.id)?.desc\n      }\n    >\n      {(desc) => (\n        <div\n          data-slot=\"data-table-column-header\"\n          className={cn(\n            \"flex in-data-[align=center]:justify-center in-data-[align=end]:justify-end\",\n            className\n          )}\n          {...props}\n        >\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"sm\"\n            onClick={target.getToggleSortingHandler()}\n            className=\"-mx-2.5 h-8 gap-1.5 px-2.5 font-medium text-muted-foreground hover:text-foreground in-data-[align=end]:flex-row-reverse data-sorted:text-foreground\"\n            data-sorted={desc === undefined ? undefined : \"\"}\n          >\n            {title}\n            {desc === undefined ? (\n              <IconSelector aria-hidden className=\"size-3.5 opacity-60\" />\n            ) : desc ? (\n              <IconArrowDown aria-hidden className=\"size-3.5\" />\n            ) : (\n              <IconArrowUp aria-hidden className=\"size-3.5\" />\n            )}\n          </Button>\n        </div>\n      )}\n    </Subscribe>\n  )\n}\n\nfunction DataTableSelectAll<TData extends RowData>({\n  table,\n  \"aria-label\": ariaLabel = \"Select all rows on this page\",\n}: {\n  table: CoreTable<DataTableFeatures, TData>\n  \"aria-label\"?: string\n}) {\n  const target = table as unknown as AnyCoreTable\n\n  return (\n    <Subscribe\n      source={target.store}\n      selector={(state) => [\n        state.rowSelection,\n        state.pagination,\n        state.globalFilter,\n      ]}\n    >\n      {() => {\n        const all = target.getIsAllPageRowsSelected()\n        const some = target.getIsSomePageRowsSelected()\n        return (\n          <Checkbox\n            aria-label={ariaLabel}\n            className=\"z-1\"\n            checked={all}\n            indeterminate={!all && some}\n            onCheckedChange={(checked) =>\n              target.toggleAllPageRowsSelected(checked)\n            }\n          />\n        )\n      }}\n    </Subscribe>\n  )\n}\n\nfunction DataTableSelectRow<TData extends RowData>({\n  row,\n  \"aria-label\": ariaLabel = \"Select row\",\n}: {\n  row: Row<DataTableFeatures, TData>\n  \"aria-label\"?: string\n}) {\n  const target = row as unknown as AnyRow\n\n  return (\n    <Subscribe\n      source={target.table.store}\n      selector={(state) => Boolean(state.rowSelection[target.id])}\n    >\n      {() => (\n        <Checkbox\n          aria-label={ariaLabel}\n          className=\"z-1\"\n          checked={target.getIsSelected()}\n          disabled={!target.getCanSelect()}\n          onCheckedChange={(checked, details) => {\n            const event = details.event as Event & { shiftKey?: boolean }\n            target.getToggleSelectedHandler()({\n              target: { checked },\n              shiftKey: Boolean(event?.shiftKey),\n              nativeEvent: event,\n            })\n          }}\n        />\n      )}\n    </Subscribe>\n  )\n}\n\ntype DataTableContentProps = {\n  className?: string\n  emptyMessage?: React.ReactNode\n  loading?: boolean\n  loadingRows?: number\n  pinStart?: string[]\n  stickyHeader?: boolean\n  containerClassName?: string\n  swipeSelect?: boolean\n}\n\nfunction scrollParentOf(element: HTMLElement | null) {\n  return element && element.scrollHeight > element.clientHeight + 1\n    ? element\n    : null\n}\n\nfunction useSwipeSelect(\n  table: AnyCoreTable,\n  containerRef: React.RefObject<HTMLDivElement | null>,\n  enabled: boolean\n) {\n  return (event: React.PointerEvent<HTMLTableSectionElement>) => {\n    if (\n      !enabled ||\n      !event.shiftKey ||\n      event.button !== 0 ||\n      event.pointerType === \"touch\"\n    ) {\n      return\n    }\n    const target = event.target as Element\n    const box = target.closest(\"[data-slot=checkbox]\")\n    const startRow = box?.closest<HTMLElement>(\"tr[data-row-id]\")\n    if (!box || !startRow) {\n      return\n    }\n    const ids = table.getRowModel().rows.map((row) => row.id)\n    const anchor = ids.indexOf(startRow.dataset.rowId ?? \"\")\n    const anchorRow = anchor === -1 ? undefined : table.getRow(ids[anchor])\n    if (!anchorRow || !anchorRow.getCanSelect()) {\n      return\n    }\n    event.preventDefault()\n\n    const value = !anchorRow.getIsSelected()\n    const snapshot: RowSelectionState = { ...table.store.state.rowSelection }\n    const body = event.currentTarget\n    const container = containerRef.current\n    let current = anchor\n    let dragged = false\n    let pointer = { x: event.clientX, y: event.clientY }\n    let frame = 0\n\n    const apply = (index: number) => {\n      const next: RowSelectionState = { ...snapshot }\n      const from = Math.min(anchor, index)\n      const to = Math.max(anchor, index)\n      for (let position = from; position <= to; position++) {\n        const row = table.getRow(ids[position])\n        if (!row?.getCanSelect()) {\n          continue\n        }\n        if (value) {\n          next[ids[position]] = true\n        } else {\n          delete next[ids[position]]\n        }\n      }\n      table.setRowSelection(next)\n    }\n\n    const visibleBounds = () => {\n      const scroller = scrollParentOf(container)\n      const bounds = scroller\n        ? scroller.getBoundingClientRect()\n        : { top: 0, bottom: window.innerHeight }\n      const head = scroller?.querySelector(\"thead\")\n      const top = head\n        ? Math.max(bounds.top, head.getBoundingClientRect().bottom)\n        : bounds.top\n      return { top, bottom: bounds.bottom }\n    }\n\n    const hitTest = () => {\n      const { top, bottom } = visibleBounds()\n      const rows = body.getBoundingClientRect()\n      const y = Math.min(Math.max(pointer.y, top + 6), bottom - 6)\n      const x = Math.min(Math.max(pointer.x, rows.left + 1), rows.right - 1)\n      const hit = document\n        .elementFromPoint(x, y)\n        ?.closest<HTMLElement>(\"tr[data-row-id]\")\n      let index = hit ? ids.indexOf(hit.dataset.rowId ?? \"\") : -1\n      if (index === -1) {\n        if (pointer.y < rows.top) {\n          index = 0\n        } else if (pointer.y > rows.bottom) {\n          index = ids.length - 1\n        } else {\n          return\n        }\n      }\n      if (index === current) {\n        return\n      }\n      current = index\n      if (index !== anchor) {\n        dragged = true\n      }\n      if (dragged) {\n        apply(index)\n      }\n    }\n\n    const autoScroll = () => {\n      frame = 0\n      const scroller = scrollParentOf(container)\n      const { top, bottom } = visibleBounds()\n      const edge = 40\n      let delta = 0\n      if (pointer.y < top + edge) {\n        delta = -Math.ceil((top + edge - pointer.y) / 4)\n      } else if (pointer.y > bottom - edge) {\n        delta = Math.ceil((pointer.y - (bottom - edge)) / 4)\n      }\n      if (delta === 0) {\n        return\n      }\n      if (scroller) {\n        scroller.scrollTop += delta\n      } else {\n        window.scrollBy(0, delta)\n      }\n      hitTest()\n      frame = requestAnimationFrame(autoScroll)\n    }\n\n    const onMove = (moveEvent: PointerEvent) => {\n      pointer = { x: moveEvent.clientX, y: moveEvent.clientY }\n      hitTest()\n      if (!frame) {\n        frame = requestAnimationFrame(autoScroll)\n      }\n    }\n\n    const stopClick = (clickEvent: MouseEvent) => {\n      clickEvent.preventDefault()\n      clickEvent.stopPropagation()\n    }\n\n    const onUp = () => {\n      window.removeEventListener(\"pointermove\", onMove)\n      window.removeEventListener(\"pointerup\", onUp)\n      window.removeEventListener(\"pointercancel\", onUp)\n      cancelAnimationFrame(frame)\n      container?.removeAttribute(\"data-swipe-selecting\")\n      if (dragged) {\n        window.addEventListener(\"click\", stopClick, {\n          capture: true,\n          once: true,\n        })\n        setTimeout(\n          () =>\n            window.removeEventListener(\"click\", stopClick, { capture: true }),\n          0\n        )\n      }\n    }\n\n    container?.setAttribute(\"data-swipe-selecting\", \"\")\n    window.addEventListener(\"pointermove\", onMove)\n    window.addEventListener(\"pointerup\", onUp)\n    window.addEventListener(\"pointercancel\", onUp)\n  }\n}\n\nfunction DataTableContent({\n  className,\n  emptyMessage = \"No results.\",\n  loading = false,\n  loadingRows = 5,\n  pinStart,\n  stickyHeader = false,\n  containerClassName,\n  swipeSelect = true,\n}: DataTableContentProps) {\n  const table = useDataTableContext()\n  const containerRef = React.useRef<HTMLDivElement>(null)\n  const onSwipeStart = useSwipeSelect(\n    table as unknown as AnyCoreTable,\n    containerRef,\n    swipeSelect\n  )\n  const leafColumns = table.getVisibleLeafColumns()\n  const firstData = leafColumns.find((column) => column.id !== \"select\")\n  const pinned = (\n    pinStart ?? [\"select\", ...(firstData ? [firstData.id] : [])]\n  ).filter((id) => leafColumns.some((column) => column.id === id))\n  const rows = table.getRowModel().rows\n  const pageCount = table.getPageCount()\n  const pageIndex = table.state.pagination.pageIndex\n\n  React.useEffect(() => {\n    if (pageCount > 0 && pageIndex > pageCount - 1) {\n      table.setPageIndex(pageCount - 1)\n    }\n  }, [pageCount, pageIndex, table])\n\n  const alignOf = (column: { columnDef: { meta?: unknown } }) =>\n    (\n      column.columnDef.meta as\n        { align?: \"start\" | \"center\" | \"end\" } | undefined\n    )?.align\n\n  const pinProps = (columnId: string) => {\n    const index = pinned.indexOf(columnId)\n    if (index === -1) {\n      return { \"data-pin-column\": undefined }\n    }\n    return {\n      pinned: \"start\" as const,\n      pinnedEdge: index === pinned.length - 1,\n      \"data-pin-column\": columnId,\n    }\n  }\n\n  React.useLayoutEffect(() => {\n    const container = containerRef.current\n    if (!container) {\n      return\n    }\n    const apply = () => {\n      let offset = 0\n      for (const id of pinned) {\n        const cells = container.querySelectorAll<HTMLElement>(\n          `[data-pin-column=\"${CSS.escape(id)}\"]`\n        )\n        cells.forEach((cell) =>\n          cell.style.setProperty(\"--pin-offset\", `${offset}px`)\n        )\n        const head = container.querySelector<HTMLElement>(\n          `th[data-pin-column=\"${CSS.escape(id)}\"]`\n        )\n        offset += head?.getBoundingClientRect().width ?? 0\n      }\n    }\n    apply()\n    const observer =\n      typeof ResizeObserver === \"function\" ? new ResizeObserver(apply) : null\n    const head = container.querySelector(\"thead\")\n    if (head) {\n      observer?.observe(head)\n    }\n    return () => observer?.disconnect()\n  })\n\n  return (\n    <Table\n      containerRef={containerRef}\n      stickyHeader={stickyHeader}\n      scrollFade={false}\n      containerClassName={cn(\n        \"rounded-lg border data-swipe-selecting:cursor-default data-swipe-selecting:select-none\",\n        containerClassName\n      )}\n      className={className}\n      aria-busy={loading || undefined}\n    >\n      <TableHeader>\n        {table.getHeaderGroups().map((group) => (\n          <TableRow key={group.id}>\n            {group.headers.map((header) => {\n              const sorted = header.column.getIsSorted()\n              return (\n                <TableHead\n                  key={header.id}\n                  colSpan={header.colSpan}\n                  align={alignOf(header.column)}\n                  aria-sort={\n                    sorted === \"asc\"\n                      ? \"ascending\"\n                      : sorted === \"desc\"\n                        ? \"descending\"\n                        : header.column.getCanSort()\n                          ? \"none\"\n                          : undefined\n                  }\n                  {...pinProps(header.column.id)}\n                >\n                  {header.isPlaceholder ? null : (\n                    <table.FlexRender header={header} />\n                  )}\n                </TableHead>\n              )\n            })}\n          </TableRow>\n        ))}\n      </TableHeader>\n      <TableBody onPointerDown={onSwipeStart}>\n        {loading ? (\n          Array.from({ length: loadingRows }, (_, index) => (\n            <TableRow\n              key={`loading-${index}`}\n              data-slot=\"data-table-loading-row\"\n            >\n              {leafColumns.map((column) => (\n                <TableCell key={column.id} {...pinProps(column.id)}>\n                  <Skeleton\n                    className={\n                      column.id === \"select\"\n                        ? \"size-4 rounded-[calc(var(--radius-sm)*0.5)]\"\n                        : \"h-4 w-full max-w-32\"\n                    }\n                  />\n                </TableCell>\n              ))}\n            </TableRow>\n          ))\n        ) : rows.length === 0 ? (\n          <TableRow data-slot=\"data-table-empty\">\n            <TableCell\n              colSpan={Math.max(1, leafColumns.length)}\n              className=\"h-24 text-center whitespace-normal text-muted-foreground\"\n            >\n              {emptyMessage}\n            </TableCell>\n          </TableRow>\n        ) : (\n          rows.map((row) => (\n            <TableRow\n              key={row.id}\n              data-row-id={row.id}\n              data-state={row.getIsSelected() ? \"selected\" : undefined}\n            >\n              {row.getVisibleCells().map((cell) => (\n                <TableCell\n                  key={cell.id}\n                  align={alignOf(cell.column)}\n                  {...pinProps(cell.column.id)}\n                >\n                  <table.FlexRender cell={cell} />\n                </TableCell>\n              ))}\n            </TableRow>\n          ))\n        )}\n      </TableBody>\n    </Table>\n  )\n}\n\nconst menuPopupClassName =\n  \"relative max-h-(--available-height) max-w-[min(var(--available-width),20rem)] min-w-44 origin-(--transform-origin) overflow-x-hidden overflow-y-auto overscroll-none rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-(length:--hairline) forced-colors:border ring-foreground/10 transition-[opacity,scale] duration-150 ease-out-quint outline-none focus-visible:outline-hidden data-ending-style:opacity-0 data-starting-style:opacity-0 data-instant:transition-none motion-safe:data-starting-style:scale-96 motion-reduce:transition-opacity\"\n\nconst menuItemClassName =\n  \"relative flex min-h-8 w-full min-w-0 cursor-default items-center gap-2 rounded-[max(calc(var(--radius-sm)*0.5),calc(var(--radius-lg)-0.25rem))] py-1.5 ps-8 pe-2 text-start text-sm outline-none focus-visible:outline-hidden select-none data-highlighted:bg-accent forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-solid data-highlighted:text-accent-foreground pointer-coarse:min-h-11\"\n\ntype DataTableViewOptionsProps = {\n  label?: string\n  groupLabel?: string\n  getLabel?: (column: AnyColumn) => string\n}\n\nfunction DataTableViewOptions({\n  label = \"View\",\n  groupLabel = \"Toggle columns\",\n  getLabel = columnLabel,\n}: DataTableViewOptionsProps) {\n  const table = useDataTableContext()\n  const columns = table\n    .getAllLeafColumns()\n    .filter((column) => column.getCanHide()) as AnyColumn[]\n\n  return (\n    <MenuPrimitive.Root>\n      <MenuPrimitive.Trigger\n        render={\n          <Button variant=\"outline\" size=\"sm\" className=\"ms-auto shrink-0\" />\n        }\n      >\n        <IconAdjustmentsHorizontal />\n        {label}\n        <IconChevronDown className=\"text-muted-foreground\" />\n      </MenuPrimitive.Trigger>\n      <MenuPrimitive.Portal>\n        <MenuPrimitive.Positioner\n          align=\"end\"\n          sideOffset={6}\n          className=\"isolate z-50 outline-none focus-visible:outline-hidden\"\n        >\n          <MenuPrimitive.Popup\n            data-slot=\"data-table-view-options\"\n            className={menuPopupClassName}\n          >\n            <MenuPrimitive.Group>\n              <MenuPrimitive.GroupLabel className=\"px-2 pt-1.5 pb-1 text-xs font-medium text-muted-foreground\">\n                {groupLabel}\n              </MenuPrimitive.GroupLabel>\n              {columns.map((column) => (\n                <MenuPrimitive.CheckboxItem\n                  key={column.id}\n                  checked={column.getIsVisible()}\n                  onCheckedChange={(checked) =>\n                    column.toggleVisibility(checked)\n                  }\n                  closeOnClick={false}\n                  className={menuItemClassName}\n                >\n                  <span className=\"pointer-events-none absolute start-2 flex size-4 items-center justify-center\">\n                    <MenuPrimitive.CheckboxItemIndicator className=\"flex items-center justify-center\">\n                      <IconCheck className=\"size-4\" />\n                    </MenuPrimitive.CheckboxItemIndicator>\n                  </span>\n                  <span className=\"min-w-0 truncate\">{getLabel(column)}</span>\n                </MenuPrimitive.CheckboxItem>\n              ))}\n            </MenuPrimitive.Group>\n          </MenuPrimitive.Popup>\n        </MenuPrimitive.Positioner>\n      </MenuPrimitive.Portal>\n    </MenuPrimitive.Root>\n  )\n}\n\ntype DataTablePaginationLabels = {\n  rowsPerPage: string\n  firstPage: string\n  previousPage: string\n  nextPage: string\n  lastPage: string\n}\n\ntype DataTablePaginationProps = React.ComponentProps<\"div\"> & {\n  pageSizes?: number[]\n  showSelection?: boolean\n  labels?: Partial<DataTablePaginationLabels>\n  formatSelection?: (selected: number, total: number) => React.ReactNode\n  formatRows?: (total: number) => React.ReactNode\n  formatPage?: (page: number, pageCount: number) => React.ReactNode\n}\n\nconst numberFormat = new Intl.NumberFormat(\"en-US\")\n\nconst defaultPaginationLabels: DataTablePaginationLabels = {\n  rowsPerPage: \"Rows per page\",\n  firstPage: \"First page\",\n  previousPage: \"Previous page\",\n  nextPage: \"Next page\",\n  lastPage: \"Last page\",\n}\n\nfunction defaultFormatSelection(selected: number, total: number) {\n  return `${numberFormat.format(selected)} of ${numberFormat.format(total)} row${total === 1 ? \"\" : \"s\"} selected`\n}\n\nfunction defaultFormatRows(total: number) {\n  return `${numberFormat.format(total)} row${total === 1 ? \"\" : \"s\"}`\n}\n\nfunction defaultFormatPage(page: number, pageCount: number) {\n  return `Page ${numberFormat.format(page)} of ${numberFormat.format(pageCount)}`\n}\n\nfunction DataTablePagination({\n  className,\n  pageSizes = [10, 20, 50, 100],\n  showSelection = true,\n  labels: labelOverrides,\n  formatSelection = defaultFormatSelection,\n  formatRows = defaultFormatRows,\n  formatPage = defaultFormatPage,\n  ...props\n}: DataTablePaginationProps) {\n  const labels = { ...defaultPaginationLabels, ...labelOverrides }\n  const table = useDataTableContext()\n  const { pageIndex, pageSize } = table.state.pagination\n  const pageCount = Math.max(1, table.getPageCount())\n  const selected = Object.keys(table.state.rowSelection).length\n  const total = table.getFilteredRowModel().rows.length\n\n  return (\n    <div\n      data-slot=\"data-table-pagination\"\n      className={cn(\n        \"flex flex-wrap items-center justify-between gap-x-6 gap-y-2 text-sm text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"min-w-0 tabular-nums [unicode-bidi:plaintext]\">\n        {showSelection ? formatSelection(selected, total) : formatRows(total)}\n      </div>\n      <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2\">\n        <label className=\"flex items-center gap-2\">\n          <span className=\"max-sm:sr-only\">{labels.rowsPerPage}</span>\n          <span className=\"relative flex items-center\">\n            <select\n              value={pageSize}\n              onChange={(event) =>\n                table.setPageSize(Number(event.target.value))\n              }\n              className={cn(\n                fieldClassName,\n                \"w-auto cursor-pointer appearance-none ps-2.5 pe-7 text-foreground tabular-nums\"\n              )}\n            >\n              {pageSizes.map((size) => (\n                <option key={size} value={size}>\n                  {size}\n                </option>\n              ))}\n            </select>\n            <IconChevronDown\n              aria-hidden\n              className=\"pointer-events-none absolute end-2 size-3.5 text-muted-foreground\"\n            />\n          </span>\n        </label>\n        <div\n          className=\"text-foreground tabular-nums [unicode-bidi:plaintext]\"\n          aria-live=\"polite\"\n        >\n          {formatPage(pageIndex + 1, pageCount)}\n        </div>\n        <div className=\"flex items-center gap-1\">\n          <Button\n            variant=\"outline\"\n            size=\"icon-sm\"\n            className=\"max-sm:hidden\"\n            aria-label={labels.firstPage}\n            disabled={!table.getCanPreviousPage()}\n            onClick={() => table.firstPage()}\n          >\n            <IconChevronsLeft className=\"rtl:-scale-x-100\" />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon-sm\"\n            aria-label={labels.previousPage}\n            disabled={!table.getCanPreviousPage()}\n            onClick={() => table.previousPage()}\n          >\n            <IconChevronLeft className=\"rtl:-scale-x-100\" />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon-sm\"\n            aria-label={labels.nextPage}\n            disabled={!table.getCanNextPage()}\n            onClick={() => table.nextPage()}\n          >\n            <IconChevronRight className=\"rtl:-scale-x-100\" />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon-sm\"\n            className=\"max-sm:hidden\"\n            aria-label={labels.lastPage}\n            disabled={!table.getCanNextPage()}\n            onClick={() => table.lastPage()}\n          >\n            <IconChevronsRight className=\"rtl:-scale-x-100\" />\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport {\n  DataTable,\n  DataTableToolbar,\n  DataTableSearch,\n  DataTableViewOptions,\n  DataTableContent,\n  DataTableColumnHeader,\n  DataTableSelectAll,\n  DataTableSelectRow,\n  DataTablePagination,\n  createDataTableColumns,\n  dataTableFeatures,\n  useDataTable,\n  useDataTableContext,\n}\nexport type {\n  DataTableFeatures,\n  DataTableInstance,\n  DataTablePaginationLabels,\n  UseDataTableOptions,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}