feat: add virtualized data table resizer

This commit is contained in:
moonrailgun 2024-08-08 00:51:22 +08:00
parent ad18666851
commit f1aaa7040e
3 changed files with 64 additions and 2 deletions

View File

@ -69,6 +69,7 @@ export function VirtualizedInfiniteDataTable<TData>(
const table = useReactTable({ const table = useReactTable({
data: flatData, data: flatData,
columns, columns,
columnResizeMode: 'onChange',
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@ -87,13 +88,32 @@ export function VirtualizedInfiniteDataTable<TData>(
overscan: 5, overscan: 5,
}); });
/**
* 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 = React.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;
}, [table.getState().columnSizingInfo, table.getState().columnSizing]);
console.log('columnSizeVars', columnSizeVars);
if (isLoading) { if (isLoading) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
return ( return (
<div <div
className="relative h-full overflow-auto" className="virtualized-infinite-data-table relative h-full overflow-auto"
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)} onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}
ref={tableContainerRef} ref={tableContainerRef}
> >
@ -101,6 +121,7 @@ export function VirtualizedInfiniteDataTable<TData>(
<table style={{ display: 'grid' }}> <table style={{ display: 'grid' }}>
<TableHeader <TableHeader
style={{ style={{
...columnSizeVars,
display: 'grid', display: 'grid',
position: 'sticky', position: 'sticky',
top: 0, top: 0,
@ -116,8 +137,9 @@ export function VirtualizedInfiniteDataTable<TData>(
return ( return (
<TableHead <TableHead
key={header.id} key={header.id}
className="relative pt-2.5"
style={{ style={{
width: header.getSize(), width: `calc(var(--header-${header?.id}-size) * 1px)`,
}} }}
> >
{header.isPlaceholder {header.isPlaceholder
@ -126,6 +148,17 @@ export function VirtualizedInfiniteDataTable<TData>(
header.column.columnDef.header, header.column.columnDef.header,
header.getContext() header.getContext()
)} )}
<div
{...{
onDoubleClick: () => header.column.resetSize(),
onMouseDown: header.getResizeHandler(),
onTouchStart: header.getResizeHandler(),
className: `resizer ${
header.column.getIsResizing() ? 'isResizing' : ''
}`,
}}
/>
</TableHead> </TableHead>
); );
})} })}

View File

@ -1,4 +1,5 @@
import './index.css'; import './index.css';
import './styles/global.less';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';

View File

@ -0,0 +1,28 @@
.virtualized-infinite-data-table {
.resizer {
position: absolute;
top: 0;
height: 100%;
right: 0;
width: 2px;
cursor: col-resize;
user-select: none;
touch-action: none;
@apply dark:bg-muted bg-neutral-300 transition-all;
}
.resizer.isResizing {
@apply opacity-100 dark:bg-muted bg-neutral-300;
}
@media (hover: hover) {
.resizer {
opacity: 0;
}
*:hover > .resizer {
opacity: 1;
}
}
}