@spaced-out/ui-design-system
Version:
Sense UI components library
123 lines (109 loc) • 3.31 kB
Flow
// @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>
);
},
);