330 lines
6.9 KiB
Markdown
330 lines
6.9 KiB
Markdown
# ADR-005: Design Tokens как единый источник стилей
|
|
|
|
**Статус:** принято
|
|
**Дата:** 2026-05-10
|
|
|
|
---
|
|
|
|
## Контекст
|
|
|
|
Проект VoIdea работает на нескольких платформах:
|
|
- Web (PWA)
|
|
- iOS (Swift/SwiftUI)
|
|
- Android (Kotlin)
|
|
|
|
Требуется унифицировать стили (цвета, шрифты, отступы) между всеми платформами.
|
|
|
|
Рассматривались:
|
|
- **Ручное копирование** — непоследовательно, сложно поддерживать
|
|
- **Shared library** — требует синхронизации
|
|
- **Design Tokens (JSON)** — единый источник, генераторы
|
|
|
|
---
|
|
|
|
## Решение
|
|
|
|
**Design Tokens в JSON + генераторы для каждой платформы**
|
|
|
|
```
|
|
docs/design-system/tokens.json (источник истины)
|
|
│
|
|
├── generators/css_generator.py → app/design-tokens/css/theme.css
|
|
├── generators/swift_generator.py → app/design-tokens/swift/Colors.swift
|
|
└── generators/kotlin_generator.py → app/design-tokens/kotlin/colors.xml
|
|
```
|
|
|
|
---
|
|
|
|
## Структура tokens.json
|
|
|
|
```json
|
|
{
|
|
"version": "1.0.0",
|
|
"project": "VoIdea",
|
|
"themes": ["system", "dark", "light"],
|
|
|
|
"colors": {
|
|
"primary": {
|
|
"500": "#6366F1",
|
|
"600": "#4F46E5",
|
|
"default": "#6366F1",
|
|
"hover": "#4F46E5"
|
|
},
|
|
"background": {
|
|
"system": "auto",
|
|
"dark": "#0F172A",
|
|
"light": "#FFFFFF"
|
|
},
|
|
"semantic": {
|
|
"error": "#EF4444",
|
|
"warning": "#F59E0B",
|
|
"success": "#22C55E",
|
|
"info": "#3B82F6"
|
|
}
|
|
},
|
|
|
|
"typography": {
|
|
"font_family": {
|
|
"primary": "Inter, system-ui, sans-serif"
|
|
},
|
|
"size": {
|
|
"xs": "0.75rem",
|
|
"sm": "0.875rem",
|
|
"base": "1rem"
|
|
}
|
|
},
|
|
|
|
"spacing": {
|
|
"xs": "0.25rem",
|
|
"sm": "0.5rem",
|
|
"md": "1rem",
|
|
"lg": "1.5rem"
|
|
},
|
|
|
|
"border_radius": {
|
|
"sm": "0.25rem",
|
|
"md": "0.5rem",
|
|
"lg": "0.75rem"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Генераторы
|
|
|
|
### CSS Generator
|
|
|
|
```python
|
|
# docs/design-system/generators/css_generator.py
|
|
|
|
def generate(tokens: dict) -> str:
|
|
css = ":root {\n"
|
|
|
|
for category, values in tokens.items():
|
|
if category == "colors":
|
|
for name, value in flatten(values):
|
|
css += f" --color-{name}: {value};\n"
|
|
|
|
elif category == "typography":
|
|
for name, value in flatten(values):
|
|
css += f" --font-{name}: {value};\n"
|
|
|
|
elif category == "spacing":
|
|
for name, value in flatten(values):
|
|
css += f" --spacing-{name}: {value};\n"
|
|
|
|
css += "}\n"
|
|
return css
|
|
```
|
|
|
|
**Выход:** `app/design-tokens/css/theme.css`
|
|
|
|
```css
|
|
:root {
|
|
--color-primary: #6366F1;
|
|
--color-background-dark: #0F172A;
|
|
--font-family-primary: Inter, system-ui, sans-serif;
|
|
--spacing-md: 1rem;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Swift Generator
|
|
|
|
```python
|
|
# docs/design-system/generators/swift_generator.py
|
|
|
|
def generate(tokens: dict) -> str:
|
|
swift = "import SwiftUI\n\n"
|
|
swift += "enum Colors {\n"
|
|
|
|
for name, value in flatten(tokens["colors"]):
|
|
snake_to_camel = to_camel_case(name)
|
|
swift += f' static let {snake_to_camel} = Color(hex: "{value}")\n'
|
|
|
|
swift += "}\n"
|
|
swift += "enum Typography {\n"
|
|
|
|
# ... font generation
|
|
|
|
return swift
|
|
```
|
|
|
|
**Выход:** `app/design-tokens/swift/Colors.swift`
|
|
|
|
```swift
|
|
import SwiftUI
|
|
|
|
enum Colors {
|
|
static let primary = Color(hex: "#6366F1")
|
|
static let backgroundDark = Color(hex: "#0F172A")
|
|
}
|
|
|
|
enum Typography {
|
|
static let fontFamilyPrimary = "Inter"
|
|
static let fontSizeBase: CGFloat = 16
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Kotlin Generator
|
|
|
|
```python
|
|
# docs/design-system/generators/kotlin_generator.py
|
|
|
|
def generate(tokens: dict) -> str:
|
|
xml = '<?xml version="1.0" encoding="utf-8"?>\n'
|
|
xml += '<resources>\n'
|
|
|
|
for name, value in flatten(tokens["colors"]):
|
|
safe_name = name.replace("_", "_")
|
|
xml += f' <color name="{safe_name}">{value}</color>\n'
|
|
|
|
xml += '</resources>\n'
|
|
return xml
|
|
```
|
|
|
|
**Выход:** `app/design-tokens/kotlin/colors.xml`
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<resources>
|
|
<color name="primary">#6366F1</color>
|
|
<color name="background_dark">#0F172A</color>
|
|
</resources>
|
|
```
|
|
|
|
---
|
|
|
|
## Темы
|
|
|
|
### System (Auto)
|
|
|
|
```css
|
|
@media (prefers-color-scheme: dark) {
|
|
:root[data-theme="system"] {
|
|
--color-background: #0F172A;
|
|
--color-text: #F8FAFC;
|
|
}
|
|
}
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
:root[data-theme="system"] {
|
|
--color-background: #FFFFFF;
|
|
--color-text: #0F172A;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Dark / Light
|
|
|
|
```css
|
|
[data-theme="dark"] {
|
|
--color-background: #0F172A;
|
|
--color-text: #F8FAFC;
|
|
}
|
|
|
|
[data-theme="light"] {
|
|
--color-background: #FFFFFF;
|
|
--color-text: #0F172A;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## CI/CD Integration
|
|
|
|
```yaml
|
|
# .github/workflows/design-system.yml
|
|
|
|
on:
|
|
push:
|
|
paths:
|
|
- 'docs/design-system/tokens.json'
|
|
|
|
jobs:
|
|
generate:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Generate CSS
|
|
run: python -m generators css
|
|
|
|
- name: Generate Swift
|
|
run: python -m generators swift
|
|
|
|
- name: Generate Kotlin
|
|
run: python -m generators kotlin
|
|
|
|
- name: Commit
|
|
run: |
|
|
git add app/design-tokens/
|
|
git commit -m "style: regenerate design tokens"
|
|
git push
|
|
```
|
|
|
|
---
|
|
|
|
## Правила использования
|
|
|
|
1. **Никогда не редактировать сгенерированные файлы вручную**
|
|
2. **Все изменения только в tokens.json**
|
|
3. **После изменения tokens.json → запустить генераторы**
|
|
4. **DocAgent обновляет документацию**
|
|
|
|
---
|
|
|
|
## Maintenance
|
|
|
|
### Обновление tokens.json
|
|
|
|
1. Открыть `docs/design-system/tokens.json`
|
|
2. Внести изменения
|
|
3. Запустить `python -m generators all`
|
|
4. Проверить сгенерированные файлы
|
|
5. Зафиксировать в git
|
|
|
|
### Добавление новых token
|
|
|
|
```json
|
|
{
|
|
"new_token": {
|
|
"system": "auto",
|
|
"dark": "#value",
|
|
"light": "#value"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Последствия
|
|
|
|
### Положительные
|
|
|
|
- Единый источник истины
|
|
- Согласованность между платформами
|
|
- Легко добавлять темы
|
|
- Автоматическая генерация
|
|
|
|
### Отрицательные
|
|
|
|
- Дополнительный слой абстракции
|
|
- Требует генерацию при изменениях
|
|
- Разные форматы вывода
|
|
|
|
---
|
|
|
|
## Ответственный
|
|
|
|
**Decision maker:** Frontend team
|
|
**Review date:** При добавлении новой платформы
|
|
|
|
---
|
|
|
|
*Создано: 2026-05-10*
|
|
*Обновляется DocAgent при изменениях* |