1import {2BlankSlate,3Box,4Button,5ColumnDef,6createColumnHelper,7DateRangePickerPreset,8onTableChangeEvent,9Table,10Title,11useTable,12} from '@coveord/plasma-mantine';13import {EditSize16Px} from '@coveord/plasma-react-icons';14import dayjs from 'dayjs';15import {FunctionComponent, useState} from 'react';1617interface IExampleRowData {18userId: number;19id: number;20title: string;21body: string;22}2324const columnHelper = createColumnHelper<IExampleRowData>();2526/**27* Define your columns outside the component rendering the table28* (or memoize them) to avoid unnecessary render loops29*/30const columns: Array<ColumnDef<IExampleRowData>> = [31columnHelper.accessor('userId', {32header: 'User ID',33cell: (info) => info.row.original.userId,34}),35columnHelper.accessor('id', {36header: 'Post ID',37cell: (info) => info.row.original.id,38}),39columnHelper.accessor('title', {40header: 'Title',41cell: (info) => info.row.original.title,42}),43Table.CollapsibleColumn as ColumnDef<IExampleRowData>,44// or if you prefer an accordion behaviour45// Table.AccordionColumn as ColumnDef<IExampleRowData>,46];4748const Demo = () => {49const [data, setData] = useState(null);50const [loading, setLoading] = useState(true);51const [pages, setPages] = useState(1);5253const fetchData: onTableChangeEvent<IExampleRowData> = async (state) => {54setLoading(true);55const searchParams = new URLSearchParams({56_sort: state.sorting?.[0]?.id ?? 'userId',57_order: state.sorting?.[0]?.desc ? 'desc' : 'asc',58_page: (state.pagination.pageIndex + 1).toString(),59_limit: state.pagination.pageSize.toString(),60userId: state.predicates.user,61title_like: state.globalFilter,62});63if (state.predicates.user === '') {64searchParams.delete('userId');65}66if (!state.globalFilter) {67searchParams.delete('title_like');68}69try {70const response = await fetch(`https://jsonplaceholder.typicode.com/posts?${searchParams.toString()}`);71const body = await response.json();72setData(body);73setPages(Math.ceil(Number(response.headers.get('x-total-count')) / state.pagination?.pageSize));74} catch (e) {75console.error(e);76} finally {77setLoading(false);78}79};8081return (82<Table83data={data}84getRowId={({id}) => id.toString()}85columns={columns}86noDataChildren={<NoData />}87onMount={fetchData}88onChange={fetchData}89loading={loading}90initialState={{dateRange: [previousDay, today], predicates: {user: ''}}}91getExpandChildren={(datum) => <Box py="xs">{datum.body}</Box>}92>93{/* you can override background color with: sx={{backgroundColor: 'white'}} for Header and Footer */}94<Table.Header>95<Table.Actions>{(datum: IExampleRowData) => <TableActions datum={datum} />}</Table.Actions>96<UserPredicate />97<Table.Filter placeholder="Search posts by title" />98<Table.DateRangePicker99rangeCalendarProps={{maxDate: dayjs().endOf('day').toDate()}}100presets={DatePickerPresets}101/>102</Table.Header>103<Table.Footer>104<Table.PerPage />105<Table.Pagination totalPages={pages} />106</Table.Footer>107</Table>108);109};110export default Demo;111112const NoData: FunctionComponent = () => {113const {clearFilters, isFiltered} = useTable();114115return isFiltered ? (116<BlankSlate>117<Title order={4}>No data found for those filters</Title>118<Button onClick={clearFilters}>Clear filters</Button>119</BlankSlate>120) : (121<BlankSlate>122<Title order={4}>No Data</Title>123</BlankSlate>124);125};126127const today: Date = dayjs().startOf('day').toDate();128const previousDay: Date = dayjs().subtract(1, 'day').endOf('day').toDate();129const previousWeek: Date = dayjs().subtract(1, 'week').endOf('day').toDate();130131const DatePickerPresets: Record<string, DateRangePickerPreset> = {132lastDay: {label: 'Last 24 hours', range: [previousDay, today]},133lastWeek: {label: 'Last week', range: [previousWeek, today]},134};135136const TableActions: FunctionComponent<{datum: IExampleRowData}> = ({datum}) => {137const actionCondition = datum.id % 2 === 0 ? true : false;138const pressedAction = () => alert('Edit action is triggered!');139return (140<>141{actionCondition ? (142<Button variant="subtle" onClick={pressedAction} leftIcon={<EditSize16Px height={16} />}>143Edit144</Button>145) : null}146</>147);148};149150const UserPredicate: FunctionComponent = () => (151<Table.Predicate152id="user"153label="User"154data={[155{156value: '',157label: 'All',158},159{value: '1', label: '1'},160{value: '2', label: '2'},161{value: '3', label: '3'},162{value: '4', label: '4'},163{value: '5', label: '5'},164{value: '6', label: '6'},165{value: '7', label: '7'},166{value: '8', label: '8'},167{value: '9', label: '9'},168{value: '10', label: '10'},169]}170/>171);
Name | Type | Default | Description |
---|---|---|---|
columns required | ColumnDef<unknown>[] | Columns to display in the table. | |
data required | unknown[] | Data to display in the table | |
children | ReactNode | Childrens to display in the table. They need to be wrap in either `Table.Header` or `Table.Footer` | |
disableRowSelection | boolean | false | Whether row selection is enabled or not |
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
| |
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. | |
initialState | InitialTableState<unknown> | Initial state of the table | |
layouts | TableLayout[] | [Table.Layouts.Rows] | Available layouts |
loading | boolean | false | Whether the table is loading or not |
multiRowSelectionEnabled | boolean | false | Whether the user can select multiple rows in order to perform actions in bulk |
noDataChildren | ReactNode | React children to show when the table has no rows to show. You can leverage useTable to get the state of the table | |
onChange | onTableChangeEvent<unknown> | Function called when the table should update
| |
onMount | onTableChangeEvent<unknown> | Function called when the table mounts
| |
onRowSelectionChange | (selectedRows: unknown[]) => void | Function called whenever the row selection changes
| |
options | Omit<Partial<TableOptions<unknown>>, "getRowId" | "data" | "initialState" | "columns" | "getRowCanExpand" | "manualPagination" | "enableRowSelection" | "enableMultiRowSelection" | "onRowSelectionChange"> | Additional options that can be passed to the table |
1import {2BlankSlate,3Button,4ColumnDef,5createColumnHelper,6onTableChangeEvent,7Table,8Title,9useTable,10} from '@coveord/plasma-mantine';11import {DeleteSize16Px, EditSize16Px} from '@coveord/plasma-react-icons';12import {FunctionComponent, useState} from 'react';1314interface IExampleRowData {15userId: number;16id: number;17title: string;18body: string;19}2021const columnHelper = createColumnHelper<IExampleRowData>();22const columns: Array<ColumnDef<IExampleRowData>> = [23columnHelper.accessor('userId', {24header: 'User ID',25cell: (info) => info.row.original.userId,26enableSorting: false,27}),28columnHelper.accessor('title', {29header: 'Title',30cell: (info) => info.row.original.title,31enableSorting: false,32}),33];3435const Demo = () => {36const [data, setData] = useState(null);37const [loading, setLoading] = useState(true);38const [pages, setPages] = useState(1);3940const fetchData: onTableChangeEvent<IExampleRowData> = async (state) => {41setLoading(true);42const searchParams = new URLSearchParams({43_page: (state.pagination.pageIndex + 1).toString(),44_limit: state.pagination.pageSize.toString(),45title_like: state.globalFilter,46});47if (!state.globalFilter) {48searchParams.delete('title_like');49}50try {51const response = await fetch(`https://jsonplaceholder.typicode.com/posts?${searchParams.toString()}`);52const body = await response.json();53setData(body);54setPages(Math.ceil(Number(response.headers.get('x-total-count')) / state.pagination?.pageSize));55} catch (e) {56console.error(e);57} finally {58setLoading(false);59}60};6162return (63<Table<IExampleRowData>64data={data}65getRowId={({id}) => id.toString()}66columns={columns}67noDataChildren={<NoData />}68onMount={fetchData}69onChange={fetchData}70loading={loading}71onRowSelectionChange={(selectedRows) =>72console.info(`Row selection changed, selected rows: ${selectedRows.map(({id}) => id).join(', ')}`)73}74multiRowSelectionEnabled75initialState={{76rowSelection: {77'1': {78userId: 1,79id: 1,80title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',81body: 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto',82},83'2': {84userId: 1,85id: 2,86title: 'qui est esse',87body: 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla',88},89},90}}91>92<Table.Header>93<Table.Actions>94{(selectedRows: IExampleRowData[]) => <TableActions data={selectedRows} />}95</Table.Actions>96<Table.Filter placeholder="Search posts by title" />97</Table.Header>98<Table.Footer>99<Table.PerPage />100<Table.Pagination totalPages={pages} />101</Table.Footer>102</Table>103);104};105export default Demo;106107const NoData: FunctionComponent = () => {108const {isFiltered, clearFilters} = useTable();109110return isFiltered ? (111<BlankSlate>112<Title order={4}>No data found for those filters</Title>113<Button onClick={clearFilters}>Clear filters</Button>114</BlankSlate>115) : (116<BlankSlate>117<Title order={4}>No Data</Title>118</BlankSlate>119);120};121122const TableActions: FunctionComponent<{data: IExampleRowData[]}> = ({data}) => {123if (data.length === 1) {124return (125<Button126variant="subtle"127onClick={() => alert(`Action triggered on a single row: ${data[0].id}`)}128leftIcon={<EditSize16Px height={16} />}129>130Single row action131</Button>132);133}134if (data.length > 1) {135return (136<Button137variant="subtle"138onClick={() => alert(`Bulk action triggered on multiple rows: ${data.map(({id}) => id).join(', ')}`)}139leftIcon={<DeleteSize16Px height={16} />}140>141Bulk action142</Button>143);144}145146return null;147};
1import {2BlankSlate,3Button,4ColumnDef,5createColumnHelper,6onTableChangeEvent,7Table,8Title,9useTable,10} from '@coveord/plasma-mantine';11import {FunctionComponent, useState} from 'react';1213interface IExampleRowData {14userId: number;15id: number;16title: string;17body: string;18}1920const columnHelper = createColumnHelper<IExampleRowData>();21const columns: Array<ColumnDef<IExampleRowData>> = [22columnHelper.accessor('userId', {23header: 'User ID',24cell: (info) => info.row.original.userId,25enableSorting: false,26}),27columnHelper.accessor('title', {28header: 'Title',29cell: (info) => info.row.original.title,30enableSorting: false,31}),32];3334const Demo = () => {35const [data, setData] = useState(null);36const [loading, setLoading] = useState(true);37const [pages, setPages] = useState(1);3839const fetchData: onTableChangeEvent<IExampleRowData> = async (state) => {40setLoading(true);41const searchParams = new URLSearchParams({42_page: (state.pagination.pageIndex + 1).toString(),43_limit: state.pagination.pageSize.toString(),44title_like: state.globalFilter,45});46if (!state.globalFilter) {47searchParams.delete('title_like');48}49try {50const response = await fetch(`https://jsonplaceholder.typicode.com/posts?${searchParams.toString()}`);51const body = await response.json();52setData(body);53setPages(Math.ceil(Number(response.headers.get('x-total-count')) / state.pagination?.pageSize));54} catch (e) {55console.error(e);56} finally {57setLoading(false);58}59};6061return (62<Table<IExampleRowData>63data={data}64getRowId={({id}) => id.toString()}65columns={columns}66noDataChildren={<NoData />}67onMount={fetchData}68onChange={fetchData}69loading={loading}70onRowSelectionChange={(selectedRows) =>71console.info(`Row selection changed, selected rows: ${selectedRows.map(({id}) => id).join(', ')}`)72}73multiRowSelectionEnabled74disableRowSelection75initialState={{76rowSelection: {77'1': {78userId: 1,79id: 1,80title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',81body: 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto',82},83'2': {84userId: 1,85id: 2,86title: 'qui est esse',87body: 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla',88},89},90}}91>92<Table.Header>93<Table.Filter placeholder="Search posts by title" />94</Table.Header>95<Table.Footer>96<Table.PerPage />97<Table.Pagination totalPages={pages} />98</Table.Footer>99</Table>100);101};102export default Demo;103104const NoData: FunctionComponent = () => {105const {isFiltered, clearFilters} = useTable();106107return isFiltered ? (108<BlankSlate>109<Title order={4}>No data found for those filters</Title>110<Button onClick={clearFilters}>Clear filters</Button>111</BlankSlate>112) : (113<BlankSlate>114<Title order={4}>No Data</Title>115</BlankSlate>116);117};
Lesly | Hoeger | 23 |
Mollie | Swift | 3 |
Jarred | Harber | 3 |
Shawn | McClure | 32 |
Johann | Runolfsdottir | 6 |
1import {2ColumnDef,3createColumnHelper,4FilterFn,5getFilteredRowModel,6getPaginationRowModel,7getSortedRowModel,8Table,9TableProps,10} from '@coveord/plasma-mantine';11import {faker} from '@faker-js/faker';12import {rankItem} from '@tanstack/match-sorter-utils';13import {useMemo} from 'react';1415export type Person = {16id: string;17firstName: string;18lastName: string;19age: number;20};2122const makeData = (len: number): Person[] =>23Array(len)24.fill(0)25.map((a, index) => ({26id: faker.datatype.uuid(),27firstName: faker.name.firstName(),28lastName: faker.name.lastName(),29age: faker.datatype.number(40),30}));3132const fuzzyFilter: FilterFn<Person> = (row, columnId, value) => rankItem(row.getValue(columnId), value).passed;3334const columnHelper = createColumnHelper<Person>();3536const columns: Array<ColumnDef<Person>> = [37columnHelper.accessor('firstName', {38header: 'First name',39cell: (info) => info.row.original.firstName,40}),41columnHelper.accessor('lastName', {42header: 'Last name',43cell: (info) => info.row.original.lastName,44}),45columnHelper.accessor('age', {46header: 'Age',47cell: (info) => info.row.original.age,48}),49];5051const options: TableProps<Person>['options'] = {52globalFilterFn: fuzzyFilter,53getFilteredRowModel: getFilteredRowModel(),54getPaginationRowModel: getPaginationRowModel(),55getSortedRowModel: getSortedRowModel(),56};5758const Demo = () => {59const data = useMemo(() => makeData(45), []);60return (61<Table62data={data}63columns={columns}64options={options}65initialState={{pagination: {pageSize: 5}}}66getRowId={({id}) => id}67>68<Table.Header>69<Table.Filter placeholder="Search" />70</Table.Header>71<Table.Footer>72<Table.PerPage values={[5, 10, 25]} />73<Table.Pagination totalPages={null} />74</Table.Footer>75</Table>76);77};78export default Demo;
No data found for filter "foo" |
1import {BlankSlate, Button, ColumnDef, createColumnHelper, Table, Title, useTable} from '@coveord/plasma-mantine';2import {NoContentSize32Px} from '@coveord/plasma-react-icons';34export type Person = {5firstName: string;6lastName: string;7age: number;8};910const NoData = () => {11const {isFiltered, clearFilters, state} = useTable();1213return isFiltered ? (14<BlankSlate>15<Title order={4}>No data found for filter "{state.globalFilter}"</Title>16<Button onClick={clearFilters}>Clear filter</Button>17</BlankSlate>18) : (19<BlankSlate withBorder={false}>20<NoContentSize32Px height={64} />21<Title order={4}>Hello Empty State</Title>22</BlankSlate>23);24};2526const Demo = () => (27<Table28data={[]}29columns={columns}30noDataChildren={<NoData />}31initialState={{globalFilter: 'foo', pagination: {pageSize: 5}}}32>33<Table.Header>34<Table.Filter placeholder="Search" />35</Table.Header>36<Table.Footer>37<Table.PerPage values={[5, 10, 25]} />38<Table.Pagination totalPages={null} />39</Table.Footer>40</Table>41);42export default Demo;4344const columnHelper = createColumnHelper<Person>();4546const columns: Array<ColumnDef<Person>> = [47columnHelper.accessor('firstName', {48header: 'First name',49cell: (info) => info.row.original.firstName,50}),51columnHelper.accessor('lastName', {52header: 'Last name',53cell: (info) => info.row.original.lastName,54}),55columnHelper.accessor('age', {56header: 'Age',57cell: (info) => info.row.original.age,58}),59];
1import {ColumnDef, createColumnHelper, onTableChangeEvent, Table, useTable} from '@coveord/plasma-mantine';2import {FunctionComponent, useEffect, useState} from 'react';34interface PostData {5id: number;6title: string;7}89const columnHelper = createColumnHelper<PostData>();10const columns: Array<ColumnDef<PostData>> = [11columnHelper.accessor('id', {12header: 'Post ID',13enableSorting: false,14}),15columnHelper.accessor('title', {16header: 'Title',17enableSorting: false,18}),19];2021const Demo = () => {22const [data, setData] = useState(null);23const [loading, setLoading] = useState(true);2425const fetchData: onTableChangeEvent<PostData> = async () => {26setLoading(true);27try {28const response = await fetch('https://jsonplaceholder.typicode.com/posts');29const body = await response.json();3031// slow down the fetch, to make the refresh more obvious32await new Promise((resolve) => setTimeout(resolve, 1000));3334setData(body);35} catch (e) {36console.error(e);37} finally {38setLoading(false);39}40};4142return (43<Table<PostData>44data={data}45getRowId={({id}) => id.toString()}46columns={columns}47onMount={fetchData}48onChange={fetchData}49loading={loading}50>51<Table.Consumer>52{/* Refresh the component every 10s, look at your network tab to validate it works */}53<IntervalRefresh every={10 * 1000} />54</Table.Consumer>55</Table>56);57};58export default Demo;5960const IntervalRefresh: FunctionComponent<{every: number}> = ({every}) => {61const {onChange} = useTable();62useEffect(() => {63const timer = setInterval(() => onChange(), every);64return () => clearInterval(timer);65}, [every]);6667return null;68};
No guidelines exist for Table yet.