diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 7f9f035..2047ec6 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -22,8 +22,8 @@ export default defineConfig({ text: 'Прикладные разделы', items: [ { text: 'Структура проекта', link: '/parts/7-project-structure' }, - { text: 'Шаблоны генерации кода', link: '/parts/17-templates-generation' }, - { text: 'Компоненты', link: '/parts/8-components' }, + { 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/11-video' }, diff --git a/RULES.md b/RULES.md index 254ab16..f3643f9 100644 --- a/RULES.md +++ b/RULES.md @@ -5,19 +5,39 @@ Базовый стек технологий и библиотек, на который опираются проекты и примеры в документации. -## Что используем обычно +## Что используем -- **TypeScript** — типизация всей логики и компонентов. +### Стек +- **React/TypeScript** — основной стек для UI и приложения. +- **Next.js** — для продуктовых сайтов. + +### Архитектура - **FSD (Feature-Sliced Design)** — структура проекта и границы модулей. -- **React + Next.js** — основной стек для UI и приложения. + +### UI компоненты - **Mantine UI** — базовые UI-компоненты. -- **SWR** — получение данных по GET (кеширование и revalidate). + +### Fetch (API) +- **@gromlab/api-codegen** — генерация API‑клиентов и типов. +- **SWR** — получение, кеширование, ревалидация, дедубликация. +- **SWR (useSWRSubscription)** - сокеты, реалтайм подписки. + +### Store - **Zustand** — глобальное состояние. + +### Локализация - **i18next (i18n)** — локализация всех пользовательских текстов. + +### Тестирование - **Vitest** — тестирование. -- **clsx** — конкатенация CSS‑классов. + +### Стили - **PostCSS Modules** — изоляция стилей. - **Mobile First** — подход к адаптивной верстке. +- **clsx** — конкатенация CSS‑классов. + +### Генерация +- **@gromlab/create** — шаблонизатор для создания слоёв и других файлов из шаблонов. @@ -595,7 +615,7 @@ src/ - Не смешивать ответственность разных слоёв в одном модуле. - + # Компоненты @@ -671,6 +691,11 @@ export const Container: FC = ({ className, ...htmlAttr }) => { export { Container } from './container.ui' ``` +## Генерация + +Генерация нужна, чтобы быстро создавать компоненты с единым каркасом и не допускать расхождений в структуре. Это даёт одинаковые папки и имена файлов, уменьшает ручные ошибки и ускоряет старт работы. + +Используйте **@gromlab/create** для создания базового шаблона компонента. После генерации проверьте название компонента/файлов и заполните описание назначения. ## Вложенные (дочерние) компоненты @@ -679,6 +704,123 @@ export { Container } from './container.ui' Вложенные компоненты подчиняются тем же правилам по структуре, именованию и стилю (включая папку `styles/` для их стилей). + + +# Шаблоны генерации кода + +Подход к использованию шаблонов и генерации кода для стандартизации структуры и ускорения разработки. + +## Что генерируем + +- Компоненты (`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.pascalCase}}}/ +│ ├── index.ts +│ ├── {{{name.pascalCase}}}.tsx +│ └── {{{name.pascalCase}}}.module.css +└── store/ # шаблон Zustand стора + └── {{{name.camelCase}}}Store/ + ├── index.ts + ├── {{{name.camelCase}}}Store.ts + └── {{{name.camelCase}}}Store.type.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
{{name}}
+} +``` + +## Шаблон компонента + +Рекомендуемая структура компонента, создаётся генератором по шаблону. + +```ts +// .templates/component/index.ts +export * from './{{name.pascalCase}}' +``` + +```tsx +// .templates/component/{{name.pascalCase}}.tsx +import { FC, HTMLAttributes } from "react"; +import styles from './{{name.kebabCase}}.module.css' +import cl from 'clsx' + +interface IOwnProps extends HTMLAttributes {} + +export const {{name.pascalCase}}:FC = ({className, ...htmlAttr}) => { + return ( +
+ {{name.kebabCase}} +
+ ) +} +``` + +```css +/* .templates/component/{{name.kebabCase}}.module.css */ +.root { + +} +``` + + diff --git a/parts/2-architecture.md b/parts/2-architecture.md index 5dfc128..90b199a 100644 --- a/parts/2-architecture.md +++ b/parts/2-architecture.md @@ -4,7 +4,7 @@ title: Архитектура # Архитектура -Архитектура построена на FSD (Feature‑Sliced Design) и строгих границах модулей. +Архитектура построена на FSD (`Feature‑Sliced Design`) и строгих границах модулей. Цель — разделить ответственность, упростить сопровождение и контроль зависимостей. ## Принципы @@ -14,7 +14,7 @@ title: Архитектура - Открывать наружу только публичный API модулей. - Не допускать циклических зависимостей. -## Слои +## Слои (FSD) - **app** — инициализация приложения, роутинг, конфигурации, глобальные провайдеры. - **screens** — экраны и их композиция. @@ -24,6 +24,17 @@ title: Архитектура - **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`. diff --git a/parts/3-code-style.md b/parts/3-code-style.md index 9a286a3..ed6b06b 100644 --- a/parts/3-code-style.md +++ b/parts/3-code-style.md @@ -78,20 +78,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) diff --git a/parts/4-naming.md b/parts/4-naming.md index 509b3e1..af32413 100644 --- a/parts/4-naming.md +++ b/parts/4-naming.md @@ -75,8 +75,12 @@ src/ └── shared/ └── ui/ └── icon/ + ├── styles/ + │ └── icon.module.css + ├── types/ + │ └── icon.interface.ts ├── icon.ui.tsx - └── icon.module.css + └── index.ts ``` **Плохо** diff --git a/parts/6-typing.md b/parts/6-typing.md index 0dcadf8..0321603 100644 --- a/parts/6-typing.md +++ b/parts/6-typing.md @@ -24,7 +24,7 @@ title: Типизация /** * Параметры кнопки. */ -interface IOwnProps extends HTMLAttributes { +interface ButtonProps extends HTMLAttributes { /** Текст кнопки. */ label: string; /** Обработчик клика по кнопке. */ @@ -34,7 +34,7 @@ interface IOwnProps extends HTMLAttributes { /** * Кнопка с пользовательскими стилями. */ -export const Button:FC = ({ className, label, onClick, ...htmlAttr }) => { +export const Button: FC = ({ className, label, onClick, ...htmlAttr }) => { return (
button diff --git a/parts/7-project-structure.md b/parts/7-project-structure.md index 5b47db5..32c7df4 100644 --- a/parts/7-project-structure.md +++ b/parts/7-project-structure.md @@ -15,28 +15,46 @@ src/ │ ├── config/ # Конфигурации и константы уровня приложения │ ├── providers/ # Провайдеры и обёртки приложения │ ├── routing/ # Конфигурация маршрутов +│ ├── styles/ # Глобальные стили, CSS-переменные, custom media │ └── index.ts # Публичный API слоя ├── screens/ # Экраны приложения -│ └── Profile/ # Экран профиля -│ └── ... # ui/model/index.ts +│ └── profile/ # Экран профиля +│ ├── profile.screen.tsx +│ └── index.ts ├── layouts/ # Общие шаблоны и каркасы страниц -│ └── MainLayout/ # Основной layout -│ └── ... # ui/index.ts +│ └── main-layout/ # Основной 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 +│ ├── 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 +│ ├── 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/ # Глобальные стили и токены └── assets/ # Ресурсы ├── images/ # Изображения ├── icons/ # Иконки @@ -56,8 +74,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`. - Внутренние файлы не импортируются напрямую извне. - Не смешивать ответственность разных слоёв в одном модуле. diff --git a/parts/8-components.md b/parts/8-0-components.md similarity index 97% rename from parts/8-components.md rename to parts/8-0-components.md index 26a2277..de04047 100644 --- a/parts/8-components.md +++ b/parts/8-0-components.md @@ -1,3 +1,7 @@ +--- +title: Компоненты +--- + # Компоненты Раздел описывает правила создания UI‑компонентов. Эти правила обязательны для всех слоёв FSD: `app`, `screens`, `layouts`, `widgets`, `features`, `entities`, `shared`. @@ -39,7 +43,7 @@ export interface ContainerProps extends HTMLAttributes {} ```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' @@ -72,7 +76,7 @@ export const Container: FC = ({ className, ...htmlAttr }) => { export { Container } from './container.ui' ``` -## Генерация +## Шаблоны и генерация кода Генерация нужна, чтобы быстро создавать компоненты с единым каркасом и не допускать расхождений в структуре. Это даёт одинаковые папки и имена файлов, уменьшает ручные ошибки и ускоряет старт работы. diff --git a/parts/17-templates-generation.md b/parts/8-1-templates-generation.md similarity index 70% rename from parts/17-templates-generation.md rename to parts/8-1-templates-generation.md index 123ff96..3bdedfb 100644 --- a/parts/17-templates-generation.md +++ b/parts/8-1-templates-generation.md @@ -1,3 +1,7 @@ +--- +title: Шаблоны генерации кода +--- + # Шаблоны генерации кода Подход к использованию шаблонов и генерации кода для стандартизации структуры и ускорения разработки. @@ -34,15 +38,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 ``` ## Синтаксис @@ -85,18 +92,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 {} ``` ```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 {} - -export const {{name.pascalCase}}:FC = ({className, ...htmlAttr}) => { +/** + * {{name.pascalCase}}. + */ +export const {{name.pascalCase}}: FC<{{name.pascalCase}}Props> = ({ className, ...htmlAttr }) => { return (
{{name.kebabCase}} @@ -106,8 +125,8 @@ export const {{name.pascalCase}}:FC = ({className, ...htmlAttr}) => { ``` ```css -/* .templates/component/{{name.kebabCase}}.module.css */ +/* .templates/component/styles/{{name.kebabCase}}.module.css */ .root { - + } ``` diff --git a/parts/9-styles.md b/parts/9-styles.md index e69de29..5ce2a88 100644 --- a/parts/9-styles.md +++ b/parts/9-styles.md @@ -0,0 +1,269 @@ +--- +title: Стили +--- + +# Стили + +Раздел описывает правила написания 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. +- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.