How to make table with context menu #4690
Unanswered
xXQiuChenXx
asked this question in
Q&A
Replies: 1 comment
-
Hey buddy, I had the same doubt for something I had to implement in my work, with the help of chatgpt and a little follow up to the components that gives shadcn I could make the table with a little more dynamic features, but in this example you can help you to see how a table works with context menu in each row. I'm sorry I don't have it as documented with comments or so, but I recently did. But if you need to see the direct code here it is :) Tech: React, Shadcn, Typescript, TanStack table DataTable code: import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from '@/components';
import { useState } from 'react';
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ChevronDown } from 'lucide-react';
import {
Button,
Input,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectGroup,
SelectItem,
} from '@/components';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
enableSorting?: boolean;
enableFiltering?: boolean;
enableColumnVisibility?: boolean;
contextMenuOptions?: (row: TData) => { label: string; onClick: () => void }[];
}
export function DataTable<TData, TValue>({
data,
columns,
enableSorting = false,
enableFiltering = false,
enableColumnVisibility = false,
contextMenuOptions,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 5 });
const [globalFilter, setGlobalFilter] = useState('');
const table = useReactTable({
data,
columns,
onSortingChange: enableSorting ? setSorting : undefined,
onColumnFiltersChange: enableFiltering ? setColumnFilters : undefined,
onColumnVisibilityChange: enableColumnVisibility
? setColumnVisibility
: undefined,
onRowSelectionChange: setRowSelection,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
getFilteredRowModel: enableFiltering ? getFilteredRowModel() : undefined,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination,
globalFilter,
},
onPaginationChange: setPagination,
});
return (
<div className="w-full">
<section className="flex flex-col sm:flex-row items-center">
{enableFiltering && (
<div className="flex items-center py-4 w-full">
<Input
placeholder="Filter..."
value={globalFilter}
onChange={(event) => setGlobalFilter(event.target.value)}
className="w-full sm:max-w-sm"
/>
</div>
)}
{enableColumnVisibility && (
<div className="pb-4 sm:pb-0 w-full sm:w-max">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto w-full sm:w-max">
Columns <ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</section>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<ContextMenu key={row.id}>
<ContextMenuTrigger asChild>
<TableRow
data-state={row.getIsSelected() ? 'selected' : undefined}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
</ContextMenuTrigger>
<ContextMenuContent>
{contextMenuOptions &&
contextMenuOptions(row.original).map((option) => (
<ContextMenuItem
key={option.label}
onClick={option.onClick}
>
{option.label}
</ContextMenuItem>
))}
</ContextMenuContent>
</ContextMenu>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
<div className="flex gap-2 items-center">
{table.getFilteredSelectedRowModel().rows.length} of{' '}
{table.getFilteredRowModel().rows.length} row(s) selected.
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="max-w-[70px] !h-8">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{[5, 10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem
className="cursor-pointer"
key={pageSize}
value={pageSize.toString()}
>
{pageSize}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
} How to use it: import { Button, Checkbox, DataTable } from '@/components';
import { ColumnDef } from '@tanstack/react-table';
type Payment = {
id: string;
amount: number;
status: 'pending' | 'processing' | 'success' | 'failed';
email: string;
};
export const Login = () => {
const data: Payment[] = [
{
id: 'm5gr84i9',
amount: 316,
status: 'success',
email: '[email protected]',
},
{
id: '3u1reuv4',
amount: 242,
status: 'success',
email: '[email protected]',
},
{
id: 'derv1ws0',
amount: 837,
status: 'processing',
email: '[email protected]',
},
{
id: '5kma53ae',
amount: 874,
status: 'success',
email: '[email protected]',
},
{
id: 'bhqecj4p',
amount: 721,
status: 'failed',
email: '[email protected]',
},
{
id: 'm5gr84i9',
amount: 316,
status: 'success',
email: '[email protected]',
},
];
const columns: ColumnDef<Payment>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllRowsSelected() ||
(table.getIsSomeRowsSelected() && 'indeterminate')
}
onCheckedChange={(value) => table.toggleAllRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => (
<div className="capitalize">{row.getValue('status')}</div>
),
},
{
accessorKey: 'email',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Email
</Button>
);
},
cell: ({ row }) => (
<div className="lowercase">{row.getValue('email')}</div>
),
},
{
accessorKey: 'amount',
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue('amount'));
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount);
return <div className="text-right font-medium">{formatted}</div>;
},
},
];
const contextOptions = (row: Payment) => [
{
label: 'Log Usuario',
onClick: () => console.log('Logging email:', row.email, 'ID:', row.id),
},
{
label: 'Otra opción',
onClick: () => console.log('Otra acción', row.id),
},
];
return (
<>
<DataTable
data={data}
columns={columns}
contextMenuOptions={contextOptions}
enableFiltering
/>
</>
);
};
export default Login; |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Is there way for doing this?
I have tried hidden and setHidden but it does seem work.
Here is my code with tanstack react table
Where does the Context Menu component I need to put? and how to show it properly?
Beta Was this translation helpful? Give feedback.
All reactions