import React, { ComponentType, forwardRef, useCallback, useEffect, useMemo } from "react"; import { VibeComponentProps } from "../../../types"; import TableBody from "../TableBody/TableBody"; import styles from "./TableVirtualizedBody.module.scss"; import { FixedSizeList as List, ListChildComponentProps, ScrollDirection } from "react-window"; import { useTable } from "../context/TableContext/TableContext"; import cx from "classnames"; import { getTestId } from "../../../tests/test-ids-utils"; import { ComponentDefaultTestId } from "../../../tests/constants"; import { RowHeights } from "../Table/TableConsts"; import AutoSizer, { Size as AutoSizerSize } from "react-virtualized-auto-sizer"; import { useTableRowMenu } from "../context/TableRowMenuContext/TableRowMenuContext"; import { ITableColumn } from "../Table/Table"; export type TableVirtualizedRow = Record & { id: string }; export interface ITableVirtualizedBodyProps extends VibeComponentProps { items: T[]; rowRenderer: (item: T) => JSX.Element; onScroll?: (horizontalScrollDirection: ScrollDirection, scrollTop: number, scrollUpdateWasRequested: boolean) => void; columns?: ITableColumn[]; headerRenderer?: (columns: ITableColumn[]) => JSX.Element; } const TableVirtualizedBody = forwardRef( ( { items, rowRenderer, onScroll, columns, headerRenderer, id, className, "data-testid": dataTestId }: ITableVirtualizedBodyProps, ref: React.ForwardedRef ) => { const { size, virtualizedListRef, onVirtualizedListScroll, markTableAsVirtualized } = useTable(); const { resetHoveredRow } = useTableRowMenu(); const virtualizedWithHeader = !!columns && !!headerRenderer; const handleOuterScroll = useCallback( (e: Event) => { const target = e.target as HTMLDivElement; resetHoveredRow(); onVirtualizedListScroll({ target, currentTarget: target } as unknown as React.UIEvent); }, [resetHoveredRow, onVirtualizedListScroll] ); useEffect(() => { const scrollElement = virtualizedListRef.current; if (!scrollElement) return; scrollElement.addEventListener("scroll", handleOuterScroll); return () => { scrollElement.removeEventListener("scroll", handleOuterScroll); }; }, [handleOuterScroll, virtualizedListRef]); const handleVirtualizedVerticalScroll = useCallback( ({ scrollDirection, scrollOffset, scrollUpdateWasRequested }: { scrollDirection: ScrollDirection; scrollOffset: number; scrollUpdateWasRequested: boolean; }) => { if (virtualizedWithHeader) return; onScroll?.(scrollDirection, scrollOffset, scrollUpdateWasRequested); }, [onScroll, virtualizedWithHeader] ); const itemRenderer = useCallback>>( ({ index, style: { width: _width, ...style } }) => { if (virtualizedWithHeader && index === 0) { return null; //placeholder for virtualized with header } const currentIndex = virtualizedWithHeader ? index - 1 : index; const currentItem = items[currentIndex]; const element = rowRenderer(currentItem); return React.cloneElement(element, { style: { ...style, ...element.props?.style }, key: index }); }, [items, rowRenderer, virtualizedWithHeader] ); useEffect(() => { if (!virtualizedWithHeader) markTableAsVirtualized(); }, [markTableAsVirtualized, virtualizedWithHeader]); const memoizedInnerElementType = useMemo( () => virtualizedWithHeader ? forwardRef(({ children, ...rest }: any, ref: React.Ref) => (
{headerRenderer(columns)} {children}
)) : undefined, [virtualizedWithHeader, headerRenderer, columns] ); return ( {items?.length && ( {({ height, width }: AutoSizerSize) => ( { virtualizedListRef.current = element; }} innerElementType={memoizedInnerElementType} > {itemRenderer} )} )} ); } ); export default TableVirtualizedBody;