Inputs
Ten Point Scale
Десятибалльная шкала пронумерованными клетками: на десяти делениях звёзды приходится пересчитывать глазами, а цифра называет оценку сразу.
- rating
- scale
- survey
- keyboard
Превью
Использовать с AI
- 1. Скопируйте ссылку.
- 2. Напишите агенту своими словами и вставьте её в предложение.
- 3. Агент откроет ссылку и поставит компонент из registry.
размести это в шапке: https://vibeui.ru/c/rating-003
Оцените сервис8 / 10
1 — совсем плохохорошо
Показать полную инструкцию
То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.
# Установка и размещение «rating-003» (Ten Point Scale) из VibeUI
## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/rating-003.json
Registry item: https://vibeui.ru/r/rating-003.json
Ставится в: components/vibeui/rating-003.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.
Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.
## 2. Что это
Десятибалльная шкала пронумерованными клетками: на десяти делениях звёзды приходится пересчитывать глазами, а цифра называет оценку сразу.
Шкала от одного до десяти пронумерованными клетками, накопительная заливка, стрелки двигают выбор внутри группы. Ноль зависимостей, один файл.
## 3. Как использовать
import { Rating003 } from "@/components/vibeui/rating-003"
<Rating003 label="Оцените сервис" max={10} defaultValue={8} />
Полный список пропсов — в установленном файле.
## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.
Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)
## 5. Сохранить как установлено
- локальную палитру --vibeui-rating-003-* и не подменять её токенами вашей темы
- собственную светлую подложку: шкалу показывают поверх любого фона
- цифры вместо значков: на десяти делениях звёзды приходится пересчитывать глазами
- roving tabindex: без него десять кнопок встают в табуляцию и до следующего поля формы десять нажатий Tab
- роли radiogroup и radio: кнопки без ролей скринридер прочитает как десять несвязанных команд
- накопительную заливку: длина полосы читается быстрее, чем позиция одной отметки
- aria-label с оценкой на каждой клетке: одна цифра без контекста ничего не значит
## 6. Можно менять
- label — вопрос над шкалой
- max — длина шкалы, семибалльная тоже работает
- defaultValue — предвыбранная оценка
- hints — слова под шкалой для каждой оценки
- акцент через проп accent
## 7. Правила
- Длина hints должна совпадать с max, иначе под крайними оценками окажется пустота.
- Шкала начинается с единицы: для NPS нужна другая, от нуля.
- Компонент неуправляемый снаружи: чтобы поднять оценку наверх, добавьте свой onChange.
- Не выносите CSS-переменные в globals.css: компонент обязан остаться одним файлом.
## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.Для разработчика
npx shadcn@latest add https://vibeui.ru/r/rating-003.jsonhttps://vibeui.ru/r/rating-003.jsonКомпонент самодостаточен: один файл, ноль зависимостей, палитра в локальных переменных --vibeui-rating-003-*. Клиентский: оценка в состоянии, есть обработка стрелок. Группа собрана на ролях radiogroup и radio с ручным roving tabindex: фокус входит в группу один раз, а стрелки двигают выбор внутри — десять кнопок в табуляции означали бы десять лишних нажатий Tab до следующего поля. Клетки до выбранной заливаются приглушённым акцентом через color-mix, поэтому оценка читается и как число, и как длина.
Исходник компонента
Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.
"use client"
import { useId, useState } from "react"
import type {
ComponentPropsWithoutRef,
CSSProperties,
KeyboardEvent,
} from "react"
export type Rating003Props = Omit<
ComponentPropsWithoutRef<"div">,
"children" | "defaultValue" | "onChange"
> & {
label?: string
max?: number
defaultValue?: number
hints?: string[]
accent?: string
}
// Идея компонента: десятибалльная шкала как ряд пронумерованных клеток. Звёзды
// на десяти делениях перестают считываться — приходится пересчитывать значки
// глазами, а цифра называет оценку сразу. Клетки до выбранной закрашиваются,
// поэтому оценка читается и как число, и как длина. Группа собрана на ролях
// radiogroup/radio с ручным roving tabindex: десять кнопок в табуляции — это
// десять лишних нажатий Tab, поэтому фокус в группу входит один раз, а стрелки
// двигают выбор внутри.
const STYLES = `
:where([data-vibeui-block="rating-003"]){
--vibeui-rating-003-surface:oklch(1 0 0);
--vibeui-rating-003-shell:oklch(0.9 0.006 265);
--vibeui-rating-003-fg:oklch(0.23 0.014 265);
--vibeui-rating-003-muted:oklch(0.55 0.014 265);
--vibeui-rating-003-border:oklch(0.9 0.006 265);
--vibeui-rating-003-empty:oklch(0.97 0.003 265);
--vibeui-rating-003-accent:oklch(0.55 0.17 265);
--vibeui-rating-003-on:oklch(1 0 0);
--vibeui-rating-003-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* Своя светлая подложка: шкалу показывают поверх любого фона. */
[data-vibeui-block="rating-003"]{
display:flex;flex-direction:column;gap:0.5rem;
width:100%;max-width:22rem;box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-rating-003-surface);
border:1px solid var(--vibeui-rating-003-shell);border-radius:0.875rem;
font-family:var(--vibeui-rating-003-font);color:var(--vibeui-rating-003-fg);
}
[data-vibeui-block="rating-003"] [data-part="head"]{
display:flex;align-items:baseline;justify-content:space-between;gap:0.75rem;margin:0;
}
[data-vibeui-block="rating-003"] [data-part="title"]{font-size:0.8125rem;font-weight:650}
[data-vibeui-block="rating-003"] [data-part="score"]{
font-size:0.8125rem;font-weight:700;color:var(--vibeui-rating-003-accent);
font-variant-numeric:tabular-nums;
}
[data-vibeui-block="rating-003"] [data-part="scale"]{display:flex;gap:0.1875rem}
[data-vibeui-block="rating-003"] button{
appearance:none;cursor:pointer;flex:1 1 0;min-width:0;
height:2.25rem;padding:0;
border:1px solid var(--vibeui-rating-003-border);border-radius:0.375rem;
background:var(--vibeui-rating-003-empty);color:var(--vibeui-rating-003-muted);
font:inherit;font-size:0.75rem;font-weight:700;font-variant-numeric:tabular-nums;
transition:background-color .12s ease,color .12s ease,border-color .12s ease;
}
/* Клетки до выбранной закрашены: оценка читается и числом, и длиной. */
[data-vibeui-block="rating-003"] button[data-filled="true"]{
background:color-mix(in oklch,var(--vibeui-rating-003-accent) 22%,white);
border-color:transparent;color:var(--vibeui-rating-003-fg);
}
[data-vibeui-block="rating-003"] button[aria-checked="true"]{
background:var(--vibeui-rating-003-accent);border-color:transparent;
color:var(--vibeui-rating-003-on);
}
[data-vibeui-block="rating-003"] button:focus-visible{outline:2px solid var(--vibeui-rating-003-accent);outline-offset:2px}
[data-vibeui-block="rating-003"] [data-part="foot"]{
display:flex;align-items:center;justify-content:space-between;gap:0.75rem;margin:0;
font-size:0.6875rem;color:var(--vibeui-rating-003-muted);
}
[data-vibeui-block="rating-003"] [data-part="hint"]{
font-size:0.75rem;font-weight:650;color:var(--vibeui-rating-003-fg);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="rating-003"] *{animation:none!important;transition:none!important}}
`
const DEFAULT_HINTS = [
"никогда",
"очень плохо",
"плохо",
"слабо",
"терпимо",
"средне",
"неплохо",
"хорошо",
"очень хорошо",
"отлично",
]
/**
* Десятибалльная шкала пронумерованными клетками с накопительной заливкой.
* Один файл, ноль зависимостей, собственная палитра.
*/
export function Rating003({
label = "Оцените сервис",
max = 10,
defaultValue = 8,
hints = DEFAULT_HINTS,
accent,
className,
style,
...props
}: Rating003Props) {
const id = useId()
const [value, setValue] = useState(defaultValue)
const points = Array.from({ length: max }, (_, index) => index + 1)
// Стрелки двигают выбор внутри группы: десять кнопок в табуляции — это
// десять лишних нажатий Tab до следующего поля формы.
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
const delta =
event.key === "ArrowRight" || event.key === "ArrowUp"
? 1
: event.key === "ArrowLeft" || event.key === "ArrowDown"
? -1
: 0
if (delta === 0) return
event.preventDefault()
setValue(Math.min(max, Math.max(1, value + delta)))
}
const palette = {
...(accent ? { "--vibeui-rating-003-accent": accent } : null),
...style,
} as CSSProperties
return (
<>
<style href="vibeui-rating-003" precedence="medium">
{STYLES}
</style>
<div
{...props}
data-vibeui-block="rating-003"
className={className}
style={palette}
>
<p data-part="head">
<span data-part="title" id={`${id}-label`}>
{label}
</span>
<span data-part="score" aria-live="polite">
{value} / {max}
</span>
</p>
<div
data-part="scale"
role="radiogroup"
aria-labelledby={`${id}-label`}
onKeyDown={onKeyDown}
>
{points.map((point) => (
<button
key={point}
type="button"
role="radio"
aria-checked={value === point}
aria-label={`${point} из ${max}`}
tabIndex={value === point ? 0 : -1}
data-filled={point < value}
onClick={() => setValue(point)}
>
{point}
</button>
))}
</div>
<p data-part="foot">
<span>1 — совсем плохо</span>
<span data-part="hint">{hints[value - 1]}</span>
</p>
</div>
</>
)
}