Skip to content

Commit

Permalink
feat(Table): add virtualization (#2489)
Browse files Browse the repository at this point in the history
* feat: virtulization on table poc

* chore: virtulized selectable demo

* chore: minor changes

* chore: update table styling and virtulization poc

* chore: add toolbar

* chore: styling fix

* fix: height of table

* chore: remove hasPadding from tableBody

* chore: scroll styling in case of not proper space is their , in virtual table div

* chore: added isVirtualized prop

* chore: added dynamic width and height

* chore: table hover actions fixes

* fix: row bottom not visible with rowHeight

* fix: distance between header height and table height

* fix: gap between table cells

* chore: added isSelectable todo

* chore: add  isVirtualized prop

* chore: remove unused classes

* chore: remove comments

* chore: code clean up

* fix: old table styling

* chore: table height fix

* fix: table bg on hover , (but hover actions are not working now ;_;)

* fix: table hover actions :tick

* fix: inconsistent size of checkbox header

* chore: added examples

* chore: update story

* chore: pagination check and prop fix

* chore: throw error

* chore: added error

* fix: make table response

* chore: update docs & components and types

* chore: fixed ts and lint errors in code

* fix: more ts error and ignore few files now

* chore: ignore table test

* chore: update test

* fix: ts error

* fix: more ts error

* chore: update virtuazlied table api

* chore: added table body styles

* refactor: styles for table and table body

* chore: code clean up

* chore: added test & updated ref type

* chore: added changeset

* fix: lint error

* chore: cleanup

* chore: update snap

* chore: removed ununsed code

* chore: unused code

* chore: move updates

* chore: update snap

* chore: update table

* chore: self review changes

* chore: removed getTableRowSelector

* chore: minior review changes

* chore: table api changes

* chore: make rowHeight internal

* chore: more review changes

* chore: update docs

* chore: added more stories

* chore: update style type , added isDisbaled and fix double background color in hover actions

* fix: build error

* chore: update tests

* fix: update table virtualized

* chore: update snap

* chore: lint fix

* chore: update default page size

* chore: rename VirtulizedWrapper  to TableVirtualizedWrapper

* fix: headerHeight , rowHeight by default

* chore: remove rowHeight

* chore: able to pass height

* chore: update ref

* chore: exmaple updates

* chore: update api docs

* feat: move isVirtualized internally

* fix: lint change

* chore: update lint

* chore: lint fix

* chore: remove boxref

* chore: update table.web.test

* chore: resolve generics

* chore: move tabledata to context

* chore: more ts changes

* chore: update body ts type

* chore: more changes

* chore: remove unsed file

* chore: change types

* chore: more values

* chore: review changes

* chore: remove basic table example

* chore: add component id

* chore: remove extra stories

* refactor: isVirtualized code

* chore: update snaps

* chore: change to local table node

* chore: update jsdocs

* chore: review changes

---------

Co-authored-by: saurabhdaware <[email protected]>
  • Loading branch information
tewarig and saurabhdaware authored Feb 24, 2025
1 parent a694ac7 commit bbf24c9
Show file tree
Hide file tree
Showing 18 changed files with 2,470 additions and 1,859 deletions.
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
126 changes: 126 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,129 @@ 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.
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
* <TableComponent
* data={data}
* isVirtualized
* rowDensity="compact"
* selectionType="multiple"
* height="700px"
* toolbar={
* <TableToolbar>
* <TableToolbarActions>
* <Button variant="secondary" marginRight="spacing.2">
* Export
* </Button>
* <Button>Payout</Button>
* </TableToolbarActions>
* </TableToolbar>
* }
* >
* {(tableData) => (
* <TableVirtualizedWrapper tableData={tableData}>
* <TableHeader>
* <TableHeaderRow>
* <TableHeaderCell>ID</TableHeaderCell>
* <TableHeaderCell>Amount</TableHeaderCell>
* <TableHeaderCell>Account</TableHeaderCell>
* <TableHeaderCell>Date</TableHeaderCell>
* <TableHeaderCell>Method</TableHeaderCell>
* <TableHeaderCell>Status</TableHeaderCell>
* </TableHeaderRow>
* </TableHeader>
* <TableBody<Item>>
* {(tableItem, index) => (
* <TableRow
* key={index}
* item={tableItem}
* hoverActions={
* <>
* <IconButton
* accessibilityLabel="Copy"
* isHighlighted
* icon={CopyIcon}
* onClick={() => console.log('copy', tableItem)}
* />
* <IconButton
* accessibilityLabel="Delete"
* isHighlighted
* icon={TrashIcon}
* onClick={() => console.log('delete', tableItem)}
* />
* </>
* }
* >
* <TableCell>
* <Code size="medium">{tableItem.paymentId}</Code>
* </TableCell>
* <TableCell>
* <Amount value={tableItem.amount} />
* </TableCell>
* <TableCell>{tableItem.account}</TableCell>
* <TableCell>
* {tableItem.date?.toLocaleDateString('en-IN', {
* year: 'numeric',
* month: '2-digit',
* day: '2-digit',
* })}
* </TableCell>
* <TableCell>{tableItem.method}</TableCell>
* <TableCell>
* <Badge
* size="medium"
* color={
* tableItem.status === 'Completed'
* ? 'positive'
* : tableItem.status === 'Pending'
* ? 'notice'
* : tableItem.status === 'Failed'
* ? 'negative'
* : 'default'
* }
* >
* {tableItem.status}
* </Badge>
* </TableCell>
* </TableRow>
* )}
* </TableBody>
* </TableVirtualizedWrapper>
* )}
* </TableComponent>
*
**/
/**
/**
* The tableData prop is an array of objects.
*/
tableData: TableNode<Item>[];
/**
* headerHeight is the height of the header
**/
headerHeight?: number;
/**
* rowHeight is the height of each row, it can be a fixed number or a function that returns a number
**/
rowHeight?: (item: TableLibraryTableNode, index: number) => number;
children: React.ReactNode;
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
78 changes: 57 additions & 21 deletions packages/blade/src/components/Table/Table.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
TablePaginationType,
TableHeaderRowProps,
} from './types';
import { getTableBodyStyles } from './commonStyles';
import { makeBorderSize, makeMotionTime } from '~utils';
import { getComponentId, isValidAllowedChildren } from '~utils/isValidAllowedChildren';
import { throwBladeError } from '~utils/logger';
Expand Down Expand Up @@ -87,21 +88,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,
});

return {
'&&&': {
...styledPropsCSSObject,
},
};
},
);
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: '100%',
}),
});
const $isSelectable = $styledProps?.isSelectable;
const $showStripedRows = $styledProps?.showStripedRows;
return {
'&&&': {
...styledPropsCSSObject,
overflow: `${$styledProps?.isVirtualized ? 'unset' : 'auto'} !important`,
},
...($styledProps?.isVirtualized
? getTableBodyStyles({
isVirtualized: $styledProps?.isVirtualized,
theme,
height: $styledProps?.height,
width: '100%',
isSelectable: $isSelectable,
showStripedRows: $showStripedRows,
})
: null),
};
});

const RefreshWrapper = styled(BaseBox)<{
isRefreshSpinnerVisible: boolean;
Expand Down Expand Up @@ -156,8 +178,10 @@ const _Table = <Item,>({
undefined,
);
const [hasHoverActions, setHasHoverActions] = React.useState(false);
const tableRootComponent = children([]);
const isVirtualized = getComponentId(tableRootComponent) === ComponentIds.VirtualizedTable;
// 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 Down Expand Up @@ -265,7 +289,7 @@ const _Table = <Item,>({
}, [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 +347,7 @@ const _Table = <Item,>({
);

// Sort Logic
const handleSortChange: MiddlewareFunction = (action, state) => {
const handleSortChange: MiddlewareFunction = (_, state) => {
onSortChange?.({
sortKey: state.sortKey,
isSortReversed: state.reverse,
Expand All @@ -341,7 +365,7 @@ const _Table = <Item,>({
},
);

const currentSortedState: TableContextType['currentSortedState'] = useMemo(() => {
const currentSortedState: TableContextType<Item>['currentSortedState'] = useMemo(() => {
return {
sortKey: sort.state.sortKey,
isSortReversed: sort.state.reverse,
Expand Down Expand Up @@ -409,7 +433,7 @@ const _Table = <Item,>({
}

// Table Context
const tableContext: TableContextType = useMemo(
const tableContext: TableContextType<Item> = useMemo(
() => ({
selectionType,
selectedRows,
Expand All @@ -434,6 +458,10 @@ const _Table = <Item,>({
showBorderedCells,
hasHoverActions,
setHasHoverActions,
columnCount,
gridTemplateColumns,
isVirtualized,
tableData: data.nodes,
}),
[
selectionType,
Expand All @@ -442,8 +470,10 @@ const _Table = <Item,>({
toggleRowSelectionById,
toggleAllRowsSelection,
deselectAllRows,
gridTemplateColumns,
rowDensity,
toggleSort,
columnCount,
currentSortedState,
setPaginationPage,
setPaginationRowSize,
Expand All @@ -459,6 +489,8 @@ const _Table = <Item,>({
showBorderedCells,
hasHoverActions,
setHasHoverActions,
isVirtualized,
data,
],
);

Expand All @@ -484,6 +516,7 @@ const _Table = <Item,>({
position="relative"
{...getStyledProps(rest)}
{...metaAttribute({ name: MetaConstants.Table })}
width={isVirtualized ? `100%` : undefined}
>
{isRefreshSpinnerMounted && (
<RefreshWrapper
Expand Down Expand Up @@ -513,6 +546,10 @@ const _Table = <Item,>({
sort={sortFunctions ? sort : null}
$styledProps={{
height,
width: isVirtualized ? `100%` : undefined,
isVirtualized,
isSelectable: selectionType !== 'none',
showStripedRows,
}}
pagination={hasPagination ? paginationConfig : null}
{...makeAccessible({ multiSelectable: selectionType === 'multiple' })}
Expand All @@ -527,7 +564,6 @@ const _Table = <Item,>({
</TableContext.Provider>
);
};

const Table = assignWithoutSideEffects(_Table, {
componentId: ComponentIds.Table,
});
Expand Down
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

0 comments on commit bbf24c9

Please sign in to comment.