JSFiddle - React, Tailwind, and code Playground

by neonDog

HTML

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css">
<div class="test"></div>

SCSS

body, html {
  font-family: Helvetica;
  background-color: #212830;
  color: #9EA1B2;
}
* {
  box-sizing:border-box;
}

$directory-color: #0568ae;
$file-color: inherit;

.neon-tree-item {
  padding:2px 4px 2px 0;
  border-radius:6px;
  outline:0;
  cursor:default;
  
  &.file .neon-tree-icon {
    color:$file-color;
  }
  
  &.directory .neon-tree-icon {
    color:$directory-color;
  }
  
  &:focus {
    color: #FFFFFF;
    background-color: #31343F;
  }
}

.neon-tree-row {
    list-style:none;
    padding-top:3px;
    padding-bottom:3px;
}

.children {
    padding-left:20px;  
}

.root.children {
  padding-left:0;
}

.neon-tree-toggle {
    width:15px;
    text-align:right;
}

.neon-tree-icon, .neon-tree-name, .neon-tree-toggle {
  display:inline-block;
  padding-left:4px;
}

.hide {
  display:none;
}

JavaScript

class NeonTree {
	
  	constructor(element,settings={}) {
	
        this.element = element;

        this.defaults = {
        		directoryIcon:'fas fa-folder',
            directoryOpenIcon:'far fa-folder',
            fileIcon:'far fa-file',
            toggleClosed:'fas fa-caret-right',
            toggleOpen:'fas fa-caret-down',
            data:[]
        };
				
        this.options = Object.assign({},this.defaults,settings);
				
        this.click = this.click.bind(this);
        this.element.addEventListener('click',this.click);
				
        this.build();
  	}

    build() {
      	this.element.innerHTML = this.buildTree(this.options.data);
    }
  	
  	buildItem(itemData) {
			
      let {text,icon,children,type,state,a_attr} = itemData;

      let html = '<a ';

      for (let key in a_attr) {
      		if (key !== 'class') {
          		html += ' '+key+'="'+a_attr[key]+'"';
          }
      }
			
      const isOpen = itemData.type === 'directory' && state && state.opened === true;
      
      if (!icon) {
        if (itemData.type === 'directory') {
            icon = isOpen === true ? this.options.directoryOpenedIcon : this.options.directoryIcon;
        } else {
        		icon = this.options.fileIcon;
        }
      }
			
      html += `class="neon-tree-item ${type} ${ a_attr.class || '' }" tabindex="0">`;
			
      if (children.length) {
          html += `<span class="neon-tree-toggle${ isOpen === true ? ' open' : ''}">
            <i class="${ isOpen === true ? this.options.toggleOpen : this.options.toggleClosed }"></i>
          </span>`;
      }

      html += `<span class="neon-tree-icon"><i class="${ icon }"></i></span><span class="neon-tree-name">${ text || '' }</span></a>`;
      return html;

    }
		
  	buildTree(itemData,depth=0,opened=true) {
				
        let html = `<ul class="children ${ depth === 0 ? 'root' : '' } ${ opened === true ? '' : 'hide' }">`;
        ++depth;
        itemData.forEach((v) => {
						
            const...