Examples › Nested tables
You can abuse the row expansion feature and make use of the noHeader
property to create nested tables.
Warning
Nested tables do not work with column pinning -
pinFirstColumn
and pinLastColumn
.Click on the expandable rows in the table below to see it in action:
Company / Department / Employee | Employees / Birth date |
---|---|
Feest, Bogan and Herzog | 37 |
Cummerata - Kuhlman | 67 |
Goyette Inc | 63 |
Runte Inc | 87 |
Goldner, Rohan and Lehner | 29 |
Doyle, Hodkiewicz and O'Connell | 61 |
Rau - O'Hara | 41 |
Tillman - Jacobi | 46 |
Connelly, Feest and Hodkiewicz | 38 |
Shanahan, Robel and Beier | 31 |
Kling - McLaughlin | 0 |
Jogi - McLaughlin | 0 |
Jogi - McLaughlin | 0 |
No records
The above example is implemented with the following code:
'use client';
import { Box } from '@mantine/core';
import { IconBuilding, IconChevronRight, IconUser, IconUsers } from '@tabler/icons-react';
import { DataTable } from 'mantine-datatable';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { useState } from 'react';
import { employees } from '~/data';
import { companies, departments } from '~/data/nested';
import classes from './NestedTablesExample.module.css';
export function NestedTablesExample() {
const [expandedCompanyIds, setExpandedCompanyIds] = useState<string[]>([]);
const [expandedDepartmentIds, setExpandedDepartmentIds] = useState<string[]>([]);
return (
<DataTable
withTableBorder
withColumnBorders
highlightOnHover
columns={[
{
accessor: 'name',
title: 'Company / Department / Employee',
noWrap: true,
render: ({ id, name }) => (
<>
<IconChevronRight
className={clsx(classes.icon, classes.expandIcon, {
[classes.expandIconRotated]: expandedCompanyIds.includes(id),
})}
/>
<IconBuilding className={classes.icon} />
<span>{name}</span>
</>
),
},
{ accessor: 'employees', title: 'Employees / Birth date', textAlign: 'right', width: 200 },
]}
records={companies}
rowExpansion={{
allowMultiple: true,
expanded: { recordIds: expandedCompanyIds, onRecordIdsChange: setExpandedCompanyIds },
content: (company) => (
<DataTable
noHeader
withColumnBorders
columns={[
{
accessor: 'name',
noWrap: true,
render: ({ id, name }) => (
<Box component="span" ml={20}>
<IconChevronRight
className={clsx(classes.icon, classes.expandIcon, {
[classes.expandIconRotated]: expandedDepartmentIds.includes(id),
})}
/>
<IconUsers className={classes.icon} />
<span>{name}</span>
</Box>
),
},
{ accessor: 'employees', textAlign: 'right', width: 200 },
]}
records={departments.filter((department) => department.company.id === company.record.id)}
rowExpansion={{
allowMultiple: true,
expanded: { recordIds: expandedDepartmentIds, onRecordIdsChange: setExpandedDepartmentIds },
content: (department) => (
<DataTable
noHeader
withColumnBorders
columns={[
{
accessor: 'name',
render: ({ firstName, lastName }) => (
<Box component="span" ml={40}>
<IconUser className={classes.icon} />
<span>
{firstName} {lastName}
</span>
</Box>
),
},
{
accessor: 'birthDate',
render: ({ birthDate }) => dayjs(birthDate).format('DD MMM YYYY'),
textAlign: 'right',
width: 200,
},
]}
records={employees.filter((employee) => employee.department.id === department.record.id)}
/>
),
}}
/>
),
}}
/>
);
}
Head over to the next example to see how you could asynchronously load data for nested tables.