Перейти к содержимому
Поиск
Разработка

Reka UI во Vue: удобные интерфейсы — мастерская и 9 живых примеров

Девять интерфейсов на настоящей Reka UI: карточка, поиск клиента, меню, вкладки, справка, фильтры, календарь и настройки. Клавиатура, состояния и исходники.

Александр Некрасов
Reka UI во Vue: удобные интерфейсы — мастерская и 9 живых примеров — обложка

Reka UI: собрать удобный интерфейс из готового поведения

В кабинете пользователя и административной панели постоянно повторяются одни и те же действия: открыть карточку, найти клиента, выбрать пункт меню, переключить вкладку, изменить настройки и указать дату. Нарисовать прямоугольник с красивой тенью несложно. Гораздо больше работы скрывается за ним: куда попадает фокус, как закрыть окно клавишей Escape, какой пункт выберет Enter и что услышит человек, использующий вспомогательные технологии.

Reka UI предоставляет низкоуровневые компоненты для Vue, в которых уже собраны многие такие связи и правила взаимодействия. Оформление остаётся вашим: можно встроить компоненты в собственную дизайн-систему, постепенно заменить проблемные элементы старого интерфейса или собрать небольшой сервис без готовой темы, диктующей весь внешний вид.

В статье девять практических экранов: редактирование контакта, поиск клиента, меню заявки, вкладки карточки, справка, переключатели, фильтр списка, календарь и общая панель настроек. Каждый пример выполняется настоящей библиотекой прямо на странице. Можно проверить клавиатуру, поменять данные и скачать полный проект с исходниками, тестами и командами Windows/Linux.

Где применять: CRM, кабинет, поддержка и внутренняя панель

Начните с конкретной задачи пользователя. Менеджер хочет быстро сменить контактное лицо, не уходя из карточки клиента. Оператор ищет организацию по названию и городу, а не перебирает длинный select. Руководитель открывает фильтр списка и применяет несколько условий сразу. Сотрудник поддержки раскрывает ответ на вопрос, сохраняя рядом форму, которую заполнял.

В таких ситуациях компоненты дают строительные блоки, но полезность определяет сценарий. Меню должно содержать действия именно выбранной записи. Вкладки должны разделять информацию одной сущности. Переключатель должен ясно отвечать, что включается, а сохранение — объяснять, осталось ли изменение локальным или уже принято сервером. Эти договорённости важнее количества декоративных эффектов.

Reka удобно внедрять постепенно: сначала исправить один диалог или combobox, затем вынести повторяющуюся обвязку в собственный компонент. Не требуется сразу переписывать все страницы приложения. Для команды полезно сохранить рабочий пример с состояниями «пусто», «ошибка» и «изменено», чтобы последующие экраны наследовали понятное поведение, а не только удачный скриншот.

Что даёт библиотека и что остаётся приложению

Reka UI — продолжение Radix Vue под новым названием. Компоненты предоставляются без готового визуального оформления: вы выбираете CSS, отступы, цвета и композицию. В нашем проекте Bootstrap 5 используется для базовых полей и кнопок, а Reka управляет поведением сложных элементов. Bootstrap JavaScript для этих же действий не подключается.

Библиотека помогает с ролями, состояниями, клавиатурой и фокусом. При этом названия элементов, тексты ошибок, контраст, размер активной области и последовательность действий остаются задачей разработчика. Наличие Reka в package.json не доказывает доступность всей страницы: неверная подпись или неудачная обвязка способны испортить поведение правильно написанного компонента.

Введение и устройство проекта описаны в официальном руководстве. Для оценки страницы дополнительно используйте раздел доступности. Серверную проверку прав, авторизацию, хранение данных и бизнес-правила Reka не реализует: компонент кнопки не решает, разрешено ли человеку выполнить операцию.

Версии, установка и воспроизводимая сборка

В проекте зафиксированы Reka UI 2.11.0, Vue 3.5.45, Bootstrap 5.3.9 и @internationalized/date 3.12.4. Сборка использует Vite 8.3.8. Версии сверены 13 ноября 2026 года. Для повторения команд нужен Node.js 24 LTS не ниже 24.15; установка серверного фреймворка не требуется.

Пакет Reka импортируется по именам компонентов. Мы не подключаем сторонний CDN: готовая сборка содержит нужный JavaScript, CSS и исходники для просмотра на собственном сайте. package-lock.json сохраняет выбранное дерево зависимостей. Поэтому в скачанном проекте используйте npm ci; npm update относится к отдельной задаче обновления с последующей проверкой поведения.

Папка public уже готова для HTTP-сервера. Если хотите только посмотреть результат, достаточно установленного Node и npm start. Для изменения Vue нужны установка зависимостей и сборка. Двойной щелчок по index.html через file:// не заменяет сервер: модули и JSON-исходники должны загружаться по HTTP.

npm install --save-exact reka-ui@2.11.0 vue@3.5.45
npm install --save-exact @internationalized/date@3.12.4 bootstrap@5.3.9
# В скачанном архиве версии уже закреплены:
npm ci --ignore-scripts
npm run build
npm test
npm start

Мини-мастерская: девять экранов на настоящих компонентах

Верхний список переключает практические экраны. Под каждым заголовком есть описание ситуации применения и короткое задание: очистить имя, найти клиента, применить фильтр или проверить сохранение. Здесь Vue и Reka исполняются в браузере, а состояние меняется от ваших действий.

Все имена, заявки и суммы учебные. В первых восьми примерах изменения существуют в памяти вкладки. Девятый экран позволяет явно сохранить настройки в localStorage этого браузера и восстановить их после перезагрузки. В интерфейсе это написано отдельно: сохранение не создаёт серверный аккаунт, не отправляет уведомление и не меняет данные настоящей CRM.

Проверьте один экран мышью, затем повторите путь клавиатурой. Под мастерской можно скачать состояние JSON, выбрать исходный файл, скопировать его или получить ZIP всего проекта. Встроенный iframe ограничивает пространство всплывающих панелей; для длительного изучения и проверки клавиатуры удобна отдельная полноэкранная вкладка.

Анатомия компонента: Root, Trigger, Content и Portal

В Reka сложный элемент обычно собирается из нескольких частей. Root связывает состояние и дочерние элементы. Trigger открывает содержимое. Content содержит интерактивную область. Portal переносит её в другое место DOM, чтобы всплывающая панель не обрезалась родителем с overflow. У диалога дополнительно есть Overlay, Title, Description и Close.

Не следует удалять части только потому, что картинка выглядит так же без них. Например, заголовок связывает смысл окна с его доступным именем, а Trigger участвует в возврате фокуса. Для изменения разметки сначала проверьте документацию конкретного примитива. Иногда подходит asChild, позволяющий использовать ваш элемент, но он требует корректной передачи атрибутов и событий.

Portal не выводит содержимое за пределы iframe: он работает внутри документа мастерской. Поэтому на узком экране мы ограничиваем ширину и высоту всплывающих областей, разрешаем внутреннюю прокрутку и оставляем закрытие доступным. Для рабочего приложения ограничения проверяются в реальном месте встраивания, а не только в отдельном каталоге компонентов.

<DialogRoot v-model:open="dialogOpen">
  <DialogTrigger>Редактировать контакт</DialogTrigger>
  <DialogPortal>
    <DialogOverlay class="dialog-overlay" />
    <DialogContent class="dialog-content">
      <DialogTitle>Контактное лицо</DialogTitle>
      <DialogDescription>Измените учебное имя.</DialogDescription>
      <!-- Здесь поля формы и сохранение -->
      <DialogClose>Отмена</DialogClose>
    </DialogContent>
  </DialogPortal>
</DialogRoot>

Экран 1. Диалог редактирования с отменой и валидацией

В карточке клиента показано контактное лицо. Нажатие «Редактировать контакт» открывает диалог и копирует текущее имя в отдельный черновик. Это принципиально: ввод в поле ещё не должен менять карточку за окном. Если человек нажал «Отмена» или Escape, исходное имя остаётся прежним.

При сохранении мы убираем крайние пробелы и проверяем длину от двух до восьмидесяти символов. Пустое имя не закрывает окно: рядом появляется конкретная ошибка, поле получает aria-invalid, а фокус возвращается в него. После корректного сохранения карточка обновляется и диалог закрывается. Человек видит результат там, откуда начал действие.

Шаблон подходит для небольшого изменения одной сущности. Если форма содержит много разделов, сложные зависимости и длительную загрузку, отдельная страница часто удобнее. Не превращайте диалог в узкий бесконечный экран только ради прежнего URL. Устройство окна и управление открытием описаны в Dialog reference.

async function saveProfile() {
  nameError.value = validateProfile(draftName.value);
  if (nameError.value) {
    await nextTick();
    nameField.value?.focus();
    return;
  }
  profile.value = draftName.value.trim();
  dialogOpen.value = false;
  notice.value = 'Карточка обновлена в памяти этой страницы.';
}

Черновик и сохранённое значение имеют разный смысл

Одна общая переменная для поля и карточки делает отмену неоднозначной: человек нажимает Cancel, а имя за окном уже изменилось. Отдельный draft устраняет этот эффект. При открытии он заполняется текущим значением, при успешном подтверждении переносится в основное состояние, при отмене отбрасывается.

На сервере добавляется ещё один уровень: отправленный черновик и подтверждённый ответ. Пока запрос не завершился, интерфейс показывает сохранение и не обещает успех. При отказе сервера окно обычно остаётся открытым, чтобы человек не вводил всё заново. Ошибки полей связываются с контролами, общая ошибка отображается отдельно.

В статическом примере запрос не имитируется случайным таймером: сохранение честно означает локальное изменение. При подключении API определите политику повторного нажатия, отмены, конфликта версии и возврата к исходным данным. Полезно сначала проверить неудачный ответ, потому что именно он показывает, насколько предсказуемо работает форма и сохраняется ли введённая человеком работа.

watch(dialogOpen, open => {
  if (open) {
    draftName.value = profile.value;
    nameError.value = '';
  }
});

Экран 2. Combobox: удобный поиск и стабильный ID

В справочнике есть две организации со словом «Маяк», поэтому одного названия недостаточно. В выпадающем списке показан город, а выбранное значение хранится как clientId. Это помогает не перепутать похожие записи и не привязывать бизнес-логику к подписи, которую однажды могут переименовать.

Введите «Самара»: список оставит нужную организацию. После выбора карточка покажет название, город, контакт и ID. Запрос без совпадений приводит к явному сообщению, а не к пустому белому прямоугольнику. Поле ввода и окончательный выбранный ID имеют разный смысл: набор текста ещё не означает выбор существующего клиента.

В примере фильтрация выполняется на пяти локальных записях настоящим Combobox. Для большого серверного справочника нужны задержка перед запросом, отмена устаревших ответов, состояние загрузки и ограниченный размер страницы. Не скачивайте всю клиентскую базу ради поиска в браузере. Особенности выбора объектов и строк разобраны в документации Combobox.

<ComboboxRoot v-model="client">
  <ComboboxAnchor>
    <ComboboxInput id="client-search"
      :display-value="id => clients.find(c => c.id === id)?.name || ''" />
    <ComboboxTrigger aria-label="Показать клиентов">⌄</ComboboxTrigger>
  </ComboboxAnchor>
  <ComboboxContent>
    <ComboboxEmpty>Ничего не найдено.</ComboboxEmpty>
    <ComboboxItem v-for="c in clients" :value="c.id"
      :text-value="c.name + ' ' + c.city">
      {{ c.name }} · {{ c.city }}
    </ComboboxItem>
  </ComboboxContent>
</ComboboxRoot>

Экран 3. Меню действий относится к конкретной заявке

В меню заявки находятся три осмысленных операции: взять в работу, отметить готовой и создать учебную копию. Заголовок меню содержит ID, чтобы сохранялась связь с выбранной записью. Уже применённый статус делает соответствующий пункт недоступным, а после действия карточка и сообщение результата обновляются вместе.

Копирование создаёт новую заявку с отдельным ID, исходная запись сохраняется. Пользователь сразу оказывается на копии и видит её в селекторе. Это полезнее меню, пункты которого только вызывают alert: здесь можно наблюдать изменение состояния и повторно работать с результатом. Операции выполняются в памяти страницы.

В реальном сервисе пункты зависят от статуса и роли сотрудника. Скрытый или disabled пункт помогает объяснить интерфейс, но сервер всё равно проверяет разрешение операции. Для удаления требуется отдельный продуманный сценарий; в мастерской такого действия нет. Клавиатурное поведение и построение меню описаны в Dropdown Menu.

function duplicate() {
  const id = 'R-' + (101 + requests.value.length);
  const item = { ...request.value, id,
    title: request.value.title + ' · копия', status: 'new' };
  requests.value.push(item);
  activeRequest.value = id;
  log('Создана учебная копия ' + id);
}

Экран 4. Вкладки карточки с общими данными

Обзор, задачи и история относятся к одному проекту. На вкладке задач можно завершить или возобновить пункт. Счётчик незавершённых задач меняется в заголовке вкладки, обзор показывает общий прогресс, а история хранит сообщение о действии. Это один набор данных с несколькими представлениями, а не три независимых декоративных блока.

Проверьте стрелки после перевода фокуса на заголовок вкладки. Семантика tablist, tab и tabpanel создаётся компонентами. Содержание скрытой вкладки не должно мешать обычной последовательности Tab. Если вкладка делает дорогой сетевой запрос, осознанно выбирайте режим активации и способ сохранить уже загруженные данные.

Не используйте tabs как замену навигации между несвязанными страницами. Когда раздел должен иметь собственную ссылку, историю браузера и возможность открыть его отдельно, нужен маршрут приложения. В нашей карточке вкладки удобны потому, что сохраняют общую сущность и короткий контекст. Варианты активации и ориентации перечислены в Tabs reference.

<TabsRoot v-model="tab">
  <TabsList aria-label="Разделы карточки клиента">
    <TabsTrigger value="overview">Обзор</TabsTrigger>
    <TabsTrigger value="tasks">Задачи</TabsTrigger>
    <TabsTrigger value="history">История</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">...</TabsContent>
  <TabsContent value="tasks">...</TabsContent>
  <TabsContent value="history">...</TabsContent>
</TabsRoot>

Экран 5. Accordion: справка, которую можно сравнивать

Справка отвечает на вопросы рядом с примерами: где сохраняются изменения, как пользоваться клавиатурой и как перенести код в свой сервис. Включён режим multiple, поэтому можно открыть несколько ответов одновременно. Это удобно, когда человек сравнивает условия и не хочет, чтобы первый текст исчезал при чтении второго.

Вопрос оформлен настоящим триггером внутри заголовка. Клик по всей строке раскрывает ответ; состояние не зависит от маленького значка плюс. Значок меняет положение как дополнительная подсказка, а основная связь определяется состоянием компонента. Текст остаётся обычным HTML и доступен для выделения и копирования.

Accordion сокращает визуальную длину страницы, но не должен прятать обязательные условия или ошибку, без которой невозможно продолжить. Если информация нужна всем до отправки формы, лучше показать её сразу. Компонент применяйте для дополнительной справки и связанных подробностей. Различие single и multiple объясняет руководство Accordion.

<AccordionRoot type="multiple" v-model="questions">
  <AccordionItem value="one">
    <AccordionHeader>
      <AccordionTrigger>Где сохраняются изменения?</AccordionTrigger>
    </AccordionHeader>
    <AccordionContent>В этом примере — локально.</AccordionContent>
  </AccordionItem>
</AccordionRoot>

Экран 6. Switch: включение должно иметь понятный результат

Переключатели управляют письмами о заявках, push и еженедельной сводкой. Рядом с каждым каналом явно написано «Включено» или «Выключено», а ниже составлено итоговое правило. Человек проверяет результат без интерпретации одного только цвета. Когда выключены все каналы, итог также сформулирован словами.

Label связан с конкретным SwitchRoot по ID. Это расширяет понятную область взаимодействия и даёт элементу доступное имя. Переведите фокус на переключатель и нажмите Space: меняется состояние той же модели Vue, что и при клике. В актуальном API используется v-model; не переносите без проверки старые примеры с другими именами свойств.

В рабочем кабинете заранее решите, сохраняется ли переключение немедленно или общей кнопкой. Смешение двух вариантов на одном экране вызывает ошибки ожидания. В самостоятельном примере изменение действует сразу в памяти, а в девятом экране входит в сохраняемый черновик. Поведение компонента описано в Switch reference.

<label for="notify-email">Письма о заявках</label>
<SwitchRoot id="notify-email" v-model="notifications.email" class="switch">
  <SwitchThumb class="switch-thumb" />
</SwitchRoot>

Экран 7. Popover с черновиком фильтра

Фильтр списка позволяет выбрать статус и минимальную сумму. Открытие Popover копирует действующие условия в черновик. Пока человек меняет поля, список за панелью остаётся прежним. «Применить» фиксирует оба условия сразу, «Отмена» закрывает панель без изменения текущего результата.

После применения отображается число найденных заявок и текст действующего фильтра. Если записей нет, интерфейс объясняет, что можно уменьшить сумму или выбрать другой статус. Кнопка сброса возвращает все заявки. Такая связка полезна в CRM, финансовых списках и кабинетах, где несколько условий должны применяться как единое действие.

Панель ограничена доступной шириной и высотой, а Reka учитывает столкновение с краями экрана. Это не отменяет проверки длинных подписей и мобильной клавиатуры. Для большого количества полей лучше использовать отдельную боковую панель или страницу. Устройство позиционирования и закрытия можно посмотреть в Popover reference.

watch(filterOpen, open => {
  if (open) draftFilter.value = { ...filter.value };
});
const filtered = computed(() => filterRequests(requests.value, filter.value));
// Применение после проверки числа:
filter.value = { ...draftFilter.value, minimum: Number(draftFilter.value.minimum) };
filterOpen.value = false;

Экран 8. Календарный день без лишнего часового пояса

Для даты визита используется CalendarDate из @internationalized/date. Значение «16 ноября» означает календарный день, а не момент времени в UTC. Это помогает избежать ситуации, когда выбранная дата при преобразовании в другую часовую зону становится предыдущим днём. В результате показаны русская подпись и строка ISO.

Календарь локализован на русский, неделя начинается с понедельника. Суббота и воскресенье недоступны для выбора. Можно переходить между месяцами и двигаться по дням клавиатурой. Ограничение выходных учебное: настоящее бронирование также проверяет занятость и правила на сервере, потому что два пользователя могут выбрать один слот одновременно.

На момент подготовки статьи Calendar в документации Reka помечен Alpha. Для рабочего проекта закрепляйте версию и повторяйте проверку после обновления. Различие календарной даты, времени и даты с зоной разобрано в документации CalendarDate.

import { CalendarDate, getDayOfWeek } from '@internationalized/date';
const visit = ref(new CalendarDate(2026, 11, 16));
const isUnavailable = date => [0, 6].includes(getDayOfWeek(date, 'en-US'));
const isoDate = visit.value.toString(); // 2026-11-16

Экран 9. Общая панель: изменения, отмена и сохранение

Последний экран объединяет название пространства, плотность списка и три канала уведомлений. Предпросмотр меняет заголовок и отступы строк, поэтому настройка плотности имеет заметный результат. Под формой показано, есть ли отличия от сохранённой версии. Человек понимает, нужно ли ещё нажимать кнопку сохранения.

«Сохранить настройки» проверяет название и записывает только разрешённые поля в localStorage. «Отменить изменения» возвращает сохранённый объект. «Вернуть учебные значения» очищает собственный ключ мастерской и восстанавливает исходный набор. Другие данные браузера при этом не затрагиваются. После перезагрузки сохранённый набор восстанавливается, если хранилище доступно.

Это клиентский сценарий настроек, а не серверный кабинет. В продукте сохранение потребует API, обработки ошибок и конфликта версий, а часть предпочтений может принадлежать человеку, организации или устройству. Разделите эти области заранее: плотность списка одного сотрудника не должна неожиданно менять экран всей команды.

function saveSettings() {
  settingsError.value = '';
  if (settings.value.workspace.trim().length < 2 ||
      settings.value.workspace.trim().length > 60) {
    settingsError.value = 'Название должно содержать от 2 до 60 символов.';
    return;
  }
  settings.value = sanitizeSettings(settings.value);
  try {
    localStorage.setItem(STORE, JSON.stringify(settings.value));
    saved.value = { ...settings.value };
    settingsMessage.value = 'Сохранено в этом браузере. Это не серверный аккаунт.';
  } catch {
    settingsMessage.value = 'Браузер не разрешил сохранение. Изменения остались в памяти страницы.';
  }
}

localStorage: восстановление не означает доверие данным

Сохранённый JSON может быть повреждён, создан старой версией интерфейса или изменён вручную. Поэтому при чтении недостаточно успешно выполнить JSON.parse. Функция sanitizeSettings выбирает только известные поля, проверяет типы и допустимые значения, а для остальных использует учебные значения по умолчанию.

Чтение и запись окружены try/catch: браузер способен ограничить доступ к хранилищу. Тогда интерфейс остаётся рабочим в памяти и объясняет, что постоянного сохранения не произошло. Это лучше, чем вывести зелёное «Готово» после неудачной записи или полностью сломать страницу из-за одного повреждённого ключа.

В мастерской нет токенов, паролей и персональных данных. Не превращайте пример настроек в рекомендацию хранить секреты в localStorage. Для серверного приложения отдельно определяются способ аутентификации, срок сессии и защита от XSS. Локальный объект с предпочтениями интерфейса не должен давать пользователю права, которых нет в серверной модели.

export function sanitizeSettings(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return defaults();
  const d = defaults();
  return {
    workspace: typeof value.workspace === 'string' &&
      value.workspace.trim().length >= 2 && value.workspace.trim().length <= 60
      ? value.workspace.trim() : d.workspace,
    email: typeof value.email === 'boolean' ? value.email : d.email,
    push: typeof value.push === 'boolean' ? value.push : d.push,
    digest: typeof value.digest === 'boolean' ? value.digest : d.digest,
    density: ['comfortable', 'compact'].includes(value.density) ? value.density : d.density
  };
}

Клавиатура и фокус: проверить путь целиком

Проверка начинается до открытия компонента. Пользователь должен добраться до кнопки обычным Tab и увидеть, где находится фокус. После открытия диалога фокус переходит внутрь, Escape закрывает окно, а дальнейший путь не должен теряться в скрытой области. Ошибка формы переводит внимание к полю, которое можно исправить.

В combobox и меню используйте стрелки и Enter. Во вкладках проверьте перемещение между заголовками и доступность активной панели. В переключателях убедитесь, что Space меняет именно выбранный канал. В календаре проверьте переход через границу недели и месяца. Библиотека предоставляет поведение, но итог зависит от добавленных вами обработчиков и DOM-структуры.

Мы оставляем заметную рамку focus-visible и не убираем outline ради чистого скриншота. Проверка JSDOM подтверждает часть событий и атрибутов, однако не заменяет браузер и экранный диктор. Для широкой аудитории проведите отдельную проверку с используемыми вспомогательными технологиями и реальными задачами.

button:focus-visible,
input:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #bd7b21;
  outline-offset: 3px;
}

Мобильный экран: переносы и всплывающие области

На телефоне элементы перестраиваются, а не просто уменьшаются. Кнопки допускают перенос текста, список заявок становится одноколоночным, действия задачи располагаются под названием. У диалога задана ширина относительно viewport и максимальная высота с внутренней прокруткой. Popover ограничен оставшимся пространством и не рассчитывает на бесконечную ширину родителя.

Календарь требует особого внимания: семь столбцов должны оставаться различимыми даже в узком iframe. На маленькой ширине размер ячеек сокращается, но цифры и выбранное состояние остаются читаемыми. Если сценарий предполагает много календарей или длинные пояснения, лучше дать пользователю отдельный экран.

Проверяйте не только первый экран. Открытая ошибка, меню у правого края, длинное название клиента и сохранённое сообщение меняют геометрию. После встраивания в WordPress мастерская получает меньшую ширину, чем окно браузера, поэтому проверка вложенного интерфейса обязательна. На странице оставлена ссылка для открытия мастерской целиком.

.dialog-content {
  width: calc(100vw - 32px);
  max-width: 510px;
  max-height: calc(100dvh - 32px);
  overflow: auto;
}
.filter-popover {
  width: 330px;
  max-width: calc(100vw - 24px);
  max-height: var(--reka-popover-content-available-height);
  overflow: auto;
}

Оформление по data-state и осторожность с анимацией

Reka отмечает состояния компонентов data-атрибутами. Можно оформить открытое меню, активную вкладку, выбранный день и включённый переключатель без отдельного дублирующего состояния CSS. Например, SwitchRoot получает checked, а положение ползунка следует за этим атрибутом. Модель Vue остаётся единственным источником значения.

Не используйте цвет как единственное объяснение. Рядом с переключателем есть текст, у календаря — подпись выбранного дня, у фильтра — действующие условия. Disabled должно отличаться от обычного состояния, но не превращать полезную подпись в нечитаемую. Контраст оценивается для реальных цветов и размеров вашего оформления.

Анимации в мастерской короткие и относятся к появлению панели или перемещению переключателя. При prefers-reduced-motion они отключаются. Движение помогает понять изменение, но не должно задерживать действие или вызывать дискомфорт. Избегайте постоянного подпрыгивания кнопок и декоративного движения внутри формы, где человек читает и вводит данные.

.switch[data-state="checked"] { background: #176b60; }
.switch[data-state="checked"] .switch-thumb {
  transform: translateX(24px);
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}

Как подключить настоящий API и сохранить понятное поведение

В рабочем сервисе начните с контракта: какие поля возвращает клиент, каким ID определяется запись и какие ошибки возможны. Combobox должен хранить стабильный идентификатор, форма редактирования — отправлять проверенный черновик, фильтр — преобразовывать условия в параметры запроса. Структуру ответа стоит отделить от декоративной разметки.

Для каждого запроса выделите загрузку, успешный результат, пустой набор и ошибку. Повторная отправка не должна незаметно создавать дубликаты. Если ответы приходят в другом порядке, старый ответ не должен перезаписывать новый выбор. Конфликт версии объясняют отдельным сообщением, сохраняя введённые человеком данные для сравнения.

Сервер проверяет права и входящие значения независимо от UI. Hidden, disabled и условный v-if не являются защитой API. На сайте есть практические материалы про TanStack Query во Vue и TanStack Form. Они дополняют низкоуровневые компоненты управлением серверными данными и сложными формами.

Windows: повторить проект и изменить первый компонент

Распакуйте исходники в отдельный каталог и откройте в нём PowerShell. Проверьте Node и npm, установите зависимости из lock-файла, соберите public и запустите тесты. Команда npm start поднимает статический сервер только на 127.0.0.1:8520. Он не открывает доступ к вашим файлам за пределами public.

После первого запуска измените подпись действия в src/App.vue, повторите npm run build и обновите страницу. Так видно различие между исходником и готовой сборкой: браузер получает файлы из public, а не Vue-компонент непосредственно из src. Старый открытый экран не меняется от сохранения файла без отдельного dev server.

Если PowerShell блокирует npm.ps1 локальной политикой, используйте npm.cmd, не меняя глобальные настройки ОС. Если порт занят, остановите свой предыдущий стенд либо задайте другой PORT. Завершение сервера — Ctrl+C в том же терминале. Не останавливайте все процессы Node: на компьютере могут работать другие проекты.

node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd start
# Открыть http://127.0.0.1:8520
# Остановить: Ctrl+C

# При необходимости другого порта:
$env:PORT = '8527'
npm.cmd start

Linux: те же исходники и просмотр через SSH

На Linux используется тот же package-lock и тот же набор команд. Подготовьте Node.js 24 LTS принятым способом, выполните npm ci, сборку и тесты в обычной учётной записи. Сборщик не должен запускаться от root только потому, что проект расположен на сервере. Выделите пользователю каталог с понятными правами.

Встроенный сервер слушает loopback. Для проверки на удалённом VPS можно открыть SSH-туннель со своего компьютера и пользоваться локальным адресом браузера. Это позволяет посмотреть результат без изменения firewall и без публикации временного порта всему Интернету. Сам SSH-доступ настраивается отдельно в инфраструктуре пользователя.

Для постоянного сайта Node-сервер не нужен: после сборки достаточно статической выдачи Nginx или Caddy. Разделяйте учебный просмотр и развёртывание. На VPS важно проверить владельца файлов, доступ веб-сервера к каталогу, MIME JavaScript и HTTPS, а не только успешное завершение npm run build.

node --version
npm ci --ignore-scripts
npm run build
npm test
npm start

# На своём компьютере, в отдельном терминале:
ssh -L 8520:127.0.0.1:8520 user@your-server

VPS: статическая выдача, HTTPS и обновление сборки

Переносите public целиком: index.html ссылается на JavaScript и CSS с хешами в имени, а мастерская отдельно запрашивает sources.json. В Vite установлен относительный base, поэтому сборка работает в подкаталоге. Если оставить только HTML или переименовать assets вручную, страница потеряет необходимые файлы.

В архиве есть пример server block Nginx. Он использует отдельный корень, стандартную таблицу MIME, try_files и разные сроки кеширования. Адаптируйте домен и HTTPS к действующей настройке сервера. Не копируйте тестовое имя как готовую production-конфигурацию. Перед reload выполните nginx -t, затем проверьте настоящий адрес браузером и curl.

Обновление удобнее выполнять через каталог релиза и переключение ссылки current. Предыдущая версия сохраняется для отката, а старые assets не исчезают мгновенно у открытых страниц. Для HTML и JSON-исходников нужна свежая версия; хешированные CSS/JS можно кешировать дольше. README показывает первую установку отдельно от последующих переключений.

server {
    listen 80;
    server_name reka.example.test;
    root /srv/reka-ui/current/public;
    index index.html;
    include /etc/nginx/mime.types;
    location / { try_files $uri $uri/ =404; }
    location /assets/ {
        try_files $uri =404;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
    location = /index.html { add_header Cache-Control "no-cache"; }
}

Проверки: правила данных и настоящая сборка компонентов

Проект содержит семь тестов модели: фильтры и границы суммы, отсутствие изменения исходного массива, проверка имени, нормализация сохранённых настроек, сравнение изменений, календарная подпись и уникальные ID девяти экранов. Они проверяют правила, которые можно случайно нарушить при замене учебных данных своими.

Отдельный тест загружает готовый Vue bundle в JSDOM и проходит девять интерфейсов настоящей Reka. Он открывает диалог, проверяет ошибку и сохранение, ищет клиента, выполняет действие меню, меняет задачу, раскрывает справку, переключает канал, применяет фильтр, проверяет недоступный выходной и сохраняет настройки. Это проверка связей приложения с библиотекой.

На Windows и в чистом Linux-контейнере прошли сборка и восемь тестов. JSDOM не доказывает качество геометрии, touch или работу экранного диктора. Поэтому браузерная проверка адаптивности и действий выполняется отдельно. После своих изменений повторите реальные сценарии, особенно закрытие всплывающих областей и восстановление фокуса.

npm run build
npm test
# Модель:
node --test tests/model.test.mjs
# Настоящие Vue/Reka-компоненты из собранного bundle:
node --test tests/ui.test.mjs

Размер сборки и постепенное внедрение

В проверенной сборке общий JavaScript занимает примерно 296 КБ без сжатия и 92 КБ gzip, CSS с Bootstrap — около 238 КБ и 33 КБ gzip. Это размер всей мастерской с девятью сценариями и календарём, а не обещание такого же размера у одного Dialog. Точные имена и размеры видны в результате npm run build.

Для одного экрана импортируйте нужные компоненты и проверяйте результат сборщика. Календарь добавляет зависимости для работы с датами; редко используемые разделы можно загружать отдельно. Но сначала измерьте проблему: искусственное дробление маленького приложения способно усложнить код сильнее, чем ускорить загрузку.

Если сайт уже использует Bootstrap, согласуйте токены, размер текста и focus-стили. Не подключайте второй управляющий JavaScript к той же кнопке диалога. Reka и Bootstrap CSS могут сосуществовать, когда каждый слой имеет ясную роль. В WordPress стили мастерской остаются внутри iframe и не меняют оформление остальных страниц.

Частые проблемы: окно, список, форма и сохранение

Если всплывающая область обрезается, проверьте Portal, overflow родителя и границы iframe. Если после закрытия пропадает фокус, убедитесь, что Trigger не удаляется условным рендерингом до завершения закрытия. Если подпись выбранного клиента выглядит как ID, задайте displayValue и храните справочник отображаемых имён.

Если изменение переключателя не отражается в модели, проверьте API установленной версии и v-model. Если фильтр меняет список до подтверждения, поля, вероятно, привязаны к действующему объекту вместо черновика. Если настройки исчезли после перезагрузки, проверьте обработку ошибки localStorage и правильность ключа.

Если календарь смещает день, посмотрите, не преобразовали ли календарную дату в момент времени без решения о зоне. Если после deploy пропала мастерская, проверьте ответ JavaScript-файла: сервер иногда отдаёт HTML страницы ошибки с кодом 200. Начинайте с конкретного сценария, текста ошибки и сетевого ответа; переустановка всех зависимостей редко объясняет причину.

Исходники и следующий шаг для своего интерфейса

Скачайте проект и выберите экран, похожий на задачу вашего продукта. Сначала замените учебные подписи и данные небольшим обезличенным набором. Затем проверьте ошибку, пустой результат, отмену и сохранение. Когда сценарий стал понятным, подключайте API и добавляйте серверную проверку прав и значений.

Авторский код доступен по MIT. В архиве есть App.vue, модель данных, стили, исходники сборки, тесты, README с Windows/Linux и пример Nginx. Уведомления зависимостей собраны в THIRD-PARTY-LICENSES.txt. Просмотр исходников в мастерской намеренно открыт; не добавляйте в такой публичный набор серверные ключи или закрытые данные проекта.

Практический результат — рабочие приёмы, которые можно переносить между экранами: отделять черновик, сохранять ID, объяснять ошибку, возвращать фокус и показывать результат действия. Продолжайте по документации Reka UI, сопоставляя компонент с пользовательской задачей. Именно это превращает библиотеку элементов в удобный сервис.