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

feat: table virtualization #2489

Open
wants to merge 78 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
083a1b3
feat: virtulization on table poc
saurabhdaware Jan 8, 2025
7d2dd2b
chore: virtulized selectable demo
tewarig Jan 12, 2025
0aa7112
chore: minor changes
tewarig Jan 14, 2025
8f9cb0d
chore: update table styling and virtulization poc
tewarig Jan 14, 2025
fc37ce6
chore: add toolbar
tewarig Jan 15, 2025
1aaf188
chore: styling fix
tewarig Jan 15, 2025
2b4aa42
fix: height of table
tewarig Jan 20, 2025
dc52d5c
chore: remove hasPadding from tableBody
tewarig Jan 20, 2025
cec32e2
chore: scroll styling in case of not proper space is their , in virtu…
tewarig Jan 20, 2025
e21ee7f
chore: added isVirtualized prop
tewarig Jan 20, 2025
46dee3a
chore: added dynamic width and height
tewarig Jan 20, 2025
b7105ab
chore: table hover actions fixes
tewarig Jan 21, 2025
cdbe7e3
fix: row bottom not visible with rowHeight
tewarig Jan 21, 2025
e236d3e
fix: distance between header height and table height
tewarig Jan 21, 2025
bf39ca1
fix: gap between table cells
tewarig Jan 21, 2025
96cbd91
chore: added isSelectable todo
tewarig Jan 21, 2025
463c2b9
chore: add isVirtualized prop
tewarig Jan 21, 2025
449feb6
chore: remove unused classes
tewarig Jan 21, 2025
6b20fcb
chore: remove comments
tewarig Jan 21, 2025
39dc02a
chore: code clean up
tewarig Jan 21, 2025
e8b7f4d
fix: old table styling
tewarig Jan 21, 2025
d3d27d9
chore: table height fix
tewarig Jan 22, 2025
628ec0f
fix: table bg on hover , (but hover actions are not working now ;_;)
tewarig Jan 22, 2025
dc41225
fix: table hover actions :tick
tewarig Jan 22, 2025
bba72bf
fix: inconsistent size of checkbox header
tewarig Jan 22, 2025
5bec187
chore: added examples
tewarig Jan 22, 2025
ed69707
chore: update story
tewarig Jan 22, 2025
6c0be96
chore: pagination check and prop fix
tewarig Jan 22, 2025
41fb380
chore: throw error
tewarig Jan 22, 2025
9976ef5
chore: added error
tewarig Jan 22, 2025
0522eb1
fix: make table response
tewarig Jan 22, 2025
0209b1f
chore: update docs & components and types
tewarig Jan 22, 2025
9112ea0
chore: fixed ts and lint errors in code
tewarig Jan 22, 2025
14c19db
fix: more ts error and ignore few files now
tewarig Jan 22, 2025
48c5af6
chore: ignore table test
tewarig Jan 23, 2025
0d68ee5
chore: update test
tewarig Jan 23, 2025
30f7122
fix: ts error
tewarig Jan 23, 2025
e74cc16
fix: more ts error
tewarig Jan 23, 2025
494db30
chore: update virtuazlied table api
tewarig Jan 23, 2025
8be1b12
chore: added table body styles
tewarig Jan 23, 2025
7cb1f2e
refactor: styles for table and table body
tewarig Jan 23, 2025
fae4a09
chore: code clean up
tewarig Jan 23, 2025
9cafe8a
chore: added test & updated ref type
tewarig Jan 23, 2025
c1fc368
chore: added changeset
tewarig Jan 23, 2025
44c03ec
fix: lint error
tewarig Jan 24, 2025
0e3a352
chore: cleanup
tewarig Jan 24, 2025
51cf688
chore: update snap
tewarig Jan 24, 2025
213540c
chore: fix snap
tewarig Jan 24, 2025
43a0f2d
chore: removed ununsed code
tewarig Jan 24, 2025
2656536
chore: unused code
tewarig Jan 24, 2025
1e51e8e
chore: move updates
tewarig Jan 24, 2025
8d6df4c
chore: update snap
tewarig Jan 24, 2025
4375895
chore: update table
tewarig Jan 24, 2025
a013e48
chore: self review changes
tewarig Jan 27, 2025
9099aa6
chore: removed getTableRowSelector
tewarig Jan 28, 2025
74c0f62
chore: minior review changes
tewarig Jan 28, 2025
70876f4
chore: table api changes
tewarig Jan 28, 2025
97c4ead
chore: make rowHeight internal
tewarig Jan 28, 2025
bb35c21
chore: more review changes
tewarig Jan 28, 2025
18b99a4
chore: update docs
tewarig Jan 28, 2025
cec9480
chore: added more stories
tewarig Jan 28, 2025
e80de07
chore: update style type , added isDisbaled and fix double background…
tewarig Jan 28, 2025
e295ca4
fix: build error
tewarig Jan 28, 2025
92906d4
chore: update tests
tewarig Jan 28, 2025
71dda31
fix: update table virtualized
tewarig Jan 28, 2025
dce37d2
chore: update snap
tewarig Jan 28, 2025
371527b
chore: lint fix
tewarig Jan 28, 2025
f18cc5e
chore: update default page size
tewarig Jan 29, 2025
6cfa4fc
chore: rename VirtulizedWrapper to TableVirtualizedWrapper
tewarig Jan 29, 2025
81d70d0
fix: headerHeight , rowHeight by default
tewarig Jan 29, 2025
cd69623
chore: remove rowHeight
tewarig Jan 29, 2025
3292ef6
chore: able to pass height
tewarig Jan 29, 2025
7ffc421
chore: update ref
tewarig Jan 29, 2025
907f699
chore: exmaple updates
tewarig Jan 29, 2025
795a213
chore: update api docs
tewarig Jan 29, 2025
8335605
feat: move isVirtualized internally
tewarig Jan 29, 2025
5fbbcd5
fix: lint change
tewarig Jan 30, 2025
17e1e09
chore: update lint
tewarig Jan 30, 2025
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/quiet-students-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@razorpay/blade': minor
---

feat(blade): add support for table virtualization
65 changes: 65 additions & 0 deletions packages/blade/codemods/aicodemod/knowledge/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,3 +594,68 @@ const TableComponent: StoryFn<typeof Table> = ({ ...args }) => {
);
};
```

# Virtualized Table

Virtaulized table is a table component that renders only the visible rows and columns. This is useful when you have a large dataset and you want to render only the visible rows and columns to improve the performance of the table.

Out implementation of virtualized table is an wrapper on top of react-table-library 's implementation. It provides a simple API to create a virtualized table.

## Props

most of props are same as Table component. we have added following table component.

```ts

isVirtualized = false, // default value is false , it is used to enable virtualization in table

ref = React.Ref<HTMLDivElement> | undefined, // ref to the table container , since in virtualized table we need to calculate the height and width of the table to render only the visible rows and columns
```
but their is a change in children prop of Table component. In virtualized table we need to pass a component named TableVirtulized that takes TableHeader, TableBody components.
VirtualizedTable is a wrapper on top of react-table-library's [Virtualized](https://github.com/table-library/react-table-library/blob/master/src/virtualized/Virtualized.tsx) component. It provides a simple API to create a virtualized table.


```ts
type VirtualizedWrapperProps<Item> = {
/**
* * @example
* <TableVirtulized
* tableData={tableData}
* rowHeight={(item, index) => {
* return 57.5;
* }}
* >
*
tewarig marked this conversation as resolved.
Show resolved Hide resolved
* <Table tableData={tableData} >
* {(tableData) => (
* <TableVirtualizedContainer tableData={tableData}>
* <TableHeader>
* <TableHeaderRow>
* </TableHeaderRow>
* </TableHeader>
* <TableBody>
* {(tableItem, index) => <TableRow key={index} item={tableItem} />}
* </TableBody>
* </TableVirtualizedContainer>
* )}
* </Table>
* </TableVirtulized>
*
**/
/**
/**
*
*/
tableData: TableNode<Item>[];
/**
* should be a function that returns the height of the row
* index 0 is the header height
**/
rowHeight: RowHeight;
/**
* should contain the TableHeader and TableBody components
**/
children: React.ReactNode;
};

```
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//ignore file for ts check
import React from 'react';
import { BasicExample } from './examples';
import StoryPageWrapper from '~utils/storybook/StoryPageWrapper';
Expand Down
191 changes: 142 additions & 49 deletions packages/blade/src/components/Table/Table.web.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { forwardRef, useCallback, useEffect, useMemo } from 'react';
import { Table as ReactTable } from '@table-library/react-table-library/table';
import { useTheme as useTableTheme } from '@table-library/react-table-library/theme';
import type { MiddlewareFunction } from '@table-library/react-table-library/types/common';
Expand Down Expand Up @@ -28,6 +28,8 @@ import type {
TablePaginationType,
TableHeaderRowProps,
} from './types';
import { getTableBodyStyles } from './commonStyles';
import type { BladeElementRef } from '~utils/types';
import { makeBorderSize, makeMotionTime } from '~utils';
import { getComponentId, isValidAllowedChildren } from '~utils/isValidAllowedChildren';
import { throwBladeError } from '~utils/logger';
Expand Down Expand Up @@ -56,8 +58,29 @@ const rowSelectType: Record<
// Get the number of TableHeaderCell components.
// This is very complicated but the only way to iterate through the structure and get number of header cells.
// Assuming number of header cells is the same as number of columns
const getTableHeaderCellCount = (children: (data: []) => React.ReactElement): number => {
const getTableHeaderCellCount = (
children: (data: []) => React.ReactElement,
isVirtualized: boolean,
): number => {
const tableRootComponent = children([]);
if (isVirtualized) {
if (
React.isValidElement<{ header?: () => React.ReactElement }>(tableRootComponent) &&
tableRootComponent?.props.header
) {
if (React.isValidElement(tableRootComponent?.props.header())) {
const tableHeaderRow = tableRootComponent.props.header().props.children;
if (
React.isValidElement<{ children?: React.ReactNode }>(tableHeaderRow) &&
tableHeaderRow.props.children
) {
const tableHeaderCells = React.Children.toArray(tableHeaderRow.props.children);
return tableHeaderCells.length;
}
}
}
}

if (tableRootComponent && React.isValidElement(tableRootComponent)) {
const tableComponentArray = React.Children.toArray(tableRootComponent);
if (React.isValidElement(tableComponentArray[0])) {
Expand Down Expand Up @@ -87,21 +110,42 @@ const getTableHeaderCellCount = (children: (data: []) => React.ReactElement): nu
return 0;
};

const StyledReactTable = styled(ReactTable)<{ $styledProps?: { height?: BoxProps['height'] } }>(
({ $styledProps }) => {
const { theme } = useTheme();
const styledPropsCSSObject = getBaseBoxStyles({
theme,
height: $styledProps?.height,
});
const StyledReactTable = styled(ReactTable)<{
$styledProps?: {
height?: BoxProps['height'];
width?: BoxProps['width'];
isVirtualized?: boolean;
isSelectable?: boolean;
showStripedRows?: boolean;
};
}>(({ $styledProps }) => {
const { theme } = useTheme();
const styledPropsCSSObject = getBaseBoxStyles({
theme,
height: $styledProps?.height,
...($styledProps?.isVirtualized && {
width: $styledProps?.width,
}),
});
const $isSelectable = $styledProps?.isSelectable;
const $showStripedRows = $styledProps?.showStripedRows;

return {
'&&&': {
...styledPropsCSSObject,
},
};
},
);
return {
'&&&': {
...styledPropsCSSObject,
},
...($styledProps?.isVirtualized
? getTableBodyStyles({
isVirtualized: $styledProps?.isVirtualized,
theme,
height: $styledProps?.height,
width: $styledProps?.width,
isSelectable: $isSelectable,
showStripedRows: $showStripedRows,
})
: null),
};
});

const RefreshWrapper = styled(BaseBox)<{
isRefreshSpinnerVisible: boolean;
Expand All @@ -120,29 +164,33 @@ const RefreshWrapper = styled(BaseBox)<{
};
});

const _Table = <Item,>({
children,
data,
multiSelectTrigger = 'row',
selectionType = 'none',
onSelectionChange,
isHeaderSticky,
isFooterSticky,
isFirstColumnSticky,
rowDensity = 'normal',
onSortChange,
sortFunctions,
toolbar,
pagination,
height,
showStripedRows,
gridTemplateColumns,
isLoading = false,
isRefreshing = false,
showBorderedCells = false,
defaultSelectedIds = [],
...rest
}: TableProps<Item>): React.ReactElement => {
const _Table = <Item,>(
{
children,
data,
multiSelectTrigger = 'row',
selectionType = 'none',
onSelectionChange,
isHeaderSticky,
isFooterSticky,
isFirstColumnSticky,
rowDensity = 'normal',
onSortChange,
sortFunctions,
toolbar,
pagination,
height,
showStripedRows,
gridTemplateColumns,
isLoading = false,
isRefreshing = false,
showBorderedCells = false,
defaultSelectedIds = [],
isVirtualized = false,
...rest
}: TableProps<Item>,
ref: React.Ref<BladeElementRef> | undefined,
): React.ReactElement => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove ref and use height and calculate the width height of table node itself

<Box ref={parentRef}>
  <Table parentRef={parentRef} />
</Box>
<Table height="" />

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 👍🏻

const { theme } = useTheme();
const [selectedRows, setSelectedRows] = React.useState<TableNode<unknown>['id'][]>(
selectionType !== 'none' ? defaultSelectedIds : [],
Expand All @@ -156,8 +204,13 @@ const _Table = <Item,>({
undefined,
);
const [hasHoverActions, setHasHoverActions] = React.useState(false);
const [VirtualizedTableDimensions, setVirtualizedTableDimensions] = React.useState({
width: 0,
height: 0,
});

// Need to make header is sticky if first column is sticky otherwise the first header cell will not be sticky
const shouldHeaderBeSticky = isHeaderSticky ?? isFirstColumnSticky;
const shouldHeaderBeSticky = isVirtualized ?? isHeaderSticky ?? isFirstColumnSticky;
const backgroundColor = tableBackgroundColor;

const isMobile = useIsMobile();
Expand All @@ -173,7 +226,7 @@ const _Table = <Item,>({
});

// Table Theme
const columnCount = getTableHeaderCellCount(children);
const columnCount = getTableHeaderCellCount(children, isVirtualized);
const firstColumnStickyHeaderCellCSS = isFirstColumnSticky
? `
&:nth-of-type(1) {
Expand Down Expand Up @@ -259,13 +312,36 @@ const _Table = <Item,>({
`,
});

useEffect(() => {
if (ref && 'current' in ref && ref.current && !height) {
if (ref?.current) {
const { width, height } = (ref.current as HTMLElement).getBoundingClientRect();
setVirtualizedTableDimensions({ width, height });
}

const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width } = entry.contentRect;
setVirtualizedTableDimensions((prev) => ({ ...prev, width }));
}
});
if (ref && 'current' in ref && ref.current) {
resizeObserver.observe(ref.current as HTMLElement);
}
return () => {
resizeObserver.disconnect();
};
}
return undefined;
}, [height, ref]);

useEffect(() => {
// Get the total number of items
setTotalItems(data.nodes.length);
}, [data.nodes]);

// Selection Logic
const onSelectChange: MiddlewareFunction = (action, state): void => {
const onSelectChange: MiddlewareFunction = (_, state): void => {
const selectedIds: Identifier[] = state.id ? [state.id] : state.ids ?? [];
setSelectedRows(selectedIds);
onSelectionChange?.({
Expand Down Expand Up @@ -323,7 +399,7 @@ const _Table = <Item,>({
);

// Sort Logic
const handleSortChange: MiddlewareFunction = (action, state) => {
const handleSortChange: MiddlewareFunction = (_, state) => {
onSortChange?.({
sortKey: state.sortKey,
isSortReversed: state.reverse,
Expand Down Expand Up @@ -434,6 +510,9 @@ const _Table = <Item,>({
showBorderedCells,
hasHoverActions,
setHasHoverActions,
columnCount,
gridTemplateColumns,
isVirtualized,
}),
[
selectionType,
Expand All @@ -442,8 +521,10 @@ const _Table = <Item,>({
toggleRowSelectionById,
toggleAllRowsSelection,
deselectAllRows,
gridTemplateColumns,
rowDensity,
toggleSort,
columnCount,
currentSortedState,
setPaginationPage,
setPaginationRowSize,
Expand All @@ -459,6 +540,7 @@ const _Table = <Item,>({
showBorderedCells,
hasHoverActions,
setHasHoverActions,
isVirtualized,
],
);

Expand All @@ -484,6 +566,7 @@ const _Table = <Item,>({
position="relative"
{...getStyledProps(rest)}
{...metaAttribute({ name: MetaConstants.Table })}
width={isVirtualized ? `${VirtualizedTableDimensions.width}px` : undefined}
>
{isRefreshSpinnerMounted && (
<RefreshWrapper
Expand All @@ -504,15 +587,19 @@ const _Table = <Item,>({
)}
{toolbar}
<StyledReactTable
role="table"
role={isVirtualized ? 'grid' : 'table'}
layout={{ fixedHeader: shouldHeaderBeSticky, horizontalScroll: true }}
data={data}
// @ts-expect-error ignore this, theme clashes with styled-component's theme. We're using useTheme from blade to get actual theme
theme={tableTheme}
select={selectionType !== 'none' ? rowSelectConfig : null}
sort={sortFunctions ? sort : null}
$styledProps={{
height,
height: isVirtualized ? height || `${VirtualizedTableDimensions.height}px` : height,
width: isVirtualized ? `${VirtualizedTableDimensions.width}px` : undefined,
saurabhdaware marked this conversation as resolved.
Show resolved Hide resolved
isVirtualized,
isSelectable: selectionType !== 'none',
showStripedRows,
}}
pagination={hasPagination ? paginationConfig : null}
{...makeAccessible({ multiSelectable: selectionType === 'multiple' })}
Expand All @@ -527,9 +614,15 @@ const _Table = <Item,>({
</TableContext.Provider>
);
};

const Table = assignWithoutSideEffects(_Table, {
componentId: ComponentIds.Table,
});
const Table = assignWithoutSideEffects(
forwardRef(_Table) as <Item>(
// https://oida.dev/typescript-react-generic-forward-refs/
// https://stackoverflow.com/questions/58469229/react-with-typescript-generics-while-using-react-forwardref
props: TableProps<Item> & { ref?: React.ForwardedRef<BladeElementRef> },
) => React.ReactElement,
{
componentId: ComponentIds.Table,
},
);

export { Table };
2 changes: 1 addition & 1 deletion packages/blade/src/components/Table/TableBody.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import type { TableBodyProps, TableCellProps, TableRowProps } from './types';
import { Text } from '~components/Typography';

const TableBody = (props: TableBodyProps): React.ReactElement => {
const TableBody = <Item,>(props: TableBodyProps<Item>): React.ReactElement => {
return <Text>Table Component is not available for Native mobile apps.</Text>;
};

Expand Down
Loading
Loading