UNPKG

@spaced-out/ui-design-system

Version:
123 lines (109 loc) 3.31 kB
// @flow strict import * as React from 'react'; // $FlowFixMe[nonstrict-import] import {FixedSizeList, VariableSizeList} from 'react-window'; import {useInfiniteScroll, useResizeObserver} from '../../hooks'; import {spaceFluid} from '../../styles/variables/_space'; import classify from '../../utils/classify'; import {CircularLoader} from '../CircularLoader'; import type {GenericObject} from '../Table'; import css from './InfinitePagination.module.css'; type ClassNames = $ReadOnly<{wrapper?: string}>; const DEFAULT_THRESHOLD = 200; export type InfinitePaginationProps = { items: Array<GenericObject>, width: string, threshold?: number, classNames?: ClassNames, renderItem: (item: GenericObject, index: number) => React.Node, getItemKey?: (item: GenericObject, index: number) => string | number, hasNextPage: boolean, loadMoreItems: () => Promise<void>, // to make Flow happy with the disjoint union isVariableSizeList is a mandatory prop ... | { itemSize: (index: number) => number, isVariableSizeList: true, } | { itemSize: number, isVariableSizeList?: false, }, }; export const InfinitePagination: React$AbstractComponent< InfinitePaginationProps, HTMLDivElement, > = React.forwardRef<InfinitePaginationProps, HTMLDivElement>( (props: InfinitePaginationProps, ref) => { const { items, width = spaceFluid, itemSize, renderItem, threshold = DEFAULT_THRESHOLD, getItemKey, classNames, hasNextPage, loadMoreItems, isVariableSizeList, } = props; const listRef = React.useRef(null); const itemsLength = items.length; const totalItemsCount = itemsLength + (hasNextPage ? 1 : 0); const [containerRef, containerHeight] = useResizeObserver(); React.useImperativeHandle(ref, () => listRef.current); React.useEffect(() => { if (listRef.current) { listRef.current.scrollToItem(0); } }, [itemsLength === 0]); const handleScroll = useInfiniteScroll({ itemSize, threshold, itemsLength, hasNextPage, loadMoreItems, containerHeight, isVariableSizeList, }); const Row = ({index, style}) => index === itemsLength ? ( <div style={style} className={css.loader}> <CircularLoader size="large" /> </div> ) : ( <div style={style} className={css.listRow}> {renderItem(items[index], index)} </div> ); const itemKey = React.useCallback( (index) => index === itemsLength ? 'loading-indicator' : getItemKey ? getItemKey(items[index], index) : index, [items, getItemKey], ); const ListComponent = isVariableSizeList ? VariableSizeList : FixedSizeList; return ( <div ref={containerRef} data-testid="InfinitePagination" className={classify(css.wrapper, classNames?.wrapper)} > <ListComponent ref={listRef} width={width} height={containerHeight} itemKey={itemKey} itemSize={itemSize} onScroll={handleScroll} itemCount={totalItemsCount} > {Row} </ListComponent> </div> ); }, );