import React, { useState, useEffect, useRef } from 'react'; interface RowData { id: number; [key: string]: any; } interface VirtualTableProps { data: RowData[]; columns: string[]; estimatedRowHeight: number; visibleRows: number; resizableColumns?: boolean; pinnedColumns?: string[]; // New prop to specify pinned columns } const VirtualTable: React.FC = ({ data, columns, estimatedRowHeight, visibleRows, resizableColumns = false, pinnedColumns = [] // Default to no pinned columns }) => { const [start, setStart] = useState(0); const [rowHeights, setRowHeights] = useState([]); const [columnWidths, setColumnWidths] = useState<{ [key: string]: number }>( Object.fromEntries(columns.map(column => [column, 100])) ); const containerRef = useRef(null); const rowRefs = useRef<(HTMLTableRowElement | null)[]>([]); const resizingColumn = useRef(null); const scrollableColumns = columns.filter(col => !pinnedColumns.includes(col)); useEffect(() => { const handleScroll = () => { if (containerRef.current) { const scrollTop = containerRef.current.scrollTop; const newStart = Math.floor(scrollTop / estimatedRowHeight); setStart(newStart); } }; containerRef.current?.addEventListener('scroll', handleScroll); return () => containerRef.current?.removeEventListener('scroll', handleScroll); }, [estimatedRowHeight]); useEffect(() => { const measureRowHeights = () => { const newRowHeights = rowRefs.current.map( (rowRef) => rowRef?.getBoundingClientRect().height || estimatedRowHeight ); setRowHeights(newRowHeights); }; measureRowHeights(); window.addEventListener('resize', measureRowHeights); return () => window.removeEventListener('resize', measureRowHeights); }, [data, estimatedRowHeight]); const getTotalHeight = () => { return rowHeights.reduce((sum, height) => sum + height, 0) || data.length * estimatedRowHeight; }; const getOffsetForIndex = (index: number) => { return rowHeights.slice(0, index).reduce((sum, height) => sum + height, 0); }; const visibleData = data.slice(start, start + visibleRows); const handleMouseDown = (column: string) => (e: React.MouseEvent) => { if (resizableColumns) { resizingColumn.current = column; e.preventDefault(); } }; const handleMouseMove = (e: React.MouseEvent) => { if (resizableColumns && resizingColumn.current) { const newWidth = Math.max(50, e.clientX - (e.target as HTMLElement).getBoundingClientRect().left); setColumnWidths(prev => ({ ...prev, [resizingColumn.current!]: newWidth })); } }; const handleMouseUp = () => { if (resizableColumns) { resizingColumn.current = null; } }; useEffect(() => { if (resizableColumns) { document.addEventListener('mousemove', handleMouseMove as any); document.addEventListener('mouseup', handleMouseUp); return () => { document.removeEventListener('mousemove', handleMouseMove as any); document.removeEventListener('mouseup', handleMouseUp); }; } }, [resizableColumns]); const renderTableContent = (columnSet: string[]) => ( {columnSet.map((column) => ( ))} {visibleData.map((row, index) => ( (rowRefs.current[start + index] = el)} > {columnSet.map((column) => ( ))} ))}
{column} {resizableColumns && (
)}
{row[column]}
); return (
{pinnedColumns.length > 0 && (
{renderTableContent(pinnedColumns)}
)}
0 ? '5px' : '0' }}>
{renderTableContent(scrollableColumns)}
); }; export default VirtualTable;