GrSlider

Пакет: @feugene/granularityядроГруппа: Формы

Берут, когда точное число не важно.

Когда брать

  • точное число не важно — громкость, яркость, прозрачность: важнее «больше — меньше»;
  • диапазон нагляденmin/max показывают границы, которых в поле ввода не видно;
  • нужен диапазон «от — до»range даёт два бегунка, которые не проходят друг сквозь друга;
  • у шкалы есть опорные точкиmarks подписывают ступени;
  • значение применяется по отпусканиюlazy не шлёт запрос на каждое движение.

Когда взять другое

НужноБерите
Нужно точное числоGrNumberInput
Оценка звёздамиGrRating
Ступеней немного и они названыGrSegmented / GrRadioGroup
Значение показывают, а не задаютGrProgressBar

Диапазон

<GrSlider v-model="range" range :min="0" :max="100" aria-label="Бюджет" />

Модель — кортеж [lo, hi]; бегунки не перепрыгивают друг друга. Клик по дорожке уводит ближайший бегунок, а когда оба сошлись в точку — тот, в сторону которого кликнули: иначе схлопнутый диапазон было бы не развести мышью.

Каждый бегунок получает своё имя: ariaLabel плюс граница из локали (gr.slider.min / gr.slider.max) — «Бюджет (минимум)».

Ориентация

<GrSlider v-model="volume" orientation="vertical" :style="{ '--gr-slider-length': '12rem' }" />

В вертикальной дорожке минимум внизу. Длина задаётся --gr-slider-length (по умолчанию 10rem), толщину по-прежнему держит --gr-slider-track-height. Подсказка уходит вбок — над бегунком она легла бы на дорожку, — а подписи меток встают справа. aria-orientation следует пропу, клавиатура не меняется: / увеличивают, / уменьшают.

Когда значение уходит наружу

lazy придерживает update:modelValue до конца жеста: во время перетаскивания бегунок ведёт внутреннее значение, а наружу уходит одно событие на отпускании (вместе с change). Это для дорогих пересчётов, которым незачем срабатывать на каждое движение мыши.

Клавиатура коммитит сразу и при lazy: нажатие клавиши дискретно, придерживать его нечего.

Не доехавший modelValue ставит бегунок на min и объясняется предупреждением в dev-режиме: иначе смещение и aria-valuenow ушли бы в NaN, а диктор прочитал бы его вслух.

Метки и подсказка

marks — словарь { [value]: label } или массив значений. Подписи меток скрыты от скринридера (aria-hidden): значение он читает с самого бегунка, а подписи стали бы случайным текстом внутри слайдера.

formatTooltip управляет и подсказкой, и aria-valuetext — «$1 200» вместо голого «1200». Без своего формата aria-valuetext не выставляется: число самодостаточно.

Оформление

sizexslg, читается из GrConfigProvider. Точечная кастомизация — через переменные: --gr-slider-rail, --gr-slider-fill, --gr-slider-thumb-bg, --gr-slider-thumb-border, --gr-slider-thumb-size, --gr-slider-track-height, --gr-slider-length.

disabled гасит заливку, бегунок и подписи меток токенами --gr-disabled-*, а не прозрачностью: opacity разбавляет выверенные на AA цвета текста.

Нативная форма

Проп name рендерит input[type="hidden"] со значением; при range — два инпута с одним именем (lo, hi). Сериализуется модель (после снапа к step), а не черновик жеста при lazy.

Playground 13

Загружается…

Код
<GrSlider />

Установка

npm i @feugene/granularity

Импорт

import { GrSlider } from '@feugene/granularity/components/GrSlider'

API

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalse
readonlyboolean | undefinedfalseТолько для чтения: значение видно, но не меняется.
invalidboolean | undefinedfalseВизуальное и ARIA-состояние ошибки.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`).
size"xs" | "sm" | "md" | "lg" | undefinedundefined
ariaLabelstring | undefinedundefined
namestring | undefinedundefinedИмя для нативной формы: hidden input на значение, при `range` — два с одним именем.
maxnumber | undefined100
orientation"horizontal" | "vertical" | undefined"horizontal"Ориентация дорожки. В вертикальной минимум внизу.
lazyboolean | undefinedfalseЭмитить `update:modelValue` только по завершении жеста: во время перетаскивания значение ведёт сам слайдер. Клавиатура коммитит сразу — нажатие клавиши дискретно, придерживать его нечего.
stepnumber | undefined1
minnumber | undefined0
rangeboolean | undefinedfalseДиапазон с двумя бегунками; модель — кортеж `[lo, hi]`.
marksGrSliderMarks | undefinedundefinedМетки делений: `{ [value]: label }` или массив значений.
showTooltipboolean | "hover" | "always" | undefinedfalseВсплывающее значение над бегунком: `true`/`'hover'` — при hover/drag/focus, `'always'` — всегда.
formatTooltip((value: number) => string) | undefinedundefinedФорматирование значения в tooltip. Оно же уходит в `aria-valuetext`.
modelValueобязательныйGrSliderModelValue`number` — одиночное значение; `[lo, hi]` — диапазон (при `range=true`).

Events

EventTypeОписание
update:modelValue[value: GrSliderModelValue]
change[value: GrSliderModelValue]
focus[event: FocusEvent]
blur[event: FocusEvent]

Methods / Expose

Methods / ExposeTypeОписание
focus() => void
blur() => void

Примеры 4

Одно значение с подсказкой

Базовый ползунок: v-model (число), диапазон min/max, всплывающее значение (show-tooltip + format-tooltip). Полная клавиатура: стрелки меняют на step, PageUp/PageDown — крупный шаг, Home/End — к границам.

Value: 40 — drag the thumb or use arrow keys, Home / End, PageUp / PageDown.

Basic
<script setup lang="ts">
import { ref } from 'vue'

import { GrSlider } from '@feugene/granularity'

const volume = ref(40)
</script>

<template>
  <div class="grid gap-4">
    <GrSlider
      v-model="volume"
      :min="0"
      :max="100"
      show-tooltip
      :format-tooltip="(v) => `${v}%`"
      aria-label="Volume"
    />

    <p class="text-sm text-[var(--gr-muted-fg)]">
      Value: <code>{{ volume }}</code> — drag the thumb or use arrow keys, Home / End, PageUp / PageDown.
    </p>
  </div>
</template>

Каждый бегунок — role="slider" с aria-valuemin/max/now, доступный с клавиатуры и для скринридеров.

Диапазон двумя бегунками

Режим range: модель — кортеж [lo, hi], два бегунка, которые не перепрыгивают друг друга. Клик по дорожке двигает ближайший бегунок.

$200
$700

From $200 to $700 — two thumbs that never cross.

Range
<script setup lang="ts">
import { ref } from 'vue'

import { GrSlider } from '@feugene/granularity'

const price = ref<[number, number]>([200, 700])
</script>

<template>
  <div class="grid gap-4">
    <GrSlider
      v-model="price"
      range
      :min="0"
      :max="1000"
      :step="50"
      show-tooltip="always"
      :format-tooltip="(v) => `$${v}`"
      aria-label="Price range"
    />

    <p class="text-sm text-[var(--gr-muted-fg)]">
      From <code>${{ price[0] }}</code> to <code>${{ price[1] }}</code> — two thumbs that never cross.
    </p>
  </div>
</template>

Для диапазона у нижнего бегунка aria-valuemax = значение верхнего, а у верхнего aria-valuemin = значение нижнего — скринридер объявляет корректные границы.

Метки, шаг, размеры и выключенное состояние

Метки делений (marks), фиксированный step, размеры (sm/md/lg) и disabled-состояние.

Marks
<script setup lang="ts">
import { ref } from 'vue'

import { GrSlider } from '@feugene/granularity'

const quality = ref(50)

const marks = {
  0: 'Low',
  25: 'Fair',
  50: 'Good',
  75: 'High',
  100: 'Max',
}
</script>

<template>
  <div class="grid gap-8">
    <GrSlider
      v-model="quality"
      :min="0"
      :max="100"
      :step="25"
      :marks="marks"
      aria-label="Quality"
    />

    <div class="grid gap-6">
      <GrSlider :model-value="30" size="sm" aria-label="Small" />
      <GrSlider :model-value="60" size="lg" disabled aria-label="Large disabled" />
    </div>
  </div>
</template>

Свои цвета и размер через переменные CSS

Внешний вид настраивается CSS-переменными на самом слайдере (или любом предке) — без новых пропов: --gr-slider-fill (активная часть), --gr-slider-rail (фон дорожки), --gr-slider-thumb-bg / --gr-slider-thumb-border (заливка и окантовка бегунка), --gr-slider-thumb-size и --gr-slider-track-height (размеры). Незаданные переменные откатываются к дефолтам темы/размера.

Committed value: $1,200

Custom
<script setup lang="ts">
import { ref } from 'vue'

import { GrSlider } from '@feugene/granularity'

const brand = ref(65)
const accent = ref(40)
const large = ref(70)
const volume = ref(35)
const budget = ref(1200)
</script>

<template>
  <div class="grid gap-8">
    <!-- Свой цвет заливки + подложка дорожки. -->
    <GrSlider
      v-model="brand"
      aria-label="Brand color"
      show-tooltip
      :style="{
        '--gr-slider-fill': '#8b5cf6',
        '--gr-slider-rail': 'color-mix(in srgb, #8b5cf6 20%, transparent)',
      }"
    />

    <!-- Сплошной бегунок: заливка = цвет, окантовка = фон. -->
    <GrSlider
      v-model="accent"
      aria-label="Accent"
      :style="{
        '--gr-slider-fill': '#f97316',
        '--gr-slider-thumb-bg': '#f97316',
        '--gr-slider-thumb-border': 'var(--gr-bg)',
      }"
    />

    <!-- Крупнее бегунок и толще дорожка. -->
    <GrSlider
      v-model="large"
      aria-label="Large"
      :style="{
        '--gr-slider-fill': 'var(--gr-success)',
        '--gr-slider-thumb-size': '1.5rem',
        '--gr-slider-track-height': '0.75rem',
      }"
    />

    <div class="flex items-start gap-10">
      <!-- Вертикальная дорожка: минимум внизу, длина — через --gr-slider-length. -->
      <GrSlider
        v-model="volume"
        orientation="vertical"
        aria-label="Volume"
        show-tooltip
        :marks="{ 0: 'Mute', 50: 'Half', 100: 'Max' }"
        :style="{ '--gr-slider-length': '12rem' }"
      />

      <!-- lazy: значение уезжает наружу только на отпускании. -->
      <div class="grid flex-1 gap-2">
        <GrSlider
          v-model="budget"
          lazy
          :min="0"
          :max="5000"
          :step="50"
          aria-label="Monthly budget"
          show-tooltip
          :format-tooltip="(value) => `$${value.toLocaleString('en-US')}`"
        />
        <div class="text-sm text-[var(--gr-muted-fg)]">
          Committed value: <span class="font-medium text-[var(--gr-fg)]">${{ budget.toLocaleString('en-US') }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

Переменные наследуются, поэтому одну тему слайдеров можно задать на контейнере формы, а отдельные слайдеры точечно переопределить.

Доступность

Паттерн APG
slider
Клавиши
/ — шаг вниз, / — шаг вверх, PageUp/PageDown — крупный шаг, Home/End — к краям

Полный клавиатурный контракт пакета

Документация компонентаВсе компоненты