Files
nextjs-style-guide/generated/ru/RULES.md
S.Gromov b37eb75542
Some checks failed
CI/CD Pipeline / deploy (push) Blocked by required conditions
CI/CD Pipeline / docker (push) Failing after 12m36s
sync
2026-03-28 21:15:15 +03:00

1405 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<a name="indexmd"></a>
# NextJS Style Guide
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
## Для ассистентов
Полная документация в одном MD файле: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/ru/RULES.md
## Структура документации
### Workflow
**Что делать** в конкретной ситуации — пошаговые инструкции.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Начало работы | Какие инструменты установить перед началом разработки? |
| Создание приложения | Как создать новый проект, откуда взять шаблон? |
| Создание страниц | Как добавить страницу: роутинг и экран? |
| Создание компонентов | Как генерировать компоненты через шаблоны? |
| Стилизация | Чем стилизовать: Mantine, токены или PostCSS? |
| Работа с данными | Как получать данные: SWR, кодген, сокеты? |
| Управление состоянием | Когда и как создавать стор (Zustand)? |
| Локализация | Как добавлять переводы и работать с i18next? |
### Базовые правила
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Технологии и библиотеки | Какой стек используем? |
| Архитектура | Как устроены слои FSD, зависимости, публичный API? |
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
| Именование | Как называть файлы, переменные, компоненты, хуки? |
| Документирование | Как писать JSDoc: что документировать, а что нет? |
| Типизация | Как типизировать: type vs interface, any/unknown, FC? |
### Прикладные разделы
**Как устроена конкретная область** — правила, структура и примеры кода для отдельных технологий и инструментов.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Структура проекта | Как организованы папки и файлы по FSD? |
| Компоненты | Как устроен компонент: файлы, пропсы, clsx, FC? |
| Шаблоны и генерация | Как работают шаблоны: синтаксис, переменные, модификаторы? |
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
| Изображения | _(не заполнен)_ |
| SVG-спрайты | _(не заполнен)_ |
| Видео | _(не заполнен)_ |
| API | _(не заполнен)_ |
| Stores | _(не заполнен)_ |
| Хуки | _(не заполнен)_ |
| Шрифты | _(не заполнен)_ |
| Локализация | _(не заполнен)_ |
<a name="workflowgetting-startedmd"></a>
# Начало работы
Что нужно установить и настроить перед началом разработки.
## Инструменты
### CLI
| Инструмент | Установка | Назначение |
|-----------|-----------|-----------|
| [@gromlab/create](https://gromlab.ru/gromov/create) | `npm i -g @gromlab/create` | Генерация файлов и папок по шаблонам |
### VS Code
#### Расширения
Каждый проект должен содержать файл `.vscode/extensions.json` — VS Code автоматически предложит установить нужные расширения при открытии проекта.
```json
// .vscode/extensions.json
{
"recommendations": [
"biomejs.biome",
"MyTemplateGenerator.mytemplategenerator",
"csstools.postcss"
]
}
```
| Расширение | Назначение |
|-----------|-----------|
| [MyTemplateGenerator](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) | Генерация файлов и папок из шаблонов через UI |
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода |
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка и автодополнение PostCSS |
#### Настройки
Каждый проект должен содержать файл `.vscode/settings.json` с базовой конфигурацией редактора.
```json
// .vscode/settings.json
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"files.associations": {
"*.css": "postcss"
}
}
```
| Настройка | Что делает |
|-----------|-----------|
| `editor.defaultFormatter` | Biome как форматтер по умолчанию |
| `editor.formatOnSave` | Автоформатирование при сохранении |
| `codeActionsOnSave` | Автофикс и сортировка импортов при сохранении |
| `files.associations` | CSS-файлы открываются с подсветкой PostCSS |
<a name="workflowcreating-appmd"></a>
# Создание приложения
Как создать новое приложение: выбор шаблона проекта и инициализация.
<a name="workflowcreating-pagesmd"></a>
# Создание страниц
Паттерн создания страниц: роутинг (page.tsx) и экран (screen).
<a name="workflowcreating-componentsmd"></a>
# Создание компонентов
Генерация компонентов через шаблоны, работа с дочерними компонентами.
<a name="workflowstylingmd"></a>
# Стилизация
Приоритет инструментов стилизации и правила их применения.
<a name="workflowdata-fetchingmd"></a>
# Работа с данными
Как получать данные: SWR, кодген API-клиентов, сокеты.
<a name="workflowstate-managementmd"></a>
# Управление состоянием
Когда и как создавать стор (Zustand), что хранить локально и глобально.
<a name="workflowlocalizationmd"></a>
# Локализация
Как добавлять переводы и работать с i18next.
<a name="basicstech-stackmd"></a>
# Технологии и библиотеки
Базовый стек технологий и библиотек, на который опираются проекты и примеры в документации.
## Что используем
### Стек
- **React/TypeScript** — основной стек для UI и приложения.
- **Next.js** — для продуктовых сайтов.
### Архитектура
- **FSD (Feature-Sliced Design)** — структура проекта и границы модулей.
### UI компоненты
- **Mantine UI** — базовые UI-компоненты.
### Fetch (API)
- **@gromlab/api-codegen** — генерация APIклиентов и типов.
- **SWR** — получение, кеширование, ревалидация, дедубликация.
- **SWR (useSWRSubscription)** - сокеты, реалтайм подписки.
### Store
- **Zustand** — глобальное состояние.
### Локализация
- **i18next (i18n)** — локализация всех пользовательских текстов.
### Тестирование
- **Vitest** — тестирование.
### Стили
- **PostCSS Modules** — изоляция стилей.
- **Mobile First** — подход к адаптивной верстке.
- **clsx** — конкатенация CSSклассов.
### Генерация
- **@gromlab/create** — шаблонизатор для создания слоёв и других файлов из шаблонов.
<a name="basicsarchitecturemd"></a>
# Архитектура
Архитектура построена на FSD (`FeatureSliced Design`) и строгих границах модулей.
Цель — разделить ответственность, упростить сопровождение и контроль зависимостей.
## Принципы
- Разделять UI, бизнес-логику и инфраструктуру.
- Держать зависимости однонаправленными.
- Открывать наружу только публичный API модулей.
- Не допускать циклических зависимостей.
## Слои (FSD)
- **app** — инициализация приложения: провайдеры, глобальные стили. В Next.js эта же папка `app/` дополнительно содержит системные файлы роутинга (`layout.tsx`, `page.tsx`).
- **screens** — UI-компоненты страниц. Каждый экран — отдельный компонент, который собирает виджеты и фичи конкретной страницы. Роутинг только использует эти компоненты — он не является частью слоя `screens`. В Next.js файлы `page.tsx` остаются тонкими: импортируют экран и рендерят его.
- **layouts** — каркас и шаблоны страниц.
- **widgets** — крупные блоки интерфейса, собирающие несколько сценариев.
- **features** — отдельные пользовательские действия и сценарии.
- **entities** — бизнес-сущности и их модель.
- **shared** — переиспользуемая инфраструктура, утилиты и базовые UIкомпоненты.
## Модули (FSD)
- Модуль — это отдельная папка в слоях `screens`, `layouts`, `widgets`, `features`, `entities`, которая реализует один сценарий/блок. В корне модуля лежит главный файл (`*.screen.tsx`, `*.layout.tsx`, `*.widget.tsx`, `*.feature.tsx`, `*.entity.tsx`) и публичный API (`index.ts`).
- Внутри модуля используются подпапки (по необходимости):
- `ui/` — дочерние UIкомпоненты модуля.
- `model/` — состояние и бизнес‑логика модуля.
- `styles/` — локальные стили модуля.
- `helpers/` — локальные хелперы.
- `lib/` — утилиты модуля.
- `api/` — APIвызовы модуля.
## Правила зависимостей
- Допустимые импорты идут сверху вниз: `app → screens → layouts → widgets → features → entities → shared`.
- Импорты между слоями — через публичный API.
- Внутри одного слоя — относительные импорты.
## Публичный API модулей
- Каждый модуль экспортирует наружу только то, что нужно другим слоям.
- Внешние импорты идут только через `index`‑файл модуля.
- Внутренние файлы не импортируются напрямую извне.
## Границы ответственности
- Бизнес‑логика не размещается в UIкомпонентах.
- UIкомпоненты должны быть максимально простыми и предсказуемыми.
- Связь между независимыми сценариями поднимается на уровень выше.
## Типовые ошибки
- Импорт из более высокого слоя в более низкий.
- Смешивание логики нескольких слоёв в одном модуле.
- Прямые импорты внутренних файлов, минуя публичный API.
<a name="basicscode-stylemd"></a>
# Стиль кода
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
## Отступы
- 2 пробела (не табы).
## Длина строк
- Ориентироваться на 100 символов, но превышение допустимо, если строка читается легко.
- Переносить выражение на новые строки, когда строка становится плохо читаемой.
- Не переносить строку внутри строковых литералов без необходимости.
**Хорошо**
```ts
const config = createRequestConfig(
endpoint,
{
headers: {
'X-Request-Id': requestId,
'X-User-Id': userId,
},
params: {
page,
pageSize,
sort: 'createdAt',
},
},
timeoutMs,
);
```
**Плохо**
```ts
// Плохо: длинная строка с вложенными структурами плохо читается.
const config = createRequestConfig(endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId }, params: { page, pageSize, sort: 'createdAt' } }, timeoutMs);
```
## Кавычки
- В JavaScript/TypeScript использовать одинарные кавычки.
- В JSX/TSX для атрибутов использовать двойные кавычки.
- Шаблонные строки использовать только при интерполяции или многострочном тексте.
**Хорошо**
```ts
const label = 'Сохранить';
const title = `Привет, ${name}`;
```
```tsx
<input type="text" placeholder="Введите имя" />
```
**Плохо**
```ts
// Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки.
const label = "Сохранить";
const title = 'Привет, ' + name;
```
```tsx
// Плохо: одинарные кавычки в JSX-атрибутах.
<input type='text' placeholder='Введите имя' />
```
## Точки с запятой и запятые
- Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным.
- В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна.
## Импорты
- В именованных импортах использовать пробелы внутри фигурных скобок.
- Типы импортировать через `import type`.
- `default` экспорт избегать, использовать именованные. `default` импорт допустим (например, стили CSS Modules, сторонние библиотеки).
- Избегать импорта всего модуля через `*`.
**Хорошо**
```ts
import { MyComponent } from 'MyComponent';
import type { User } from '../model/types';
import styles from './styles/button.module.css';
```
**Плохо**
```ts
// Плохо: отсутствие пробелов в именованном импорте.
import type {User} from '../model/types';
// Плохо: default экспорт.
export default MyComponent;
```
## Ранние возвраты (early return)
- Использовать ранние возвраты для упрощения чтения.
- Избегать `else` после `return`.
**Хорошо**
```ts
const getName = (user?: { name: string }) => {
if (!user) {
return 'Гость';
}
return user.name;
};
```
**Плохо**
```ts
// Плохо: лишний else после return усложняет чтение.
const getName = (user?: { name: string }) => {
if (user) {
return user.name;
} else {
return 'Гость';
}
};
```
## Форматирование объектов и массивов
- В многострочных объектах каждое свойство на новой строке.
- В многострочных массивах каждый элемент на новой строке.
- Объекты и массивы можно писать в одну строку, если длина строки не превышает 100 символов.
- В однострочных объектах и массивах использовать пробелы после запятых.
**Хорошо**
```ts
const roles = ['admin', 'editor', 'viewer'];
const options = { id: 1, name: 'User' };
const config = {
url: '/api/users',
method: 'GET',
params: { page: 1, pageSize: 20 },
};
```
**Плохо**
```ts
// Плохо: нет пробелов после запятых и объект слишком длинный для одной строки.
const roles = ['admin','editor','viewer'];
const options = { id: 1,name: 'User' };
const config = { url: '/api/users', method: 'GET', params: { page: 1, pageSize: 20 } };
```
<a name="basicsnamingmd"></a>
# Именование
Именование должно быть предсказуемым, коротким и отражать смысл сущности.
## Базовые правила
| Что | Рекомендуется |
| ---------------- | ---------------------- |
| Папки | `kebab-case` |
| Файлы | `kebab-case` |
| Переменные | `camelCase` |
| Константы | `SCREAMING_SNAKE_CASE` |
| Классы | `PascalCase` |
| React-компоненты | `PascalCase` |
| Хуки | `useSomething` |
| CSS классы | `camelCase` |
## Архитектурный неймспейс
Соглашение о суффиксах, которые обозначают слой (уровень абстракции), роль или тип файла.
- Суффиксы используются для обозначения слоя, роли или типа файла.
- Суффиксы всегда пишутся в единственном числе.
- Формат имени: `name.<suffix>.ts` или `name.<suffix>.tsx`.
**UI и слои FSD**
- `.screen.tsx` — экран
- `.layout.tsx` — layout
- `.widget.tsx` — виджет
- `.feature.tsx` — UI фичи
- `.entity.tsx` — UI сущности
- `.ui.tsx` — UIкомпонент
**Логика и модель**
- `.store.ts` — стор
- `.service.ts` — сервис
**Типы и контракты**
- `.type.ts` — типы и интерфейсы
- `.interface.ts` — файл с интерфейсами (если нужен отдельный контракт)
- `.enum.ts` — enum
- `.dto.ts` — внешние DTO
- `.schema.ts` — схемы валидации
- `.constant.ts` — константы
- `.config.ts` — конфигурация
**Утилиты и хелперы**
- `.util.ts` — утилиты
- `.helper.ts` — вспомогательные функции
- `.lib.ts` — вспомогательные функции
**Тесты**
- `.test.ts` / `.test.tsx`
- `.mock.ts`
**Хорошо**
```text
src/
├── screens/
│ └── main/
│ ├── main.screen.tsx
│ └── index.ts
├── features/
│ └── auth-by-email/
│ ├── ui/
│ │ └── login-form.ui.tsx
│ ├── auth-by-email.feature.tsx
│ └── index.ts
└── shared/
└── ui/
└── icon/
├── styles/
│ └── icon.module.css
├── types/
│ └── icon.interface.ts
├── icon.ui.tsx
└── index.ts
```
**Плохо**
```text
// Плохо: нет единых правил для слоёв и публичных файлов.
src/
├── screens/
│ └── Main/
│ └── Main.tsx
└── features/
└── authByEmail/
└── login-form.tsx
```
## Булевы значения
- Использовать префиксы `is`, `has`, `can`, `should`.
**Хорошо**
```ts
const isReady = true;
const hasAccess = false;
const canSubmit = true;
const shouldRedirect = false;
```
**Плохо**
```ts
// Плохо: неясное булево значение без префикса.
const ready = true;
const access = false;
const submit = true;
```
## События и обработчики
- Обработчики начинать с `handle`.
- События и колбэки начинать с `on`.
**Хорошо**
```ts
const handleSubmit = () => { ... };
const onSubmit = () => { ... };
```
**Плохо**
```ts
// Плохо: неочевидное назначение имени.
const submitClick = () => { ... };
```
## Коллекции
- Для массивов использовать имена во множественном числе.
- Для словарей/мап — использовать суффиксы `ById`, `Map`, `Dict`.
**Хорошо**
```ts
const users = [];
const usersById = {} as Record<string, User>;
const userIds = ['u1', 'u2'];
const ordersMap = new Map<string, Order>();
const featureFlagsDict = { beta: true, legacy: false } as Record<string, boolean>;
```
**Плохо**
```ts
// Плохо: имя не отражает, что это коллекция.
const user = [];
// Плохо: словарь назван как массив.
const usersMap = [];
// Плохо: по имени непонятно, что это словарь.
const users = {} as Record<string, User>;
```
<a name="basicsdocumentationmd"></a>
# Документирование
Документирование должно помогать понять назначение сущности, а не дублировать её типы или очевидные детали.
## Правила
- Документировать только назначение функций, компонентов, типов, интерфейсов и enum.
- Не документировать параметры, возвращаемые значения, типы пропсов и очевидные детали.
- В интерфейсах, типах и enum описывать только смысл поля или значения.
- Описание должно быть кратким, информативным и завершаться точкой.
## Примеры
**Хорошо**
```ts
/**
* Список задач пользователя.
*/
export const TodoList = memo(() => { ... });
/**
* Интерфейс задачи.
*/
export interface TodoItem {
/** Уникальный идентификатор задачи. */
id: string;
/** Текст задачи. */
text: string;
/** Статус выполнения задачи. */
completed: boolean;
}
/**
* Перечисление фильтров задач.
*/
export enum TodoFilter {
/** Все задачи. */
All = 'all',
/** Только активные задачи. */
Active = 'active',
/** Только выполненные задачи. */
Completed = 'completed',
}
```
**Плохо**
```ts
// Плохо: дублирование параметров и возвращаемых значений.
/**
* @param id - идентификатор задачи
* @returns объект задачи
*/
// Плохо: описание очевидных деталей.
/**
* id — идентификатор задачи
* text — текст задачи
* completed — статус выполнения
*/
```
<a name="basicstypingmd"></a>
# Типизация
Типизация обязательна для всех публичных интерфейсов, функций и компонентов.
Цель — предсказуемость, безопасность и автодополнение.
## Общие правила
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
- Избегать `any` и `unknown` без необходимости.
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
## Типы для компонентов
- Типизировать параметры и публичный интерфейс компонента.
- Дефолтные значения описывать явно в коде.
**Хорошо**
```tsx
/**
* Параметры кнопки.
*/
interface ButtonProps extends HTMLAttributes<HTMLDivElement> {
/** Текст кнопки. */
label: string;
/** Обработчик клика по кнопке. */
onClick: () => void;
}
/**
* Кнопка с пользовательскими стилями.
*/
export const Button: FC<ButtonProps> = ({ className, label, onClick, ...htmlAttr }) => {
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
button
</div>
)
}
```
**Плохо**
```tsx
// Плохо: параметры не типизированы.
export const Button = (props) => (
<button type="button" onClick={props.onClick}>
{props.label}
</button>
);
```
## Функции
- Для публичных функций указывать возвращаемый тип.
- Не полагаться на неявный вывод для важных API.
**Хорошо**
```ts
export const formatPrice = (value: number): string => {
return `${value} ₽`;
};
```
**Плохо**
```ts
// Плохо: нет явного возвращаемого типа.
export const formatPrice = (value: number) => {
return `${value} ₽`;
};
```
## Работа с any/unknown
- `any` использовать только для временных заглушек.
- `unknown` сужать через проверки перед использованием.
**Хорошо**
```ts
const parse = (value: unknown): string => {
if (typeof value === 'string') {
return value;
}
return '';
};
```
**Плохо**
```ts
// Плохо: any отключает проверку типов.
const parse = (value: any) => value;
```
<a name="appliedproject-structuremd"></a>
# Структура проекта
Раздел описывает базовую структуру проекта и принципы организации модулей на уровне папок и файлов.
## Базовая структура проекта
Слои FSD не зависят от фреймворка. Различается только содержимое `app/` — в React SPA это конфигурация роутинга, в Next.js — системные файлы фреймворка (`layout.tsx`, `page.tsx`, route-сегменты).
```text
src/
├── app/ # Инициализация приложения (см. «Слой app/»)
├── screens/ # UI-компоненты страниц
│ └── profile/
│ ├── profile.screen.tsx
│ └── index.ts
├── layouts/ # Общие шаблоны и каркасы страниц
│ └── main-layout/
│ ├── main-layout.layout.tsx
│ └── index.ts
├── widgets/ # Крупные блоки интерфейса
│ └── header/
│ ├── header.widget.tsx
│ └── index.ts
├── features/ # Пользовательские сценарии
│ └── auth-by-email/
│ ├── ui/
│ │ └── login-form.ui.tsx
│ ├── model/
│ │ └── auth-by-email.store.ts
│ ├── auth-by-email.feature.tsx
│ └── index.ts
├── entities/ # Бизнес-сущности
│ └── user/
│ ├── model/
│ │ └── user.store.ts
│ ├── user.entity.tsx
│ └── index.ts
└── shared/ # Общие ресурсы проекта
├── ui/ # Повторно используемые UI-элементы
│ └── icon/
│ ├── styles/
│ │ └── icon.module.css
│ ├── types/
│ │ └── icon.interface.ts
│ ├── icon.ui.tsx
│ └── index.ts
├── lib/ # Утилиты и хелперы
├── services/ # Общие сервисы и клиенты
├── config/ # Общие конфигурации и константы
└── assets/ # Ресурсы
├── images/
├── icons/
├── fonts/
└── video/
```
## Слой app/
Общее для обоих вариантов: провайдеры и глобальные стили. Различается только способ организации роутинга.
### React SPA
```text
src/app/
├── providers/ # Провайдеры и обёртки приложения
├── routing/ # Конфигурация маршрутов (React Router)
├── styles/ # Глобальные стили, CSS-переменные, custom media
└── index.ts # Entry point приложения
```
### Next.js (App Router)
```text
src/app/
├── providers/ # Провайдеры и обёртки приложения
├── styles/ # Глобальные стили, CSS-переменные, custom media
├── layout.tsx # Корневой layout (подключает providers, styles)
├── page.tsx # Главная страница
└── profile/
└── page.tsx # Рендерит ProfileScreen
```
В Next.js файлы `page.tsx` остаются тонкими — они только импортируют экран из `screens/` и рендерят его. Вся логика, зависимости и стили страницы живут в компоненте экрана, а не в `app/`.
```tsx
// src/app/profile/page.tsx
import { ProfileScreen } from '@/screens/profile';
export default function ProfilePage() {
return <ProfileScreen />;
}
```
**Плохо**
```text
// Плохо: слои смешаны, нет понятных границ и публичного API.
src/
├── components/
├── api/
├── styles/
└── user.ts
```
## Правила организации
- В слоях FSD (`features`, `entities`, `widgets`, `screens` и т.д.) `ui/` используется только для дочерних элементов, которые относятся к модулю и не экспортируются отдельно. Главные компоненты, которые составляют сам слой, держат собственные `*.feature.tsx`, `*.widget.tsx` и т. п., а `ui/` служит для вспомогательных мелких компонентов.
- В `shared/ui/` хранятся базовые UI-элементы/компоненты, которыми пользуются сразу несколько модулей; в этом случае они экспортируются наружу и не считаются «дочерними» для слоя.
- Если модуль строится вокруг «главного» компонента (`*.feature.tsx`, `*.screen.tsx`, `*.widget.tsx`), помещайте его в корень модуля и экспортируйте через `index.ts`. Проверяйте, что `ui/` не используется просто как «контейнер» слоя.
- Каждый слой и модуль хранится в собственной папке.
- Внутренние реализации разделяются на `ui/`, `model/`, `styles/`, `helpers/`, `lib/`, `api/`.
- Публичный API модуля объявляется в `index.ts`.
- Внутренние файлы не импортируются напрямую извне.
- Не смешивать ответственность разных слоёв в одном модуле.
<a name="appliedcomponentsmd"></a>
# Компоненты
Раздел описывает правила создания UIкомпонентов. Эти правила обязательны для всех слоёв FSD: `app`, `screens`, `layouts`, `widgets`, `features`, `entities`, `shared`.
## Базовая структура компонента
Минимальный набор файлов: компонент, стили, типы и публичный экспорт.
```text
container/
├── styles/
│ └── container.module.scss
├── types/
│ └── container.interface.ts
├── container.ui.tsx
└── index.ts
```
## Пример базового компонента
`styles/container.module.scss`
```scss
.root {}
```
В CSS Modules использование имени класса **.root** — это общепринятое соглашение (best practice)
`types/container.interface.ts`
```ts
import type { HTMLAttributes } from 'react'
/**
* Параметры контейнера.
*/
export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {}
```
Интерфес параметров компонента всегда наследует свойства своего тега: div, button, итд..
`container.ui.tsx`
```tsx
import type { FC } from 'react'
import cl from 'clsx'
import type { ContainerProps } from './types/container.interface'
import styles from './styles/container.module.scss'
/**
* Контейнер с адаптивной максимальной шириной.
*
* Используется для:
* - ограничения ширины контента
* - центрирования содержимого
* - построения адаптивной сетки страницы
*/
export const Container: FC<ContainerProps> = ({ className, ...htmlAttr }) => {
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
Container...
</div>
)
}
```
- Компонент объявляется через `const` и экспортируется именованно.
- Пропсы деструктурируются в сигнатуре; если их больше двух — деструктуризацию переносим в тело компонента.
- Из пропсов отдельно извлекаются `className` и `...htmlAttr`, чтобы корректно объединять классы и прокидывать остальные атрибуты.
- `cl` — короткое имя функции для конкатенации CSSклассов.
- `FC<>` используется для декларации `children`.
`index.ts`
```ts
export { Container } from './container.ui'
```
## Шаблоны и генерация кода
Создание компонентов — **только через шаблоны**. Ручное создание файловой структуры компонента запрещено. Это обеспечивает единообразие каркаса, одинаковые папки и имена файлов, уменьшает ручные ошибки и ускоряет старт работы.
После генерации через **@gromlab/create** — проверить название компонента/файлов и заполнить описание назначения. Подробный порядок действий и перечень обязательных шаблонов — в разделе «Workflow».
## Вложенные (дочерние) компоненты
Если для реализации функционала нужны компоненты, которые используются только внутри текущего компонента, создавайте их как вложенные в папке `ui/`. Такие компоненты не экспортируются наружу и используются только локально.
Вложенные компоненты подчиняются тем же правилам по структуре, именованию и стилю (включая папку `styles/` для их стилей).
<a name="appliedtemplates-generationmd"></a>
# Шаблоны генерации кода
Раздел описывает инструменты, синтаксис шаблонов и примеры. Порядок действий при создании модулей и перечень обязательных шаблонов — в разделе «Workflow».
## Обязательность
- Создание типовых модулей — **только через шаблоны**. Ручное создание файловой структуры модуля запрещено, если для него существует шаблон.
- Перед созданием нового модуля — проверить наличие подходящего шаблона в `.templates/`.
- Если подходящего шаблона нет — сначала создать шаблон, затем использовать его.
## Что генерируем
- Компоненты (`screens`, `layouts`, `widgets`, `features`, `entities`).
- Страницы (nextjs `app`, `pages`).
- Типовые инфраструктурные модули (например, `store`).
## Чем генерируем
### VSCode extension
[расширение VS Code](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) — создание файлов и папок из шаблонов через UIинтерфейс внутри редактора.
### CLI (для агентов)
[@gromlab/create](https://gromlab.ru/gromov/create) — CLI для генерации файлов и папок по шаблонам.
Примеры:
```bash
# Создать компонент
create component button
# Создать компонент используя NPX
npx @gromlab/create component button
```
## Структура папок
Все шаблоны лежат в `.templates/` в корне проекта.
Каждая папка в `.templates/` — это уникальный шаблон.
```text
.templates/ # корневая папка всех шаблонов
├── component/ # шаблон компонента
│ └── {{name.kebabCase}}/
│ ├── styles/
│ │ └── {{name.kebabCase}}.module.css
│ ├── types/
│ │ └── {{name.kebabCase}}.interface.ts
│ ├── {{name.kebabCase}}.ui.tsx
│ └── index.ts
└── store/ # шаблон Zustand стора
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.store.ts
├── {{name.kebabCase}}.type.ts
└── index.ts
```
## Синтаксис
- Переменные в шаблонах работают в именах файлов/папок и внутри файлов.
- Базовая переменная — `name`.
Формат записи переменной:
```text
{{variable}}
```
Модификаторы — это преобразования переменной, которые меняют регистр и формат записи. Они пишутся после имени через точку и применяются в момент генерации.
```text
{{name.pascalCase}} -> MyButton
{{name.camelCase}} -> myButton
{{name.kebabCase}} -> my-button
{{name.snakeCase}} -> my_button
{{name.screamingSnakeCase}} -> MY_BUTTON
```
Пример использования в шаблоне:
```text
{{name}}.tsx
{{name.pascalCase}}.tsx
```
```tsx
export const {{name.pascalCase}} = () => {
return <div>{{name}}</div>
}
```
## Шаблон компонента
Структура компонента по шаблону. Создаётся генератором автоматически.
```ts
// .templates/component/index.ts
export { {{name.pascalCase}} } from './{{name.kebabCase}}.ui'
```
```ts
// .templates/component/types/{{name.kebabCase}}.interface.ts
import type { HTMLAttributes } from 'react'
/**
* Параметры {{name.pascalCase}}.
*/
export interface {{name.pascalCase}}Props extends HTMLAttributes<HTMLDivElement> {}
```
```tsx
// .templates/component/{{name.kebabCase}}.ui.tsx
import type { FC } from 'react'
import cl from 'clsx'
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.interface'
import styles from './styles/{{name.kebabCase}}.module.css'
/**
* {{name.pascalCase}}.
*/
export const {{name.pascalCase}}: FC<{{name.pascalCase}}Props> = ({ className, ...htmlAttr }) => {
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{{name.kebabCase}}
</div>
)
}
```
```css
/* .templates/component/styles/{{name.kebabCase}}.module.css */
.root {
}
```
<a name="appliedstylesmd"></a>
# Стили
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
## Приоритет стилизации
Приоритет инструментов стилизации (от высшего к низшему):
1. **Mantine-компоненты и их пропсы** — в первую очередь использовать встроенные возможности Mantine.
2. **Глобальные CSS-токены** (`--color-*`, `--space-*`, `--radius-*`) — для значений, которые не покрываются Mantine.
3. **PostCSS Module файлы** — когда Mantine не покрывает задачу и нужна кастомная стилизация.
- Инлайн-стили в компонентах запрещены.
- Произвольные магические значения цветов, отступов и скруглений запрещены — использовать токены.
- Глобальные стили вне `app/styles/` запрещены.
Подробный порядок действий — в разделе «Workflow».
## Общие правила
- Только **PostCSS** и **CSS Modules** для кастомной стилизации.
- Подход **Mobile First** — стили пишутся от мобильных к десктопу.
- Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`).
- Модификаторы — отдельный класс с `_`, применяется через `&._modifier`.
**Хорошо**
```css
.submitButton {
padding: 8px 16px;
&._disabled {
opacity: 0.5;
}
}
```
**Плохо**
```css
/* Плохо: kebab-case и вложенный элемент вместо отдельного класса. */
.submit-button {
padding: 8px 16px;
&__icon {
margin-right: 8px;
}
}
```
## Вложенность
- Вложенность селекторов запрещена.
- Исключения:
- Псевдоклассы: `&:hover`, `&:active`, `&:focus`, `&:disabled` и т.д.
- Псевдоэлементы: `&::before`, `&::after`.
- Медиа-запросы: `@media`.
- Модификаторы: `&._active`, `&._disabled`.
- Каждый вложенный блок отделяется пустой строкой от предыдущих свойств.
**Хорошо**
```css
.card {
padding: 16px;
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&::after {
content: '';
display: block;
}
&._highlighted {
border-color: var(--color-primary);
}
@media (--md) {
padding: 24px;
}
}
.cardTitle {
font-size: 16px;
@media (--md) {
font-size: 20px;
}
}
```
**Плохо**
```css
/* Плохо: вложенность селекторов, нет пустых строк между блоками. */
.card {
padding: 16px;
.cardTitle {
font-size: 16px;
}
&:hover {
background-color: var(--color-bg-hover);
}
}
```
## Медиа-запросы
- Только **Custom Media Queries**: `@media (--md) {}`.
- Запрещены произвольные breakpoints: `@media (min-width: 768px)`.
- `@media` пишется только **внутри** селектора.
- Запрещено писать `@media` на верхнем уровне с селекторами внутри.
**Хорошо**
```css
.sidebar {
display: none;
@media (--md) {
display: block;
}
}
.sidebarTitle {
font-size: 14px;
@media (--md) {
font-size: 18px;
}
}
```
**Плохо**
```css
/* Плохо: @media на верхнем уровне с селекторами внутри. */
@media (--md) {
.sidebar {
display: block;
}
.sidebarTitle {
font-size: 18px;
}
}
/* Плохо: произвольный breakpoint вместо custom media. */
.sidebar {
@media (min-width: 992px) {
display: block;
}
}
```
## CSS-переменные
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `app/styles/variables.css` через `:root`.
- Файл переменных подключается один раз в корневом layout/entry point — после этого переменные доступны глобально через каскад.
- Не дублировать магические значения в компонентах.
**Хорошо**
```css
/* app/styles/variables.css */
:root {
--color-primary: #3b82f6;
--color-bg: #ffffff;
--color-bg-hover: #f5f5f5;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--radius-1: 4px;
--radius-2: 8px;
}
```
```css
/* компонент */
.card {
padding: var(--space-3);
border-radius: var(--radius-2);
background-color: var(--color-bg);
}
```
**Плохо**
```css
/* Плохо: магические значения вместо переменных. */
.card {
padding: 12px;
border-radius: 8px;
background-color: #ffffff;
}
```
## Custom Media
- Breakpoints определяются через Custom Media Queries в `app/styles/media.css`.
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
```css
/* app/styles/media.css */
@custom-media --sm (min-width: 36em);
@custom-media --md (min-width: 62em);
@custom-media --lg (min-width: 82em);
```
## Импорт стилей
- Стили компонента импортируются только внутри своего компонента.
- Запрещено импортировать стили одного компонента в другой.
- Custom media не импортируются в файлы стилей — они подключаются глобально через конфиг PostCSS.
## Форматирование
- Пустая строка между селекторами верхнего уровня.
- Пустая строка перед каждым вложенным блоком (медиа, псевдокласс, модификатор).
**Хорошо**
```css
.userBar {
display: none;
color: var(--color-text);
@media (--md) {
display: flex;
}
}
.userBarButton {
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&._active {
background-color: var(--color-primary);
}
}
```
**Плохо**
```css
/* Плохо: нет пустых строк между селекторами и вложенными блоками. */
.userBar {
display: none;
color: var(--color-text);
@media (--md) {
display: flex;
}
}
.userBarButton {
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&._active {
background-color: var(--color-primary);
}
}
```
## Единицы измерения
- `px` — основная единица измерения.
- Остальные (`em`, `rem`, `%`, `vh`/`vw`) — допускаются по необходимости дизайна.
## Порядок CSS-свойств
В стилях рекомендуется придерживаться логического порядка свойств:
1. Позиционирование (`position`, `top`, `left`, `z-index`).
2. Блочная модель (`display`, `width`, `height`, `margin`, `padding`).
3. Оформление (`background`, `border`, `box-shadow`, `border-radius`).
4. Текст (`font`, `color`, `text-align`, `line-height`).
5. Прочее (`transition`, `animation`, `opacity`, `cursor`).
## Комментарии
- Желательно не писать комментарии в CSS.
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
<a name="appliedimages-spritesmd"></a>
<a name="appliedsvg-spritesmd"></a>
# SVG-спрайты
<a name="appliedvideomd"></a>
<a name="appliedapimd"></a>
<a name="appliedstoresmd"></a>
<a name="appliedhooksmd"></a>
<a name="appliedfontsmd"></a>
<a name="appliedlocalizationmd"></a>