{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "description": "A TanStack Table kit in the shadcn data-table idiom, styled to the Untitled UI look: sorting, faceted and multi-column filters, global search, row selection with a bulk-action bar, pagination, column visibility, pinning, expandable sub-rows, loading skeletons and an empty state.",
  "dependencies": [
    "@tanstack/react-table@^8.21.3",
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/badge",
    "@nx-ui/button",
    "@nx-ui/checkbox",
    "@nx-ui/dropdown-menu",
    "@nx-ui/empty",
    "@nx-ui/input",
    "@nx-ui/popover",
    "@nx-ui/select",
    "@nx-ui/separator",
    "@nx-ui/skeleton",
    "@nx-ui/table",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/data-table/bulk-actions.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type Table } from \"@tanstack/react-table\"\nimport { XClose } from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Separator } from \"@/components/ui/separator\"\n\n/**\n * DataTableBulkActions — the floating bar that appears when one or more rows are\n * selected. Shows the selection count, a clear-selection control, and a slot for\n * bulk-action buttons.\n *\n * Render it just above (or below) the table; it returns `null` when nothing is\n * selected, so it can be mounted unconditionally.\n */\nexport function DataTableBulkActions<TData>({\n  table,\n  children,\n  className,\n}: {\n  table: Table<TData>\n  /** Bulk-action buttons; receive the selected rows via the table instance. */\n  children?: React.ReactNode\n  className?: string\n}) {\n  const selectedCount = table.getFilteredSelectedRowModel().rows.length\n  if (selectedCount === 0) return null\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-wrap items-center gap-3 rounded-xl border border-border bg-background px-4 py-2.5 shadow-sm\",\n        className\n      )}\n    >\n      <div className=\"flex items-center gap-2\">\n        <span className=\"inline-flex h-6 min-w-6 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-primary-foreground tabular-nums\">\n          {selectedCount}\n        </span>\n        <span className=\"text-sm font-medium text-secondary-foreground\">\n          selected\n        </span>\n        <Button\n          variant=\"ghost\"\n          size=\"icon-xs\"\n          aria-label=\"Clear selection\"\n          onClick={() => table.resetRowSelection()}\n          className=\"text-fg-quaternary\"\n        >\n          <XClose />\n        </Button>\n      </div>\n\n      {children ? (\n        <>\n          <Separator orientation=\"vertical\" className=\"h-5\" />\n          <div className=\"flex flex-wrap items-center gap-2\">{children}</div>\n        </>\n      ) : null}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/bulk-actions.tsx"
    },
    {
      "path": "components/blocks/data-table/column-header.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type Column } from \"@tanstack/react-table\"\nimport {\n  ArrowDown,\n  ArrowUp,\n  ChevronSelectorVertical,\n  EyeOff,\n  Pin01,\n  Pin02,\n} from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\n/**\n * DataTableColumnHeader — a sortable, pinnable column header cell.\n *\n * When the column is sortable it renders a button that opens a small menu\n * offering ascending/descending sort, hide, and (optionally) left/right pin.\n * When it is not sortable it renders plain header text with the UUI header\n * treatment (text-xs, medium, quaternary). Drop this into a column def's\n * `header` render.\n */\nexport function DataTableColumnHeader<TData, TValue>({\n  column,\n  title,\n  className,\n  /** Show the pin left/right actions in the menu. */\n  enablePinning = false,\n  align = \"start\",\n}: {\n  column: Column<TData, TValue>\n  title: string\n  className?: string\n  enablePinning?: boolean\n  align?: \"start\" | \"end\"\n}) {\n  const canSort = column.getCanSort()\n  const canHide = column.getCanHide()\n  const sorted = column.getIsSorted()\n  const isPinned = column.getIsPinned()\n\n  if (!canSort && !canHide && !enablePinning) {\n    return (\n      <span\n        className={cn(\n          \"text-xs font-medium text-fg-quaternary\",\n          align === \"end\" && \"block text-right\",\n          className\n        )}\n      >\n        {title}\n      </span>\n    )\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center\",\n        align === \"end\" && \"justify-end\",\n        className\n      )}\n    >\n      <DropdownMenu>\n        <DropdownMenuTrigger\n          className={cn(\n            \"-mx-2 inline-flex h-7 items-center gap-1 rounded-md px-2 text-xs font-medium text-fg-quaternary outline-none transition-colors hover:bg-muted hover:text-secondary-foreground focus-visible:ring-4 focus-visible:ring-ring/25 data-[popup-open]:bg-muted data-[popup-open]:text-secondary-foreground\"\n          )}\n        >\n          <span>{title}</span>\n          {sorted === \"desc\" ? (\n            <ArrowDown className=\"size-3.5\" aria-hidden />\n          ) : sorted === \"asc\" ? (\n            <ArrowUp className=\"size-3.5\" aria-hidden />\n          ) : (\n            <ChevronSelectorVertical\n              className=\"size-3.5 opacity-70\"\n              aria-hidden\n            />\n          )}\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align={align} className=\"w-40\">\n          {canSort ? (\n            <>\n              <DropdownMenuItem onClick={() => column.toggleSorting(false)}>\n                <ArrowUp />\n                Ascending\n              </DropdownMenuItem>\n              <DropdownMenuItem onClick={() => column.toggleSorting(true)}>\n                <ArrowDown />\n                Descending\n              </DropdownMenuItem>\n              {sorted ? (\n                <DropdownMenuItem onClick={() => column.clearSorting()}>\n                  <ChevronSelectorVertical />\n                  Clear sort\n                </DropdownMenuItem>\n              ) : null}\n            </>\n          ) : null}\n\n          {enablePinning ? (\n            <>\n              {(canSort || canHide) && <DropdownMenuSeparator />}\n              {isPinned !== \"left\" ? (\n                <DropdownMenuItem onClick={() => column.pin(\"left\")}>\n                  <Pin01 />\n                  Pin to left\n                </DropdownMenuItem>\n              ) : null}\n              {isPinned !== \"right\" ? (\n                <DropdownMenuItem onClick={() => column.pin(\"right\")}>\n                  <Pin02 />\n                  Pin to right\n                </DropdownMenuItem>\n              ) : null}\n              {isPinned ? (\n                <DropdownMenuItem onClick={() => column.pin(false)}>\n                  <Pin01 />\n                  Unpin\n                </DropdownMenuItem>\n              ) : null}\n            </>\n          ) : null}\n\n          {canHide ? (\n            <>\n              <DropdownMenuSeparator />\n              <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>\n                <EyeOff />\n                Hide column\n              </DropdownMenuItem>\n            </>\n          ) : null}\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/column-header.tsx"
    },
    {
      "path": "components/blocks/data-table/columns.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  type ColumnDef,\n  type FilterFn,\n  type Row,\n} from \"@tanstack/react-table\"\nimport { ChevronDown, ChevronRight } from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\n\n/**\n * Reusable column-definition factories and filter functions for the data-table\n * kit. These keep the per-table column files terse while guaranteeing the\n * selection / expand columns look and behave identically everywhere.\n */\n\n/** A leading checkbox column: header selects the page, cells select the row. */\nexport function selectionColumn<TData>(): ColumnDef<TData> {\n  return {\n    id: \"select\",\n    size: 40,\n    enableSorting: false,\n    enableHiding: false,\n    header: ({ table }) => (\n      <div className=\"flex items-center justify-center pr-0\">\n        <Checkbox\n          checked={table.getIsAllPageRowsSelected()}\n          indeterminate={\n            table.getIsSomePageRowsSelected() &&\n            !table.getIsAllPageRowsSelected()\n          }\n          onCheckedChange={(value) =>\n            table.toggleAllPageRowsSelected(!!value)\n          }\n          aria-label=\"Select all rows on this page\"\n        />\n      </div>\n    ),\n    cell: ({ row }) => (\n      <div className=\"flex items-center justify-center pr-0\">\n        <Checkbox\n          checked={row.getIsSelected()}\n          onCheckedChange={(value) => row.toggleSelected(!!value)}\n          aria-label=\"Select row\"\n        />\n      </div>\n    ),\n  }\n}\n\n/** A leading chevron column that toggles an expandable sub-row. */\nexport function expandColumn<TData>(): ColumnDef<TData> {\n  return {\n    id: \"expand\",\n    size: 36,\n    enableSorting: false,\n    enableHiding: false,\n    header: () => null,\n    cell: ({ row }) =>\n      row.getCanExpand() ? (\n        <button\n          type=\"button\"\n          onClick={row.getToggleExpandedHandler()}\n          aria-label={row.getIsExpanded() ? \"Collapse row\" : \"Expand row\"}\n          aria-expanded={row.getIsExpanded()}\n          className=\"flex size-6 items-center justify-center rounded-md text-fg-quaternary outline-none transition-colors hover:bg-muted hover:text-secondary-foreground focus-visible:ring-4 focus-visible:ring-ring/25\"\n        >\n          <ChevronRight\n            className={cn(\n              \"size-4 transition-transform\",\n              row.getIsExpanded() && \"rotate-90\"\n            )}\n          />\n        </button>\n      ) : null,\n  }\n}\n\n/** A row-expand chevron rendered inline (for use inside a data cell). */\nexport function ExpandChevron<TData>({ row }: { row: Row<TData> }) {\n  return (\n    <ChevronDown\n      className={cn(\n        \"size-4 shrink-0 text-fg-quaternary transition-transform\",\n        !row.getIsExpanded() && \"-rotate-90\"\n      )}\n      aria-hidden\n    />\n  )\n}\n\n/**\n * `arrIncludesSome` — a faceted-filter function: keep the row when the cell\n * value is one of the selected values. Assign it to a column's `filterFn`.\n *\n * Generic so it slots into any `ColumnDef<TData>.filterFn` without a type\n * mismatch — TanStack keys filter functions to the row's data type.\n */\nexport const facetedFilterFn: FilterFn<any> = (\n  row,\n  columnId,\n  filterValue: string[]\n) => {\n  if (!filterValue?.length) return true\n  const value = row.getValue(columnId)\n  return filterValue.includes(String(value))\n}\n\n/**\n * `globalContains` — a case-insensitive global filter that stringifies every\n * cell of the row and checks for a substring match. Pass it as the table's\n * `globalFilterFn`.\n */\nexport const globalFilterFn: FilterFn<any> = (\n  row,\n  _columnId,\n  filterValue: string\n) => {\n  const search = String(filterValue ?? \"\").toLowerCase()\n  if (!search) return true\n  return row.getAllCells().some((cell) => {\n    const value = cell.getValue()\n    if (value == null) return false\n    return String(value).toLowerCase().includes(search)\n  })\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/columns.tsx"
    },
    {
      "path": "components/blocks/data-table/data-table.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  type Cell,\n  type ColumnDef,\n  type Header,\n  flexRender,\n  type Row,\n  type Table as TanstackTable,\n} from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableFooter,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\"\nimport {\n  Empty,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n} from \"@/components/ui/empty\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\n\n/**\n * DataTable — the render surface for a TanStack `Table` instance, styled to the\n * Untitled UI look (rounded-xl card container, gray-50 header, row hover,\n * sticky-pinnable columns).\n *\n * This component is deliberately headless-agnostic: it takes a fully-configured\n * `table` instance (built by the consumer with `useReactTable`) and paints it.\n * The sibling pieces — {@link DataTableToolbar}, {@link DataTablePagination},\n * {@link DataTableViewOptions}, {@link DataTableColumnHeader},\n * {@link DataTableRowActions} and {@link DataTableBulkActions} — compose around\n * it to form the full kit. See the `tables/*` showcase pages for end-to-end use.\n */\n\n/** Resolve the sticky-pinning styles for a pinned column cell/header. */\nfunction getPinningStyles<TData>(\n  column: Cell<TData, unknown>[\"column\"]\n): React.CSSProperties {\n  const isPinned = column.getIsPinned()\n  if (!isPinned) return {}\n  const isLastLeft =\n    isPinned === \"left\" && column.getIsLastColumn(\"left\")\n  const isFirstRight =\n    isPinned === \"right\" && column.getIsFirstColumn(\"right\")\n  return {\n    left: isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined,\n    right: isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined,\n    position: \"sticky\",\n    zIndex: 1,\n    // A hairline shadow marks the pinned edge, matching the UUI \"frozen column\".\n    boxShadow: isLastLeft\n      ? \"inset -1px 0 0 0 var(--color-border)\"\n      : isFirstRight\n        ? \"inset 1px 0 0 0 var(--color-border)\"\n        : undefined,\n  }\n}\n\nexport interface DataTableProps<TData> {\n  /** A configured TanStack table instance (from `useReactTable`). */\n  table: TanstackTable<TData>\n  /** The column definitions — used only to size the empty/loading colspan. */\n  columns: ColumnDef<TData, unknown>[]\n  /** Show a skeleton body instead of rows. */\n  isLoading?: boolean\n  /** Number of skeleton rows to render while loading. */\n  loadingRowCount?: number\n  /** Render an expanded sub-row panel beneath an expanded row. */\n  renderSubComponent?: (props: { row: Row<TData> }) => React.ReactNode\n  /** Callback when a body row is clicked (ignores clicks on interactive cells). */\n  onRowClick?: (row: Row<TData>) => void\n  /** Empty-state content shown when there are no rows and not loading. */\n  emptyState?: React.ReactNode\n  className?: string\n}\n\nexport function DataTable<TData>({\n  table,\n  columns,\n  isLoading = false,\n  loadingRowCount = 8,\n  renderSubComponent,\n  onRowClick,\n  emptyState,\n  className,\n}: DataTableProps<TData>) {\n  const rows = table.getRowModel().rows\n  const colCount = table.getVisibleFlatColumns().length\n\n  return (\n    <div\n      data-slot=\"data-table\"\n      className={cn(\n        \"w-full overflow-hidden rounded-xl border border-border bg-background shadow-xs\",\n        className\n      )}\n    >\n      <div className=\"relative w-full overflow-x-auto\">\n        <Table className=\"border-separate border-spacing-0\">\n          <TableHeader className=\"bg-transparent [&_tr]:border-b-0\">\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow\n                key={headerGroup.id}\n                className=\"border-b-0 hover:bg-transparent\"\n              >\n                {headerGroup.headers.map((header) => (\n                  <DataTableHeadCell key={header.id} header={header} />\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n\n          <TableBody>\n            {isLoading ? (\n              Array.from({ length: loadingRowCount }).map((_, rowIndex) => (\n                <TableRow key={`skeleton-${rowIndex}`} className=\"border-b-0\">\n                  {table.getVisibleFlatColumns().map((column) => (\n                    <TableCell\n                      key={column.id}\n                      className=\"border-b border-border\"\n                      style={getPinningStyles<TData>(\n                        column as unknown as Cell<TData, unknown>[\"column\"]\n                      )}\n                    >\n                      <Skeleton className=\"h-4 w-full max-w-[160px]\" />\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : rows.length === 0 ? (\n              <TableRow className=\"border-b-0 hover:bg-transparent\">\n                <TableCell\n                  colSpan={colCount}\n                  className=\"border-b border-border p-0\"\n                >\n                  {emptyState ?? <DataTableEmpty />}\n                </TableCell>\n              </TableRow>\n            ) : (\n              rows.map((row) => (\n                <React.Fragment key={row.id}>\n                  <TableRow\n                    data-state={row.getIsSelected() ? \"selected\" : undefined}\n                    className={cn(\n                      \"group/row border-b-0 hover:bg-transparent\",\n                      onRowClick && \"cursor-pointer\"\n                    )}\n                    onClick={\n                      onRowClick\n                        ? (event) => {\n                            // Ignore clicks originating from interactive cells\n                            // (checkboxes, buttons, links, menus).\n                            const target = event.target as HTMLElement\n                            if (\n                              target.closest(\n                                \"button, a, input, [role=checkbox], [role=menuitem]\"\n                              )\n                            ) {\n                              return\n                            }\n                            onRowClick(row)\n                          }\n                        : undefined\n                    }\n                  >\n                    {row.getVisibleCells().map((cell) => (\n                      <TableCell\n                        key={cell.id}\n                        data-pinned={cell.column.getIsPinned() || undefined}\n                        className={cn(\n                          \"border-b border-border bg-background transition-colors\",\n                          \"in-data-[state=selected]:bg-muted group-hover/row:bg-muted\",\n                          cell.column.getIsPinned() && \"bg-background\"\n                        )}\n                        style={getPinningStyles<TData>(cell.column)}\n                      >\n                        {flexRender(\n                          cell.column.columnDef.cell,\n                          cell.getContext()\n                        )}\n                      </TableCell>\n                    ))}\n                  </TableRow>\n\n                  {renderSubComponent && row.getIsExpanded() ? (\n                    <TableRow className=\"border-b-0 hover:bg-transparent\">\n                      <TableCell\n                        colSpan={row.getVisibleCells().length}\n                        className=\"border-b border-border bg-bg-secondary_subtle p-0\"\n                      >\n                        {renderSubComponent({ row })}\n                      </TableCell>\n                    </TableRow>\n                  ) : null}\n                </React.Fragment>\n              ))\n            )}\n          </TableBody>\n\n          {table.getFooterGroups().some((group) =>\n            group.headers.some(\n              (header) => header.column.columnDef.footer != null\n            )\n          ) ? (\n            <TableFooter className=\"border-t-0 bg-bg-secondary_subtle\">\n              {table.getFooterGroups().map((footerGroup) => (\n                <TableRow\n                  key={footerGroup.id}\n                  className=\"border-b-0 hover:bg-transparent\"\n                >\n                  {footerGroup.headers.map((header) => (\n                    <TableCell\n                      key={header.id}\n                      className=\"border-t border-border font-semibold text-foreground\"\n                      style={getPinningStyles<TData>(\n                        header.column as unknown as Cell<\n                          TData,\n                          unknown\n                        >[\"column\"]\n                      )}\n                    >\n                      {header.isPlaceholder\n                        ? null\n                        : flexRender(\n                            header.column.columnDef.footer,\n                            header.getContext()\n                          )}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))}\n            </TableFooter>\n          ) : null}\n        </Table>\n      </div>\n    </div>\n  )\n}\n\n/** A single header cell, wiring up pinning styles and the flex-rendered head. */\nfunction DataTableHeadCell<TData>({\n  header,\n}: {\n  header: Header<TData, unknown>\n}) {\n  return (\n    <TableHead\n      data-pinned={header.column.getIsPinned() || undefined}\n      className={cn(\n        \"h-11 border-b border-border bg-bg-secondary_subtle\",\n        header.column.getIsPinned() && \"bg-bg-secondary_subtle\"\n      )}\n      style={getPinningStyles<TData>(\n        header.column as unknown as Cell<TData, unknown>[\"column\"]\n      )}\n    >\n      {header.isPlaceholder\n        ? null\n        : flexRender(header.column.columnDef.header, header.getContext())}\n    </TableHead>\n  )\n}\n\n/** The default empty state, composed from the `ui/empty` primitive. */\nfunction DataTableEmpty() {\n  return (\n    <Empty className=\"border-0 py-16\">\n      <EmptyHeader>\n        <EmptyMedia variant=\"icon\">\n          <SearchOff />\n        </EmptyMedia>\n        <EmptyTitle>No results</EmptyTitle>\n        <EmptyDescription>\n          No records match your filters. Try adjusting or clearing them.\n        </EmptyDescription>\n      </EmptyHeader>\n    </Empty>\n  )\n}\n\nfunction SearchOff() {\n  // Local icon to avoid an extra import in the empty fallback.\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"m21 21-4.35-4.35M11 3a8 8 0 1 0 5.66 13.66\" />\n      <path d=\"m3 3 18 18\" />\n    </svg>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/data-table.tsx"
    },
    {
      "path": "components/blocks/data-table/index.ts",
      "content": "\"use client\"\n\n/**\n * data-table — a production-grade TanStack Table kit in the shadcn data-table\n * idiom, styled to the Untitled UI look.\n *\n * Covers: sorting, multi-column + faceted filters (with badge counts), global\n * search, row selection with a bulk-action bar, pagination, column visibility,\n * column pinning, expanding sub-rows, a loading skeleton state and an empty\n * state (composed from `ui/empty`).\n *\n * The pieces are decoupled: build a table instance with `useReactTable`, paint\n * it with {@link DataTable}, then compose {@link DataTableToolbar},\n * {@link DataTablePagination}, {@link DataTableViewOptions},\n * {@link DataTableBulkActions} and {@link DataTableRowActions} around it. Column\n * defs use {@link DataTableColumnHeader} for sortable/pinnable headers and the\n * {@link selectionColumn} / {@link expandColumn} factories.\n */\n\nexport { DataTable, type DataTableProps } from \"./data-table\"\nexport { DataTableColumnHeader } from \"./column-header\"\nexport {\n  DataTableToolbar,\n  DataTableFacetedFilter,\n  type FacetedFilterConfig,\n  type FacetedFilterOption,\n} from \"./toolbar\"\nexport { DataTableViewOptions } from \"./view-options\"\nexport { DataTablePagination } from \"./pagination\"\nexport { DataTableBulkActions } from \"./bulk-actions\"\nexport { DataTableRowActions, type RowAction } from \"./row-actions\"\nexport {\n  selectionColumn,\n  expandColumn,\n  ExpandChevron,\n  facetedFilterFn,\n  globalFilterFn,\n} from \"./columns\"\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/index.ts"
    },
    {
      "path": "components/blocks/data-table/pagination.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type Table } from \"@tanstack/react-table\"\nimport {\n  ChevronLeft,\n  ChevronLeftDouble,\n  ChevronRight,\n  ChevronRightDouble,\n} from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\n\n/**\n * DataTablePagination — the table footer with a page-size selector, a\n * human-readable row range (\"1–10 of 128\"), an optional selection count, and\n * first/prev/next/last controls.\n */\nexport function DataTablePagination<TData>({\n  table,\n  pageSizeOptions = [10, 20, 30, 50],\n  /** Show \"N of M row(s) selected\" on the left. */\n  showSelectionCount = true,\n  className,\n}: {\n  table: Table<TData>\n  pageSizeOptions?: number[]\n  showSelectionCount?: boolean\n  className?: string\n}) {\n  const { pageIndex, pageSize } = table.getState().pagination\n  const totalRows = table.getFilteredRowModel().rows.length\n  const selectedCount = table.getFilteredSelectedRowModel().rows.length\n  const firstRow = totalRows === 0 ? 0 : pageIndex * pageSize + 1\n  const lastRow = Math.min((pageIndex + 1) * pageSize, totalRows)\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-col items-center justify-between gap-4 border-t border-border px-4 py-3 sm:flex-row sm:px-6\",\n        className\n      )}\n    >\n      <div className=\"flex-1 text-sm text-muted-foreground\">\n        {showSelectionCount && selectedCount > 0 ? (\n          <span>\n            {selectedCount} of {totalRows} row{totalRows === 1 ? \"\" : \"s\"}{\" \"}\n            selected\n          </span>\n        ) : (\n          <span className=\"tabular-nums\">\n            {firstRow}\n            {\"–\"}\n            {lastRow} of {totalRows}\n          </span>\n        )}\n      </div>\n\n      <div className=\"flex items-center gap-4 sm:gap-6\">\n        <div className=\"flex items-center gap-2\">\n          <p className=\"text-sm font-medium text-secondary-foreground\">\n            Rows per page\n          </p>\n          <Select\n            value={`${pageSize}`}\n            onValueChange={(value) => table.setPageSize(Number(value))}\n          >\n            <SelectTrigger size=\"sm\" className=\"w-[4.5rem]\">\n              <SelectValue placeholder={`${pageSize}`} />\n            </SelectTrigger>\n            <SelectContent>\n              {pageSizeOptions.map((option) => (\n                <SelectItem key={option} value={`${option}`}>\n                  {option}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n\n        <div className=\"flex items-center gap-1\">\n          <span className=\"mr-2 text-sm font-medium text-secondary-foreground tabular-nums\">\n            Page {pageIndex + 1} of {Math.max(1, table.getPageCount())}\n          </span>\n          <Button\n            variant=\"secondary\"\n            size=\"icon-sm\"\n            aria-label=\"First page\"\n            onClick={() => table.firstPage()}\n            disabled={!table.getCanPreviousPage()}\n          >\n            <ChevronLeftDouble />\n          </Button>\n          <Button\n            variant=\"secondary\"\n            size=\"icon-sm\"\n            aria-label=\"Previous page\"\n            onClick={() => table.previousPage()}\n            disabled={!table.getCanPreviousPage()}\n          >\n            <ChevronLeft />\n          </Button>\n          <Button\n            variant=\"secondary\"\n            size=\"icon-sm\"\n            aria-label=\"Next page\"\n            onClick={() => table.nextPage()}\n            disabled={!table.getCanNextPage()}\n          >\n            <ChevronRight />\n          </Button>\n          <Button\n            variant=\"secondary\"\n            size=\"icon-sm\"\n            aria-label=\"Last page\"\n            onClick={() => table.lastPage()}\n            disabled={!table.getCanNextPage()}\n          >\n            <ChevronRightDouble />\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/pagination.tsx"
    },
    {
      "path": "components/blocks/data-table/row-actions.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { DotsHorizontal } from \"@untitledui/icons\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\n/** A single row action; a `separator` entry renders a divider instead. */\nexport type RowAction =\n  | {\n      type?: \"item\"\n      label: string\n      icon?: React.ComponentType<{ className?: string }>\n      shortcut?: string\n      destructive?: boolean\n      onSelect?: () => void\n      disabled?: boolean\n    }\n  | { type: \"separator\" }\n  | { type: \"label\"; label: string }\n\n/**\n * DataTableRowActions — the trailing \"⋯\" menu on each table row.\n *\n * Renders a compact icon-button trigger and a dropdown built from a declarative\n * `actions` array (items, separators and section labels). Destructive items get\n * the destructive treatment.\n */\nexport function DataTableRowActions({\n  actions,\n  label = \"Open row actions\",\n}: {\n  actions: RowAction[]\n  label?: string\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            variant=\"ghost\"\n            size=\"icon-sm\"\n            aria-label={label}\n            className=\"text-fg-quaternary data-[popup-open]:bg-muted\"\n          >\n            <DotsHorizontal />\n          </Button>\n        }\n      />\n      <DropdownMenuContent align=\"end\" className=\"w-44\">\n        {renderActions(actions)}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\n/** A single item entry — one interactive row in the menu. */\nfunction ActionItem({\n  action,\n}: {\n  action: Extract<RowAction, { type?: \"item\" }>\n}) {\n  const Icon = action.icon\n  return (\n    <DropdownMenuItem\n      variant={action.destructive ? \"destructive\" : \"default\"}\n      disabled={action.disabled}\n      onClick={() => action.onSelect?.()}\n    >\n      {Icon ? <Icon /> : null}\n      {action.label}\n      {action.shortcut ? (\n        <DropdownMenuShortcut>{action.shortcut}</DropdownMenuShortcut>\n      ) : null}\n    </DropdownMenuItem>\n  )\n}\n\n/**\n * Render the declarative actions array into menu parts. A `label` entry opens a\n * DropdownMenuGroup that captures the label and every following item up to the\n * next label or separator — Base UI requires a GroupLabel to live inside a\n * Group, so a bare label would otherwise throw when the menu mounts. Items with\n * no preceding label render directly (items need no group).\n */\nfunction renderActions(actions: RowAction[]): React.ReactNode {\n  const nodes: React.ReactNode[] = []\n  let index = 0\n\n  while (index < actions.length) {\n    const action = actions[index]\n\n    if (action.type === \"separator\") {\n      nodes.push(<DropdownMenuSeparator key={`sep-${index}`} />)\n      index += 1\n      continue\n    }\n\n    if (action.type === \"label\") {\n      const groupChildren: React.ReactNode[] = [\n        <DropdownMenuLabel key={`label-${index}`}>\n          {action.label}\n        </DropdownMenuLabel>,\n      ]\n      let cursor = index + 1\n      while (cursor < actions.length) {\n        const next = actions[cursor]\n        if (next.type === \"label\" || next.type === \"separator\") break\n        groupChildren.push(<ActionItem key={`item-${cursor}`} action={next} />)\n        cursor += 1\n      }\n      nodes.push(\n        <DropdownMenuGroup key={`group-${index}`}>\n          {groupChildren}\n        </DropdownMenuGroup>\n      )\n      index = cursor\n      continue\n    }\n\n    nodes.push(<ActionItem key={`item-${index}`} action={action} />)\n    index += 1\n  }\n\n  return nodes\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/row-actions.tsx"
    },
    {
      "path": "components/blocks/data-table/toolbar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type Column, type Table } from \"@tanstack/react-table\"\nimport { CheckSquare, PlusCircle, SearchLg, XClose } from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport { Input } from \"@/components/ui/input\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\n\n/** A selectable option for a faceted filter. */\nexport interface FacetedFilterOption {\n  label: string\n  value: string\n  icon?: React.ComponentType<{ className?: string }>\n}\n\nexport interface FacetedFilterConfig {\n  /** The column id to filter on. */\n  columnId: string\n  /** The trigger label, e.g. \"Status\". */\n  title: string\n  options: FacetedFilterOption[]\n}\n\n/**\n * DataTableToolbar — the row above the table: a global search input, one or\n * more faceted (multi-select) filters showing badge counts of the active\n * selection, and a \"Clear\" button that appears when any filter is active.\n */\nexport function DataTableToolbar<TData>({\n  table,\n  /** Placeholder for the global search input. */\n  searchPlaceholder = \"Search…\",\n  /** Bind the search box to the table's global filter (default) or a column. */\n  searchColumnId,\n  facetedFilters = [],\n  children,\n  className,\n}: {\n  table: Table<TData>\n  searchPlaceholder?: string\n  searchColumnId?: string\n  facetedFilters?: FacetedFilterConfig[]\n  /** Trailing content, e.g. the view-options / export buttons. */\n  children?: React.ReactNode\n  className?: string\n}) {\n  const isFiltered =\n    table.getState().columnFilters.length > 0 ||\n    (table.getState().globalFilter?.length ?? 0) > 0\n\n  const searchValue = searchColumnId\n    ? ((table.getColumn(searchColumnId)?.getFilterValue() as string) ?? \"\")\n    : ((table.getState().globalFilter as string) ?? \"\")\n\n  const setSearch = (value: string) => {\n    if (searchColumnId) {\n      table.getColumn(searchColumnId)?.setFilterValue(value)\n    } else {\n      table.setGlobalFilter(value)\n    }\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\",\n        className\n      )}\n    >\n      <div className=\"flex flex-1 flex-wrap items-center gap-2\">\n        <div className=\"relative w-full sm:max-w-xs\">\n          <SearchLg\n            className=\"pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-fg-quaternary\"\n            aria-hidden\n          />\n          <Input\n            value={searchValue}\n            onChange={(event) => setSearch(event.target.value)}\n            placeholder={searchPlaceholder}\n            className=\"h-9 pl-9 text-sm\"\n            aria-label=\"Search\"\n          />\n        </div>\n\n        {facetedFilters.map((filter) => {\n          const column = table.getColumn(filter.columnId)\n          if (!column) return null\n          return (\n            <DataTableFacetedFilter\n              key={filter.columnId}\n              column={column}\n              title={filter.title}\n              options={filter.options}\n            />\n          )\n        })}\n\n        {isFiltered ? (\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            onClick={() => {\n              table.resetColumnFilters()\n              table.setGlobalFilter(\"\")\n            }}\n            className=\"text-secondary-foreground\"\n          >\n            Clear\n            <XClose />\n          </Button>\n        ) : null}\n      </div>\n\n      {children ? (\n        <div className=\"flex items-center gap-2\">{children}</div>\n      ) : null}\n    </div>\n  )\n}\n\n/**\n * DataTableFacetedFilter — a popover multi-select for a single column, with a\n * badge showing the count / labels of the active selection. Uses the column's\n * faceted unique-value counts to show a per-option tally.\n */\nexport function DataTableFacetedFilter<TData, TValue>({\n  column,\n  title,\n  options,\n}: {\n  column: Column<TData, TValue>\n  title: string\n  options: FacetedFilterOption[]\n}) {\n  const facets = column.getFacetedUniqueValues()\n  const selectedValues = new Set(\n    (column.getFilterValue() as string[]) ?? []\n  )\n\n  const toggle = (value: string) => {\n    const next = new Set(selectedValues)\n    if (next.has(value)) {\n      next.delete(value)\n    } else {\n      next.add(value)\n    }\n    const filterValues = Array.from(next)\n    column.setFilterValue(filterValues.length ? filterValues : undefined)\n  }\n\n  return (\n    <Popover>\n      <PopoverTrigger\n        render={\n          <Button variant=\"secondary\" size=\"sm\" className=\"border-dashed\">\n            <PlusCircle />\n            {title}\n            {selectedValues.size > 0 ? (\n              <>\n                <Separator\n                  orientation=\"vertical\"\n                  className=\"mx-0.5 h-4\"\n                />\n                <Badge\n                  variant=\"secondary\"\n                  className=\"rounded-sm px-1 font-normal lg:hidden\"\n                >\n                  {selectedValues.size}\n                </Badge>\n                <div className=\"hidden gap-1 lg:flex\">\n                  {selectedValues.size > 2 ? (\n                    <Badge\n                      variant=\"secondary\"\n                      className=\"rounded-sm px-1 font-normal\"\n                    >\n                      {selectedValues.size} selected\n                    </Badge>\n                  ) : (\n                    options\n                      .filter((option) => selectedValues.has(option.value))\n                      .map((option) => (\n                        <Badge\n                          key={option.value}\n                          variant=\"secondary\"\n                          className=\"rounded-sm px-1 font-normal\"\n                        >\n                          {option.label}\n                        </Badge>\n                      ))\n                  )}\n                </div>\n              </>\n            ) : null}\n          </Button>\n        }\n      />\n      <PopoverContent className=\"w-56 p-0\" align=\"start\">\n        <div className=\"flex items-center gap-2 border-b border-border px-3 py-2.5\">\n          <CheckSquare className=\"size-4 text-fg-quaternary\" aria-hidden />\n          <span className=\"text-sm font-medium text-secondary-foreground\">\n            {title}\n          </span>\n        </div>\n        <div className=\"max-h-64 overflow-y-auto p-1\">\n          {options.map((option) => {\n            const isSelected = selectedValues.has(option.value)\n            const Icon = option.icon\n            const count = facets.get(option.value)\n            return (\n              <button\n                key={option.value}\n                type=\"button\"\n                onClick={() => toggle(option.value)}\n                className=\"flex w-full cursor-default items-center gap-2 rounded-md px-2.5 py-2 text-sm font-medium text-secondary-foreground outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent\"\n                aria-pressed={isSelected}\n              >\n                <Checkbox\n                  checked={isSelected}\n                  className=\"pointer-events-none\"\n                  tabIndex={-1}\n                />\n                {Icon ? (\n                  <Icon className=\"size-4 text-fg-quaternary\" />\n                ) : null}\n                <span className=\"flex-1 text-left\">{option.label}</span>\n                {count ? (\n                  <span className=\"font-mono text-xs text-muted-foreground tabular-nums\">\n                    {count}\n                  </span>\n                ) : null}\n              </button>\n            )\n          })}\n        </div>\n        {selectedValues.size > 0 ? (\n          <div className=\"border-t border-border p-1\">\n            <button\n              type=\"button\"\n              onClick={() => column.setFilterValue(undefined)}\n              className=\"w-full rounded-md px-2.5 py-2 text-center text-sm font-medium text-secondary-foreground outline-none hover:bg-accent focus-visible:bg-accent\"\n            >\n              Clear filters\n            </button>\n          </div>\n        ) : null}\n      </PopoverContent>\n    </Popover>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/toolbar.tsx"
    },
    {
      "path": "components/blocks/data-table/view-options.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type Table } from \"@tanstack/react-table\"\nimport { Columns03 } from \"@untitledui/icons\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\n/** Title-case a camelCase / snake column id for the visibility menu label. */\nfunction humaniseColumnId(id: string): string {\n  return id\n    .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n    .replace(/[_-]+/g, \" \")\n    .replace(/^\\w/, (character) => character.toUpperCase())\n}\n\n/**\n * DataTableViewOptions — the \"Columns\" dropdown that toggles column visibility.\n *\n * Lists every hideable column as a checkbox item. Column display names come\n * from an optional `columnLabels` map, falling back to a humanised column id.\n */\nexport function DataTableViewOptions<TData>({\n  table,\n  columnLabels,\n}: {\n  table: Table<TData>\n  columnLabels?: Record<string, string>\n}) {\n  const columns = table\n    .getAllColumns()\n    .filter(\n      (column) =>\n        typeof column.accessorFn !== \"undefined\" && column.getCanHide()\n    )\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button variant=\"secondary\" size=\"sm\">\n            <Columns03 />\n            Columns\n          </Button>\n        }\n      />\n      <DropdownMenuContent align=\"end\" className=\"w-48\">\n        <DropdownMenuGroup>\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              // Keep the menu open across multiple toggles.\n              closeOnClick={false}\n            >\n              {columnLabels?.[column.id] ?? humaniseColumnId(column.id)}\n            </DropdownMenuCheckboxItem>\n          ))}\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/data-table/view-options.tsx"
    }
  ],
  "type": "registry:block"
}