Учебное руководство
Главы руководства
На этой странице

UIKit на Mantine

packages/ui — независимый React-пакет @gheilt/ui. Сайт использует его реальные компоненты; Storybook показывает их изолированно с тем же UiProvider. Установка и API, почему используются CSS-слои.

Базовые компоненты

Button, ActionIcon, Anchor, Text, Title, Paper, Card, поля, селекты, Checkbox, Radio, Switch, Modal, Drawer, Alert, Tabs, Accordion, Table, Loader, Skeleton, Progress и Tooltip берутся непосредственно из Mantine. Тема задаёт общую палитру, шрифт, скругления и defaults. Нативные props (variant, size, loading, disabled, error) сохраняются, поэтому библиотека не вводит собственную копию их API. Notifications подключены один раз в UiProvider, управление — через notifications.show из @mantine/notifications.

Составные компоненты

КомпонентОтветственность
PageContainer / SectionШирина, заголовок, якорь и действие раздела
SiteHeader / SiteNavigation / SiteFooterСлоты шапки, адаптивное меню и подвал
Carousel / AwardCarousel / LogoCarouselГотовый Mantine/Embla механизм слайдов
HeroCarouselИзображение и текст большого слайда
ArticleCard / ArticleCollectionКарточки, featured desktop/mobile и обычная сетка
ScheduleДни, площадки, время и ссылки событий
InformationCard / RoutePanel / MapEmbedВыбор маршрута или размещения, карта
ImageText / FaqИллюстрированный текст и раскрываемые ответы

У UIKit нет доступа к CMS и нет импортов Next.js. Для роутера передаётся renderLink. Пример Next: renderLink={props => <Link {...props} />}. Маршруты передают содержимое как ReactNode; Strapi Blocks рендерит адаптер приложения. Группировка расписания находится в apps/web/src/lib/schedule.ts, использует Europe/Moscow и сохраняет все события. Извлечение карточек из Blocks — в apps/web/src/lib/information.ts. Для iframe разрешён только HTTPS yandex.ru/map-widget/v1/, остальные ссылки остаются обычными ссылками и не превращаются в embed.

Storybook

pnpm --filter @gheilt/ui storybook запускает каталог на http://localhost:6006. pnpm --filter @gheilt/ui build-storybook собирает статический каталог. Используются React Webpack 5 и SWC, отдельной Vite-сборки нет. Истории включают варианты кнопок, типографику, панели, поля с ошибками, модалки, уведомления, загрузку, таблицы, пустые карусели, одиночный слайд, расписание с пустым днём и длинный вопрос FAQ. Controls позволяют менять параметры, Autodocs показывает API. Проверяйте эти состояния на узком и широком экране. /ui-kit в Next — небольшой встроенный пример; полный каталог находится в Storybook.

Учебная документация в каталоге

Раздел «Обучение» находится первым в Storybook. Шесть MDX-страниц в packages/ui/src/docs объясняют устройство UIKit, подключение к React/Next, тему и CSS Modules, композицию и адаптеры ссылок, CSF/Controls/Docs, а также содержат практикум с критериями проверки. Живые примеры используют существующие stories. На Docs-странице каждого компонента есть назначение, пример применения, ключевые параметры и ограничения; у примитивов приведены ссылки на API Mantine.

Для новой учебной страницы добавьте .mdx в src/docs с Meta title, для описания компонента — parameters.docs.description.component в его CSF-файл. Пример подключения задаётся строкой в parameters.docs.usage: общий шаблон .storybook/component-docs.tsx выводит его через штатный блок Source, отдельно от текста и живых stories. Это обходит отсутствие fenced-кода в Markdown-описаниях текущего Storybook 10.6. На MDX-страницах обычные fenced-блоки работают. Нумерация заголовков и storySort в .storybook/preview.tsx задают порядок уроков. Текст в MDX проверяется статической сборкой Storybook: TypeScript сам по себе не проверяет кодовые блоки, приведённые как учебные примеры.

Данные и миграция

Site.programPoster — редактируемая медиаиллюстрация расписания в Strapi/Garage. Порядок FAQ хранится в поле order. Карточки маршрутов и размещения образуются из заголовков и абзацев соответствующих страниц; безопасная ссылка на карту внутри карточки используется для iframe. Меню берётся из Site.navigation.

infra/strapi/scripts/upgrade-presentation.mjs — узкое повторяемое обновление импортированного оформления. Запускается внутри подготовленного Strapi-контейнера с snapshot и report; --dry-run читает published/draft и проверяет конфликты без записи. В рабочем режиме обновляет только content страниц directions/placement, FAQ.order, Site.navigation и Site.programPoster/overviewTitle/overviewDescription/overviewImage. Новости и события, тексты FAQ и прочие настройки Site не переписывает. До первой записи сверяются все draft/published и исходное содержимое страниц. Любые неопубликованные изменения или редакторские правки страниц останавливают обновление; миграция не публикует их автоматически. Миграция не имеет общей транзакции: при ошибке изучите report и повторите после исправления причины. Не запускайте параллельно другой импорт.

Учебные упражнения

  1. Измените primaryColor через UiProvider: компоненты должны сохранить общий вид.
  2. Добавьте историю собственного состояния поля без обёртки над TextInput.
  3. Подключите Section и ArticleCollection к обычному React-приложению без Next/CMS.
  4. Добавьте площадку в расписание: UIKit не должен знать, откуда пришли данные.
  5. Проверьте длинные подписи и пустые списки в Storybook на ширине 390 px.

Обзор события и фильтр по дням

EventOverview показывает название, описание, квадратную фотографию и произвольное действие. Данные берутся из Site.overviewTitle, overviewDescription, overviewImage; UIKit ничего не знает о Strapi. TabbedArticleCollection принимает группы { id, label, items }, опциональный initialGroup и адаптер ссылок. На главной странице адаптер группирует подсобытия по московской дате и сохраняет ссылки /events/[slug]. Обзор фестиваля и его подсобытия — разные блоки.

Storybook на VPS

Каталог доступен на https://storybook.gheilt.mxsource.xyz. Статическая сборка упакована в отдельный образ infra/storybook/Dockerfile. Внутри контейнера Caddy раздаёт файлы на 8080, а основной Caddy обеспечивает публичный HTTPS и HTTP/3. Порт контейнера не публикуется на хосте; два Compose-проекта используют существующую сеть gheilt_default. Контейнер работает от UID 1000 с файловой системой только для чтения, без Linux capabilities. Индексация запрещена через robots.txt и X-Robots-Tag. Каталог публичный; секреты и реальные пользовательские данные в stories добавлять не следует.

Отдельный workflow .github/workflows/storybook.yaml собирает образ, публикует его в GHCR и запускает scripts/deploy/storybook.sh. Он обновляет только Storybook и перезагружает конфигурацию основного Caddy, сохраняя работающие образы остальных сервисов. На VPS образ закреплён digest в /opt/gheilt/storybook/current/release.env. Публикация запускается при изменении UIKit/статических ресурсов/конфигурации Storybook или вручную:

gh workflow run storybook.yaml --ref main

Деплой использует общий /opt/gheilt/deploy.lock, проверяет health контейнера и доступность публичного index.json. При ошибке восстанавливает конфигурацию Caddy и предыдущий образ каталога. На первом деплое добавляет блок поддомена в фактически подключённый Caddyfile; в репозитории этот блок тоже сохранён, поэтому очередной обычный деплой сайта его сохраняет. Основной Compose-проект нужно запускать первым: он создаёт общую сеть и основной прокси.

Основание: публикация статической сборки Storybook и перезагрузка Caddy.

Организация stories

У каждого базового компонента отдельный CSF-файл в src/stories с собственной component-метаинформацией и типом Meta<typeof Component>. Разделы Actions, Forms, Overlays, Typography, Surfaces, Feedback, Navigation и Data группируют компоненты по назначению; состояния находятся внутри соответствующего компонента. Default у Modal/Drawer связывает открытие и закрытие с args через useArgs: Controls opened отражает состояние окна. Opened использует локальное состояние закрытия и реагирует на изменение opened в Controls; при новом открытии story окно снова открыто. В Docs Opened размещён в отдельном iframe высотой 450 px, чтобы портал, фокус и блокировка прокрутки оставались внутри примера. Controls в Docs управляют основным Default, а на отдельной странице Opened — его начальными параметрами. Составные блоки также имеют собственные метаданные; они не наследуют Controls другой карусели или расписания. Сценарии без одного компонента (пустое состояние, очередь уведомлений) выделены отдельно, без фиктивной component: Button.

Учебный раздел Storybook

В каталоге «Обучение» есть шесть MDX-глав: введение, подключение, тема и CSS-каскад, композиция, создание stories/docs и практикум. Главы находятся в packages/ui/src/docs, подключаются через stories в .storybook/main.ts и показываются перед каталогом компонентов через storySort.

MDX объединяет обычный текст, примеры JSX и живые Canvas существующих stories. Изменение примера в Controls меняет его args, а не исходники приложения. Для проверки новых глав нужно собрать Storybook: одного tsc --noEmit недостаточно, потому что TypeScript не компилирует MDX и не строит индекс stories.

pnpm --filter @gheilt/ui build-storybook

Страница 404 приложения — пример композиции готового UI kit с Mantine: разбор компонентов и серверной границы.