Фронтенд — это React 19 SPA, собранный с помощью Vite 7 и TypeScript 5.9. Он запускается внутри Electron BrowserWindow, загружаемого из file://wwwroot/index.html.
| Библиотека | Назначение |
|---|---|
| React 19 | UI-фреймворк |
| TypeScript 5.9 | Типобезопасность |
| Vite 7 | Инструмент сборки |
| TailwindCSS v4 | Утилитарный CSS |
| GSAP 3 + @gsap/react | Анимации |
| Lucide React | Иконки |
| React Router DOM | Клиентская маршрутизация |
| Страница | Файл | Маршрут | Описание |
|---|---|---|---|
| Панель управления | pages/Dashboard.tsx |
/ |
Запуск игры, прогресс, статус |
| Новости | pages/News.tsx |
/news |
Лента новостей Hytale |
| Настройки | pages/Settings.tsx |
/settings |
Настройки приложения |
| Менеджер модов | pages/ModManager.tsx |
/mods |
Просмотр и управление модами |
| Компонент | Файл | Описание |
|---|---|---|
| TitleBar | components/TitleBar.tsx |
Заголовок безрамочного окна с кнопками управления |
| Sidebar | components/Sidebar.tsx |
Боковая панель навигации с иконками |
| GlassCard | components/GlassCard.tsx |
Обёртка карточки в стиле glass-morphism |
Чтобы интерфейс оставался единообразным и поддерживаемым, используйте общие примитивы из Frontend/src/components/ui/:
- PageContainer (
components/ui/PageContainer.tsx) — единая максимальная ширина, центрирование и адаптивные отступы для основных страниц - SettingsHeader (
components/ui/SettingsHeader.tsx) — унифицированный заголовок секции/страницы (title + опциональное описание, опциональный слот для действий) - SelectionCard (
components/ui/SelectionCard.tsx) — переиспользуемая карточка-выбор с вариантами оформления, слотом иконки, состоянием selected и обработчиком клика
Для большинства интерактивных элементов (кнопки, иконки-кнопки, таб-переключатели, скролл-области и lightbox) используйте:
- Controls (
components/ui/Controls.tsx) — стабильный barrel-export (реализации лежат вcomponents/ui/controls/)
Этот файл специально централизует внешний вид/поведение UI, чтобы не плодить множество одноразовых стилей кнопок.
Что использовать
Button— базовая кнопка для большинства действийIconButton— квадратные действия только с иконкой (refresh/copy/export и т.д.)LinkButton— кнопка в стиле ссылки для текстовых действий (без одноразовыхclassNameкнопок)LauncherActionButton— градиентные основные действия (Play/Stop/Download/Update/Select) с "лаунчерным" шрифтом/весомSegmentedControl— переключатели в стиле табов с бегунком (как в Instances)AccentSegmentedControl— обёртка надSegmentedControl, автоматически применяющая текущий accent-стиль (используется для фильтров в Logs и табов Instances)Switch— accent-reactive переключательScrollArea— единообразный overflow + опциональныйthin-scrollbarImageLightbox— просмотр скриншотов по центру с навигацией1/3 < >DropdownTriggerButton— стандартный триггер dropdown (label + chevron + состояние open)MenuActionButton— полноширинные пункты меню для hover-меню (например, Worlds overlay)MenuItemButton— полноширинные пункты для context/popover меню (заменяет одноразовыеbutton className="..."в меню)ModalFooterActions— стандартная строка действий в футере модалки (отступы + граница + фон)
Размеры IconButton
- Используйте
IconButton size="sm" | "md" | "lg"вместо ручныхh-/w-классов.
Варианты IconButton
- Используйте
variant="overlay"для навигации в скриншотах/lightbox (без glass hover).
Правило
- Если хочется написать новую кнопку с кастомным
className="...rounded...hover..."— лучше использоватьButton/IconButtonиз@/components/ui/Controls.
Правки controls
- Если нужно поменять конкретный элемент, правьте роль-модуль в
components/ui/controls/, аControls.tsxдержите как тонкий re-export.
Если файл страницы становится слишком большим, выносите связные блоки UI в отдельную папку рядом со страницей и переносите общие типы туда же.
- Пример:
InstancesPageиспользуетFrontend/src/pages/instances/для вынесенных компонентов и общих типов.
import { ipc } from '../lib/ipc';
import type { Profile } from '../lib/ipc';
interface Props {
profileId: string;
}
export function ProfileCard({ profileId }: Props) {
const [profile, setProfile] = useState<Profile | null>(null);
useEffect(() => {
ipc.profile.get().then(setProfile);
}, [profileId]);
if (!profile) return <div>Loading...</div>;
return (
<div className="p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-light)' }}>
{profile.name}
</div>
);
}Все цвета темы определены как CSS custom properties в Frontend/src/index.css:
:root {
--bg-darkest: #0D0D10; /* Фон приложения */
--bg-dark: #14141A; /* Боковая панель, заголовок */
--bg-medium: #1C1C26; /* Карточки */
--bg-light: #252533; /* Приподнятые поверхности */
--bg-lighter: #2E2E40; /* Границы, полоса прокрутки */
--text-primary: #F0F0F5; /* Основной текст */
--text-secondary: #A0A0B8; /* Второстепенный текст */
--text-muted: #6B6B80; /* Приглушённый / неактивный */
--accent: #7C5CFC; /* Основной акцент (фиолетовый) */
--accent-hover: #6A4AE8; /* Акцент при наведении */
--success: #4ADE80;
--warning: #FBBF24;
--error: #F87171;
}Использование:
- Классы Tailwind для компоновки/отступов:
className="flex items-center gap-2 p-4" - CSS-переменные для цветов темы:
style={{ color: 'var(--accent)' }} - Никогда не используйте захардкоженные hex-цвета — всегда CSS-переменные
Переходы между страницами и микровзаимодействия используют GSAP:
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
export function MyPage() {
const containerRef = useRef<HTMLDivElement>(null);
useGSAP(() => {
gsap.from(containerRef.current, {
opacity: 0, y: 20, duration: 0.5, ease: 'power2.out'
});
}, []);
return <div ref={containerRef}>...</div>;
}Весь IPC доступен через автогенерируемый объект ipc:
import { ipc } from '../lib/ipc';
// Invoke (запрос/ответ)
const settings = await ipc.settings.get();
// Send (без ожидания ответа)
ipc.windowCtl.minimize();
// Подписка на события
ipc.game.onProgress((data) => setProgress(data.progress));
// Открыть URL
ipc.browser.open('https://example.com');Состояние игры управляется через GameContext:
const { isPlaying, launch, cancel } = useGame();Добавляйте новые контексты в Frontend/src/contexts/ для состояния других доменов.
Все иконки берутся из Lucide React — без пользовательских SVG-файлов:
import { Settings, Download, Play } from 'lucide-react';
<Settings size={18} style={{ color: 'var(--text-secondary)' }} />