Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wise-trees-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@propeldata/ui-kit': minor
---

Added virtualization to DataGrid component
95 changes: 95 additions & 0 deletions .pnp.cjs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/ui-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@radix-ui/themes": "^3.1.3",
"@tanstack/react-query": "^4.29.17",
"@tanstack/react-table": "^8.15.3",
"@tanstack/react-virtual": "^3.11.2",
"chart.js": "^4.1.2",
"chartjs-adapter-luxon": "1.2.1",
"classnames": "^2.5.1",
Expand Down
4 changes: 1 addition & 3 deletions packages/ui-kit/src/components/DataGrid/DataGrid.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
position: relative;

position: sticky;
top: 0px;
top: 0;

box-sizing: border-box;

Expand Down Expand Up @@ -240,13 +240,11 @@
.tableIndexCell {
text-align: center;
width: var(--propel-space-7);
min-width: var(--propel-space-7);
max-width: var(--propel-space-7);

left: 0;

height: var(--propel-space-7);
min-height: var(--propel-space-7);
max-height: var(--propel-space-7);

background-color: var(--propel-color-background);
Expand Down
15 changes: 15 additions & 0 deletions packages/ui-kit/src/components/DataGrid/DataGrid.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,18 @@ export const Custom: Story = {
},
render: (args) => <DataGrid {...args} />
}

export const Virtualization: Story = {
args: {
...Basic.args,
query: {
...Basic.args?.query
},
paginationProps: {
defaultPageSize: 20000,
pageSizeOptions: [1, 20000]
},
virtualize: true
},
render: (args) => <DataGrid {...args} />
}
12 changes: 8 additions & 4 deletions packages/ui-kit/src/components/DataGrid/DataGrid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const handlers = [
ctx.data({
dataGrid: {
headers: ['name'],
rows: [['page 2']],
rows: [['page 2'], ['page 2.2']],
pageInfo: {
hasNextPage: false,
hasPreviousPage: true,
Expand All @@ -32,7 +32,7 @@ const handlers = [
ctx.data({
dataGrid: {
headers: ['name'],
rows: [['page 1']],
rows: [['page 1'], ['page 1.1']],
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
Expand Down Expand Up @@ -138,7 +138,11 @@ describe('DataGrid', () => {
}, 10000)

it('paginates forward and backwards', async () => {
dom = render(<DataGrid query={{ dataPool: { name: 'should-paginate' }, accessToken: 'test-token' }} />)
dom = render(
<div style={{ height: '1000px' }}>
<DataGrid query={{ dataPool: { name: 'should-paginate' }, accessToken: 'test-token' }} />
</div>
)

await dom.findByText('page 1')

Expand All @@ -151,7 +155,7 @@ describe('DataGrid', () => {

fireEvent.click(await dom.findByTestId('propel-datagrid-paginate-back'))
await dom.findByText('page 1')
}, 30000)
}, 40000)

it('changes page size', async () => {
dom = render(<DataGrid query={{ dataPool: { name: 'changes-page-size' }, accessToken: 'test-token' }} />)
Expand Down
143 changes: 94 additions & 49 deletions packages/ui-kit/src/components/DataGrid/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@tanstack/react-table'
import { Tooltip } from '@radix-ui/themes'
import '@radix-ui/themes/styles.css'
import { useVirtualizer, VirtualItem } from '@tanstack/react-virtual'

import { withContainer } from '../withContainer'
import { ErrorFallback, ErrorFallbackProps } from '../ErrorFallback'
Expand Down Expand Up @@ -68,6 +69,7 @@ export const DataGridComponent = React.forwardRef<HTMLDivElement, DataGridProps>
hideBorder,
accentColor,
showRowCount = false,
virtualize = false,
...rest
},
forwardedRef
Expand Down Expand Up @@ -257,6 +259,24 @@ export const DataGridComponent = React.forwardRef<HTMLDivElement, DataGridProps>
...reactTablePagination
})

const headerGroups = table.getHeaderGroups()
const rowGroup = table.getRowModel().rows

const rowVirtualizer = useVirtualizer({
count: rowGroup?.length ?? 0,
estimateSize: () => 40, //estimate row height for accurate scrollbar dragging
getScrollElement: () => containerRef.current,
//measure dynamic row height, except in firefox because it measures table border height incorrectly
measureElement:
typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1
? (element) => element?.getBoundingClientRect().height
: undefined,
overscan: 5,
enabled: virtualize
})

const visibleRowGroup = virtualize ? rowVirtualizer.getVirtualItems() : rowGroup

useEffect(() => {
function setDefaultSizing() {
const INDEX_COLUMN_WIDTH = 40
Expand Down Expand Up @@ -440,9 +460,6 @@ export const DataGridComponent = React.forwardRef<HTMLDivElement, DataGridProps>

const linesStyle = getLinesStyle(tableLinesLayout)

const headerGroups = table.getHeaderGroups()
const rowGroup = table.getRowModel().rows

return (
<div
ref={setRef}
Expand All @@ -452,7 +469,17 @@ export const DataGridComponent = React.forwardRef<HTMLDivElement, DataGridProps>
>
<div ref={containerRef} className={componentStyles.container}>
<div className={componentStyles.tableContainer}>
<div ref={tableRef} className={componentStyles.table} style={{ width: '100%' }} {...slotProps?.table}>
<div
ref={tableRef}
className={componentStyles.table}
style={{
height: virtualize ? rowVirtualizer.getTotalSize() + 'px' : undefined,
position: virtualize ? 'relative' : undefined,
width: '100%',
maxHeight: 'unset'
}}
{...slotProps?.table}
>
<div className={classNames(componentStyles.tableHead)}>
{headerGroups.map((headerGroup) => (
<div
Expand Down Expand Up @@ -526,65 +553,83 @@ export const DataGridComponent = React.forwardRef<HTMLDivElement, DataGridProps>
))}
</div>
<div role="rowgroup" className={componentStyles.tableBody}>
{rowGroup.map((row, index) => (
<div
role="row"
className={classNames(componentStyles.tableRow, {
[componentStyles.preventGap]: isTableOverflowingY
})}
style={{ width: isResizable ? 'fit-content' : '100%' }}
key={row.id}
>
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */}
{visibleRowGroup.map((groupRow) => {
const virtualRow = { ...groupRow } as VirtualItem
let row: Row<string[]>
if (virtualRow.start != null) {
row = rowGroup[virtualRow.index]
} else {
row = groupRow as Row<string[]>
}

return (
<div
{...slotProps?.cell}
role="cell"
onClick={() => handleRowIndexClick(row)}
className={classNames(
componentStyles.tableCell,
componentStyles.tableIndexCell,
{
[componentStyles.selectedRow]: row.id === selectedRow?.id
},
linesStyle,
{
[componentStyles.hideBorder]: hideBorder
},
slotProps?.cell?.className
)}
role="row"
data-index={row.index}
ref={(node) => rowVirtualizer.measureElement(node)} //measure dynamic row height
className={classNames(componentStyles.tableRow, {
[componentStyles.preventGap]: isTableOverflowingY
})}
style={{
display: 'flex',
position: virtualize ? 'absolute' : undefined,
transform: virtualize ? `translateY(${virtualRow.start}px)` : undefined, //this should always be a `style` as it changes on scroll
width: isResizable ? 'fit-content' : '100%'
}}
key={row.id}
>
<div>{index + 1 + pageIndex * (isStatic ? clientPagination.pageSize : pageSize)}</div>
</div>
{row.getVisibleCells().map((cell, index) => (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */}
<div
{...slotProps?.cell}
role="cell"
onClick={() => handleRowCellClick(cell)}
style={{
...slotProps?.cell?.style,
width: isResizable ? cell.column.getSize() : cell.column.getSize()
}}
onClick={() => handleRowIndexClick(row)}
className={classNames(
componentStyles.tableCell,
componentStyles.tableIndexCell,
{
[componentStyles.selectedRow]:
cell.id === selectedRow?.cells[index].id || cell.id === selectedCell?.id
[componentStyles.selectedRow]: row.id === selectedRow?.id
},
linesStyle,
slotProps?.cell?.className,
{
[componentStyles.hideBorder]: hideBorder,
[componentStyles.numeric]: !isNaN(Number(cell.getValue()))
}
[componentStyles.hideBorder]: hideBorder
},
slotProps?.cell?.className
)}
key={cell.id}
style={slotProps?.cell?.style}
>
<span>{flexRender(cell.column.columnDef.cell, cell.getContext())}</span>
<div>{row.index + 1 + pageIndex * (isStatic ? clientPagination.pageSize : pageSize)}</div>
</div>
))}
</div>
))}
{row.getVisibleCells().map((cell, index) => (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events
<div
{...slotProps?.cell}
role="cell"
onClick={() => handleRowCellClick(cell)}
style={{
...slotProps?.cell?.style,
width: isResizable ? cell.column.getSize() : cell.column.getSize()
}}
className={classNames(
componentStyles.tableCell,
{
[componentStyles.selectedRow]:
cell.id === selectedRow?.cells[index].id || cell.id === selectedCell?.id
},
linesStyle,
slotProps?.cell?.className,
{
[componentStyles.hideBorder]: hideBorder,
[componentStyles.numeric]: !isNaN(Number(cell.getValue()))
}
)}
key={cell.id}
>
<span>{flexRender(cell.column.columnDef.cell, cell.getContext())}</span>
</div>
))}
</div>
)
})}
</div>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/ui-kit/src/components/DataGrid/DataGrid.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export interface DataGridProps extends ThemeSettingProps, DataComponentProps<'di
prettifyHeaders?: boolean
/** If true, row count will be shown in the footer */
showRowCount?: boolean
/** If true, the table will be virtualized */
virtualize?: boolean
}

interface DataGridPaginationProps {
Expand Down
20 changes: 20 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5750,6 +5750,7 @@ __metadata:
"@swc/jest": ^0.2.26
"@tanstack/react-query": ^4.29.17
"@tanstack/react-table": ^8.15.3
"@tanstack/react-virtual": ^3.11.2
"@testing-library/jest-dom": ^5.16.5
"@testing-library/react": ^14.0.0
"@testing-library/user-event": ^14.4.3
Expand Down Expand Up @@ -9587,13 +9588,32 @@ __metadata:
languageName: node
linkType: hard

"@tanstack/react-virtual@npm:^3.11.2":
version: 3.11.2
resolution: "@tanstack/react-virtual@npm:3.11.2::__archiveUrl=https%3A%2F%2Fregistry.npmjs.org%2F%40tanstack%2Freact-virtual%2F-%2Freact-virtual-3.11.2.tgz"
dependencies:
"@tanstack/virtual-core": 3.11.2
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
checksum: a1136da0ec4c2ecbd4f996d8b84f228f0b8d851b15806e01049a160ad1d9b2eef0e0a491035fe017c6f84a0e125334f69ea23b32c180df23614ea4a8eeb7490c
languageName: node
linkType: hard

"@tanstack/table-core@npm:8.15.3":
version: 8.15.3
resolution: "@tanstack/table-core@npm:8.15.3::__archiveUrl=https%3A%2F%2Fregistry.npmjs.org%2F%40tanstack%2Ftable-core%2F-%2Ftable-core-8.15.3.tgz"
checksum: eab528dcbb994cd8182f099e5b59f416b3b9cde08e88c676614f3484bc6172c7e3e3d895d1e35fa64a749b237390bc665b01187286ee37ac0417d8286953dad9
languageName: node
linkType: hard

"@tanstack/virtual-core@npm:3.11.2":
version: 3.11.2
resolution: "@tanstack/virtual-core@npm:3.11.2::__archiveUrl=https%3A%2F%2Fregistry.npmjs.org%2F%40tanstack%2Fvirtual-core%2F-%2Fvirtual-core-3.11.2.tgz"
checksum: b5c91662461e3edd1cba0efbaa89e1d061c8bb605bb78d1e87e2a687335c740a731c96a81798b05491df4882ff2fbd27b312f5e7440e4f9d553a81fb2283156a
languageName: node
linkType: hard

"@testing-library/dom@npm:^9.0.0":
version: 9.3.3
resolution: "@testing-library/dom@npm:9.3.3::__archiveUrl=https%3A%2F%2Fregistry.npmjs.org%2F%40testing-library%2Fdom%2F-%2Fdom-9.3.3.tgz"
Expand Down