Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Improve data grid #3231

Merged
merged 11 commits into from
Jan 24, 2024
4 changes: 2 additions & 2 deletions packages/eds-data-grid-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8",
"styled-components": ">=4.2"
"styled-components": ">=4.2",
"@equinor/eds-core-react": "workspace:^"
},
"dependencies": {
"@equinor/eds-core-react": "workspace:^",
"@equinor/eds-icons": "workspace:^",
"@equinor/eds-tokens": "workspace:*",
"@equinor/eds-utils": "workspace:^",
Expand Down
74 changes: 73 additions & 1 deletion packages/eds-data-grid-react/src/EdsDataGrid.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@ import { Meta, StoryFn } from '@storybook/react'
import { EdsDataGrid } from './EdsDataGrid'
import { columns, groupedColumns, Photo } from './stories/columns'
import { data } from './stories/data'
import { CSSProperties, useEffect, useState } from 'react'
import {
CSSProperties,
ChangeEvent,
FormEvent,
useEffect,
useRef,
useState,
} from 'react'
import {
Button,
Checkbox,
Pagination,
Paper,
TextField,
Typography,
} from '@equinor/eds-core-react'
import page from './EdsDataGrid.docs.mdx'
import { Column, Row } from '@tanstack/react-table'
import { tokens } from '@equinor/eds-tokens'
import { action } from '@storybook/addon-actions'
import { EdsDataGridProps } from './EdsDataGridProps'
import { Virtualizer } from './types'

const meta: Meta<typeof EdsDataGrid<Photo>> = {
title: 'EDS Data grid',
Expand Down Expand Up @@ -198,6 +207,69 @@ export const ColumnOrdering: StoryFn<EdsDataGridProps<Photo>> = (args) => {
)
}

export const ScrollToIndex: StoryFn<EdsDataGridProps<Photo>> = (args) => {
const [index, setIndex] = useState<number>(100)

const rowVirtualizerInstanceRef =
useRef<Virtualizer<HTMLDivElement, Element>>(null)

const scrollToIndex = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()

rowVirtualizerInstanceRef.current?.scrollToIndex(index, {
align: 'center',
behavior: 'smooth',
})
}

return (
<>
<form
style={{
display: 'flex',
alignItems: 'end',
gap: '1rem',
marginBottom: '1rem',
}}
onSubmit={scrollToIndex}
>
<TextField
id="index"
type="number"
label="Index"
name="index"
value={String(index)}
style={{ width: '8rem' }}
min={0}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setIndex(e.target.valueAsNumber)
}
/>
<Button type="submit" style={{ flexShrink: 0 }} disabled={isNaN(index)}>
Scroll To Index
</Button>
</form>

<EdsDataGrid
{...args}
rowVirtualizerInstanceRef={rowVirtualizerInstanceRef}
/>
</>
)
}

ScrollToIndex.args = {
stickyHeader: true,
enableVirtual: true,
height: 300,
rows: Array.from(new Array(100)).flatMap((_, copyNumber) =>
data.map<(typeof data)[number]>((item, index, { length }) => ({
...item,
id: index + copyNumber * length,
})),
),
}

export const HideShowColumns: StoryFn<EdsDataGridProps<Photo>> = (args) => {
const [visible, setVisible] = useState<{ [key in keyof Photo]?: boolean }>({})
return (
Expand Down
84 changes: 58 additions & 26 deletions packages/eds-data-grid-react/src/EdsDataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Pagination, Table, Typography, useEds } from '@equinor/eds-core-react'
import {
ColumnDef,
ColumnFiltersState,
Expand All @@ -16,7 +17,7 @@ import {
TableOptions,
useReactTable,
} from '@tanstack/react-table'
import { Pagination, Table, useEds } from '@equinor/eds-core-react'
import { useVirtualizer } from '@tanstack/react-virtual'
import {
CSSProperties,
useCallback,
Expand All @@ -28,7 +29,6 @@ import {
import { TableHeaderRow } from './components/TableHeaderRow'
import { TableRow } from './components/TableRow'
import { TableProvider } from './EdsDataGridContext'
import { useVirtualizer } from '@tanstack/react-virtual'
import { EdsDataGridProps } from './EdsDataGridProps'

export function EdsDataGrid<T>({
Expand Down Expand Up @@ -64,7 +64,10 @@ export function EdsDataGrid<T>({
columnPinState,
scrollbarHorizontal,
width,
minWidth,
height,
getRowId,
rowVirtualizerInstanceRef,
}: EdsDataGridProps<T>) {
const [sorting, setSorting] = useState<SortingState>(sortingState ?? [])
const [selection, setSelection] = useState<RowSelectionState>(
Expand Down Expand Up @@ -147,6 +150,23 @@ export function EdsDataGrid<T>({
const options: TableOptions<T> = {
data: rows,
columns: _columns,
defaultColumn: {
cell: (context) => {
return (
<Typography
style={{
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
group="table"
variant="cell_text"
>
{String(context.getValue() ?? '')}
</Typography>
)
},
},
columnResizeMode: columnResizeMode,
state: {
sorting,
Expand All @@ -173,6 +193,7 @@ export function EdsDataGrid<T>({
enableRowSelection: rowSelection ?? false,
enableColumnPinning: true,
enablePinning: true,
getRowId,
}

useEffect(() => {
Expand Down Expand Up @@ -267,6 +288,8 @@ export function EdsDataGrid<T>({
getScrollElement: () => parentRef.current,
estimateSize,
})
if (rowVirtualizerInstanceRef) rowVirtualizerInstanceRef.current = virtualizer

const virtualRows = virtualizer.getVirtualItems()
const paddingTop = virtualRows.length ? virtualRows[0].start : 0
const paddingBottom = virtualRows.length
Expand Down Expand Up @@ -298,9 +321,9 @@ export function EdsDataGrid<T>({
style={{
height: height ?? 'auto',
...parentRefStyle,
width: scrollbarHorizontal ? width : 'auto',
tableLayout: scrollbarHorizontal ? 'fixed' : 'auto',
width: scrollbarHorizontal ? width ?? '100%' : 'auto',
overflow: 'auto',
contain: 'layout paint style',
}}
ref={parentRef}
>
Expand All @@ -311,7 +334,9 @@ export function EdsDataGrid<T>({
.join(' ')}
{...{
style: {
tableLayout: scrollbarHorizontal ? 'fixed' : 'auto',
width: table.getTotalSize(),
minWidth: scrollbarHorizontal ? minWidth : 'auto',
},
}}
>
Expand All @@ -327,43 +352,50 @@ export function EdsDataGrid<T>({
/>
))}
</Table.Head>
<Table.Body>
<Table.Body style={{ backgroundColor: 'inherit' }}>
{table.getRowModel().rows.length === 0 && emptyMessage && (
<Table.Row>
<Table.Cell colSpan={table.getHeaderGroups().length}>
<Table.Cell colSpan={table.getFlatHeaders().length}>
{emptyMessage}
</Table.Cell>
</Table.Row>
)}
{enableVirtual && (
<>
<Table.Row
data-testid="virtual-padding-top"
className={'virtual-padding-top'}
>
<Table.Cell
style={{
height: `${paddingTop}px`,
}}
></Table.Cell>
</Table.Row>
{paddingTop > 0 && (
<Table.Row
data-testid="virtual-padding-top"
className={'virtual-padding-top'}
style={{ pointerEvents: 'none' }}
>
<Table.Cell
style={{
height: `${paddingTop}px`,
}}
/>
</Table.Row>
)}

{virtualRows.map((row) => (
<TableRow
key={row.index}
row={table.getRowModel().rows[row.index]}
/>
))}
<Table.Row
data-testid="virtual-padding-bottom"
className={'virtual-padding-bottom'}
>
<Table.Cell
style={{
height: `${paddingBottom}px`,
}}
></Table.Cell>
</Table.Row>

{paddingBottom > 0 && (
<Table.Row
data-testid="virtual-padding-bottom"
className={'virtual-padding-bottom'}
style={{ pointerEvents: 'none' }}
>
<Table.Cell
style={{
height: `${paddingBottom}px`,
}}
/>
</Table.Row>
)}
</>
)}
{!enableVirtual &&
Expand Down
36 changes: 32 additions & 4 deletions packages/eds-data-grid-react/src/EdsDataGridProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
Row,
RowSelectionState,
SortingState,
TableOptions,
} from '@tanstack/react-table'
import { CSSProperties, ReactElement } from 'react'
import { Virtualizer } from '@tanstack/react-virtual'
import { CSSProperties, MutableRefObject, ReactElement } from 'react'

type BaseProps<T> = {
/**
Expand Down Expand Up @@ -67,14 +69,32 @@ type BaseProps<T> = {
scrollbarHorizontal?: boolean
/**
* Width of the table. Only takes effect if {@link scrollbarHorizontal} is true.
*
* No suffix (like `px` or `rem`) equals to `px`.
* @default 800
*/
width?: number
width?: string | number
/**
* Min width of the table element.
*
* @example minWidth: 800
* @default none
*/
minWidth?: string | number
/**
* Height of the table.
*
* No suffix (like `px` or `rem`) equals to `px`.
* @default none
*/
height?: number
height?: string | number
/**
* This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with `.` using their grandparents' index eg. `index.index.index`). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc.
* @example getRowId: row => row.userId
* @link [API Docs](https://tanstack.com/table/v8/docs/api/core/table#getrowid)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/tables)
*/
getRowId?: TableOptions<T>['getRowId']
}

type StyleProps<T> = {
Expand Down Expand Up @@ -180,13 +200,21 @@ type ColumnProps = {
columnPinState?: ColumnPinningState
}

type RefProps = {
rowVirtualizerInstanceRef?: MutableRefObject<Virtualizer<
HTMLDivElement,
Element
> | null>
}

export type EdsDataGridProps<T> = BaseProps<T> &
StyleProps<T> &
SortProps &
FilterProps &
PagingProps &
ColumnProps &
VirtualProps & {
VirtualProps &
RefProps & {
/**
* Which columns are visible. If not set, all columns are visible. undefined means that the column is visible.
* @default undefined
Expand Down
13 changes: 3 additions & 10 deletions packages/eds-data-grid-react/src/components/TableBodyCell.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Cell, ColumnPinningPosition, flexRender } from '@tanstack/react-table'
import { Table, Typography } from '@equinor/eds-core-react'
import { Table } from '@equinor/eds-core-react'
import { useTableContext } from '../EdsDataGridContext'
import { useMemo } from 'react'
import { tokens } from '@equinor/eds-tokens'
import styled from 'styled-components'

type Props<T> = {
Expand All @@ -21,11 +20,7 @@ const StyledCell = styled(Table.Cell)<{
return ''
}}
z-index: ${(p) => (p.$pinned ? 11 : 'auto')};
background-color: ${(p) =>
p.$pinned ? tokens.colors.ui.background__default.hex : 'inherit'};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background-color: inherit;
`

export function TableBodyCell<T>({ cell }: Props<T>) {
Expand Down Expand Up @@ -54,9 +49,7 @@ export function TableBodyCell<T>({ cell }: Props<T>) {
},
}}
>
<Typography as="span" group="table" variant="cell_text">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Typography>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</StyledCell>
)
}
Loading
Loading