vueform / slider

Vue 3 slider component with multihandles, tooltips merging and formatting (+Tailwind CSS support).

Home Page:https://vueform.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Does this specific 'step' feature exist? - possible feature request

ShirinShirinak opened this issue · comments

Can we do steps like: 0,100,200,1000,5000 (so the value would be changing and we would have markings on the slider, so in this case there would be 3 markings on the slider (100,200.1000).

If we can't do it right now, will it be possible to add this feature in the future please? Thank you

In Vue 3 you can do it by yourself using Teleport feature

As i see some demand on this feature i would like to share my solution to implement steps. Feel free to use it (just pass :discrete="true"):

<script setup lang="ts">
// this plugin has bugged typescript annotations. See https://github.com/vueform/slider/issues/71
// eslint-disable-next-line import/default
import Slider from '@vueform/slider'
import '@vueform/slider/themes/default.css'

interface Props {
  max?: number;
  min?: number;
  step?: number;
  discrete?: boolean;
  tooltips?: boolean;
  modelValue: number | number[];
}

const props = withDefaults(defineProps<Props>(), {
  min: 0,
  max: 100,
  step: 10,
  tooltips: true
})

const emit = defineEmits(['update:modelValue'])

const slider = ref(null)

const model = computed({
  get () {
    return props.modelValue
  },
  set (newValue) {
    emit('update:modelValue', newValue)
  }
})

const isArray = computed(() => Array.isArray(model.value) && model.value.length > 1)

const isDefault = (index: number) => {
  if (!isArray.value) {
    return (model.value as number) < index * props.step
  }
  const modelArray = model.value as number[]
  const first = modelArray[0]
  const last = modelArray[modelArray.length - 1]
  const value = (index + props.min / props.step) * props.step
  return last < value || first > value
}

const isActive = (index: number) => {
  if (!isArray.value) {
    return (model.value as number) >= index * props.step
  }
  const modelArray = model.value as number[]
  const first = modelArray[0]
  const last = modelArray[modelArray.length - 1]
  const value = (index + props.min / props.step) * props.step
  return last >= value && first <= value
}

</script>

<template>
  <Slider
    id="slider"
    ref="slider"
    v-model="model"
    class="slider"
    :tooltips="props.tooltips"
    :step="props.step"
    :min="props.min"
    :max="props.max"
    tooltip-position="bottom"
    :lazy="false"
  />
  <Teleport v-if="slider" to="div.slider-handle-lower > .slider-touch-area">
    @
  </Teleport>
  <Teleport v-if="slider && isArray" to="div.slider-handle-upper > .slider-touch-area">
    @
  </Teleport>
  <Teleport v-if="slider && discrete" to="div.slider-base">
    <div class="line absolute bottom-0 top-[-1px] grid grid-flow-col items-center w-full">
      <div class="h-1 w-1" />
      <div
        v-for="i in ((props.max - props.min) / props.step) - 1"
        :key="`point-${i}`"
        class="h-1 w-1 border !box-content bg-white-100 rounded-full"
        :class="{
          'border-accent': isActive(i),
          'border-black-5': isDefault(i)
        }"
      />
    </div>
  </Teleport>
</template>

<style scoped lang="postcss">
.slider {
  @apply my-4;
  --slider-height: theme('spacing.1');
  --slider-bg: theme('colors.black-5');
  --slider-connect-bg: theme('colors.accent');
  --slider-radius: theme('borderRadius.sm');
  --slider-handle-border: theme('borderWidth.DEFAULT') solid theme('borderColor.black-10');
  --slider-handle-shadow: theme('boxShadow.gray');
  --slider-handle-shadow-active: theme('boxShadow.gray');
  --slider-tooltip-bg: theme('colors.transparent');
  --slider-tooltip-bg-disabled: theme('colors.transparent');
  --slider-tooltip-color: theme('colors.black-100');
  --slider-tooltip-radius: theme('borderRadius.none');
  --slider-tooltip-py: theme('spacing.0');
  --slider-tooltip-px: theme('spacing.0');
  --slider-tooltip-arrow-size: theme('spacing.0');
  --slider-tooltip-distance: theme('spacing.1');
  :deep(.slider-base) {
     .slider-origin {
      .slider-handle {
        &:focus {
          box-shadow: none;
        }
        .slider-touch-area {
          @apply flex items-center justify-center text-accent;
          .nuxt-icon {
            @apply h-3 w-3;
          }
        }
        .slider-tooltip {
          @apply text-other;
        }
      }
    }
  }
}
</style>