Components
npx shadcn@latest add https://21st.dev/r/originui/tableLoading preview...
"use client";
import { cn } from "@/lib/utils";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from "@tanstack/react-table";
import { ChevronDown, ChevronUp } from "lucide-react";
import { useEffect, useState } from "react";
type Item = {
id: string;
name: string;
email: string;
location: string;
flag: string;
status: "Active" | "Inactive" | "Pending";
balance: number;
department: string;
role: string;
joinDate: string;
lastActive: string;
performance: "Excellent" | "Good" | "Average" | "Poor";
};
const columns: ColumnDef<Item>[] = [
{
header: "Name",
accessorKey: "name",
cell: ({ row }) => <div className="truncate font-medium">{row.getValue("name")}</div>,
sortUndefined: "last",
sortDescFirst: false,
},
{
header: "Email",
accessorKey: "email",
},
{
header: "Location",
accessorKey: "location",
cell: ({ row }) => (
<div className="truncate">
<span className="text-lg leading-none">{row.original.flag}</span> {row.getValue("location")}
</div>
),
},
{
header: "Status",
accessorKey: "status",
},
{
header: "Balance",
accessorKey: "balance",
cell: ({ row }) => {
const amount = parseFloat(row.getValue("balance"));
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
return formatted;
},
},
{
header: "Department",
accessorKey: "department",
},
{
header: "Role",
accessorKey: "role",
},
{
header: "Join Date",
accessorKey: "joinDate",
},
{
header: "Last Active",
accessorKey: "lastActive",
},
{
header: "Performance",
accessorKey: "performance",
},
];
function Component() {
const [data, setData] = useState<Item[]>([]);
const [sorting, setSorting] = useState<SortingState>([
{
id: "name",
desc: false,
},
]);
useEffect(() => {
async function fetchPosts() {
const res = await fetch(
"https://res.cloudinary.com/dlzlfasou/raw/upload/users-01_fertyx.json",
);
const data = await res.json();
setData(data.slice(0, 5)); // Limit to 5 items
}
fetchPosts();
}, []);
const table = useReactTable({
data,
columns,
columnResizeMode: "onChange",
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
state: {
sorting,
},
enableSortingRemoval: false,
});
return (
<div className="bg-background max-w-[1000px]">
<Table
className="table-fixed"
style={{
width: table.getCenterTotalSize(),
}}
>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-muted/50">
{headerGroup.headers.map((header) => {
return (
<TableHead
key={header.id}
className="relative h-10 select-none border-t [&>.cursor-col-resize]:last:opacity-0"
aria-sort={
header.column.getIsSorted() === "asc"
? "ascending"
: header.column.getIsSorted() === "desc"
? "descending"
: "none"
}
{...{
colSpan: header.colSpan,
style: {
width: header.getSize(),
},
}}
>
{header.isPlaceholder ? null : (
<div
className={cn(
header.column.getCanSort() &&
"flex h-full cursor-pointer select-none items-center justify-between gap-2",
)}
onClick={header.column.getToggleSortingHandler()}
onKeyDown={(e) => {
// Enhanced keyboard handling for sorting
if (header.column.getCanSort() && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
header.column.getToggleSortingHandler()?.(e);
}
}}
tabIndex={header.column.getCanSort() ? 0 : undefined}
>
<span className="truncate">
{flexRender(header.column.columnDef.header, header.getContext())}
</span>
{{
asc: (
<ChevronUp
className="shrink-0 opacity-60"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
),
desc: (
<ChevronDown
className="shrink-0 opacity-60"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
),
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
{header.column.getCanResize() && (
<div
{...{
onDoubleClick: () => header.column.resetSize(),
onMouseDown: header.getResizeHandler(),
onTouchStart: header.getResizeHandler(),
className:
"absolute top-0 h-full w-4 cursor-col-resize user-select-none touch-none -right-2 z-10 flex justify-center before:absolute before:w-px before:inset-y-0 before:bg-border before:translate-x-px",
}}
/>
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="truncate">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<p className="mt-4 text-center text-sm text-muted-foreground">
Resizable and sortable columns made with{" "}
<a
className="underline hover:text-foreground"
href="https://tanstack.com/table"
target="_blank"
rel="noopener noreferrer"
>
TanStack Table
</a>
</p>
</div>
);
}
export { Component }Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...