Skip to content

Commit 05b1e6e

Browse files
authored
feat(AnalyticalTable): horizontal virtualization of table cells (#640)
1 parent 07c5b6a commit 05b1e6e

File tree

15 files changed

+1936
-1493
lines changed

15 files changed

+1936
-1493
lines changed

packages/main/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"react-content-loader": "5.1.0",
3838
"react-jss": "10.1.1",
3939
"react-table": "7.5.0",
40-
"react-virtual": "2.2.1"
40+
"react-virtual": "2.2.4"
4141
},
4242
"peerDependencies": {
4343
"@ui5/webcomponents": "1.0.0-rc.8",

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ import { DefaultNoDataComponent } from './defaults/NoDataComponent';
101101
LoadingComponent: DefaultLoadingComponent,
102102
scaleWidthMode: TableScaleWidthMode.Default,
103103
selectionMode: TableSelectionMode.SINGLE_SELECT,
104-
selectionBehavior: TableSelectionBehavior.ROW
104+
selectionBehavior: TableSelectionBehavior.ROW,
105+
overscanCountHorizontal: 5
105106
}}
106107
argTypes={{
107108
ref: { type: null },
@@ -153,6 +154,7 @@ import { DefaultNoDataComponent } from './defaults/NoDataComponent';
153154
infiniteScrollThreshold={args.infiniteScrollThreshold}
154155
onLoadMore={args.onLoadMore}
155156
selectionBehavior={args.selectionBehavior}
157+
overscanCountHorizontal={args.overscanCountHorizontal}
156158
/>
157159
</div>
158160
)}

packages/main/src/components/AnalyticalTable/AnalyticalTable.test.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,29 +289,54 @@ describe('AnalyticalTable', () => {
289289
const UsingTable = (props) => {
290290
tableRef = useRef(null);
291291
return (
292-
<AnalyticalTable ref={tableRef} title="Table Title" data={data} columns={columns} visibleRows={1} minRows={1} />
292+
<AnalyticalTable
293+
style={{ width: '170px' }}
294+
ref={tableRef}
295+
title="Table Title"
296+
data={data}
297+
columns={columns}
298+
visibleRows={1}
299+
minRows={1}
300+
/>
293301
);
294302
};
295303

296-
render(<UsingTable />);
304+
const { getByRole } = render(<UsingTable />);
297305

298306
// Check existence + type
299307
expect(typeof tableRef.current.scrollTo).toBe('function');
300308
expect(typeof tableRef.current.scrollToItem).toBe('function');
309+
expect(typeof tableRef.current.horizontalScrollTo).toBe('function');
310+
expect(typeof tableRef.current.horizontalScrollToItem).toBe('function');
301311

302312
// call functions
303-
const tableInnerRef = tableRef.current.querySelector("div[class^='AnalyticalTable-table'] > div");
313+
const tableBodyRef = tableRef.current.querySelector("div[class^='AnalyticalTable-tbody']");
314+
const tableContainerRef = getByRole('grid', { hidden: true });
315+
304316
act(() => {
305317
tableRef.current.scrollToItem(1, 'start');
306318
});
307319

308-
expect(tableInnerRef.scrollTop).toBe(44);
320+
expect(tableBodyRef.scrollTop).toBe(44);
309321

310322
act(() => {
311323
tableRef.current.scrollTo(2);
312324
});
313325

314-
expect(tableInnerRef.scrollTop).toBe(2);
326+
expect(tableBodyRef.scrollTop).toBe(2);
327+
328+
screen.debug(tableContainerRef, 9999999);
329+
act(() => {
330+
tableRef.current.horizontalScrollToItem(1, 'start');
331+
});
332+
333+
expect(tableContainerRef.scrollLeft).toBe(150);
334+
335+
act(() => {
336+
tableRef.current.horizontalScrollTo(2);
337+
});
338+
339+
expect(tableContainerRef.scrollLeft).toBe(2);
315340
});
316341

317342
test('with highlight row', () => {

packages/main/src/components/AnalyticalTable/AnayticalTable.jss.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ const styles = {
5656
boxSizing: 'border-box',
5757
display: 'flex',
5858
willChange: 'transform',
59-
height: CssSizeVariables.sapWcrAnalyticalTableRowHeight,
6059
'&:hover': {
6160
backgroundColor: ThemingParameters.sapList_Hover_Background
6261
},
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters';
2+
import React, { useCallback } from 'react';
3+
import { createUseStyles } from 'react-jss';
4+
import { useVirtual, VirtualItem } from 'react-virtual';
5+
import { ColumnHeader } from './index';
6+
7+
const styles = {
8+
resizer: {
9+
display: 'inline-block',
10+
width: '3px',
11+
height: '100%',
12+
position: 'absolute',
13+
bottom: 0,
14+
top: 0,
15+
transform: 'translateX(-50%)',
16+
zIndex: 1,
17+
cursor: 'col-resize',
18+
willChange: 'transform',
19+
'&:hover, &:active': {
20+
backgroundColor: ThemingParameters.sapContent_DragAndDropActiveColor
21+
}
22+
}
23+
};
24+
25+
const useStyles = createUseStyles(styles);
26+
27+
export const ColumnHeaderContainer = (props) => {
28+
const {
29+
headerProps,
30+
headerGroup,
31+
onSort,
32+
onGroupByChanged,
33+
onDragStart,
34+
onDragOver,
35+
onDrop,
36+
onDragEnter,
37+
onDragEnd,
38+
dragOver,
39+
tableRef,
40+
visibleColumnsWidth,
41+
overscanCountHorizontal,
42+
resizeInfo,
43+
reactWindowRef
44+
} = props;
45+
const columnVirtualizer = useVirtual({
46+
size: visibleColumnsWidth.length,
47+
parentRef: tableRef,
48+
estimateSize: useCallback(
49+
(index) => {
50+
return visibleColumnsWidth[index];
51+
},
52+
[visibleColumnsWidth]
53+
),
54+
horizontal: true,
55+
overscan: overscanCountHorizontal
56+
});
57+
58+
reactWindowRef.current = {
59+
...reactWindowRef.current,
60+
horizontalScrollToOffset: columnVirtualizer.scrollToOffset,
61+
horizontalScrollToIndex: columnVirtualizer.scrollToIndex
62+
};
63+
64+
const classes = useStyles();
65+
66+
return (
67+
<div {...headerProps} role="rowgroup" style={{ width: `${columnVirtualizer.totalSize}px` }}>
68+
{columnVirtualizer.virtualItems.map((virtualColumn: VirtualItem) => {
69+
const column = headerGroup.headers[virtualColumn.index];
70+
const isLastColumn = !column.disableResizing && virtualColumn.index + 1 === headerGroup.headers.length;
71+
return (
72+
<>
73+
{column.canResize && column.getResizerProps && (
74+
<div
75+
{...column.getResizerProps()}
76+
data-resizer
77+
className={classes.resizer}
78+
style={{ left: `${column.totalFlexWidth + column.totalLeft - (isLastColumn ? 3 : 0)}px` }}
79+
/>
80+
)}
81+
<ColumnHeader
82+
{...column.getHeaderProps()}
83+
onSort={onSort}
84+
onGroupBy={onGroupByChanged}
85+
onDragStart={onDragStart}
86+
onDragOver={onDragOver}
87+
onDrop={onDrop}
88+
onDragEnter={onDragEnter}
89+
onDragEnd={onDragEnd}
90+
dragOver={column.id === dragOver}
91+
isDraggable={column.canReorder && !resizeInfo.isResizingColumn}
92+
virtualColumn={virtualColumn}
93+
>
94+
{column.render('Header')}
95+
</ColumnHeader>
96+
</>
97+
);
98+
})}
99+
</div>
100+
);
101+
};

packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import React, {
1717
useMemo,
1818
useRef
1919
} from 'react';
20+
import { VirtualItem } from 'react-virtual';
2021
import { Ui5PopoverDomRef } from '../../../interfaces/Ui5PopoverDomRef';
2122
import { ColumnType } from '../types/ColumnType';
2223
import { ColumnHeaderModal } from './ColumnHeaderModal';
@@ -41,6 +42,7 @@ export interface ColumnHeaderProps {
4142
isDraggable: boolean;
4243
role: string;
4344
isLastColumn: boolean;
45+
virtualColumn: VirtualItem;
4446
}
4547

4648
const styles = {
@@ -79,20 +81,6 @@ const styles = {
7981
color: ThemingParameters.sapContent_IconColor,
8082
right: getRTL() === false ? '0.5rem' : undefined,
8183
left: getRTL() === true ? '0.5rem' : undefined
82-
},
83-
resizer: {
84-
display: 'inline-block',
85-
width: '3px',
86-
height: '100%',
87-
position: 'absolute',
88-
bottom: 0,
89-
top: 0,
90-
transform: 'translateX(-50%)',
91-
zIndex: 1,
92-
cursor: 'col-resize',
93-
'&:hover, &:active': {
94-
backgroundColor: ThemingParameters.sapContent_DragAndDropActiveColor
95-
}
9684
}
9785
};
9886

@@ -117,7 +105,7 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props: ColumnHeaderProps) =>
117105
isDraggable,
118106
dragOver,
119107
role,
120-
isLastColumn
108+
virtualColumn
121109
} = props;
122110

123111
const isFiltered = column.filterValue && column.filterValue.length > 0;
@@ -159,9 +147,16 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props: ColumnHeaderProps) =>
159147
);
160148

161149
if (!column) return null;
162-
163150
return (
164-
<>
151+
<div
152+
style={{
153+
position: 'absolute',
154+
top: 0,
155+
left: 0,
156+
width: `${virtualColumn.size}px`,
157+
transform: `translateX(${virtualColumn.start}px)`
158+
}}
159+
>
165160
<div
166161
id={id}
167162
className={className}
@@ -197,14 +192,6 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props: ColumnHeaderProps) =>
197192
</div>
198193
{hasPopover && <ColumnHeaderModal column={column} onSort={onSort} onGroupBy={onGroupBy} ref={popoverRef} />}
199194
</div>
200-
{column.canResize && column.getResizerProps && (
201-
<div
202-
{...column.getResizerProps()}
203-
data-resizer
204-
className={classes.resizer}
205-
style={{ left: `${column.totalLeft + column.totalFlexWidth - (isLastColumn ? 3 : 0)}px` }}
206-
/>
207-
)}
208-
</>
195+
</div>
209196
);
210197
};

0 commit comments

Comments
 (0)