chore: перевести проект на SLM-структуру и добавить SVG-спрайты
- Добавлены devDependencies: svg-sprite, postcss-preset-mantine, postcss-simple-vars, colorette - Добавлен npm-скрипт `sprite` для генерации SVG-спрайтов - Обновлены настройки и расширения VS Code - Переименованы слои: entities → business, features → infrastructure, shared/ui → ui - Обновлены шаблоны генерации (.templates) под новые слои - Обновлены path-алиасы в tsconfig.json: убран префикс @/, добавлены алиасы по слоям - Импорт в src/app/page.tsx переведён на алиас слоя - Удалён postcss.config.mjs - Добавлен скрипт scripts/create-svg-sprite.js - Добавлены исходные SVG-иконки и сгенерированные спрайты - Добавлен модуль src/shared/sprites/icons.generated.ts - Добавлены глобальные стилевые токены: variables.css, media.css - Применён медиа-токен в src/screens/home/styles/home.module.css - Добавлен AGENTS.md с инструкциями для AI-ассистента
This commit is contained in:
1
.templates/business/{{name.kebabCase}}/index.ts
Normal file
1
.templates/business/{{name.kebabCase}}/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { {{name.pascalCase}}Business } from './{{name.kebabCase}}.business'
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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,15 +1,15 @@
|
|||||||
import cl from 'clsx'
|
import cl from 'clsx'
|
||||||
import type { {{name.pascalCase}}EntityProps } from './types/{{name.kebabCase}}.type'
|
import type { {{name.pascalCase}}BusinessProps } from './types/{{name.kebabCase}}.type'
|
||||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <Назначение сущности {{name.pascalCase}} в 1 строке>.
|
* <Назначение бизнес-модуля {{name.pascalCase}} в 1 строке>.
|
||||||
*
|
*
|
||||||
* Используется для:
|
* Используется для:
|
||||||
* - <сценарий 1>
|
* - <сценарий 1>
|
||||||
* - <сценарий 2>
|
* - <сценарий 2>
|
||||||
*/
|
*/
|
||||||
export const {{name.pascalCase}}Entity = (props: {{name.pascalCase}}EntityProps) => {
|
export const {{name.pascalCase}}Business = (props: {{name.pascalCase}}BusinessProps) => {
|
||||||
const { children, className, ...htmlAttr } = props
|
const { children, className, ...htmlAttr } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { {{name.pascalCase}}Entity } from './{{name.kebabCase}}.entity'
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import type { HTMLAttributes } from 'react'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Параметры сущности {{name.pascalCase}}.
|
|
||||||
*/
|
|
||||||
export type {{name.pascalCase}}EntityParams = {}
|
|
||||||
|
|
||||||
/** HTML-атрибуты корневого элемента. */
|
|
||||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
|
||||||
|
|
||||||
export type {{name.pascalCase}}EntityProps = RootAttrs & {{name.pascalCase}}EntityParams
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { {{name.pascalCase}}Feature } from './{{name.kebabCase}}.feature'
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import type { HTMLAttributes } from 'react'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Параметры фичи {{name.pascalCase}}.
|
|
||||||
*/
|
|
||||||
export type {{name.pascalCase}}FeatureParams = {}
|
|
||||||
|
|
||||||
/** HTML-атрибуты корневого элемента. */
|
|
||||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
|
||||||
|
|
||||||
export type {{name.pascalCase}}FeatureProps = RootAttrs & {{name.pascalCase}}FeatureParams
|
|
||||||
1
.templates/infrastructure/{{name.kebabCase}}/index.ts
Normal file
1
.templates/infrastructure/{{name.kebabCase}}/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { {{name.pascalCase}}Infra } from './{{name.kebabCase}}.infra'
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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
.templates/ui/{{name.kebabCase}}/index.ts
Normal file
1
.templates/ui/{{name.kebabCase}}/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { {{name.pascalCase}} } from './{{name.kebabCase}}.ui'
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.root {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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,15 +1,15 @@
|
|||||||
import cl from 'clsx'
|
import cl from 'clsx'
|
||||||
import type { {{name.pascalCase}}FeatureProps } from './types/{{name.kebabCase}}.type'
|
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <Назначение фичи {{name.pascalCase}} в 1 строке>.
|
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
|
||||||
*
|
*
|
||||||
* Используется для:
|
* Используется для:
|
||||||
* - <сценарий 1>
|
* - <сценарий 1>
|
||||||
* - <сценарий 2>
|
* - <сценарий 2>
|
||||||
*/
|
*/
|
||||||
export const {{name.pascalCase}}Feature = (props: {{name.pascalCase}}FeatureProps) => {
|
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||||
const { children, className, ...htmlAttr } = props
|
const { children, className, ...htmlAttr } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"biomejs.biome",
|
"biomejs.biome",
|
||||||
"MyTemplateGenerator.mytemplategenerator",
|
"MyTemplateGenerator.mytemplategenerator"
|
||||||
"csstools.postcss"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -4,8 +4,8 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.biome": "explicit",
|
"source.fixAll.biome": "explicit",
|
||||||
"source.organizeImports.biome": "explicit"
|
"source.organizeImports.biome": "explicit"
|
||||||
},
|
|
||||||
"files.associations": {
|
|
||||||
"*.css": "postcss"
|
|
||||||
}
|
}
|
||||||
|
// "files.associations": {
|
||||||
|
// "*.css": "postcss"
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
83
AGENTS.md
83
AGENTS.md
@@ -1,83 +0,0 @@
|
|||||||
# Правила работы с проектом
|
|
||||||
|
|
||||||
## Стайлгайд
|
|
||||||
|
|
||||||
Проект строго придерживается стайлгайда. Перед любой работой с кодом — прочитай полную документацию:
|
|
||||||
|
|
||||||
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
|
|
||||||
|
|
||||||
Все решения по архитектуре, именованию, стилям, компонентам и структуре принимаются на основе этого документа. Отклонения от стайлгайда недопустимы без явного согласования.
|
|
||||||
|
|
||||||
## Язык общения
|
|
||||||
|
|
||||||
- Всегда использовать русский язык: размышления, пояснения, подсказки, инструкции — всё формулировать по-русски.
|
|
||||||
- Не переключаться на английский без прямого запроса пользователя.
|
|
||||||
|
|
||||||
## Коммиты
|
|
||||||
|
|
||||||
- НЕ добавлять подпись
|
|
||||||
- Писать сообщения коммитов на русском языке
|
|
||||||
|
|
||||||
### Формат
|
|
||||||
|
|
||||||
```
|
|
||||||
<тип>: <краткое описание>
|
|
||||||
|
|
||||||
- Детали в прошедшем времени
|
|
||||||
- Каждый пункт — отдельное изменение
|
|
||||||
```
|
|
||||||
|
|
||||||
### Типы коммитов
|
|
||||||
|
|
||||||
| Тип | Назначение |
|
|
||||||
|---|---|
|
|
||||||
| `feat` | Новая функциональность |
|
|
||||||
| `fix` | Исправление бага |
|
|
||||||
| `refactor` | Рефакторинг без изменения поведения |
|
|
||||||
| `style` | Стили, форматирование, отступы |
|
|
||||||
| `docs` | Документация |
|
|
||||||
| `chore` | Настройка, зависимости, CI |
|
|
||||||
| `test` | Тесты |
|
|
||||||
| `perf` | Оптимизация производительности |
|
|
||||||
|
|
||||||
### Правила
|
|
||||||
|
|
||||||
- Первая строка — не длиннее 72 символов
|
|
||||||
- Описание — с маленькой буквы (если не имя собственное)
|
|
||||||
- Пункты — в прошедшем времени
|
|
||||||
- Scope (область) опционален: `feat(auth): ...`, `fix(ui): ...`
|
|
||||||
|
|
||||||
### Примеры
|
|
||||||
|
|
||||||
```
|
|
||||||
feat: автодополнение и режимы запуска
|
|
||||||
|
|
||||||
- Добавлены служебные команды и генерация completion для bash/zsh/fish
|
|
||||||
- Введён детект режимов запуска (npx/local/direct/global)
|
|
||||||
- Обновлены help и документация
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
fix: некорректная ширина sidebar на мобильных
|
|
||||||
|
|
||||||
- Исправлен медиа-запрос для breakpoint --sm
|
|
||||||
- Убран фиксированный width в пользу max-width
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
chore: настройка Biome и VS Code
|
|
||||||
|
|
||||||
- Добавлены одинарные кавычки в конфигурацию Biome
|
|
||||||
- Исключена папка .templates из проверок
|
|
||||||
- Обновлён quickfix.biome на source.fixAll.biome
|
|
||||||
```
|
|
||||||
|
|
||||||
## Генерация кода
|
|
||||||
|
|
||||||
- Модули (компоненты, фичи, виджеты, сущности, layouts, screens, сторы) создаются только из шаблонов `.templates/`.
|
|
||||||
- Ручное создание файловой структуры модулей запрещено.
|
|
||||||
- Генерация: `npx @gromlab/create <шаблон> <имя> <путь>`
|
|
||||||
|
|
||||||
## Next.js
|
|
||||||
|
|
||||||
This is NOT the Next.js you know. This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
||||||
1575
package-lock.json
generated
1575
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "biome check",
|
"lint": "biome check",
|
||||||
"format": "biome format --write"
|
"format": "biome format --write",
|
||||||
|
"sprite": "node scripts/create-svg-sprite.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "^8.3.18",
|
||||||
@@ -24,6 +25,10 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
"colorette": "^2.0.20",
|
||||||
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
|
"postcss-simple-vars": "^7.0.1",
|
||||||
|
"svg-sprite": "^2.0.4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
161
public/img/sprites/icons/sprite.stack.html
Normal file
161
public/img/sprites/icons/sprite.stack.html
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>SVG stack preview | svg-sprite</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: #666;
|
||||||
|
background: #fafafa;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: block;
|
||||||
|
padding: 3em 3em 2em;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
header p {
|
||||||
|
margin: 2em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding: 2em 3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section li {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #fff;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 2em 2em 0;
|
||||||
|
vertical-align: top;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 1em 1em 3em;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
margin: 0;
|
||||||
|
width: 144px;
|
||||||
|
height: 144px;
|
||||||
|
position: relative;
|
||||||
|
background: #ccc url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath fill='%23fff' d='M6 0h6v6H6zM0 6h6v6H0z'/%3E%3C/svg%3E") top left repeat;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
position: absolute;
|
||||||
|
left: 1em;
|
||||||
|
right: 1em;
|
||||||
|
bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 3em 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #0f7595;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Sprite shape dimensions
|
||||||
|
====================================================================================================
|
||||||
|
You will need to set the sprite shape dimensions via CSS when you use them as stack SVGs, otherwise
|
||||||
|
they would become a huge 100% in size. You may use the following dimension classes for doing so.
|
||||||
|
They might well be outsourced to an external stylesheet of course.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.svg-arrow-down-dims { width: 24px; height: 24px; }
|
||||||
|
.svg-arrow-right-dims { width: 20px; height: 20px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
====================================================================================================
|
||||||
|
-->
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>SVG stack preview</h1>
|
||||||
|
<p>This preview features an SVG stack. Please have a look at the HTML source for further details and be aware of the following constraints:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Your browser has to <a href="https://caniuse.com/svg-fragment" target="_blank" rel="noopener noreferrer">support SVG fragment identifiers</a> for SVG stacks to work.</li>
|
||||||
|
<li>Support for SVG fragment identifiers hasn't always been that decent. For older browsers you will have to use some prolyfill like <a href="https://github.com/preciousforever/SVG-Stacker/blob/master/fixsvgstack.jquery.js" target="_blank" rel="noopener noreferrer">fixsvgstack.jquery.js</a>.</li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SVG stack
|
||||||
|
====================================================================================================
|
||||||
|
These SVG images make use of fragment identifiers (IDs) to reference certain portions of the
|
||||||
|
external sprite. By default, all shapes inside the sprite are hidden by CSS. The `:target` pseudo
|
||||||
|
selector is used to show the very shape that is referenced by the fragment identifier.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li title="arrow-down">
|
||||||
|
<div class="icon-box">
|
||||||
|
<img src="sprite.stack.svg#arrow-down" class="svg-arrow-down-dims" alt="arrow-down">
|
||||||
|
</div>
|
||||||
|
<h2>arrow-down, </h2>
|
||||||
|
</li>
|
||||||
|
<li title="arrow-right">
|
||||||
|
<div class="icon-box">
|
||||||
|
<img src="sprite.stack.svg#arrow-right" class="svg-arrow-right-dims" alt="arrow-right">
|
||||||
|
</div>
|
||||||
|
<h2>arrow-right, </h2>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
====================================================================================================
|
||||||
|
-->
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<p>Generated at Tue, 21 Apr 2026 18:16:57 GMT by <a href="https://github.com/svg-sprite/svg-sprite" target="_blank" rel="noopener noreferrer">svg-sprite</a>.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
public/img/sprites/icons/sprite.stack.svg
Normal file
1
public/img/sprites/icons/sprite.stack.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>:root>svg{display:none}:root>svg:target{display:block}</style><svg viewBox="0 0 24 24" fill="none" id="arrow-down" xmlns="http://www.w3.org/2000/svg"><path d="M18.07 14.43 12 20.5l-6.07-6.07M12 3.5v16.83" stroke="var(--icon-color-1, currentColor)" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg><svg viewBox="0 0 20 20" fill="none" id="arrow-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M18.25 10a.75.75 0 0 1-.22.53l-5.833 5.834a.75.75 0 1 1-1.06-1.06l4.553-4.554H1.667a.75.75 0 0 1 0-1.5H15.69l-4.553-4.553a.75.75 0 0 1 1.06-1.06l5.834 5.833c.14.14.22.331.22.53Z" fill="var(--icon-color-1, currentColor)"/></svg></svg>
|
||||||
|
After Width: | Height: | Size: 843 B |
126
scripts/create-svg-sprite.js
Normal file
126
scripts/create-svg-sprite.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Генерация SVG-спрайтов и TypeScript-типов имён иконок.
|
||||||
|
*
|
||||||
|
* Читает подпапки из ASSETS_DIR, для каждой собирает SVG в спрайт (stack или symbol)
|
||||||
|
* и генерирует .generated.ts файл с union-типом имён иконок.
|
||||||
|
*
|
||||||
|
* Режим спрайта определяется суффиксом папки: «icons?symbol» → symbol, иначе stack.
|
||||||
|
*
|
||||||
|
* Запуск: npm run sprite
|
||||||
|
*/
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const SVGSpriter = require('svg-sprite')
|
||||||
|
const color = require('colorette')
|
||||||
|
|
||||||
|
const ROOT = process.cwd()
|
||||||
|
|
||||||
|
/** Папка с исходными SVG-файлами. */
|
||||||
|
const ASSETS_DIR = path.join(ROOT, 'src/shared/sprites')
|
||||||
|
|
||||||
|
/** Папка для сгенерированных спрайтов. */
|
||||||
|
const DEST_DIR = path.join(ROOT, 'public/img/sprites')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Преобразует kebab-case строку в PascalCase.
|
||||||
|
*/
|
||||||
|
const toPascalCase = (str) =>
|
||||||
|
str.replace(/(^|[-_])([a-z])/g, (_, __, c) => c.toUpperCase())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает конфигурацию режима для svg-sprite.
|
||||||
|
*/
|
||||||
|
const getModeConfig = (mode, destDir) => ({
|
||||||
|
dest: destDir,
|
||||||
|
sprite: `sprite.${mode}.svg`,
|
||||||
|
example: true,
|
||||||
|
rootviewbox: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерирует TypeScript-файл с union-типом имён иконок спрайта.
|
||||||
|
*/
|
||||||
|
const generateIconNames = (folderName, svgFiles) => {
|
||||||
|
const names = svgFiles
|
||||||
|
.map((filePath) => path.basename(filePath, '.svg'))
|
||||||
|
.sort()
|
||||||
|
|
||||||
|
const typeName = `${toPascalCase(folderName)}IconName`
|
||||||
|
|
||||||
|
const content = [
|
||||||
|
'/**',
|
||||||
|
` * Имена иконок спрайта «${folderName}».`,
|
||||||
|
' * @generated — файл создан автоматически (npm run sprite), не редактировать вручную.',
|
||||||
|
' */',
|
||||||
|
`export type ${typeName} =`,
|
||||||
|
names.map((name) => ` | '${name}'`).join('\n'),
|
||||||
|
'',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
const outputPath = path.join(ASSETS_DIR, `${folderName}.generated.ts`)
|
||||||
|
fs.writeFileSync(outputPath, content)
|
||||||
|
console.log(
|
||||||
|
color.green(`Generated types: ${folderName}.generated.ts (${names.length} icons)`),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обрабатывает одну папку со спрайтами.
|
||||||
|
*/
|
||||||
|
const processFolder = (fullFolderName) => {
|
||||||
|
const folderPath = path.join(ASSETS_DIR, fullFolderName)
|
||||||
|
|
||||||
|
if (!fs.lstatSync(folderPath).isDirectory()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCustomMode = fullFolderName.includes('?')
|
||||||
|
const parts = fullFolderName.split('?')
|
||||||
|
const mode = hasCustomMode ? parts.pop() : 'stack'
|
||||||
|
const folderName = parts[0]
|
||||||
|
|
||||||
|
const svgFiles = fs
|
||||||
|
.readdirSync(folderPath)
|
||||||
|
.filter((file) => file.endsWith('.svg'))
|
||||||
|
.map((file) => path.join(folderPath, file))
|
||||||
|
|
||||||
|
if (!svgFiles.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
log: 'debug',
|
||||||
|
mode: {
|
||||||
|
[mode]: getModeConfig(mode, path.join(DEST_DIR, folderName)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const spriter = new SVGSpriter(config)
|
||||||
|
|
||||||
|
for (const fileName of svgFiles) {
|
||||||
|
spriter.add(fileName, null, fs.readFileSync(fileName, 'utf-8'))
|
||||||
|
}
|
||||||
|
|
||||||
|
spriter.compile((error, result) => {
|
||||||
|
if (error) {
|
||||||
|
console.log(color.red(error.message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const modeResult of Object.values(result)) {
|
||||||
|
for (const resource of Object.values(modeResult)) {
|
||||||
|
fs.mkdirSync(path.dirname(resource.path), { recursive: true })
|
||||||
|
fs.writeFileSync(resource.path, resource.contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
generateIconNames(folderName, svgFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = fs.readdirSync(ASSETS_DIR)
|
||||||
|
entries.forEach(processFolder)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(color.red(err.message))
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { HomeScreen } from '@/screens/home';
|
import { HomeScreen } from 'screens/home';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Главная',
|
title: 'Главная',
|
||||||
|
|||||||
@@ -2,4 +2,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
|
@media (--md) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/shared/sprites/icons.generated.ts
Normal file
7
src/shared/sprites/icons.generated.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Имена иконок спрайта «icons».
|
||||||
|
* @generated — файл создан автоматически (npm run sprite), не редактировать вручную.
|
||||||
|
*/
|
||||||
|
export type IconsIconName =
|
||||||
|
| 'arrow-down'
|
||||||
|
| 'arrow-right'
|
||||||
4
src/shared/sprites/icons/arrow-down.svg
Normal file
4
src/shared/sprites/icons/arrow-down.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.0701 14.4301L12.0001 20.5001L5.93005 14.4301" stroke="var(--icon-color-1, currentColor)" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 3.5V20.33" stroke="var(--icon-color-1, currentColor)" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 431 B |
3
src/shared/sprites/icons/arrow-right.svg
Normal file
3
src/shared/sprites/icons/arrow-right.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2503 10.0003C18.2503 10.1992 18.1713 10.39 18.0307 10.5307L12.1973 16.364C11.9044 16.6569 11.4296 16.6569 11.1367 16.364C10.8438 16.0711 10.8438 15.5962 11.1367 15.3033L15.6897 10.7503L1.66699 10.7503C1.25278 10.7503 0.916992 10.4145 0.916992 10.0003C0.916992 9.58611 1.25278 9.25033 1.66699 9.25033L15.6897 9.25033L11.1367 4.69732C10.8438 4.40443 10.8438 3.92956 11.1367 3.63666C11.4296 3.34377 11.9044 3.34377 12.1973 3.63666L18.0307 9.47C18.1713 9.61065 18.2503 9.80141 18.2503 10.0003Z" fill="var(--icon-color-1, currentColor)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 667 B |
0
src/shared/styles/.gitkeep
Normal file
0
src/shared/styles/.gitkeep
Normal file
15
src/shared/styles/media.css
Normal file
15
src/shared/styles/media.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@custom-media --xs (max-width: 575px);
|
||||||
|
@custom-media --sm (min-width: 576px);
|
||||||
|
@custom-media --md (min-width: 768px);
|
||||||
|
@custom-media --lg (min-width: 992px);
|
||||||
|
@custom-media --xl (min-width: 1200px);
|
||||||
|
@custom-media --2xl (min-width: 1408px);
|
||||||
|
@custom-media --3xl (min-width: 1920px);
|
||||||
|
|
||||||
|
@custom-media --h-xs (min-height: 667px);
|
||||||
|
@custom-media --h-sm (min-height: 702px);
|
||||||
|
@custom-media --h-md (min-height: 810px);
|
||||||
|
@custom-media --h-lg (min-height: 900px);
|
||||||
|
@custom-media --h-xl (min-height: 1000px);
|
||||||
|
@custom-media --h-xxl (min-height: 1100px);
|
||||||
|
@custom-media --h-xxxl (min-height: 1200px);
|
||||||
102
src/shared/styles/variables.css
Normal file
102
src/shared/styles/variables.css
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
:root {
|
||||||
|
/* ========================================
|
||||||
|
* Цвета
|
||||||
|
* ======================================== */
|
||||||
|
|
||||||
|
/* Фоновые */
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--color-bg-hero: #edf3f9;
|
||||||
|
--color-bg-accent: #f2f8fa;
|
||||||
|
--color-bg-dark: #08050d;
|
||||||
|
|
||||||
|
/* Текстовые */
|
||||||
|
--color-text: #08050d;
|
||||||
|
--color-text-secondary: #4e5566;
|
||||||
|
--color-text-tertiary: #8a8f9c;
|
||||||
|
|
||||||
|
/* Бордеры */
|
||||||
|
--color-border: #d9dde5;
|
||||||
|
--color-border-light: #e8ebf3;
|
||||||
|
|
||||||
|
/* Скроллбар */
|
||||||
|
--color-scrollbar: #e1eff4;
|
||||||
|
|
||||||
|
/* Акцент */
|
||||||
|
--color-accent: #129d9d;
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
* Шрифты
|
||||||
|
* ======================================== */
|
||||||
|
--font-display: 'Biocad Display', sans-serif;
|
||||||
|
--font-serif: 'Lora', serif;
|
||||||
|
--font-sans: 'Noto Sans', sans-serif;
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
* Отступы
|
||||||
|
* ======================================== */
|
||||||
|
--space-1: 4px;
|
||||||
|
--space-2: 8px;
|
||||||
|
--space-3: 12px;
|
||||||
|
--space-4: 16px;
|
||||||
|
--space-5: 20px;
|
||||||
|
--space-6: 24px;
|
||||||
|
--space-7: 28px;
|
||||||
|
--space-8: 32px;
|
||||||
|
--space-9: 36px;
|
||||||
|
--space-10: 40px;
|
||||||
|
--space-11: 44px;
|
||||||
|
--space-12: 48px;
|
||||||
|
--space-13: 52px;
|
||||||
|
--space-14: 56px;
|
||||||
|
--space-15: 60px;
|
||||||
|
--space-16: 64px;
|
||||||
|
--space-17: 68px;
|
||||||
|
--space-18: 72px;
|
||||||
|
--space-19: 76px;
|
||||||
|
--space-20: 80px;
|
||||||
|
--space-21: 84px;
|
||||||
|
--space-22: 88px;
|
||||||
|
--space-23: 92px;
|
||||||
|
--space-24: 96px;
|
||||||
|
--space-25: 100px;
|
||||||
|
--space-26: 104px;
|
||||||
|
--space-27: 108px;
|
||||||
|
--space-28: 112px;
|
||||||
|
--space-29: 116px;
|
||||||
|
--space-30: 120px;
|
||||||
|
--space-31: 124px;
|
||||||
|
--space-32: 128px;
|
||||||
|
--space-33: 132px;
|
||||||
|
--space-34: 136px;
|
||||||
|
--space-35: 140px;
|
||||||
|
--space-36: 144px;
|
||||||
|
--space-37: 148px;
|
||||||
|
--space-38: 152px;
|
||||||
|
--space-39: 156px;
|
||||||
|
--space-40: 160px;
|
||||||
|
--space-41: 164px;
|
||||||
|
--space-42: 168px;
|
||||||
|
--space-43: 172px;
|
||||||
|
--space-44: 176px;
|
||||||
|
--space-45: 180px;
|
||||||
|
--space-46: 184px;
|
||||||
|
--space-47: 188px;
|
||||||
|
--space-48: 192px;
|
||||||
|
--space-49: 196px;
|
||||||
|
--space-50: 200px;
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
* Скругления
|
||||||
|
* ======================================== */
|
||||||
|
--radius-1: 12px;
|
||||||
|
--radius-2: 18px;
|
||||||
|
--radius-3: 24px;
|
||||||
|
--radius-4: 36px;
|
||||||
|
--radius-full: 800px;
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
* Layout
|
||||||
|
* ======================================== */
|
||||||
|
--content-width: 1728px;
|
||||||
|
--content-padding: 48px;
|
||||||
|
}
|
||||||
0
src/ui/.gitkeep
Normal file
0
src/ui/.gitkeep
Normal file
@@ -19,7 +19,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"app/*": ["./src/app/*"],
|
||||||
|
"business/*": ["./src/business/*"],
|
||||||
|
"infrastructure/*": ["./src/infrastructure/*"],
|
||||||
|
"layouts/*": ["./src/layouts/*"],
|
||||||
|
"screens/*": ["./src/screens/*"],
|
||||||
|
"ui/*": ["./src/ui/*"],
|
||||||
|
"shared/*": ["./src/ui/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
Reference in New Issue
Block a user