Componentdata1.56 KB gzipZero dependencies
User Table
User management table with role badges, status dots and remove action.
Preview
Live Preview
| User | Rol | Durum | Action |
|---|---|---|---|
AL Ada a@m.com | admin | Active |
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add user-tableOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { 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.tsxZero 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
| Prop | Type | Description |
|---|---|---|
| users | UserRow[] | |
| onRemove | (id: string) => void |