bvaughn / react-window

React components for efficiently rendering large lists and tabular data
https://react-window.now.sh/
MIT License
15.49k stars 778 forks source link

[ STICKY COLUMNS ] : Not able to make the first two columns as sticky #737

Open navneetbz opened 10 months ago

navneetbz commented 10 months ago

finding it very difficult to make the first 2 columns as sticky. I am using this piece of code

import React from "react";
import { VariableSizeGrid as Grid } from "react-window";

function getCellIndicies(child: any) {
  return { row: child.props.rowIndex, column: child.props.columnIndex };
}

function getShownIndicies(children : any) {
  let minRow = Infinity;
  let maxRow = -Infinity;
  let minColumn = Infinity;
  let maxColumn = -Infinity;

  React.Children.forEach(children, (child) => {
    const { row, column } = getCellIndicies(child);
    minRow = Math.min(minRow, row);
    maxRow = Math.max(maxRow, row);
    minColumn = Math.min(minColumn, column);
    maxColumn = Math.max(maxColumn, column);
  });

  return {
    from: {
      row: minRow,
      column: minColumn
    },
    to: {
      row: maxRow,
      column: maxColumn
    }
  };
}

function useInnerElementType(Cell : any, columnWidth : any, rowHeight: any) {
  return React.useMemo(
    () =>
      React.forwardRef((props, ref : any) => {
        function sumRowsHeights(index: any) {
          let sum = 0;

          while (index > 1) {
            sum += rowHeight(index - 1);
            index -= 1;
          }

          return sum;
        }

        function sumColumnWidths(index: any) {
          let sum = 0;

          while (index > 1) {
            sum += columnWidth(index - 1);
            index -= 1;
          }

          return sum;
        }

        const shownIndecies = getShownIndicies((props as any).children);

        const children = React.Children.map((props as any).children, (child) => {
          const { column, row } = getCellIndicies(child);

          // do not show non-sticky cell
          if (column === 0 || row === 0) {
            return null
          }

          return child;
        });

        children.push(
          React.createElement(Cell, {
            key: "0:0",
            rowIndex: 0,
            columnIndex: 0,
            style: {
              display: "inline-flex",
              width: columnWidth(0),
              height: rowHeight(0),
              position: "sticky",
              top: 0,
              left: 0,
              zIndex: 4
            }
          })
        );

        const shownColumnsCount =
          shownIndecies.to.column - shownIndecies.from.column;

        for (let i = 1; i <= shownColumnsCount; i += 1) {
          const columnIndex = i + shownIndecies.from.column;
          const rowIndex = 0;
          const width = columnWidth(columnIndex);
          const height = rowHeight(rowIndex);

          const marginLeft = i === 1 ? sumColumnWidths(columnIndex) : undefined;

          children.push(
            React.createElement(Cell, {
              key: `${rowIndex}:${columnIndex}`,
              rowIndex,
              columnIndex,
              style: {
                marginLeft,
                display: "inline-flex",
                width,
                height,
                position: "sticky",
                top: 0,
                zIndex: 3
              }
            })
          );
        }

        const shownRowsCount = shownIndecies.to.row - shownIndecies.from.row;

        for (let i = 1; i <= shownRowsCount; i += 1) {
          const columnIndex = 0;
          const rowIndex = i + shownIndecies.from.row;
          const width = columnWidth(columnIndex);
          const height = rowHeight(rowIndex);

          const marginTop = i === 1 ? sumRowsHeights(rowIndex) : undefined;

          children.push(
            React.createElement(Cell, {
              key: `${rowIndex}:${columnIndex}`,
              rowIndex,
              columnIndex,
              style: {
                marginTop,
                width,
                height,
                position: "sticky",
                left: 0,
                zIndex: 2
              }
            })
          );
        }

        return (
          <div ref={ref} {...props}>
            {children}
          </div>
        );
      }),
    [Cell, columnWidth, rowHeight]
  );
}

export function GridWithStickyCells(props: any) {
  return (
    <Grid
      {...props}
      innerElementType={useInnerElementType(
        props.children,
        props.columnWidth,
        props.rowHeight
      )}
    />
  );
}

This code makes the headers as sticky and only the 0th column as sticky but I want to make the 1st column also sticky.. can some one please help me out.

marcosperez commented 9 months ago

Hi navneetbz ! this is my code and Its worked for me, I hope can be usefull for you

import React from 'react'
import { VariableSizeGrid as Grid } from 'react-window'

function getCellIndicies(child) {
  return { row: child.props.rowIndex, column: child.props.columnIndex }
}

function getShownIndicies(children) {
  let minRow = Infinity
  let maxRow = -Infinity
  let minColumn = Infinity
  let maxColumn = -Infinity

  React.Children.forEach(children, child => {
    const { row, column } = getCellIndicies(child)
    minRow = Math.min(minRow, row)
    maxRow = Math.max(maxRow, row)
    minColumn = Math.min(minColumn, column)
    maxColumn = Math.max(maxColumn, column)
  })

  return {
    from: {
      row: minRow,
      column: minColumn,
    },
    to: {
      row: maxRow,
      column: maxColumn,
    },
  }
}

function useInnerElementType(Cell, columnWidth, rowHeight, fixedColumns = 1) {
  return React.useMemo(
    () =>
      React.forwardRef((props, ref) => {
        function sumRowsHeights(index) {
          let sum = 0

          while (index > 1) {
            sum += rowHeight(index - 1)
            index -= 1
          }

          return sum
        }

        function sumColumnWidthsFT(from, to) {
          let sum = 0
          for (let index = from; index < to; index++) {
            sum += columnWidth(index)
          }
          return sum
        }

        const shownIndecies = getShownIndicies(props.children)
        const showFixedColumns = fixedColumns - 1
        const children = React.Children.map(props.children, child => {
          const { column, row } = getCellIndicies(child)

          if (column < fixedColumns || row === 0) {
            return null
          }

          return child
        })
        for (let fixColIdx = 0; fixColIdx <= showFixedColumns; fixColIdx++) {
          children.push(
            React.createElement(Cell, {
              key: `0:${fixColIdx}`,
              rowIndex: 0,
              columnIndex: fixColIdx,
              style: {
                display: 'inline-flex',
                width: columnWidth(fixColIdx),
                height: rowHeight(0),
                position: 'sticky',
                top: 0,
                left: fixColIdx === 0 ? 0 : sumColumnWidthsFT(0, fixColIdx),
                zIndex: 4,
              },
            })
          )
        }

        const shownColumnsCount =
          shownIndecies.to.column - shownIndecies.from.column

        for (let i = fixedColumns; i <= shownColumnsCount; i += 1) {
          const columnIndex = i + shownIndecies.from.column
          const rowIndex = 0
          const width = columnWidth(columnIndex)
          const height = rowHeight(rowIndex)

          const marginLeft =
            shownIndecies.from.column !== 0 &&
            columnIndex === shownIndecies.from.column + fixedColumns
              ? sumColumnWidthsFT(
                  fixedColumns,
                  fixedColumns + shownIndecies.from.column
                )
              : 0
          const colsWidth = sumColumnWidthsFT(0, columnIndex + 1) - marginLeft
          const marginRight =
            columnIndex === shownIndecies.to.column
              ? `calc( 100% - ${colsWidth}px)`
              : 0
          children.push(
            React.createElement(Cell, {
              key: `${rowIndex}:${columnIndex}`,
              rowIndex,
              columnIndex,
              style: {
                marginLeft,
                marginRight,
                display: 'inline-flex',
                width,
                height,
                position: 'sticky',
                top: 0,
                zIndex: 3,
              },
            })
          )
        }

        const shownRowsCount = shownIndecies.to.row - shownIndecies.from.row

        for (let i = 1; i <= shownRowsCount; i += 1) {
          for (let j = 0; j <= showFixedColumns; j += 1) {
            const columnIndex = j
            const rowIndex = i + shownIndecies.from.row
            const width = columnWidth(columnIndex)
            const height = rowHeight(rowIndex)

            const left =
              columnIndex === 0 ? 0 : sumColumnWidthsFT(0, columnIndex)
            const colsWidth = sumColumnWidthsFT(0, columnIndex + 1)
            const marginRight =
              showFixedColumns === columnIndex
                ? `calc( 100% - ${colsWidth}px)`
                : undefined

            const marginTop = i === 1 ? sumRowsHeights(rowIndex) : undefined

            children.push(
              React.createElement(Cell, {
                key: `${rowIndex}:${columnIndex}`,
                rowIndex,
                columnIndex,
                style: {
                  marginTop,
                  display: 'inline-flex',
                  width,
                  height,
                  position: 'sticky',
                  left,
                  zIndex: 2,
                  overflow: 'hidden',
                  marginRight,
                },
              })
            )
          }
        }

        return (
          <div ref={ref} {...props}>
            {children}
          </div>
        )
      }),
    [Cell, columnWidth, rowHeight]
  )
}

function StickyGridFn(props, ref) {
  return (
    <Grid
      ref={ref}
      {...props}
      innerElementType={useInnerElementType(
        props.children,
        props.columnWidth,
        props.rowHeight,
        props.fixedColumns
      )}
    />
  )
}

export const StickyGrid = React.forwardRef(StickyGridFn)
navneetbz commented 8 months ago

Hey @marcosperez thanks for the help but this isn't working as I expected it to work..