list component optimizations

This commit is contained in:
jeffvli
2025-10-22 17:35:48 -07:00
parent fe0813502d
commit 17e4c5cbb3
2 changed files with 530 additions and 346 deletions
@@ -4,7 +4,7 @@ import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { AnimatePresence } from 'motion/react'; import { AnimatePresence } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react'; import { useOverlayScrollbars } from 'overlayscrollbars-react';
import { import React, {
CSSProperties, CSSProperties,
Ref, Ref,
UIEvent, UIEvent,
@@ -31,6 +31,143 @@ import {
import { ItemListHandle } from '/@/renderer/components/item-list/types'; import { ItemListHandle } from '/@/renderer/components/item-list/types';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
interface VirtualizedGridListProps {
data: unknown[];
enableExpansion: boolean;
enableSelection: boolean;
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
internalState: ItemListStateActions;
itemGridRef: React.RefObject<any>;
itemType: LibraryItem;
onRowsRendered: (visibleRows: { startIndex: number; stopIndex: number }) => void;
onScroll: (e: UIEvent<HTMLDivElement>) => void;
tableMeta: null | {
columnCount: number;
itemHeight: number;
rowCount: number;
};
}
const VirtualizedGridList = React.memo(
({
data,
enableExpansion,
enableSelection,
gap,
internalState,
itemGridRef,
itemType,
onRowsRendered,
onScroll,
tableMeta,
}: VirtualizedGridListProps) => {
const elements = useMemo(() => {
if (!tableMeta) {
return [];
}
return data
.map((d, i) => {
return {
data: d,
index: i,
};
})
.reduce(
(acc, d) => {
if (d.index % (tableMeta?.columnCount || 0) === 0) {
acc.push([]);
}
const prev = acc[acc.length - 1];
prev.push(d);
return acc;
},
[] as { data: any; index: number }[][],
);
}, [tableMeta, data]);
const itemProps: GridItemProps = {
columns: tableMeta?.columnCount || 0,
data: elements,
enableExpansion,
enableSelection,
gap,
internalState,
itemType,
};
return (
<List
listRef={itemGridRef}
onRowsRendered={onRowsRendered}
onScroll={onScroll}
rowComponent={ListComponent}
rowCount={tableMeta?.rowCount || 0}
rowHeight={tableMeta?.itemHeight || 0}
rowProps={itemProps}
/>
);
},
);
VirtualizedGridList.displayName = 'VirtualizedGridList';
// Throttled function moved outside component for better performance
const createThrottledSetTableMeta = (itemsPerRow?: number) => {
return throttle(
(
width: number,
dataLength: number,
type: LibraryItem,
setTableMeta: (meta: any) => void,
) => {
const isSm = width >= 600;
const isMd = width >= 768;
const isLg = width >= 960;
const isXl = width >= 1200;
const is2xl = width >= 1440;
const is3xl = width >= 1920;
const is4xl = width >= 2560;
let dynamicItemsPerRow = 2;
if (is4xl) {
dynamicItemsPerRow = 12;
} else if (is3xl) {
dynamicItemsPerRow = 10;
} else if (is2xl) {
dynamicItemsPerRow = 8;
} else if (isXl) {
dynamicItemsPerRow = 6;
} else if (isLg) {
dynamicItemsPerRow = 5;
} else if (isMd) {
dynamicItemsPerRow = 4;
} else if (isSm) {
dynamicItemsPerRow = 3;
} else {
dynamicItemsPerRow = 2;
}
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
const widthPerItem = Number(width) / setItemsPerRow;
const itemHeight = widthPerItem + getDataRowsCount(type) * 26;
if (widthPerItem === 0) {
return;
}
setTableMeta({
columnCount: setItemsPerRow,
itemHeight,
rowCount: Math.ceil(dataLength / setItemsPerRow),
});
},
200,
);
};
export interface GridItemProps { export interface GridItemProps {
columns: number; columns: number;
data: any[]; data: any[];
@@ -155,56 +292,13 @@ export const ItemGridList = ({
rowCount: number; rowCount: number;
}>(null); }>(null);
// Throttled function to update table meta // Use throttled function created outside component for better performance
const throttledSetTableMeta = useMemo(() => { const throttledSetTableMeta = useMemo(() => {
return throttle((width: number, dataLength: number, type: LibraryItem) => { return createThrottledSetTableMeta(itemsPerRow);
const isSm = width >= 600;
const isMd = width >= 768;
const isLg = width >= 960;
const isXl = width >= 1200;
const is2xl = width >= 1440;
const is3xl = width >= 1920;
const is4xl = width >= 2560;
let dynamicItemsPerRow = 2;
if (is4xl) {
dynamicItemsPerRow = 12;
} else if (is3xl) {
dynamicItemsPerRow = 10;
} else if (is2xl) {
dynamicItemsPerRow = 8;
} else if (isXl) {
dynamicItemsPerRow = 6;
} else if (isLg) {
dynamicItemsPerRow = 5;
} else if (isMd) {
dynamicItemsPerRow = 4;
} else if (isSm) {
dynamicItemsPerRow = 3;
} else {
dynamicItemsPerRow = 2;
}
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
const widthPerItem = Number(width) / setItemsPerRow;
const itemHeight = widthPerItem + getDataRowsCount(type) * 26;
if (widthPerItem === 0) {
return;
}
setTableMeta({
columnCount: setItemsPerRow,
itemHeight,
rowCount: Math.ceil(dataLength / setItemsPerRow),
});
}, 200);
}, [itemsPerRow]); }, [itemsPerRow]);
useLayoutEffect(() => { useLayoutEffect(() => {
throttledSetTableMeta(containerWidth, data.length, itemType); throttledSetTableMeta(containerWidth, data.length, itemType, setTableMeta);
}, [containerWidth, data.length, itemType, throttledSetTableMeta]); }, [containerWidth, data.length, itemType, throttledSetTableMeta]);
const handleOnRowsRendered = useCallback( const handleOnRowsRendered = useCallback(
@@ -237,41 +331,6 @@ export const ItemGridList = ({
], ],
); );
const elements = useMemo(() => {
if (!tableMeta) {
return [];
}
return data
.map((d, i) => {
return {
data: d,
index: i,
};
})
.reduce(
(acc, d) => {
if (d.index % (tableMeta?.columnCount || 0) === 0) {
acc.push([]);
}
const prev = acc[acc.length - 1];
prev.push(d);
return acc;
},
[] as { data: any; index: number }[][],
);
}, [tableMeta, data]);
const itemProps: GridItemProps = {
columns: tableMeta?.columnCount || 0,
data: elements,
enableExpansion,
enableSelection,
gap,
internalState,
itemType,
};
useEffect(() => { useEffect(() => {
if (!initialTop || isInitialScrollPositionSet.current || !tableMeta?.itemHeight) return; if (!initialTop || isInitialScrollPositionSet.current || !tableMeta?.itemHeight) return;
isInitialScrollPositionSet.current = true; isInitialScrollPositionSet.current = true;
@@ -319,14 +378,17 @@ export const ItemGridList = ({
data-overlayscrollbars-initialize="" data-overlayscrollbars-initialize=""
ref={mergedContainerRef} ref={mergedContainerRef}
> >
<List <VirtualizedGridList
listRef={itemGridRef} data={data}
enableExpansion={enableExpansion}
enableSelection={enableSelection}
gap={gap}
internalState={internalState}
itemGridRef={itemGridRef}
itemType={itemType}
onRowsRendered={handleOnRowsRendered} onRowsRendered={handleOnRowsRendered}
onScroll={handleScroll} onScroll={handleScroll}
rowComponent={ListComponent} tableMeta={tableMeta}
rowCount={tableMeta?.rowCount || 0}
rowHeight={tableMeta?.itemHeight || 0}
rowProps={itemProps}
/> />
<AnimatePresence> <AnimatePresence>
{hasExpanded && ( {hasExpanded && (
@@ -4,7 +4,7 @@ import { useMergedRef } from '@mantine/hooks';
import clsx from 'clsx'; import clsx from 'clsx';
import { AnimatePresence } from 'motion/react'; import { AnimatePresence } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react'; import { useOverlayScrollbars } from 'overlayscrollbars-react';
import { import React, {
type JSXElementConstructor, type JSXElementConstructor,
Ref, Ref,
useCallback, useCallback,
@@ -28,6 +28,354 @@ import { parseTableColumns } from '/@/renderer/components/item-list/helpers/pars
import { ItemListHandle, ItemTableListColumnConfig } from '/@/renderer/components/item-list/types'; import { ItemListHandle, ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
interface VirtualizedTableGridProps {
calculatedColumnWidths: number[];
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
cellPadding: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
data: unknown[];
enableAlternateRowColors: boolean;
enableExpansion: boolean;
enableHeader: boolean;
enableHorizontalBorders: boolean;
enableRowHoverHighlight: boolean;
enableSelection: boolean;
enableVerticalBorders: boolean;
getRowHeight: (index: number, cellProps: TableItemProps) => number;
headerHeight: number;
internalState: ItemListStateActions;
itemType: LibraryItem;
mergedRowRef: React.Ref<HTMLDivElement>;
onCellsRendered?: GridProps<TableItemProps>['onCellsRendered'];
parsedColumns: ReturnType<typeof parseTableColumns>;
pinnedLeftColumnCount: number;
pinnedLeftColumnRef: React.RefObject<HTMLDivElement>;
pinnedRightColumnCount: number;
pinnedRightColumnRef: React.RefObject<HTMLDivElement>;
pinnedRowCount: number;
pinnedRowRef: React.RefObject<HTMLDivElement>;
showLeftShadow: boolean;
showRightShadow: boolean;
size: 'compact' | 'default' | 'large';
totalColumnCount: number;
totalRowCount: number;
}
const VirtualizedTableGrid = React.memo(
({
calculatedColumnWidths,
CellComponent,
cellPadding,
data,
enableAlternateRowColors,
enableExpansion,
enableHeader,
enableHorizontalBorders,
enableRowHoverHighlight,
enableSelection,
enableVerticalBorders,
getRowHeight,
headerHeight,
internalState,
itemType,
mergedRowRef,
onCellsRendered,
parsedColumns,
pinnedLeftColumnCount,
pinnedLeftColumnRef,
pinnedRightColumnCount,
pinnedRightColumnRef,
pinnedRowCount,
pinnedRowRef,
showLeftShadow,
showRightShadow,
size,
totalColumnCount,
totalRowCount,
}: VirtualizedTableGridProps) => {
const columnWidth = useCallback(
(index: number) => calculatedColumnWidths[index],
[calculatedColumnWidths],
);
const itemProps: TableItemProps = useMemo(
() => ({
cellPadding,
columns: parsedColumns,
data: enableHeader ? [null, ...data] : data,
enableAlternateRowColors,
enableExpansion,
enableHeader,
enableHorizontalBorders,
enableRowHoverHighlight,
enableSelection,
enableVerticalBorders,
getRowHeight,
internalState,
itemType,
size,
}),
[
cellPadding,
parsedColumns,
enableHeader,
data,
enableAlternateRowColors,
enableExpansion,
enableHorizontalBorders,
enableRowHoverHighlight,
enableSelection,
enableVerticalBorders,
getRowHeight,
internalState,
itemType,
size,
],
);
const PinnedRowCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
/>
);
},
[pinnedLeftColumnCount, CellComponent],
);
const PinnedColumnCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent {...cellProps} rowIndex={cellProps.rowIndex + pinnedRowCount} />
);
},
[pinnedRowCount, CellComponent],
);
const PinnedRightColumnCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={
cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount
}
rowIndex={cellProps.rowIndex + pinnedRowCount}
/>
);
},
[pinnedLeftColumnCount, pinnedRowCount, totalColumnCount, CellComponent],
);
const PinnedRightIntersectionCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={
cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount
}
/>
);
},
[pinnedLeftColumnCount, totalColumnCount, CellComponent],
);
const RowCell = useCallback(
(cellProps: CellComponentProps<TableItemProps>) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
rowIndex={cellProps.rowIndex + pinnedRowCount}
/>
);
},
[pinnedLeftColumnCount, pinnedRowCount, CellComponent],
);
return (
<div className={styles.itemTableContainer}>
<div
className={styles.itemTablePinnedColumnsGridContainer}
style={{
minWidth: `${Array.from({ length: pinnedLeftColumnCount }, () => 0).reduce(
(a, _, i) => a + columnWidth(i),
0,
)}px`,
}}
>
{!!(pinnedLeftColumnCount || pinnedRowCount) && (
<div
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
[styles.withHeader]: enableHeader,
})}
style={{
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
}}
>
<Grid
cellComponent={CellComponent as any}
cellProps={itemProps}
className={styles.noScrollbar}
columnCount={pinnedLeftColumnCount}
columnWidth={columnWidth}
rowCount={pinnedRowCount}
rowHeight={getRowHeight}
/>
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
</div>
)}
{!!pinnedLeftColumnCount && (
<div
className={styles.itemTablePinnedColumnsContainer}
ref={pinnedLeftColumnRef}
>
<Grid
cellComponent={PinnedColumnCell}
cellProps={itemProps}
className={clsx(styles.noScrollbar, styles.height100)}
columnCount={pinnedLeftColumnCount}
columnWidth={columnWidth}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return getRowHeight(index + pinnedRowCount, cellProps);
}}
/>
</div>
)}
</div>
<div className={styles.itemTablePinnedRowsContainer}>
{!!pinnedRowCount && (
<div
className={clsx(styles.itemTablePinnedRowsGridContainer, {
[styles.withHeader]: enableHeader,
})}
ref={pinnedRowRef}
style={
{
'--header-height': `${headerHeight}px`,
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
} as React.CSSProperties
}
>
<Grid
cellComponent={PinnedRowCell}
cellProps={itemProps}
className={styles.noScrollbar}
columnCount={totalColumnCount}
columnWidth={(index) => {
return columnWidth(index + pinnedLeftColumnCount);
}}
rowCount={Array.from({ length: pinnedRowCount }, () => 0).length}
rowHeight={getRowHeight}
/>
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
</div>
)}
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
<Grid
cellComponent={RowCell}
cellProps={itemProps}
className={styles.height100}
columnCount={totalColumnCount}
columnWidth={(index) => {
return columnWidth(index + pinnedLeftColumnCount);
}}
onCellsRendered={onCellsRendered}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return getRowHeight(index + pinnedRowCount, cellProps);
}}
/>
{pinnedLeftColumnCount > 0 && showLeftShadow && (
<div className={styles.itemTableLeftScrollShadow} />
)}
{pinnedRightColumnCount > 0 && showRightShadow && (
<div className={styles.itemTableRightScrollShadow} />
)}
</div>
</div>
{!!pinnedRightColumnCount && (
<div
className={styles.itemTablePinnedColumnsGridContainer}
style={{
minWidth: `${Array.from(
{ length: pinnedRightColumnCount },
() => 0,
).reduce(
(a, _, i) =>
a + columnWidth(i + pinnedLeftColumnCount + totalColumnCount),
0,
)}px`,
}}
>
{!!(pinnedRightColumnCount || pinnedRowCount) && (
<div
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
[styles.withHeader]: enableHeader,
})}
style={{
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
}}
>
<Grid
cellComponent={PinnedRightIntersectionCell}
cellProps={itemProps}
className={styles.noScrollbar}
columnCount={pinnedRightColumnCount}
columnWidth={(index) => {
return columnWidth(
index + pinnedLeftColumnCount + totalColumnCount,
);
}}
rowCount={pinnedRowCount}
rowHeight={getRowHeight}
/>
{enableHeader && (
<div className={styles.itemTablePinnedHeaderShadow} />
)}
</div>
)}
<div
className={styles.itemTablePinnedRightColumnsContainer}
ref={pinnedRightColumnRef}
>
<Grid
cellComponent={PinnedRightColumnCell}
cellProps={itemProps}
className={clsx(styles.noScrollbar, styles.height100)}
columnCount={pinnedRightColumnCount}
columnWidth={(index) => {
return columnWidth(
index + pinnedLeftColumnCount + totalColumnCount,
);
}}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return getRowHeight(index + pinnedRowCount, cellProps);
}}
/>
</div>
</div>
)}
</div>
);
},
);
VirtualizedTableGrid.displayName = 'VirtualizedTableGrid';
export interface TableItemProps { export interface TableItemProps {
cellPadding?: ItemTableListProps['cellPadding']; cellPadding?: ItemTableListProps['cellPadding'];
columns: ItemTableListColumnConfig[]; columns: ItemTableListColumnConfig[];
@@ -166,9 +514,6 @@ export const ItemTableList = ({
return distributed; return distributed;
}, [parsedColumns, centerContainerWidth]); }, [parsedColumns, centerContainerWidth]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const columnWidth = (index: number, _cellProps: TableItemProps) =>
calculatedColumnWidths[index];
const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length; const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length;
const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length; const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length;
@@ -602,80 +947,6 @@ export const ItemTableList = ({
], ],
); );
const PinnedRowCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
/>
);
},
[pinnedLeftColumnCount, CellComponent],
);
const PinnedColumnCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return <CellComponent {...cellProps} rowIndex={cellProps.rowIndex + pinnedRowCount} />;
},
[pinnedRowCount, CellComponent],
);
const PinnedRightColumnCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount}
rowIndex={cellProps.rowIndex + pinnedRowCount}
/>
);
},
[pinnedLeftColumnCount, pinnedRowCount, totalColumnCount, CellComponent],
);
const PinnedRightIntersectionCell = useCallback(
(cellProps: CellComponentProps & TableItemProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount}
/>
);
},
[pinnedLeftColumnCount, totalColumnCount, CellComponent],
);
const RowCell = useCallback(
(cellProps: CellComponentProps<TableItemProps>) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
rowIndex={cellProps.rowIndex + pinnedRowCount}
/>
);
},
[pinnedLeftColumnCount, pinnedRowCount, CellComponent],
);
const itemProps: TableItemProps = {
cellPadding,
columns: parsedColumns,
data: enableHeader ? [null, ...data] : data,
enableAlternateRowColors,
enableExpansion,
enableHeader,
enableHorizontalBorders,
enableRowHoverHighlight,
enableSelection,
enableVerticalBorders,
getRowHeight,
internalState,
itemType,
size,
};
const isInitialScrollPositionSet = useRef<boolean>(false); const isInitialScrollPositionSet = useRef<boolean>(false);
useEffect(() => { useEffect(() => {
@@ -718,186 +989,37 @@ export const ItemTableList = ({
return ( return (
<div className={styles.itemTableListContainer}> <div className={styles.itemTableListContainer}>
<div className={styles.itemTableContainer}> <VirtualizedTableGrid
<div calculatedColumnWidths={calculatedColumnWidths}
className={styles.itemTablePinnedColumnsGridContainer} CellComponent={CellComponent}
style={{ cellPadding={cellPadding}
minWidth: `${Array.from({ length: pinnedLeftColumnCount }, () => 0).reduce( data={data}
(a, _, i) => a + columnWidth(i, itemProps), enableAlternateRowColors={enableAlternateRowColors}
0, enableExpansion={enableExpansion}
)}px`, enableHeader={enableHeader}
}} enableHorizontalBorders={enableHorizontalBorders}
> enableRowHoverHighlight={enableRowHoverHighlight}
{!!(pinnedLeftColumnCount || pinnedRowCount) && ( enableSelection={enableSelection}
<div enableVerticalBorders={enableVerticalBorders}
className={clsx(styles.itemTablePinnedIntersectionGridContainer, { getRowHeight={getRowHeight}
[styles.withHeader]: enableHeader, headerHeight={headerHeight}
})} internalState={internalState}
style={{ itemType={itemType}
minHeight: `${Array.from( mergedRowRef={mergedRowRef}
{ length: pinnedRowCount }, onCellsRendered={handleOnCellsRendered}
() => 0, parsedColumns={parsedColumns}
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`, pinnedLeftColumnCount={pinnedLeftColumnCount}
}} pinnedLeftColumnRef={pinnedLeftColumnRef}
> pinnedRightColumnCount={pinnedRightColumnCount}
<Grid pinnedRightColumnRef={pinnedRightColumnRef}
cellComponent={CellComponent as any} pinnedRowCount={pinnedRowCount}
cellProps={itemProps} pinnedRowRef={pinnedRowRef}
className={styles.noScrollbar} showLeftShadow={showLeftShadow}
columnCount={pinnedLeftColumnCount} showRightShadow={showRightShadow}
columnWidth={columnWidth} size={size}
rowCount={pinnedRowCount} totalColumnCount={totalColumnCount}
rowHeight={getRowHeight} totalRowCount={totalRowCount}
/> />
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
</div>
)}
{!!pinnedLeftColumnCount && (
<div
className={styles.itemTablePinnedColumnsContainer}
ref={pinnedLeftColumnRef}
>
<Grid
cellComponent={PinnedColumnCell}
cellProps={itemProps}
className={clsx(styles.noScrollbar, styles.height100)}
columnCount={pinnedLeftColumnCount}
columnWidth={columnWidth}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return getRowHeight(index + pinnedRowCount, cellProps);
}}
/>
</div>
)}
</div>
<div className={styles.itemTablePinnedRowsContainer}>
{!!pinnedRowCount && (
<div
className={clsx(styles.itemTablePinnedRowsGridContainer, {
[styles.withHeader]: enableHeader,
})}
ref={pinnedRowRef}
style={
{
'--header-height': `${headerHeight}px`,
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
} as React.CSSProperties
}
>
<Grid
cellComponent={PinnedRowCell}
cellProps={itemProps}
className={styles.noScrollbar}
columnCount={totalColumnCount}
columnWidth={(index, cellProps) => {
return columnWidth(index + pinnedLeftColumnCount, cellProps);
}}
rowCount={Array.from({ length: pinnedRowCount }, () => 0).length}
rowHeight={getRowHeight}
/>
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
</div>
)}
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
<Grid
cellComponent={RowCell}
cellProps={itemProps}
className={styles.height100}
columnCount={totalColumnCount}
columnWidth={(index, cellProps) => {
return columnWidth(index + pinnedLeftColumnCount, cellProps);
}}
onCellsRendered={handleOnCellsRendered}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return getRowHeight(index + pinnedRowCount, cellProps);
}}
/>
{pinnedLeftColumnCount > 0 && showLeftShadow && (
<div className={styles.itemTableLeftScrollShadow} />
)}
{pinnedRightColumnCount > 0 && showRightShadow && (
<div className={styles.itemTableRightScrollShadow} />
)}
</div>
</div>
{!!pinnedRightColumnCount && (
<div
className={styles.itemTablePinnedColumnsGridContainer}
style={{
minWidth: `${Array.from(
{ length: pinnedRightColumnCount },
() => 0,
).reduce(
(a, _, i) =>
a +
columnWidth(
i + pinnedLeftColumnCount + totalColumnCount,
itemProps,
),
0,
)}px`,
}}
>
{!!(pinnedRightColumnCount || pinnedRowCount) && (
<div
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
[styles.withHeader]: enableHeader,
})}
style={{
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
}}
>
<Grid
cellComponent={PinnedRightIntersectionCell}
cellProps={itemProps}
className={styles.noScrollbar}
columnCount={pinnedRightColumnCount}
columnWidth={(index, cellProps) => {
return columnWidth(
index + pinnedLeftColumnCount + totalColumnCount,
cellProps,
);
}}
rowCount={pinnedRowCount}
rowHeight={getRowHeight}
/>
{enableHeader && (
<div className={styles.itemTablePinnedHeaderShadow} />
)}
</div>
)}
<div
className={styles.itemTablePinnedRightColumnsContainer}
ref={pinnedRightColumnRef}
>
<Grid
cellComponent={PinnedRightColumnCell}
cellProps={itemProps}
className={clsx(styles.noScrollbar, styles.height100)}
columnCount={pinnedRightColumnCount}
columnWidth={(index, cellProps) => {
return columnWidth(
index + pinnedLeftColumnCount + totalColumnCount,
cellProps,
);
}}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return getRowHeight(index + pinnedRowCount, cellProps);
}}
/>
</div>
</div>
)}
</div>
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{hasExpanded && ( {hasExpanded && (
<ExpandedListContainer> <ExpandedListContainer>