JSFiddle - React, Tailwind, and code Playground

by denvit

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
  <button @click="showFirst = !showFirst">
    CHANGE
  </button>
  <circular v-if="showFirst" :value="value" @select="value = arguments[0]" :items="items"></circular>
  <circular v-if="!showFirst" :value="45" @select="value = arguments[0]" :items="items"></circular>
</div>

CSS

.CircularSelect {
  width: 250px;
  height: 250px;
  border-radius: 50%;
  background: #e0e0e0;
  padding: 24px;
  position: relative;
  cursor: pointer;
}

.CircularSelect__Inner {
  position: relative;
  height: 100%;
}

.CircularSelect__Pointer {
  width: 1px;
  height: 50%;
  margin: 0 auto;
  transform-origin: bottom center;
  min-height: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #027be3;
  pointer-events: none;
}

.CircularSelect__Pointer__Bubble {
  width: 10px;
  height: 10px;
  background: #027be3;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

.CircularSelect__Position {
  position: absolute;
  min-height: 32px;
  width: 32px;
  height: 32px;
  font-size: 12px;
  line-height: 32px;
  margin: 0;
  padding: 0;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  text-align: center;
  cursor: pointer;
  pointer-events: none;
}

.CircularSelect__Position--selected {
  background-color: #027be3;
  color: #ffffff;
}

.CircularSelect__Position--disabled {
  color: #bfbfbf;
}

JavaScript

var circular = {
  name: 'circular',
  template: `<div class="CircularSelect" @mousedown="dragStart" @mouseup="dragStop" @mouseleave="dragStop" @touchstart="dragStart" @touchend="dragStop">
        <div class="CircularSelect__Inner" ref="circle">
            <div ref="pointer" class="CircularSelect__Pointer">
                <div v-show="points.indexOf(value) === -1" class="CircularSelect__Pointer__Bubble"></div>
            </div>

            <div
                v-for="(item, index) in items" class="CircularSelect__Position"
                v-if="points.indexOf(item.id) >= 0"
                :class="{ 'CircularSelect__Position--selected': item.id === value, 'CircularSelect__Position--disabled': item.disabled }"
                :style="itemStyle(index)" @click="select(item)">
                {{ item.title }}
            </div>
        </div>
    </div>`,
  props: {
    value: {
      required: true,
    },
    items: {
      type: Array,
      required: true,
    },
    keyPoints: {
      type: Array,
    },
  },

  data() {
    return {
      circleRadius: 0,
      centerPosition: {
        top: 0,
        left: 0,
      },
    };
  },

  computed: {
    itemAngle() {
        return 360 / this.items.length;
      },

      points() {
        if (this.keyPoints) {
          return this.keyPoints;
        }

        return this.items.map(item => item.id);
      },
  },

  mounted() {
    this.refresh();
  },

  updated() {
    //this.refresh();
  },

  methods: {
    refresh() {
        this.circleRadius = this.$refs.circle.clientWidth / 2;

        this.$refs.pointer.style.transform = `rotate(${this.itemAngle * this.items.findIndex(item => item.id === this.value)}deg)`;
      },

      itemStyle(index) {
        let angle = index * this.itemAngle - 90;

        return {
          top: 50 + 50 * Math.sin(angle * Math.PI / 180) + '%',
          left: 50 + 50 * Math.cos(angle * Math.PI / 180) + '%',
        };
      },

      dragStart(event) {
       ...