GrRating

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

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

Когда брать

  • оценка ставится в один клик — отзыв, качество поддержки, сложность задачи;
  • оценка дробнаяallowHalf для половинок;
  • рядом нужна расшифровкаtexts подписывает ступени словами: «Плохо», «Отлично»;
  • оценку показывают, а не ставятreadonly для средней оценки в карточке товара.

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

НужноБерите
Шкала числовая и широкаяGrSlider
Нужно точное числоGrNumberInput
Вариантов немного и они названыGrRadioGroup
Показать долю, а не оценкуGrProgressBar

Оценка словами

<GrRating v-model="score" :texts="['Ужасно', 'Плохо', 'Нормально', 'Хорошо', 'Отлично']" show-text />

texts — по одной подписи на деление. Подпись уходит и в видимый текст, и в aria-valuetext («4 из 5, хорошо»): в этом и смысл рейтинга — диктор читает оценку словами, а не голым числом. Дробное значение округляется вверх до своего деления, массив короче max оставляет верхние деления без подписи.

formatText сильнее texts для видимого текста — у потребителя может быть своя формулировка.

Предпросмотр

Наведение показывает оценку под курсором и эмитит hoverChange; уход со шкалы и потеря фокуса возвращают модель и шлют hoverChange(null).

Обработчик висит на самой шкале, а не на компоненте целиком: подпись лежит рядом со шкалой, и курсор, переведённый на неё, обязан гасить предпросмотр — иначе он залипает. В readonly и disabled предпросмотра нет вовсе.

С клавиатуры предпросмотра не существует: стрелки сразу фиксируют оценку, и hoverChange там не эмитится.

Компактный вид

<GrRating :model-value="3" readonly compact show-text />

compact рисует только заполненные символы — для таблиц и списков, где пять звёзд в каждой строке съедают ширину. Половинка считается символом: «2,5» рисуется тремя. Для диктора компактный режим ничем не отличается — роль и текстовая подпись те же.

Режимы и доступность

Интерактивная шкала реализует slider-паттерн: role="slider", aria-valuemin/max/now/valuetext, стрелки, Home/End. Режим только для чтения — role="img" с текстовой подписью, чтобы оценка читалась одной фразой, а не набором элементов.

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

Оформление

ТочкаЧто задаёт
sizexslg, читается из GrConfigProvider
toneцвет заливки из шкалы тонов
--gr-rating-colorцвет заливки точечно, сильнее tone
--gr-rating-void-colorцвет незаполненного символа
icon / слот #symbolсвой символ вместо встроенной звезды

Символ по умолчанию — инлайновый SVG: иконочные маски i-lucide-star дают только контур. Свой символ — Vue-компонент или класс иконки вашей UnoCSS-сборки (i-lucide-* требует вашего presetIcons, см. «Иконки»).

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

Проп name рендерит input[type="hidden"] со значением оценки. 0 — «не выбрано»: input не рендерится, как у неотмеченной радиокнопки.

Playground 14

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

Код
<GrRating />

Установка

npm i @feugene/granularity

Импорт

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

API

Props

PropTypeпо умолчаниюОписание
tone"primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined"warning"Тон заливки; точечно перекрывается переменной `--gr-rating-color`.
iconstring | Component | undefinedundefinedСимвол вместо встроенной звезды: Vue-компонент либо класс иконки вашей UnoCSS-сборки (`'i-lucide-heart'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).
disabledboolean | undefinedfalse
readonlyboolean | undefinedfalseТолько показ: без ввода и фокуса.
invalidboolean | undefinedfalseВизуальное и ARIA-состояние ошибки.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`).
size"xs" | "sm" | "md" | "lg" | undefinedundefined
ariaLabelstring | undefinedundefined
clearableboolean | undefinedfalseПовторный клик по текущей оценке сбрасывает её в `0`.
namestring | undefinedundefinedИмя для нативной формы: hidden input со значением; `0` — «не выбрано», input не рендерится.
maxnumber | undefined5Количество символов шкалы.
compactboolean | undefinedfalseКомпактный вид: рисуются только заполненные символы. Осмысленно вместе с `readonly` — в списках и таблицах пять звёзд в каждой строке съедают ширину.
allowHalfboolean | undefinedfalseПоловинчатые оценки: клик по левой половине символа даёт `.5`.
showTextboolean | undefinedfalseПоказывать числовую подпись справа от шкалы.
formatText((value: number) => string) | undefinedundefinedФормат подписи. По умолчанию — само значение. Сильнее `texts`.
textsstring[] | undefinedundefinedПодписи по делениям: «3 из 5, нормально» вместо «3 из 5». Уходят и в видимый текст, и в `aria-valuetext` — ради этого рейтинг и существует. Массив короче `max` оставляет верхние деления без подписи.
modelValueобязательныйnumberТекущая оценка. Дробная (`3.5`) поддерживается при `allowHalf`.

Slots

SlotTypeОписание
symbol{ index: number; filled: boolean; }Свой символ вместо звезды. `filled` — закрашенная половина символа.
text{ value: number; }Подпись рядом с оценкой.

Events

EventTypeОписание
update:modelValue[value: number]
change[value: number]
clear[]
focus[event: FocusEvent]
blur[event: FocusEvent]
hoverChange[value: number | null]

Methods / Expose

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

Примеры 3

Базовая оценка

Оценка в один клик: v-model — число, show-text печатает значение рядом. Шкала фокусируется и управляется стрелками, Home/End.

4

Score: 4 — click a star, or use arrow keys, Home / End.

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

import { GrRating } from '@feugene/granularity'

const score = ref(4)
</script>

<template>
  <div class="grid gap-4">
    <GrRating v-model="score" show-text aria-label="Rate the delivery" />

    <p class="text-sm text-[var(--gr-muted-fg)]">
      Score: <code>{{ score }}</code> — click a star, or use arrow keys, Home / End.
    </p>
  </div>
</template>

Шкала — role="slider" с aria-valuenow/aria-valuetext, поэтому скринридер объявляет «4 из 5», а не пять безымянных иконок.

Половинки, сброс и только чтение

allow-half даёт половинчатые оценки (клик по левой половине символа), clearable сбрасывает повторным кликом, readonly показывает чужие оценки без ввода.

Your rating
3.5 / 5
  • Anna K.

    Arrived a day earlier than promised.

  • Mark T.

    Good quality, packaging could be better.

  • Elena P.

    Exactly as described.

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

import { GrRating } from '@feugene/granularity'

const myScore = ref(3.5)

const reviews = [
  { author: 'Anna K.', score: 5, text: 'Arrived a day earlier than promised.' },
  { author: 'Mark T.', score: 3.5, text: 'Good quality, packaging could be better.' },
  { author: 'Elena P.', score: 4, text: 'Exactly as described.' },
]
</script>

<template>
  <div class="grid gap-6">
    <div class="grid gap-2">
      <span class="text-sm font-medium">Your rating</span>
      <GrRating
        v-model="myScore"
        allow-half
        clearable
        show-text
        :format-text="(v) => (v ? `${v} / 5` : 'Not rated')"
        aria-label="Your rating"
      />
    </div>

    <ul class="grid gap-3">
      <li v-for="review in reviews" :key="review.author" class="grid gap-1">
        <div class="flex items-center gap-2">
          <GrRating :model-value="review.score" readonly allow-half size="sm" />
          <span class="text-sm font-medium">{{ review.author }}</span>
        </div>
        <p class="text-sm text-[var(--gr-muted-fg)]">
          {{ review.text }}
        </p>
      </li>
    </ul>
  </div>
</template>

В режиме readonly шкала становится role="img" с оценкой в подписи — она не попадает в таб-порядок и не притворяется контролом.

Свой символ, тон и размер

Символ меняется пропом icon (любая UnoCSS-иконка) или слотом #symbol, цвет — тоном либо переменной --gr-rating-color, размер — size или --gr-rating-symbol-size.

Custom symbol and tone
Own colour via CSS variable
Labels per step
Хорошо
Compact read-only (for tables and lists)
3
4.5
Sizes and disabled

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

import { GrRating } from '@feugene/granularity'

const likes = ref(3)
const difficulty = ref(2)
const size = ref(4)

// Подписи по делениям: диктор читает «4 из 5, хорошо», а не голое число.
const service = ref(4)
const serviceTexts = ['Ужасно', 'Плохо', 'Нормально', 'Хорошо', 'Отлично']
</script>

<template>
  <div class="grid gap-6">
    <div class="grid gap-2">
      <span class="text-sm font-medium">Custom symbol and tone</span>
      <GrRating
        v-model="likes"
        icon="i-lucide-heart"
        tone="danger"
        aria-label="How much you liked it"
      />
    </div>

    <div class="grid gap-2">
      <span class="text-sm font-medium">Own colour via CSS variable</span>
      <GrRating
        v-model="difficulty"
        :max="4"
        style="--gr-rating-color: var(--gr-info)"
        aria-label="Difficulty"
      />
    </div>

    <div class="grid gap-2">
      <span class="text-sm font-medium">Labels per step</span>
      <GrRating
        v-model="service"
        :texts="serviceTexts"
        show-text
        aria-label="Service quality"
      />
    </div>

    <div class="grid gap-2">
      <span class="text-sm font-medium">Compact read-only (for tables and lists)</span>
      <div class="flex items-center gap-6">
        <GrRating :model-value="3" readonly compact show-text aria-label="Compact rating" />
        <GrRating :model-value="4.5" readonly compact allow-half show-text aria-label="Compact half rating" />
      </div>
    </div>

    <div class="grid gap-2">
      <span class="text-sm font-medium">Sizes and disabled</span>
      <div class="flex items-center gap-6">
        <GrRating v-model="size" size="sm" aria-label="Small" />
        <GrRating v-model="size" size="md" aria-label="Medium" />
        <GrRating v-model="size" size="lg" aria-label="Large" />
        <GrRating :model-value="2" disabled aria-label="Disabled" />
      </div>
    </div>
  </div>
</template>

Доступность

Паттерн APG
slider (интерактивный) / img (readonly)
Клавиши
/ — больше, / — меньше, Home/End — к краям

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

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