mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 07:09:02 +08:00
feat(generics): add a new performant resizable table (#6751)
* feat(generics): add a new performant resizble table
This commit is contained in:
parent
43b0cdbb6a
commit
366ca3bb3e
@ -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",
|
||||
@ -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",
|
||||
|
55
frontend/src/components/TableV3/TableV3.styles.scss
Normal file
55
frontend/src/components/TableV3/TableV3.styles.scss
Normal file
@ -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
frontend/src/components/TableV3/TableV3.tsx
Normal file
135
frontend/src/components/TableV3/TableV3.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
@ -2599,6 +2599,11 @@
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@faker-js/faker@9.3.0":
|
||||
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/react-table@8.20.6":
|
||||
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/table-core@8.20.5":
|
||||
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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user