Skip to content

Commit b832083

Browse files
author
Shawn Zhu
committed
feat(PHC-4380): create ReactTableModule #331
1 parent 2c5973d commit b832083

File tree

6 files changed

+350
-2
lines changed

6 files changed

+350
-2
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@
108108
"react-remove-scroll": "^2.3.0",
109109
"reakit": "1.0.0-rc.1"
110110
},
111+
"optionalDependencies": {
112+
"@tanstack/react-table": "^8.7.9"
113+
},
111114
"resolutions": {
112115
"jsdom": "^16.3.0"
113116
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import React, { useRef } from 'react';
2+
import { ComponentStory, ComponentMeta } from '@storybook/react';
3+
import { createColumnHelper, Row } from '@tanstack/react-table';
4+
import { Checkbox } from '../Checkbox';
5+
6+
import { ReactTableModule } from './ReactTableModule';
7+
8+
export default {
9+
title: 'Components/TableModule2',
10+
component: ReactTableModule,
11+
argTypes: {},
12+
} as ComponentMeta<typeof ReactTableModule>;
13+
14+
type Food = {
15+
description: string;
16+
calories: string;
17+
fat: string;
18+
carbs: string;
19+
category: string;
20+
};
21+
22+
const data: Food[] = [
23+
{
24+
description: 'Frozen yoghurt',
25+
calories: '159',
26+
fat: '6.0',
27+
carbs: '24',
28+
category: 'yogurt',
29+
},
30+
{
31+
description: 'Ice cream sandwich',
32+
calories: '237',
33+
fat: '9.0',
34+
carbs: '37',
35+
category: 'ice cream',
36+
},
37+
{
38+
description: 'Eclair',
39+
calories: '262',
40+
fat: '16.0',
41+
carbs: '24',
42+
category: 'dessert',
43+
},
44+
{
45+
description: 'Cupcake',
46+
calories: '305',
47+
fat: '3.7',
48+
carbs: '67',
49+
category: 'cake',
50+
},
51+
];
52+
53+
const columnHelper = createColumnHelper<Food>();
54+
55+
const columns = [
56+
columnHelper.accessor('description', {
57+
cell: (info) => info.getValue(),
58+
header: 'Description',
59+
}),
60+
columnHelper.accessor((row) => row.calories, {
61+
id: 'calories',
62+
cell: (info) => <i>{info.getValue()}</i>,
63+
header: () => <span>Calories</span>,
64+
}),
65+
columnHelper.accessor('fat', {
66+
header: () => 'Fat',
67+
cell: (info) => info.renderValue(),
68+
}),
69+
columnHelper.accessor('carbs', {
70+
header: () => <span>Carbs</span>,
71+
}),
72+
columnHelper.accessor('category', {
73+
header: 'Category',
74+
}),
75+
];
76+
77+
const config = [
78+
{
79+
header: {
80+
label: 'Description',
81+
},
82+
cell: {
83+
content: (dataValue: any) => {
84+
return dataValue.description;
85+
},
86+
},
87+
},
88+
{
89+
header: {
90+
label: 'Calories',
91+
},
92+
cell: {
93+
content: (dataValue: any) => {
94+
return dataValue.calories;
95+
},
96+
},
97+
},
98+
{
99+
header: {
100+
label: 'Fat',
101+
},
102+
cell: {
103+
content: (dataValue: any) => {
104+
return dataValue.fat;
105+
},
106+
},
107+
},
108+
{
109+
header: {
110+
label: 'Carbs',
111+
},
112+
cell: {
113+
content: (dataValue: any) => {
114+
return dataValue.carbs;
115+
},
116+
},
117+
},
118+
{
119+
header: {
120+
label: 'Category',
121+
},
122+
cell: {
123+
content: (dataValue: any) => {
124+
return dataValue.category;
125+
},
126+
},
127+
},
128+
];
129+
130+
const Template: ComponentStory<typeof ReactTableModule> = (args) => {
131+
const tableRef = useRef<HTMLTableElement>(null);
132+
133+
const [rowSelection, setRowSelection] = React.useState({});
134+
135+
if (args.enableRowSelection) {
136+
args.state = { rowSelection };
137+
args.onRowSelectionChange = setRowSelection;
138+
}
139+
140+
return (
141+
<div style={{ overflow: 'auto', width: '80%', height: '400px' }}>
142+
<ReactTableModule {...args} ref={tableRef} />
143+
</div>
144+
);
145+
};
146+
147+
export const Default = Template.bind({});
148+
Default.args = {
149+
data,
150+
config,
151+
};
152+
153+
export const RowSelection = Template.bind({});
154+
const selectionColumn = {
155+
id: 'select',
156+
header: ({ table }) => (
157+
<Checkbox
158+
{...{
159+
label: ' ',
160+
checked: table.getIsAllRowsSelected(),
161+
indeterminate: table.getIsSomeRowsSelected(),
162+
onChange: table.getToggleAllRowsSelectedHandler(),
163+
}}
164+
/>
165+
),
166+
cell: ({ row }) => (
167+
<div className="px-1">
168+
<Checkbox
169+
{...{
170+
label: ' ',
171+
checked: row.getIsSelected(),
172+
disabled: !row.getCanSelect(),
173+
onChange: row.getToggleSelectedHandler(),
174+
}}
175+
/>
176+
</div>
177+
),
178+
};
179+
180+
RowSelection.args = {
181+
data,
182+
columns: [selectionColumn, ...columns],
183+
enableRowSelection: true,
184+
};
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as React from 'react';
2+
import clsx from 'clsx';
3+
4+
import {
5+
getCoreRowModel,
6+
useReactTable,
7+
ColumnDef,
8+
OnChangeFn,
9+
TableState,
10+
} from '@tanstack/react-table';
11+
12+
import {
13+
TableModuleProps,
14+
useStyles,
15+
testIds,
16+
} from '../TableModule/TableModule';
17+
import { TableModuleRow } from '../TableModule/TableModuleRow';
18+
import { TableHeaderCell } from '../TableModule/TableHeaderCell';
19+
import { DotLoader } from '../DotLoader/index';
20+
import { getTestProps } from '../../testUtils/getTestProps';
21+
import { mapTableConfigToColumnDef } from './utils';
22+
23+
export interface ReactTableProps<T> extends TableModuleProps {
24+
data: Array<T>;
25+
columns?: ColumnDef<T, any>[];
26+
enableRowSelection?: boolean;
27+
onRowSelectionChange?: OnChangeFn<T>;
28+
state?: Partial<TableState>;
29+
}
30+
31+
export const ReactTableModule = React.memo(
32+
React.forwardRef<HTMLTableElement, ReactTableProps<any>>(
33+
(
34+
{
35+
columns,
36+
config,
37+
className,
38+
data,
39+
enableRowSelection,
40+
isLoading = false,
41+
onRowSelectionChange,
42+
rowRole,
43+
maxCellWidth,
44+
rowClickLabel,
45+
state,
46+
...rootProps
47+
},
48+
forwardedRef
49+
) => {
50+
const classes = useStyles({});
51+
52+
if (columns === undefined && !!config) {
53+
columns = config.map(mapTableConfigToColumnDef);
54+
}
55+
56+
const table = useReactTable({
57+
data,
58+
columns,
59+
enableRowSelection,
60+
getCoreRowModel: getCoreRowModel(),
61+
onRowSelectionChange,
62+
state,
63+
});
64+
65+
const headings = table.getHeaderGroups()[0].headers;
66+
67+
return (
68+
<table
69+
role="table"
70+
className={clsx(classes.root, className)}
71+
ref={forwardedRef}
72+
{...rootProps}
73+
>
74+
<thead className={classes.tableHeader}>
75+
{table.getHeaderGroups().map((headerGroup) => (
76+
<tr
77+
key={headerGroup.id}
78+
className={classes.tableRow}
79+
role="row"
80+
{...getTestProps(testIds.headerRow)}
81+
>
82+
{headerGroup.headers.map((header, i) => (
83+
<TableHeaderCell
84+
index={i}
85+
key={header.id}
86+
header={{ label: header.id }}
87+
></TableHeaderCell>
88+
))}
89+
</tr>
90+
))}
91+
</thead>
92+
<tbody role="rowgroup">
93+
{!isLoading &&
94+
data &&
95+
table
96+
.getRowModel()
97+
.rows.map((row, rowIndex) => (
98+
<TableModuleRow
99+
key={`tableRow-${rowIndex}`}
100+
data={row}
101+
rowRole={rowRole}
102+
maxCellWidth={maxCellWidth}
103+
row={row}
104+
headingsLength={headings?.length}
105+
cells={row.getVisibleCells()}
106+
rowClickLabel={rowClickLabel}
107+
/>
108+
))}
109+
{isLoading && (
110+
<tr
111+
className={classes.tableRow}
112+
{...getTestProps(testIds.isLoadingRow)}
113+
>
114+
<td
115+
className={classes.tableLoadingCell}
116+
colSpan={table.getHeaderGroups()[0].headers.length}
117+
>
118+
<DotLoader size={0} />
119+
</td>
120+
</tr>
121+
)}
122+
</tbody>
123+
</table>
124+
);
125+
}
126+
)
127+
);

src/components/TableModule/TableModuleRow.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import clsx from 'clsx';
22
import * as React from 'react';
3+
import { Cell, flexRender } from '@tanstack/react-table';
34
import { useStyles } from './TableModule';
45
import { getTestProps } from '../../testUtils/getTestProps';
56
import { TableCell } from './types';
@@ -21,7 +22,7 @@ export interface TableModuleRowProps
2122
maxCellWidth?: 1 | 2;
2223
row: any;
2324
headingsLength: number;
24-
cells: Array<TableCell>;
25+
cells: Array<TableCell> | Array<Cell>;
2526
rowActions?: TableModuleProps['rowActions'];
2627
rowClickLabel?: TableModuleProps['rowClickLabel'];
2728
stickyCols?: Array<number>;
@@ -78,7 +79,10 @@ const TableModuleRow: React.FC<TableModuleRowProps> = React.memo(
7879
const rowContents = React.useMemo(
7980
() =>
8081
cells?.map((cell, colIndex) => {
81-
const cellContent = cell.content
82+
// table-core API
83+
const cellContent = cell.column
84+
? flexRender(cell.column.columnDef.cell, cell.getContext())
85+
: cell.content // private API
8286
? cell.content(row)
8387
: cell.valuePath && row[cell.valuePath];
8488
return (

src/components/TableModule/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createColumnHelper, ColumnDef } from '@tanstack/react-table';
2+
import { TableConfiguration } from './types';
3+
4+
const columnHelper = createColumnHelper();
5+
6+
/**
7+
* returns react table ColumnDef.
8+
* @param config legacy configuration of TableModule
9+
*/
10+
export const mapTableConfigToColumnDef = (
11+
config: TableConfiguration<any>
12+
): ColumnDef<any, any> => {
13+
// TODO support accessor columnFn
14+
return {
15+
id: config.header.label || config.header.content(config.header),
16+
accessorFn: config.cell.content,
17+
};
18+
};

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5619,6 +5619,18 @@
56195619
"@svgr/plugin-svgo" "^4.2.0"
56205620
loader-utils "^1.2.3"
56215621

5622+
"@tanstack/react-table@^8.7.9":
5623+
version "8.7.9"
5624+
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.7.9.tgz#9efcd168fb0080a7e0bc213b5eac8b55513babf4"
5625+
integrity sha512-6MbbQn5AupSOkek1+6IYu+1yZNthAKTRZw9tW92Vi6++iRrD1GbI3lKTjJalf8lEEKOqapPzQPE20nywu0PjCA==
5626+
dependencies:
5627+
"@tanstack/table-core" "8.7.9"
5628+
5629+
"@tanstack/table-core@8.7.9":
5630+
version "8.7.9"
5631+
resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.7.9.tgz#0e975f8a5079972f1827a569079943d43257c42f"
5632+
integrity sha512-4RkayPMV1oS2SKDXfQbFoct1w5k+pvGpmX18tCXMofK/VDRdA2hhxfsQlMvsJ4oTX8b0CI4Y3GDKn5T425jBCw==
5633+
56225634
"@testing-library/dom@^7.2.1", "@testing-library/dom@^7.22.3":
56235635
version "7.31.2"
56245636
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"

0 commit comments

Comments
 (0)