JSFiddle - React, Tailwind, and code Playground

by neonDog

HTML

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha2/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="row" style="width: 80%;">
  <div class="col-1">
    <div id="spinner"></div>
  </div>
  <div class="col-1">
    <div id="spinner2"></div>
  </div>
  <div class="col-1">
    <div id="spinner3"></div>
  </div>
</div>

SCSS

.neon-spinner {
  
  .input-group-append {
    display: flex;
    flex-direction: column;
  }
  
  .input-wrap { //Copying normal .input-group > .form-control styles
    display:flex;
    flex-direction: row;
    position: relative;
    flex: 1 1 auto;
    width: 1%;
    min-width: 0;
  }
  
  .unit-dropdown {
    position:absolute;
    top: 0;
    bottom: 0;
    right: 3px;
    z-index: 10;
    opacity: 0.5;
    border-radius: 4px;
    font-weight: bold;
    display: flex;
    align-items: center;
    
    .btn.unit-dropdown-text {
      width:100%;
      padding: 2px;
      font-size: 0.5em;
    }
  }
  
  .btn.btn-xs {
    padding: 1px;
    height: 100%;
    font-size: 0.6em;
    //cursor: n-resize;
    &[data-dir="up"] {
      border-bottom-left-radius: 0;
      border-bottom-right-radius: 0;
      border-top-left-radius: 0;
      
    }
    &[data-dir="down"] {
      border-top: 0;
      border-top-left-radius: 0;
      border-top-right-radius: 0;
      border-bottom-left-radius: 0;
    }
  }
  
}

JavaScript

const roundToNearest = (x, to, down) => Math[down ? 'ceil' : 'floor'](x/to)*to;


class NeonSpinner {

	constructor(container, settings={}) {
    
    const defaults = {
    	units: [null, 'px', 'em', 'rem', '%', 'vw', 'vh'],
    	step: 1,
      min: -2,
      max: 50,
      emptyValue: null,
    	draggable: true,
      roundToNearest: true,
      onInput:(value, unit) => { console.error(value, unit) },
      onChange:(value, unit)=>{ console.warn(value, unit) },
      template: `
      	<div class="input-wrap">
         <input type="text" class="form-control form-control-sm">

         <div class="unit-dropdown dropdown">
           <div class="unit-dropdown-text btn btn-secondary">rem</div>
         </div>

       </div>

        <div class="input-group-append">
          <button class="btn btn-xs btn-outline-secondary neon-spinner-click" type="button" data-action="spin" data-dir="up"><i class="fa fa-angle-up"></i></button>
          <button class="btn btn-xs btn-outline-secondary neon-spinner-click" type="button" data-action="spin" data-dir="down"><i class="fa fa-angle-down"></i></button>
        </div>`
    };
    
    this.options = Object.assign({}, defaults, settings);
    
    this.container = container;
    
    this.render();
    
    this.value = this.options.emptyValue;
    this.unit = this.options.units[0] || null; //Default to first unit declared
    
    this._mousedown = this._mousedown.bind(this);
    this._mouseup = this._mouseup.bind(this);
    this._change = this._change.bind(this);
    this._input = this._input.bind(this);
    this.eventListeners();
    
    this.input.max = this.options.max;
    this.input.min = this.options.min;
  }
  
  render() {
  	this.container.innerHTML = this.options.template;
    this.container.className = 'input-group neon-spinner';
    this.input = this.container.getElementsByTagName('input')[0];
    this.unitDropdownText = this.container.getElementsByClassName('unit-dropdown-text')[0];
  }
  
  fireEvent(type) {
...