Layout

Table

A table displays large quantities of items or data in a list format. Filtering features, date picker, collapsible rows and actions may be added.

View source
User:
Jan 29, 2023 - Jan 30, 2023

No Data

Results per page
1
import {
2
BlankSlate,
3
Box,
4
Button,
5
ColumnDef,
6
createColumnHelper,
7
DateRangePickerPreset,
8
Table,
9
Title,
10
useTable,
11
} from '@coveord/plasma-mantine';
12
import {EditSize16Px} from '@coveord/plasma-react-icons';
13
import {FunctionComponent, useState} from 'react';
14
15
interface IExampleRowData {
16
userId: number;
17
id: number;
18
title: string;
19
body: string;
20
}
21
22
export default () => {
23
const columnHelper = createColumnHelper<IExampleRowData>();
24
const columns: Array<ColumnDef<IExampleRowData>> = [
25
columnHelper.accessor('userId', {
26
header: 'User ID',
27
cell: (info) => info.row.original.userId,
28
}),
29
columnHelper.accessor('id', {
30
header: 'Post ID',
31
cell: (info) => info.row.original.id,
32
}),
33
columnHelper.accessor('title', {
34
header: 'Title',
35
cell: (info) => info.row.original.title,
36
}),
37
Table.CollapsibleColumn as ColumnDef<IExampleRowData>,
38
];
39
const [data, setData] = useState([]);
40
const [loading, setLoading] = useState(false);
41
const [pages, setPages] = useState(1);
42
43
const fetchData = (state: any) => {
44
setLoading(true);
45
const searchParams = new URLSearchParams({
46
_sort: state.sorting?.[0]?.id ?? 'userId',
47
_order: state.sorting?.[0]?.desc ? 'desc' : 'asc',
48
_page: state.pagination.pageIndex + 1,
49
_limit: state.pagination.pageSize,
50
userId: state.predicates.user,
51
title_like: state.globalFilter,
52
});
53
if (state.predicates.user === '') {
54
searchParams.delete('userId');
55
}
56
if (!state.globalFilter) {
57
searchParams.delete('title_like');
58
}
59
fetch(`https://jsonplaceholder.typicode.com/posts?${searchParams.toString()}`)
60
.then((response) => response.json())
61
.then((json) => setData(json))
62
.then(() => setPages(Math.ceil(100 / state.pagination?.pageSize)))
63
.catch((e) => console.log(e));
64
setLoading(false);
65
};
66
67
return (
68
<Table
69
data={data}
70
getRowId={({id}) => id.toString()}
71
columns={columns}
72
noDataChildren={<NoData />}
73
onMount={(state) => {
74
fetchData(state);
75
}}
76
onChange={(state) => {
77
fetchData(state);
78
}}
79
loading={loading}
80
initialState={{dateRange: [previousDay, today], predicates: {user: ''}}}
81
getExpandChildren={(datum) => <Box py="xs">{datum.body}</Box>}
82
>
83
{/* you can override background color with: sx={{backgroundColor: 'white'}} for Header and Footer */}
84
<Table.Header>
85
<Table.Actions>{(datum: IExampleRowData) => <TableActions datum={datum} />}</Table.Actions>
86
<UserPredicate />
87
<Table.Filter placeholder="Search posts by title" />
88
<Table.DateRangePicker presets={DatePickerPresets} />
89
</Table.Header>
90
<Table.Footer>
91
<Table.PerPage />
92
<Table.Pagination totalPages={pages} />
93
</Table.Footer>
94
</Table>
95
);
96
};
97
98
const NoData: FunctionComponent = () => {
99
const {state, form, clearFilters} = useTable();
100
const isFiltered = !!state.globalFilter || !!form.values.predicates.user;
101
102
return isFiltered ? (
103
<BlankSlate>
104
<Title order={4}>No data found for those filters</Title>
105
<Button onClick={clearFilters}>Clear filters</Button>
106
</BlankSlate>
107
) : (
108
<BlankSlate>
109
<Title order={4}>No Data</Title>
110
</BlankSlate>
111
);
112
};
113
114
const today: Date = new Date();
115
const previous: Date = new Date(new Date().getTime());
116
const previousDay: Date = new Date(previous.setDate(previous.getDate() - 1));
117
const previousWeek: Date = new Date(previous.setDate(previous.getDate() - 7));
118
119
const DatePickerPresets: Record<string, DateRangePickerPreset> = {
120
lastDay: {label: 'Last 24 hours', range: [previousDay, today]},
121
lastWeek: {label: 'Last week', range: [previousWeek, today]},
122
};
123
124
const TableActions: FunctionComponent<{datum: IExampleRowData}> = ({datum}) => {
125
const actionCondition = datum.id % 2 === 0 ? true : false;
126
const pressedAction = () => alert('Edit action is triggered!');
127
return (
128
<>
129
{actionCondition ? (
130
<Button variant="subtle" onClick={pressedAction} leftIcon={<EditSize16Px height={16} />}>
131
Edit
132
</Button>
133
) : null}
134
</>
135
);
136
};
137
138
const UserPredicate: FunctionComponent = () => (
139
<Table.Predicate
140
id="user"
141
label="User"
142
data={[
143
{
144
value: '',
145
label: 'All',
146
},
147
{value: '1', label: '1'},
148
{value: '2', label: '2'},
149
{value: '3', label: '3'},
150
{value: '4', label: '4'},
151
{value: '5', label: '5'},
152
{value: '6', label: '6'},
153
{value: '7', label: '7'},
154
{value: '8', label: '8'},
155
{value: '9', label: '9'},
156
{value: '10', label: '10'},
157
]}
158
/>
159
);

Props

NameTypeDefaultDescription
columnsrequiredColumnDef<unknown, unknown>[]
Columns to display in the table.
datarequiredunknown[]
Data to display in the table
childrenReactNode
Childrens to display in the table. They need to be wrap in either `Table.Header` or `Table.Footer`
doubleClickAction(datum: unknown) => void
Action passed when user double clicks on a row
getExpandChildren(datum: unknown) => ReactNode
Function that generates the expandable content of a row Return null for rows that don't need to be expandable
  • datumthe row for which the children should be generated.
getRowId(originalRow: unknown, index: number, parent?: Row<unknown>) => string
Defines how each row is uniquely identified. It is highly recommended that you specify this prop to an ID that makes sense.
initialStatePartial<VisibilityTableState & ColumnOrderTableState & ColumnPinningTableState & FiltersTableState & ... 5 more ... & RowSelectionTableState> & Partial<...>
Initial state of the table
loadingbooleanfalse
Whether the table is loading or not
multiRowSelectionEnabledbooleanfalse
Whether the user can select multiple rows in order to perform actions in bulk
noDataChildrenReactNode
React children to show when the table has no rows to show. You can leverage useTable to get the state of the table
onChangeonTableChangeEvent
Function called when the table should update
  • statethe state of the table
onMountonTableChangeEvent
Function called when the table mounts
  • statethe state of the table

Examples

Table with bulk selection of rows

No Data

Results per page
1
import {BlankSlate, Button, ColumnDef, createColumnHelper, Table, Title, useTable} from '@coveord/plasma-mantine';
2
import {DeleteSize16Px, EditSize16Px} from '@coveord/plasma-react-icons';
3
import {FunctionComponent, useState} from 'react';
4
5
interface IExampleRowData {
6
userId: number;
7
id: number;
8
title: string;
9
body: string;
10
}
11
12
export default () => {
13
const columnHelper = createColumnHelper<IExampleRowData>();
14
const columns: Array<ColumnDef<IExampleRowData>> = [
15
columnHelper.accessor('userId', {
16
header: 'User ID',
17
cell: (info) => info.row.original.userId,
18
}),
19
columnHelper.accessor('title', {
20
header: 'Title',
21
cell: (info) => info.row.original.title,
22
}),
23
];
24
const [data, setData] = useState([]);
25
const [loading, setLoading] = useState(false);
26
const [pages, setPages] = useState(1);
27
28
const fetchData = (state: any) => {
29
setLoading(true);
30
const searchParams = new URLSearchParams({
31
_page: state.pagination.pageIndex + 1,
32
_limit: state.pagination.pageSize,
33
title_like: state.globalFilter,
34
});
35
if (!state.globalFilter) {
36
searchParams.delete('title_like');
37
}
38
fetch(`https://jsonplaceholder.typicode.com/posts?${searchParams.toString()}`)
39
.then((response) => response.json())
40
.then((json) => setData(json))
41
.then(() => setPages(Math.ceil(100 / state.pagination?.pageSize)))
42
.catch((e) => console.log(e));
43
setLoading(false);
44
};
45
46
return (
47
<Table<IExampleRowData>
48
data={data}
49
getRowId={({id}) => id.toString()}
50
columns={columns}
51
noDataChildren={<NoData />}
52
onMount={(state) => {
53
fetchData(state);
54
}}
55
onChange={(state) => {
56
fetchData(state);
57
}}
58
loading={loading}
59
multiRowSelectionEnabled
60
>
61
<Table.Header>
62
<Table.Actions>
63
{(selectedRows: IExampleRowData[]) => <TableActions data={selectedRows} />}
64
</Table.Actions>
65
<Table.Filter placeholder="Search posts by title" />
66
</Table.Header>
67
<Table.Footer>
68
<Table.PerPage />
69
<Table.Pagination totalPages={pages} />
70
</Table.Footer>
71
</Table>
72
);
73
};
74
75
const NoData: FunctionComponent = () => {
76
const {state, form, clearFilters} = useTable();
77
const isFiltered = !!state.globalFilter || !!form.values.predicates.user;
78
79
return isFiltered ? (
80
<BlankSlate>
81
<Title order={4}>No data found for those filters</Title>
82
<Button onClick={clearFilters}>Clear filters</Button>
83
</BlankSlate>
84
) : (
85
<BlankSlate>
86
<Title order={4}>No Data</Title>
87
</BlankSlate>
88
);
89
};
90
91
const TableActions: FunctionComponent<{data: IExampleRowData[]}> = ({data}) => {
92
if (data.length === 1) {
93
return (
94
<Button
95
variant="subtle"
96
onClick={() => alert(`Action triggered on a single row: ${data[0].id}`)}
97
leftIcon={<EditSize16Px height={16} />}
98
>
99
Single row action
100
</Button>
101
);
102
}
103
if (data.length > 1) {
104
return (
105
<Button
106
variant="subtle"
107
onClick={() => alert(`Bulk action triggered on multiple rows: ${data.map(({id}) => id).join(', ')}`)}
108
leftIcon={<DeleteSize16Px height={16} />}
109
>
110
Bulk action
111
</Button>
112
);
113
}
114
115
return null;
116
};

No guidelines exist for Table yet.

Create guidelines