-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(generics): add a new performant resizable table (#6751)
* feat(generics): add a new performant resizble table
- Loading branch information
1 parent
43b0cdb
commit 366ca3b
Showing
4 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
.div-table { | ||
border: 1px solid lightgray; | ||
width: fit-content; | ||
} | ||
|
||
.div-tr { | ||
display: flex; | ||
width: fit-content; | ||
height: 30px; | ||
} | ||
|
||
.div-th, | ||
.div-td { | ||
box-shadow: inset 0 0 0 1px lightgray; | ||
padding: 0.25rem; | ||
} | ||
|
||
.div-th { | ||
padding: 2px 4px; | ||
position: relative; | ||
font-weight: bold; | ||
text-align: center; | ||
height: 30px; | ||
} | ||
|
||
.div-td { | ||
height: 30px; | ||
} | ||
|
||
.resizer { | ||
position: absolute; | ||
top: 0; | ||
height: 100%; | ||
right: 0; | ||
width: 5px; | ||
background: rgba(0, 0, 0, 0.5); | ||
cursor: col-resize; | ||
user-select: none; | ||
touch-action: none; | ||
} | ||
|
||
.resizer.isResizing { | ||
background: blue; | ||
opacity: 1; | ||
} | ||
|
||
@media (hover: hover) { | ||
.resizer { | ||
opacity: 0; | ||
} | ||
|
||
*:hover > .resizer { | ||
opacity: 1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import './TableV3.styles.scss'; | ||
|
||
import { | ||
ColumnDef, | ||
flexRender, | ||
getCoreRowModel, | ||
Table, | ||
useReactTable, | ||
} from '@tanstack/react-table'; | ||
import React, { useMemo } from 'react'; | ||
|
||
// here we are manually rendering the table body so that we can memoize the same for performant re-renders | ||
function TableBody<T>({ table }: { table: Table<T> }): JSX.Element { | ||
return ( | ||
<div className="div-tbody"> | ||
{table.getRowModel().rows.map((row) => ( | ||
<div key={row.id} className="div-tr"> | ||
{row.getVisibleCells().map((cell) => ( | ||
<div | ||
key={cell.id} | ||
className="div-td" | ||
// we are manually setting the column width here based on the calculated column vars | ||
style={{ | ||
width: `calc(var(--col-${cell.column.id}-size) * 1px)`, | ||
}} | ||
> | ||
{cell.renderValue<any>()} | ||
</div> | ||
))} | ||
</div> | ||
))} | ||
</div> | ||
); | ||
} | ||
|
||
// memoize the table body based on the data object being passed to the table | ||
const MemoizedTableBody = React.memo( | ||
TableBody, | ||
(prev, next) => prev.table.options.data === next.table.options.data, | ||
) as typeof TableBody; | ||
|
||
interface ITableConfig { | ||
defaultColumnMinSize: number; | ||
defaultColumnMaxSize: number; | ||
} | ||
interface ITableV3Props<T> { | ||
columns: ColumnDef<T, any>[]; | ||
data: T[]; | ||
config: ITableConfig; | ||
} | ||
|
||
export function TableV3<T>(props: ITableV3Props<T>): JSX.Element { | ||
const { data, columns, config } = props; | ||
|
||
const table = useReactTable({ | ||
data, | ||
columns, | ||
defaultColumn: { | ||
minSize: config.defaultColumnMinSize, | ||
maxSize: config.defaultColumnMaxSize, | ||
}, | ||
columnResizeMode: 'onChange', | ||
getCoreRowModel: getCoreRowModel(), | ||
debugTable: true, | ||
debugHeaders: true, | ||
debugColumns: true, | ||
}); | ||
|
||
/** | ||
* Instead of calling `column.getSize()` on every render for every header | ||
* and especially every data cell (very expensive), | ||
* we will calculate all column sizes at once at the root table level in a useMemo | ||
* and pass the column sizes down as CSS variables to the <table> element. | ||
*/ | ||
const columnSizeVars = useMemo(() => { | ||
const headers = table.getFlatHeaders(); | ||
const colSizes: { [key: string]: number } = {}; | ||
for (let i = 0; i < headers.length; i++) { | ||
const header = headers[i]!; | ||
colSizes[`--header-${header.id}-size`] = header.getSize(); | ||
colSizes[`--col-${header.column.id}-size`] = header.column.getSize(); | ||
} | ||
return colSizes; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [table.getState().columnSizingInfo, table.getState().columnSizing]); | ||
|
||
return ( | ||
<div className="p-2"> | ||
{/* Here in the <table> equivalent element (surrounds all table head and data cells), we will define our CSS variables for column sizes */} | ||
<div | ||
className="div-table" | ||
style={{ | ||
...columnSizeVars, // Define column sizes on the <table> element | ||
width: table.getTotalSize(), | ||
}} | ||
> | ||
<div className="div-thead"> | ||
{table.getHeaderGroups().map((headerGroup) => ( | ||
<div key={headerGroup.id} className="div-tr"> | ||
{headerGroup.headers.map((header) => ( | ||
<div | ||
key={header.id} | ||
className="div-th" | ||
style={{ | ||
width: `calc(var(--header-${header?.id}-size) * 1px)`, | ||
}} | ||
> | ||
{header.isPlaceholder | ||
? null | ||
: flexRender(header.column.columnDef.header, header.getContext())} | ||
<div | ||
{...{ | ||
onDoubleClick: (): void => header.column.resetSize(), | ||
onMouseDown: header.getResizeHandler(), | ||
onTouchStart: header.getResizeHandler(), | ||
className: `resizer ${ | ||
header.column.getIsResizing() ? 'isResizing' : '' | ||
}`, | ||
}} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
))} | ||
</div> | ||
{/* When resizing any column we will render this special memoized version of our table body */} | ||
{table.getState().columnSizingInfo.isResizingColumn ? ( | ||
<MemoizedTableBody table={table} /> | ||
) : ( | ||
<TableBody table={table} /> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2599,6 +2599,11 @@ | |
minimatch "^3.0.4" | ||
strip-json-comments "^3.1.1" | ||
|
||
"@faker-js/[email protected]": | ||
version "9.3.0" | ||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.3.0.tgz#ef398dab34c67faaa0e348318c905eae3564fa58" | ||
integrity sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw== | ||
|
||
"@floating-ui/core@^1.4.2": | ||
version "1.5.2" | ||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071" | ||
|
@@ -3678,6 +3683,18 @@ | |
dependencies: | ||
"@sinonjs/commons" "^1.7.0" | ||
|
||
"@tanstack/[email protected]": | ||
version "8.20.6" | ||
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.6.tgz#a1f3103327aa59aa621931f4087a7604a21054d0" | ||
integrity sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ== | ||
dependencies: | ||
"@tanstack/table-core" "8.20.5" | ||
|
||
"@tanstack/[email protected]": | ||
version "8.20.5" | ||
resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.20.5.tgz#3974f0b090bed11243d4107283824167a395cf1d" | ||
integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg== | ||
|
||
"@testing-library/dom@^8.5.0": | ||
version "8.20.0" | ||
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz" | ||
|