Components
Loading preview...
A simple table component for displaying tabular data.
npx shadcn@latest add https://21st.dev/r/coss.com/table"use client";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import type React from "react";
import { useMemo, useState } from "react";
import {
Table, TableBody, TableCell,
TableFooter, TableHead, TableHeader, TableRow,
} from "@/components/ui/component";
// Checkbox
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props): React.ReactElement {
return (
<CheckboxPrimitive.Root
className={cn(
"relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-[.25rem] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none ring-ring transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[3px] not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
className,
)}
data-slot="checkbox"
{...props}
>
<CheckboxPrimitive.Indicator
className="absolute -inset-px flex items-center justify-center rounded-[.25rem] text-primary-foreground data-unchecked:hidden data-checked:bg-primary data-indeterminate:text-foreground"
render={(props: React.ComponentProps<"span">, state: CheckboxPrimitive.Indicator.State) => (
<span {...props}>
{state.indeterminate ? (
<svg aria-hidden="true" className="size-3.5 sm:size-3" fill="none" height="24" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" viewBox="0 0 24 24" width="24"><path d="M5.252 12h13.496" /></svg>
) : (
<svg aria-hidden="true" className="size-3.5 sm:size-3" fill="none" height="24" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" viewBox="0 0 24 24" width="24"><path d="M5.252 12.7 10.2 18.63 18.748 5.37" /></svg>
)}
</span>
)}
/>
</CheckboxPrimitive.Root>
);
}
// Badge
const badgeVariants = cva(
"relative inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-sm border border-transparent font-medium outline-none",
{
defaultVariants: { size: "default", variant: "default" },
variants: {
size: { default: "h-5.5 min-w-5.5 px-[calc(--spacing(1)-1px)] text-sm sm:h-4.5 sm:min-w-4.5 sm:text-xs" },
variant: { outline: "border-input bg-background text-foreground dark:bg-input/32" },
},
},
);
interface BadgeProps extends useRender.ComponentProps<"span"> {
variant?: VariantProps<typeof badgeVariants>["variant"];
size?: VariantProps<typeof badgeVariants>["size"];
}
function Badge({ className, variant, size, render, ...props }: BadgeProps): React.ReactElement {
return useRender({ defaultTagName: "span", props: mergeProps<"span">({ className: cn(badgeVariants({ className, size, variant })), "data-slot": "badge" }, props), render });
}
// Frame
function Frame({ className, ...props }: React.ComponentProps<"div">): React.ReactElement {
return (
<div className={cn("relative flex flex-col rounded-2xl bg-muted/72 p-1", className)} data-slot="frame" {...props} />
);
}
type Project = {
id: string;
project: string;
status: "Paid" | "Unpaid" | "Pending" | "Failed";
team: string;
budget: number;
};
const data: Project[] = [
{ budget: 12500, id: "1", project: "Website Redesign", status: "Paid", team: "Frontend Team" },
{ budget: 8750, id: "2", project: "Mobile App", status: "Unpaid", team: "Mobile Team" },
{ budget: 5200, id: "3", project: "API Integration", status: "Pending", team: "Backend Team" },
{ budget: 3800, id: "4", project: "Database Migration", status: "Paid", team: "DevOps Team" },
{ budget: 7200, id: "5", project: "User Dashboard", status: "Paid", team: "UX Team" },
{ budget: 2100, id: "6", project: "Security Audit", status: "Failed", team: "Security Team" },
];
const statusColor: Record<Project["status"], string> = {
Paid: "bg-emerald-500", Unpaid: "bg-muted-foreground/64", Pending: "bg-amber-500", Failed: "bg-red-500",
};
const getColumns = (): ColumnDef<Project>[] => [
{
id: "select",
enableSorting: false,
header: ({ table }) => {
const isAll = table.getIsAllPageRowsSelected();
const isSome = table.getIsSomePageRowsSelected();
return (
<Checkbox
aria-label="Select all"
checked={isAll}
indeterminate={isSome && !isAll}
onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
/>
);
},
cell: ({ row }) => (
<Checkbox
aria-label="Select row"
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
onCheckedChange={(v) => row.toggleSelected(!!v)}
/>
),
},
{
accessorKey: "project",
header: "Project",
cell: ({ row }) => <div className="font-medium">{row.getValue("project")}</div>,
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const s = row.getValue("status") as Project["status"];
return (
<Badge variant="outline">
<span aria-hidden="true" className={`size-1.5 rounded-full ${statusColor[s]}`} />
{s}
</Badge>
);
},
},
{ accessorKey: "team", header: "Team" },
{
accessorKey: "budget",
header: () => <div className="text-right">Budget</div>,
cell: ({ row }) => {
const amt = Number.parseFloat(row.getValue("budget"));
return <div className="text-right">{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(amt)}</div>;
},
},
];
export default function Particle() {
const [rowSelection, setRowSelection] = useState({});
const columns = useMemo(() => getColumns(), []);
const table = useReactTable({ columns, data, enableRowSelection: true, getCoreRowModel: getCoreRowModel(), onRowSelectionChange: setRowSelection, state: { rowSelection } });
return (
<div className="flex items-center justify-center w-full min-h-screen bg-background p-8">
<Frame className="w-full max-w-2xl">
<Table>
<TableHeader>
{table.getHeaderGroups().map((hg) => (
<TableRow key={hg.id}>
{hg.headers.map((h) => (
<TableHead key={h.id}>{h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())}</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow data-state={row.getIsSelected() && "selected"} key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={4}>Total Budget</TableCell>
<TableCell className="text-right">$39,550</TableCell>
</TableRow>
</TableFooter>
</Table>
</Frame>
</div>
);
}