Skip to content

Commit

Permalink
feat(PHC-4380): create ReactTableModule #331
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnzhu committed Mar 20, 2023
1 parent 2c5973d commit b832083
Show file tree
Hide file tree
Showing 6 changed files with 350 additions and 2 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
"react-remove-scroll": "^2.3.0",
"reakit": "1.0.0-rc.1"
},
"optionalDependencies": {
"@tanstack/react-table": "^8.7.9"
},
"resolutions": {
"jsdom": "^16.3.0"
}
Expand Down
184 changes: 184 additions & 0 deletions src/components/TableModule/ReactTableModule.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import React, { useRef } from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { createColumnHelper, Row } from '@tanstack/react-table';
import { Checkbox } from '../Checkbox';

import { ReactTableModule } from './ReactTableModule';

export default {
title: 'Components/TableModule2',
component: ReactTableModule,
argTypes: {},
} as ComponentMeta<typeof ReactTableModule>;

type Food = {
description: string;
calories: string;
fat: string;
carbs: string;
category: string;
};

const data: Food[] = [
{
description: 'Frozen yoghurt',
calories: '159',
fat: '6.0',
carbs: '24',
category: 'yogurt',
},
{
description: 'Ice cream sandwich',
calories: '237',
fat: '9.0',
carbs: '37',
category: 'ice cream',
},
{
description: 'Eclair',
calories: '262',
fat: '16.0',
carbs: '24',
category: 'dessert',
},
{
description: 'Cupcake',
calories: '305',
fat: '3.7',
carbs: '67',
category: 'cake',
},
];

const columnHelper = createColumnHelper<Food>();

const columns = [
columnHelper.accessor('description', {
cell: (info) => info.getValue(),
header: 'Description',
}),
columnHelper.accessor((row) => row.calories, {
id: 'calories',
cell: (info) => <i>{info.getValue()}</i>,
header: () => <span>Calories</span>,
}),
columnHelper.accessor('fat', {
header: () => 'Fat',
cell: (info) => info.renderValue(),
}),
columnHelper.accessor('carbs', {
header: () => <span>Carbs</span>,
}),
columnHelper.accessor('category', {
header: 'Category',
}),
];

const config = [
{
header: {
label: 'Description',
},
cell: {
content: (dataValue: any) => {
return dataValue.description;
},
},
},
{
header: {
label: 'Calories',
},
cell: {
content: (dataValue: any) => {
return dataValue.calories;
},
},
},
{
header: {
label: 'Fat',
},
cell: {
content: (dataValue: any) => {
return dataValue.fat;
},
},
},
{
header: {
label: 'Carbs',
},
cell: {
content: (dataValue: any) => {
return dataValue.carbs;
},
},
},
{
header: {
label: 'Category',
},
cell: {
content: (dataValue: any) => {
return dataValue.category;
},
},
},
];

const Template: ComponentStory<typeof ReactTableModule> = (args) => {
const tableRef = useRef<HTMLTableElement>(null);

const [rowSelection, setRowSelection] = React.useState({});

if (args.enableRowSelection) {
args.state = { rowSelection };
args.onRowSelectionChange = setRowSelection;
}

return (
<div style={{ overflow: 'auto', width: '80%', height: '400px' }}>
<ReactTableModule {...args} ref={tableRef} />
</div>
);
};

export const Default = Template.bind({});
Default.args = {
data,
config,
};

export const RowSelection = Template.bind({});
const selectionColumn = {
id: 'select',
header: ({ table }) => (
<Checkbox
{...{
label: ' ',
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
}}
/>
),
cell: ({ row }) => (
<div className="px-1">
<Checkbox
{...{
label: ' ',
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
onChange: row.getToggleSelectedHandler(),
}}
/>
</div>
),
};

RowSelection.args = {
data,
columns: [selectionColumn, ...columns],
enableRowSelection: true,
};
127 changes: 127 additions & 0 deletions src/components/TableModule/ReactTableModule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as React from 'react';
import clsx from 'clsx';

import {
getCoreRowModel,
useReactTable,
ColumnDef,
OnChangeFn,
TableState,
} from '@tanstack/react-table';

import {
TableModuleProps,
useStyles,
testIds,
} from '../TableModule/TableModule';
import { TableModuleRow } from '../TableModule/TableModuleRow';
import { TableHeaderCell } from '../TableModule/TableHeaderCell';
import { DotLoader } from '../DotLoader/index';
import { getTestProps } from '../../testUtils/getTestProps';
import { mapTableConfigToColumnDef } from './utils';

export interface ReactTableProps<T> extends TableModuleProps {
data: Array<T>;
columns?: ColumnDef<T, any>[];
enableRowSelection?: boolean;
onRowSelectionChange?: OnChangeFn<T>;
state?: Partial<TableState>;
}

export const ReactTableModule = React.memo(
React.forwardRef<HTMLTableElement, ReactTableProps<any>>(
(
{
columns,
config,
className,
data,
enableRowSelection,
isLoading = false,
onRowSelectionChange,
rowRole,
maxCellWidth,
rowClickLabel,
state,
...rootProps
},
forwardedRef
) => {
const classes = useStyles({});

if (columns === undefined && !!config) {
columns = config.map(mapTableConfigToColumnDef);
}

const table = useReactTable({
data,
columns,
enableRowSelection,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange,
state,
});

const headings = table.getHeaderGroups()[0].headers;

return (
<table
role="table"
className={clsx(classes.root, className)}
ref={forwardedRef}
{...rootProps}
>
<thead className={classes.tableHeader}>
{table.getHeaderGroups().map((headerGroup) => (
<tr
key={headerGroup.id}
className={classes.tableRow}
role="row"
{...getTestProps(testIds.headerRow)}
>
{headerGroup.headers.map((header, i) => (
<TableHeaderCell
index={i}
key={header.id}
header={{ label: header.id }}
></TableHeaderCell>
))}
</tr>
))}
</thead>
<tbody role="rowgroup">
{!isLoading &&
data &&
table
.getRowModel()
.rows.map((row, rowIndex) => (
<TableModuleRow
key={`tableRow-${rowIndex}`}
data={row}
rowRole={rowRole}
maxCellWidth={maxCellWidth}
row={row}
headingsLength={headings?.length}
cells={row.getVisibleCells()}
rowClickLabel={rowClickLabel}
/>
))}
{isLoading && (
<tr
className={classes.tableRow}
{...getTestProps(testIds.isLoadingRow)}
>
<td
className={classes.tableLoadingCell}
colSpan={table.getHeaderGroups()[0].headers.length}
>
<DotLoader size={0} />
</td>
</tr>
)}
</tbody>
</table>
);
}
)
);
8 changes: 6 additions & 2 deletions src/components/TableModule/TableModuleRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import clsx from 'clsx';
import * as React from 'react';
import { Cell, flexRender } from '@tanstack/react-table';
import { useStyles } from './TableModule';
import { getTestProps } from '../../testUtils/getTestProps';
import { TableCell } from './types';
Expand All @@ -21,7 +22,7 @@ export interface TableModuleRowProps
maxCellWidth?: 1 | 2;
row: any;
headingsLength: number;
cells: Array<TableCell>;
cells: Array<TableCell> | Array<Cell>;
rowActions?: TableModuleProps['rowActions'];
rowClickLabel?: TableModuleProps['rowClickLabel'];
stickyCols?: Array<number>;
Expand Down Expand Up @@ -78,7 +79,10 @@ const TableModuleRow: React.FC<TableModuleRowProps> = React.memo(
const rowContents = React.useMemo(
() =>
cells?.map((cell, colIndex) => {
const cellContent = cell.content
// table-core API
const cellContent = cell.column
? flexRender(cell.column.columnDef.cell, cell.getContext())
: cell.content // private API
? cell.content(row)
: cell.valuePath && row[cell.valuePath];
return (
Expand Down
18 changes: 18 additions & 0 deletions src/components/TableModule/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createColumnHelper, ColumnDef } from '@tanstack/react-table';
import { TableConfiguration } from './types';

const columnHelper = createColumnHelper();

/**
* returns react table ColumnDef.
* @param config legacy configuration of TableModule
*/
export const mapTableConfigToColumnDef = (
config: TableConfiguration<any>
): ColumnDef<any, any> => {
// TODO support accessor columnFn
return {
id: config.header.label || config.header.content(config.header),
accessorFn: config.cell.content,
};
};
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5619,6 +5619,18 @@
"@svgr/plugin-svgo" "^4.2.0"
loader-utils "^1.2.3"

"@tanstack/react-table@^8.7.9":
version "8.7.9"
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.7.9.tgz#9efcd168fb0080a7e0bc213b5eac8b55513babf4"
integrity sha512-6MbbQn5AupSOkek1+6IYu+1yZNthAKTRZw9tW92Vi6++iRrD1GbI3lKTjJalf8lEEKOqapPzQPE20nywu0PjCA==
dependencies:
"@tanstack/table-core" "8.7.9"

"@tanstack/table-core@8.7.9":
version "8.7.9"
resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.7.9.tgz#0e975f8a5079972f1827a569079943d43257c42f"
integrity sha512-4RkayPMV1oS2SKDXfQbFoct1w5k+pvGpmX18tCXMofK/VDRdA2hhxfsQlMvsJ4oTX8b0CI4Y3GDKn5T425jBCw==

"@testing-library/dom@^7.2.1", "@testing-library/dom@^7.22.3":
version "7.31.2"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
Expand Down

0 comments on commit b832083

Please sign in to comment.