mirror of
https://github.com/fergalmoran/onearmy-community-platform.git
synced 2025-12-31 14:08:20 +00:00
153 lines
4.5 KiB
TypeScript
153 lines
4.5 KiB
TypeScript
import * as React from 'react'
|
|
import { Flex, Box } from 'theme-ui'
|
|
// TODO: Remove direct usage of Theme
|
|
import { preciousPlasticTheme } from 'oa-themes'
|
|
const themes = preciousPlasticTheme.styles
|
|
import type { ListRowProps } from 'react-virtualized'
|
|
import {
|
|
List,
|
|
WindowScroller,
|
|
CellMeasurerCache,
|
|
CellMeasurer,
|
|
} from 'react-virtualized'
|
|
import { emStringToPx } from 'src/utils/helpers'
|
|
|
|
interface IProps {
|
|
data: any[]
|
|
renderItem: (data: any) => JSX.Element
|
|
widthBreakpoints: number[]
|
|
}
|
|
interface IState {
|
|
totalColumns: number
|
|
data: any[]
|
|
dataRows: any[][]
|
|
}
|
|
|
|
// Use a measurement cache to dynamically calculate dynamic row heights based on content
|
|
const cache = new CellMeasurerCache({
|
|
fixedWidth: true,
|
|
})
|
|
|
|
/**
|
|
* Display a list of flex items as a virtualized list (only render what is shown),
|
|
* automatically calculating the number of rows to be displayed via breakpoints.
|
|
* Note, does not use react masonry/grid layouts to allow use with page scroller
|
|
*/
|
|
export class VirtualizedFlex extends React.Component<IProps, IState> {
|
|
static defaultProps: IProps = {
|
|
widthBreakpoints: themes.breakpoints.map(emStringToPx),
|
|
data: [],
|
|
renderItem: (data) => <div>RenderItem {data}</div>,
|
|
}
|
|
constructor(props: IProps) {
|
|
super(props)
|
|
this.state = { data: [], dataRows: [], totalColumns: -1 }
|
|
}
|
|
componentDidMount() {
|
|
this.generateRowData(this.props)
|
|
}
|
|
|
|
/* eslint-disable @typescript-eslint/naming-convention*/
|
|
UNSAFE_componentWillReceiveProps(props: IProps) {
|
|
this.generateRowData(props)
|
|
}
|
|
|
|
/**
|
|
* Split data into rows and columns depending on breakpoints
|
|
*/
|
|
generateRowData(props: IProps) {
|
|
const oldColumns = this.state.totalColumns
|
|
const oldData = this.state.data
|
|
const { widthBreakpoints, data } = props
|
|
const currentWidth = window.innerWidth
|
|
const totalColumns = this._calcTotalColumns(currentWidth, widthBreakpoints)
|
|
// only re-render when data or columns have changed
|
|
if (oldColumns !== totalColumns || oldData.length !== data.length) {
|
|
const dataRows = this._dataToRows(data, totalColumns)
|
|
this.setState({ totalColumns, dataRows })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When rendering a row call the measurer to check the rendered
|
|
* content and update the row height to ensure fit
|
|
*/
|
|
rowRenderer(rowProps: ListRowProps) {
|
|
const { index, key, style, parent, columnIndex } = rowProps
|
|
const { renderItem } = this.props
|
|
const row = this.state.dataRows![index]
|
|
return (
|
|
<CellMeasurer
|
|
cache={cache}
|
|
key={key}
|
|
parent={parent}
|
|
// simply measure first cell as all will be the same
|
|
columnIndex={columnIndex}
|
|
rowIndex={index}
|
|
>
|
|
<Flex key={key} style={style}>
|
|
{row.map((rowData: any, i: number) => (
|
|
<Box
|
|
py={4}
|
|
px={4}
|
|
key={key + i}
|
|
sx={{
|
|
width: ['100%', `${100 * 0.5}%`, `${100 / 3}%`],
|
|
height: '100%',
|
|
}}
|
|
>
|
|
{renderItem(rowData)}
|
|
</Box>
|
|
))}
|
|
</Flex>
|
|
</CellMeasurer>
|
|
)
|
|
}
|
|
render() {
|
|
const { dataRows } = this.state
|
|
const { data } = this.props
|
|
return dataRows.length > 0 && data.length > 0 ? (
|
|
<WindowScroller onResize={() => this.generateRowData(this.props)}>
|
|
{({ onChildScroll, isScrolling, height, width, scrollTop }) => (
|
|
<List
|
|
autoHeight
|
|
width={width}
|
|
height={height}
|
|
isScrolling={isScrolling}
|
|
onScroll={onChildScroll}
|
|
scrollTop={scrollTop}
|
|
rowCount={dataRows.length}
|
|
overscanRowCount={2}
|
|
rowRenderer={(rowProps) => this.rowRenderer(rowProps)}
|
|
deferredMeasurementCache={cache}
|
|
rowHeight={cache.rowHeight}
|
|
/>
|
|
)}
|
|
</WindowScroller>
|
|
) : null
|
|
}
|
|
/**
|
|
* Takes an array and subdivides into row/column array
|
|
* of arrays for a specified number of columns
|
|
*/
|
|
private _dataToRows(data: any[], columns: number) {
|
|
const totalRows = Math.ceil(data.length / columns)
|
|
const rows: typeof data[] = []
|
|
for (let i = 0; i < totalRows; i++) {
|
|
rows.push(data.slice(columns * i, columns * (i + 1)))
|
|
}
|
|
return rows
|
|
}
|
|
/**
|
|
* Use theme breakpoints to decide how many columns
|
|
*/
|
|
private _calcTotalColumns(containerWidth: number, breakpoints: number[]) {
|
|
return Math.min(
|
|
[0, ...breakpoints, Infinity].findIndex(
|
|
(width) => containerWidth < width,
|
|
),
|
|
breakpoints.length,
|
|
)
|
|
}
|
|
}
|