React

by Pratik Galoria

HTML

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css">
<div id="app"></div>

SCSS

body {
  padding: 10px;
  font-family: Helvetica;
}

#app {
  background: #2d2d3a;
  border-radius: 6px;
  padding: 20px 0;
}

ul {
  margin-left: 20px;
}

li {
  color: #fff;
  margin: 8px 0;
  
  > i {
    color: #57626e;
    font-size: 20px;
  }
  
  > span {
    padding: 0 10px;
  }
}

React

class Treeview extends React.Component {
  constructor(props) {
    super(props);
  }
  
  render() {
    return (
      <ul>
        {this.props.data.map(node => <TreeNode node={node} />)}
      </ul>
    )
  }
}

class TreeNode extends React.Component {
	constructor(props) {
  	super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = {
    	isCollapsed: true,
    };
  }
  
  handleClick(e) {
  	e.stopPropagation();
  	this.setState((state, props) => ({
      isCollapsed: !state.isCollapsed,
    }));
  }
  
  render() {
  	const {isCollapsed} = this.state;
  	const {node} = this.props;
    
    return (
      <li key={node.value} onClick={this.handleClick}>
        <i className={isCollapsed ? `fas fa-plus-square` : `fas fa-minus-square`} />
        <span>{node.value} {node.items && `(${node.items.length})`}</span>
        {node.items && (
          <ul style={{display: isCollapsed ? 'none' : 'block'}}>
            {node.items.map(item => <TreeNode node={item} />)}
          </ul>
        )}
      </li>
    );
  }
}

// API response
const data = [
  {
    value: 'GN0001',
    items: [
      {
        value: 'Channel1',
        items: [
          {value: 'Sub1'},
          {
            value: 'Sub2',
            items: [
              {
                value: 'Step'
              },
            ],
          },
        ],
      },
      {value: 'Channel2'},
    ]
  }
];

ReactDOM.render(<Treeview data={data} />, document.querySelector("#app"))