Skip to content

Commit

Permalink
feat(generics): add a new performant resizable table (#6751)
Browse files Browse the repository at this point in the history
* feat(generics): add a new performant resizble table
  • Loading branch information
vikrantgupta25 authored Jan 5, 2025
1 parent 43b0cdb commit 366ca3b
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 0 deletions.
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@sentry/react": "8.41.0",
"@sentry/webpack-plugin": "2.22.6",
"@signozhq/design-tokens": "1.1.4",
"@tanstack/react-table": "8.20.6",
"@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0",
"@visx/shape": "3.5.0",
Expand Down Expand Up @@ -153,6 +154,7 @@
"@babel/preset-typescript": "^7.21.4",
"@commitlint/cli": "^16.3.0",
"@commitlint/config-conventional": "^16.2.4",
"@faker-js/faker": "9.3.0",
"@jest/globals": "^27.5.1",
"@playwright/test": "^1.22.0",
"@testing-library/jest-dom": "5.16.5",
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/components/TableV3/TableV3.styles.scss
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;
}
}
135 changes: 135 additions & 0 deletions frontend/src/components/TableV3/TableV3.tsx
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>
);
}
17 changes: 17 additions & 0 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 366ca3b

Please sign in to comment.