{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "description": "A generic data table with search, sorting, row selection, CSV export, pagination, row actions, and destructive action confirmation.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "alert-dialog",
    "button",
    "checkbox",
    "dropdown-menu",
    "input",
    "label",
    "select",
    "skeleton",
    "table"
  ],
  "files": [
    {
      "path": "registry/corr/components/data-table.tsx",
      "content": "import {\n  useEffect,\n  useMemo,\n  useState,\n  type DragEvent,\n  type ReactNode,\n} from \"react\"\nimport {\n  ArrowDown,\n  ArrowUp,\n  ArrowUpDown,\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n  GripVertical,\n  MoreHorizontal,\n} from \"lucide-react\"\n\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\"\nimport { generateThreeWordGuard } from \"@/lib/typed-guard\"\n\nexport type DataTableColumn<TData> = {\n  id: string\n  header: ReactNode\n  cell: (row: TData) => ReactNode\n  sortValue?: (row: TData) => string | number | null | undefined\n  sortable?: boolean\n  className?: string\n  width?: string\n  exportHeader?: string\n  exportValue?: (row: TData) => string | number | null | undefined\n  exportable?: boolean\n}\n\nexport type DataTableRowAction<TData> = {\n  label: string\n  onClick: (row: TData) => void | Promise<void>\n  icon?: ReactNode\n  tone?: \"default\" | \"warning\" | \"destructive\"\n  destructive?: boolean\n  disabled?: boolean\n  confirmTitle?: string\n  confirmDescription?: string\n  confirmKeyword?: string\n  confirmKeywordMode?: \"fixed\" | \"random-3-words\"\n}\n\nexport type DataTableRowActionGroup<TData> = {\n  label?: string\n  actions: DataTableRowAction<TData>[]\n}\n\ntype SortDirection = \"asc\" | \"desc\"\nexport type DataTableSortState = {\n  columnId: string\n  direction: SortDirection\n} | null\n\nexport type DataTableToolbarContext<TData> = {\n  selectedRows: TData[]\n  filteredRows: TData[]\n  exportRows: TData[]\n  exportCsv: (fileName?: string) => void\n  clearSelection: () => void\n}\n\nexport type DataTableServerPagination = {\n  page: number\n  pageSize: number\n  total: number\n  onPageChange: (page: number) => void\n  onPageSizeChange: (pageSize: number) => void\n}\n\ntype DataTableProps<TData> = {\n  columns: DataTableColumn<TData>[]\n  data: TData[]\n  getRowId: (row: TData) => string\n  emptyMessage?: string\n  enableRowSelection?: boolean\n  enableSorting?: boolean\n  rowActions?: (row: TData) => DataTableRowAction<TData>[]\n  rowActionGroups?: (row: TData) => DataTableRowActionGroup<TData>[]\n  enableRowReordering?: boolean\n  onRowOrderChange?: (rows: TData[]) => void\n  toolbarActions?: (context: DataTableToolbarContext<TData>) => ReactNode\n  selectionActions?: (context: DataTableToolbarContext<TData>) => ReactNode\n  searchPlaceholder?: string\n  searchableText?: (row: TData) => string\n  exportFileName?: string\n  isLoading?: boolean\n  skeletonRows?: number\n  enablePagination?: boolean\n  pageSizeOptions?: number[]\n  defaultPageSize?: number\n  serverPagination?: DataTableServerPagination\n  searchValue?: string\n  onSearchValueChange?: (value: string) => void\n  sortState?: DataTableSortState\n  onSortStateChange?: (state: DataTableSortState) => void\n}\n\nfunction compareValues(\n  left: string | number | null | undefined,\n  right: string | number | null | undefined\n) {\n  if (left == null && right == null) return 0\n  if (left == null) return -1\n  if (right == null) return 1\n\n  if (typeof left === \"number\" && typeof right === \"number\") {\n    return left - right\n  }\n\n  return String(left).localeCompare(String(right), undefined, {\n    numeric: true,\n    sensitivity: \"base\",\n  })\n}\n\nfunction csvEscape(value: string) {\n  if (value.includes(\",\") || value.includes(\"\\n\") || value.includes('\"')) {\n    return `\"${value.split('\"').join('\"\"')}\"`\n  }\n  return value\n}\n\nfunction formatTableNumber(value: number) {\n  return value.toLocaleString()\n}\n\nfunction formatCellValue(value: ReactNode): ReactNode {\n  if (typeof value === \"number\" && Number.isFinite(value)) {\n    return formatTableNumber(value)\n  }\n  if (typeof value === \"bigint\") {\n    return value.toLocaleString()\n  }\n  return value\n}\n\nexport function DataTable<TData>({\n  columns,\n  data,\n  getRowId,\n  emptyMessage = \"No results.\",\n  enableRowSelection = false,\n  enableSorting = false,\n  rowActions,\n  rowActionGroups,\n  enableRowReordering = false,\n  onRowOrderChange,\n  toolbarActions,\n  selectionActions,\n  searchPlaceholder = \"Search\",\n  searchableText,\n  exportFileName = \"export.csv\",\n  isLoading = false,\n  skeletonRows = 6,\n  enablePagination = true,\n  pageSizeOptions = [5, 10, 15, 20, 50],\n  defaultPageSize = 5,\n  serverPagination,\n  searchValue,\n  onSearchValueChange,\n  sortState,\n  onSortStateChange,\n}: DataTableProps<TData>) {\n  const [search, setSearch] = useState(searchValue ?? \"\")\n  const [internalSortState, setInternalSortState] =\n    useState<DataTableSortState>(null)\n  const [selectedRowIds, setSelectedRowIds] = useState<Record<string, boolean>>(\n    {}\n  )\n  const [pendingDestructiveAction, setPendingDestructiveAction] = useState<{\n    row: TData\n    action: DataTableRowAction<TData>\n  } | null>(null)\n  const [confirmKeywordInput, setConfirmKeywordInput] = useState(\"\")\n  const [confirmKeywordValue, setConfirmKeywordValue] = useState(\"DELETE\")\n  const [draggedRowId, setDraggedRowId] = useState<string | null>(null)\n  const [dragOverRowId, setDragOverRowId] = useState<string | null>(null)\n  const [pageIndex, setPageIndex] = useState(0)\n  const [pageSize, setPageSize] = useState(defaultPageSize)\n  const serverPaginationState = serverPagination ?? null\n  const isServerPagination = Boolean(serverPaginationState)\n  const effectiveSortState = sortState ?? internalSortState\n\n  useEffect(() => {\n    if (searchValue === undefined) return\n    setSearch(searchValue)\n  }, [searchValue])\n\n  const activeSearch = searchValue ?? search\n\n  const filteredData = useMemo(() => {\n    if (isServerPagination) {\n      return data\n    }\n\n    const query = activeSearch.trim().toLowerCase()\n    if (!query || !searchableText) {\n      return data\n    }\n\n    return data.filter((row) =>\n      searchableText(row).toLowerCase().includes(query)\n    )\n  }, [activeSearch, data, isServerPagination, searchableText])\n\n  const sortedData = useMemo(() => {\n    if (isServerPagination) {\n      return filteredData\n    }\n\n    if (!effectiveSortState) {\n      return filteredData\n    }\n\n    const column = columns.find(\n      (item) => item.id === effectiveSortState.columnId\n    )\n    if (!column?.sortValue) {\n      return filteredData\n    }\n\n    const direction = effectiveSortState.direction === \"asc\" ? 1 : -1\n    return [...filteredData].sort(\n      (left, right) =>\n        direction *\n        compareValues(column.sortValue?.(left), column.sortValue?.(right))\n    )\n  }, [columns, effectiveSortState, filteredData, isServerPagination])\n\n  const totalRows = serverPaginationState\n    ? serverPaginationState.total\n    : sortedData.length\n  const currentPageSize = serverPaginationState\n    ? serverPaginationState.pageSize\n    : pageSize\n  const effectiveSkeletonRows = isServerPagination\n    ? currentPageSize\n    : skeletonRows\n  const pageCount = Math.max(1, Math.ceil(totalRows / currentPageSize))\n  const effectivePageIndex = serverPaginationState\n    ? Math.min(Math.max(serverPaginationState.page - 1, 0), pageCount - 1)\n    : Math.min(pageIndex, pageCount - 1)\n  const pagedData = useMemo(() => {\n    if (isServerPagination) {\n      return sortedData\n    }\n    if (!enablePagination) {\n      return sortedData\n    }\n    const start = effectivePageIndex * currentPageSize\n    const end = start + currentPageSize\n    return sortedData.slice(start, end)\n  }, [\n    currentPageSize,\n    effectivePageIndex,\n    enablePagination,\n    isServerPagination,\n    sortedData,\n  ])\n\n  useEffect(() => {\n    if (isServerPagination) {\n      return\n    }\n    if (!enablePagination) {\n      setPageIndex(0)\n      return\n    }\n    if (pageIndex > pageCount - 1) {\n      setPageIndex(Math.max(pageCount - 1, 0))\n    }\n  }, [enablePagination, isServerPagination, pageCount, pageIndex])\n\n  useEffect(() => {\n    if (isServerPagination) {\n      return\n    }\n    setPageIndex(0)\n  }, [isServerPagination, activeSearch, effectiveSortState, pageSize])\n\n  const selectedRows = useMemo(\n    () => data.filter((row) => selectedRowIds[getRowId(row)]),\n    [data, getRowId, selectedRowIds]\n  )\n\n  const exportRows = useMemo(() => {\n    if (selectedRows.length > 0) {\n      return selectedRows\n    }\n    if (activeSearch.trim().length > 0) {\n      return filteredData\n    }\n    return data\n  }, [activeSearch, data, filteredData, selectedRows])\n\n  const allVisibleRowsSelected =\n    pagedData.length > 0 &&\n    pagedData.every((row) => selectedRowIds[getRowId(row)])\n  const someVisibleRowsSelected =\n    !allVisibleRowsSelected &&\n    pagedData.some((row) => selectedRowIds[getRowId(row)])\n\n  const exportableColumns = useMemo(\n    () => columns.filter((column) => column.exportable !== false),\n    [columns]\n  )\n\n  function clearSelection() {\n    setSelectedRowIds({})\n  }\n\n  function downloadCsv(fileName: string, rows: TData[]) {\n    const header = exportableColumns\n      .map((column) =>\n        csvEscape(String(column.exportHeader ?? column.header ?? column.id))\n      )\n      .join(\",\")\n\n    const lines = rows.map((row) => {\n      return exportableColumns\n        .map((column) => {\n          const raw = column.exportValue\n            ? column.exportValue(row)\n            : column.sortValue\n              ? column.sortValue(row)\n              : \"\"\n          return csvEscape(raw == null ? \"\" : String(raw))\n        })\n        .join(\",\")\n    })\n\n    const csv = [header, ...lines].join(\"\\n\")\n    const blob = new Blob([csv], { type: \"text/csv;charset=utf-8;\" })\n    const url = URL.createObjectURL(blob)\n    const anchor = document.createElement(\"a\")\n    anchor.href = url\n    anchor.download = fileName\n    anchor.click()\n    URL.revokeObjectURL(url)\n  }\n\n  function exportCsv(fileName = exportFileName) {\n    downloadCsv(fileName, exportRows)\n  }\n\n  const context: DataTableToolbarContext<TData> = {\n    selectedRows,\n    filteredRows: filteredData,\n    exportRows,\n    exportCsv,\n    clearSelection,\n  }\n\n  function toggleSort(column: DataTableColumn<TData>) {\n    const canSortColumn = isServerPagination\n      ? Boolean(column.sortable)\n      : Boolean(column.sortable && column.sortValue)\n    if (!enableSorting || !canSortColumn) {\n      return\n    }\n\n    const nextSortState: DataTableSortState =\n      !effectiveSortState || effectiveSortState.columnId !== column.id\n        ? { columnId: column.id, direction: \"asc\" }\n        : effectiveSortState.direction === \"asc\"\n          ? { columnId: column.id, direction: \"desc\" }\n          : null\n\n    if (sortState === undefined) {\n      setInternalSortState(nextSortState)\n    }\n    onSortStateChange?.(nextSortState)\n  }\n\n  function reorderRows(activeRowId: string, overRowId: string) {\n    if (activeRowId === overRowId) {\n      return\n    }\n\n    const activeIndex = data.findIndex((row) => getRowId(row) === activeRowId)\n    const overIndex = data.findIndex((row) => getRowId(row) === overRowId)\n\n    if (activeIndex === -1 || overIndex === -1) {\n      return\n    }\n\n    const nextRows = [...data]\n    const [activeRow] = nextRows.splice(activeIndex, 1)\n    nextRows.splice(overIndex, 0, activeRow)\n    onRowOrderChange?.(nextRows)\n  }\n\n  function handleRowDragStart(event: DragEvent<HTMLButtonElement>, row: TData) {\n    const rowId = getRowId(row)\n    setDraggedRowId(rowId)\n    event.dataTransfer.effectAllowed = \"move\"\n    event.dataTransfer.setData(\"text/plain\", rowId)\n  }\n\n  function handleRowDrop(event: DragEvent<HTMLTableRowElement>, row: TData) {\n    event.preventDefault()\n    const activeRowId = event.dataTransfer.getData(\"text/plain\") || draggedRowId\n\n    if (activeRowId) {\n      reorderRows(activeRowId, getRowId(row))\n    }\n\n    setDraggedRowId(null)\n    setDragOverRowId(null)\n  }\n\n  const destructiveKeyword = confirmKeywordValue\n\n  if (isLoading) {\n    return (\n      <div className=\"flex flex-col gap-4\">\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n          <Skeleton className=\"h-10 w-full sm:max-w-sm\" />\n          <div className=\"sm:flex sm:flex-1 sm:justify-center\">\n            <Skeleton className=\"h-10 w-56\" />\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <Skeleton className=\"h-10 w-24\" />\n            <Skeleton className=\"h-10 w-28\" />\n          </div>\n        </div>\n\n        <div className=\"overflow-x-auto rounded-lg border bg-card\">\n          <Table className=\"table-fixed\">\n            <colgroup>\n              {enableRowSelection ? <col style={{ width: \"2.5rem\" }} /> : null}\n              {enableRowReordering ? <col style={{ width: \"2.5rem\" }} /> : null}\n              {columns.map((column) => (\n                <col\n                  key={`skeleton-col-${column.id}`}\n                  style={\n                    column.width\n                      ? { width: column.width, minWidth: column.width }\n                      : undefined\n                  }\n                />\n              ))}\n              <col style={{ width: \"5rem\", minWidth: \"5rem\" }} />\n            </colgroup>\n            <TableHeader>\n              <TableRow>\n                {enableRowSelection ? (\n                  <TableHead className=\"w-10\">\n                    <Skeleton className=\"h-4 w-4\" />\n                  </TableHead>\n                ) : null}\n                {enableRowReordering ? <TableHead className=\"w-10\" /> : null}\n                {columns.map((column) => (\n                  <TableHead key={column.id} className={column.className}>\n                    <Skeleton className=\"h-4 w-24\" />\n                  </TableHead>\n                ))}\n                <TableHead className=\"sticky right-0 w-20 bg-card pr-2 text-right\">\n                  <Skeleton className=\"ml-auto h-4 w-12\" />\n                </TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              {Array.from({ length: effectiveSkeletonRows }).map(\n                (_, rowIndex) => (\n                  <TableRow key={`skeleton-row-${rowIndex}`}>\n                    {enableRowSelection ? (\n                      <TableCell className=\"w-10\">\n                        <Skeleton className=\"h-4 w-4\" />\n                      </TableCell>\n                    ) : null}\n                    {enableRowReordering ? (\n                      <TableCell className=\"w-10\">\n                        <Skeleton className=\"h-4 w-4\" />\n                      </TableCell>\n                    ) : null}\n                    {columns.map((column) => (\n                      <TableCell\n                        key={`skeleton-cell-${rowIndex}-${column.id}`}\n                        className={column.className}\n                      >\n                        <Skeleton className=\"h-5 w-full max-w-40\" />\n                      </TableCell>\n                    ))}\n                    <TableCell className=\"sticky right-0 bg-card text-right\">\n                      <Skeleton className=\"ml-auto h-8 w-8\" />\n                    </TableCell>\n                  </TableRow>\n                )\n              )}\n            </TableBody>\n          </Table>\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <>\n      <div className=\"flex flex-col gap-4\">\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n          <Input\n            value={activeSearch}\n            onChange={(event) => {\n              const value = event.target.value\n              if (onSearchValueChange) {\n                onSearchValueChange(value)\n                return\n              }\n              setSearch(value)\n            }}\n            placeholder={searchPlaceholder}\n            className=\"w-full sm:max-w-sm\"\n          />\n\n          {selectedRows.length > 0 ? (\n            <div className=\"min-w-0\">\n              <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n                <span className=\"text-muted-foreground\">\n                  {formatTableNumber(selectedRows.length)} selected\n                </span>\n                {selectionActions?.(context)}\n              </div>\n            </div>\n          ) : null}\n\n          <div className=\"hidden sm:block sm:flex-1\" />\n\n          {toolbarActions ? (\n            <div className=\"max-w-full min-w-0 overflow-x-auto sm:overflow-visible\">\n              <div className=\"flex w-max max-w-full items-center gap-2\">\n                {toolbarActions(context)}\n              </div>\n            </div>\n          ) : null}\n        </div>\n\n        <div className=\"overflow-x-auto rounded-lg border bg-card\">\n          <Table className=\"table-fixed\">\n            <colgroup>\n              {enableRowSelection ? <col style={{ width: \"2.5rem\" }} /> : null}\n              {enableRowReordering ? <col style={{ width: \"2.5rem\" }} /> : null}\n              {columns.map((column) => (\n                <col\n                  key={`col-${column.id}`}\n                  style={\n                    column.width\n                      ? { width: column.width, minWidth: column.width }\n                      : undefined\n                  }\n                />\n              ))}\n              <col style={{ width: \"5rem\", minWidth: \"5rem\" }} />\n            </colgroup>\n            <TableHeader>\n              <TableRow>\n                {enableRowSelection ? (\n                  <TableHead className=\"w-10\">\n                    <Checkbox\n                      checked={\n                        allVisibleRowsSelected ||\n                        (someVisibleRowsSelected ? \"indeterminate\" : false)\n                      }\n                      onCheckedChange={(checked) => {\n                        const value = Boolean(checked)\n                        setSelectedRowIds((prev) => {\n                          const next = { ...prev }\n                          for (const row of pagedData) {\n                            next[getRowId(row)] = value\n                          }\n                          return next\n                        })\n                      }}\n                      aria-label=\"Select all rows\"\n                    />\n                  </TableHead>\n                ) : null}\n                {enableRowReordering ? <TableHead className=\"w-10\" /> : null}\n\n                {columns.map((column) => {\n                  const isSorted = effectiveSortState?.columnId === column.id\n                  const canSortColumn = isServerPagination\n                    ? Boolean(column.sortable)\n                    : Boolean(column.sortable && column.sortValue)\n\n                  return (\n                    <TableHead key={column.id} className={column.className}>\n                      {enableSorting && canSortColumn ? (\n                        <Button\n                          variant=\"ghost\"\n                          className=\"-ml-2 h-8 px-2\"\n                          onClick={() => toggleSort(column)}\n                        >\n                          {column.header}\n                          {isSorted ? (\n                            effectiveSortState?.direction === \"asc\" ? (\n                              <ArrowUp className=\"ml-1 size-3.5\" />\n                            ) : (\n                              <ArrowDown className=\"ml-1 size-3.5\" />\n                            )\n                          ) : (\n                            <ArrowUpDown className=\"ml-1 size-3.5\" />\n                          )}\n                        </Button>\n                      ) : (\n                        column.header\n                      )}\n                    </TableHead>\n                  )\n                })}\n\n                <TableHead className=\"sticky right-0 w-20 bg-card pr-2 text-right\">\n                  Actions\n                </TableHead>\n              </TableRow>\n            </TableHeader>\n\n            <TableBody>\n              {pagedData.length > 0 ? (\n                pagedData.map((row) => {\n                  const rowId = getRowId(row)\n                  const groups = rowActionGroups\n                    ? rowActionGroups(row)\n                    : rowActions\n                      ? [{ actions: rowActions(row) }]\n                      : []\n\n                  return (\n                    <TableRow\n                      key={rowId}\n                      data-state={\n                        selectedRowIds[rowId] ? \"selected\" : undefined\n                      }\n                      data-dragging={\n                        draggedRowId === rowId ? \"true\" : undefined\n                      }\n                      data-drag-over={\n                        dragOverRowId === rowId ? \"true\" : undefined\n                      }\n                      className=\"group data-[drag-over=true]:bg-muted/60 data-[dragging=true]:opacity-50\"\n                      onDragOver={(event) => {\n                        if (!enableRowReordering || !draggedRowId) {\n                          return\n                        }\n                        event.preventDefault()\n                        event.dataTransfer.dropEffect = \"move\"\n                        setDragOverRowId(rowId)\n                      }}\n                      onDragLeave={() => {\n                        if (dragOverRowId === rowId) {\n                          setDragOverRowId(null)\n                        }\n                      }}\n                      onDrop={(event) => handleRowDrop(event, row)}\n                    >\n                      {enableRowSelection ? (\n                        <TableCell className=\"w-10\">\n                          <Checkbox\n                            checked={Boolean(selectedRowIds[rowId])}\n                            onCheckedChange={(checked) => {\n                              const value = Boolean(checked)\n                              setSelectedRowIds((prev) => ({\n                                ...prev,\n                                [rowId]: value,\n                              }))\n                            }}\n                            aria-label={`Select row ${rowId}`}\n                          />\n                        </TableCell>\n                      ) : null}\n                      {enableRowReordering ? (\n                        <TableCell className=\"w-10\">\n                          <Button\n                            type=\"button\"\n                            variant=\"ghost\"\n                            size=\"icon\"\n                            draggable\n                            onDragStart={(event) =>\n                              handleRowDragStart(event, row)\n                            }\n                            onDragEnd={() => {\n                              setDraggedRowId(null)\n                              setDragOverRowId(null)\n                            }}\n                            className=\"h-7 w-7 cursor-grab text-muted-foreground active:cursor-grabbing\"\n                            aria-label={`Reorder row ${rowId}`}\n                          >\n                            <GripVertical className=\"h-4 w-4\" />\n                          </Button>\n                        </TableCell>\n                      ) : null}\n\n                      {columns.map((column) => (\n                        <TableCell\n                          key={`${rowId}:${column.id}`}\n                          className={column.className}\n                        >\n                          {formatCellValue(column.cell(row))}\n                        </TableCell>\n                      ))}\n\n                      <TableCell className=\"sticky right-0 bg-card text-right group-data-[state=selected]:bg-muted\">\n                        <DropdownMenu>\n                          <DropdownMenuTrigger asChild>\n                            <Button\n                              variant=\"ghost\"\n                              size=\"icon\"\n                              className=\"h-8 w-8 p-0\"\n                            >\n                              <span className=\"sr-only\">Open menu</span>\n                              <MoreHorizontal className=\"h-4 w-4\" />\n                            </Button>\n                          </DropdownMenuTrigger>\n                          <DropdownMenuContent align=\"end\" className=\"w-52\">\n                            {groups.length > 0 ? (\n                              groups.map((group, groupIndex) => (\n                                <div\n                                  key={`${rowId}:group:${group.label ?? groupIndex}`}\n                                >\n                                  {group.label ? (\n                                    <DropdownMenuLabel>\n                                      {group.label}\n                                    </DropdownMenuLabel>\n                                  ) : null}\n                                  {group.actions.map((action) => (\n                                    <DropdownMenuItem\n                                      key={`${rowId}:${action.label}`}\n                                      disabled={action.disabled}\n                                      variant={\n                                        action.tone === \"destructive\" ||\n                                        action.destructive\n                                          ? \"destructive\"\n                                          : \"default\"\n                                      }\n                                      className={\n                                        action.tone === \"destructive\" ||\n                                        action.destructive\n                                          ? \"my-0.5 bg-destructive/10 font-medium text-destructive! focus:bg-destructive/15 focus:text-destructive! dark:bg-destructive/20 dark:focus:bg-destructive/30 [&_svg]:text-destructive!\"\n                                          : \"my-0.5\"\n                                      }\n                                      onSelect={(event) => {\n                                        event.preventDefault()\n                                        if (\n                                          action.destructive ||\n                                          action.tone === \"destructive\"\n                                        ) {\n                                          setPendingDestructiveAction({\n                                            row,\n                                            action,\n                                          })\n                                          setConfirmKeywordInput(\"\")\n                                          setConfirmKeywordValue(\n                                            action.confirmKeywordMode ===\n                                              \"random-3-words\"\n                                              ? generateThreeWordGuard()\n                                              : (action.confirmKeyword ??\n                                                  \"DELETE\")\n                                          )\n                                          return\n                                        }\n                                        void action.onClick(row)\n                                      }}\n                                    >\n                                      {action.icon ? (\n                                        <span className=\"shrink-0\">\n                                          {action.icon}\n                                        </span>\n                                      ) : null}\n                                      {action.label}\n                                    </DropdownMenuItem>\n                                  ))}\n                                  {groupIndex < groups.length - 1 ? (\n                                    <DropdownMenuSeparator />\n                                  ) : null}\n                                </div>\n                              ))\n                            ) : (\n                              <DropdownMenuItem disabled>\n                                No actions\n                              </DropdownMenuItem>\n                            )}\n                          </DropdownMenuContent>\n                        </DropdownMenu>\n                      </TableCell>\n                    </TableRow>\n                  )\n                })\n              ) : (\n                <TableRow>\n                  <TableCell\n                    colSpan={\n                      columns.length +\n                      1 +\n                      (enableRowSelection ? 1 : 0) +\n                      (enableRowReordering ? 1 : 0)\n                    }\n                    className=\"h-24 text-center text-muted-foreground\"\n                  >\n                    {emptyMessage}\n                  </TableCell>\n                </TableRow>\n              )}\n            </TableBody>\n          </Table>\n        </div>\n\n        {enablePagination ? (\n          <div className=\"flex flex-col gap-2 px-2 sm:flex-row sm:items-center sm:justify-between\">\n            <div className=\"text-sm text-muted-foreground\">\n              {totalRows === 0\n                ? \"No rows\"\n                : `Showing ${formatTableNumber(\n                    effectivePageIndex * currentPageSize + 1\n                  )}-${formatTableNumber(\n                    Math.min(\n                      (effectivePageIndex + 1) * currentPageSize,\n                      totalRows\n                    )\n                  )} of ${formatTableNumber(totalRows)}`}\n            </div>\n            <div className=\"flex items-center gap-3\">\n              <div className=\"hidden items-center gap-2 sm:flex\">\n                <Label\n                  htmlFor=\"rows-per-page\"\n                  className=\"text-xs text-muted-foreground\"\n                >\n                  Rows per page\n                </Label>\n                <Select\n                  value={String(currentPageSize)}\n                  onValueChange={(value) => {\n                    const nextSize = Number(value)\n                    if (serverPaginationState) {\n                      serverPaginationState.onPageSizeChange(nextSize)\n                      return\n                    }\n                    setPageSize(nextSize)\n                    setPageIndex(0)\n                  }}\n                >\n                  <SelectTrigger id=\"rows-per-page\" className=\"h-8 w-20\">\n                    <SelectValue />\n                  </SelectTrigger>\n                  <SelectContent>\n                    {pageSizeOptions.map((size) => (\n                      <SelectItem key={size} value={String(size)}>\n                        {size}\n                      </SelectItem>\n                    ))}\n                  </SelectContent>\n                </Select>\n              </div>\n              <div className=\"text-sm text-muted-foreground\">\n                Page {formatTableNumber(effectivePageIndex + 1)} of{\" \"}\n                {formatTableNumber(pageCount)}\n              </div>\n              <div className=\"flex items-center gap-1\">\n                <Button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  className=\"h-8 w-8\"\n                  onClick={() => {\n                    if (serverPaginationState) {\n                      serverPaginationState.onPageChange(1)\n                      return\n                    }\n                    setPageIndex(0)\n                  }}\n                  disabled={effectivePageIndex === 0}\n                >\n                  <span className=\"sr-only\">First page</span>\n                  <ChevronsLeft className=\"h-4 w-4\" />\n                </Button>\n                <Button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  className=\"h-8 w-8\"\n                  onClick={() => {\n                    if (serverPaginationState) {\n                      serverPaginationState.onPageChange(\n                        Math.max(serverPaginationState.page - 1, 1)\n                      )\n                      return\n                    }\n                    setPageIndex((prev) => Math.max(prev - 1, 0))\n                  }}\n                  disabled={effectivePageIndex === 0}\n                >\n                  <span className=\"sr-only\">Previous page</span>\n                  <ChevronLeft className=\"h-4 w-4\" />\n                </Button>\n                <Button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  className=\"h-8 w-8\"\n                  onClick={() => {\n                    if (serverPaginationState) {\n                      serverPaginationState.onPageChange(\n                        Math.min(serverPaginationState.page + 1, pageCount)\n                      )\n                      return\n                    }\n                    setPageIndex((prev) => Math.min(prev + 1, pageCount - 1))\n                  }}\n                  disabled={effectivePageIndex >= pageCount - 1}\n                >\n                  <span className=\"sr-only\">Next page</span>\n                  <ChevronRight className=\"h-4 w-4\" />\n                </Button>\n                <Button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  className=\"h-8 w-8\"\n                  onClick={() => {\n                    if (serverPaginationState) {\n                      serverPaginationState.onPageChange(pageCount)\n                      return\n                    }\n                    setPageIndex(pageCount - 1)\n                  }}\n                  disabled={effectivePageIndex >= pageCount - 1}\n                >\n                  <span className=\"sr-only\">Last page</span>\n                  <ChevronsRight className=\"h-4 w-4\" />\n                </Button>\n              </div>\n            </div>\n          </div>\n        ) : null}\n      </div>\n\n      <AlertDialog\n        open={Boolean(pendingDestructiveAction)}\n        onOpenChange={(open) => {\n          if (!open) {\n            setPendingDestructiveAction(null)\n            setConfirmKeywordInput(\"\")\n            setConfirmKeywordValue(\"DELETE\")\n          }\n        }}\n      >\n        <AlertDialogContent className=\"w-[min(calc(100vw-2rem),34rem)] max-w-lg border ring-0\">\n          <AlertDialogHeader className=\"place-items-stretch text-left\">\n            <AlertDialogTitle>\n              {pendingDestructiveAction?.action.confirmTitle ??\n                \"Confirm destructive action\"}\n            </AlertDialogTitle>\n            <AlertDialogDescription className=\"w-full space-y-3\">\n              <p>\n                {pendingDestructiveAction?.action.confirmDescription ??\n                  \"Type the verification phrase below to confirm this action.\"}\n              </p>\n              <div className=\"w-full rounded-md bg-muted px-3 py-3 text-center font-mono text-sm text-foreground\">\n                {destructiveKeyword}\n              </div>\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <Input\n            className=\"w-full focus-visible:border-input focus-visible:ring-0\"\n            value={confirmKeywordInput}\n            onChange={(event) => setConfirmKeywordInput(event.target.value)}\n            placeholder={`Type ${destructiveKeyword}`}\n          />\n          <AlertDialogFooter>\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <AlertDialogAction\n              variant=\"destructive\"\n              disabled={confirmKeywordInput !== destructiveKeyword}\n              onClick={() => {\n                if (\n                  !pendingDestructiveAction ||\n                  confirmKeywordInput !== destructiveKeyword\n                ) {\n                  return\n                }\n                void pendingDestructiveAction.action.onClick(\n                  pendingDestructiveAction.row\n                )\n                setPendingDestructiveAction(null)\n                setConfirmKeywordInput(\"\")\n                setConfirmKeywordValue(\"DELETE\")\n              }}\n            >\n              Confirm\n            </AlertDialogAction>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n    </>\n  )\n}\n",
      "type": "registry:component"
    },
    {
      "path": "registry/corr/lib/typed-guard.ts",
      "content": "const WORDS = [\n  \"amber\",\n  \"branch\",\n  \"copper\",\n  \"delta\",\n  \"ember\",\n  \"frost\",\n  \"harbor\",\n  \"linen\",\n  \"maple\",\n  \"orbit\",\n  \"pixel\",\n  \"quartz\",\n  \"river\",\n  \"signal\",\n  \"tempo\",\n  \"violet\",\n  \"willow\",\n  \"zinc\",\n]\n\nexport function generateThreeWordGuard() {\n  const values = new Set<string>()\n\n  while (values.size < 3) {\n    values.add(WORDS[Math.floor(Math.random() * WORDS.length)])\n  }\n\n  return Array.from(values).join(\"-\")\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:component"
}