Skip to content
Snippets Groups Projects
Slider.vue 3.96 KiB
Newer Older
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { computed } from 'vue';
import { convertThemeToColorBlackDefault, convertThemeToColorWhiteDefault } from '@/app/helpers';

interface Props {
  value: string | number;
  width?: string | number;
  min?: string | number;
  max?: string | number;
  step?: string | number;
  size?: 'small' | 'medium' | 'large' | 'extraLarge';
  theme?: string;
  backgroundColor?: string;
  orientation?: 'horizontal' | 'vertical';
  isSmooth?: any;
  options?: {
    label: string;
    value: number;
    color?: string;
}
const props = defineProps<Props>();
const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit);
const optionValue = ref(
  typeof value.value === 'string'
    ? props.options!.findIndex((option) => option.label === value.value)
    : value.value
);
watch([optionValue], () => {
  if (props.options) {
    value.value = props.options!.find((option) => option.value == optionValue.value)!.label;
  } else value.value = optionValue.value;
});
const sliderButtonSize = computed(() => {
  if (!props.size) return '40px';
  switch (props.size) {
    case 'small':
      return '25px';
    case 'medium':
      return '40px';
    case 'large':
      return '70px';
    case 'extraLarge':
      return '100px';
  }
});
const sliderHeight = computed(() => `${Math.floor(sliderButtonSize.value.slice(0, -2) / 3)}px`);
const sliderBorderRadius = props.isSmooth ? sliderHeight.value : '0%';
const sliderButtonBorderRadius = props.isSmooth ? '50%' : '0%';
const themeColor = computed(() => convertThemeToColorWhiteDefault(props.theme));
const themeBackground = computed(() => convertThemeToColorBlackDefault(props.backgroundColor));
</script>

<template>
  <div
    :class="[
      'slideContainer',
      {
        verticalSlider: orientation === 'vertical'
      }
    ]"
    :style="`width: ${width ?? 200}px`"
  >
    <input
      type="range"
      class="slider"
      :min="min ?? 0"
      :max="max ?? 100"
      :step="step ?? 1"
    />
    <input type="range" list="values" class="opacity-0 size-0" />

    <div v-if="options?.length">
      <ul
        class="marksList"
        :style="`width: ${width ?? 200}px; margin-bottom: 5px; font-size: 10px; padding: 0 15px`"
        <li v-for="option of options" :key="option">|</li>
      </ul>
      <datalist
        id="values"
        :class="[
          '',
          {
            datalistVertical: orientation === 'vertical'
          }
        ]"
      >
        <template v-for="option of options" :key="option.value">
          <option
            :value="option.value"
            :label="option.label"
            :style="`color: ${option.color ?? 'white'}`"
          ></option>
        </template>
      </datalist>
    </div>
  </div>
</template>

<style scoped>
.slideContainer {
  width: v-bind(width);
}
.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: v-bind(sliderHeight);
  background: v-bind(themeBackground);
  outline: none;
  border-radius: v-bind(sliderBorderRadius);
  filter: brightness(100%);
  -webkit-transition: 0.2s;
  transition: filter 0.2s;
  cursor: pointer;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: v-bind(sliderButtonSize);
  height: v-bind(sliderButtonSize);
  border-radius: v-bind(sliderButtonBorderRadius);
  background: v-bind(themeColor);
  -webkit-transition: 0.2s;
  transition: filter 0.2s;
}

.slider::-webkit-slider-thumb:hover {
  filter: brightness(90%);
}

.slider::-moz-range-thumb {
  width: v-bind(sliderButtonSize);
  height: v-bind(sliderButtonSize);
  background: v-bind(themeColor);
  cursor: pointer;
}
.verticalSlider {
  transform: rotate(270deg);
}
datalist {
  display: flex;
  justify-content: space-between;
  width: v-bind(width);
}
.datalistVertical {
  flex-direction: column;
  writing-mode: vertical-lr;
}
option {
  padding: 0;
}
.marksList {
  display: flex;
  justify-content: space-between;
}