Components
npx shadcn@latest add https://21st.dev/r/sean0205/data-grid-tableLoading preview...
import { useEffect, useMemo, useState } from 'react';
import {
Avatar,
AvatarFallback,
AvatarImage,
AvatarIndicator,
AvatarStatus,
avatarStatusVariants,
} from '@/components/ui/avatar';
import { DataGrid, DataGridContainer } from '@/components/ui/data-grid-table';
import { DataGridPagination } from '@/components/ui/data-grid-table';
import {
DataGridTable,
DataGridTableRowSelect,
DataGridTableRowSelectAll,
} from '@/components/ui/data-grid-table';
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
import {
ColumnDef,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
RowSelectionState,
SortingState,
useReactTable,
} from '@tanstack/react-table';
interface IData {
id: string;
name: string;
availability: 'online' | 'away' | 'busy' | 'offline';
avatar: string;
status: 'active' | 'inactive';
flag: string;
email: string;
company: string;
role: string;
joined: string;
location: string;
balance: number;
}
const demoData: IData[] = [
{
id: '1',
name: 'Kathryn Campbell',
availability: 'online',
avatar: 'women/1.jpg',
status: 'active',
flag: '🇺🇸',
email: 'kathryn@apple.com',
company: 'Apple',
role: 'CEO',
joined: '2021-04-15',
location: 'San Francisco, USA',
balance: 5143.03,
},
{
id: '2',
name: 'Robert Smith',
availability: 'away',
avatar: 'men/2.jpg',
status: 'inactive',
flag: '🇬🇧',
email: 'robert@openai.com',
company: 'OpenAI',
role: 'CTO',
joined: '2020-07-20',
location: 'London, UK',
balance: 4321.87,
},
{
id: '3',
name: 'Sophia Johnson',
availability: 'busy',
avatar: 'women/3.jpg',
status: 'active',
flag: '🇨🇦',
email: 'sophia@meta.com',
company: 'Meta',
role: 'Designer',
joined: '2019-03-12',
location: 'Toronto, Canada',
balance: 7654.98,
},
{
id: '4',
name: 'Lucas Walker',
availability: 'offline',
avatar: 'men/4.jpg',
status: 'inactive',
flag: '🇦🇺',
email: 'lucas@tesla.com',
company: 'Tesla',
role: 'Developer',
joined: '2022-01-18',
location: 'Sydney, Australia',
balance: 3456.45,
},
{
id: '5',
name: 'Emily Davis',
availability: 'online',
avatar: 'women/5.jpg',
status: 'active',
flag: '🇩🇪',
email: 'emily@sap.com',
company: 'SAP',
role: 'Lawyer',
joined: '2023-05-23',
location: 'Berlin, Germany',
balance: 9876.54,
},
{
id: '6',
name: 'James Brown',
availability: 'away',
avatar: 'men/6.jpg',
status: 'active',
flag: '🇲🇾',
email: 'james@keenthemes.com',
company: 'Keenthemes',
role: 'Director',
joined: '2018-11-30',
location: 'Kuala Lumpur, MY',
balance: 6214.22,
},
{
id: '7',
name: 'Isabella Martinez',
availability: 'busy',
avatar: 'women/7.jpg',
status: 'inactive',
flag: '🇪🇸',
email: 'isabella@bbva.es',
company: 'BBVA',
role: 'Product Manager',
joined: '2021-06-14',
location: 'Barcelona, Spain',
balance: 5321.77,
},
{
id: '8',
name: 'Benjamin Harris',
availability: 'offline',
avatar: 'men/8.jpg',
status: 'active',
flag: '🇯🇵',
email: 'benjamin@sony.jp',
company: 'Sony',
role: 'Marketing Lead',
joined: '2020-10-22',
location: 'Tokyo, Japan',
balance: 8452.39,
},
{
id: '9',
name: 'Olivia Brown',
availability: 'online',
avatar: 'men/9.jpg',
status: 'active',
flag: '🇫🇷',
email: 'olivia@lvmh.fr',
company: 'LVMH',
role: 'Data Scientist',
joined: '2019-09-17',
location: 'Paris, France',
balance: 7345.1,
},
{
id: '10',
name: 'Michael Clark',
availability: 'away',
avatar: 'women/10.jpg',
status: 'inactive',
flag: '🇮🇹',
email: 'michael@eni.it',
company: 'ENI',
role: 'Engineer',
joined: '2023-02-11',
location: 'Milan, Italy',
balance: 5214.88,
},
{
id: '11',
name: 'Ava Wilson',
availability: 'busy',
avatar: 'women/11.jpg',
status: 'active',
flag: '🇧🇷',
email: 'ava@vale.br',
company: 'Vale',
role: 'Software Engineer',
joined: '2022-12-01',
location: 'Rio de Janeiro, Brazil',
balance: 9421.5,
},
{
id: '12',
name: 'David Young',
availability: 'offline',
avatar: 'men/12.jpg',
status: 'active',
flag: '🇮🇳',
email: 'david@tata.in',
company: 'Tata',
role: 'Sales Manager',
joined: '2020-03-27',
location: 'Mumbai, India',
balance: 4521.67,
},
];
export default function DataGridDemo() {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 5,
});
const [sorting, setSorting] = useState<SortingState>([{ id: 'name', desc: true }]);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [selectedIds, setSelectedIds] = useState<string[]>([]);
useEffect(() => {
const selectedRowIds = Object.keys(rowSelection);
if (selectedRowIds.length > 0) {
setSelectedIds(selectedRowIds);
} else {
setSelectedIds([]);
}
}, [rowSelection]);
const columns = useMemo<ColumnDef<IData>[]>(
() => [
{
accessorKey: 'id',
header: () => <DataGridTableRowSelectAll />,
cell: ({ row }) => <DataGridTableRowSelect row={row} />,
enableSorting: false,
size: 35,
meta: {
headerClassName: '',
cellClassName: '',
},
},
{
accessorKey: 'name',
id: 'name',
header: 'Name',
cell: ({ row }) => {
const availability = row.original.availability as keyof typeof avatarStatusVariants;
return (
<div className="flex items-center gap-3">
<Avatar className="size-8">
<AvatarImage src={`https://randomuser.me/api/portraits/${row.original.avatar}`} alt={row.original.name} />
<AvatarFallback>N</AvatarFallback>
<AvatarIndicator className="-end-2 -bottom-2">
<AvatarStatus variant={availability} className="size-2.5" />
</AvatarIndicator>
</Avatar>
<div className="space-y-px">
<div className="font-medium text-foreground">{row.original.name}</div>
<div className="text-muted-foreground">{row.original.email}</div>
</div>
</div>
);
},
size: 200,
enableSorting: true,
enableHiding: false,
},
{
accessorKey: 'company',
id: 'company',
header: 'Role',
cell: ({ row }) => {
return (
<div className="space-y-0.5">
<div className="font-medium text-foreground">{row.original.role}</div>
<div className="text-muted-foreground">{row.original.company}</div>
</div>
);
},
size: 140,
enableSorting: true,
enableHiding: false,
},
{
accessorKey: 'location',
header: 'Location',
cell: ({ row }) => {
return (
<div className="flex items-center gap-1.5">
{row.original.flag}
<div className="font-medium text-foreground">{row.original.location}</div>
</div>
);
},
size: 180,
meta: {
headerClassName: '',
cellClassName: 'text-start',
},
},
{
accessorKey: 'joined',
header: 'Joined',
cell: (info) => info.getValue() as string,
size: 120,
meta: {
headerClassName: '',
cellClassName: 'font-medium',
},
},
],
[],
);
const table = useReactTable({
columns,
data: demoData,
pageCount: Math.ceil((demoData?.length || 0) / pagination.pageSize),
getRowId: (row: IData) => row.id,
state: {
pagination,
sorting,
rowSelection,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
return (
<DataGrid table={table} recordCount={demoData?.length || 0}>
<div className="w-full space-y-2.5 px-5">
<DataGridContainer>
<ScrollArea>
<DataGridTable />
<ScrollBar orientation="horizontal" />
</ScrollArea>
</DataGridContainer>
<DataGridPagination />
</div>
</DataGrid>
);
}
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...