mirror of
https://github.com/gromlab-ru/svg-sprites.git
synced 2026-07-21 20:30:16 +03:00
Merge pull request #1 from gromov-sergei/feat/github-migration-localization
feat: локализовать документацию и автоматизировать релизы
This commit is contained in:
42
.github/workflows/ci.yml
vendored
Normal file
42
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify and build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Verify
|
||||
run: npm run verify
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
107
.github/workflows/release.yml
vendored
Normal file
107
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish package and skills
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
environment: npm
|
||||
|
||||
steps:
|
||||
- name: Checkout release tag
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Update npm
|
||||
run: npm install --global npm@latest
|
||||
|
||||
- name: Check release version
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
node --input-type=module -e '
|
||||
import { readFileSync } from "node:fs"
|
||||
|
||||
const packageJson = JSON.parse(readFileSync("package.json", "utf8"))
|
||||
const expectedTag = `v${packageJson.version}`
|
||||
|
||||
if (process.env.RELEASE_TAG !== expectedTag) {
|
||||
throw new Error(
|
||||
`Release tag ${process.env.RELEASE_TAG} does not match package version ${expectedTag}`,
|
||||
)
|
||||
}
|
||||
'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Verify
|
||||
run: npm run verify
|
||||
|
||||
- name: Build package
|
||||
run: npm run build
|
||||
|
||||
- name: Build skills
|
||||
run: npm run build:skill
|
||||
|
||||
- name: Prepare release files
|
||||
run: mkdir release
|
||||
|
||||
- name: Pack npm package
|
||||
run: npm pack --ignore-scripts --pack-destination release
|
||||
|
||||
- name: Pack skills
|
||||
working-directory: skills/artifacts
|
||||
run: |
|
||||
zip -r ../../release/svg-sprites.zip svg-sprites
|
||||
zip -r ../../release/svg-sprites-ru.zip svg-sprites-ru
|
||||
|
||||
- name: Create checksums
|
||||
run: sha256sum release/* > release/SHA256SUMS
|
||||
|
||||
- name: Publish npm package
|
||||
env:
|
||||
IS_PRERELEASE: ${{ github.event.release.prerelease }}
|
||||
run: |
|
||||
PACKAGE_SPEC=$(node -p "const pkg = require('./package.json'); pkg.name + '@' + pkg.version")
|
||||
|
||||
if npm view "$PACKAGE_SPEC" version --json > /dev/null 2>&1; then
|
||||
echo "$PACKAGE_SPEC is already published"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
DIST_TAG=latest
|
||||
if [ "$IS_PRERELEASE" = "true" ]; then
|
||||
DIST_TAG=next
|
||||
fi
|
||||
|
||||
npm publish release/*.tgz --access public --provenance --tag "$DIST_TAG"
|
||||
|
||||
- name: Upload GitHub Release assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
release/*.tgz
|
||||
release/*.zip
|
||||
release/SHA256SUMS
|
||||
overwrite_files: true
|
||||
333
README.md
333
README.md
@@ -1,77 +1,80 @@
|
||||
# @gromlab/svg-sprites
|
||||
|
||||
🇬🇧 English | [🇷🇺 Русский](README_RU.md)
|
||||
|
||||
 
|
||||
|
||||
CLI для генерации SVG-спрайтов и типизированных компонентов иконок для React и Next.js.
|
||||
A CLI for generating SVG sprites and typed icon components for React and Next.js.
|
||||
|
||||

|
||||

|
||||
|
||||
## Навигация
|
||||
## Navigation
|
||||
|
||||
- [Возможности](#возможности)
|
||||
- [Таблица поддержки](#таблица-поддержки)
|
||||
- [Требования](#требования)
|
||||
- [Быстрый старт](#быстрый-старт)
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [Конфигурация](#конфигурация)
|
||||
- [Features](#features)
|
||||
- [Support matrix](#support-matrix)
|
||||
- [Requirements](#requirements)
|
||||
- [Quick start](#quick-start)
|
||||
- [React + Vite](docs/en/react-vite.md)
|
||||
- [React + Webpack 5](docs/en/react-webpack.md)
|
||||
- [Next.js App Router](docs/en/next-app.md)
|
||||
- [Next.js Pages Router](docs/en/next-pages.md)
|
||||
- [Configuration](#configuration)
|
||||
- [React](#react)
|
||||
- [Next.js](#nextjs)
|
||||
- [Множественные спрайты](#множественные-спрайты)
|
||||
- [Multiple sprites](#multiple-sprites)
|
||||
- [TypeScript](#typescript)
|
||||
- [Форматы спрайтов](#форматы-спрайтов)
|
||||
- [Способы отображения](#способы-отображения)
|
||||
- [Трансформации](#трансформации)
|
||||
- [Управление цветом иконок](#управление-цветом-иконок)
|
||||
- [Кеширование](#кеширование)
|
||||
- [Sprite formats](#sprite-formats)
|
||||
- [Rendering methods](#rendering-methods)
|
||||
- [Transformations](#transformations)
|
||||
- [Icon color management](#icon-color-management)
|
||||
- [Caching](#caching)
|
||||
- [SpriteViewer](#spriteviewer)
|
||||
- [Миграция с 0.1.x](docs/ru/migration-1.md)
|
||||
- [Документация](#документация)
|
||||
- [Migrating from 0.1.x](docs/en/migration-1.md)
|
||||
- [Documentation](#documentation)
|
||||
|
||||
## Возможности
|
||||
## Features
|
||||
|
||||
- **TypeScript-friendly** — типизированные React-компоненты, union-типы и runtime-списки доступных иконок.
|
||||
- **Чистая генерация** — generated-файлы автоматически исключаются из Git, спрайт не нужно вручную размещать в `public`, а генератор обновляет только принадлежащие ему файлы.
|
||||
- **Общие иконки без копирования** — SVG из локальной папки и `inputFiles` объединяются в один спрайт; один файл можно использовать в нескольких спрайтах.
|
||||
- **Встроенное интерактивное превью** — `<SpriteViewer>` подключается как страница приложения и показывает переданные React- и Next.js-спрайты с поиском, настройкой цветов и примерами использования.
|
||||
- **Настраиваемые трансформации SVG** — удаление `width` и `height` с сохранением `viewBox`, замена исходных цветов на CSS-переменные и transitions для `fill` и `stroke`.
|
||||
- **Отдельный кешируемый SVG asset** — SVG path-данные не попадают в JavaScript chunks, а сборщик выпускает файл с content hash.
|
||||
- **Множественные спрайты** — независимые React- и Next.js-модули со своими компонентами, типами и SVG assets.
|
||||
- **Server-first Next.js** — generated-компоненты работают в Server Components, SSR и SSG без директивы `'use client'`.
|
||||
- **Форматы под разные сценарии** — React и Next.js используют `stack`, legacy-режим также поддерживает `symbol` для существующих интеграций.
|
||||
- **AI-agent friendly** - the repository includes a ready-to-use skill with up-to-date documentation for configuring, migrating, and troubleshooting `@gromlab/svg-sprites`.
|
||||
- **TypeScript-friendly** - typed React components, union types, and runtime lists of available icons.
|
||||
- **Clean generation** - generated files are automatically excluded from Git, the sprite does not need to be placed in `public` manually, and the generator updates only files it owns.
|
||||
- **Shared icons without copying** - SVGs from the local folder and `inputFiles` are merged into a single sprite; one file can be used in multiple sprites.
|
||||
- **Built-in interactive preview** - `<SpriteViewer>` is integrated as an application page and displays the provided React and Next.js sprites with search, color controls, and usage examples.
|
||||
- **Configurable SVG transformations** - remove `width` and `height` while preserving `viewBox`, replace source colors with CSS variables, and add transitions for `fill` and `stroke`.
|
||||
- **Separate cacheable SVG asset** - SVG path data does not end up in JavaScript chunks, and the bundler emits a file with a content hash.
|
||||
- **Multiple sprites** - independent React and Next.js modules with their own components, types, and SVG assets.
|
||||
- **Server-first Next.js** - generated components work in Server Components, SSR, and SSG without the `'use client'` directive.
|
||||
- **Formats for different use cases** - React and Next.js use `stack`; legacy mode also supports `symbol` for existing integrations.
|
||||
|
||||
## Таблица поддержки
|
||||
## Support matrix
|
||||
|
||||
| Среда | Ключ мода API | Статус |
|
||||
| Environment | API mode key | Status |
|
||||
|---|---|---|
|
||||
| React + Vite | `react@vite` | Готово |
|
||||
| React + Webpack 5 | `react@webpack` | Готово |
|
||||
| Next.js 16.2+ App Router + Turbopack | `next@app/turbopack` | Готово |
|
||||
| Next.js 13.4+ App Router + Webpack 5 | `next@app/webpack` | Готово |
|
||||
| Next.js 16.2+ Pages Router + Turbopack | `next@pages/turbopack` | Готово |
|
||||
| Next.js 12.2+ Pages Router + Webpack 5 | `next@pages/webpack` | Готово |
|
||||
| Vue | — | Скоро |
|
||||
| Standalone | — | Скоро |
|
||||
| React + Vite | `react@vite` | Ready |
|
||||
| React + Webpack 5 | `react@webpack` | Ready |
|
||||
| Next.js 16.2+ App Router + Turbopack | `next@app/turbopack` | Ready |
|
||||
| Next.js 13.4+ App Router + Webpack 5 | `next@app/webpack` | Ready |
|
||||
| Next.js 16.2+ Pages Router + Turbopack | `next@pages/turbopack` | Ready |
|
||||
| Next.js 12.2+ Pages Router + Webpack 5 | `next@pages/webpack` | Ready |
|
||||
| Vue | - | Coming soon |
|
||||
| Standalone | - | Coming soon |
|
||||
|
||||
## Требования
|
||||
## Requirements
|
||||
|
||||
- Node.js 18 или новее;
|
||||
- пакет распространяется только как ESM и подключается через `import`;
|
||||
- React 18 или 19 требуется только для generated-компонентов и точки входа `@gromlab/svg-sprites/react`;
|
||||
- для типизации subpath exports используйте TypeScript 5+ с `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
|
||||
- Node.js 18 or newer;
|
||||
- the package is distributed as ESM only and is loaded via `import`;
|
||||
- React 18 or 19 is required only for generated components and the `@gromlab/svg-sprites/react` entry point;
|
||||
- for subpath export typings, use TypeScript 5+ with `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
|
||||
|
||||
## Быстрый старт
|
||||
## Quick start
|
||||
|
||||
Для быстрого старта воспользуйтесь инструкцией для вашего стека:
|
||||
For a quick start, follow the guide for your stack:
|
||||
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [React + Vite](docs/en/react-vite.md)
|
||||
- [React + Webpack 5](docs/en/react-webpack.md)
|
||||
- [Next.js App Router](docs/en/next-app.md)
|
||||
- [Next.js Pages Router](docs/en/next-pages.md)
|
||||
|
||||
## Конфигурация
|
||||
## Configuration
|
||||
|
||||
### React
|
||||
|
||||
@@ -80,7 +83,7 @@ import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'Иконки файлового менеджера',
|
||||
description: 'File manager icons',
|
||||
inputFolder: './icons',
|
||||
inputFiles: [
|
||||
'../../shared/icons/check.svg',
|
||||
@@ -94,78 +97,78 @@ export default defineReactSpriteConfig({
|
||||
})
|
||||
```
|
||||
|
||||
| Опция | Тип | По умолчанию | Назначение |
|
||||
| Option | Type | Default | Purpose |
|
||||
|---|---|---|---|
|
||||
| `name` | `string` | Имя папки | Имя спрайта, компонента и публичных типов |
|
||||
| `description` | `string` | Нет | Описание для типов и debug-манифеста |
|
||||
| `inputFolder` | `string` | `./icons` | Папка с исходными SVG относительно конфига |
|
||||
| `inputFiles` | `string[]` | `[]` | Дополнительные SVG-файлы относительно конфига |
|
||||
| `transform` | `TransformOptions` | Все включены | [Настройки трансформации](#трансформации) исходных SVG |
|
||||
| `generatedNotice` | `boolean` | `true` | Полное либо короткое предупреждение в generated-файлах |
|
||||
| `name` | `string` | Folder name | Name of the sprite, component, and public types |
|
||||
| `description` | `string` | None | Description for types and the debug manifest |
|
||||
| `inputFolder` | `string` | `./icons` | Folder containing source SVGs, relative to the config |
|
||||
| `inputFiles` | `string[]` | `[]` | Additional SVG files, relative to the config |
|
||||
| `transform` | `TransformOptions` | All enabled | [Transformation settings](#transformations) for source SVGs |
|
||||
| `generatedNotice` | `boolean` | `true` | Full or short warning in generated files |
|
||||
|
||||
`inputFolder` и `inputFiles` объединяются в один спрайт, поэтому один SVG-файл можно использовать в нескольких спрайтах без копирования. Если неявной папки `./icons` нет, но `inputFiles` заполнен, генерация продолжается только по списку. Явно указанная отсутствующая папка считается ошибкой. Одинаковые пути дедуплицируются, а разные файлы с одинаковым именем иконки считаются ошибкой.
|
||||
`inputFolder` and `inputFiles` are merged into a single sprite, so one SVG file can be used in multiple sprites without copying. If the implicit `./icons` folder does not exist but `inputFiles` is populated, generation continues using only the list. An explicitly specified missing folder is an error. Duplicate paths are deduplicated, while different files with the same icon name are treated as an error.
|
||||
|
||||
`name` записывается в kebab-case и должно начинаться с латинской буквы. React и Next.js presets создают формат `stack`.
|
||||
`name` is stored in kebab-case and must start with a Latin letter. The React and Next.js presets produce the `stack` format.
|
||||
|
||||
### Next.js
|
||||
|
||||
Next.js использует тот же `svg-sprite.config.ts` и набор опций. Для типизации можно использовать отдельный хелпер:
|
||||
Next.js uses the same `svg-sprite.config.ts` and set of options. For type checking, you can use a dedicated helper:
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'Иконки файлового менеджера',
|
||||
description: 'File manager icons',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
Роутер и сборщик выбираются через mode key, поэтому переключение между Turbopack и Webpack всегда явно отражено в команде генерации.
|
||||
The router and bundler are selected through the mode key, so switching between Turbopack and Webpack is always explicitly reflected in the generation command.
|
||||
|
||||
## Множественные спрайты
|
||||
## Multiple sprites
|
||||
|
||||
Приложение может содержать несколько независимых спрайтов с разной областью использования:
|
||||
An application can contain several independent sprites for different scopes:
|
||||
|
||||
**Проблема:** один глобальный спрайт загружает иконки, которые текущему экрану не нужны.
|
||||
**Problem:** one global sprite loads icons that the current screen does not need.
|
||||
|
||||
**Решение:** общие иконки хранить глобально, а наборы страниц и крупных компонентов — в отдельных спрайтах, загружаемых вместе с ними.
|
||||
**Solution:** keep shared icons globally, and place icon sets for pages and large components in separate sprites that load alongside them.
|
||||
|
||||
```text
|
||||
global → GlobalIcon → общие иконки приложения
|
||||
analytics-page → AnalyticsPageIcon → иконки отдельной страницы
|
||||
file-manager → FileManagerIcon → иконки крупного компонента
|
||||
global -> GlobalIcon -> shared application icons
|
||||
analytics-page -> AnalyticsPageIcon -> icons for a specific page
|
||||
file-manager -> FileManagerIcon -> icons for a large component
|
||||
```
|
||||
|
||||
- **Глобальный спрайт** содержит небольшие общие иконки, используемые в разных частях приложения: навигацию, состояния и базовые действия.
|
||||
- **Спрайт страницы** загружается вместе с конкретным разделом и не увеличивает общий спрайт иконками, которые больше нигде не нужны.
|
||||
- **Спрайт крупного компонента** инкапсулирует собственный набор иконок сложного UI-модуля, например файлового менеджера или редактора.
|
||||
- **Global sprite** contains a small set of shared icons used in different parts of the application: navigation, states, and basic actions.
|
||||
- **Page sprite** loads with a specific section and does not increase the shared sprite with icons that are not needed anywhere else.
|
||||
- **Large component sprite** encapsulates the icon set of a complex UI module, such as a file manager or editor.
|
||||
|
||||
Каждая группа получает:
|
||||
Each group gets:
|
||||
|
||||
- собственный SVG asset;
|
||||
- собственный типизированный компонент;
|
||||
- отдельный список имён иконок;
|
||||
- отдельный debug-манифест;
|
||||
- независимый cache lifecycle.
|
||||
- its own SVG asset;
|
||||
- its own typed component;
|
||||
- a separate list of icon names;
|
||||
- a separate debug manifest;
|
||||
- an independent cache lifecycle.
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
Главная возможность TypeScript API — автодополнение имён иконок непосредственно в prop `icon`:
|
||||
The main feature of the TypeScript API is icon name autocomplete directly in the `icon` prop:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="folder" />
|
||||
// ↑ редактор предлагает все иконки спрайта
|
||||
// ^ the editor suggests every icon in the sprite
|
||||
```
|
||||
|
||||
Имена SVG-файлов становятся допустимыми значениями `icon`. Опечатка или неизвестное имя сразу становятся ошибкой TypeScript:
|
||||
SVG file names become valid `icon` values. A typo or unknown name immediately becomes a TypeScript error:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="unknown" /> // ошибка TypeScript
|
||||
<FileManagerIcon icon="unknown" /> // TypeScript error
|
||||
```
|
||||
|
||||
Для программного доступа generated-модуль экспортирует readonly-массив всех доступных иконок конкретного спрайта:
|
||||
For programmatic access, the generated module exports a readonly array of all icons available in a specific sprite:
|
||||
|
||||
```ts
|
||||
import { fileManagerIconNames } from './svg-sprite'
|
||||
@@ -173,39 +176,39 @@ import { fileManagerIconNames } from './svg-sprite'
|
||||
// readonly ['check', 'folder', ...]
|
||||
```
|
||||
|
||||
Этот список можно использовать в собственных каталогах, select-компонентах, тестах и других runtime-сценариях. Из него также выводится union-тип `FileManagerIconName`.
|
||||
You can use this list in custom catalogs, select components, tests, and other runtime scenarios. The `FileManagerIconName` union type is also derived from it.
|
||||
|
||||
Имена файлов с пробелами и другими небезопасными для SVG ID символами остаются частью публичного TypeScript API. Для внутреннего `<symbol id>` генератор создаёт стабильный hash ID.
|
||||
File names containing spaces and other characters unsafe for SVG IDs remain part of the public TypeScript API. For the internal `<symbol id>`, the generator creates a stable hash ID.
|
||||
|
||||
```text
|
||||
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
|
||||
folder open.svg -> icon="folder open" -> id="icon-<stable-hash>"
|
||||
```
|
||||
|
||||
Для таких имён используйте generated-компонент или `id` из debug-манифеста. Ручные примеры ниже с `#<имя>` подходят только для имён, которые уже являются безопасными SVG ID.
|
||||
For such names, use the generated component or the `id` from the debug manifest. The manual examples below using `#<name>` are suitable only for names that are already safe SVG IDs.
|
||||
|
||||
## Форматы спрайтов
|
||||
## Sprite formats
|
||||
|
||||
`stack` — более современный формат, поэтому он используется по умолчанию. Иконки можно отображать через `<svg><use>`, `<img>` и CSS `background-image`.
|
||||
`stack` is the more modern format, so it is used by default. Icons can be rendered through `<svg><use>`, `<img>`, and CSS `background-image`.
|
||||
|
||||
`symbol` сохраняется для совместимости с существующими интеграциями и поддерживает отображение только через `<svg><use>`.
|
||||
`symbol` is retained for compatibility with existing integrations and supports rendering only through `<svg><use>`.
|
||||
|
||||
## Способы отображения
|
||||
## Rendering methods
|
||||
|
||||
### React-компонент — рекомендуется
|
||||
### React component - recommended
|
||||
|
||||
Generated-компонент предоставляет типизацию, автодополнение имён иконок и сам формирует URL SVG asset.
|
||||
The generated component provides type safety and icon name autocomplete, and constructs the SVG asset URL itself.
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="check" width={24} height={24} />
|
||||
```
|
||||
|
||||
Через `color` и `--icon-color-N` доступны одноцветные и многоцветные иконки.
|
||||
Monochrome and multicolor icons are supported through `color` and `--icon-color-N`.
|
||||
|
||||
### Самостоятельно через `<svg><use>`
|
||||
### Manually with `<svg><use>`
|
||||
|
||||
Хороший низкоуровневый способ с полным управлением размерами и цветами. React-компонент под капотом использует именно его.
|
||||
A good low-level method that provides full control over dimensions and colors. This is exactly what the React component uses under the hood.
|
||||
|
||||
Способ получения `spriteUrl` зависит от сборщика.
|
||||
How you obtain `spriteUrl` depends on the bundler.
|
||||
|
||||
**Vite:**
|
||||
|
||||
@@ -222,7 +225,7 @@ const spriteUrl = new URL(
|
||||
).href
|
||||
```
|
||||
|
||||
**Next.js с Webpack 5 или Turbopack:**
|
||||
**Next.js with Webpack 5 or Turbopack:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
@@ -231,7 +234,7 @@ const spriteUrl = new URL(
|
||||
).href
|
||||
```
|
||||
|
||||
После получения URL иконка отображается одинаково:
|
||||
After obtaining the URL, the icon is rendered the same way:
|
||||
|
||||
```tsx
|
||||
<svg width={24} height={24}>
|
||||
@@ -239,17 +242,17 @@ const spriteUrl = new URL(
|
||||
</svg>
|
||||
```
|
||||
|
||||
Vite, Webpack 5 и Next.js сами заменяют исходный путь на итоговый URL asset с hash.
|
||||
Vite, Webpack 5, and Next.js replace the source path with the final hashed asset URL automatically.
|
||||
|
||||
### Через `<img>` — менее эффективно
|
||||
### With `<img>` - less efficient
|
||||
|
||||
```tsx
|
||||
<img src={`${spriteUrl}#check`} width={24} height={24} alt="Готово" />
|
||||
<img src={`${spriteUrl}#check`} width={24} height={24} alt="Done" />
|
||||
```
|
||||
|
||||
SVG загружается как изолированное изображение: изменить его цвета через `color` или `--icon-color-N` нельзя.
|
||||
The SVG loads as an isolated image: its colors cannot be changed through `color` or `--icon-color-N`.
|
||||
|
||||
### Через CSS `background-image` — менее эффективно
|
||||
### With CSS `background-image` - less efficient
|
||||
|
||||
```css
|
||||
.icon {
|
||||
@@ -257,9 +260,9 @@ SVG загружается как изолированное изображен
|
||||
}
|
||||
```
|
||||
|
||||
Как и `<img>`, этот способ не позволяет управлять внутренними цветами SVG. Путь указывается относительно CSS-файла, а Vite/Webpack заменяет его на итоговый URL с hash при сборке.
|
||||
Like `<img>`, this method does not allow you to control internal SVG colors. The path is specified relative to the CSS file, and Vite/Webpack replaces it with the final hashed URL during the build.
|
||||
|
||||
### Через CSS mask — менее эффективно
|
||||
### With CSS mask - less efficient
|
||||
|
||||
```css
|
||||
.icon {
|
||||
@@ -268,37 +271,37 @@ SVG загружается как изолированное изображен
|
||||
}
|
||||
```
|
||||
|
||||
Mask оставляет только силуэт и окрашивает его одним цветом. Исходные цвета, gradients и различия между `fill` и `stroke` теряются.
|
||||
A mask retains only the silhouette and colors it with a single color. The original colors, gradients, and distinctions between `fill` and `stroke` are lost.
|
||||
|
||||
## Трансформации
|
||||
## Transformations
|
||||
|
||||
Все трансформации включены по умолчанию и настраиваются независимо через `transform`.
|
||||
All transformations are enabled by default and configured independently through `transform`.
|
||||
|
||||
| Опция | По умолчанию | Что делает |
|
||||
| Option | Default | What it does |
|
||||
|---|---|---|
|
||||
| `removeSize` | `true` | Удаляет `width` и `height` с корневого `<svg>`, сохраняя существующий `viewBox`. Размер иконки после этого задаётся снаружи. |
|
||||
| `replaceColors` | `true` | Заменяет цвета `fill` и `stroke` на `--icon-color-N`. Для одноцветной иконки fallback становится `currentColor`, для многоцветной сохраняются исходные цвета. |
|
||||
| `addTransition` | `true` | Добавляет `style="transition:fill 0.3s,stroke 0.3s;"` непосредственно цветным элементам SVG. Существующий `transition` не перезаписывается. |
|
||||
| `removeSize` | `true` | Removes `width` and `height` from the root `<svg>` while preserving the existing `viewBox`. The icon size is then set externally. |
|
||||
| `replaceColors` | `true` | Replaces `fill` and `stroke` colors with `--icon-color-N`. For a monochrome icon, the fallback becomes `currentColor`; for a multicolor icon, the original colors are preserved. |
|
||||
| `addTransition` | `true` | Adds `style="transition:fill 0.3s,stroke 0.3s;"` directly to colored SVG elements. An existing `transition` is not overwritten. |
|
||||
|
||||
Чтобы отключить преобразование, передайте для соответствующей опции `false`. Подробнее о результате `replaceColors` — в разделе [«Управление цветом иконок»](#управление-цветом-иконок).
|
||||
To disable a transformation, pass `false` for the corresponding option. For more details about the result of `replaceColors`, see [Icon color management](#icon-color-management).
|
||||
|
||||
## Управление цветом иконок
|
||||
## Icon color management
|
||||
|
||||
При включённой замене цветов генератор анализирует `fill` и `stroke` и преобразует их в CSS custom properties.
|
||||
When color replacement is enabled, the generator analyzes `fill` and `stroke` and converts them to CSS custom properties.
|
||||
|
||||
### Монохромные иконки
|
||||
### Monochrome icons
|
||||
|
||||
Если найден один цвет, fallback заменяется на `currentColor`:
|
||||
If one color is found, the fallback is replaced with `currentColor`:
|
||||
|
||||
```svg
|
||||
stroke="var(--icon-color-1, currentColor)"
|
||||
```
|
||||
|
||||
Цветом управляет CSS-свойство `color` внешнего `<svg>` или его родителя.
|
||||
The color is controlled by the CSS `color` property of the outer `<svg>` or its parent.
|
||||
|
||||
### Многоцветные иконки
|
||||
### Multicolor icons
|
||||
|
||||
Каждый уникальный цвет получает отдельную переменную с исходным fallback:
|
||||
Each unique color gets a separate variable with the original fallback:
|
||||
|
||||
```svg
|
||||
fill="var(--icon-color-1, #798198)"
|
||||
@@ -306,7 +309,7 @@ fill="var(--icon-color-2, #ffffff)"
|
||||
fill="var(--icon-color-3, #129d9d)"
|
||||
```
|
||||
|
||||
Страница может заменить только необходимые цвета:
|
||||
The page can override only the required colors:
|
||||
|
||||
```css
|
||||
.icon {
|
||||
@@ -315,62 +318,62 @@ fill="var(--icon-color-3, #129d9d)"
|
||||
}
|
||||
```
|
||||
|
||||
### Ограничения цветов
|
||||
### Color limitations
|
||||
|
||||
- `none`, `transparent`, `inherit`, `unset` и `initial` не заменяются;
|
||||
- цвета в атрибутах `fill`, `stroke` и inline `style` обрабатываются надёжнее всего;
|
||||
- CSS-классы и внешние stylesheets внутри исходного SVG не являются основным сценарием трансформации;
|
||||
- gradients, patterns, filters и значения `url(#...)` требуют отдельной проверки и могут быть несовместимы с автоматической заменой цветов;
|
||||
- CSS-переменные страницы доступны при `<svg><use>`, но недоступны внутри `<img>` и `background-image`.
|
||||
- `none`, `transparent`, `inherit`, `unset`, and `initial` are not replaced;
|
||||
- colors in `fill`, `stroke`, and inline `style` attributes are handled most reliably;
|
||||
- CSS classes and external stylesheets inside the source SVG are not the primary transformation use case;
|
||||
- gradients, patterns, filters, and `url(#...)` values require separate verification and may be incompatible with automatic color replacement;
|
||||
- page CSS variables are available with `<svg><use>`, but are not available inside `<img>` and `background-image`.
|
||||
|
||||
## Кеширование
|
||||
## Caching
|
||||
|
||||
Vite, Webpack и Next.js target выпускают спрайт отдельным asset с content hash:
|
||||
The Vite, Webpack, and Next.js targets emit the sprite as a separate asset with a content hash:
|
||||
|
||||
```text
|
||||
/assets/sprite-<hash>.svg
|
||||
```
|
||||
|
||||
Это даёт следующие свойства:
|
||||
This provides the following properties:
|
||||
|
||||
- SVG кешируется независимо от JavaScript;
|
||||
- изменение React-кода не меняет содержимое спрайта;
|
||||
- изменение иконок создаёт новый hash asset;
|
||||
- один файл используется всеми экземплярами generated-компонента;
|
||||
- SVG path-данные отсутствуют в JavaScript chunks.
|
||||
- the SVG is cached independently of JavaScript;
|
||||
- changes to React code do not alter the sprite contents;
|
||||
- icon changes produce a new hashed asset;
|
||||
- one file is used by every instance of the generated component;
|
||||
- SVG path data is absent from JavaScript chunks.
|
||||
|
||||
Vite target запрещает inline через `?no-inline`. Webpack 5 target использует Asset Modules через `new URL(..., import.meta.url)`.
|
||||
The Vite target prevents inlining through `?no-inline`. The Webpack 5 target uses Asset Modules through `new URL(..., import.meta.url)`.
|
||||
|
||||
## SpriteViewer
|
||||
|
||||
`SpriteViewer` — React-компонент для просмотра generated-спрайтов внутри debug-маршрута приложения.
|
||||
`SpriteViewer` is a React component for viewing generated sprites inside an application's debug route.
|
||||
|
||||
Он использует отдельные манифесты и показывает:
|
||||
It uses separate manifests and displays:
|
||||
|
||||
- группы спрайтов;
|
||||
- список и количество иконок;
|
||||
- поиск и системную светлую/тёмную тему;
|
||||
- модальное превью с `viewBox` и настройкой цветовых переменных;
|
||||
- примеры React, SVG, IMG и CSS с копированием кода.
|
||||
- sprite groups;
|
||||
- the icon list and count;
|
||||
- search and the system light/dark theme;
|
||||
- a preview modal with the `viewBox` and color variable controls;
|
||||
- React, SVG, IMG, and CSS examples with code copying.
|
||||
|
||||
Production-компоненты не импортируют debug-манифесты. Способ подключения Viewer зависит от сборщика:
|
||||
Production components do not import debug manifests. How you integrate the Viewer depends on the bundler:
|
||||
|
||||
- [React + Vite: автоматический `import.meta.glob`](docs/ru/react-vite.md#6-добавьте-debug-страницу);
|
||||
- [React + Webpack 5: статические `import()`](docs/ru/react-webpack.md#6-добавьте-debug-страницу);
|
||||
- [Next.js App Router](docs/ru/next-app.md#5-добавьте-spriteviewer);
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md#5-добавьте-spriteviewer).
|
||||
- [React + Vite: automatic `import.meta.glob`](docs/en/react-vite.md#6-add-a-debug-page);
|
||||
- [React + Webpack 5: static `import()`](docs/en/react-webpack.md#6-add-a-debug-page);
|
||||
- [Next.js App Router](docs/en/next-app.md#5-add-spriteviewer);
|
||||
- [Next.js Pages Router](docs/en/next-pages.md#5-add-spriteviewer).
|
||||
|
||||
Viewer подключается из отдельной клиентской точки входа `@gromlab/svg-sprites/react` и не попадает в production-компоненты иконок.
|
||||
The Viewer is imported from the separate `@gromlab/svg-sprites/react` client entry point and is not included in production icon components.
|
||||
|
||||
### Тема Viewer
|
||||
### Viewer theme
|
||||
|
||||
По умолчанию `colorTheme="auto"`: Viewer следует `prefers-color-scheme` и реагирует на смену системной темы. Тему приложения можно передать явно:
|
||||
By default, `colorTheme="auto"`: the Viewer follows `prefers-color-scheme` and responds to system theme changes. The application theme can be passed explicitly:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer sources={sources} colorTheme="dark" />
|
||||
```
|
||||
|
||||
Допустимые значения `colorTheme`: `auto`, `light`, `dark`. При управлении темой извне встроенный переключатель скрывается. Чтобы оставить его и обновлять тему приложения через Viewer, передайте callback:
|
||||
Valid `colorTheme` values are `auto`, `light`, and `dark`. When the theme is controlled externally, the built-in switch is hidden. To keep it and update the application theme through the Viewer, pass a callback:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer
|
||||
@@ -380,16 +383,16 @@ Viewer подключается из отдельной клиентской т
|
||||
/>
|
||||
```
|
||||
|
||||
## Документация
|
||||
## Documentation
|
||||
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [Legacy mode](docs/ru/legacy.md)
|
||||
- [Миграция с 0.1.x](docs/ru/migration-1.md)
|
||||
- [Программный API](docs/ru/programmatic-api.md)
|
||||
- [React + Vite](docs/en/react-vite.md)
|
||||
- [React + Webpack 5](docs/en/react-webpack.md)
|
||||
- [Next.js App Router](docs/en/next-app.md)
|
||||
- [Next.js Pages Router](docs/en/next-pages.md)
|
||||
- [Legacy mode](docs/en/legacy.md)
|
||||
- [Migrating from 0.1.x](docs/en/migration-1.md)
|
||||
- [Programmatic API](docs/en/programmatic-api.md)
|
||||
|
||||
## Лицензия
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
398
README_RU.md
Normal file
398
README_RU.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# @gromlab/svg-sprites
|
||||
|
||||
[🇬🇧 English](README.md) | 🇷🇺 Русский
|
||||
|
||||
 
|
||||
|
||||
CLI для генерации SVG-спрайтов и типизированных компонентов иконок для React и Next.js.
|
||||
|
||||

|
||||
|
||||
## Навигация
|
||||
|
||||
- [Возможности](#возможности)
|
||||
- [Таблица поддержки](#таблица-поддержки)
|
||||
- [Требования](#требования)
|
||||
- [Быстрый старт](#быстрый-старт)
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [Конфигурация](#конфигурация)
|
||||
- [React](#react)
|
||||
- [Next.js](#nextjs)
|
||||
- [Множественные спрайты](#множественные-спрайты)
|
||||
- [TypeScript](#typescript)
|
||||
- [Форматы спрайтов](#форматы-спрайтов)
|
||||
- [Способы отображения](#способы-отображения)
|
||||
- [Трансформации](#трансформации)
|
||||
- [Управление цветом иконок](#управление-цветом-иконок)
|
||||
- [Кеширование](#кеширование)
|
||||
- [SpriteViewer](#spriteviewer)
|
||||
- [Миграция с 0.1.x](docs/ru/migration-1.md)
|
||||
- [Документация](#документация)
|
||||
|
||||
## Возможности
|
||||
|
||||
- **AI-agent friendly** — репозиторий содержит готовый skill с актуальной документацией для настройки, миграции и диагностики `@gromlab/svg-sprites`.
|
||||
- **TypeScript-friendly** — типизированные React-компоненты, union-типы и runtime-списки доступных иконок.
|
||||
- **Чистая генерация** — generated-файлы автоматически исключаются из Git, спрайт не нужно вручную размещать в `public`, а генератор обновляет только принадлежащие ему файлы.
|
||||
- **Общие иконки без копирования** — SVG из локальной папки и `inputFiles` объединяются в один спрайт; один файл можно использовать в нескольких спрайтах.
|
||||
- **Встроенное интерактивное превью** — `<SpriteViewer>` подключается как страница приложения и показывает переданные React- и Next.js-спрайты с поиском, настройкой цветов и примерами использования.
|
||||
- **Настраиваемые трансформации SVG** — удаление `width` и `height` с сохранением `viewBox`, замена исходных цветов на CSS-переменные и transitions для `fill` и `stroke`.
|
||||
- **Отдельный кешируемый SVG asset** — SVG path-данные не попадают в JavaScript chunks, а сборщик выпускает файл с content hash.
|
||||
- **Множественные спрайты** — независимые React- и Next.js-модули со своими компонентами, типами и SVG assets.
|
||||
- **Server-first Next.js** — generated-компоненты работают в Server Components, SSR и SSG без директивы `'use client'`.
|
||||
- **Форматы под разные сценарии** — React и Next.js используют `stack`, legacy-режим также поддерживает `symbol` для существующих интеграций.
|
||||
|
||||
## Таблица поддержки
|
||||
|
||||
| Среда | Ключ мода API | Статус |
|
||||
|---|---|---|
|
||||
| React + Vite | `react@vite` | Готово |
|
||||
| React + Webpack 5 | `react@webpack` | Готово |
|
||||
| Next.js 16.2+ App Router + Turbopack | `next@app/turbopack` | Готово |
|
||||
| Next.js 13.4+ App Router + Webpack 5 | `next@app/webpack` | Готово |
|
||||
| Next.js 16.2+ Pages Router + Turbopack | `next@pages/turbopack` | Готово |
|
||||
| Next.js 12.2+ Pages Router + Webpack 5 | `next@pages/webpack` | Готово |
|
||||
| Vue | — | Скоро |
|
||||
| Standalone | — | Скоро |
|
||||
|
||||
## Требования
|
||||
|
||||
- Node.js 18 или новее;
|
||||
- пакет распространяется только как ESM и подключается через `import`;
|
||||
- React 18 или 19 требуется только для generated-компонентов и точки входа `@gromlab/svg-sprites/react`;
|
||||
- для типизации subpath exports используйте TypeScript 5+ с `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
Для быстрого старта воспользуйтесь инструкцией для вашего стека:
|
||||
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### React
|
||||
|
||||
```ts
|
||||
import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'Иконки файлового менеджера',
|
||||
inputFolder: './icons',
|
||||
inputFiles: [
|
||||
'../../shared/icons/check.svg',
|
||||
],
|
||||
transform: {
|
||||
removeSize: true,
|
||||
replaceColors: true,
|
||||
addTransition: true,
|
||||
},
|
||||
generatedNotice: true,
|
||||
})
|
||||
```
|
||||
|
||||
| Опция | Тип | По умолчанию | Назначение |
|
||||
|---|---|---|---|
|
||||
| `name` | `string` | Имя папки | Имя спрайта, компонента и публичных типов |
|
||||
| `description` | `string` | Нет | Описание для типов и debug-манифеста |
|
||||
| `inputFolder` | `string` | `./icons` | Папка с исходными SVG относительно конфига |
|
||||
| `inputFiles` | `string[]` | `[]` | Дополнительные SVG-файлы относительно конфига |
|
||||
| `transform` | `TransformOptions` | Все включены | [Настройки трансформации](#трансформации) исходных SVG |
|
||||
| `generatedNotice` | `boolean` | `true` | Полное либо короткое предупреждение в generated-файлах |
|
||||
|
||||
`inputFolder` и `inputFiles` объединяются в один спрайт, поэтому один SVG-файл можно использовать в нескольких спрайтах без копирования. Если неявной папки `./icons` нет, но `inputFiles` заполнен, генерация продолжается только по списку. Явно указанная отсутствующая папка считается ошибкой. Одинаковые пути дедуплицируются, а разные файлы с одинаковым именем иконки считаются ошибкой.
|
||||
|
||||
`name` записывается в kebab-case и должно начинаться с латинской буквы. React и Next.js presets создают формат `stack`.
|
||||
|
||||
### Next.js
|
||||
|
||||
Next.js использует тот же `svg-sprite.config.ts` и набор опций. Для типизации можно использовать отдельный хелпер:
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'Иконки файлового менеджера',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
Роутер и сборщик выбираются через mode key, поэтому переключение между Turbopack и Webpack всегда явно отражено в команде генерации.
|
||||
|
||||
## Множественные спрайты
|
||||
|
||||
Приложение может содержать несколько независимых спрайтов с разной областью использования:
|
||||
|
||||
**Проблема:** один глобальный спрайт загружает иконки, которые текущему экрану не нужны.
|
||||
|
||||
**Решение:** общие иконки хранить глобально, а наборы страниц и крупных компонентов — в отдельных спрайтах, загружаемых вместе с ними.
|
||||
|
||||
```text
|
||||
global → GlobalIcon → общие иконки приложения
|
||||
analytics-page → AnalyticsPageIcon → иконки отдельной страницы
|
||||
file-manager → FileManagerIcon → иконки крупного компонента
|
||||
```
|
||||
|
||||
- **Глобальный спрайт** содержит небольшие общие иконки, используемые в разных частях приложения: навигацию, состояния и базовые действия.
|
||||
- **Спрайт страницы** загружается вместе с конкретным разделом и не увеличивает общий спрайт иконками, которые больше нигде не нужны.
|
||||
- **Спрайт крупного компонента** инкапсулирует собственный набор иконок сложного UI-модуля, например файлового менеджера или редактора.
|
||||
|
||||
Каждая группа получает:
|
||||
|
||||
- собственный SVG asset;
|
||||
- собственный типизированный компонент;
|
||||
- отдельный список имён иконок;
|
||||
- отдельный debug-манифест;
|
||||
- независимый cache lifecycle.
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
Главная возможность TypeScript API — автодополнение имён иконок непосредственно в prop `icon`:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="folder" />
|
||||
// ↑ редактор предлагает все иконки спрайта
|
||||
```
|
||||
|
||||
Имена SVG-файлов становятся допустимыми значениями `icon`. Опечатка или неизвестное имя сразу становятся ошибкой TypeScript:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="unknown" /> // ошибка TypeScript
|
||||
```
|
||||
|
||||
Для программного доступа generated-модуль экспортирует readonly-массив всех доступных иконок конкретного спрайта:
|
||||
|
||||
```ts
|
||||
import { fileManagerIconNames } from './svg-sprite'
|
||||
|
||||
// readonly ['check', 'folder', ...]
|
||||
```
|
||||
|
||||
Этот список можно использовать в собственных каталогах, select-компонентах, тестах и других runtime-сценариях. Из него также выводится union-тип `FileManagerIconName`.
|
||||
|
||||
Имена файлов с пробелами и другими небезопасными для SVG ID символами остаются частью публичного TypeScript API. Для внутреннего `<symbol id>` генератор создаёт стабильный hash ID.
|
||||
|
||||
```text
|
||||
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
|
||||
```
|
||||
|
||||
Для таких имён используйте generated-компонент или `id` из debug-манифеста. Ручные примеры ниже с `#<имя>` подходят только для имён, которые уже являются безопасными SVG ID.
|
||||
|
||||
## Форматы спрайтов
|
||||
|
||||
`stack` — более современный формат, поэтому он используется по умолчанию. Иконки можно отображать через `<svg><use>`, `<img>` и CSS `background-image`.
|
||||
|
||||
`symbol` сохраняется для совместимости с существующими интеграциями и поддерживает отображение только через `<svg><use>`.
|
||||
|
||||
## Способы отображения
|
||||
|
||||
### React-компонент — рекомендуется
|
||||
|
||||
Generated-компонент предоставляет типизацию, автодополнение имён иконок и сам формирует URL SVG asset.
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="check" width={24} height={24} />
|
||||
```
|
||||
|
||||
Через `color` и `--icon-color-N` доступны одноцветные и многоцветные иконки.
|
||||
|
||||
### Самостоятельно через `<svg><use>`
|
||||
|
||||
Хороший низкоуровневый способ с полным управлением размерами и цветами. React-компонент под капотом использует именно его.
|
||||
|
||||
Способ получения `spriteUrl` зависит от сборщика.
|
||||
|
||||
**Vite:**
|
||||
|
||||
```tsx
|
||||
import spriteUrl from './svg-sprite/generated/sprite.svg?no-inline'
|
||||
```
|
||||
|
||||
**Webpack 5:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
'./svg-sprite/generated/sprite.svg',
|
||||
import.meta.url,
|
||||
).href
|
||||
```
|
||||
|
||||
**Next.js с Webpack 5 или Turbopack:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
'./svg-sprite/generated/sprite.svg',
|
||||
import.meta.url,
|
||||
).href
|
||||
```
|
||||
|
||||
После получения URL иконка отображается одинаково:
|
||||
|
||||
```tsx
|
||||
<svg width={24} height={24}>
|
||||
<use href={`${spriteUrl}#check`} />
|
||||
</svg>
|
||||
```
|
||||
|
||||
Vite, Webpack 5 и Next.js сами заменяют исходный путь на итоговый URL asset с hash.
|
||||
|
||||
### Через `<img>` — менее эффективно
|
||||
|
||||
```tsx
|
||||
<img src={`${spriteUrl}#check`} width={24} height={24} alt="Готово" />
|
||||
```
|
||||
|
||||
SVG загружается как изолированное изображение: изменить его цвета через `color` или `--icon-color-N` нельзя.
|
||||
|
||||
### Через CSS `background-image` — менее эффективно
|
||||
|
||||
```css
|
||||
.icon {
|
||||
background: url('./svg-sprite/generated/sprite.svg#check') center / contain no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Как и `<img>`, этот способ не позволяет управлять внутренними цветами SVG. Путь указывается относительно CSS-файла, а Vite/Webpack заменяет его на итоговый URL с hash при сборке.
|
||||
|
||||
### Через CSS mask — менее эффективно
|
||||
|
||||
```css
|
||||
.icon {
|
||||
background-color: currentColor;
|
||||
mask: url('./svg-sprite/generated/sprite.svg#check') center / contain no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Mask оставляет только силуэт и окрашивает его одним цветом. Исходные цвета, gradients и различия между `fill` и `stroke` теряются.
|
||||
|
||||
## Трансформации
|
||||
|
||||
Все трансформации включены по умолчанию и настраиваются независимо через `transform`.
|
||||
|
||||
| Опция | По умолчанию | Что делает |
|
||||
|---|---|---|
|
||||
| `removeSize` | `true` | Удаляет `width` и `height` с корневого `<svg>`, сохраняя существующий `viewBox`. Размер иконки после этого задаётся снаружи. |
|
||||
| `replaceColors` | `true` | Заменяет цвета `fill` и `stroke` на `--icon-color-N`. Для одноцветной иконки fallback становится `currentColor`, для многоцветной сохраняются исходные цвета. |
|
||||
| `addTransition` | `true` | Добавляет `style="transition:fill 0.3s,stroke 0.3s;"` непосредственно цветным элементам SVG. Существующий `transition` не перезаписывается. |
|
||||
|
||||
Чтобы отключить преобразование, передайте для соответствующей опции `false`. Подробнее о результате `replaceColors` — в разделе [«Управление цветом иконок»](#управление-цветом-иконок).
|
||||
|
||||
## Управление цветом иконок
|
||||
|
||||
При включённой замене цветов генератор анализирует `fill` и `stroke` и преобразует их в CSS custom properties.
|
||||
|
||||
### Монохромные иконки
|
||||
|
||||
Если найден один цвет, fallback заменяется на `currentColor`:
|
||||
|
||||
```svg
|
||||
stroke="var(--icon-color-1, currentColor)"
|
||||
```
|
||||
|
||||
Цветом управляет CSS-свойство `color` внешнего `<svg>` или его родителя.
|
||||
|
||||
### Многоцветные иконки
|
||||
|
||||
Каждый уникальный цвет получает отдельную переменную с исходным fallback:
|
||||
|
||||
```svg
|
||||
fill="var(--icon-color-1, #798198)"
|
||||
fill="var(--icon-color-2, #ffffff)"
|
||||
fill="var(--icon-color-3, #129d9d)"
|
||||
```
|
||||
|
||||
Страница может заменить только необходимые цвета:
|
||||
|
||||
```css
|
||||
.icon {
|
||||
--icon-color-1: #4b5563;
|
||||
--icon-color-3: #14b8a6;
|
||||
}
|
||||
```
|
||||
|
||||
### Ограничения цветов
|
||||
|
||||
- `none`, `transparent`, `inherit`, `unset` и `initial` не заменяются;
|
||||
- цвета в атрибутах `fill`, `stroke` и inline `style` обрабатываются надёжнее всего;
|
||||
- CSS-классы и внешние stylesheets внутри исходного SVG не являются основным сценарием трансформации;
|
||||
- gradients, patterns, filters и значения `url(#...)` требуют отдельной проверки и могут быть несовместимы с автоматической заменой цветов;
|
||||
- CSS-переменные страницы доступны при `<svg><use>`, но недоступны внутри `<img>` и `background-image`.
|
||||
|
||||
## Кеширование
|
||||
|
||||
Vite, Webpack и Next.js target выпускают спрайт отдельным asset с content hash:
|
||||
|
||||
```text
|
||||
/assets/sprite-<hash>.svg
|
||||
```
|
||||
|
||||
Это даёт следующие свойства:
|
||||
|
||||
- SVG кешируется независимо от JavaScript;
|
||||
- изменение React-кода не меняет содержимое спрайта;
|
||||
- изменение иконок создаёт новый hash asset;
|
||||
- один файл используется всеми экземплярами generated-компонента;
|
||||
- SVG path-данные отсутствуют в JavaScript chunks.
|
||||
|
||||
Vite target запрещает inline через `?no-inline`. Webpack 5 target использует Asset Modules через `new URL(..., import.meta.url)`.
|
||||
|
||||
## SpriteViewer
|
||||
|
||||
`SpriteViewer` — React-компонент для просмотра generated-спрайтов внутри debug-маршрута приложения.
|
||||
|
||||
Он использует отдельные манифесты и показывает:
|
||||
|
||||
- группы спрайтов;
|
||||
- список и количество иконок;
|
||||
- поиск и системную светлую/тёмную тему;
|
||||
- модальное превью с `viewBox` и настройкой цветовых переменных;
|
||||
- примеры React, SVG, IMG и CSS с копированием кода.
|
||||
|
||||
Production-компоненты не импортируют debug-манифесты. Способ подключения Viewer зависит от сборщика:
|
||||
|
||||
- [React + Vite: автоматический `import.meta.glob`](docs/ru/react-vite.md#6-добавьте-debug-страницу);
|
||||
- [React + Webpack 5: статические `import()`](docs/ru/react-webpack.md#6-добавьте-debug-страницу);
|
||||
- [Next.js App Router](docs/ru/next-app.md#5-добавьте-spriteviewer);
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md#5-добавьте-spriteviewer).
|
||||
|
||||
Viewer подключается из отдельной клиентской точки входа `@gromlab/svg-sprites/react` и не попадает в production-компоненты иконок.
|
||||
|
||||
### Тема Viewer
|
||||
|
||||
По умолчанию `colorTheme="auto"`: Viewer следует `prefers-color-scheme` и реагирует на смену системной темы. Тему приложения можно передать явно:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer sources={sources} colorTheme="dark" />
|
||||
```
|
||||
|
||||
Допустимые значения `colorTheme`: `auto`, `light`, `dark`. При управлении темой извне встроенный переключатель скрывается. Чтобы оставить его и обновлять тему приложения через Viewer, передайте callback:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer
|
||||
sources={sources}
|
||||
colorTheme={appTheme}
|
||||
onColorThemeChange={setAppTheme}
|
||||
/>
|
||||
```
|
||||
|
||||
## Документация
|
||||
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [Legacy mode](docs/ru/legacy.md)
|
||||
- [Миграция с 0.1.x](docs/ru/migration-1.md)
|
||||
- [Программный API](docs/ru/programmatic-api.md)
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT
|
||||
102
docs/en/legacy.md
Normal file
102
docs/en/legacy.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Legacy mode
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
A quick guide to generating centralized SVG sprites in `symbol` and `stack` formats, with an optional HTML preview.
|
||||
|
||||
## 1. Install the package
|
||||
|
||||
```bash
|
||||
npm install @gromlab/svg-sprites
|
||||
```
|
||||
|
||||
## 2. Prepare the icons and config
|
||||
|
||||
```text
|
||||
project/
|
||||
├── src/assets/icons/
|
||||
│ ├── check.svg
|
||||
│ └── folder.svg
|
||||
└── svg-sprites.config.ts
|
||||
```
|
||||
|
||||
```ts
|
||||
// svg-sprites.config.ts
|
||||
import { defineLegacyConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineLegacyConfig({
|
||||
output: 'public/sprites',
|
||||
preview: true,
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'symbol',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## 3. Run generation
|
||||
|
||||
```bash
|
||||
npx svg-sprites --mode legacy .
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
public/sprites/
|
||||
├── icons.sprite.svg
|
||||
└── preview.html
|
||||
```
|
||||
|
||||
With `preview: false`, the HTML file is not created. For the `stack` format, specify `format: 'stack'`.
|
||||
|
||||
## 4. Use the symbol sprite
|
||||
|
||||
```html
|
||||
<svg width="24" height="24" aria-label="Done">
|
||||
<use href="/sprites/icons.sprite.svg#check"></use>
|
||||
</svg>
|
||||
```
|
||||
|
||||
## 5. Add a package script
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"sprites": "svg-sprites --mode legacy .",
|
||||
"prebuild": "npm run sprites"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple sprites
|
||||
|
||||
Add multiple entries to `sprites`:
|
||||
|
||||
```ts
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'symbol',
|
||||
},
|
||||
{
|
||||
name: 'logos',
|
||||
input: 'src/assets/logos',
|
||||
format: 'stack',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
All output files and the shared `preview.html` will be written to `output`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Config not found: make sure `svg-sprites.config.ts` is located in the specified root directory.
|
||||
- No icons: check `sprites[].input` and the `.svg` extension.
|
||||
- Preview not needed: set `preview: false`.
|
||||
|
||||
For programmatic use, see [`generateLegacy`](programmatic-api.md#generatelegacy).
|
||||
96
docs/en/migration-1.md
Normal file
96
docs/en/migration-1.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Migrating from 0.1.x to 1.0
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
Version 1.0 separates local generation for React and Next.js from the centralized legacy mode. The old config cannot be mixed with the new API in a single CLI invocation.
|
||||
|
||||
## CLI
|
||||
|
||||
The CLI now always requires an explicit `--mode` and a path to the configuration directory:
|
||||
|
||||
```text
|
||||
svg-sprites
|
||||
→ svg-sprites --mode <mode> <path>
|
||||
```
|
||||
|
||||
Choose a mode based on your environment:
|
||||
|
||||
| Environment | Mode |
|
||||
|---|---|
|
||||
| React + Vite | `react@vite` |
|
||||
| React + Webpack 5 | `react@webpack` |
|
||||
| Next.js App Router + Turbopack | `next@app/turbopack` |
|
||||
| Next.js App Router + Webpack 5 | `next@app/webpack` |
|
||||
| Next.js Pages Router + Turbopack | `next@pages/turbopack` |
|
||||
| Next.js Pages Router + Webpack 5 | `next@pages/webpack` |
|
||||
| Centralized legacy setup | `legacy` |
|
||||
|
||||
## React and Next.js
|
||||
|
||||
Instead of a root-level `svg-sprites.config.ts`, create a local `svg-sprite.config.ts` next to the icon set:
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'global',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
For regular React, use `defineReactSpriteConfig`. A folder and an explicit list of shared SVG files can be combined using `inputFolder` and `inputFiles`.
|
||||
|
||||
The old `publicPath` and `react` options are no longer needed. The generated module is created next to the config and adds its own `.gitignore`, while Vite, Webpack, or Next.js emits the SVG as a separate asset with a content hash.
|
||||
|
||||
The `<SvgSprite icon="..." />` component is replaced by a component whose name is derived from `name`:
|
||||
|
||||
```tsx
|
||||
<GlobalIcon icon="check" />
|
||||
```
|
||||
|
||||
To browse the icons, add `<SpriteViewer>` as a debug page in the application. A separate `preview.html` is available only in legacy mode.
|
||||
|
||||
## Legacy mode
|
||||
|
||||
If you need to preserve the centralized structure, rename the helper and the format fields:
|
||||
|
||||
```ts
|
||||
import { defineLegacyConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineLegacyConfig({
|
||||
output: 'public/sprites',
|
||||
preview: true,
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'stack',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
- `defineConfig` has been replaced with `defineLegacyConfig`;
|
||||
- `sprites[].mode` has been renamed to `sprites[].format`;
|
||||
- `generate` has been replaced with `generateLegacy`;
|
||||
- `loadConfig` has been replaced with `loadLegacyConfig`;
|
||||
- `publicPath` and generation of the old shared React component have been removed.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
svg-sprites --mode legacy .
|
||||
```
|
||||
|
||||
## Programmatic API
|
||||
|
||||
The package is distributed as ESM only. Replace `require()` with `import`.
|
||||
|
||||
`compileSpriteContent` now returns `Promise<Uint8Array>` so that the public declarations do not require `@types/node` to be installed. In Node.js, the actual result is compatible with APIs that accept `Uint8Array`.
|
||||
|
||||
## After migration
|
||||
|
||||
1. Remove the old generated files and rules that ignored the entire directory containing the source icons.
|
||||
2. Add an explicit generation command before `dev`, `build`, and `typecheck`.
|
||||
3. Run generation and type checking.
|
||||
4. Check all icons and color variables using `SpriteViewer` or the legacy `preview.html`.
|
||||
102
docs/en/next-app.md
Normal file
102
docs/en/next-app.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Next.js App Router
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
Two explicit modes are supported:
|
||||
|
||||
| Bundler | Mode key | Next.js version |
|
||||
|---|---|---|
|
||||
| Turbopack | `next@app/turbopack` | 16.2+ |
|
||||
| Webpack 5 | `next@app/webpack` | 13.4+ |
|
||||
|
||||
## 1. Install the package
|
||||
|
||||
```bash
|
||||
npm install @gromlab/svg-sprites
|
||||
```
|
||||
|
||||
## 2. Create a sprite module
|
||||
|
||||
```text
|
||||
src/ui/file-manager/svg-sprite/
|
||||
├── icons/
|
||||
│ ├── check.svg
|
||||
│ └── folder.svg
|
||||
└── svg-sprite.config.ts
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
})
|
||||
```
|
||||
|
||||
## 3. Add generation
|
||||
|
||||
For Turbopack:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"sprite:file-manager": "svg-sprites --mode next@app/turbopack src/ui/file-manager/svg-sprite",
|
||||
"predev": "npm run sprite:file-manager",
|
||||
"prebuild": "npm run sprite:file-manager"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Webpack, replace the mode key with `next@app/webpack`. In Next 13–15, Webpack is used with the regular `next build` command; in Next 16, use `next build --webpack`.
|
||||
|
||||
## 4. Use it in a Server Component
|
||||
|
||||
The generated component does not contain `'use client'`, so it can be imported directly into `page.tsx` or `layout.tsx`:
|
||||
|
||||
```tsx
|
||||
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main>
|
||||
<FileManagerIcon icon="folder" width={24} height={24} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Next.js emits a separate SVG asset with a content hash. The same generated code is used during SSR and in the browser, with no URL mismatch.
|
||||
|
||||
## 5. Add SpriteViewer
|
||||
|
||||
The viewer is interactive, so it requires a separate Client Component boundary:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { SpriteViewer } from '@gromlab/svg-sprites/react'
|
||||
|
||||
const sources = [
|
||||
() => import('@/ui/file-manager/svg-sprite/manifest'),
|
||||
]
|
||||
|
||||
export default function SpritesPage() {
|
||||
return <SpriteViewer sources={sources} />
|
||||
}
|
||||
```
|
||||
|
||||
## Verify the bundler
|
||||
|
||||
```bash
|
||||
# Turbopack
|
||||
npx next build --turbopack
|
||||
|
||||
# Webpack 5
|
||||
npx next build --webpack
|
||||
```
|
||||
|
||||
For Next 13–15 with Webpack, use `npx next build` without the flag.
|
||||
|
||||
The Next.js command and the generator mode key must target the same bundler.
|
||||
96
docs/en/next-pages.md
Normal file
96
docs/en/next-pages.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Next.js Pages Router
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
Two explicit modes are supported:
|
||||
|
||||
| Bundler | Mode key | Next.js version |
|
||||
|---|---|---|
|
||||
| Turbopack | `next@pages/turbopack` | 16.2+ |
|
||||
| Webpack 5 | `next@pages/webpack` | 12.2+ |
|
||||
|
||||
Next.js 12.2 requires React 18.
|
||||
|
||||
## 1. Install the package
|
||||
|
||||
```bash
|
||||
npm install @gromlab/svg-sprites
|
||||
```
|
||||
|
||||
## 2. Create a sprite module
|
||||
|
||||
```text
|
||||
src/ui/file-manager/svg-sprite/
|
||||
├── icons/
|
||||
│ ├── check.svg
|
||||
│ └── folder.svg
|
||||
└── svg-sprite.config.ts
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
})
|
||||
```
|
||||
|
||||
## 3. Add generation
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"sprite:file-manager": "svg-sprites --mode next@pages/webpack src/ui/file-manager/svg-sprite",
|
||||
"predev": "npm run sprite:file-manager",
|
||||
"prebuild": "npm run sprite:file-manager"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Next.js 16.2 with Turbopack, replace the mode key with `next@pages/turbopack`.
|
||||
|
||||
## 4. Use it on a page
|
||||
|
||||
```tsx
|
||||
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
|
||||
|
||||
export default function FilesPage() {
|
||||
return <FileManagerIcon icon="folder" width={24} height={24} />
|
||||
}
|
||||
|
||||
export function getServerSideProps() {
|
||||
return { props: {} }
|
||||
}
|
||||
```
|
||||
|
||||
The component works the same way with SSR, SSG, and client-side navigation. Next.js emits a separate SVG asset with a content hash.
|
||||
|
||||
## 5. Add SpriteViewer
|
||||
|
||||
```tsx
|
||||
import { SpriteViewer } from '@gromlab/svg-sprites/react'
|
||||
|
||||
const sources = [
|
||||
() => import('@/ui/file-manager/svg-sprite/manifest'),
|
||||
]
|
||||
|
||||
export default function SpritesPage() {
|
||||
return <SpriteViewer sources={sources} />
|
||||
}
|
||||
```
|
||||
|
||||
## Verify the bundler
|
||||
|
||||
```bash
|
||||
# Turbopack
|
||||
npx next build --turbopack
|
||||
|
||||
# Webpack 5
|
||||
npx next build --webpack
|
||||
```
|
||||
|
||||
For Next 12–15 with Webpack, use `npx next build` without the flag.
|
||||
|
||||
The Next.js command and the generator mode key must target the same bundler.
|
||||
203
docs/en/programmatic-api.md
Normal file
203
docs/en/programmatic-api.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Programmatic API
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
The package provides a main Node.js entry point and a separate React runtime entry point. Both are distributed as ESM only and must be loaded with `import`.
|
||||
|
||||
To resolve `@gromlab/svg-sprites/react` in TypeScript, use `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
|
||||
|
||||
## Main entry point
|
||||
|
||||
```ts
|
||||
import {
|
||||
defineNextSpriteConfig,
|
||||
defineReactSpriteConfig,
|
||||
generateNextSprite,
|
||||
generateReactSprite,
|
||||
} from '@gromlab/svg-sprites'
|
||||
```
|
||||
|
||||
The main entry point does not import React and can be used in CLIs, build scripts, and Node.js tools.
|
||||
|
||||
## `generateReactSprite`
|
||||
|
||||
```ts
|
||||
import { generateReactSprite } from '@gromlab/svg-sprites'
|
||||
|
||||
const result = await generateReactSprite(
|
||||
'src/ui/file-manager/svg-sprite',
|
||||
'vite',
|
||||
)
|
||||
```
|
||||
|
||||
The second argument is required:
|
||||
|
||||
```ts
|
||||
type ReactAssetTarget = 'vite' | 'webpack'
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```ts
|
||||
type ReactSpriteGenerationResult = {
|
||||
name: string
|
||||
rootDir: string
|
||||
generatedDir: string
|
||||
spritePath: string
|
||||
manifestPath: string
|
||||
iconCount: number
|
||||
target: 'vite' | 'webpack'
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
console.log(result.name)
|
||||
console.log(result.iconCount)
|
||||
console.log(result.spritePath)
|
||||
console.log(result.manifestPath)
|
||||
```
|
||||
|
||||
The function loads `svg-sprite.config.ts` from the specified root, compiles the SVG files, and safely updates managed files.
|
||||
|
||||
## `generateNextSprite`
|
||||
|
||||
```ts
|
||||
import { generateNextSprite } from '@gromlab/svg-sprites'
|
||||
|
||||
const result = await generateNextSprite(
|
||||
'src/ui/file-manager/svg-sprite',
|
||||
{
|
||||
router: 'app',
|
||||
bundler: 'turbopack',
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
Available values:
|
||||
|
||||
```ts
|
||||
type NextSpriteGenerationOptions = {
|
||||
router: 'app' | 'pages'
|
||||
bundler: 'turbopack' | 'webpack'
|
||||
}
|
||||
```
|
||||
|
||||
The result also contains the selected `router`, `bundler`, and the full target in the form `next@app/turbopack`.
|
||||
|
||||
## `defineReactSpriteConfig`
|
||||
|
||||
```ts
|
||||
import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
inputFolder: './icons',
|
||||
inputFiles: [
|
||||
'../../shared/icons/check.svg',
|
||||
],
|
||||
transform: {
|
||||
removeSize: true,
|
||||
replaceColors: true,
|
||||
addTransition: true,
|
||||
},
|
||||
generatedNotice: true,
|
||||
})
|
||||
```
|
||||
|
||||
`inputFolder` and `inputFiles` are combined. The helper returns the configuration without runtime transformations and provides TypeScript autocomplete.
|
||||
|
||||
## `defineNextSpriteConfig`
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
Next.js uses the same configuration contract as the React presets.
|
||||
|
||||
## `generateLegacy`
|
||||
|
||||
```ts
|
||||
import { generateLegacy } from '@gromlab/svg-sprites'
|
||||
|
||||
const results = await generateLegacy({
|
||||
output: 'public/sprites',
|
||||
preview: false,
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'symbol',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
Returns an array:
|
||||
|
||||
```ts
|
||||
type SpriteResult = {
|
||||
name: string
|
||||
format: 'symbol' | 'stack'
|
||||
spritePath: string
|
||||
iconCount: number
|
||||
}
|
||||
```
|
||||
|
||||
For details, see [Legacy mode](legacy.md).
|
||||
|
||||
## Low-level functions
|
||||
|
||||
The main entry point also exports:
|
||||
|
||||
```ts
|
||||
import {
|
||||
compileSprite,
|
||||
compileSpriteContent,
|
||||
createShapeTransform,
|
||||
generatePreview,
|
||||
loadLegacyConfig,
|
||||
loadReactSpriteConfig,
|
||||
resolveSpriteEntry,
|
||||
resolveSprites,
|
||||
} from '@gromlab/svg-sprites'
|
||||
```
|
||||
|
||||
These functions are intended for custom orchestration built on top of the existing compiler and writer. For standard usage, prefer `generateReactSprite` and `generateLegacy`.
|
||||
|
||||
## React runtime entry point
|
||||
|
||||
```tsx
|
||||
import { SpriteViewer } from '@gromlab/svg-sprites/react'
|
||||
```
|
||||
|
||||
Types:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
SpriteManifest,
|
||||
SpriteManifestColor,
|
||||
SpriteManifestIcon,
|
||||
SpriteManifestLoader,
|
||||
SpriteManifestModule,
|
||||
SpriteViewerColorTheme,
|
||||
SpriteViewerProps,
|
||||
SpriteViewerSource,
|
||||
SpriteViewerSources,
|
||||
} from '@gromlab/svg-sprites/react'
|
||||
```
|
||||
|
||||
The React entry point contains `'use client'` and is intended for debug tools. Generated production components are imported from the application's local sprite modules, not from the package's React entry point.
|
||||
|
||||
`SpriteViewerProps.colorTheme` accepts `auto | light | dark`. The default is `auto`, which follows `prefers-color-scheme`; to synchronize it with the application theme, pass the computed `light` or `dark` value.
|
||||
|
||||
## Related guides
|
||||
|
||||
- [React + Vite](react-vite.md)
|
||||
- [React + Webpack 5](react-webpack.md)
|
||||
116
docs/en/react-vite.md
Normal file
116
docs/en/react-vite.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# React + Vite
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
A quick guide to installing and using SVG sprites in a React and Vite project.
|
||||
|
||||
The result is a typed React component and a separate cacheable SVG asset.
|
||||
|
||||
## 1. Install the package
|
||||
|
||||
```bash
|
||||
npm install @gromlab/svg-sprites
|
||||
```
|
||||
|
||||
## 2. Create the sprite directory
|
||||
|
||||
```text
|
||||
src/ui/file-manager/svg-sprite/
|
||||
├── icons/
|
||||
│ ├── check.svg
|
||||
│ └── folder.svg
|
||||
└── svg-sprite.config.ts
|
||||
```
|
||||
|
||||
Place the source SVG files in `icons/`.
|
||||
|
||||
## 3. Add the configuration
|
||||
|
||||
```ts
|
||||
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
|
||||
import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
})
|
||||
```
|
||||
|
||||
By default, SVG files are loaded from `./icons`. You can add shared icons from other directories through `inputFiles`: the directory and file list are combined into a single sprite.
|
||||
|
||||
The complete list of options is available under [Configuration → React](../../README.md#react).
|
||||
|
||||
## 4. Add generation to package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"sprite:file-manager": "svg-sprites --mode react@vite src/ui/file-manager/svg-sprite",
|
||||
"predev": "npm run sprite:file-manager",
|
||||
"prebuild": "npm run sprite:file-manager",
|
||||
"pretypecheck": "npm run sprite:file-manager"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generated files are excluded from Git, so generation must run before `dev`, `build`, and `typecheck`.
|
||||
|
||||
First run:
|
||||
|
||||
```bash
|
||||
npm run sprite:file-manager
|
||||
```
|
||||
|
||||
## 5. Use the component
|
||||
|
||||
The name `file-manager` is converted to `FileManagerIcon`:
|
||||
|
||||
```tsx
|
||||
import { FileManagerIcon } from './svg-sprite'
|
||||
|
||||
export const OpenFolderButton = () => (
|
||||
<button type="button">
|
||||
<FileManagerIcon icon="folder" width={24} height={24} />
|
||||
Open
|
||||
</button>
|
||||
)
|
||||
```
|
||||
|
||||
TypeScript checks the `icon` value against the file names:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="check" /> // valid
|
||||
<FileManagerIcon icon="unknown" /> // TypeScript error
|
||||
```
|
||||
|
||||
Types, display methods, and color controls are described in the [main documentation](../../README.md#display-methods).
|
||||
|
||||
Vite emits the sprite as a separate file named like `assets/sprite-<hash>.svg`. SVG path data is not included in JavaScript.
|
||||
|
||||
## 6. Add a debug page
|
||||
|
||||
After integrating the icons, you can display all React sprites with `SpriteViewer`:
|
||||
|
||||
```tsx
|
||||
import { SpriteViewer } from '@gromlab/svg-sprites/react'
|
||||
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
|
||||
|
||||
const sources = import.meta.glob<SpriteManifestModule>(
|
||||
'/src/**/svg-sprite/manifest.ts',
|
||||
)
|
||||
|
||||
export const IconsDebugPage = () => (
|
||||
<SpriteViewer sources={sources} title="Project icons" />
|
||||
)
|
||||
```
|
||||
|
||||
Vite automatically finds the generated `manifest.ts` for each React sprite. The `import.meta.glob` pattern must be a string literal, and generation must run before Vite starts.
|
||||
|
||||
Only include the Viewer on a debug route or in an internal tool.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Missing `index.ts`: run `npm run sprite:file-manager`.
|
||||
- The Viewer cannot find the sprite: check the glob path and make sure `manifest.ts` exists.
|
||||
- `Refusing to overwrite a user file` error: there is a user file at a generated path.
|
||||
- The icon does not change color: use `color` or `--icon-color-N`.
|
||||
118
docs/en/react-webpack.md
Normal file
118
docs/en/react-webpack.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# React + Webpack 5
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
A quick guide to installing and using SVG sprites in a React and Webpack 5 project.
|
||||
|
||||
The result is a typed React component and a separate SVG asset emitted through Webpack Asset Modules.
|
||||
|
||||
## 1. Install the package
|
||||
|
||||
```bash
|
||||
npm install @gromlab/svg-sprites
|
||||
```
|
||||
|
||||
## 2. Create the sprite directory
|
||||
|
||||
```text
|
||||
src/ui/file-manager/svg-sprite/
|
||||
├── icons/
|
||||
│ ├── check.svg
|
||||
│ └── folder.svg
|
||||
└── svg-sprite.config.ts
|
||||
```
|
||||
|
||||
Place the source SVG files in `icons/`.
|
||||
|
||||
## 3. Add the configuration
|
||||
|
||||
```ts
|
||||
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
|
||||
import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
})
|
||||
```
|
||||
|
||||
By default, SVG files are loaded from `./icons`. You can add shared icons from other directories through `inputFiles`: the directory and file list are combined into a single sprite.
|
||||
|
||||
The complete list of options is available under [Configuration → React](../../README.md#react).
|
||||
|
||||
## 4. Add generation to package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"sprite:file-manager": "svg-sprites --mode react@webpack src/ui/file-manager/svg-sprite",
|
||||
"predev": "npm run sprite:file-manager",
|
||||
"prebuild": "npm run sprite:file-manager",
|
||||
"pretypecheck": "npm run sprite:file-manager"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generated files are excluded from Git, so generation must run before `dev`, `build`, and `typecheck`.
|
||||
|
||||
First run:
|
||||
|
||||
```bash
|
||||
npm run sprite:file-manager
|
||||
```
|
||||
|
||||
## 5. Use the component
|
||||
|
||||
```tsx
|
||||
import { FileManagerIcon } from './svg-sprite'
|
||||
|
||||
export const OpenFolderButton = () => (
|
||||
<button type="button">
|
||||
<FileManagerIcon icon="folder" width={24} height={24} />
|
||||
Open
|
||||
</button>
|
||||
)
|
||||
```
|
||||
|
||||
TypeScript checks the `icon` value against the file names:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="folder" /> // valid
|
||||
<FileManagerIcon icon="missing" /> // TypeScript error
|
||||
```
|
||||
|
||||
Types, display methods, and color controls are described in the [main documentation](../../README.md#display-methods).
|
||||
|
||||
Webpack processes the generated `new URL('./sprite.svg', import.meta.url)` through Asset Modules and emits a separate SVG asset.
|
||||
|
||||
If the project already uses a custom SVG loader, make sure it does not intercept the generated `sprite.svg` instead of Asset Modules.
|
||||
|
||||
## 6. Add a debug page
|
||||
|
||||
Webpack does not support Vite's `import.meta.glob` API, so provide static loaders:
|
||||
|
||||
```tsx
|
||||
import { SpriteViewer } from '@gromlab/svg-sprites/react'
|
||||
|
||||
const sources = [
|
||||
() => import('./ui/file-manager/svg-sprite/manifest'),
|
||||
() => import('./ui/navigation/svg-sprite/manifest'),
|
||||
]
|
||||
|
||||
export const IconsDebugPage = () => (
|
||||
<SpriteViewer sources={sources} title="Project icons" />
|
||||
)
|
||||
```
|
||||
|
||||
The paths in `import()` must be string literals. Webpack creates chunks for the manifests and associates them with the SVG assets.
|
||||
|
||||
Only include the Viewer on a debug route or in an internal tool.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Missing `index.ts`: run `npm run sprite:file-manager`.
|
||||
- The Viewer does not load the sprite: check the path in `import()` and make sure `manifest.ts` exists.
|
||||
- Incorrect asset URL: check `output.publicPath`.
|
||||
- Another loader intercepts the SVG: exclude the generated sprite from the incompatible rule.
|
||||
|
||||
For Next.js, use the separate mode keys described in the [App Router](next-app.md) and [Pages Router](next-pages.md) guides.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Legacy mode
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Краткая инструкция по генерации централизованных SVG-спрайтов форматов `symbol` и `stack` с optional HTML preview.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Миграция с 0.1.x на 1.0
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Версия 1.0 разделяет локальную генерацию для React и Next.js и централизованный legacy-режим. Старый config нельзя смешивать с новым API в одном вызове CLI.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Next.js App Router
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Поддерживаются два явных режима:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Next.js Pages Router
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Поддерживаются два явных режима:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Программный API
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Пакет предоставляет основную Node.js точку входа и отдельный React runtime entry. Обе точки распространяются только как ESM и подключаются через `import`.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# React + Vite
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Краткая инструкция по установке и использованию SVG-спрайтов в проекте на React и Vite.
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineReactSpriteConfig({
|
||||
|
||||
По умолчанию SVG берутся из `./icons`. Общие иконки из других папок можно добавить через `inputFiles`: папка и список объединяются в один спрайт.
|
||||
|
||||
Полный список опций находится в разделе [Конфигурация → React](../../README.md#react).
|
||||
Полный список опций находится в разделе [Конфигурация → React](../../README_RU.md#react).
|
||||
|
||||
## 4. Добавьте генерацию в package.json
|
||||
|
||||
@@ -83,7 +83,7 @@ export const OpenFolderButton = () => (
|
||||
<FileManagerIcon icon="unknown" /> // ошибка TypeScript
|
||||
```
|
||||
|
||||
Типы, способы отображения и управление цветами описаны в [основной документации](../../README.md#способы-отображения).
|
||||
Типы, способы отображения и управление цветами описаны в [основной документации](../../README_RU.md#способы-отображения).
|
||||
|
||||
Vite выпустит спрайт отдельным файлом вида `assets/sprite-<hash>.svg`. SVG path-данные не попадут в JavaScript.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# React + Webpack 5
|
||||
|
||||
[← Главная](../../README.md)
|
||||
[← Главная](../../README_RU.md)
|
||||
|
||||
Краткая инструкция по установке и использованию SVG-спрайтов в проекте на React и Webpack 5.
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineReactSpriteConfig({
|
||||
|
||||
По умолчанию SVG берутся из `./icons`. Общие иконки из других папок можно добавить через `inputFiles`: папка и список объединяются в один спрайт.
|
||||
|
||||
Полный список опций находится в разделе [Конфигурация → React](../../README.md#react).
|
||||
Полный список опций находится в разделе [Конфигурация → React](../../README_RU.md#react).
|
||||
|
||||
## 4. Добавьте генерацию в package.json
|
||||
|
||||
@@ -81,7 +81,7 @@ export const OpenFolderButton = () => (
|
||||
<FileManagerIcon icon="missing" /> // ошибка TypeScript
|
||||
```
|
||||
|
||||
Типы, способы отображения и управление цветами описаны в [основной документации](../../README.md#способы-отображения).
|
||||
Типы, способы отображения и управление цветами описаны в [основной документации](../../README_RU.md#способы-отображения).
|
||||
|
||||
Webpack обработает generated `new URL('./sprite.svg', import.meta.url)` через Asset Modules и выпустит отдельный SVG asset.
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"dist/chunk-*.js",
|
||||
"dist/chunk-*.js.map",
|
||||
"dist/preview-template.html",
|
||||
"README_RU.md",
|
||||
"docs/en/*.md",
|
||||
"docs/ru/*.md",
|
||||
"LICENSE",
|
||||
"THIRD_PARTY_NOTICES.md"
|
||||
@@ -63,11 +65,11 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gromlab.ru/gromov/svg-sprites"
|
||||
"url": "https://github.com/gromov-sergei/svg-sprites"
|
||||
},
|
||||
"homepage": "https://gromlab.ru/gromov/svg-sprites",
|
||||
"homepage": "https://github.com/gromov-sergei/svg-sprites",
|
||||
"bugs": {
|
||||
"url": "https://gromlab.ru/gromov/svg-sprites/issues"
|
||||
"url": "https://github.com/gromov-sergei/svg-sprites/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}}Business } from './{{name.kebabCase}}.business'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}BusinessParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}BusinessProps = RootAttrs & {{name.pascalCase}}BusinessParams
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}BusinessProps } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение бизнес-модуля {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Business = (props: {{name.pascalCase}}BusinessProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}}Infra } from './{{name.kebabCase}}.infra'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры инфраструктурного модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}InfraParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}InfraProps = RootAttrs & {{name.pascalCase}}InfraParams
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}InfraProps } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение инфраструктурного модуля {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Infra = (props: {{name.pascalCase}}InfraProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}}Layout } from './{{name.kebabCase}}.layout'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры {{name.pascalCase}}Layout.
|
||||
*/
|
||||
export type {{name.pascalCase}}LayoutParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}LayoutProps = RootAttrs & {{name.pascalCase}}LayoutParams
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}LayoutProps } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение layout {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Layout = (props: {{name.pascalCase}}LayoutProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}} } from './{{name.kebabCase}}'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Params = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}}Screen } from './{{name.kebabCase}}.screen'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры экрана {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}ScreenParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}ScreenProps = RootAttrs & {{name.pascalCase}}ScreenParams
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}ScreenProps } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение экрана {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Screen = (props: {{name.pascalCase}}ScreenProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { use{{name.pascalCase}}Store } from './{{name.kebabCase}}.store'
|
||||
export type { {{name.pascalCase}}State } from './{{name.kebabCase}}.type'
|
||||
@@ -1,9 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
import type { {{name.pascalCase}}State } from './{{name.kebabCase}}.type'
|
||||
|
||||
/**
|
||||
* Стор {{name.pascalCase}}.
|
||||
*/
|
||||
export const use{{name.pascalCase}}Store = create<{{name.pascalCase}}State>()(() => ({
|
||||
|
||||
}))
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Состояние {{name.pascalCase}}.
|
||||
*/
|
||||
export interface {{name.pascalCase}}State {
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}} } from './{{name.kebabCase}}.ui'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Params = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { {{name.pascalCase}}Widget } from './{{name.kebabCase}}.widget'
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры виджета {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}WidgetParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}WidgetProps = RootAttrs & {{name.pascalCase}}WidgetParams
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}WidgetProps } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение виджета {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Widget = (props: {{name.pascalCase}}WidgetProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
Это корневой диспетчер. Он определяет твою роль и отправляет тебя к твоему файлу инструкций. Дальше ты работаешь **строго** по нему.
|
||||
|
||||
## Жёсткие правила
|
||||
|
||||
1. Прочитай **только** файл своей роли из таблицы ниже.
|
||||
2. Не читай файлы других ролей. Не читай `ai/` рекурсивно «для контекста».
|
||||
3. Внутри файла роли есть свои обязательные разделы, прикладные разделы и триггеры — следуй его внутреннему протоколу, не додумывай свой.
|
||||
4. Дополнительные файлы из `ai/` читай **только** когда на них явно ссылается твой файл роли или сработавший триггер.
|
||||
|
||||
## Определение роли
|
||||
|
||||
Роль определяется в таком порядке:
|
||||
|
||||
1. Переменная окружения `AI_ROLE`.
|
||||
2. Явное указание в первом сообщении пользователя («работай как developer», «ты reviewer» и т.п.).
|
||||
3. Если ни того, ни другого нет — **остановись и спроси**. Не выбирай роль сам.
|
||||
|
||||
## Карта ролей
|
||||
|
||||
| Роль | Файл инструкций | Назначение |
|
||||
|--------------|-------------------|-----------------------------------------------|
|
||||
| `developer` | `ai/DEVELOP.md` | Написание и редактирование кода проекта |
|
||||
| `reviewer` | `ai/REVIEW.md` | Код-ревью, проверка на соответствие стайлгайду |
|
||||
| `architect` | `ai/ARCHITECT.md` | Проектирование модулей, слоёв, API |
|
||||
| ... | ... | ... |
|
||||
|
||||
> Оставь в таблице только те роли, которые реально существуют в `ai/`.
|
||||
|
||||
## Протокол запуска
|
||||
|
||||
1. Определи роль (см. выше).
|
||||
2. Открой соответствующий файл из таблицы — это твой единственный источник истины.
|
||||
3. Выполняй его внутренний протокол: сначала обязательные правила, затем прикладные разделы и триггеры по мере появления задач.
|
||||
4. Если в ходе работы нужна инструкция, которой нет ни в твоём файле роли, ни в её триггерах — **не ищи её сам в других ролях**. Сообщи пользователю и спроси, как быть (переключить роль, дополнить инструкцию, и т.п.).
|
||||
|
||||
## Что запрещено
|
||||
|
||||
- Читать файлы других ролей даже выборочно.
|
||||
- Сканировать `ai/` целиком или строить по ней собственную карту.
|
||||
- Смешивать правила из разных ролей в одном ответе.
|
||||
- Додумывать правила, которых нет в твоём файле роли.
|
||||
@@ -1,96 +0,0 @@
|
||||
# Стайлгайд — Разработка
|
||||
|
||||
Правила и стандарты разработки на Next.js и TypeScript.
|
||||
|
||||
## Как работать
|
||||
|
||||
1. **Изучи обязательные правила** (таблица ниже) — они действуют при любой задаче.
|
||||
2. Найди задачу в таблицах триггеров → открой триггер.
|
||||
3. Триггер укажет какие прикладные разделы прочитать и какие шаги выполнить.
|
||||
4. Перед каждой подзадачей возвращайся к триггерам — проверяй, нет ли готового.
|
||||
5. Если триггера нет — ищи прикладной раздел по области задачи.
|
||||
|
||||
---
|
||||
|
||||
## Обязательные правила
|
||||
|
||||
Прочитай эти разделы **до начала работы**. Соблюдай при написании любого кода.
|
||||
|
||||
| Раздел | Файл | Что внутри |
|
||||
|--------|------|------------|
|
||||
| Структура проекта | applied/project-structure.md | Организация папок и файлов |
|
||||
| Архитектура | basics/architecture.md | SLM Design: слои, модули, сегменты |
|
||||
| Стиль кода | basics/code-style.md | Форматирование, импорты, отступы |
|
||||
| Именование | basics/naming.md | Имена файлов, переменных, событий |
|
||||
| Типизация | basics/typing.md | type vs interface, generic, any/unknown |
|
||||
| Документирование | basics/documentation.md | JSDoc для функций, компонентов, типов |
|
||||
| Технологии | basics/tech-stack.md | Допустимые библиотеки и зависимости |
|
||||
|
||||
---
|
||||
|
||||
## Прикладные разделы
|
||||
|
||||
Справочник по областям. Читай тот раздел, который относится к текущей задаче.
|
||||
|
||||
| Область | Файл | Когда читать |
|
||||
|---------|------|--------------|
|
||||
| Компоненты | applied/components.md | Создание или редактирование React-компонентов |
|
||||
| Стили | applied/styles.md | CSS Modules, PostCSS, переменные, медиа-запросы |
|
||||
| Файлы роутинга | applied/page-level.md | page.tsx, layout.tsx, error.tsx, not-found.tsx |
|
||||
| Шаблоны и генерация | applied/templates-generation.md | Генерация кода из шаблонов |
|
||||
| Настройка VS Code | applied/vscode.md | Расширения, settings.json, сниппеты |
|
||||
| SVG-спрайты | applied/svg-sprites.md | Работа с SVG-иконками и спрайтами |
|
||||
| Хуки | applied/hooks.md | Создание и использование кастомных хуков *(в разработке)* |
|
||||
| Сторы | applied/stores.md | Глобальное состояние, Zustand *(в разработке)* |
|
||||
| API | applied/api.md | Запросы, клиенты, обработка ответов *(в разработке)* |
|
||||
| Локализация | applied/localization.md | i18next, переводы *(в разработке)* |
|
||||
| Изображения | applied/images-sprites.md | Подключение и оптимизация изображений *(в разработке)* |
|
||||
| Шрифты | applied/fonts.md | Подключение и настройка шрифтов *(в разработке)* |
|
||||
| Видео | applied/video.md | Встраивание видео *(в разработке)* |
|
||||
|
||||
---
|
||||
|
||||
## Триггеры
|
||||
|
||||
Пошаговые инструкции. Найди задачу → открой триггер → выполняй по шагам.
|
||||
|
||||
### Создание
|
||||
|
||||
| Задача | Триггер | Описание |
|
||||
|--------|---------|----------|
|
||||
| Создать компонент | triggers/develop/create-component.md | Переиспользуемый UI-элемент без бизнес-логики |
|
||||
| Создать фичу | triggers/develop/create-feature.md | Самодостаточный блок с бизнес-логикой и UI |
|
||||
| Создать виджет | triggers/develop/create-widget.md | Композиция нескольких фичей и сущностей |
|
||||
| Создать сущность | triggers/develop/create-entity.md | Бизнес-объект с моделью данных и UI-представлением |
|
||||
| Создать хук | triggers/develop/create-hook.md | Кастомный React-хук с переиспользуемой логикой |
|
||||
| Создать стор | triggers/develop/create-store.md | Глобальное или модульное состояние через Zustand |
|
||||
| Создать страницу | triggers/develop/create-page.md | Новый route в Next.js — экран + page.tsx |
|
||||
| Создать layout | triggers/develop/create-layout.md | Общая обёртка layout.tsx для группы страниц |
|
||||
| Создать проект | triggers/develop/create-project.md | Инициализация нового проекта из шаблона |
|
||||
| Сгенерировать модуль | triggers/develop/generate-module.md | Создание модуля из шаблонов `.templates/` |
|
||||
|
||||
### Стилизация и ресурсы
|
||||
|
||||
| Задача | Триггер | Описание |
|
||||
|--------|---------|----------|
|
||||
| Стилизовать компонент | triggers/develop/style-component.md | Выбор подхода и написание CSS для компонента |
|
||||
| Добавить иконку | triggers/develop/add-icon.md | SVG-иконка через спрайт-систему |
|
||||
| Добавить изображение | triggers/develop/add-image.md | Растровое изображение (png, jpg, webp) |
|
||||
| Добавить видео | triggers/develop/add-video.md | Встраивание видео на страницу |
|
||||
| Подключить шрифт | triggers/develop/add-font.md | Подключение нового шрифта в проект |
|
||||
|
||||
### Данные и состояние
|
||||
|
||||
| Задача | Триггер | Описание |
|
||||
|--------|---------|----------|
|
||||
| Добавить API-запрос | triggers/develop/add-api-request.md | Клиентский запрос данных через SWR |
|
||||
| Подключить стор | triggers/develop/connect-store.md | Подключение существующего стора к компоненту |
|
||||
| Серверные данные (RSC) | triggers/develop/add-server-data.md | Получение данных в серверных компонентах |
|
||||
|
||||
### Инфраструктура
|
||||
|
||||
| Задача | Триггер | Описание |
|
||||
|--------|---------|----------|
|
||||
| Добавить перевод | triggers/develop/add-localization.md | Ключи перевода и подключение i18next |
|
||||
| Добавить зависимость | triggers/develop/add-dependency.md | Подключение новой npm-библиотеки |
|
||||
| Настроить VS Code | triggers/develop/setup-vscode.md | Расширения, настройки редактора, сниппеты |
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [api, запрос, fetch, SWR, эндпоинт, REST, клиент]
|
||||
when: "Работа с API: запросы, клиенты, обработка ответов"
|
||||
---
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
title: Компоненты
|
||||
scope: applied
|
||||
keywords: [компонент, props, jsx, ui, clsx, cl, React, FC]
|
||||
when: "Создание или редактирование React-компонентов: структура, пропсы, стили"
|
||||
---
|
||||
# Компоненты
|
||||
|
||||
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
||||
|
||||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture).
|
||||
|
||||
|
||||
## Правила организации
|
||||
|
||||
1. Один компонент — один файл.
|
||||
2. Компонент не содержит бизнес-логики — логика и сайд-эффекты выносятся в хуки или сторы.
|
||||
3. Дочерние компоненты размещаются в сегменте `ui/` и подчиняются тем же правилам структуры.
|
||||
4. Публичный API модуля — только `index.ts`. Прямые импорты внутренних файлов запрещены.
|
||||
|
||||
## Базовая структура компонента
|
||||
|
||||
Минимальный набор файлов: компонент, стили, типы и публичный экспорт.
|
||||
|
||||
```text
|
||||
container/
|
||||
├── styles/
|
||||
│ └── container.module.css
|
||||
├── types/
|
||||
│ └── container.type.ts
|
||||
├── container.ui.tsx
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
## Именования
|
||||
|
||||
- Имя корневого css класса всегда `.root`
|
||||
- Тип пропсов именуется `{ComponentName}Props`.
|
||||
- Тип пользовательских параметров именуется `{ComponentName}Params`.
|
||||
|
||||
## Типизация
|
||||
|
||||
Структура типов компонента показана в [примере](#пример). Ниже — обоснования ключевых решений.
|
||||
|
||||
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
||||
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
||||
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
||||
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/basics/typing).
|
||||
|
||||
## Реализация
|
||||
|
||||
- Пропсы деструктурируются в теле компонента, не в параметрах.
|
||||
- Порядок: пользовательские → системные (`children`, `className`) → `...htmlAttr`.
|
||||
- `className` объединяется с корневым классом через `cl()`: `cl(styles.root, className)`.
|
||||
- `...htmlAttr` прокидывается на корневой элемент.
|
||||
|
||||
## Пример
|
||||
|
||||
`container/types/container.type.ts`
|
||||
|
||||
```ts
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры компонента Container.
|
||||
*/
|
||||
export type ContainerParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type ContainerProps = RootAttrs & ContainerParams
|
||||
```
|
||||
|
||||
`container/styles/container.module.css`
|
||||
|
||||
```css
|
||||
.root {
|
||||
max-width: var(--content-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-4);
|
||||
}
|
||||
```
|
||||
|
||||
`container/container.ui.tsx`
|
||||
|
||||
```tsx
|
||||
import cl from 'clsx'
|
||||
import type { ContainerProps } from './types/container.type'
|
||||
import styles from './styles/container.module.css'
|
||||
|
||||
/**
|
||||
* Контейнер с адаптивной максимальной шириной.
|
||||
*
|
||||
* Используется для:
|
||||
* - обёртки контента страниц с ограничением ширины
|
||||
* - центрирования блоков в лейауте
|
||||
*/
|
||||
export const Container = (props: ContainerProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`container/index.ts`
|
||||
|
||||
```ts
|
||||
export { Container } from './container.ui'
|
||||
```
|
||||
|
||||
## Дочерние компоненты
|
||||
|
||||
Если модулю нужны внутренние подкомпоненты — генерировать их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [шрифт, font, next/font, подключение шрифта, woff]
|
||||
when: "Подключение и настройка шрифтов"
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [хук, hook, use, кастомный хук, useState, useEffect]
|
||||
when: "Создание или использование кастомных хуков"
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [изображение, картинка, image, next/image, public, оптимизация]
|
||||
when: "Работа с изображениями: подключение, оптимизация"
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [i18n, локализация, перевод, язык, i18next, namespace]
|
||||
when: "Локализация: добавление переводов, работа с i18next"
|
||||
---
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
title: Файлы роутинга
|
||||
scope: applied
|
||||
keywords: [page.tsx, layout.tsx, error.tsx, not-found.tsx, loading.tsx, App Router, metadata]
|
||||
when: "Работа с файлами роутинга Next.js App Router: page, layout, error, not-found"
|
||||
---
|
||||
# Файлы роутинга
|
||||
|
||||
Правила для специальных файлов App Router (`page.tsx`, `layout.tsx`, `error.tsx`, `not-found.tsx` и др.) — чем наш подход отличается от дефолтного.
|
||||
|
||||
## Что нужно знать
|
||||
|
||||
Страница в проекте — это два файла: экран в `src/screens/` (вся логика, стили, зависимости) и `page.tsx` в `src/app/` (точка входа для роутинга Next.js). Экран генерируется из шаблона, `page.tsx` создаётся вручную.
|
||||
|
||||
## Организация
|
||||
|
||||
- `page.tsx` — тонкий файл: только `metadata` и рендер экрана. Логика, стили и зависимости живут в экране, не в `page.tsx`.
|
||||
- `error.tsx` и `not-found.tsx` делегируют разметку экранам по тому же принципу.
|
||||
- `layout.tsx` — точка подключения провайдеров и глобальных стилей. Вёрстка layout-обёрток выносится в слой `layouts/`.
|
||||
- Стили в файлах роутинга не используются — стилизация только внутри вызываемых компонентов.
|
||||
|
||||
## Реализация
|
||||
|
||||
- Каждый `page.tsx` экспортирует `metadata` с `title` — он подставляется в шаблон корневого layout (`%s | App`).
|
||||
- Корневой `layout.tsx` задаёт `metadata` с `title.template`, `description`, `metadataBase` и OpenGraph-настройками.
|
||||
|
||||
## Примеры
|
||||
|
||||
`src/app/profile/[id]/page.tsx`
|
||||
|
||||
```tsx
|
||||
import type { Metadata } from 'next'
|
||||
import { ProfileScreen } from '@/screens/profile'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Профиль',
|
||||
description: 'Страница профиля пользователя',
|
||||
}
|
||||
|
||||
type ProfilePageProps = {
|
||||
params: Promise<{ id: string }>
|
||||
}
|
||||
|
||||
export default async function ProfilePage({ params }: ProfilePageProps) {
|
||||
const { id } = await params
|
||||
|
||||
return <ProfileScreen id={id} />
|
||||
}
|
||||
```
|
||||
|
||||
`src/app/error.tsx`
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { ErrorScreen } from '@/screens/error'
|
||||
|
||||
type ErrorPageProps = {
|
||||
error: Error & { digest?: string }
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
|
||||
return <ErrorScreen error={error} reset={reset} />
|
||||
}
|
||||
|
||||
export default ErrorPage
|
||||
```
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
title: Структура проекта
|
||||
scope: applied
|
||||
keywords: [структура проекта, папки, src/app, src/shared, SLM Design, Next.js структура]
|
||||
when: "Организация папок и файлов в Next.js проекте"
|
||||
---
|
||||
# Структура проекта
|
||||
|
||||
Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
|
||||
|
||||
## Корень репозитория
|
||||
|
||||
```text
|
||||
project-root/
|
||||
├── .templates/ # Шаблоны для генерации модулей
|
||||
├── .vscode/ # Настройки и рекомендуемые расширения VS Code
|
||||
├── public/ # Статика, доступная по прямому URL
|
||||
├── src/ # Исходный код приложения
|
||||
├── .env.example # Переменные окружения проекта (шаблон)
|
||||
├── .env # Переменные окружения проекта (не коммитить)
|
||||
├── .gitignore
|
||||
├── AGENTS.md # Инструкции для AI-агентов
|
||||
├── biome.json # Линтер и форматтер (вместо ESLint + Prettier)
|
||||
├── next.config.ts # Конфигурация Next.js
|
||||
├── package.json # Зависимости и скрипты
|
||||
├── postcss.config.mjs # Конфигурация PostCSS
|
||||
└── tsconfig.json # Конфигурация TypeScript
|
||||
```
|
||||
|
||||
## Папка `public/`
|
||||
|
||||
Хранит статические файлы, которые отдаются по прямому URL без обработки сборщиком:
|
||||
|
||||
```text
|
||||
public/
|
||||
└── og-image.png
|
||||
```
|
||||
|
||||
Компоненты, стили и другой исходный код здесь не размещаются.
|
||||
|
||||
## Папка `src/`
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/ # Роутинг Next.js, провайдеры, глобальные стили
|
||||
├── layouts/ # Каркасы страниц (header, footer, sidebar)
|
||||
├── screens/ # Контент конкретной страницы
|
||||
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
||||
├── business/ # Бизнес-домены (auth, catalog, orders)
|
||||
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
|
||||
├── ui/ # UI-кит без бизнес-логики (button, modal, toast)
|
||||
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
||||
```
|
||||
|
||||
Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture).
|
||||
|
||||
### Папка `app/`
|
||||
|
||||
Точка входа приложения. Совмещает инициализацию (провайдеры, глобальные стили) и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── providers/ # Провайдеры приложения
|
||||
├── styles/ # Глобальные стили
|
||||
├── layout.tsx # Корневой layout
|
||||
└── page.tsx # Главная страница
|
||||
```
|
||||
|
||||
## Папка `.templates/`
|
||||
|
||||
Содержит шаблоны для генерации кода. Каждый подкаталог — шаблон отдельного типа модуля:
|
||||
|
||||
```text
|
||||
.templates/
|
||||
├── component/ # Шаблон компонента
|
||||
├── screen/ # Шаблон экрана
|
||||
├── layout/ # Шаблон layout
|
||||
├── widget/ # Шаблон виджета
|
||||
├── business/ # Шаблон бизнес-модуля
|
||||
└── store/ # Шаблон стора
|
||||
```
|
||||
|
||||
Подробнее о генерации описано в разделе [Шаблоны и генерация кода](./templates-generation).
|
||||
|
||||
## Конфигурационные файлы
|
||||
|
||||
| Файл | Назначение |
|
||||
|---|---|
|
||||
| `next.config.ts` | Настройки Next.js: редиректы, переменные окружения, webpack |
|
||||
| `tsconfig.json` | Настройки TypeScript: пути, строгость, таргет |
|
||||
| `biome.json` | Правила линтера и форматтера Biome |
|
||||
| `postcss.config.mjs` | Подключение PostCSS-плагинов (CSS Modules, custom media) |
|
||||
| `package.json` | Зависимости, версии, npm-скрипты |
|
||||
| `AGENTS.md` | Инструкции для AI-агентов, работающих в проекте |
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
- `.env` — переменные окружения проекта, запрещено коммитить
|
||||
- `.env.example` — шаблон, коммитится в репозиторий
|
||||
|
||||
Переменные с префиксом `NEXT_PUBLIC_` доступны в клиентском коде. Остальные доступны только на сервере.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [стор, store, zustand, состояние, глобальное состояние]
|
||||
when: "Работа с глобальным состоянием: создание стора, подписка"
|
||||
---
|
||||
@@ -1,285 +0,0 @@
|
||||
---
|
||||
title: Стили
|
||||
scope: applied
|
||||
keywords: [css, postcss, модули, css modules, токены, медиа-запросы, вложенность, класс]
|
||||
when: "Стилизация: CSS Modules, PostCSS, переменные, медиа-запросы"
|
||||
---
|
||||
# Стили
|
||||
|
||||
Раздел описывает правила написания 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.
|
||||
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
|
||||
|
||||
## Приоритет стилизации
|
||||
|
||||
Основной UI-фреймворк проекта — **Mantine**. При стилизации компонентов придерживаться следующего приоритета:
|
||||
|
||||
1. **Mantine-компоненты и их пропсы** — в первую очередь использовать встроенные возможности Mantine (пропсы, `classNames`, `styles`).
|
||||
2. **Глобальные CSS-токены** (`--color-*`, `--space-*`, `--radius-*`) — для значений, которые не покрываются Mantine.
|
||||
3. **PostCSS Modules** — когда Mantine не покрывает задачу и нужна кастомная стилизация.
|
||||
|
||||
## Что запрещено
|
||||
|
||||
- **Инлайн-стили** — использование атрибута `style` в компонентах строго запрещено.
|
||||
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
||||
- **Глобальные стили** вне `app/styles/` запрещены.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: SVG-спрайты
|
||||
scope: applied
|
||||
keywords: [svg, спрайт, иконка, icon, sprite]
|
||||
when: "Работа с SVG-иконками и спрайтами"
|
||||
---
|
||||
# SVG-спрайты
|
||||
@@ -1,174 +0,0 @@
|
||||
---
|
||||
title: Шаблоны и генерация кода
|
||||
scope: applied
|
||||
keywords: [шаблон, генерация, template, scaffold, plop, hygen, .templates]
|
||||
when: "Генерация кода из шаблонов, создание новых шаблонов"
|
||||
---
|
||||
<!-- @formatter:off -->
|
||||
::: v-pre
|
||||
|
||||
# Шаблоны и генерация кода
|
||||
|
||||
Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI.
|
||||
|
||||
## Структура шаблонов
|
||||
|
||||
Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон.
|
||||
|
||||
```text
|
||||
.templates/
|
||||
├── component/ # шаблон компонента
|
||||
│ └── {{name.kebabCase}}/
|
||||
│ ├── styles/
|
||||
│ │ └── {{name.kebabCase}}.module.css
|
||||
│ ├── types/
|
||||
│ │ └── {{name.kebabCase}}.type.ts
|
||||
│ ├── {{name.kebabCase}}.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
|
||||
```
|
||||
|
||||
## Как создать новый шаблон
|
||||
|
||||
1. Создать папку в `.templates/` с именем шаблона (например `hook`).
|
||||
2. Внутри разместить файлы и папки, используя `{{name}}` и модификаторы в именах и содержимом.
|
||||
3. Шаблон сразу доступен и в расширении VS Code, и в CLI.
|
||||
|
||||
Пример — создание шаблона для хука:
|
||||
|
||||
```text
|
||||
.templates/
|
||||
└── hook/
|
||||
└── {{name.kebabCase}}/
|
||||
├── {{name.kebabCase}}.hook.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
```ts
|
||||
// .templates/hook/{{name.kebabCase}}.hook.ts
|
||||
export const {{name.camelCase}} = () => {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// .templates/hook/index.ts
|
||||
export { {{name.camelCase}} } from './{{name.kebabCase}}.hook'
|
||||
```
|
||||
|
||||
## Примеры шаблонов
|
||||
|
||||
### Шаблон компонента
|
||||
|
||||
```ts
|
||||
// .templates/component/index.ts
|
||||
export { {{name.pascalCase}} } from './{{name.kebabCase}}'
|
||||
```
|
||||
|
||||
```ts
|
||||
// .templates/component/types/{{name.kebabCase}}.type.ts
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Params = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||||
```
|
||||
|
||||
```tsx
|
||||
// .templates/component/{{name.kebabCase}}.tsx
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* {{name.pascalCase}}.
|
||||
*/
|
||||
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* .templates/component/styles/{{name.kebabCase}}.module.css */
|
||||
.root {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Генерация через VS Code
|
||||
|
||||
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
|
||||
|
||||
1. ПКМ на целевой папке в проводнике VS Code.
|
||||
2. **Generate from template** → выбрать шаблон.
|
||||
3. Ввести имя (например `button`) — расширение подставит его во все переменные `{{name}}`.
|
||||
|
||||
## Генерация через CLI
|
||||
|
||||
[@gromlab/create](https://www.npmjs.com/package/@gromlab/create) — CLI для генерации из тех же шаблонов. Используется через npx, глобальная установка не требуется.
|
||||
|
||||
```bash
|
||||
npx @gromlab/create <шаблон> <имя> <путь>
|
||||
```
|
||||
|
||||
| Команда | Что создаёт |
|
||||
|---|---|
|
||||
| `npx @gromlab/create component button src/ui` | Компонент |
|
||||
| `npx @gromlab/create business auth src/business` | Бизнес-модуль |
|
||||
| `npx @gromlab/create widget header src/widgets` | Виджет |
|
||||
| `npx @gromlab/create layout admin src/layouts` | Layout |
|
||||
| `npx @gromlab/create screen home src/screens` | Экран |
|
||||
| `npx @gromlab/create store auth src/business/auth/stores` | Стор |
|
||||
|
||||
:::
|
||||
|
||||
## Какие модули генерируются из шаблонов
|
||||
|
||||
| Модуль | Слой | Шаблон |
|
||||
|---|---|---|
|
||||
| Компонент | `ui/` | `component` |
|
||||
| Бизнес-модуль | `business/` | `business` |
|
||||
| Виджет | `widgets/` | `widget` |
|
||||
| Layout | `layouts/` | `layout` |
|
||||
| Экран | `screens/` | `screen` |
|
||||
| Стор | `stores/` | `store` |
|
||||
|
||||
## Когда создавать новый шаблон
|
||||
|
||||
- Повторяющаяся структура появляется больше одного раза.
|
||||
- Существующий шаблон не покрывает нужный тип модуля.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
scope: applied
|
||||
keywords: [видео, video, плеер, mp4]
|
||||
when: "Встраивание и работа с видео"
|
||||
---
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: Настройка VS Code
|
||||
scope: applied
|
||||
keywords: [vscode, редактор, расширение, настройка, extension, .vscode]
|
||||
when: "Настройка VS Code: расширения, settings.json, сниппеты"
|
||||
---
|
||||
# Настройка VS Code
|
||||
|
||||
Каждый проект содержит папку `.vscode/` с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями.
|
||||
|
||||
## Структура `.vscode/`
|
||||
|
||||
```text
|
||||
.vscode/
|
||||
├── extensions.json # Рекомендуемые расширения
|
||||
└── settings.json # Настройки редактора для проекта
|
||||
```
|
||||
|
||||
Оба файла коммитятся в репозиторий.
|
||||
|
||||
## Расширения
|
||||
|
||||
Файл `.vscode/extensions.json` определяет список расширений, которые VS Code предложит установить при открытии проекта.
|
||||
|
||||
```json
|
||||
// .vscode/extensions.json
|
||||
{
|
||||
"recommendations": [
|
||||
"biomejs.biome",
|
||||
"MyTemplateGenerator.mytemplategenerator",
|
||||
"csstools.postcss"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Расширение | Назначение |
|
||||
|---|---|
|
||||
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
|
||||
| Template File Generator \| gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню |
|
||||
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) |
|
||||
|
||||
### Зачем это нужно
|
||||
|
||||
- Новый участник команды получает все нужные расширения одним кликом.
|
||||
- Нет разночтений: все используют одинаковый форматтер и линтер.
|
||||
- Расширения привязаны к проекту, а не к конкретному разработчику.
|
||||
|
||||
## Настройки редактора
|
||||
|
||||
Файл `.vscode/settings.json` переопределяет пользовательские настройки VS Code на уровне проекта.
|
||||
|
||||
```json
|
||||
// .vscode/settings.json
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.css": "postcss"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Разбор настроек
|
||||
|
||||
| Настройка | Значение | Что делает |
|
||||
|---|---|---|
|
||||
| `editor.defaultFormatter` | `biomejs.biome` | Biome используется как единственный форматтер для всех файлов |
|
||||
| `editor.formatOnSave` | `true` | Код автоматически форматируется при каждом сохранении |
|
||||
| `codeActionsOnSave.source.fixAll.biome` | `explicit` | Biome автоматически применяет безопасные исправления при сохранении |
|
||||
| `codeActionsOnSave.source.organizeImports.biome` | `explicit` | Импорты сортируются и группируются автоматически при сохранении |
|
||||
| `files.associations` | `"*.css": "postcss"` | Все CSS-файлы открываются с подсветкой PostCSS вместо стандартного CSS |
|
||||
|
||||
### Зачем это нужно
|
||||
|
||||
- **Единый стиль кода** -- форматирование происходит автоматически, невозможно закоммитить неформатированный код.
|
||||
- **Автофикс при сохранении** -- распространённые ошибки линтинга исправляются без ручного вмешательства.
|
||||
- **Сортировка импортов** -- импорты всегда в одном порядке, без конфликтов при мерже.
|
||||
- **PostCSS-подсветка** -- кастомные at-правила (`@custom-media`, `@define-mixin`) подсвечиваются корректно, а не как ошибки.
|
||||
|
||||
## Что не должно быть в `.vscode/`
|
||||
|
||||
Не коммитятся файлы, специфичные для конкретного разработчика:
|
||||
|
||||
- **Не коммитить**: отладочные конфигурации с локальными путями, персональные сниппеты, настройки тем оформления.
|
||||
- **Коммитить**: только `extensions.json` и `settings.json` с общими для команды настройками.
|
||||
@@ -1,665 +0,0 @@
|
||||
---
|
||||
title: Архитектура
|
||||
scope: basics
|
||||
keywords: [SLM Design, слой, модуль, сегмент, архитектура, FSD, scoped layered module]
|
||||
when: "Организация кода: слои, модули, зависимости между модулями"
|
||||
---
|
||||
|
||||
<!-- /index -->
|
||||
# SLM Design
|
||||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||
|
||||
## Преимущества
|
||||
|
||||
### Вертикальная организация домена
|
||||
|
||||
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
|
||||
|
||||
### Dependency Injection без фреймворков
|
||||
|
||||
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
|
||||
|
||||
### Разделение ответственности без перегрузки слоёв
|
||||
|
||||
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
|
||||
|
||||
### Горизонтальная инкапсуляция
|
||||
|
||||
Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
|
||||
|
||||
### Колокация по умолчанию
|
||||
|
||||
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
|
||||
|
||||
### Явное разделение каркаса и контента
|
||||
|
||||
Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью.
|
||||
|
||||
### Масштабирование через группировку
|
||||
|
||||
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
|
||||
|
||||
## Происхождение
|
||||
|
||||
SLM Design вырос на основе:
|
||||
|
||||
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
|
||||
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
|
||||
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
|
||||
- **Colocation Principle** — код живёт рядом с местом использования
|
||||
|
||||
## Пример структуры проекта
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/
|
||||
│
|
||||
├── layouts/
|
||||
│ ├── main/
|
||||
│ └── dashboard/
|
||||
│
|
||||
├── screens/
|
||||
│ ├── home/
|
||||
│ ├── products/
|
||||
│ ├── product-detail/
|
||||
│ └── about/
|
||||
│
|
||||
├── widgets/
|
||||
│ ├── page-heading/
|
||||
│ ├── hero-section/
|
||||
│ └── promo-banner/
|
||||
│
|
||||
├── business/
|
||||
│ ├── auth/
|
||||
│ ├── catalog/
|
||||
│ ├── orders/
|
||||
│ └── chat/
|
||||
│
|
||||
├── infrastructure/
|
||||
│ ├── theme/
|
||||
│ ├── i18n/
|
||||
│ ├── backend-api/
|
||||
│ └── logger/
|
||||
│
|
||||
├── ui/
|
||||
│ ├── button/
|
||||
│ ├── input/
|
||||
│ ├── modal/
|
||||
│ ├── toast/
|
||||
│ └── dropdown/
|
||||
│
|
||||
└── shared/
|
||||
├── lib/
|
||||
├── types/
|
||||
└── styles/
|
||||
```
|
||||
|
||||
## Принципы
|
||||
|
||||
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
|
||||
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
|
||||
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
|
||||
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
|
||||
|
||||
<!-- /reference/layers -->
|
||||
## Слои
|
||||
|
||||
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
||||
|
||||
### Определение
|
||||
|
||||
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
|
||||
|
||||
### Группы слоёв
|
||||
|
||||
Слои делятся на три группы:
|
||||
|
||||
| Группа | Слои | Описание |
|
||||
|--------|------|----------|
|
||||
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
||||
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
||||
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
||||
|
||||
### Направление зависимостей
|
||||
|
||||
Любой импорт между модулями — только через публичный API.
|
||||
|
||||
```
|
||||
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
|
||||
```
|
||||
|
||||
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
||||
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
||||
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API
|
||||
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
|
||||
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
|
||||
|
||||
|
||||
### Слой App
|
||||
|
||||
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
|
||||
|
||||
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
|
||||
|
||||
#### Требования
|
||||
|
||||
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
|
||||
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
|
||||
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
|
||||
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
|
||||
- Никем не импортируется
|
||||
|
||||
### Слой Layouts
|
||||
|
||||
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
|
||||
|
||||
```text
|
||||
src/layouts/
|
||||
├── main/
|
||||
├── dashboard/
|
||||
└── auth/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Содержит только модули
|
||||
- Не содержит бизнес-логику
|
||||
- Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую
|
||||
|
||||
### Слой Screens
|
||||
|
||||
Контент конкретной страницы: собирает её из модулей нижних слоёв.
|
||||
|
||||
```text
|
||||
src/screens/
|
||||
├── home/
|
||||
├── products/
|
||||
├── product-detail/
|
||||
├── about/
|
||||
└── contacts/
|
||||
```
|
||||
|
||||
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`).
|
||||
|
||||
```text
|
||||
src/screens/
|
||||
├── shop/
|
||||
│ ├── home/
|
||||
│ ├── products/
|
||||
│ ├── product-detail/
|
||||
│ └── cart/
|
||||
├── account/
|
||||
│ ├── profile/
|
||||
│ ├── settings/
|
||||
│ └── order-history/
|
||||
└── info/
|
||||
├── about/
|
||||
├── contacts/
|
||||
└── faq/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Содержит только модули
|
||||
- Не содержит бизнес-логику
|
||||
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business`
|
||||
|
||||
### Слой Widgets
|
||||
|
||||
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
|
||||
|
||||
Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget.
|
||||
|
||||
```text
|
||||
src/widgets/
|
||||
├── page-heading/
|
||||
├── hero-section/
|
||||
├── onboarding-checklist/
|
||||
├── promo-banner/
|
||||
└── error-boundary/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/`
|
||||
- Используется в нескольких screens или layouts
|
||||
|
||||
### Слой Business
|
||||
|
||||
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
|
||||
|
||||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую.
|
||||
|
||||
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
|
||||
|
||||
```text
|
||||
src/business/
|
||||
├── auth/
|
||||
├── catalog/
|
||||
├── orders/
|
||||
├── checkout/
|
||||
└── chat/
|
||||
```
|
||||
|
||||
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`).
|
||||
|
||||
```text
|
||||
src/business/
|
||||
├── commerce/
|
||||
│ ├── catalog/
|
||||
│ ├── cart/
|
||||
│ ├── orders/
|
||||
│ └── checkout/
|
||||
└── communication/
|
||||
├── chat/
|
||||
└── notifications/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Один модуль = один бизнес-домен
|
||||
- Циклические зависимости между доменами запрещены
|
||||
- Импорт кода между доменами — через фабрику. `import type` — напрямую
|
||||
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
||||
|
||||
### Слой Infrastructure
|
||||
|
||||
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
||||
|
||||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`.
|
||||
|
||||
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||
|
||||
```text
|
||||
src/infrastructure/
|
||||
├── theme/
|
||||
├── i18n/
|
||||
├── backend-api/
|
||||
├── maps-api/
|
||||
├── logger/
|
||||
├── feature-flags/
|
||||
└── realtime/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Один модуль = один техсервис
|
||||
- Импортирует `infrastructure/`, `ui/`, `shared/`
|
||||
|
||||
### Слой UI
|
||||
|
||||
UI-кит без бизнес-логики: button, carousel, toast, modal.
|
||||
|
||||
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
|
||||
|
||||
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
|
||||
|
||||
```text
|
||||
src/ui/
|
||||
├── button/
|
||||
├── input/
|
||||
├── icon/
|
||||
├── carousel/
|
||||
├── modal/
|
||||
├── toast/
|
||||
├── dropdown/
|
||||
├── tabs/
|
||||
└── tooltip/
|
||||
```
|
||||
|
||||
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
|
||||
|
||||
```text
|
||||
src/ui/
|
||||
├── primitives/
|
||||
│ ├── button/
|
||||
│ ├── input/
|
||||
│ ├── icon/
|
||||
│ └── badge/
|
||||
└── composites/
|
||||
├── carousel/
|
||||
├── modal/
|
||||
├── dropdown/
|
||||
├── tabs/
|
||||
└── tooltip/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Не содержит бизнес-логику
|
||||
- Импортирует только `ui/` и `shared/`
|
||||
|
||||
### Слой Shared
|
||||
|
||||
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
|
||||
|
||||
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
||||
|
||||
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||
|
||||
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
||||
|
||||
```text
|
||||
src/shared/
|
||||
├── lib/
|
||||
├── types/
|
||||
├── styles/
|
||||
└── sprites/
|
||||
```
|
||||
|
||||
#### Требования
|
||||
|
||||
- Не имеет runtime-состояния
|
||||
|
||||
<!-- /reference/modules -->
|
||||
## Модули
|
||||
|
||||
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.
|
||||
|
||||
### Определение
|
||||
|
||||
**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.**
|
||||
|
||||
### Модуль vs компонент
|
||||
|
||||
**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля.
|
||||
|
||||
**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`).
|
||||
|
||||
```text
|
||||
auth/
|
||||
├── ui/
|
||||
│ ├── auth-guard.tsx
|
||||
│ └── logout-button.tsx
|
||||
├── parts/
|
||||
│ ├── login-form/
|
||||
│ ├── registration-form/
|
||||
│ └── restore-form/
|
||||
├── hooks/
|
||||
├── stores/
|
||||
├── types/
|
||||
├── auth.tsx # корневой компонент (опционален)
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
### Структура
|
||||
|
||||
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов.
|
||||
|
||||
```text
|
||||
{module-name}/
|
||||
├── {module-name}.tsx # корневой компонент (опционален)
|
||||
├── ui/ # компоненты модуля (только .tsx)
|
||||
├── parts/ # вложенные модули (со своими сегментами)
|
||||
├── hooks/ # хуки
|
||||
├── stores/ # сторы состояния
|
||||
├── services/ # внешние источники данных
|
||||
├── mappers/ # трансформация данных между форматами
|
||||
├── types/ # типы
|
||||
├── styles/ # стили
|
||||
├── lib/ # утилиты модуля
|
||||
├── config/ # константы
|
||||
└── index.ts # публичный API
|
||||
```
|
||||
|
||||
Подробное описание каждого сегмента — в разделе [Сегменты](/reference/segments).
|
||||
|
||||
### Публичный API
|
||||
|
||||
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
|
||||
|
||||
```ts
|
||||
// business/auth/index.ts
|
||||
export type { User, Session } from './types/user.types'
|
||||
export { useAuth } from './hooks/use-auth.hook'
|
||||
export { AuthGuard } from './ui/auth-guard'
|
||||
```
|
||||
|
||||
Импорт в обход `index.ts` запрещён:
|
||||
|
||||
```ts
|
||||
// Плохо
|
||||
import { validateToken } from '@/business/auth/lib/tokens'
|
||||
|
||||
// Хорошо
|
||||
import { useAuth } from '@/business/auth'
|
||||
```
|
||||
|
||||
### Фабрика
|
||||
|
||||
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
|
||||
|
||||
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью.
|
||||
|
||||
#### Модуль без зависимостей — прямой экспорт:
|
||||
|
||||
```ts
|
||||
// business/auth/index.ts
|
||||
export { useAuth } from './hooks/use-auth'
|
||||
export { useCurrentUser } from './hooks/use-current-user'
|
||||
export type { User, Session } from './types'
|
||||
```
|
||||
|
||||
#### Модуль с зависимостями — фабрика:
|
||||
|
||||
```ts
|
||||
// business/chat/types/deps.ts
|
||||
import type { User } from '@/business/auth'
|
||||
|
||||
export interface ChatDeps {
|
||||
useCurrentUser: () => User | null
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// business/chat/index.ts
|
||||
import type { ChatDeps } from './types/deps'
|
||||
|
||||
export function chatFactory(deps: ChatDeps) {
|
||||
return {
|
||||
useMessages: (roomId: string) => {
|
||||
const user = deps.useCurrentUser()
|
||||
// ...
|
||||
},
|
||||
useSendMessage: (roomId: string) => {
|
||||
const user = deps.useCurrentUser()
|
||||
return (text: string) => { /* ... */ }
|
||||
},
|
||||
useChatRooms: () => {
|
||||
const user = deps.useCurrentUser()
|
||||
// ...
|
||||
},
|
||||
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
|
||||
}
|
||||
}
|
||||
|
||||
export type { Message, ChatRoom } from './types'
|
||||
export type { ChatDeps } from './types/deps'
|
||||
```
|
||||
|
||||
#### Использование на странице:
|
||||
|
||||
```tsx
|
||||
// screens/support/support.tsx
|
||||
import { useCurrentUser } from '@/business/auth'
|
||||
import { chatFactory } from '@/business/chat'
|
||||
|
||||
const chat = chatFactory({ useCurrentUser })
|
||||
|
||||
export function SupportScreen() {
|
||||
const { useMessages, useSendMessage, ChatBadge } = chat
|
||||
const messages = useMessages('support')
|
||||
const sendMessage = useSendMessage('support')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ChatBadge count={messages.length} />
|
||||
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
|
||||
<MessageInput onSend={sendMessage} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Жизненный цикл
|
||||
|
||||
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
||||
|
||||
- Нужен на одной странице → `screens/{name}/parts/`
|
||||
- Появился в 2+ местах → поднимается по природе:
|
||||
- абстрактный UI → `ui/`
|
||||
- блок с данными/логикой → `widgets/`
|
||||
- представление бизнес-домена → `business/{area}/parts/`
|
||||
|
||||
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
||||
|
||||
<!-- /reference/segments -->
|
||||
## Сегменты
|
||||
|
||||
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
||||
|
||||
### Определение
|
||||
|
||||
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
|
||||
|
||||
### Обзор
|
||||
|
||||
| Сегмент | Содержимое |
|
||||
|---------|------------|
|
||||
| `ui/` | Компоненты модуля — только `.tsx` файлы |
|
||||
| `parts/` | Вложенные модули со своими сегментами |
|
||||
| `hooks/` | React-хуки |
|
||||
| `stores/` | Сторы состояния |
|
||||
| `services/` | Работа с внешними источниками данных |
|
||||
| `mappers/` | Трансформация данных между форматами |
|
||||
| `types/` | TypeScript-типы и интерфейсы |
|
||||
| `styles/` | Стили |
|
||||
| `lib/` | Утилиты и хелперы модуля |
|
||||
| `config/` | Константы и конфигурация |
|
||||
|
||||
### Сегмент ui/
|
||||
|
||||
Компоненты, принадлежащие модулю. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.
|
||||
|
||||
```text
|
||||
auth/
|
||||
├── ui/
|
||||
│ ├── auth-provider.tsx
|
||||
│ ├── auth-guard.tsx
|
||||
│ └── logout-button.tsx
|
||||
├── types/
|
||||
├── hooks/
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`.
|
||||
|
||||
### Сегмент parts/
|
||||
|
||||
Вложенные модули со своими сегментами. Каждый элемент `parts/` — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.
|
||||
|
||||
```text
|
||||
home/
|
||||
├── parts/
|
||||
│ ├── hero-section/
|
||||
│ │ ├── hero-section.tsx
|
||||
│ │ ├── styles/
|
||||
│ │ └── parts/
|
||||
│ │ └── top-banner/
|
||||
│ │ └── top-banner.tsx
|
||||
│ └── features-section/
|
||||
│ ├── features-section.tsx
|
||||
│ └── hooks/
|
||||
├── home.screen.tsx
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
Отличие от `ui/`: элемент `parts/` — модуль со своими сегментами. Элемент `ui/` — компонент, один `.tsx` файл.
|
||||
|
||||
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
|
||||
|
||||
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
|
||||
|
||||
### Сегмент hooks/
|
||||
|
||||
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
|
||||
|
||||
```text
|
||||
hooks/
|
||||
├── use-auth.hook.ts
|
||||
├── use-session.hook.ts
|
||||
└── use-permissions.hook.ts
|
||||
```
|
||||
|
||||
### Сегмент stores/
|
||||
|
||||
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
|
||||
|
||||
```text
|
||||
stores/
|
||||
├── auth.store.ts
|
||||
└── session.store.ts
|
||||
```
|
||||
|
||||
### Сегмент services/
|
||||
|
||||
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
|
||||
|
||||
```text
|
||||
services/
|
||||
├── auth.service.ts
|
||||
└── token.service.ts
|
||||
```
|
||||
|
||||
### Сегмент mappers/
|
||||
|
||||
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
|
||||
|
||||
```text
|
||||
mappers/
|
||||
├── map-user.ts
|
||||
├── map-product.ts
|
||||
└── map-order-to-dto.ts
|
||||
```
|
||||
|
||||
### Сегмент types/
|
||||
|
||||
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
|
||||
|
||||
```text
|
||||
types/
|
||||
├── user.type.ts
|
||||
└── session.type.ts
|
||||
```
|
||||
|
||||
### Сегмент styles/
|
||||
|
||||
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
|
||||
|
||||
```text
|
||||
styles/
|
||||
├── auth.module.css
|
||||
└── login-form.module.css
|
||||
```
|
||||
|
||||
### Сегмент lib/
|
||||
|
||||
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
|
||||
|
||||
```text
|
||||
lib/
|
||||
├── validate-email.ts
|
||||
└── format-phone.ts
|
||||
```
|
||||
|
||||
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
|
||||
|
||||
### Сегмент config/
|
||||
|
||||
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
|
||||
|
||||
```text
|
||||
config/
|
||||
├── routes.ts
|
||||
└── constants.ts
|
||||
```
|
||||
@@ -1,154 +0,0 @@
|
||||
---
|
||||
title: Стиль кода
|
||||
scope: basics
|
||||
keywords: [форматирование, импорт, отступ, кавычки, early return, точка с запятой, линтер]
|
||||
when: "Написание или ревью любого кода: форматирование, импорты, структура файла"
|
||||
---
|
||||
# Стиль кода
|
||||
|
||||
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
|
||||
|
||||
## Отступы
|
||||
|
||||
- 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 } };
|
||||
```
|
||||
@@ -1,136 +0,0 @@
|
||||
---
|
||||
title: Документирование
|
||||
scope: basics
|
||||
keywords: [JSDoc, комментарий, документирование, описание функции, описание компонента]
|
||||
when: "Документирование кода: JSDoc для функций, компонентов, типов"
|
||||
---
|
||||
# Документирование
|
||||
|
||||
Этот раздел описывает правила документирования кода: когда и как писать
|
||||
комментарии к компонентам, функциям, типам и интерфейсам.
|
||||
|
||||
## Общие правила
|
||||
|
||||
- Документировать публичные функции, компоненты, типы, интерфейсы и enum.
|
||||
- Не документировать очевидное — если название говорит само за себя, комментарий не нужен.
|
||||
- Не документировать параметры, возвращаемые значения и типы пропсов — они видны из сигнатуры.
|
||||
- Описание через пользу и назначение, а не через внутреннюю реализацию.
|
||||
- Описание завершается точкой.
|
||||
|
||||
## Функции
|
||||
|
||||
Для документирования функций используется шаблон. Описание механики опционально —
|
||||
добавляется когда логика нетривиальна.
|
||||
|
||||
**Шаблон**
|
||||
```ts
|
||||
/**
|
||||
* <Что делает функция в 1 строке>.
|
||||
*
|
||||
* <Опционально: описание сложной механики или важных нюансов>.
|
||||
*/
|
||||
```
|
||||
|
||||
**Хорошо**
|
||||
```ts
|
||||
/**
|
||||
* Форматирует цену с символом валюты.
|
||||
*/
|
||||
export const formatPrice = (value: number): string => { ... }
|
||||
|
||||
/**
|
||||
* Рекурсивно собирает дерево категорий из плоского списка.
|
||||
*
|
||||
* Группирует элементы по parentId, начиная с корневых (parentId = null).
|
||||
* Категории без родителя попадают в корень дерева.
|
||||
*/
|
||||
export const buildCategoryTree = (categories: Category[]): CategoryTree[] => { ... }
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
```ts
|
||||
// Плохо: дублирует сигнатуру.
|
||||
/**
|
||||
* @param value - число
|
||||
* @returns строка с ценой
|
||||
*/
|
||||
```
|
||||
|
||||
## Компоненты
|
||||
|
||||
Компонент описывает своё **назначение** и **сценарии применения** — это помогает понять, когда и где его использовать, без необходимости читать реализацию.
|
||||
|
||||
**Шаблон**
|
||||
```ts
|
||||
/**
|
||||
* <Назначение компонента в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
* - <сценарий 3>
|
||||
*/
|
||||
```
|
||||
|
||||
**Хорошо**
|
||||
```tsx
|
||||
/**
|
||||
* Контейнер с адаптивной максимальной шириной.
|
||||
*
|
||||
* Используется для:
|
||||
* - обёртки контента страниц с ограничением ширины
|
||||
* - центрирования блоков в лейауте
|
||||
*/
|
||||
export const Container = (props: ContainerProps) => { ... }
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
```tsx
|
||||
// Плохо: описывает реализацию, а не назначение.
|
||||
/**
|
||||
* Рендерит div с className и htmlAttr.
|
||||
*/
|
||||
|
||||
// Плохо: нет описания вообще.
|
||||
export const Container = (props: ContainerProps) => { ... }
|
||||
```
|
||||
|
||||
## Типы, интерфейсы, enum
|
||||
|
||||
Документируются назначение сущности и каждое её поле.
|
||||
|
||||
**Хорошо**
|
||||
```ts
|
||||
/**
|
||||
* Фильтры списка задач.
|
||||
*/
|
||||
export enum TodoFilter {
|
||||
/** Все задачи. */
|
||||
ALL = 'all',
|
||||
/** Только активные. */
|
||||
ACTIVE = 'active',
|
||||
/** Только завершённые. */
|
||||
COMPLETED = 'completed',
|
||||
}
|
||||
|
||||
/**
|
||||
* Задача пользователя.
|
||||
*/
|
||||
export interface TodoItem {
|
||||
/** Уникальный идентификатор задачи. */
|
||||
id: string;
|
||||
/** Текст задачи. */
|
||||
text: string;
|
||||
/** Статус выполнения. */
|
||||
completed: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
```ts
|
||||
// Плохо: описывает очевидное.
|
||||
export interface TodoItem {
|
||||
/** id — это id */
|
||||
id: string;
|
||||
}
|
||||
```
|
||||
@@ -1,149 +0,0 @@
|
||||
---
|
||||
title: Именование
|
||||
scope: basics
|
||||
keywords: [camelCase, kebab-case, PascalCase, имя файла, имя переменной, имя компонента, имя хука]
|
||||
when: "Создание файлов, переменных, компонентов, хуков — выбор имени"
|
||||
---
|
||||
# Именование
|
||||
|
||||
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
|
||||
|
||||
## Базовые правила
|
||||
|
||||
| Что | Рекомендуется |
|
||||
| ---------------- | ---------------------- |
|
||||
| Папки | `kebab-case` |
|
||||
| Файлы | `kebab-case` |
|
||||
| Переменные | `camelCase` |
|
||||
| Константы | `SCREAMING_SNAKE_CASE` |
|
||||
| Классы | `PascalCase` |
|
||||
| React-компоненты | `PascalCase` |
|
||||
| Хуки | `useSomething` |
|
||||
| CSS классы | `camelCase` |
|
||||
| Ключи enum | `SCREAMING_SNAKE_CASE` |
|
||||
|
||||
|
||||
## Именование файлов
|
||||
|
||||
Суффикс обозначает роль или тип файла. Пишется в единственном числе.
|
||||
Формат: `name.<suffix>.ts`.
|
||||
|
||||
**Хуки**
|
||||
- `use-name.hook.ts` — файл хука, функция именуется `useName`
|
||||
|
||||
**Корневые компоненты модулей**
|
||||
- `.business.tsx` — бизнес-модуль (`business/`)
|
||||
- `.infra.tsx` — инфраструктурный модуль (`infrastructure/`)
|
||||
- `.ui.tsx` — UI-компонент (`ui/`)
|
||||
- `.screen.tsx` — экран (`screens/`)
|
||||
- `.widget.tsx` — виджет (`widgets/`)
|
||||
- `.layout.tsx` — layout (`layouts/`)
|
||||
|
||||
**Логика**
|
||||
- `.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` — тесты
|
||||
- `.mock.ts` — моки
|
||||
|
||||
**Хорошо**
|
||||
```text
|
||||
business/
|
||||
└── auth-by-email/
|
||||
├── ui/
|
||||
│ └── login-form.tsx
|
||||
├── hooks/
|
||||
│ └── use-auth.hook.ts
|
||||
├── stores/
|
||||
│ └── auth.store.ts
|
||||
├── types/
|
||||
│ └── auth.type.ts
|
||||
├── auth-by-email.business.tsx
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
**Плохо**
|
||||
```text
|
||||
business/
|
||||
└── authByEmail/
|
||||
├── LoginForm.tsx
|
||||
├── useAuth.ts
|
||||
├── authStore.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
## Булевы значения
|
||||
|
||||
- Использовать префиксы `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>;
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: Технологии и библиотеки
|
||||
scope: basics
|
||||
keywords: [стек, React, TypeScript, Next.js, Mantine, библиотека, зависимость]
|
||||
when: "Выбор библиотеки или технологии, проверка допустимости зависимости"
|
||||
---
|
||||
# Технологии и библиотеки
|
||||
|
||||
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
|
||||
|
||||
## Что используем
|
||||
|
||||
### Стек
|
||||
- `React` / `TypeScript` — основной стек для UI и приложения.
|
||||
- `Next.js` — для продуктовых сайтов.
|
||||
|
||||
### Архитектура
|
||||
- `SLM Design (Scoped Layered Module Design)` — модульная архитектура: слои, модули, направление зависимостей. Подробнее в разделе [Архитектура](/basics/architecture).
|
||||
|
||||
### UI компоненты
|
||||
- `Mantine UI` — базовые UI-компоненты.
|
||||
|
||||
### Работа с данными (API)
|
||||
- `@gromlab/api-codegen` — генерация API‑клиентов и типов.
|
||||
- `SWR` — получение, кеширование, ревалидация, дедубликация.
|
||||
- `SWR (useSWRSubscription)` — сокеты, реалтайм подписки.
|
||||
|
||||
### Store
|
||||
- `Zustand` — глобальное состояние.
|
||||
|
||||
### Локализация
|
||||
- `i18next (i18n)` — локализация всех пользовательских текстов.
|
||||
|
||||
### Тестирование
|
||||
- `Vitest` — тестирование.
|
||||
|
||||
### Стили
|
||||
- `PostCSS Modules` — изоляция стилей.
|
||||
- `Mobile First` — подход к адаптивной верстке.
|
||||
- `clsx` — конкатенация CSS‑классов.
|
||||
|
||||
### Генерация
|
||||
- `@gromlab/create` — шаблонизатор для создания слоёв и других файлов из шаблонов.
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
title: Типизация
|
||||
scope: basics
|
||||
keywords: [type, interface, generic, any, unknown, enum, типизация, пропсы]
|
||||
when: "Типизация кода: выбор type vs interface, работа с generic, запрет any"
|
||||
---
|
||||
# Типизация
|
||||
|
||||
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`.
|
||||
|
||||
## Общие правила
|
||||
|
||||
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
|
||||
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
||||
- Избегать `any` и `unknown` без необходимости.
|
||||
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
||||
|
||||
## Функции
|
||||
|
||||
- Для публичных функций указывать возвращаемый тип.
|
||||
- Не полагаться на неявный вывод для важных 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;
|
||||
```
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Добавить API-запрос
|
||||
---
|
||||
|
||||
# Добавить API-запрос
|
||||
|
||||
Инструкция по добавлению запроса к серверу: создание клиента, хука, обработка ответа.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/api.md — правила API-слоя: клиенты, эндпоинты, обработка ошибок
|
||||
- basics/typing.md — типизация запросов и ответов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи подход:
|
||||
- Клиентские данные → SWR / хук
|
||||
- Серверные данные → серверный компонент (RSC)
|
||||
|
||||
2. Опиши типы запроса и ответа.
|
||||
|
||||
3. Создай или расширь API-клиент (→ applied/api.md).
|
||||
|
||||
4. Создай хук для использования в компоненте (→ triggers/develop/create-hook.md).
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-hook.md — хук для запроса
|
||||
- triggers/develop/create-component.md — компонент, использующий данные
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Типы запроса и ответа описаны
|
||||
- [ ] Хук для использования в компоненте создан
|
||||
- [ ] Обработка ошибок реализована
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: Добавить зависимость
|
||||
---
|
||||
|
||||
# Добавить зависимость
|
||||
|
||||
Инструкция по добавлению новой npm-зависимости в проект. Проверь допустимость перед установкой.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- basics/tech-stack.md — разрешённый стек, допустимые библиотеки
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Проверь, что библиотека не дублирует уже используемую (→ basics/tech-stack.md).
|
||||
|
||||
2. Проверь, что библиотека входит в разрешённый список или обоснуй необходимость.
|
||||
|
||||
3. Установи как `dependency` или `devDependency` в зависимости от назначения.
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Библиотека не дублирует уже используемую
|
||||
- [ ] Библиотека входит в разрешённый список (→ basics/tech-stack.md)
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
title: Подключить шрифт
|
||||
---
|
||||
|
||||
# Подключить шрифт
|
||||
|
||||
Инструкция по подключению и настройке шрифта в проекте.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/fonts.md — правила подключения шрифтов: форматы, загрузка, CSS-переменные
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Подготовь файлы шрифта (woff2).
|
||||
|
||||
2. Подключи шрифт по правилам (→ applied/fonts.md).
|
||||
|
||||
3. Зарегистрируй CSS-переменную для шрифта.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/style-component.md — использование шрифта в стилях
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Файл шрифта в формате woff2
|
||||
- [ ] CSS-переменная для шрифта зарегистрирована
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
title: Добавить иконку
|
||||
---
|
||||
|
||||
# Добавить иконку
|
||||
|
||||
Инструкция по добавлению SVG-иконки в проект через спрайт-систему.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/svg-sprites.md — правила SVG-спрайтов: структура, именование, использование
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Подготовь SVG-файл: убери лишние атрибуты, оптимизируй.
|
||||
|
||||
2. Добавь SVG в спрайт по правилам (→ applied/svg-sprites.md).
|
||||
|
||||
3. Используй иконку в компоненте через компонент-обёртку.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — если нужен компонент-обёртка для иконки
|
||||
- triggers/develop/style-component.md — стилизация иконки (размер, цвет)
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] SVG оптимизирован — убраны лишние атрибуты
|
||||
- [ ] Иконка добавлена в спрайт по правилам (→ applied/svg-sprites.md)
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
title: Добавить изображение
|
||||
---
|
||||
|
||||
# Добавить изображение
|
||||
|
||||
Инструкция по добавлению и использованию растровых изображений в проекте.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/images-sprites.md — правила работы с изображениями: оптимизация, форматы, подключение
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи тип изображения:
|
||||
- Статическое (логотип, декор) → `public/`
|
||||
- Динамическое (контентное) → URL из API
|
||||
|
||||
2. Оптимизируй изображение (формат, размер, сжатие).
|
||||
|
||||
3. Подключи в компоненте по правилам (→ applied/images-sprites.md).
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — если нужен компонент-обёртка для изображения
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Изображение оптимизировано (формат, размер, сжатие)
|
||||
- [ ] Подключено по правилам (→ applied/images-sprites.md)
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
title: Добавить перевод
|
||||
---
|
||||
|
||||
# Добавить перевод
|
||||
|
||||
Инструкция по добавлению локализации: создание ключей перевода и подключение в компоненте.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/localization.md — правила локализации: namespace, ключи, форматирование
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи namespace для переводов (→ applied/localization.md).
|
||||
|
||||
2. Добавь ключи перевода в файлы локализации.
|
||||
|
||||
3. Подключи переводы в компоненте (→ applied/localization.md).
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — если компонент ещё не создан
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Ключи перевода добавлены в файлы локализации
|
||||
- [ ] Namespace определён (→ applied/localization.md)
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
title: Добавить серверные данные
|
||||
---
|
||||
|
||||
# Добавить серверные данные
|
||||
|
||||
Инструкция по получению данных в серверных компонентах (RSC) Next.js.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/page-level.md — серверные компоненты в App Router
|
||||
- applied/api.md — API-клиенты
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи где получать данные:
|
||||
- В `page.tsx` / `layout.tsx` → серверный fetch
|
||||
- В клиентском компоненте → SWR (→ triggers/develop/add-api-request.md)
|
||||
|
||||
2. Создай или расширь серверный API-клиент.
|
||||
|
||||
3. Получи данные в серверном компоненте и передай через пропсы.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/add-api-request.md — клиентские запросы (SWR)
|
||||
- triggers/develop/create-page.md — серверный fetch в page.tsx
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Определён тип: серверный fetch или клиентский SWR
|
||||
- [ ] Типы запроса и ответа описаны
|
||||
- [ ] Данные передаются через пропсы, не через глобальное состояние
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
title: Добавить видео
|
||||
---
|
||||
|
||||
# Добавить видео
|
||||
|
||||
Инструкция по встраиванию видео в проект.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/video.md — правила работы с видео: форматы, плеер, оптимизация
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи тип видео:
|
||||
- Локальное → `public/`
|
||||
- Внешнее (YouTube, Vimeo) → embed
|
||||
|
||||
2. Подключи видео по правилам (→ applied/video.md).
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — если нужен компонент-обёртка для видео
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Видео подключено по правилам (→ applied/video.md)
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
title: Подключить стор к компоненту
|
||||
---
|
||||
|
||||
# Подключить стор к компоненту
|
||||
|
||||
Инструкция по подключению стора к React-компоненту.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/stores.md — правила сторов: подписка, селекторы
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи нужен ли стор:
|
||||
- Локальное состояние → `useState` / `useReducer`
|
||||
- Глобальное состояние → стор
|
||||
|
||||
2. Если стор не существует — создай его (→ triggers/develop/create-store.md).
|
||||
|
||||
3. Подключи стор в компоненте через селектор (→ applied/stores.md).
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-store.md — создание нового стора
|
||||
- triggers/develop/create-hook.md — хук-обёртка над стором
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Используется селектор, а не подписка на весь стор
|
||||
- [ ] Выбор локальное/глобальное состояние обоснован
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: Создать компонент
|
||||
---
|
||||
|
||||
# Создать компонент
|
||||
|
||||
Инструкция по созданию React-компонента в проекте. Определи слой, сгенерируй из шаблона, реализуй по правилам.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/components.md — правила компонентов: структура файлов, пропсы, документирование
|
||||
- basics/naming.md — именование файла и экспортов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи слой компонента по его назначению (→ basics/architecture.md):
|
||||
- `ui/` — переиспользуемый UI без бизнес-логики
|
||||
- `business/` — бизнес-домен с логикой и UI
|
||||
- `widgets/` — составной блок, не привязанный к домену
|
||||
- `screens/{name}/parts/` — локальный блок одной страницы
|
||||
|
||||
2. Сгенерируй модуль из шаблона (→ triggers/develop/generate-module.md).
|
||||
|
||||
3. Реализуй компонент по правилам (→ applied/components.md).
|
||||
|
||||
4. Если нужны стили — см. triggers/develop/style-component.md.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/style-component.md — стилизация компонента
|
||||
- triggers/develop/add-icon.md — добавление иконки в компонент
|
||||
- triggers/develop/generate-module.md — генерация из шаблона
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Компонент создан из шаблона, не вручную
|
||||
- [ ] Файл и экспорт именованы по конвенции (→ basics/naming.md)
|
||||
- [ ] Пропсы типизированы (→ basics/typing.md)
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: Создать сущность
|
||||
---
|
||||
|
||||
# Создать сущность
|
||||
|
||||
Инструкция по созданию бизнес-модуля на слое `business/`. Сущность — бизнес-домен с UI-представлением и моделью данных.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- basics/architecture.md — слои и зависимости
|
||||
- applied/components.md — правила компонентов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Сгенерируй модуль из шаблона `business` (→ triggers/develop/generate-module.md).
|
||||
|
||||
2. Определи модель данных — типы в `types/`.
|
||||
|
||||
3. Реализуй UI-компонент сущности.
|
||||
|
||||
4. Настрой публичный API — экспорт через `index.ts`.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — UI-компонент сущности
|
||||
- triggers/develop/create-store.md — стор для сущности
|
||||
- triggers/develop/generate-module.md — генерация из шаблона
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Модуль создан из шаблона `business`
|
||||
- [ ] Модель данных определена — типы в `types/`
|
||||
- [ ] Публичный API настроен — экспорт через `index.ts`
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: Создать фичу
|
||||
---
|
||||
|
||||
# Создать фичу
|
||||
|
||||
Инструкция по созданию бизнес-модуля на слое `business/`. Фича — самодостаточный блок с бизнес-логикой и UI.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- basics/architecture.md — слои и зависимости
|
||||
- applied/components.md — правила компонентов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Сгенерируй модуль из шаблона `business` (→ triggers/develop/generate-module.md).
|
||||
|
||||
2. Реализуй компонент фичи (→ applied/components.md).
|
||||
|
||||
3. Если нужен стор — создай в `stores/` (→ triggers/develop/create-store.md).
|
||||
|
||||
4. Если нужны хуки — создай в `hooks/` (→ triggers/develop/create-hook.md).
|
||||
|
||||
5. Настрой публичный API — экспорт через `index.ts`.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — компонент внутри фичи
|
||||
- triggers/develop/create-store.md — стор для фичи
|
||||
- triggers/develop/create-hook.md — хук для фичи
|
||||
- triggers/develop/generate-module.md — генерация из шаблона
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Модуль создан из шаблона `business`
|
||||
- [ ] Публичный API настроен — экспорт через `index.ts`
|
||||
- [ ] Cross-domain зависимости реализованы через фабрику (→ basics/architecture.md)
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
title: Создать хук
|
||||
---
|
||||
|
||||
# Создать хук
|
||||
|
||||
Инструкция по созданию кастомного React-хука. Определи где он живёт, реализуй по правилам.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/hooks.md — правила хуков
|
||||
- basics/naming.md — именование (префикс `use`)
|
||||
- basics/typing.md — типизация параметров и возврата
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи область хука:
|
||||
- Утилитарный (не привязан к бизнес-логике) → `shared/hooks/`
|
||||
- Привязан к фиче/сущности → `model/` внутри модуля
|
||||
|
||||
2. Создай файл с именем `use-{name}.ts`.
|
||||
|
||||
3. Реализуй хук по правилам (→ applied/hooks.md).
|
||||
|
||||
4. Экспортируй через публичный API модуля.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — если хук используется в новом компоненте
|
||||
- triggers/develop/connect-store.md — если хук подключает стор
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Имя начинается с `use` (→ basics/naming.md)
|
||||
- [ ] Параметры и возвращаемое значение типизированы
|
||||
- [ ] Хук экспортирован через публичный API модуля
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: Создать layout
|
||||
---
|
||||
|
||||
# Создать layout
|
||||
|
||||
Инструкция по созданию layout.tsx в Next.js App Router.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/page-level.md — правила layout.tsx: провайдеры, metadata, вёрстка
|
||||
- applied/project-structure.md — структура `src/app/`
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи уровень layout:
|
||||
- Корневой (`src/app/layout.tsx`) — провайдеры, глобальные стили, metadata
|
||||
- Вложенный (`src/app/{route}/layout.tsx`) — layout для группы страниц
|
||||
|
||||
2. Создай `layout.tsx` в нужном маршруте.
|
||||
|
||||
3. Вёрстку layout-обёрток вынеси в слой `layouts/` (→ applied/page-level.md).
|
||||
|
||||
4. Layout содержит только провайдеры и вызов layout-компонента — не вёрстку.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-page.md — страницы внутри layout
|
||||
- triggers/develop/create-component.md — layout-компонент в `layouts/`
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Вёрстка вынесена в layout-компонент в `layouts/`
|
||||
- [ ] layout.tsx содержит только провайдеры и вызов layout-компонента
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
title: Создать страницу
|
||||
---
|
||||
|
||||
# Создать страницу
|
||||
|
||||
Инструкция по добавлению нового route в Next.js проект. Страница — это экран + page.tsx.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/page-level.md — правила файлов роутинга: page.tsx, layout.tsx, metadata
|
||||
- applied/project-structure.md — где располагаются файлы
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Сгенерируй экран из шаблона `screen` в `src/screens/` (→ triggers/develop/generate-module.md).
|
||||
|
||||
2. Заполни экран логикой и стилями.
|
||||
|
||||
3. Создай `page.tsx` в нужном маршруте `src/app/`.
|
||||
- page.tsx тонкий: только `metadata` и рендер экрана
|
||||
- Никакой логики, стилей и хуков в page.tsx
|
||||
|
||||
4. Добавь `metadata` с `title` (→ applied/page-level.md).
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/generate-module.md — генерация экрана из шаблона
|
||||
- triggers/develop/create-layout.md — если нужен новый layout для маршрута
|
||||
- triggers/develop/create-component.md — компоненты внутри экрана
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Экран создан из шаблона `screen` в `src/screens/`
|
||||
- [ ] page.tsx тонкий — только metadata и рендер экрана
|
||||
- [ ] metadata содержит title и description
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
title: Создать проект
|
||||
scope: applied
|
||||
keywords: [создать проект, новый проект, tiged, шаблон проекта, init]
|
||||
when: "Создание нового Next.js проекта из шаблона"
|
||||
---
|
||||
|
||||
# Создать проект
|
||||
|
||||
Инструкция по созданию нового Next.js проекта из готового шаблона. Проект готов к разработке сразу после установки зависимостей.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- basics/getting-started.md — знакомство со стеком и особенностями проекта
|
||||
- applied/project-structure.md — структура папок и файлов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Создай проект из шаблона:
|
||||
|
||||
```bash
|
||||
npx tiged git@gromlab.ru:templates/nextjs.git my-app
|
||||
cd my-app
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Ознакомься со структурой проекта (→ applied/project-structure.md).
|
||||
|
||||
3. Настрой VS Code (→ triggers/develop/setup-vscode.md).
|
||||
|
||||
## Что входит в шаблон
|
||||
|
||||
- Next.js + TypeScript (App Router)
|
||||
- Mantine UI + PostCSS Modules
|
||||
- Biome (линтинг и форматирование)
|
||||
- Zustand, SWR
|
||||
- Структура SLM Design (`screens/`, `layouts/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`)
|
||||
- Шаблоны генерации (`.templates/`)
|
||||
- Конфигурация VS Code (`.vscode/`)
|
||||
- CSS-токены (цвета, отступы, радиусы, медиа)
|
||||
- Open Graph метаданные
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/setup-vscode.md — настройка редактора
|
||||
- triggers/develop/create-page.md — добавление первой страницы
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Проект создан из шаблона через `npx tiged`
|
||||
- [ ] Зависимости установлены
|
||||
- [ ] VS Code настроен (→ triggers/develop/setup-vscode.md)
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
title: Создать стор
|
||||
---
|
||||
|
||||
# Создать стор
|
||||
|
||||
Инструкция по созданию стора для управления состоянием. Определи область, сгенерируй из шаблона.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/stores.md — правила сторов
|
||||
- basics/naming.md — именование
|
||||
- basics/typing.md — типизация состояния и экшенов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи область стора:
|
||||
- Глобальный → `shared/model/`
|
||||
- Привязан к фиче/сущности → `model/` внутри модуля
|
||||
|
||||
2. Сгенерируй из шаблона `store` (→ triggers/develop/generate-module.md).
|
||||
|
||||
3. Реализуй стор по правилам (→ applied/stores.md).
|
||||
|
||||
4. Экспортируй через публичный API модуля.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/connect-store.md — подключение стора к компоненту
|
||||
- triggers/develop/generate-module.md — генерация из шаблона
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Стор создан из шаблона `store`
|
||||
- [ ] Состояние и экшены типизированы
|
||||
- [ ] Стор экспортирован через публичный API модуля
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
title: Создать виджет
|
||||
---
|
||||
|
||||
# Создать виджет
|
||||
|
||||
Инструкция по созданию модуля на слое `widgets/`. Виджет — композиция фичей и сущностей.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- basics/architecture.md — слои и зависимости
|
||||
- applied/components.md — правила компонентов
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Сгенерируй модуль из шаблона `widget` (→ triggers/develop/generate-module.md).
|
||||
|
||||
2. Скомпонуй виджет из существующих фичей и сущностей.
|
||||
|
||||
3. Настрой публичный API — экспорт через `index.ts`.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-feature.md — если нужна новая фича для виджета
|
||||
- triggers/develop/create-component.md — UI-компоненты внутри виджета
|
||||
- triggers/develop/generate-module.md — генерация из шаблона
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Виджет создан из шаблона `widget`
|
||||
- [ ] Композиция из существующих фичей/сущностей, не дублирует логику
|
||||
- [ ] Публичный API настроен — экспорт через `index.ts`
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
title: Сгенерировать модуль из шаблона
|
||||
---
|
||||
|
||||
# Сгенерировать модуль из шаблона
|
||||
|
||||
Инструкция по генерации модуля из шаблонов `.templates/`. Ручное создание файловой структуры запрещено.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/templates-generation.md — шаблоны, синтаксис, инструменты генерации
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи тип модуля и шаблон (→ applied/templates-generation.md):
|
||||
- Компонент → `component`
|
||||
- Бизнес-модуль → `business`
|
||||
- Виджет → `widget`
|
||||
- Layout → `layout`
|
||||
- Экран → `screen`
|
||||
- Стор → `store`
|
||||
|
||||
2. Запусти генерацию (→ applied/templates-generation.md).
|
||||
|
||||
3. Если подходящего шаблона нет — сначала создай шаблон, затем генерируй.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — после генерации компонента
|
||||
- triggers/develop/create-feature.md — после генерации бизнес-модуля
|
||||
- triggers/develop/create-store.md — после генерации стора
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Модуль создан из шаблона, не вручную
|
||||
- [ ] Выбран правильный шаблон для типа модуля (→ applied/templates-generation.md)
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: Настроить VS Code
|
||||
---
|
||||
|
||||
# Настроить VS Code
|
||||
|
||||
Инструкция по настройке VS Code для работы с проектом.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/vscode.md — настройки, расширения, сниппеты
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Установи рекомендованные расширения (→ applied/vscode.md).
|
||||
|
||||
2. Проверь настройки `.vscode/settings.json`.
|
||||
|
||||
3. Настрой сниппеты при необходимости.
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Рекомендованные расширения установлены
|
||||
- [ ] Настройки `.vscode/settings.json` проверены
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Стилизовать компонент
|
||||
---
|
||||
|
||||
# Стилизовать компонент
|
||||
|
||||
Инструкция по выбору подхода к стилизации и написанию стилей для компонента.
|
||||
|
||||
## Прочитай перед началом
|
||||
|
||||
- applied/styles.md — правила CSS: PostCSS Modules, токены, медиа-запросы
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Определи подход (→ applied/styles.md):
|
||||
- Mantine-компонент → используй пропсы Mantine, не пиши CSS
|
||||
- CSS-токены достаточно → используй токены
|
||||
- Нужна кастомная стилизация → PostCSS Modules
|
||||
|
||||
2. Создай файл стилей `{component-name}.module.css` рядом с компонентом.
|
||||
|
||||
3. Напиши стили по правилам (→ applied/styles.md).
|
||||
|
||||
4. Подключи стили в компоненте через `cl()`.
|
||||
|
||||
## Смежные триггеры
|
||||
|
||||
- triggers/develop/create-component.md — если компонент ещё не создан
|
||||
- triggers/develop/add-icon.md — если нужна иконка в компоненте
|
||||
|
||||
## Проверь себя
|
||||
|
||||
- [ ] Приоритет стилизации соблюдён: Mantine → токены → PostCSS Modules
|
||||
- [ ] Нет инлайн-стилей и магических значений
|
||||
- [ ] Файл стилей именован `{component-name}.module.css`
|
||||
@@ -96,7 +96,7 @@ export const App = () => {
|
||||
<span className={styles.footerText}>@gromlab/svg-sprites</span>
|
||||
<a
|
||||
className={styles.footerLink}
|
||||
href="https://gromlab.ru/gromov/svg-sprites"
|
||||
href="https://github.com/gromov-sergei/svg-sprites"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# AI skills
|
||||
|
||||
Исходники скила `svg-sprites` находятся в `skills/svg-sprites/`. Готовый самодостаточный артефакт записывается в `skills/artifacts/svg-sprites/` и коммитится в Git.
|
||||
Исходники скилов `svg-sprites` и `svg-sprites-ru` находятся в `skills/svg-sprites/`. Готовые самодостаточные артефакты записываются в `skills/artifacts/svg-sprites/` и `skills/artifacts/svg-sprites-ru/` и коммитятся в Git.
|
||||
|
||||
Основные `README.md` и `docs/ru/*.md` являются источником истины. Сборка копирует их в `references/` готового скила, поэтому вручную редактировать файлы внутри `skills/artifacts/` нельзя.
|
||||
Основные `README.md`, `README_RU.md` и файлы `docs/{en,ru}/*.md` являются источником истины. Сборка копирует их в `references/` готового скила, поэтому вручную редактировать файлы внутри `skills/artifacts/` нельзя.
|
||||
|
||||
```bash
|
||||
npm run build:skill
|
||||
npm run check:skill
|
||||
```
|
||||
|
||||
`build:skill` обновляет артефакт, а `check:skill` без изменения файлов проверяет его содержимое и синхронность с документацией.
|
||||
`build:skill` обновляет оба артефакта, а `check:skill` без изменения файлов проверяет их содержимое и синхронность с документацией. Версия без суффикса использует английский язык, версия с суффиксом `-ru` — русский.
|
||||
|
||||
64
skills/artifacts/svg-sprites-ru/SKILL.md
Normal file
64
skills/artifacts/svg-sprites-ru/SKILL.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: svg-sprites-ru
|
||||
description: "Используй при настройке, генерации, миграции или диагностике SVG-спрайтов через @gromlab/svg-sprites. Триггеры: SVG sprite, SVG-спрайт, svg-sprites, svg-sprite.config.ts, svg-sprites.config.ts, defineReactSpriteConfig, defineNextSpriteConfig, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer, icon=\"...\", --icon-color-N, generated-компонент или иконка не появилась в превью и автодополнении. НЕ используй для favicon, растровых изображений, icon fonts, выбора набора иконок или inline SVG без спрайтов."
|
||||
---
|
||||
|
||||
<!-- Generated from skills/svg-sprites/src/SKILL_RU.md. Do not edit manually. -->
|
||||
|
||||
# SVG Sprites
|
||||
|
||||
## Назначение
|
||||
|
||||
Используй этот скил для работы с `@gromlab/svg-sprites`: первичной настройки, добавления и переиспользования иконок, генерации React-компонентов, подключения `SpriteViewer`, миграции legacy-конфигурации и диагностики ошибок.
|
||||
|
||||
Не навязывай проекту конкретную архитектуру каталогов. Сначала изучи существующие `package.json`, конфигурацию спрайта, используемый фреймворк, роутер и сборщик.
|
||||
|
||||
## Рабочий алгоритм
|
||||
|
||||
1. Определи существующий режим и не смешивай его API с другим режимом.
|
||||
2. Для React выбери `react@vite` или `react@webpack` и открой соответствующий reference.
|
||||
3. Для Next.js определи App Router или Pages Router, затем Turbopack или Webpack, и открой соответствующий reference.
|
||||
4. Для существующего `svg-sprites.config.ts` с несколькими спрайтами используй legacy-документацию. Не мигрируй такой проект без явного запроса.
|
||||
5. Изучи локальные scripts и добавляй генерацию перед `dev`, `build` и `typecheck`, если generated-файлы не хранятся в Git.
|
||||
6. После изменения конфигурации или SVG запусти генерацию, затем доступную проверку типов или сборку проекта.
|
||||
|
||||
## Правила React и Next.js
|
||||
|
||||
- Используй локальный `svg-sprite.config.ts` и подходящий config helper: `defineReactSpriteConfig` или `defineNextSpriteConfig`.
|
||||
- Не редактируй `generated/`, `index.ts`, `manifest.ts` и созданный генератором `.gitignore` вручную.
|
||||
- Имена исходных SVG становятся допустимыми значениями prop `icon`; используй generated-компонент и его публичные типы вместо deep imports.
|
||||
- Объединяй локальную папку и `inputFiles`, когда общая иконка нужна нескольким спрайтам. Не создавай копии одного SVG без необходимости.
|
||||
- В Next.js generated-компонент можно использовать в Server Components, SSR и SSG. Не добавляй `'use client'` только ради иконки.
|
||||
- Спрайт должен оставаться внешним asset сборщика: не переносить SVG path-данные в JavaScript и не класть generated-файл вручную в `public`.
|
||||
|
||||
## Цвета и трансформации
|
||||
|
||||
- По умолчанию генератор удаляет `width` и `height`, заменяет поддерживаемые `fill` и `stroke` на CSS-переменные и добавляет transitions.
|
||||
- Для монохромной иконки сначала управляй `color`; для многоцветной используй `--icon-color-N`.
|
||||
- Не обещай автоматическую замену цветов внутри внешних stylesheets, gradients, patterns, filters и значений `url(#...)` без проверки результата.
|
||||
- CSS-переменные страницы работают при `<svg><use>`, но не проникают внутрь `<img>` и `background-image`.
|
||||
|
||||
## Превью
|
||||
|
||||
Для React и Next.js подключай `<SpriteViewer>` отдельной debug-страницей приложения. Передай ему manifests или lazy loaders спрайтов. Viewer поддерживает поиск, светлую и тёмную темы, настройку цветов и примеры React, SVG, IMG и CSS.
|
||||
|
||||
`SpriteViewer` является клиентским debug-инструментом и импортируется из `@gromlab/svg-sprites/react`; production-компоненты иконок от него не зависят.
|
||||
|
||||
## Диагностика
|
||||
|
||||
- Если имя иконки отсутствует в автодополнении, проверь входную папку и `inputFiles`, затем перезапусти генерацию.
|
||||
- Если два файла имеют одинаковое имя иконки, устрани конфликт вместо выбора одного файла неявно.
|
||||
- Если генератор отказывается перезаписывать файл, не удаляй защитный marker и не обходи writer: перенеси пользовательский файл или выбери другой каталог спрайта.
|
||||
- Если asset не загружается, сначала проверь соответствие CLI mode реальному сборщику проекта и обработку generated SVG его asset pipeline.
|
||||
- Если проект использует старый API, сверь установленную версию пакета и legacy reference перед изменениями.
|
||||
|
||||
## References
|
||||
|
||||
- [Основная документация и API](./references/README_RU.md)
|
||||
- [React + Vite](./references/docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](./references/docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](./references/docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](./references/docs/ru/next-pages.md)
|
||||
- [Legacy mode](./references/docs/ru/legacy.md)
|
||||
- [Миграция с 0.1.x](./references/docs/ru/migration-1.md)
|
||||
- [Программный API](./references/docs/ru/programmatic-api.md)
|
||||
398
skills/artifacts/svg-sprites-ru/references/README.md
Normal file
398
skills/artifacts/svg-sprites-ru/references/README.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# @gromlab/svg-sprites
|
||||
|
||||
🇬🇧 English | [🇷🇺 Русский](README_RU.md)
|
||||
|
||||
 
|
||||
|
||||
A CLI for generating SVG sprites and typed icon components for React and Next.js.
|
||||
|
||||

|
||||
|
||||
## Navigation
|
||||
|
||||
- [Features](#features)
|
||||
- [Support matrix](#support-matrix)
|
||||
- [Requirements](#requirements)
|
||||
- [Quick start](#quick-start)
|
||||
- [React + Vite](docs/en/react-vite.md)
|
||||
- [React + Webpack 5](docs/en/react-webpack.md)
|
||||
- [Next.js App Router](docs/en/next-app.md)
|
||||
- [Next.js Pages Router](docs/en/next-pages.md)
|
||||
- [Configuration](#configuration)
|
||||
- [React](#react)
|
||||
- [Next.js](#nextjs)
|
||||
- [Multiple sprites](#multiple-sprites)
|
||||
- [TypeScript](#typescript)
|
||||
- [Sprite formats](#sprite-formats)
|
||||
- [Rendering methods](#rendering-methods)
|
||||
- [Transformations](#transformations)
|
||||
- [Icon color management](#icon-color-management)
|
||||
- [Caching](#caching)
|
||||
- [SpriteViewer](#spriteviewer)
|
||||
- [Migrating from 0.1.x](docs/en/migration-1.md)
|
||||
- [Documentation](#documentation)
|
||||
|
||||
## Features
|
||||
|
||||
- **AI-agent friendly** - the repository includes a ready-to-use skill with up-to-date documentation for configuring, migrating, and troubleshooting `@gromlab/svg-sprites`.
|
||||
- **TypeScript-friendly** - typed React components, union types, and runtime lists of available icons.
|
||||
- **Clean generation** - generated files are automatically excluded from Git, the sprite does not need to be placed in `public` manually, and the generator updates only files it owns.
|
||||
- **Shared icons without copying** - SVGs from the local folder and `inputFiles` are merged into a single sprite; one file can be used in multiple sprites.
|
||||
- **Built-in interactive preview** - `<SpriteViewer>` is integrated as an application page and displays the provided React and Next.js sprites with search, color controls, and usage examples.
|
||||
- **Configurable SVG transformations** - remove `width` and `height` while preserving `viewBox`, replace source colors with CSS variables, and add transitions for `fill` and `stroke`.
|
||||
- **Separate cacheable SVG asset** - SVG path data does not end up in JavaScript chunks, and the bundler emits a file with a content hash.
|
||||
- **Multiple sprites** - independent React and Next.js modules with their own components, types, and SVG assets.
|
||||
- **Server-first Next.js** - generated components work in Server Components, SSR, and SSG without the `'use client'` directive.
|
||||
- **Formats for different use cases** - React and Next.js use `stack`; legacy mode also supports `symbol` for existing integrations.
|
||||
|
||||
## Support matrix
|
||||
|
||||
| Environment | API mode key | Status |
|
||||
|---|---|---|
|
||||
| React + Vite | `react@vite` | Ready |
|
||||
| React + Webpack 5 | `react@webpack` | Ready |
|
||||
| Next.js 16.2+ App Router + Turbopack | `next@app/turbopack` | Ready |
|
||||
| Next.js 13.4+ App Router + Webpack 5 | `next@app/webpack` | Ready |
|
||||
| Next.js 16.2+ Pages Router + Turbopack | `next@pages/turbopack` | Ready |
|
||||
| Next.js 12.2+ Pages Router + Webpack 5 | `next@pages/webpack` | Ready |
|
||||
| Vue | - | Coming soon |
|
||||
| Standalone | - | Coming soon |
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 18 or newer;
|
||||
- the package is distributed as ESM only and is loaded via `import`;
|
||||
- React 18 or 19 is required only for generated components and the `@gromlab/svg-sprites/react` entry point;
|
||||
- for subpath export typings, use TypeScript 5+ with `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
|
||||
|
||||
## Quick start
|
||||
|
||||
For a quick start, follow the guide for your stack:
|
||||
|
||||
- [React + Vite](docs/en/react-vite.md)
|
||||
- [React + Webpack 5](docs/en/react-webpack.md)
|
||||
- [Next.js App Router](docs/en/next-app.md)
|
||||
- [Next.js Pages Router](docs/en/next-pages.md)
|
||||
|
||||
## Configuration
|
||||
|
||||
### React
|
||||
|
||||
```ts
|
||||
import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
inputFolder: './icons',
|
||||
inputFiles: [
|
||||
'../../shared/icons/check.svg',
|
||||
],
|
||||
transform: {
|
||||
removeSize: true,
|
||||
replaceColors: true,
|
||||
addTransition: true,
|
||||
},
|
||||
generatedNotice: true,
|
||||
})
|
||||
```
|
||||
|
||||
| Option | Type | Default | Purpose |
|
||||
|---|---|---|---|
|
||||
| `name` | `string` | Folder name | Name of the sprite, component, and public types |
|
||||
| `description` | `string` | None | Description for types and the debug manifest |
|
||||
| `inputFolder` | `string` | `./icons` | Folder containing source SVGs, relative to the config |
|
||||
| `inputFiles` | `string[]` | `[]` | Additional SVG files, relative to the config |
|
||||
| `transform` | `TransformOptions` | All enabled | [Transformation settings](#transformations) for source SVGs |
|
||||
| `generatedNotice` | `boolean` | `true` | Full or short warning in generated files |
|
||||
|
||||
`inputFolder` and `inputFiles` are merged into a single sprite, so one SVG file can be used in multiple sprites without copying. If the implicit `./icons` folder does not exist but `inputFiles` is populated, generation continues using only the list. An explicitly specified missing folder is an error. Duplicate paths are deduplicated, while different files with the same icon name are treated as an error.
|
||||
|
||||
`name` is stored in kebab-case and must start with a Latin letter. The React and Next.js presets produce the `stack` format.
|
||||
|
||||
### Next.js
|
||||
|
||||
Next.js uses the same `svg-sprite.config.ts` and set of options. For type checking, you can use a dedicated helper:
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'File manager icons',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
The router and bundler are selected through the mode key, so switching between Turbopack and Webpack is always explicitly reflected in the generation command.
|
||||
|
||||
## Multiple sprites
|
||||
|
||||
An application can contain several independent sprites for different scopes:
|
||||
|
||||
**Problem:** one global sprite loads icons that the current screen does not need.
|
||||
|
||||
**Solution:** keep shared icons globally, and place icon sets for pages and large components in separate sprites that load alongside them.
|
||||
|
||||
```text
|
||||
global -> GlobalIcon -> shared application icons
|
||||
analytics-page -> AnalyticsPageIcon -> icons for a specific page
|
||||
file-manager -> FileManagerIcon -> icons for a large component
|
||||
```
|
||||
|
||||
- **Global sprite** contains a small set of shared icons used in different parts of the application: navigation, states, and basic actions.
|
||||
- **Page sprite** loads with a specific section and does not increase the shared sprite with icons that are not needed anywhere else.
|
||||
- **Large component sprite** encapsulates the icon set of a complex UI module, such as a file manager or editor.
|
||||
|
||||
Each group gets:
|
||||
|
||||
- its own SVG asset;
|
||||
- its own typed component;
|
||||
- a separate list of icon names;
|
||||
- a separate debug manifest;
|
||||
- an independent cache lifecycle.
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
The main feature of the TypeScript API is icon name autocomplete directly in the `icon` prop:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="folder" />
|
||||
// ^ the editor suggests every icon in the sprite
|
||||
```
|
||||
|
||||
SVG file names become valid `icon` values. A typo or unknown name immediately becomes a TypeScript error:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="unknown" /> // TypeScript error
|
||||
```
|
||||
|
||||
For programmatic access, the generated module exports a readonly array of all icons available in a specific sprite:
|
||||
|
||||
```ts
|
||||
import { fileManagerIconNames } from './svg-sprite'
|
||||
|
||||
// readonly ['check', 'folder', ...]
|
||||
```
|
||||
|
||||
You can use this list in custom catalogs, select components, tests, and other runtime scenarios. The `FileManagerIconName` union type is also derived from it.
|
||||
|
||||
File names containing spaces and other characters unsafe for SVG IDs remain part of the public TypeScript API. For the internal `<symbol id>`, the generator creates a stable hash ID.
|
||||
|
||||
```text
|
||||
folder open.svg -> icon="folder open" -> id="icon-<stable-hash>"
|
||||
```
|
||||
|
||||
For such names, use the generated component or the `id` from the debug manifest. The manual examples below using `#<name>` are suitable only for names that are already safe SVG IDs.
|
||||
|
||||
## Sprite formats
|
||||
|
||||
`stack` is the more modern format, so it is used by default. Icons can be rendered through `<svg><use>`, `<img>`, and CSS `background-image`.
|
||||
|
||||
`symbol` is retained for compatibility with existing integrations and supports rendering only through `<svg><use>`.
|
||||
|
||||
## Rendering methods
|
||||
|
||||
### React component - recommended
|
||||
|
||||
The generated component provides type safety and icon name autocomplete, and constructs the SVG asset URL itself.
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="check" width={24} height={24} />
|
||||
```
|
||||
|
||||
Monochrome and multicolor icons are supported through `color` and `--icon-color-N`.
|
||||
|
||||
### Manually with `<svg><use>`
|
||||
|
||||
A good low-level method that provides full control over dimensions and colors. This is exactly what the React component uses under the hood.
|
||||
|
||||
How you obtain `spriteUrl` depends on the bundler.
|
||||
|
||||
**Vite:**
|
||||
|
||||
```tsx
|
||||
import spriteUrl from './svg-sprite/generated/sprite.svg?no-inline'
|
||||
```
|
||||
|
||||
**Webpack 5:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
'./svg-sprite/generated/sprite.svg',
|
||||
import.meta.url,
|
||||
).href
|
||||
```
|
||||
|
||||
**Next.js with Webpack 5 or Turbopack:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
'./svg-sprite/generated/sprite.svg',
|
||||
import.meta.url,
|
||||
).href
|
||||
```
|
||||
|
||||
After obtaining the URL, the icon is rendered the same way:
|
||||
|
||||
```tsx
|
||||
<svg width={24} height={24}>
|
||||
<use href={`${spriteUrl}#check`} />
|
||||
</svg>
|
||||
```
|
||||
|
||||
Vite, Webpack 5, and Next.js replace the source path with the final hashed asset URL automatically.
|
||||
|
||||
### With `<img>` - less efficient
|
||||
|
||||
```tsx
|
||||
<img src={`${spriteUrl}#check`} width={24} height={24} alt="Done" />
|
||||
```
|
||||
|
||||
The SVG loads as an isolated image: its colors cannot be changed through `color` or `--icon-color-N`.
|
||||
|
||||
### With CSS `background-image` - less efficient
|
||||
|
||||
```css
|
||||
.icon {
|
||||
background: url('./svg-sprite/generated/sprite.svg#check') center / contain no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Like `<img>`, this method does not allow you to control internal SVG colors. The path is specified relative to the CSS file, and Vite/Webpack replaces it with the final hashed URL during the build.
|
||||
|
||||
### With CSS mask - less efficient
|
||||
|
||||
```css
|
||||
.icon {
|
||||
background-color: currentColor;
|
||||
mask: url('./svg-sprite/generated/sprite.svg#check') center / contain no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
A mask retains only the silhouette and colors it with a single color. The original colors, gradients, and distinctions between `fill` and `stroke` are lost.
|
||||
|
||||
## Transformations
|
||||
|
||||
All transformations are enabled by default and configured independently through `transform`.
|
||||
|
||||
| Option | Default | What it does |
|
||||
|---|---|---|
|
||||
| `removeSize` | `true` | Removes `width` and `height` from the root `<svg>` while preserving the existing `viewBox`. The icon size is then set externally. |
|
||||
| `replaceColors` | `true` | Replaces `fill` and `stroke` colors with `--icon-color-N`. For a monochrome icon, the fallback becomes `currentColor`; for a multicolor icon, the original colors are preserved. |
|
||||
| `addTransition` | `true` | Adds `style="transition:fill 0.3s,stroke 0.3s;"` directly to colored SVG elements. An existing `transition` is not overwritten. |
|
||||
|
||||
To disable a transformation, pass `false` for the corresponding option. For more details about the result of `replaceColors`, see [Icon color management](#icon-color-management).
|
||||
|
||||
## Icon color management
|
||||
|
||||
When color replacement is enabled, the generator analyzes `fill` and `stroke` and converts them to CSS custom properties.
|
||||
|
||||
### Monochrome icons
|
||||
|
||||
If one color is found, the fallback is replaced with `currentColor`:
|
||||
|
||||
```svg
|
||||
stroke="var(--icon-color-1, currentColor)"
|
||||
```
|
||||
|
||||
The color is controlled by the CSS `color` property of the outer `<svg>` or its parent.
|
||||
|
||||
### Multicolor icons
|
||||
|
||||
Each unique color gets a separate variable with the original fallback:
|
||||
|
||||
```svg
|
||||
fill="var(--icon-color-1, #798198)"
|
||||
fill="var(--icon-color-2, #ffffff)"
|
||||
fill="var(--icon-color-3, #129d9d)"
|
||||
```
|
||||
|
||||
The page can override only the required colors:
|
||||
|
||||
```css
|
||||
.icon {
|
||||
--icon-color-1: #4b5563;
|
||||
--icon-color-3: #14b8a6;
|
||||
}
|
||||
```
|
||||
|
||||
### Color limitations
|
||||
|
||||
- `none`, `transparent`, `inherit`, `unset`, and `initial` are not replaced;
|
||||
- colors in `fill`, `stroke`, and inline `style` attributes are handled most reliably;
|
||||
- CSS classes and external stylesheets inside the source SVG are not the primary transformation use case;
|
||||
- gradients, patterns, filters, and `url(#...)` values require separate verification and may be incompatible with automatic color replacement;
|
||||
- page CSS variables are available with `<svg><use>`, but are not available inside `<img>` and `background-image`.
|
||||
|
||||
## Caching
|
||||
|
||||
The Vite, Webpack, and Next.js targets emit the sprite as a separate asset with a content hash:
|
||||
|
||||
```text
|
||||
/assets/sprite-<hash>.svg
|
||||
```
|
||||
|
||||
This provides the following properties:
|
||||
|
||||
- the SVG is cached independently of JavaScript;
|
||||
- changes to React code do not alter the sprite contents;
|
||||
- icon changes produce a new hashed asset;
|
||||
- one file is used by every instance of the generated component;
|
||||
- SVG path data is absent from JavaScript chunks.
|
||||
|
||||
The Vite target prevents inlining through `?no-inline`. The Webpack 5 target uses Asset Modules through `new URL(..., import.meta.url)`.
|
||||
|
||||
## SpriteViewer
|
||||
|
||||
`SpriteViewer` is a React component for viewing generated sprites inside an application's debug route.
|
||||
|
||||
It uses separate manifests and displays:
|
||||
|
||||
- sprite groups;
|
||||
- the icon list and count;
|
||||
- search and the system light/dark theme;
|
||||
- a preview modal with the `viewBox` and color variable controls;
|
||||
- React, SVG, IMG, and CSS examples with code copying.
|
||||
|
||||
Production components do not import debug manifests. How you integrate the Viewer depends on the bundler:
|
||||
|
||||
- [React + Vite: automatic `import.meta.glob`](docs/en/react-vite.md#6-add-a-debug-page);
|
||||
- [React + Webpack 5: static `import()`](docs/en/react-webpack.md#6-add-a-debug-page);
|
||||
- [Next.js App Router](docs/en/next-app.md#5-add-spriteviewer);
|
||||
- [Next.js Pages Router](docs/en/next-pages.md#5-add-spriteviewer).
|
||||
|
||||
The Viewer is imported from the separate `@gromlab/svg-sprites/react` client entry point and is not included in production icon components.
|
||||
|
||||
### Viewer theme
|
||||
|
||||
By default, `colorTheme="auto"`: the Viewer follows `prefers-color-scheme` and responds to system theme changes. The application theme can be passed explicitly:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer sources={sources} colorTheme="dark" />
|
||||
```
|
||||
|
||||
Valid `colorTheme` values are `auto`, `light`, and `dark`. When the theme is controlled externally, the built-in switch is hidden. To keep it and update the application theme through the Viewer, pass a callback:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer
|
||||
sources={sources}
|
||||
colorTheme={appTheme}
|
||||
onColorThemeChange={setAppTheme}
|
||||
/>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [React + Vite](docs/en/react-vite.md)
|
||||
- [React + Webpack 5](docs/en/react-webpack.md)
|
||||
- [Next.js App Router](docs/en/next-app.md)
|
||||
- [Next.js Pages Router](docs/en/next-pages.md)
|
||||
- [Legacy mode](docs/en/legacy.md)
|
||||
- [Migrating from 0.1.x](docs/en/migration-1.md)
|
||||
- [Programmatic API](docs/en/programmatic-api.md)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
398
skills/artifacts/svg-sprites-ru/references/README_RU.md
Normal file
398
skills/artifacts/svg-sprites-ru/references/README_RU.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# @gromlab/svg-sprites
|
||||
|
||||
[🇬🇧 English](README.md) | 🇷🇺 Русский
|
||||
|
||||
 
|
||||
|
||||
CLI для генерации SVG-спрайтов и типизированных компонентов иконок для React и Next.js.
|
||||
|
||||

|
||||
|
||||
## Навигация
|
||||
|
||||
- [Возможности](#возможности)
|
||||
- [Таблица поддержки](#таблица-поддержки)
|
||||
- [Требования](#требования)
|
||||
- [Быстрый старт](#быстрый-старт)
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [Конфигурация](#конфигурация)
|
||||
- [React](#react)
|
||||
- [Next.js](#nextjs)
|
||||
- [Множественные спрайты](#множественные-спрайты)
|
||||
- [TypeScript](#typescript)
|
||||
- [Форматы спрайтов](#форматы-спрайтов)
|
||||
- [Способы отображения](#способы-отображения)
|
||||
- [Трансформации](#трансформации)
|
||||
- [Управление цветом иконок](#управление-цветом-иконок)
|
||||
- [Кеширование](#кеширование)
|
||||
- [SpriteViewer](#spriteviewer)
|
||||
- [Миграция с 0.1.x](docs/ru/migration-1.md)
|
||||
- [Документация](#документация)
|
||||
|
||||
## Возможности
|
||||
|
||||
- **AI-agent friendly** — репозиторий содержит готовый skill с актуальной документацией для настройки, миграции и диагностики `@gromlab/svg-sprites`.
|
||||
- **TypeScript-friendly** — типизированные React-компоненты, union-типы и runtime-списки доступных иконок.
|
||||
- **Чистая генерация** — generated-файлы автоматически исключаются из Git, спрайт не нужно вручную размещать в `public`, а генератор обновляет только принадлежащие ему файлы.
|
||||
- **Общие иконки без копирования** — SVG из локальной папки и `inputFiles` объединяются в один спрайт; один файл можно использовать в нескольких спрайтах.
|
||||
- **Встроенное интерактивное превью** — `<SpriteViewer>` подключается как страница приложения и показывает переданные React- и Next.js-спрайты с поиском, настройкой цветов и примерами использования.
|
||||
- **Настраиваемые трансформации SVG** — удаление `width` и `height` с сохранением `viewBox`, замена исходных цветов на CSS-переменные и transitions для `fill` и `stroke`.
|
||||
- **Отдельный кешируемый SVG asset** — SVG path-данные не попадают в JavaScript chunks, а сборщик выпускает файл с content hash.
|
||||
- **Множественные спрайты** — независимые React- и Next.js-модули со своими компонентами, типами и SVG assets.
|
||||
- **Server-first Next.js** — generated-компоненты работают в Server Components, SSR и SSG без директивы `'use client'`.
|
||||
- **Форматы под разные сценарии** — React и Next.js используют `stack`, legacy-режим также поддерживает `symbol` для существующих интеграций.
|
||||
|
||||
## Таблица поддержки
|
||||
|
||||
| Среда | Ключ мода API | Статус |
|
||||
|---|---|---|
|
||||
| React + Vite | `react@vite` | Готово |
|
||||
| React + Webpack 5 | `react@webpack` | Готово |
|
||||
| Next.js 16.2+ App Router + Turbopack | `next@app/turbopack` | Готово |
|
||||
| Next.js 13.4+ App Router + Webpack 5 | `next@app/webpack` | Готово |
|
||||
| Next.js 16.2+ Pages Router + Turbopack | `next@pages/turbopack` | Готово |
|
||||
| Next.js 12.2+ Pages Router + Webpack 5 | `next@pages/webpack` | Готово |
|
||||
| Vue | — | Скоро |
|
||||
| Standalone | — | Скоро |
|
||||
|
||||
## Требования
|
||||
|
||||
- Node.js 18 или новее;
|
||||
- пакет распространяется только как ESM и подключается через `import`;
|
||||
- React 18 или 19 требуется только для generated-компонентов и точки входа `@gromlab/svg-sprites/react`;
|
||||
- для типизации subpath exports используйте TypeScript 5+ с `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
Для быстрого старта воспользуйтесь инструкцией для вашего стека:
|
||||
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### React
|
||||
|
||||
```ts
|
||||
import { defineReactSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineReactSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'Иконки файлового менеджера',
|
||||
inputFolder: './icons',
|
||||
inputFiles: [
|
||||
'../../shared/icons/check.svg',
|
||||
],
|
||||
transform: {
|
||||
removeSize: true,
|
||||
replaceColors: true,
|
||||
addTransition: true,
|
||||
},
|
||||
generatedNotice: true,
|
||||
})
|
||||
```
|
||||
|
||||
| Опция | Тип | По умолчанию | Назначение |
|
||||
|---|---|---|---|
|
||||
| `name` | `string` | Имя папки | Имя спрайта, компонента и публичных типов |
|
||||
| `description` | `string` | Нет | Описание для типов и debug-манифеста |
|
||||
| `inputFolder` | `string` | `./icons` | Папка с исходными SVG относительно конфига |
|
||||
| `inputFiles` | `string[]` | `[]` | Дополнительные SVG-файлы относительно конфига |
|
||||
| `transform` | `TransformOptions` | Все включены | [Настройки трансформации](#трансформации) исходных SVG |
|
||||
| `generatedNotice` | `boolean` | `true` | Полное либо короткое предупреждение в generated-файлах |
|
||||
|
||||
`inputFolder` и `inputFiles` объединяются в один спрайт, поэтому один SVG-файл можно использовать в нескольких спрайтах без копирования. Если неявной папки `./icons` нет, но `inputFiles` заполнен, генерация продолжается только по списку. Явно указанная отсутствующая папка считается ошибкой. Одинаковые пути дедуплицируются, а разные файлы с одинаковым именем иконки считаются ошибкой.
|
||||
|
||||
`name` записывается в kebab-case и должно начинаться с латинской буквы. React и Next.js presets создают формат `stack`.
|
||||
|
||||
### Next.js
|
||||
|
||||
Next.js использует тот же `svg-sprite.config.ts` и набор опций. Для типизации можно использовать отдельный хелпер:
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'file-manager',
|
||||
description: 'Иконки файлового менеджера',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
Роутер и сборщик выбираются через mode key, поэтому переключение между Turbopack и Webpack всегда явно отражено в команде генерации.
|
||||
|
||||
## Множественные спрайты
|
||||
|
||||
Приложение может содержать несколько независимых спрайтов с разной областью использования:
|
||||
|
||||
**Проблема:** один глобальный спрайт загружает иконки, которые текущему экрану не нужны.
|
||||
|
||||
**Решение:** общие иконки хранить глобально, а наборы страниц и крупных компонентов — в отдельных спрайтах, загружаемых вместе с ними.
|
||||
|
||||
```text
|
||||
global → GlobalIcon → общие иконки приложения
|
||||
analytics-page → AnalyticsPageIcon → иконки отдельной страницы
|
||||
file-manager → FileManagerIcon → иконки крупного компонента
|
||||
```
|
||||
|
||||
- **Глобальный спрайт** содержит небольшие общие иконки, используемые в разных частях приложения: навигацию, состояния и базовые действия.
|
||||
- **Спрайт страницы** загружается вместе с конкретным разделом и не увеличивает общий спрайт иконками, которые больше нигде не нужны.
|
||||
- **Спрайт крупного компонента** инкапсулирует собственный набор иконок сложного UI-модуля, например файлового менеджера или редактора.
|
||||
|
||||
Каждая группа получает:
|
||||
|
||||
- собственный SVG asset;
|
||||
- собственный типизированный компонент;
|
||||
- отдельный список имён иконок;
|
||||
- отдельный debug-манифест;
|
||||
- независимый cache lifecycle.
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
Главная возможность TypeScript API — автодополнение имён иконок непосредственно в prop `icon`:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="folder" />
|
||||
// ↑ редактор предлагает все иконки спрайта
|
||||
```
|
||||
|
||||
Имена SVG-файлов становятся допустимыми значениями `icon`. Опечатка или неизвестное имя сразу становятся ошибкой TypeScript:
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="unknown" /> // ошибка TypeScript
|
||||
```
|
||||
|
||||
Для программного доступа generated-модуль экспортирует readonly-массив всех доступных иконок конкретного спрайта:
|
||||
|
||||
```ts
|
||||
import { fileManagerIconNames } from './svg-sprite'
|
||||
|
||||
// readonly ['check', 'folder', ...]
|
||||
```
|
||||
|
||||
Этот список можно использовать в собственных каталогах, select-компонентах, тестах и других runtime-сценариях. Из него также выводится union-тип `FileManagerIconName`.
|
||||
|
||||
Имена файлов с пробелами и другими небезопасными для SVG ID символами остаются частью публичного TypeScript API. Для внутреннего `<symbol id>` генератор создаёт стабильный hash ID.
|
||||
|
||||
```text
|
||||
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
|
||||
```
|
||||
|
||||
Для таких имён используйте generated-компонент или `id` из debug-манифеста. Ручные примеры ниже с `#<имя>` подходят только для имён, которые уже являются безопасными SVG ID.
|
||||
|
||||
## Форматы спрайтов
|
||||
|
||||
`stack` — более современный формат, поэтому он используется по умолчанию. Иконки можно отображать через `<svg><use>`, `<img>` и CSS `background-image`.
|
||||
|
||||
`symbol` сохраняется для совместимости с существующими интеграциями и поддерживает отображение только через `<svg><use>`.
|
||||
|
||||
## Способы отображения
|
||||
|
||||
### React-компонент — рекомендуется
|
||||
|
||||
Generated-компонент предоставляет типизацию, автодополнение имён иконок и сам формирует URL SVG asset.
|
||||
|
||||
```tsx
|
||||
<FileManagerIcon icon="check" width={24} height={24} />
|
||||
```
|
||||
|
||||
Через `color` и `--icon-color-N` доступны одноцветные и многоцветные иконки.
|
||||
|
||||
### Самостоятельно через `<svg><use>`
|
||||
|
||||
Хороший низкоуровневый способ с полным управлением размерами и цветами. React-компонент под капотом использует именно его.
|
||||
|
||||
Способ получения `spriteUrl` зависит от сборщика.
|
||||
|
||||
**Vite:**
|
||||
|
||||
```tsx
|
||||
import spriteUrl from './svg-sprite/generated/sprite.svg?no-inline'
|
||||
```
|
||||
|
||||
**Webpack 5:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
'./svg-sprite/generated/sprite.svg',
|
||||
import.meta.url,
|
||||
).href
|
||||
```
|
||||
|
||||
**Next.js с Webpack 5 или Turbopack:**
|
||||
|
||||
```tsx
|
||||
const spriteUrl = new URL(
|
||||
'./svg-sprite/generated/sprite.svg',
|
||||
import.meta.url,
|
||||
).href
|
||||
```
|
||||
|
||||
После получения URL иконка отображается одинаково:
|
||||
|
||||
```tsx
|
||||
<svg width={24} height={24}>
|
||||
<use href={`${spriteUrl}#check`} />
|
||||
</svg>
|
||||
```
|
||||
|
||||
Vite, Webpack 5 и Next.js сами заменяют исходный путь на итоговый URL asset с hash.
|
||||
|
||||
### Через `<img>` — менее эффективно
|
||||
|
||||
```tsx
|
||||
<img src={`${spriteUrl}#check`} width={24} height={24} alt="Готово" />
|
||||
```
|
||||
|
||||
SVG загружается как изолированное изображение: изменить его цвета через `color` или `--icon-color-N` нельзя.
|
||||
|
||||
### Через CSS `background-image` — менее эффективно
|
||||
|
||||
```css
|
||||
.icon {
|
||||
background: url('./svg-sprite/generated/sprite.svg#check') center / contain no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Как и `<img>`, этот способ не позволяет управлять внутренними цветами SVG. Путь указывается относительно CSS-файла, а Vite/Webpack заменяет его на итоговый URL с hash при сборке.
|
||||
|
||||
### Через CSS mask — менее эффективно
|
||||
|
||||
```css
|
||||
.icon {
|
||||
background-color: currentColor;
|
||||
mask: url('./svg-sprite/generated/sprite.svg#check') center / contain no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Mask оставляет только силуэт и окрашивает его одним цветом. Исходные цвета, gradients и различия между `fill` и `stroke` теряются.
|
||||
|
||||
## Трансформации
|
||||
|
||||
Все трансформации включены по умолчанию и настраиваются независимо через `transform`.
|
||||
|
||||
| Опция | По умолчанию | Что делает |
|
||||
|---|---|---|
|
||||
| `removeSize` | `true` | Удаляет `width` и `height` с корневого `<svg>`, сохраняя существующий `viewBox`. Размер иконки после этого задаётся снаружи. |
|
||||
| `replaceColors` | `true` | Заменяет цвета `fill` и `stroke` на `--icon-color-N`. Для одноцветной иконки fallback становится `currentColor`, для многоцветной сохраняются исходные цвета. |
|
||||
| `addTransition` | `true` | Добавляет `style="transition:fill 0.3s,stroke 0.3s;"` непосредственно цветным элементам SVG. Существующий `transition` не перезаписывается. |
|
||||
|
||||
Чтобы отключить преобразование, передайте для соответствующей опции `false`. Подробнее о результате `replaceColors` — в разделе [«Управление цветом иконок»](#управление-цветом-иконок).
|
||||
|
||||
## Управление цветом иконок
|
||||
|
||||
При включённой замене цветов генератор анализирует `fill` и `stroke` и преобразует их в CSS custom properties.
|
||||
|
||||
### Монохромные иконки
|
||||
|
||||
Если найден один цвет, fallback заменяется на `currentColor`:
|
||||
|
||||
```svg
|
||||
stroke="var(--icon-color-1, currentColor)"
|
||||
```
|
||||
|
||||
Цветом управляет CSS-свойство `color` внешнего `<svg>` или его родителя.
|
||||
|
||||
### Многоцветные иконки
|
||||
|
||||
Каждый уникальный цвет получает отдельную переменную с исходным fallback:
|
||||
|
||||
```svg
|
||||
fill="var(--icon-color-1, #798198)"
|
||||
fill="var(--icon-color-2, #ffffff)"
|
||||
fill="var(--icon-color-3, #129d9d)"
|
||||
```
|
||||
|
||||
Страница может заменить только необходимые цвета:
|
||||
|
||||
```css
|
||||
.icon {
|
||||
--icon-color-1: #4b5563;
|
||||
--icon-color-3: #14b8a6;
|
||||
}
|
||||
```
|
||||
|
||||
### Ограничения цветов
|
||||
|
||||
- `none`, `transparent`, `inherit`, `unset` и `initial` не заменяются;
|
||||
- цвета в атрибутах `fill`, `stroke` и inline `style` обрабатываются надёжнее всего;
|
||||
- CSS-классы и внешние stylesheets внутри исходного SVG не являются основным сценарием трансформации;
|
||||
- gradients, patterns, filters и значения `url(#...)` требуют отдельной проверки и могут быть несовместимы с автоматической заменой цветов;
|
||||
- CSS-переменные страницы доступны при `<svg><use>`, но недоступны внутри `<img>` и `background-image`.
|
||||
|
||||
## Кеширование
|
||||
|
||||
Vite, Webpack и Next.js target выпускают спрайт отдельным asset с content hash:
|
||||
|
||||
```text
|
||||
/assets/sprite-<hash>.svg
|
||||
```
|
||||
|
||||
Это даёт следующие свойства:
|
||||
|
||||
- SVG кешируется независимо от JavaScript;
|
||||
- изменение React-кода не меняет содержимое спрайта;
|
||||
- изменение иконок создаёт новый hash asset;
|
||||
- один файл используется всеми экземплярами generated-компонента;
|
||||
- SVG path-данные отсутствуют в JavaScript chunks.
|
||||
|
||||
Vite target запрещает inline через `?no-inline`. Webpack 5 target использует Asset Modules через `new URL(..., import.meta.url)`.
|
||||
|
||||
## SpriteViewer
|
||||
|
||||
`SpriteViewer` — React-компонент для просмотра generated-спрайтов внутри debug-маршрута приложения.
|
||||
|
||||
Он использует отдельные манифесты и показывает:
|
||||
|
||||
- группы спрайтов;
|
||||
- список и количество иконок;
|
||||
- поиск и системную светлую/тёмную тему;
|
||||
- модальное превью с `viewBox` и настройкой цветовых переменных;
|
||||
- примеры React, SVG, IMG и CSS с копированием кода.
|
||||
|
||||
Production-компоненты не импортируют debug-манифесты. Способ подключения Viewer зависит от сборщика:
|
||||
|
||||
- [React + Vite: автоматический `import.meta.glob`](docs/ru/react-vite.md#6-добавьте-debug-страницу);
|
||||
- [React + Webpack 5: статические `import()`](docs/ru/react-webpack.md#6-добавьте-debug-страницу);
|
||||
- [Next.js App Router](docs/ru/next-app.md#5-добавьте-spriteviewer);
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md#5-добавьте-spriteviewer).
|
||||
|
||||
Viewer подключается из отдельной клиентской точки входа `@gromlab/svg-sprites/react` и не попадает в production-компоненты иконок.
|
||||
|
||||
### Тема Viewer
|
||||
|
||||
По умолчанию `colorTheme="auto"`: Viewer следует `prefers-color-scheme` и реагирует на смену системной темы. Тему приложения можно передать явно:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer sources={sources} colorTheme="dark" />
|
||||
```
|
||||
|
||||
Допустимые значения `colorTheme`: `auto`, `light`, `dark`. При управлении темой извне встроенный переключатель скрывается. Чтобы оставить его и обновлять тему приложения через Viewer, передайте callback:
|
||||
|
||||
```tsx
|
||||
<SpriteViewer
|
||||
sources={sources}
|
||||
colorTheme={appTheme}
|
||||
onColorThemeChange={setAppTheme}
|
||||
/>
|
||||
```
|
||||
|
||||
## Документация
|
||||
|
||||
- [React + Vite](docs/ru/react-vite.md)
|
||||
- [React + Webpack 5](docs/ru/react-webpack.md)
|
||||
- [Next.js App Router](docs/ru/next-app.md)
|
||||
- [Next.js Pages Router](docs/ru/next-pages.md)
|
||||
- [Legacy mode](docs/ru/legacy.md)
|
||||
- [Миграция с 0.1.x](docs/ru/migration-1.md)
|
||||
- [Программный API](docs/ru/programmatic-api.md)
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT
|
||||
102
skills/artifacts/svg-sprites-ru/references/docs/en/legacy.md
Normal file
102
skills/artifacts/svg-sprites-ru/references/docs/en/legacy.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Legacy mode
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
A quick guide to generating centralized SVG sprites in `symbol` and `stack` formats, with an optional HTML preview.
|
||||
|
||||
## 1. Install the package
|
||||
|
||||
```bash
|
||||
npm install @gromlab/svg-sprites
|
||||
```
|
||||
|
||||
## 2. Prepare the icons and config
|
||||
|
||||
```text
|
||||
project/
|
||||
├── src/assets/icons/
|
||||
│ ├── check.svg
|
||||
│ └── folder.svg
|
||||
└── svg-sprites.config.ts
|
||||
```
|
||||
|
||||
```ts
|
||||
// svg-sprites.config.ts
|
||||
import { defineLegacyConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineLegacyConfig({
|
||||
output: 'public/sprites',
|
||||
preview: true,
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'symbol',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## 3. Run generation
|
||||
|
||||
```bash
|
||||
npx svg-sprites --mode legacy .
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
public/sprites/
|
||||
├── icons.sprite.svg
|
||||
└── preview.html
|
||||
```
|
||||
|
||||
With `preview: false`, the HTML file is not created. For the `stack` format, specify `format: 'stack'`.
|
||||
|
||||
## 4. Use the symbol sprite
|
||||
|
||||
```html
|
||||
<svg width="24" height="24" aria-label="Done">
|
||||
<use href="/sprites/icons.sprite.svg#check"></use>
|
||||
</svg>
|
||||
```
|
||||
|
||||
## 5. Add a package script
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"sprites": "svg-sprites --mode legacy .",
|
||||
"prebuild": "npm run sprites"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple sprites
|
||||
|
||||
Add multiple entries to `sprites`:
|
||||
|
||||
```ts
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'symbol',
|
||||
},
|
||||
{
|
||||
name: 'logos',
|
||||
input: 'src/assets/logos',
|
||||
format: 'stack',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
All output files and the shared `preview.html` will be written to `output`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Config not found: make sure `svg-sprites.config.ts` is located in the specified root directory.
|
||||
- No icons: check `sprites[].input` and the `.svg` extension.
|
||||
- Preview not needed: set `preview: false`.
|
||||
|
||||
For programmatic use, see [`generateLegacy`](programmatic-api.md#generatelegacy).
|
||||
@@ -0,0 +1,96 @@
|
||||
# Migrating from 0.1.x to 1.0
|
||||
|
||||
[← Back to home](../../README.md)
|
||||
|
||||
Version 1.0 separates local generation for React and Next.js from the centralized legacy mode. The old config cannot be mixed with the new API in a single CLI invocation.
|
||||
|
||||
## CLI
|
||||
|
||||
The CLI now always requires an explicit `--mode` and a path to the configuration directory:
|
||||
|
||||
```text
|
||||
svg-sprites
|
||||
→ svg-sprites --mode <mode> <path>
|
||||
```
|
||||
|
||||
Choose a mode based on your environment:
|
||||
|
||||
| Environment | Mode |
|
||||
|---|---|
|
||||
| React + Vite | `react@vite` |
|
||||
| React + Webpack 5 | `react@webpack` |
|
||||
| Next.js App Router + Turbopack | `next@app/turbopack` |
|
||||
| Next.js App Router + Webpack 5 | `next@app/webpack` |
|
||||
| Next.js Pages Router + Turbopack | `next@pages/turbopack` |
|
||||
| Next.js Pages Router + Webpack 5 | `next@pages/webpack` |
|
||||
| Centralized legacy setup | `legacy` |
|
||||
|
||||
## React and Next.js
|
||||
|
||||
Instead of a root-level `svg-sprites.config.ts`, create a local `svg-sprite.config.ts` next to the icon set:
|
||||
|
||||
```ts
|
||||
import { defineNextSpriteConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineNextSpriteConfig({
|
||||
name: 'global',
|
||||
inputFolder: './icons',
|
||||
})
|
||||
```
|
||||
|
||||
For regular React, use `defineReactSpriteConfig`. A folder and an explicit list of shared SVG files can be combined using `inputFolder` and `inputFiles`.
|
||||
|
||||
The old `publicPath` and `react` options are no longer needed. The generated module is created next to the config and adds its own `.gitignore`, while Vite, Webpack, or Next.js emits the SVG as a separate asset with a content hash.
|
||||
|
||||
The `<SvgSprite icon="..." />` component is replaced by a component whose name is derived from `name`:
|
||||
|
||||
```tsx
|
||||
<GlobalIcon icon="check" />
|
||||
```
|
||||
|
||||
To browse the icons, add `<SpriteViewer>` as a debug page in the application. A separate `preview.html` is available only in legacy mode.
|
||||
|
||||
## Legacy mode
|
||||
|
||||
If you need to preserve the centralized structure, rename the helper and the format fields:
|
||||
|
||||
```ts
|
||||
import { defineLegacyConfig } from '@gromlab/svg-sprites'
|
||||
|
||||
export default defineLegacyConfig({
|
||||
output: 'public/sprites',
|
||||
preview: true,
|
||||
sprites: [
|
||||
{
|
||||
name: 'icons',
|
||||
input: 'src/assets/icons',
|
||||
format: 'stack',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
- `defineConfig` has been replaced with `defineLegacyConfig`;
|
||||
- `sprites[].mode` has been renamed to `sprites[].format`;
|
||||
- `generate` has been replaced with `generateLegacy`;
|
||||
- `loadConfig` has been replaced with `loadLegacyConfig`;
|
||||
- `publicPath` and generation of the old shared React component have been removed.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
svg-sprites --mode legacy .
|
||||
```
|
||||
|
||||
## Programmatic API
|
||||
|
||||
The package is distributed as ESM only. Replace `require()` with `import`.
|
||||
|
||||
`compileSpriteContent` now returns `Promise<Uint8Array>` so that the public declarations do not require `@types/node` to be installed. In Node.js, the actual result is compatible with APIs that accept `Uint8Array`.
|
||||
|
||||
## After migration
|
||||
|
||||
1. Remove the old generated files and rules that ignored the entire directory containing the source icons.
|
||||
2. Add an explicit generation command before `dev`, `build`, and `typecheck`.
|
||||
3. Run generation and type checking.
|
||||
4. Check all icons and color variables using `SpriteViewer` or the legacy `preview.html`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user