Skip to content

A React component for efficiently rendering tree like structures with support for position: sticky

License

Notifications You must be signed in to change notification settings

marchaos/react-virtualized-sticky-tree

This branch is 1 commit ahead of master.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

479e62a · Jun 24, 2022
Dec 31, 2020
Jul 11, 2017
Jul 1, 2017
Jul 1, 2017
Aug 12, 2020
Jan 26, 2018
Oct 29, 2017
Oct 10, 2019
Jun 24, 2022
Jun 24, 2022

Repository files navigation

react-virtualized-sticky-tree

A React component for efficiently rendering tree like structures with support for position: sticky. react-virtualized-sticky-tree uses a similar API to react-virtualized.

Demo

https://marchaos.github.io/react-virtualized-sticky-tree/

Getting Started

npm install react-virtualized-sticky-tree --save

Usage

Basic Example

import { StickyTree } from 'react-virtualized-sticky-tree';

const tree = {
  root: { name: 'Root', children: ['child1', 'child2', 'child3'], depth: 0 },
  child1: { name: 'Child 1', children: ['child4'], depth: 1 },
  child2: { name: 'Child 2', depth: 2 },
  child3: { name: 'Child 3', depth: 2 },
  child4: { name: 'Child 4', depth: 3 },
};

const getChildren = (id) => {
  if (tree[id].children) {
    return tree[id].children.map(id => ({ id, height: 30, isSticky: true }));
  }
};

const rowRenderer = ({ id, style }) => {
  const node = tree[id];
  return <div style={style}>{node.name}</div>
};

render() {
  return (
      <StickyTree
        root={{ id: 'root', height: 30 }}
        width={width}
        height={height}
        getChildren={getChildren}
        rowRenderer={rowRenderer}
        renderRoot={true}
        overscanRowCount={20}
      />
  );
)

Nested Sticky Header Styles

StickyTree renders the component within a nested structure so that the header's position may be 'stuck' at different levels (see demo). When passing the root node or items in the children array, specifying isSticky: true will make the item sticky.

Every nested sticky level should have a top which is at the bottom of the sticky level above it. For example. If your root node is 30px high and has a top of 0, the next sticky node should have a top of 30px. The z-index of the node should also be lower than the nodes above it (so that it is scrolled out of view underneath its parent node). If your root node is z-index 4, then the node below could be 3, below that 2 and so on.

An implementation of this would look like:

const getChildren = (id) => {
    if (shouldBeSticky(id)) {
      return tree[id].children.map(childId => ({
         id: childId, 
         isSticky: true,
         stickyTop: tree[childId].depth * 10,
         zIndex: 30 - tree[childId].depth, 
         height: 10
      }))
    }
    return tree[id].children.map(childId => ({ id: childId, isSticky: false, height: 10 }))
};

/**
 * Here, style will include the styles to make the node sticky in the right position. 
 */
const rowRenderer = ({ id, style }) => {
  return <div className="row" style={style}>{mytree[id].name}</div>;
};

Be sure to pass a sticky root node to StickyTree if it should be sticky

<StickyTree
    className="treee"
    root={{ id: 'root', isSticky: true, stickyTop: 0, zIndex: 3, height: 10 }}
    rowRenderer={rowRenderer}
    getChildren={getChildren}
/>

Dynamic Height Container

If the containing element of your tree has a dynamic height, you can use react-measure to provide the width and height to sticky-tree so that it can resize to the available width.

For Simplicity, react-virtualized-sticky-tree includes a component which uses react-measure to achieve this:

import { AutoSizedStickyTree } from 'react-virtualized-sticky-tree';

<AutoSizedStickyTree
    className="tree"
    root={{ id: 'root', isSticky: true, stickyTop: 0, zIndex: 3, height: 30 }}
    rowRenderer={rowRenderer}
    getChildren={getChildren}
    ...
/>

If you want to do this yourself, you can install react-measure:

npm install react-measure --save

as a HOC:

const MeasuredTree = withContentRect('bounds')(({ measureRef, measure, contentRect }) => (
  <div ref={measureRef} className="sticky-wrapper">
    <StickyTree
      root={{id: 0}}
      getChildren={getChildren}
      rowRenderer={rowRenderer}
      renderRoot={true}
      width={contentRect.bounds.width}
      height={contentRect.bounds.height}
      overscanRowCount={20}
    />
  </div>
));

or within render()

<Measure
    bounds={true}
    onResize={(contentRect) => {this.setState({ dimensions: contentRect.bounds });}}
>
    {({ measureRef }) => 
          <div ref={measureRef} className="sticky-tree-wrapper">
              <StickyTree
                  width={this.state.dimensions.width}
                  height={this.state.dimensions.height}
                  root={{id: 0 }}
                  renderRoot={true}
                  rowRenderer={this.rowRenderer}
                  getChildren={this.getChildren}
                  overscanRowCount={20}
              />
          </div>
    }
</Measure>

Supported Browsers

  • Tested with Chrome 59+
  • Tested with Safari 11+
  • Tested with Firefox 54+

Rendering tree structures is supported in all modern browsers. For position: sticky, See http://caniuse.com/#search=position%3Asticky