{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"table-01","type":"registry:component","title":"User Management Table","description":"A comprehensive data table with user management capabilities including sorting, filtering, column visibility toggles, row selection, pagination, and action dropdowns. Built with TanStack Table for performance and flexibility. Features avatar display, status badges, date formatting, and contextual actions. Perfect for admin dashboards, user management systems, and data display applications requiring full CRUD operations.","dependencies":["@tanstack/react-table","lucide-react"],"registryDependencies":["avatar","badge","button","checkbox","dropdown-menu","input","table"],"files":[{"path":"src/registry/blocks/application/data-display/tables/table-01.tsx","content":"\"use client\";\n\nimport {\n  type ColumnDef,\n  type ColumnFiltersState,\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  type SortingState,\n  useReactTable,\n  type VisibilityState,\n} from \"@tanstack/react-table\";\nimport {\n  ArrowUpDown,\n  ChevronDown,\n  MoreHorizontal,\n  Search,\n  Users,\n} from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/registry/lib/utils\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/registry/ui/avatar\";\nimport { Badge } from \"@/registry/ui/badge\";\nimport { Button } from \"@/registry/ui/button\";\nimport { Checkbox } from \"@/registry/ui/checkbox\";\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/registry/ui/dropdown-menu\";\nimport { Input } from \"@/registry/ui/input\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/registry/ui/table\";\n\ninterface User {\n  id: string;\n  name: string;\n  email: string;\n  role: string;\n  status: \"active\" | \"inactive\" | \"pending\";\n  joinDate: string;\n  avatar: string;\n}\n\nexport interface Table01Props {\n  className?: string;\n}\n\nconst content: {\n  header: {\n    title: string;\n    description: string;\n  };\n  searchPlaceholder: string;\n  emptyMessage: string;\n  users: User[];\n  features: {\n    search: boolean;\n    columnToggle: boolean;\n    rowSelection: boolean;\n    pagination: {\n      pageSize: number;\n    };\n  };\n} = {\n  header: {\n    title: \"User Management\",\n    description:\n      \"Manage and view user accounts with advanced filtering and actions\",\n  },\n  searchPlaceholder: \"Search users...\",\n  emptyMessage: \"No users found.\",\n  users: [\n    {\n      id: \"1\",\n      name: \"John Doe\",\n      email: \"john@example.com\",\n      role: \"Admin\",\n      status: \"active\",\n      joinDate: \"2024-01-15\",\n      avatar: \"https://randomuser.me/api/portraits/men/1.jpg\",\n    },\n    {\n      id: \"2\",\n      name: \"Jane Smith\",\n      email: \"jane@example.com\",\n      role: \"Editor\",\n      status: \"active\",\n      joinDate: \"2024-02-20\",\n      avatar: \"https://randomuser.me/api/portraits/women/2.jpg\",\n    },\n    {\n      id: \"3\",\n      name: \"Mike Johnson\",\n      email: \"mike@example.com\",\n      role: \"Viewer\",\n      status: \"inactive\",\n      joinDate: \"2024-01-10\",\n      avatar: \"https://randomuser.me/api/portraits/men/3.jpg\",\n    },\n    {\n      id: \"4\",\n      name: \"Sarah Wilson\",\n      email: \"sarah@example.com\",\n      role: \"Editor\",\n      status: \"pending\",\n      joinDate: \"2024-03-05\",\n      avatar: \"https://randomuser.me/api/portraits/women/4.jpg\",\n    },\n    {\n      id: \"5\",\n      name: \"David Brown\",\n      email: \"david@example.com\",\n      role: \"Admin\",\n      status: \"active\",\n      joinDate: \"2023-12-01\",\n      avatar: \"https://randomuser.me/api/portraits/men/5.jpg\",\n    },\n    {\n      id: \"6\",\n      name: \"Lisa Anderson\",\n      email: \"lisa@example.com\",\n      role: \"Editor\",\n      status: \"active\",\n      joinDate: \"2024-02-28\",\n      avatar: \"https://randomuser.me/api/portraits/women/6.jpg\",\n    },\n    {\n      id: \"7\",\n      name: \"Robert Taylor\",\n      email: \"robert@example.com\",\n      role: \"Viewer\",\n      status: \"inactive\",\n      joinDate: \"2024-01-20\",\n      avatar: \"https://randomuser.me/api/portraits/men/7.jpg\",\n    },\n    {\n      id: \"8\",\n      name: \"Emily Davis\",\n      email: \"emily@example.com\",\n      role: \"Admin\",\n      status: \"pending\",\n      joinDate: \"2024-03-10\",\n      avatar: \"https://randomuser.me/api/portraits/women/8.jpg\",\n    },\n  ],\n  features: {\n    search: true,\n    columnToggle: true,\n    rowSelection: true,\n    pagination: {\n      pageSize: 10,\n    },\n  },\n};\n\nconst getStatusBadgeProps = (status: string) => {\n  switch (status) {\n    case \"active\":\n      return {\n        variant: \"default\" as const,\n        className:\n          \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300\",\n      };\n    case \"inactive\":\n      return {\n        variant: \"secondary\" as const,\n        className:\n          \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300\",\n      };\n    case \"pending\":\n      return {\n        variant: \"outline\" as const,\n        className:\n          \"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300\",\n      };\n    default:\n      return {\n        variant: \"secondary\" as const,\n        className: \"\",\n      };\n  }\n};\n\nconst generateInitials = (name: string): string => {\n  return name\n    .split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .toUpperCase();\n};\n\nexport function Table01({ className }: Table01Props) {\n  const [sorting, setSorting] = React.useState<SortingState>([]);\n  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(\n    [],\n  );\n  const [columnVisibility, setColumnVisibility] =\n    React.useState<VisibilityState>({});\n  const [rowSelection, setRowSelection] = React.useState({});\n\n  const handleCopyUserId = React.useCallback(\n    (userId: string) => (e: React.MouseEvent) => {\n      e.stopPropagation();\n      navigator.clipboard.writeText(userId);\n      console.log(\"User ID copied:\", userId);\n    },\n    [],\n  );\n\n  const handleUserView = React.useCallback(\n    (user: User) => (e: React.MouseEvent) => {\n      e.stopPropagation();\n      console.log(\"View user:\", user);\n    },\n    [],\n  );\n\n  const handleUserEdit = React.useCallback(\n    (user: User) => (e: React.MouseEvent) => {\n      e.stopPropagation();\n      console.log(\"Edit user:\", user);\n    },\n    [],\n  );\n\n  const handleUserDelete = React.useCallback(\n    (user: User) => (e: React.MouseEvent) => {\n      e.stopPropagation();\n      console.log(\"Delete user:\", user);\n    },\n    [],\n  );\n\n  const handleRowClick = (user: User) => (_e: React.MouseEvent) => {\n    console.log(\"Row clicked:\", user);\n  };\n\n  const columns: ColumnDef<User>[] = React.useMemo(() => {\n    const baseColumns: ColumnDef<User>[] = [\n      {\n        accessorKey: \"name\",\n        header: ({ column }) => (\n          <Button\n            variant=\"ghost\"\n            onClick={() => column.toggleSorting(column.getIsSorted() === \"asc\")}\n            className=\"h-auto p-0 font-medium\"\n            data-testid=\"sort-name-button\"\n          >\n            User\n            <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n          </Button>\n        ),\n        cell: ({ row }) => (\n          <div className=\"flex items-center gap-3\" data-testid=\"user-cell\">\n            <Avatar className=\"h-8 w-8\">\n              <AvatarImage\n                src={row.original.avatar}\n                alt={row.getValue(\"name\")}\n              />\n              <AvatarFallback>\n                {generateInitials(row.getValue(\"name\"))}\n              </AvatarFallback>\n            </Avatar>\n            <div className=\"min-w-0\">\n              <div className=\"font-medium truncate\">{row.getValue(\"name\")}</div>\n              <div className=\"text-sm text-muted-foreground truncate\">\n                {row.original.email}\n              </div>\n            </div>\n          </div>\n        ),\n      },\n      {\n        accessorKey: \"role\",\n        header: ({ column }) => (\n          <Button\n            variant=\"ghost\"\n            onClick={() => column.toggleSorting(column.getIsSorted() === \"asc\")}\n            className=\"h-auto p-0 font-medium\"\n            data-testid=\"sort-role-button\"\n          >\n            Role\n            <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n          </Button>\n        ),\n        cell: ({ row }) => (\n          <div className=\"font-medium\" data-testid=\"role-cell\">\n            {row.getValue(\"role\")}\n          </div>\n        ),\n      },\n      {\n        accessorKey: \"status\",\n        header: \"Status\",\n        cell: ({ row }) => {\n          const status = row.getValue(\"status\") as string;\n          const badgeProps = getStatusBadgeProps(status);\n          return (\n            <Badge\n              variant={badgeProps.variant}\n              className={badgeProps.className}\n              data-testid=\"status-badge\"\n            >\n              {status}\n            </Badge>\n          );\n        },\n      },\n      {\n        accessorKey: \"joinDate\",\n        header: ({ column }) => (\n          <Button\n            variant=\"ghost\"\n            onClick={() => column.toggleSorting(column.getIsSorted() === \"asc\")}\n            className=\"h-auto p-0 font-medium\"\n            data-testid=\"sort-date-button\"\n          >\n            Join Date\n            <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n          </Button>\n        ),\n        cell: ({ row }) => {\n          const date = new Date(row.getValue(\"joinDate\"));\n          return (\n            <div className=\"text-sm\" data-testid=\"date-cell\">\n              {date.toLocaleDateString(\"en-US\", {\n                year: \"numeric\",\n                month: \"2-digit\",\n                day: \"2-digit\",\n              })}\n            </div>\n          );\n        },\n      },\n      {\n        id: \"actions\",\n        enableHiding: false,\n        cell: ({ row }) => {\n          const user = row.original;\n\n          return (\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  className=\"h-8 w-8 p-0\"\n                  data-testid=\"actions-menu-trigger\"\n                >\n                  <span className=\"sr-only\">Open menu</span>\n                  <MoreHorizontal className=\"h-4 w-4\" />\n                </Button>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\" data-testid=\"actions-menu\">\n                <DropdownMenuLabel>Actions</DropdownMenuLabel>\n                <DropdownMenuItem\n                  onClick={handleCopyUserId(user.id)}\n                  data-testid=\"copy-id-action\"\n                >\n                  Copy user ID\n                </DropdownMenuItem>\n                <DropdownMenuSeparator />\n                <DropdownMenuItem\n                  onClick={handleUserView(user)}\n                  data-testid=\"view-user-action\"\n                >\n                  View user\n                </DropdownMenuItem>\n                <DropdownMenuItem\n                  onClick={handleUserEdit(user)}\n                  data-testid=\"edit-user-action\"\n                >\n                  Edit user\n                </DropdownMenuItem>\n                <DropdownMenuItem\n                  className=\"text-red-600\"\n                  onClick={handleUserDelete(user)}\n                  data-testid=\"delete-user-action\"\n                >\n                  Delete user\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          );\n        },\n      },\n    ];\n\n    if (content.features.rowSelection) {\n      return [\n        {\n          id: \"select\",\n          header: ({ table }) => (\n            <Checkbox\n              checked={\n                table.getIsAllPageRowsSelected() ||\n                (table.getIsSomePageRowsSelected() && \"indeterminate\")\n              }\n              onCheckedChange={(value) =>\n                table.toggleAllPageRowsSelected(!!value)\n              }\n              aria-label=\"Select all\"\n              data-testid=\"select-all-checkbox\"\n            />\n          ),\n          cell: ({ row }) => (\n            <Checkbox\n              checked={row.getIsSelected()}\n              onCheckedChange={(value) => row.toggleSelected(!!value)}\n              aria-label=\"Select row\"\n              data-testid=\"select-row-checkbox\"\n            />\n          ),\n          enableSorting: false,\n          enableHiding: false,\n        },\n        ...baseColumns,\n      ];\n    }\n\n    return baseColumns;\n  }, [handleCopyUserId, handleUserDelete, handleUserEdit, handleUserView]);\n\n  const table = useReactTable({\n    data: content.users,\n    columns,\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onColumnVisibilityChange: setColumnVisibility,\n    onRowSelectionChange: setRowSelection,\n    initialState: {\n      pagination: {\n        pageSize: content.features.pagination.pageSize,\n      },\n    },\n    state: {\n      sorting,\n      columnFilters,\n      columnVisibility,\n      rowSelection,\n    },\n  });\n\n  return (\n    <div\n      className={cn(\"w-full space-y-6 p-6\", className)}\n      data-testid=\"table-container\"\n    >\n      {/* Header */}\n      <div className=\"space-y-2\">\n        <h2 className=\"text-2xl font-bold tracking-tight\">\n          {content.header.title}\n        </h2>\n        <p className=\"text-muted-foreground\">{content.header.description}</p>\n      </div>\n\n      {/* Controls */}\n      <div className=\"flex items-center justify-between\">\n        <div className=\"flex items-center space-x-2\">\n          {content.features.search && (\n            <div className=\"relative\">\n              <Search className=\"absolute left-2 top-2.5 h-4 w-4 text-muted-foreground\" />\n              <Input\n                placeholder={content.searchPlaceholder}\n                value={\n                  (table.getColumn(\"name\")?.getFilterValue() as string) ?? \"\"\n                }\n                onChange={(event) =>\n                  table.getColumn(\"name\")?.setFilterValue(event.target.value)\n                }\n                className=\"pl-8 max-w-sm\"\n                data-testid=\"search-input\"\n              />\n            </div>\n          )}\n        </div>\n        {content.features.columnToggle && (\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button\n                variant=\"outline\"\n                className=\"ml-auto bg-transparent\"\n                data-testid=\"column-toggle\"\n              >\n                Columns <ChevronDown className=\"ml-2 h-4 w-4\" />\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"end\" data-testid=\"column-toggle-menu\">\n              {table\n                .getAllColumns()\n                .filter((column) => column.getCanHide())\n                .map((column) => (\n                  <DropdownMenuCheckboxItem\n                    key={column.id}\n                    className=\"capitalize\"\n                    checked={column.getIsVisible()}\n                    onCheckedChange={(value) =>\n                      column.toggleVisibility(!!value)\n                    }\n                    data-testid={`toggle-${column.id}-column`}\n                  >\n                    {column.id}\n                  </DropdownMenuCheckboxItem>\n                ))}\n            </DropdownMenuContent>\n          </DropdownMenu>\n        )}\n      </div>\n\n      {/* Table */}\n      <div\n        className=\"rounded-md border border-border\"\n        data-testid=\"table-wrapper\"\n      >\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                {headerGroup.headers.map((header) => (\n                  <TableHead key={header.id}>\n                    {header.isPlaceholder\n                      ? null\n                      : flexRender(\n                          header.column.columnDef.header,\n                          header.getContext(),\n                        )}\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody data-testid=\"table-body\">\n            {table.getRowModel().rows?.length ? (\n              table.getRowModel().rows.map((row) => (\n                <TableRow\n                  key={row.id}\n                  data-state={row.getIsSelected() && \"selected\"}\n                  className=\"hover:bg-muted/50 cursor-pointer\"\n                  onClick={handleRowClick(row.original)}\n                  data-testid=\"table-row\"\n                >\n                  {row.getVisibleCells().map((cell) => (\n                    <TableCell key={cell.id}>\n                      {flexRender(\n                        cell.column.columnDef.cell,\n                        cell.getContext(),\n                      )}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell\n                  colSpan={columns.length}\n                  className=\"h-24 text-center\"\n                  data-testid=\"empty-state\"\n                >\n                  <div className=\"flex flex-col items-center justify-center space-y-2\">\n                    <Users className=\"h-8 w-8 text-muted-foreground\" />\n                    <p className=\"text-muted-foreground\">\n                      {content.emptyMessage}\n                    </p>\n                  </div>\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n\n      {/* Pagination */}\n      <div\n        className=\"flex items-center justify-between space-x-2 py-4\"\n        data-testid=\"pagination-controls\"\n      >\n        <div className=\"flex-1 text-sm text-muted-foreground\">\n          {table.getFilteredSelectedRowModel().rows.length} of{\" \"}\n          {table.getFilteredRowModel().rows.length} row(s) selected.\n        </div>\n        <div className=\"space-x-2\">\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            onClick={() => table.previousPage()}\n            disabled={!table.getCanPreviousPage()}\n            data-testid=\"previous-page-button\"\n          >\n            Previous\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            onClick={() => table.nextPage()}\n            disabled={!table.getCanNextPage()}\n            data-testid=\"next-page-button\"\n          >\n            Next\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n","type":"registry:component"},{"path":"src/registry/lib/utils.ts","content":"import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n","type":"registry:lib"}]}