Skip to content

Commit 76b9488

Browse files
authored
refactor(grid-view): migrate GridView from Flow to TypeScript (#4734)
1 parent 5aef4b0 commit 76b9488

7 files changed

Lines changed: 155 additions & 4 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import * as React from 'react';
2+
import { CellMeasurer, CellMeasurerCache } from '@box/react-virtualized/dist/es/CellMeasurer';
3+
import Table, { Column } from '@box/react-virtualized/dist/es/Table';
4+
import getProp from 'lodash/get';
5+
import GridViewSlot from './GridViewSlot';
6+
import type { Collection } from '../../common/types/core';
7+
8+
import '@box/react-virtualized/styles.css';
9+
import './GridView.scss';
10+
11+
interface TableCellRendererParams {
12+
/** Data key associated with the rendered table column */
13+
dataKey: string;
14+
/** Parent virtualized grid passed to CellMeasurer */
15+
parent: unknown;
16+
/** Index of the row being rendered */
17+
rowIndex: number;
18+
}
19+
20+
export interface GridViewProps {
21+
/** Number of item columns displayed in each row */
22+
columnCount: number;
23+
/** Collection containing the items rendered in the grid */
24+
currentCollection: Collection;
25+
/** Height of the virtualized grid */
26+
height: number;
27+
/** Row to scroll into view */
28+
scrollToRow?: number;
29+
/** Renders the contents of a grid slot */
30+
slotRenderer: (slotIndex: number) => React.ReactElement | null | undefined;
31+
/** Width of the virtualized grid */
32+
width: number;
33+
}
34+
35+
interface RowGetterParams {
36+
index: number;
37+
}
38+
39+
class GridView extends React.Component<GridViewProps> {
40+
cache = new CellMeasurerCache({
41+
defaultHeight: 300,
42+
defaultWidth: 400,
43+
fixedWidth: true,
44+
});
45+
46+
componentDidUpdate({ columnCount: prevColumnCount, width: prevWidth }: GridViewProps) {
47+
const { columnCount, width } = this.props;
48+
49+
// The React Virtualized Table must be notified whenever the heights of rows
50+
// could potentially change. If omitted, rows are sized
51+
// incorrectly resulting in gaps or content overlap.
52+
if (columnCount !== prevColumnCount || width !== prevWidth) {
53+
this.cache.clearAll();
54+
this.forceUpdate();
55+
}
56+
}
57+
58+
cellRenderer = ({ dataKey, parent, rowIndex }: TableCellRendererParams) => {
59+
const { columnCount, currentCollection, slotRenderer } = this.props;
60+
const count = getProp(currentCollection, 'items.length', 0);
61+
const contents = [];
62+
63+
const startingIndex = rowIndex * columnCount;
64+
const maxSlotIndex = Math.min(startingIndex + columnCount, count);
65+
66+
for (let slotIndex = startingIndex; slotIndex < maxSlotIndex; slotIndex += 1) {
67+
const { id, selected } = getProp(currentCollection, `items[${slotIndex}]`);
68+
69+
// using item's id as key is important for renrendering. React Virtualized Table rerenders
70+
// on every 1px scroll, so using improper key would lead to image flickering in each
71+
// card of the grid view when scrolling.
72+
contents.push(
73+
<GridViewSlot
74+
key={id}
75+
selected={selected}
76+
slotIndex={slotIndex}
77+
slotRenderer={slotRenderer}
78+
slotWidth={`${(100 / columnCount).toFixed(4)}%`}
79+
/>,
80+
);
81+
}
82+
83+
return (
84+
<CellMeasurer key={dataKey} cache={this.cache} columnIndex={0} parent={parent} rowIndex={rowIndex}>
85+
<div className="bdl-GridView-row">{contents}</div>
86+
</CellMeasurer>
87+
);
88+
};
89+
90+
rowGetter = ({ index }: RowGetterParams) => {
91+
return index;
92+
};
93+
94+
render() {
95+
const { columnCount, currentCollection, height, scrollToRow = 0, width } = this.props;
96+
const count = getProp(currentCollection, 'items.length', 0);
97+
const rowCount = Math.ceil(count / columnCount);
98+
99+
return (
100+
<Table
101+
className="bdl-GridView"
102+
disableHeader
103+
height={height}
104+
rowCount={rowCount}
105+
rowGetter={this.rowGetter}
106+
rowHeight={this.cache.rowHeight}
107+
width={width}
108+
gridClassName="bdl-GridView-body"
109+
rowClassName="bdl-GridView-tableRow"
110+
scrollToIndex={scrollToRow}
111+
sortDirection="ASC"
112+
>
113+
<Column cellRenderer={this.cellRenderer} dataKey="" flexGrow={1} width={400} />
114+
</Table>
115+
);
116+
}
117+
}
118+
119+
export default GridView;

src/components/grid-view/GridViewSlider.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ import './GridViewSlider.scss';
77
import messages from '../../elements/common/messages';
88

99
export interface GridViewSliderProps {
10+
/** Current number of columns displayed in the grid */
1011
columnCount: number;
12+
/** Maximum number of columns supported by the grid */
1113
gridMaxColumns: number;
14+
/** Minimum number of columns supported by the grid */
1215
gridMinColumns: number;
16+
/** Maximum number of columns available at the current viewport size */
1317
maxColumnCount: number;
18+
/** Called when the selected column count changes */
1419
onChange: (newSliderValue: number) => void;
1520
}
1621

File renamed without changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react';
2+
import classNames from 'classnames';
3+
import './GridViewSlot.scss';
4+
5+
export interface GridViewSlotProps {
6+
/** Whether the item rendered in this slot is selected */
7+
selected: boolean;
8+
/** Index of the item rendered in this slot */
9+
slotIndex: number;
10+
/** Renders the contents of the slot */
11+
slotRenderer: (slotIndex: number) => React.ReactElement | null | undefined;
12+
/** Width assigned to the slot */
13+
slotWidth: string;
14+
}
15+
16+
const GridViewSlot = ({ selected, slotIndex, slotRenderer, slotWidth }: GridViewSlotProps) => (
17+
<div className="bdl-GridViewSlot" style={{ maxWidth: slotWidth, flexBasis: slotWidth }}>
18+
<div
19+
className={classNames('bdl-GridViewSlot-content', {
20+
'bdl-GridViewSlot-content--selected': selected,
21+
})}
22+
>
23+
{slotRenderer(slotIndex)}
24+
</div>
25+
</div>
26+
);
27+
28+
export default GridViewSlot;

src/components/grid-view/__tests__/GridView.test.js renamed to src/components/grid-view/__tests__/GridView.test.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import * as React from 'react';
2+
import { shallow } from 'enzyme';
23

34
import GridView from '../GridView';
45

56
describe('components/grid-view/GridView', () => {
67
test('should render()', () => {
7-
const collection = { items: [{ type: 'folder', id: '001', name: 'Example Folder' }] };
8+
const collection = { items: [{ type: 'folder', id: '001', name: 'Example Folder' }] } as const;
89
const wrapper = shallow(
910
<GridView
1011
columnCount={5}
1112
currentCollection={collection}
1213
height={600}
13-
onItemClick={() => {}}
14-
onItemSelect={() => {}}
15-
slotRenderer={index => <div> {index} </div>}
14+
slotRenderer={(index: number) => <div> {index} </div>}
1615
width={400}
1716
/>,
1817
);

src/components/grid-view/__tests__/__snapshots__/GridView.test.js.snap renamed to src/components/grid-view/__tests__/__snapshots__/GridView.test.tsx.snap

File renamed without changes.

0 commit comments

Comments
 (0)