sync
This commit is contained in:
@@ -1,11 +1,26 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
const versions = Array.from({ length: 100 }, (_, i) => {
|
||||
const major = Math.floor(i / 10) + 1;
|
||||
const minor = i % 10;
|
||||
return `v${major}.${minor}`;
|
||||
}).reverse();
|
||||
|
||||
export default defineConfig({
|
||||
lang: 'ru-RU',
|
||||
title: 'Frontend Style Guide',
|
||||
description: 'Правила и стандарты разработки фронтенд-проектов на React/NextJS и TypeScript',
|
||||
themeConfig: {
|
||||
siteTitle: 'Frontend Style Guide',
|
||||
nav: [
|
||||
{
|
||||
text: versions[0],
|
||||
items: versions.map((v) => ({
|
||||
text: v,
|
||||
link: `/${v}/`,
|
||||
})),
|
||||
},
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Базовые правила',
|
||||
@@ -25,7 +40,8 @@ export default defineConfig({
|
||||
{ text: 'Компоненты', link: '/parts/8-0-components' },
|
||||
{ text: 'Шаблоны и генерация кода', link: '/parts/8-1-templates-generation' },
|
||||
{ text: 'Стили', link: '/parts/9-styles' },
|
||||
{ text: 'Изображения/спрайты', link: '/parts/10-images-sprites' },
|
||||
{ text: 'Изображения', link: '/parts/10-images-sprites' },
|
||||
{ text: 'SVG-спрайты', link: '/parts/17-svg-sprites' },
|
||||
{ text: 'Видео', link: '/parts/11-video' },
|
||||
{ text: 'API', link: '/parts/12-api' },
|
||||
{ text: 'Stores', link: '/parts/13-stores' },
|
||||
|
||||
38
AGENTS.md
38
AGENTS.md
@@ -1,38 +0,0 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Назначение репозитория
|
||||
Этот репозиторий хранит документацию и правила разработки фронтенд‑проектов (React/Next.js, TypeScript) и публикуется через VitePress. Контент пишется на русском языке. Сейчас ведётся рефакторинг документации: цель — краткие, чёткие и не дублирующиеся правила.
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `index.md` — главная страница документации.
|
||||
- `parts/` — разделы документации, один файл на тему. Именование: `N-title.md` в `kebab-case` (например, `3-code-style.md`).
|
||||
- `.vitepress/config.ts` — конфигурация VitePress и sidebar; добавляя новый раздел, обновляйте список ссылок.
|
||||
- `RULES.md` — агрегированный документ, собирается из `parts/`.
|
||||
- `concat-md.js` — скрипт сборки `RULES.md`.
|
||||
- `OLD_parts/` — архив старой документации, используется только как справочник при переносе идей.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `npm install` — установка зависимостей.
|
||||
- `npm run dev` — локальный сервер VitePress (обычно localhost:5173).
|
||||
- `npm run build` — сборка статического сайта.
|
||||
- `npm run serve` — предпросмотр собранной статики.
|
||||
- `npm run docs` — пересборка `RULES.md` из `parts/`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Язык документации — русский.
|
||||
- Для новых разделов придерживайтесь нумерации и `kebab-case` в именах файлов.
|
||||
- В примерах кода ориентируйтесь на правила из `RULES.md`: отступ 2 пробела, одинарные кавычки в TS, двойные в JSX, `import type` для типов, избегать `default` экспортов.
|
||||
|
||||
## Принципы рефакторинга документации
|
||||
- Один смысл — один раздел. Не размазывайте правила по нескольким файлам.
|
||||
- Если правило уже описано (например, нейминг), не повторяйте его в других разделах — добавляйте недостающее только в профильный файл.
|
||||
- При переносе из `OLD_parts/` переписывайте кратко и по делу, исключая устаревшее и дубли.
|
||||
- Новые правила добавляйте только в подходящий раздел; если такого нет — создайте его и обновите sidebar.
|
||||
|
||||
## Testing Guidelines
|
||||
Тестовая инфраструктура отсутствует. Если добавляете тесты или проверяющие скрипты, добавьте соответствующий `npm`‑скрипт и опишите его в этом документе.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- История коммитов содержит короткие однословные сообщения (например, `sync`, `first`) — формального стандарта не видно.
|
||||
- Для PR: укажите цель изменений, список затронутых разделов и отметьте, обновляли ли `RULES.md` и `sidebar`.
|
||||
- Если меняется структура документации или навигация, приложите краткий скриншот/описание результата.
|
||||
463
RULES.md
463
RULES.md
@@ -44,7 +44,7 @@
|
||||
|
||||
# Архитектура
|
||||
|
||||
Архитектура построена на FSD (Feature‑Sliced Design) и строгих границах модулей.
|
||||
Архитектура построена на FSD (`Feature‑Sliced Design`) и строгих границах модулей.
|
||||
Цель — разделить ответственность, упростить сопровождение и контроль зависимостей.
|
||||
|
||||
## Принципы
|
||||
@@ -54,16 +54,27 @@
|
||||
- Открывать наружу только публичный API модулей.
|
||||
- Не допускать циклических зависимостей.
|
||||
|
||||
## Слои
|
||||
## Слои (FSD)
|
||||
|
||||
- **app** — инициализация приложения, роутинг, конфигурации, глобальные провайдеры.
|
||||
- **screens** — экраны и их композиция.
|
||||
- **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`.
|
||||
@@ -167,20 +178,22 @@ const title = 'Привет, ' + name;
|
||||
|
||||
- В именованных импортах использовать пробелы внутри фигурных скобок.
|
||||
- Типы импортировать через `import type`.
|
||||
- `default` импорт и экспорт избегать, использовать именованные.
|
||||
- `default` экспорт избегать, использовать именованные. `default` импорт допустим (например, стили CSS Modules, сторонние библиотеки).
|
||||
- Избегать импорта всего модуля через `*`.
|
||||
|
||||
**Хорошо**
|
||||
```ts
|
||||
import { MyComponent } from 'MyComponent';
|
||||
import type { User } from '../model/types';
|
||||
import styles from './styles/button.module.css';
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
```ts
|
||||
// Плохо: default импорт и отсутствие пробелов в именованном импорте.
|
||||
import MyComponent from 'MyComponent';
|
||||
// Плохо: отсутствие пробелов в именованном импорте.
|
||||
import type {User} from '../model/types';
|
||||
// Плохо: default экспорт.
|
||||
export default MyComponent;
|
||||
```
|
||||
|
||||
## Ранние возвраты (early return)
|
||||
@@ -314,8 +327,12 @@ src/
|
||||
└── shared/
|
||||
└── ui/
|
||||
└── icon/
|
||||
├── styles/
|
||||
│ └── icon.module.css
|
||||
├── types/
|
||||
│ └── icon.interface.ts
|
||||
├── icon.ui.tsx
|
||||
└── icon.module.css
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
@@ -480,7 +497,7 @@ export enum TodoFilter {
|
||||
/**
|
||||
* Параметры кнопки.
|
||||
*/
|
||||
interface IOwnProps extends HTMLAttributes<HTMLDivElement> {
|
||||
interface ButtonProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/** Текст кнопки. */
|
||||
label: string;
|
||||
/** Обработчик клика по кнопке. */
|
||||
@@ -490,7 +507,7 @@ interface IOwnProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Кнопка с пользовательскими стилями.
|
||||
*/
|
||||
export const Button:FC<IOwnProps> = ({ className, label, onClick, ...htmlAttr }) => {
|
||||
export const Button: FC<ButtonProps> = ({ className, label, onClick, ...htmlAttr }) => {
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
button
|
||||
@@ -560,40 +577,91 @@ const parse = (value: any) => value;
|
||||
|
||||
## Базовая структура проекта
|
||||
|
||||
**Хорошо**
|
||||
Слои FSD не зависят от фреймворка. Различается только содержимое `app/` — в React SPA это конфигурация роутинга, в Next.js — системные файлы фреймворка (`layout.tsx`, `page.tsx`, route-сегменты).
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/ # Инициализация приложения, роутинг, провайдеры
|
||||
│ ├── config/ # Конфигурации и константы уровня приложения
|
||||
│ ├── providers/ # Провайдеры и обёртки приложения
|
||||
│ ├── routing/ # Конфигурация маршрутов
|
||||
│ └── index.ts # Публичный API слоя
|
||||
├── screens/ # Экраны приложения
|
||||
│ └── Profile/ # Экран профиля
|
||||
│ └── ... # ui/model/index.ts
|
||||
├── app/ # Инициализация приложения (см. «Слой app/»)
|
||||
├── screens/ # UI-компоненты страниц
|
||||
│ └── profile/
|
||||
│ ├── profile.screen.tsx
|
||||
│ └── index.ts
|
||||
├── layouts/ # Общие шаблоны и каркасы страниц
|
||||
│ └── MainLayout/ # Основной layout
|
||||
│ └── ... # ui/index.ts
|
||||
│ └── main-layout/
|
||||
│ ├── main-layout.layout.tsx
|
||||
│ └── index.ts
|
||||
├── widgets/ # Крупные блоки интерфейса
|
||||
│ └── Header/ # Виджет шапки
|
||||
│ └── ... # ui/index.ts
|
||||
│ └── header/
|
||||
│ ├── header.widget.tsx
|
||||
│ └── index.ts
|
||||
├── features/ # Пользовательские сценарии
|
||||
│ └── auth-by-email/ # Авторизация по email
|
||||
│ └── ... # ui/model/api/index.ts
|
||||
│ └── auth-by-email/
|
||||
│ ├── ui/
|
||||
│ │ └── login-form.ui.tsx
|
||||
│ ├── model/
|
||||
│ │ └── auth-by-email.store.ts
|
||||
│ ├── auth-by-email.feature.tsx
|
||||
│ └── index.ts
|
||||
├── entities/ # Бизнес-сущности
|
||||
│ └── user/ # Сущность пользователя
|
||||
│ └── ... # ui/model/api/lib/index.ts
|
||||
│ └── user/
|
||||
│ ├── model/
|
||||
│ │ └── user.store.ts
|
||||
│ ├── user.entity.tsx
|
||||
│ └── index.ts
|
||||
└── shared/ # Общие ресурсы проекта
|
||||
├── ui/ # Базовые UI-компоненты
|
||||
├── ui/ # Повторно используемые UI-элементы
|
||||
│ └── icon/
|
||||
│ ├── styles/
|
||||
│ │ └── icon.module.css
|
||||
│ ├── types/
|
||||
│ │ └── icon.interface.ts
|
||||
│ ├── icon.ui.tsx
|
||||
│ └── index.ts
|
||||
├── lib/ # Утилиты и хелперы
|
||||
├── services/ # Общие сервисы и клиенты
|
||||
├── config/ # Общие конфигурации
|
||||
├── styles/ # Глобальные стили и токены
|
||||
├── config/ # Общие конфигурации и константы
|
||||
└── assets/ # Ресурсы
|
||||
├── images/ # Изображения
|
||||
├── icons/ # Иконки
|
||||
├── fonts/ # Шрифты
|
||||
└── video/ # Видео
|
||||
├── 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 />;
|
||||
}
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
@@ -608,8 +676,12 @@ src/
|
||||
|
||||
## Правила организации
|
||||
|
||||
- В слоях 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`, `lib`, `api`.
|
||||
- Внутренние реализации разделяются на `ui/`, `model/`, `styles/`, `helpers/`, `lib/`, `api/`.
|
||||
- Публичный API модуля объявляется в `index.ts`.
|
||||
- Внутренние файлы не импортируются напрямую извне.
|
||||
- Не смешивать ответственность разных слоёв в одном модуле.
|
||||
@@ -658,7 +730,7 @@ export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
```tsx
|
||||
import type { FC } from 'react'
|
||||
import { cl } from 'clsx'
|
||||
import cl from 'clsx'
|
||||
import type { ContainerProps } from './types/container.interface'
|
||||
import styles from './styles/container.module.scss'
|
||||
|
||||
@@ -691,7 +763,7 @@ export const Container: FC<ContainerProps> = ({ className, ...htmlAttr }) => {
|
||||
export { Container } from './container.ui'
|
||||
```
|
||||
|
||||
## Генерация
|
||||
## Шаблоны и генерация кода
|
||||
|
||||
Генерация нужна, чтобы быстро создавать компоненты с единым каркасом и не допускать расхождений в структуре. Это даёт одинаковые папки и имена файлов, уменьшает ручные ошибки и ускоряет старт работы.
|
||||
|
||||
@@ -742,15 +814,18 @@ npx @gromlab/create component button
|
||||
```text
|
||||
.templates/ # корневая папка всех шаблонов
|
||||
├── component/ # шаблон компонента
|
||||
│ └── {{{name.pascalCase}}}/
|
||||
│ ├── index.ts
|
||||
│ ├── {{{name.pascalCase}}}.tsx
|
||||
│ └── {{{name.pascalCase}}}.module.css
|
||||
│ └── {{name.kebabCase}}/
|
||||
│ ├── styles/
|
||||
│ │ └── {{name.kebabCase}}.module.css
|
||||
│ ├── types/
|
||||
│ │ └── {{name.kebabCase}}.interface.ts
|
||||
│ ├── {{name.kebabCase}}.ui.tsx
|
||||
│ └── index.ts
|
||||
└── store/ # шаблон Zustand стора
|
||||
└── {{{name.camelCase}}}Store/
|
||||
├── index.ts
|
||||
├── {{{name.camelCase}}}Store.ts
|
||||
└── {{{name.camelCase}}}Store.type.ts
|
||||
└── {{name.kebabCase}}/
|
||||
├── {{name.kebabCase}}.store.ts
|
||||
├── {{name.kebabCase}}.type.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
## Синтаксис
|
||||
@@ -793,18 +868,30 @@ export const {{name.pascalCase}} = () => {
|
||||
|
||||
```ts
|
||||
// .templates/component/index.ts
|
||||
export * from './{{name.pascalCase}}'
|
||||
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.pascalCase}}.tsx
|
||||
import { FC, HTMLAttributes } from "react";
|
||||
import styles from './{{name.kebabCase}}.module.css'
|
||||
// .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'
|
||||
|
||||
interface IOwnProps extends HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export const {{name.pascalCase}}:FC<IOwnProps> = ({className, ...htmlAttr}) => {
|
||||
/**
|
||||
* {{name.pascalCase}}.
|
||||
*/
|
||||
export const {{name.pascalCase}}: FC<{{name.pascalCase}}Props> = ({ className, ...htmlAttr }) => {
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{{name.kebabCase}}
|
||||
@@ -814,7 +901,7 @@ export const {{name.pascalCase}}:FC<IOwnProps> = ({className, ...htmlAttr}) => {
|
||||
```
|
||||
|
||||
```css
|
||||
/* .templates/component/{{name.kebabCase}}.module.css */
|
||||
/* .templates/component/styles/{{name.kebabCase}}.module.css */
|
||||
.root {
|
||||
|
||||
}
|
||||
@@ -823,6 +910,271 @@ export const {{name.pascalCase}}:FC<IOwnProps> = ({className, ...htmlAttr}) => {
|
||||
|
||||
<a name="9-stylesmd"></a>
|
||||
|
||||
# Стили
|
||||
|
||||
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
|
||||
|
||||
## Общие правила
|
||||
|
||||
- Только **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="10-images-spritesmd"></a>
|
||||
@@ -851,3 +1203,8 @@ export const {{name.pascalCase}}:FC<IOwnProps> = ({className, ...htmlAttr}) => {
|
||||
|
||||
<a name="16-localizationmd"></a>
|
||||
|
||||
|
||||
|
||||
<a name="17-svg-spritesmd"></a>
|
||||
|
||||
# SVG-спрайты
|
||||
|
||||
5
parts/17-svg-sprites.md
Normal file
5
parts/17-svg-sprites.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: SVG-спрайты
|
||||
---
|
||||
|
||||
# SVG-спрайты
|
||||
@@ -16,8 +16,8 @@ title: Архитектура
|
||||
|
||||
## Слои (FSD)
|
||||
|
||||
- **app** — инициализация приложения, роутинг, конфигурации, глобальные провайдеры.
|
||||
- **screens** — экраны и их композиция.
|
||||
- **app** — инициализация приложения: провайдеры, глобальные стили. В Next.js эта же папка `app/` дополнительно содержит системные файлы роутинга (`layout.tsx`, `page.tsx`).
|
||||
- **screens** — UI-компоненты страниц. Каждый экран — отдельный компонент, который собирает виджеты и фичи конкретной страницы. Роутинг только использует эти компоненты — он не является частью слоя `screens`. В Next.js файлы `page.tsx` остаются тонкими: импортируют экран и рендерят его.
|
||||
- **layouts** — каркас и шаблоны страниц.
|
||||
- **widgets** — крупные блоки интерфейса, собирающие несколько сценариев.
|
||||
- **features** — отдельные пользовательские действия и сценарии.
|
||||
|
||||
@@ -8,29 +8,25 @@ title: Структура проекта
|
||||
|
||||
## Базовая структура проекта
|
||||
|
||||
**Хорошо**
|
||||
Слои FSD не зависят от фреймворка. Различается только содержимое `app/` — в React SPA это конфигурация роутинга, в Next.js — системные файлы фреймворка (`layout.tsx`, `page.tsx`, route-сегменты).
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/ # Инициализация приложения, роутинг, провайдеры
|
||||
│ ├── config/ # Конфигурации и константы уровня приложения
|
||||
│ ├── providers/ # Провайдеры и обёртки приложения
|
||||
│ ├── routing/ # Конфигурация маршрутов
|
||||
│ ├── styles/ # Глобальные стили, CSS-переменные, custom media
|
||||
│ └── index.ts # Публичный API слоя
|
||||
├── screens/ # Экраны приложения
|
||||
│ └── profile/ # Экран профиля
|
||||
├── app/ # Инициализация приложения (см. «Слой app/»)
|
||||
├── screens/ # UI-компоненты страниц
|
||||
│ └── profile/
|
||||
│ ├── profile.screen.tsx
|
||||
│ └── index.ts
|
||||
├── layouts/ # Общие шаблоны и каркасы страниц
|
||||
│ └── main-layout/ # Основной layout
|
||||
│ └── main-layout/
|
||||
│ ├── main-layout.layout.tsx
|
||||
│ └── index.ts
|
||||
├── widgets/ # Крупные блоки интерфейса
|
||||
│ └── header/ # Виджет шапки
|
||||
│ └── header/
|
||||
│ ├── header.widget.tsx
|
||||
│ └── index.ts
|
||||
├── features/ # Пользовательские сценарии
|
||||
│ └── auth-by-email/ # Авторизация по email
|
||||
│ └── auth-by-email/
|
||||
│ ├── ui/
|
||||
│ │ └── login-form.ui.tsx
|
||||
│ ├── model/
|
||||
@@ -38,7 +34,7 @@ src/
|
||||
│ ├── auth-by-email.feature.tsx
|
||||
│ └── index.ts
|
||||
├── entities/ # Бизнес-сущности
|
||||
│ └── user/ # Сущность пользователя
|
||||
│ └── user/
|
||||
│ ├── model/
|
||||
│ │ └── user.store.ts
|
||||
│ ├── user.entity.tsx
|
||||
@@ -54,12 +50,49 @@ src/
|
||||
│ └── index.ts
|
||||
├── lib/ # Утилиты и хелперы
|
||||
├── services/ # Общие сервисы и клиенты
|
||||
├── config/ # Общие конфигурации
|
||||
├── config/ # Общие конфигурации и константы
|
||||
└── assets/ # Ресурсы
|
||||
├── images/ # Изображения
|
||||
├── icons/ # Иконки
|
||||
├── fonts/ # Шрифты
|
||||
└── video/ # Видео
|
||||
├── 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 />;
|
||||
}
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
|
||||
Reference in New Issue
Block a user