JSFiddle - React, Tailwind, and code Playground

by neonDog

HTML

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxst.icons8.com/vue-static/landings/line-awesome/font-awesome-line-awesome/css/all.min.css">
<div class="neon-page-block">
test 
test<br>
test

</div>

SCSS

body {
  background: #39D;
}

.pie-menu-container {
  display: flex;
  justify-content: center;
  position: absolute;
  bottom:-70px;
  left:0;
  right:0;
}

.neon-pie-menu-circle {

  div {
    text-decoration: none;
    color: #666;
    display: block;
    height: 40px;
    width: 40px;
    line-height: 40px;
    margin-left: -20px;
    margin-top: -20px;
    position: absolute;
    text-align: center;
    cursor: pointer;
    background: white;
    border-radius: 50%;
    transform: scale(0);
    opacity:0;
    transition: all 0.3s ease;
    
    &:hover {
      transform: scale(1.1)!important;
      color: #cef;
      background:#ccc;
    }
    
  }
  
  &.open div {
    opacity: 1;
    transform: scale(1);
  }
  
  @for $i from 1 through 10 {
    div:nth-child(#{$i}) {
      transition-delay: $i * 50ms;
    }
  }
}


.plus-minus-btn {
  background: #dde;
  width: 50px;
  height: 50px;
  border: 0;
  position: relative;
  border-radius:50%;
  &:focus {outline:0;}
  
  &:hover {
    background: #eef;
  }
  
  span {
    position: absolute;
    transition: 0.3s;
    background: #666;
    border-radius: 2px;
    
    &:first-of-type {
      top: 25%;
      bottom: 25%;
      width: 10%;
      left: 45%;
    }

    &:last-of-type {
      left: 25%;
      right: 25%;
      height: 10%;
      top: 45%;
    }
  }
  
  &:focus span:first-of-type, &:focus span:last-of-type {
    transform: rotate(90deg);
  }
  
  &:focus span:last-of-type {
    left: 50%;
    right: 50%;
  }
  
}


.neon-page-block {
  min-height:200px;
  outline: 2px red solid;
  background:#ccc;
  position:relative;
}

JavaScript

class NeonPieMenu {
	
	constructor(element,settings={}) {
  	
    this.defaults = {
    	distance:120,
    	items:[
        {icon:'fa fa-heading fa-2x',action:'home', label:'Heading'},
        {icon:'fa fa-font fa-2x',action:'rich-text', label:'Rich-text'},
        {icon:'fa fa-images fa-2x',action:'gallery', label:'Gallery'},
        {icon:'fa fa-camera fa-2x',action:'image', label:'Image'}
      ],
      onClick:(action,el)=>{}
    };
    
    this.options = Object.assign({}, this.defaults, settings);
    
    this.element = element;
    this.circle = element.getElementsByClassName('neon-pie-menu-circle')[0];
    this.button = element.getElementsByTagName('button')[0];
    this.open = false;
    
    this.renderItems();
    this._click = this._click.bind(this);
    this._focus = this._focus.bind(this);
    this._blur = this._blur.bind(this);
    
    this.eventListeners(true);
  }
  
  updateData(data={}) {
  	this.options = Object.assign({}, this.options, settings);
    this.renderItems();
  }
  
  renderItems() {
  	const frag = document.createDocumentFragment();
    const o = this.options;
    
    for(let i = 0, l = o.items.length; i < l; i++) {
    	const item = o.items[i];
    	const itemEl = document.createElement('div');
      itemEl.style.left = (50 - o.distance * Math.cos(-0.5 * Math.PI - 2*(1/l)*i*Math.PI)).toFixed(4) + "%";
      itemEl.style.top = (50 + o.distance * Math.sin(-0.5 * Math.PI - 2*(1/l)*i*Math.PI)).toFixed(4) + "%";
      itemEl.className = 'neon-pie-menu-click ' + item.icon;
      itemEl.setAttribute('data-action', item.action);
     	item.tooltip && itemEl.setAttribute('data-tooltip', item.tooltip);
      frag.append(itemEl);
    }
    
    this.circle.innerHTML = '';
    this.circle.append(frag);
  }
  
  _click(e) {
  	const el = e.target.closest('.neon-pie-menu-click');
    if (!el && this.open) {
    	this.button.blur();
      return;
    }
    
    const data = el.dataset;
    
    this.options.onClick(data.action, el);
   ...