Rating и badges

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

  • реактивными (двусторонняя привязка значения)
  • доступными (ARIA-атрибуты, навигация с клавиатуры)
  • гибкими (кастомизация количества элементов, шага, иконок)
  • лёгкими для стилизации (Tailwind, CSS variables, scoped styles)

Базовая реализация рейтинга

Минимальный компонент рейтинга на Svelte:

<script>
  export let value = 0;
  export let max = 5;
  export let readonly = false;

  const handleClick = (index) => {
    if (!readonly) {
      value = index;
    }
  };
</script>

<div class="flex gap-1">
  {#each Array(max) as _, i}
    <button
      class="text-yellow-400"
      on:click={() => handleClick(i + 1)}
      aria-label={`Rate ${i + 1}`}
    >
      {i < value ? '★' : '☆'}
    </button>
  {/each}
</div>

Двусторонняя привязка

Svelte позволяет легко реализовать binding:

<Rating bind:value={rating} />

Это делает компонент пригодным для форм, отзывов и интерактивных интерфейсов.

Поддержка дробных значений

Для более точных оценок (например, 4.5) требуется дополнительная логика:

{#each Array(max) as _, i}
  <span>
    {#if i + 1 <= value}
      ★
    {:else if i < value}
      ⯨
    {:else}
      ☆
    {/if}
  </span>
{/each}

Здесь используется частично заполненная иконка.

Кастомизация через слоты

UI-библиотеки часто предоставляют возможность переопределить отображение:

<Rating let:item let:active>
  <MyStar {active} />
</Rating>

Это позволяет использовать SVG, анимации и любые визуальные элементы.

Доступность (Accessibility)

Ключевые аспекты:

  • role="slider" или role="radiogroup"
  • aria-valuenow, aria-valuemin, aria-valuemax
  • управление через клавиатуру (стрелки)

Пример:

<div
  role="slider"
  aria-valuenow={value}
  aria-valuemin="0"
  aria-valuemax={max}
  tabindex="0"
  on:keydown={(e) => {
    if (e.key === 'ArrowRight') value = Math.min(value + 1, max);
    if (e.key === 'ArrowLeft') value = Math.max(value - 1, 0);
  }}
>

Интеграция с формами

<input type="hidden" name="rating" value={value} />

Позволяет использовать компонент внутри <form> без дополнительной логики.


Компоненты бейджей (Badges)

Бейджи — компактные визуальные элементы для отображения статуса, количества или категории. Используются в списках, карточках, навигации и уведомлениях.

Базовый бейдж

<script>
  export let text = '';
</script>

<span class="px-2 py-1 text-xs rounded bg-gray-200">
  {text}
</span>

Варианты оформления

UI-библиотеки обычно предлагают набор предопределённых стилей:

<script>
  export let variant = 'default';

  const styles = {
    default: 'bg-gray-200 text-gray-800',
    success: 'bg-green-200 text-green-800',
    warning: 'bg-yellow-200 text-yellow-800',
    danger: 'bg-red-200 text-red-800'
  };
</script>

<span class={`px-2 py-1 rounded ${styles[variant]}`}>
  <slot />
</span>

Бейджи с числом (Counter badges)

Часто используются для уведомлений:

<span class="relative">
  <slot />
  <span class="absolute -top-2 -right-2 bg-red-500 text-white text-xs px-1 rounded-full">
    {count}
  </span>
</span>

Динамические бейджи

Реактивность Svelte позволяет легко менять содержимое:

$: statusColor = status === 'active' ? 'green' : 'gray';
<span class={`bg-${statusColor}-200`}>
  {status}
</span>

Иконки внутри бейджей

<span class="flex items-center gap-1 bg-blue-100 px-2 py-1 rounded">
  <Icon name="info" />
  Info
</span>

Бейджи как ссылки или кнопки

<a href="/notifications" class="badge">
  Notifications
</a>

или

<button class="badge" on:click={handleClick}>
  Clickable
</button>

Группировка бейджей

<div class="flex gap-2">
  <Badge>New</Badge>
  <Badge variant="success">Active</Badge>
  <Badge variant="danger">Error</Badge>
</div>

Архитектурные особенности в UI-библиотеках

Headless-подход

Многие Svelte UI-библиотеки (например, shadcn-svelte, skeleton, melt-ui) реализуют headless-компоненты:

  • логика отделена от представления
  • стили полностью контролируются разработчиком

Пример:

const rating = createRating();
<div use:rating.root>
  {#each rating.items as item}
    <button use:rating.item={item} />
  {/each}
</div>

Контролируемые и неконтролируемые компоненты

  • контролируемые: значение приходит извне (value)
  • неконтролируемые: состояние хранится внутри
export let value;
let internal = value ?? 0;

Темизация

Использование CSS variables:

.badge {
  background-color: var(--badge-bg);
}

Позволяет централизованно управлять стилями.


Производительность и оптимизация

Минимизация перерендеринга

Использование keyed each:

{#each items as item (item.id)}

Ленивая отрисовка

Рейтинг часто используется в списках:

  • избегать тяжёлых SVG
  • использовать простые символы или спрайты

Тестирование компонентов

Unit-тесты

Проверка:

  • корректного отображения значения
  • реакции на события
  • граничных значений

Accessibility тесты

  • проверка ARIA-атрибутов
  • навигация с клавиатуры
  • screen reader compatibility

Практические сценарии использования

Рейтинг товара

<Rating bind:value={product.rating} readonly />

Форма отзыва

<form>
  <Rating bind:value={rating} />
  <textarea bind:value={comment}></textarea>
</form>

Бейдж статуса

<Badge variant="success">Online</Badge>

Уведомления

<Badge>{notifications.length}</Badge>

Расширенные возможности

Анимации

<span class:scale-110={hovered}>

Tooltip внутри бейджей

<Tooltip content="New feature">
  <Badge>New</Badge>
</Tooltip>

Комбинация rating + badge

<div class="flex items-center gap-2">
  <Rating value={4.5} readonly />
  <Badge>Top Rated</Badge>
</div>

Типичные ошибки

  • отсутствие keyboard navigation
  • жёстко захардкоженные стили
  • отсутствие поддержки дробных значений
  • игнорирование SSR (важно для SvelteKit)
  • неправильная работа с реактивностью (value без $:)

SSR и SvelteKit

При использовании в SvelteKit:

  • избегать window в компоненте
  • учитывать гидрацию
  • использовать onMount только при необходимости

Интеграция с популярными UI-библиотеками

Skeleton

  • готовые стилизованные badges
  • headless rating через actions

Melt UI

  • мощные примитивы для создания кастомных rating-компонентов
  • акцент на accessibility

Shadcn-Svelte

  • адаптация популярных React UI-паттернов
  • готовые badge-компоненты с Tailwind

Расширение компонентов

Создание compound components

<Rating>
  <Rating.Item />
  <Rating.Item />
</Rating>

Использование контекста

setContext('rating', { value });
const { value } = getContext('rating');

Позволяет управлять состоянием без проп-дриллинга.


Такие компоненты формируют основу интерфейсных паттернов: компактное отображение информации (badges) и интуитивная оценка (rating). Их качественная реализация напрямую влияет на UX, особенно в интерфейсах с высокой плотностью данных.