Componentdata1.56 KB gzipZero dependencies

User Table

User management table with role badges, status dots and remove action.

Preview

Live Preview
UserRolDurumAction
AL

Ada

a@m.com

adminActive

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add user-table

Or install the full npm package:

npm install bigbullui
import { UserTable } from "@/components/ui/user-table";

<UserTable />

Usage guidance

Use User Table when a ticket stub surface needs User management table with role badges, status dots and remove action. Keep props minimal and prefer composition with sibling stubs.

Avoid User Table for long form flows or dense data grids where a dedicated layout block fits better.

Source code

user-table.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export type UserStatus = "active" | "invited" | "suspended";

export interface UserRow {
  id: string;
  name: string;
  initials: string;
  email: string;
  role: "admin" | "editor" | "member" | "viewer";
  status: UserStatus;
}

export interface UserTableProps extends React.HTMLAttributes<HTMLDivElement> {
  users: UserRow[];
  onRemove?: (id: string) => void;
}

const ROLE_TONE: Record<UserRow["role"], string> = {
  admin: "border-accent/60 bg-accent/10 text-accent",
  editor: "border-info/50 bg-info/10 text-info",
  member: "border-border bg-secondary text-secondary-foreground",
  viewer: "border-border/60 bg-transparent text-muted-foreground",
};

const STATUS_TONE: Record<UserStatus, string> = {
  active: "text-success",
  invited: "text-warning",
  suspended: "text-destructive",
};

const STATUS_LABEL: Record<UserStatus, string> = { active: "Active", invited: "Invited", suspended: "Suspended" };

/** User management table: role badge + status + remove action. */
export function UserTable({ users, onRemove, className, ...props }: UserTableProps) {
  const [removed, setRemoved] = React.useState<string[]>([]);
  const visible = users.filter((u) => !removed.includes(u.id));

  return (
    <div className={cn("w-full max-w-2xl overflow-hidden rounded-lg border border-border", className)} {...props}>
      <style>{`
        @keyframes userRowIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
        @keyframes userRowOut { to { opacity: 0; transform: translateX(24px); } }
      `}</style>
      <div className="overflow-x-auto">
        <table className="w-full min-w-max text-sm">
          <thead>
            <tr className="border-b border-border bg-secondary/60 font-mono text-[10px] uppercase tracking-[0.15em] text-muted-foreground">
              <th className="px-4 py-2.5 text-start font-medium">User</th>
              <th className="px-4 py-2.5 text-start font-medium">Rol</th>
              <th className="px-4 py-2.5 text-start font-medium">Durum</th>
              <th className="px-4 py-2.5 text-right font-medium">Action</th>
            </tr>
          </thead>
          <tbody>
            {visible.map((u, idx) => (
              <tr
                key={u.id}
                className="border-b border-border/50 last:border-0 animate-[userRowIn_0.3s_ease-out_both] motion-reduce:animate-none transition-colors hover:bg-secondary/30 motion-reduce:transition-none"
                style={{ animationDelay: `${idx * 50}ms` }}
              >
                <td className="px-4 py-2.5">
                  <div className="flex items-center gap-2.5">
                    <span className="flex size-8 shrink-0 items-center justify-center rounded-full border border-dashed border-border bg-secondary font-mono text-[10px] font-bold">
                      {u.initials}
                    </span>
                    <div className="min-w-0">
                      <p className="truncate font-medium">{u.name}</p>
                      <p className="truncate text-xs text-muted-foreground">{u.email}</p>
                    </div>
                  </div>
                </td>
                <td className="px-4 py-2.5">
                  <span className={cn("rounded-full border px-2 py-0.5 font-mono text-[9px] uppercase tracking-wider", ROLE_TONE[u.role])}>
                    {u.role}
                  </span>
                </td>
                <td className="px-4 py-2.5">
                  <span className={cn("inline-flex items-center gap-1.5 text-xs", STATUS_TONE[u.status])}>
                    <span className="size-1.5 rounded-full bg-current" aria-hidden="true" />
                    {STATUS_LABEL[u.status]}
                  </span>
                </td>
                <td className="px-4 py-2.5 text-right">
                  <button
                    onClick={() => { setRemoved((r) => [...r, u.id]); onRemove?.(u.id); }}
                    className="rounded-sm border border-dashed border-border px-2 py-1 font-mono text-[9px] uppercase tracking-wider text-muted-foreground transition-colors hover:border-destructive hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring motion-reduce:transition-none"
                    style={removed.includes(u.id) ? { animation: "userRowOut 0.25s ease-in both" } : undefined}
                  >
                    KALDIR
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      {visible.length === 0 && (
        <p className="p-6 text-center text-sm text-muted-foreground">All users have been removed.</p>
      )}
    </div>
  );
}

Props

PropTypeDescription
usersUserRow[]
onRemove(id: string) => void