JSFiddle - React, Tailwind, and code Playground

by Anderson Carlos Woss

HTML

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="foo" data-rows="5">
  <li data-id="0">Opção 0</li>
  <li data-id="1">Opção 1</li>
  <li data-id="2">Opção 2</li>
  <li data-id="3">Opção 3</li>
  <li data-id="4">Opção 4</li>
  <li data-id="5">Opção 5</li>
  <li data-id="6" class="selected">Opção 6</li>
  <li data-id="7">Opção 7</li>
  <li data-id="8">Opção 8</li>
</ul>

CSS

#foo {
  width: 300px;
  border-top: 1px solid black;
  border-bottom: 1px solid black;
  overflow: hidden;
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: center;
  color: black;
}

#foo:focus {
  outline: none;
}

#foo li {
  padding: 5px;
}

#foo li.selected {
  font-weight: bold;
}

#foo li:hover {
  color: blue;
  font-weight: bold;
  cursor: pointer;
}

#foo:focus li.selected, #foo:focus li:hover {
  color: blue;
  font-weight: bold;
  cursor: pointer;
}

#foo li.selected:after {
  content: '\2713';
  margin-left: 10px;
}

JavaScript

$(() => {

  (select => {

    "use strict";
    
    // Opções:
    const options = select.children("li");
    
    // Quantidade de opções:
    const length = options.length;
    
    // Número de opções exibidas:
    const rows = parseInt(select.data("rows"));
    
    // Altura do elemento:
    const height = rows * ($(options[0]).height() + 10);
        
    // Posição da opção selecionada:
    const half = parseInt(rows/2 + 1);
    
    // Define o tabindex do elemento:
    select.attr("tabindex", 0);

		// Define a altura do elemento conforme o número de opções exibidos:
    select.css("height", `${height}px`);
    
    function moveFirstToLast() {
    	select.append(select.children("li:first-child").remove());
    }
    
    function moveLastToFirst() {
    	select.prepend(select.children("li:last-child").remove());
    }
    
    function selectHalfOption() {
    	$(options).removeClass("selected");
      select.children(`li:nth-child(${half})`).addClass("selected");
    }
    
    // Centraliza a opção selecionada.
    function center() {
      while (!select.children(`li:nth-child(${half})`).hasClass("selected")) {
        moveFirstToLast();
      }
    }
    
    center();
    
    // Seleciona uma opção no clique:
    select.on("click", "li", function (event) {
      $(options).removeClass("selected");
      $(this).addClass("selected");
      center();
    });
    
    select.on("keydown", function (event) {
    	if (event.keyCode == 38) {
      	moveLastToFirst();
        selectHalfOption();
      } else if (event.keyCode == 40) {
      	moveFirstToLast();
        selectHalfOption();
      }
    });
    
    select.on("mousewheel", function (event) {
    	if (select.is(":focus")) {
        if (event.originalEvent.wheelDelta /120 > 0) {
					moveLastToFirst();
          selectHalfOption();
        } else {
          moveFirstToLast();
          selectHalfOption();
        }
      }
    });

  })($("#foo"));

});