@sxl-studio/token-transformer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.en.md +638 -0
  2. package/README.md +715 -0
  3. package/config.example.json +45 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +160 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/loader.d.ts +8 -0
  8. package/dist/core/loader.js +105 -0
  9. package/dist/core/loader.js.map +1 -0
  10. package/dist/core/parser.d.ts +5 -0
  11. package/dist/core/parser.js +186 -0
  12. package/dist/core/parser.js.map +1 -0
  13. package/dist/core/types.d.ts +83 -0
  14. package/dist/core/types.js +2 -0
  15. package/dist/core/types.js.map +1 -0
  16. package/dist/core/writer.d.ts +6 -0
  17. package/dist/core/writer.js +124 -0
  18. package/dist/core/writer.js.map +1 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/transformers/css.d.ts +2 -0
  23. package/dist/transformers/css.js +485 -0
  24. package/dist/transformers/css.js.map +1 -0
  25. package/dist/transformers/kotlin.d.ts +2 -0
  26. package/dist/transformers/kotlin.js +445 -0
  27. package/dist/transformers/kotlin.js.map +1 -0
  28. package/dist/transformers/swiftui.d.ts +2 -0
  29. package/dist/transformers/swiftui.js +436 -0
  30. package/dist/transformers/swiftui.js.map +1 -0
  31. package/dist/transformers/vue3.d.ts +28 -0
  32. package/dist/transformers/vue3.js +534 -0
  33. package/dist/transformers/vue3.js.map +1 -0
  34. package/dist/utils/color.d.ts +11 -0
  35. package/dist/utils/color.js +101 -0
  36. package/dist/utils/color.js.map +1 -0
  37. package/dist/utils/dimension.d.ts +7 -0
  38. package/dist/utils/dimension.js +62 -0
  39. package/dist/utils/dimension.js.map +1 -0
  40. package/dist/utils/naming.d.ts +13 -0
  41. package/dist/utils/naming.js +58 -0
  42. package/dist/utils/naming.js.map +1 -0
  43. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,715 @@
1
+ # SXL Studio — Token Transformer
2
+
3
+ Инструмент для трансформации дизайн-токенов из JSON (DTCG-формат) в валидный код для трёх платформ:
4
+ - **Web** — CSS Custom Properties
5
+ - **iOS** — SwiftUI
6
+ - **Android** — Kotlin Compose
7
+ - **Vue 3** — Composition JSON → Vue 3 SFC (`.vue`)
8
+
9
+ ---
10
+
11
+ ## Быстрый старт
12
+
13
+ ```bash
14
+ # Установка зависимостей
15
+ npm install
16
+
17
+ # Запуск с конфигом по умолчанию
18
+ npx tsx src/cli.ts
19
+
20
+ # Запуск с указанием конфига
21
+ npx tsx src/cli.ts --config=config.example.json
22
+
23
+ # Генерация конфига по умолчанию
24
+ npx tsx src/cli.ts --init
25
+
26
+ # Трансформация composition → Vue 3
27
+ npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Структура проекта
33
+
34
+ ```
35
+ Transformer/
36
+ ├── src/
37
+ │ ├── cli.ts # Точка входа CLI
38
+ │ ├── core/
39
+ │ │ ├── types.ts # Типы и интерфейсы
40
+ │ │ ├── parser.ts # Парсинг JSON, резолв алиасов, мат. выражения
41
+ │ │ ├── loader.ts # Загрузка токенов из файлов
42
+ │ │ └── writer.ts # Трансформация и запись файлов
43
+ │ ├── transformers/
44
+ │ │ ├── css.ts # Генерация CSS Custom Properties
45
+ │ │ ├── swiftui.ts # Генерация SwiftUI-кода
46
+ │ │ ├── kotlin.ts # Генерация Kotlin Compose-кода
47
+ │ │ └── vue3.ts # Composition JSON → Vue 3 SFC
48
+ │ └── utils/
49
+ │ ├── naming.ts # Утилиты именования (kebab, camel, pascal)
50
+ │ ├── color.ts # Парсинг и форматирование цветов
51
+ │ └── dimension.ts # Парсинг и форматирование размеров
52
+ ├── config.example.json # Конфиг для тестовых токенов
53
+ ├── config.admin-ui.json # Конфиг для admin-ui проекта
54
+ ├── config.site-ui.json # Конфиг для site-ui проекта
55
+ ├── project-style/ # Выходные файлы (генерируется)
56
+ └── package.json
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Конфигурация
62
+
63
+ Конфиг — JSON-файл со следующей структурой:
64
+
65
+ ### Полная схема
66
+
67
+ ```json
68
+ {
69
+ "source": {
70
+ "tokenDir": "../Plugin/tokens",
71
+ "configFile": "config.json",
72
+ "include": ["example/*.json", "core/*.json"],
73
+ "exclude": ["config.json", "**/diff-id*.json", "**/composition*"]
74
+ },
75
+ "platforms": {
76
+ "css": {
77
+ "outputDir": "./project-style/example",
78
+ "prefix": "ds",
79
+ "resolveAliases": false,
80
+ "splitEffects": true,
81
+ "showDescriptions": true,
82
+ "codeSyntax": {
83
+ "colorFormat": "hex",
84
+ "prefix": "ds"
85
+ },
86
+ "fileMapping": [
87
+ {
88
+ "sources": ["example/*.json"],
89
+ "output": "all-tokens.css",
90
+ "filter": {
91
+ "types": ["color", "dimension"],
92
+ "paths": ["color.", "spacing."],
93
+ "excludePaths": ["color.internal."]
94
+ }
95
+ }
96
+ ]
97
+ },
98
+ "swiftui": {
99
+ "outputDir": "./project-style/example",
100
+ "prefix": "DS",
101
+ "resolveAliases": true,
102
+ "showDescriptions": true,
103
+ "fileMapping": [
104
+ {
105
+ "sources": ["example/*.json"],
106
+ "output": "AllTokens.swift"
107
+ }
108
+ ]
109
+ },
110
+ "kotlin": {
111
+ "outputDir": "./project-style/example",
112
+ "prefix": "DS",
113
+ "resolveAliases": true,
114
+ "showDescriptions": true,
115
+ "fileMapping": [
116
+ {
117
+ "sources": ["example/*.json"],
118
+ "output": "AllTokens.kt"
119
+ }
120
+ ]
121
+ }
122
+ },
123
+ "settings": {
124
+ "remBase": 16,
125
+ "verbose": false
126
+ }
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ### Описание полей конфига
133
+
134
+ #### `source` — Источник токенов
135
+
136
+ | Поле | Тип | По умолчанию | Описание |
137
+ |------|-----|-------------|----------|
138
+ | `tokenDir` | `string` | обязательный | Путь к папке с JSON-токенами |
139
+ | `configFile` | `string` | `"config.json"` | Путь к config.json плагина (внутри tokenDir) |
140
+ | `include` | `string[]` | все `**/*.json` | Glob-паттерны файлов для включения |
141
+ | `exclude` | `string[]` | `["config.json"]` | Glob-паттерны файлов для исключения |
142
+
143
+ #### `platforms` — Настройки платформ
144
+
145
+ Каждая платформа (`css`, `swiftui`, `kotlin`) настраивается независимо:
146
+
147
+ | Поле | Тип | По умолчанию | Описание |
148
+ |------|-----|-------------|----------|
149
+ | `outputDir` | `string` | обязательный | Папка для выходных файлов |
150
+ | `prefix` | `string` | `""` | Префикс для имён переменных |
151
+ | `resolveAliases` | `boolean` | CSS: `false`, Swift/Kotlin: `true` | Резолвить ли алиасы в финальные значения |
152
+ | `splitEffects` | `boolean` | `true` | Разделять ли эффекты на отдельные переменные (только CSS) |
153
+ | `showDescriptions` | `boolean` | `true` | Включать ли `$description` как комментарии в код |
154
+ | `codeSyntax` | `object` | — | Настройки формата кода |
155
+ | `fileMapping` | `array` | — | Правила объединения токенов в файлы |
156
+
157
+ #### `resolveAliases` — Управление алиасами
158
+
159
+ Ключевая настройка, определяющая как обрабатываются алиасные ссылки (`{token.path}`) в составных токенах.
160
+
161
+ **CSS (`resolveAliases: false` — по умолчанию):**
162
+ ```css
163
+ /* Алиасы сохраняются как var() */
164
+ --typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
165
+ --shadow-sm: var(--number-fx-offset-xs) var(--number-fx-offset-none) var(--number-fx-blur-xs) var(--number-fx-offset-none) var(--color-hex-alpha);
166
+ ```
167
+
168
+ **CSS (`resolveAliases: true`):**
169
+ ```css
170
+ /* Все значения полностью разрешены */
171
+ --ds-typography-heading-xl: 700 30px/24px Inter;
172
+ --ds-shadow-sm: 2px 0px 8px 0px rgba(255, 85, 0, 0.502);
173
+ ```
174
+
175
+ **SwiftUI/Kotlin (`resolveAliases: false`):**
176
+ - Чистые алиасы (весь токен = ссылка) → ссылка на константу в том же scope
177
+ - Составные токены с алиасными полями → комментарий `/// @ref` / `/** @ref */` с путями
178
+
179
+ ```swift
180
+ /// @ref {color.hex-full}
181
+ static let primary = hexFull // ← прямая ссылка
182
+
183
+ /// @ref {fontFamily.sans}, {fontWeight.bold}, {fontSize.3xl}, {lineHeight.relaxed}
184
+ static let headingXl: Font = .system(size: 30, weight: .bold) // ← значения + ref-комментарий
185
+ ```
186
+
187
+ **SwiftUI/Kotlin (`resolveAliases: true` — по умолчанию):**
188
+ ```swift
189
+ static let primary = Color(red: 1, green: 0.3333, blue: 0, opacity: 1) // ← полностью разрешено
190
+ ```
191
+
192
+ #### `splitEffects` — Разделение эффектов (только CSS)
193
+
194
+ Управляет поведением смешанных эффектов (shadow + blur + backdrop-blur).
195
+
196
+ **`splitEffects: true` (по умолчанию):**
197
+
198
+ Смешанные эффекты разделяются на отдельные переменные с суффиксами. Каждая переменная соответствует конкретному CSS-свойству:
199
+
200
+ ```css
201
+ /* box-shadow */
202
+ --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
203
+ /* filter */
204
+ --effects-full-mix-blur: blur(2px);
205
+ /* backdrop-filter */
206
+ --effects-full-mix-backdrop-blur: blur(20px);
207
+ ```
208
+
209
+ Использование:
210
+ ```css
211
+ .card {
212
+ box-shadow: var(--effects-full-mix);
213
+ filter: var(--effects-full-mix-blur);
214
+ backdrop-filter: var(--effects-full-mix-backdrop-blur);
215
+ }
216
+ ```
217
+
218
+ **`splitEffects: false`:**
219
+
220
+ Все эффекты выводятся в одну переменную, только shadow-часть:
221
+
222
+ ```css
223
+ --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
224
+ ```
225
+
226
+ #### `showDescriptions` — Описания токенов
227
+
228
+ Управляет выводом `$description` из JSON в качестве комментариев. Работает на всех платформах.
229
+
230
+ **`showDescriptions: true` (по умолчанию):**
231
+ ```css
232
+ /* Heading XL — all values raw */
233
+ --typography-heading-xl: 700 30px/24px Inter;
234
+ ```
235
+
236
+ ```swift
237
+ /// Heading XL — all values raw
238
+ static let headingXl: Font = .system(size: 30, weight: .bold)
239
+ ```
240
+
241
+ ```kotlin
242
+ /** Heading XL — all values raw */
243
+ val HeadingXl = TextStyle(...)
244
+ ```
245
+
246
+ **`showDescriptions: false`:**
247
+ ```css
248
+ --typography-heading-xl: 700 30px/24px Inter;
249
+ ```
250
+
251
+ ```swift
252
+ static let headingXl: Font = .system(size: 30, weight: .bold)
253
+ ```
254
+
255
+ ```kotlin
256
+ val HeadingXl = TextStyle(...)
257
+ ```
258
+
259
+ #### `fileMapping` — Правила объединения файлов
260
+
261
+ Позволяет собрать токены из нескольких JSON-файлов в один выходной файл.
262
+
263
+ ```json
264
+ {
265
+ "sources": ["core/palette.json", "projects/aui/colors.json", "projects/modes/themes/light.json"],
266
+ "output": "core.css",
267
+ "filter": {
268
+ "types": ["color", "dimension"],
269
+ "paths": ["color.primary"],
270
+ "excludePaths": ["color.internal"]
271
+ }
272
+ }
273
+ ```
274
+
275
+ | Поле | Описание |
276
+ |------|----------|
277
+ | `sources` | Массив путей к JSON-файлам (поддерживает `*` wildcards) |
278
+ | `output` | Имя выходного файла (относительно `outputDir`) |
279
+ | `filter.types` | Фильтр по типу токена |
280
+ | `filter.paths` | Включить только пути начинающиеся с указанных |
281
+ | `filter.excludePaths` | Исключить пути начинающиеся с указанных |
282
+
283
+ #### `settings` — Глобальные настройки
284
+
285
+ | Поле | Тип | По умолчанию | Описание |
286
+ |------|-----|-------------|----------|
287
+ | `remBase` | `number` | `16` | Базовое значение для вычисления rem |
288
+ | `verbose` | `boolean` | `false` | Подробный вывод логов |
289
+
290
+ ---
291
+
292
+ ## Поддерживаемые типы токенов
293
+
294
+ ### Простые типы
295
+
296
+ | Тип | CSS | SwiftUI | Kotlin |
297
+ |-----|-----|---------|--------|
298
+ | `color` | `#hex` / `rgba()` | `Color(red:green:blue:opacity:)` | `Color(0xAARRGGBB)` |
299
+ | `dimension` | `16px` / `1rem` | `CGFloat` | `Dp` |
300
+ | `spacing`, `sizing` | `16px` | `CGFloat` | `Dp` |
301
+ | `borderRadius`, `borderWidth` | `4px` | `CGFloat` | `Dp` |
302
+ | `opacity` | `0.5` | `Double` | `Float` |
303
+ | `number` | `42` | числовое значение | числовое значение |
304
+ | `fontFamily` | `"Inter"` | `String` | `String` |
305
+ | `fontWeight` | `700` | `Font.Weight` | `FontWeight` |
306
+ | `fontSize`, `lineHeight` | `16px` | `CGFloat` | `TextUnit (sp)` |
307
+ | `letterSpacing` | `0.5px` | `CGFloat` | `TextUnit (sp)` |
308
+ | `duration` | `200ms` | `String` | `String` |
309
+ | `cubicBezier` | `cubic-bezier(...)` | `String` | `String` |
310
+ | `boolean` | `true` | `Bool` | `Boolean` |
311
+ | `text`, `string` | `"value"` | `String` | `String` |
312
+ | `textCase` | `uppercase` | `String` | `String` |
313
+ | `textDecoration` | `underline` | `String` | `String` |
314
+ | `strokeStyle` | `dashed /* ... */` | `String` | `String` |
315
+
316
+ ### Составные типы
317
+
318
+ | Тип | CSS | SwiftUI | Kotlin |
319
+ |-----|-----|---------|--------|
320
+ | `typography` | `700 16px/24px Inter` | `Font.system(size:weight:)` | `TextStyle(...)` |
321
+ | `shadow` | `0 4px 8px rgba(...)` | `.shadow(color:radius:x:y:)` | `elevation (Dp)` |
322
+ | `border` | `1px solid #000` | `(color:width:style:)` | `BorderStroke(...)` |
323
+ | `fill` | `#hex` / `gradient(...)` | `Color(...)` / `Gradient(...)` | `Color(...)` / `Brush(...)` |
324
+ | `gradient` | `linear-gradient(...)` | `LinearGradient(...)` | `Brush.linearGradient(...)` |
325
+ | `effects` | см. ниже | `.shadow(...)` | `elevation (Dp)` |
326
+ | `blur` | `blur(4px)` | `.blur(radius:)` | `Modifier.blur(...)` |
327
+ | `backdrop-blur` | `blur(16px)` | `.blur(radius:)` | `Modifier.blur(...)` |
328
+ | `transition` | `all 200ms ease` | `String` | `String` |
329
+ | `grid` | `repeat(12, 1fr)` | `String` | `String` |
330
+
331
+ ---
332
+
333
+ ## Обработка Effects (CSS)
334
+
335
+ В CSS разные типы эффектов используют разные CSS-свойства. Трансформер автоматически создаёт отдельные переменные для каждого типа:
336
+
337
+ ### Один тип эффекта
338
+
339
+ ```css
340
+ /* box-shadow */
341
+ --effects-card: 0 4px 8px rgba(0,0,0,0.1);
342
+
343
+ /* filter */
344
+ --blur-sm: blur(4px);
345
+
346
+ /* backdrop-filter */
347
+ --backdrop-blur-md: blur(12px);
348
+ ```
349
+
350
+ ### Смешанные эффекты (shadow + blur + backdrop-blur)
351
+
352
+ Когда токен содержит несколько типов эффектов, они разделяются на отдельные переменные с суффиксами:
353
+
354
+ ```css
355
+ /* box-shadow */
356
+ --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
357
+ /* filter */
358
+ --effects-full-mix-blur: blur(2px);
359
+ /* backdrop-filter */
360
+ --effects-full-mix-backdrop-blur: blur(20px);
361
+ ```
362
+
363
+ Использование в CSS:
364
+ ```css
365
+ .card {
366
+ box-shadow: var(--effects-full-mix);
367
+ filter: var(--effects-full-mix-blur);
368
+ backdrop-filter: var(--effects-full-mix-backdrop-blur);
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Алиасы в составных токенах
375
+
376
+ Все составные токены (typography, shadow, border, fill, gradient, effects) поддерживают алиасные ссылки `{token.path}` в любом подсвойстве.
377
+
378
+ ### Пример JSON
379
+
380
+ ```json
381
+ {
382
+ "heading": {
383
+ "xl": {
384
+ "$type": "typography",
385
+ "$value": {
386
+ "fontFamily": "{fontFamily.sans}",
387
+ "fontWeight": "{fontWeight.bold}",
388
+ "fontSize": "{fontSize.3xl}",
389
+ "lineHeight": "{lineHeight.relaxed}"
390
+ }
391
+ }
392
+ }
393
+ }
394
+ ```
395
+
396
+ ### Результат CSS (resolveAliases: false)
397
+
398
+ ```css
399
+ --typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
400
+ ```
401
+
402
+ ### Результат CSS (resolveAliases: true)
403
+
404
+ ```css
405
+ --typography-heading-xl: 700 30px/24px Inter;
406
+ ```
407
+
408
+ ---
409
+
410
+ ## codeSyntax (Figma)
411
+
412
+ Если токен содержит `$extensions.figma.codeSyntax`, трансформер использует указанные имена переменных:
413
+
414
+ ```json
415
+ {
416
+ "primary": {
417
+ "$value": "#0066FF",
418
+ "$extensions": {
419
+ "figma.codeSyntax": {
420
+ "Web": "var(--color-primary)",
421
+ "iOS": "Color.primary",
422
+ "Android": "@color/primary"
423
+ }
424
+ }
425
+ }
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ## Математические выражения
432
+
433
+ Токены поддерживают математические выражения, которые вычисляются при трансформации:
434
+
435
+ ```json
436
+ {
437
+ "spacing": {
438
+ "sm": { "$value": "{spacing.base} * 2" },
439
+ "md": { "$value": "{spacing.base} * 4" }
440
+ }
441
+ }
442
+ ```
443
+
444
+ Поддерживаемые операции: `+`, `-`, `*`, `/`, `round()`.
445
+
446
+ Выражения вычисляются рекурсивно, включая подсвойства составных токенов.
447
+
448
+ ---
449
+
450
+ ## Примеры запуска
451
+
452
+ ### Трансформация всех проектов
453
+
454
+ ```bash
455
+ # Тестовые токены (example)
456
+ npx tsx src/cli.ts --config=config.example.json
457
+
458
+ # Боевые проекты
459
+ npx tsx src/cli.ts --config=config.admin-ui.json
460
+ npx tsx src/cli.ts --config=config.site-ui.json
461
+ ```
462
+
463
+ ### Пример конфига для нового проекта
464
+
465
+ ```json
466
+ {
467
+ "source": {
468
+ "tokenDir": "./path/to/tokens",
469
+ "exclude": ["config.json"]
470
+ },
471
+ "platforms": {
472
+ "css": {
473
+ "outputDir": "./dist/css",
474
+ "prefix": "my",
475
+ "resolveAliases": false,
476
+ "splitEffects": true,
477
+ "showDescriptions": true,
478
+ "fileMapping": [
479
+ {
480
+ "sources": ["core/*.json", "themes/light.json"],
481
+ "output": "core.css"
482
+ },
483
+ {
484
+ "sources": ["themes/dark.json"],
485
+ "output": "themes/dark.css"
486
+ }
487
+ ]
488
+ },
489
+ "swiftui": {
490
+ "outputDir": "./dist/ios",
491
+ "prefix": "My",
492
+ "resolveAliases": false,
493
+ "showDescriptions": true,
494
+ "fileMapping": [
495
+ {
496
+ "sources": ["core/*.json", "themes/light.json"],
497
+ "output": "MyTokens.swift"
498
+ }
499
+ ]
500
+ },
501
+ "kotlin": {
502
+ "outputDir": "./dist/android",
503
+ "prefix": "My",
504
+ "resolveAliases": true,
505
+ "showDescriptions": false,
506
+ "fileMapping": [
507
+ {
508
+ "sources": ["core/*.json", "themes/light.json"],
509
+ "output": "MyTokens.kt"
510
+ }
511
+ ]
512
+ }
513
+ },
514
+ "settings": {
515
+ "remBase": 16
516
+ }
517
+ }
518
+ ```
519
+
520
+ ---
521
+
522
+ ## Composition → Vue 3 (SFC)
523
+
524
+ Трансформер поддерживает преобразование composition-токенов из JSON в готовые Vue 3 Single File Components (`.vue`).
525
+
526
+ ### Запуск
527
+
528
+ ```bash
529
+ npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
530
+ ```
531
+
532
+ | Параметр | Описание |
533
+ |----------|----------|
534
+ | `--composition` | Путь к JSON-файлу с composition-токеном |
535
+ | `--output` | Папка для выходного `.vue` файла (по умолчанию — рядом с исходником) |
536
+
537
+ ### Формат входного JSON
538
+
539
+ ```json
540
+ {
541
+ "$type": "composition",
542
+ "name": "WButton",
543
+ "props": {
544
+ "state": ["default", "hover", "active", "disabled"],
545
+ "style": ["accent", "secondary", "tertiary"],
546
+ "size": ["sm", "md", "lg"]
547
+ },
548
+ "structure": {
549
+ "tag": "FRAME", "class": "btn",
550
+ "children": [
551
+ { "tag": "INSTANCE", "class": "btn-icon-left", "ref": { "component": "icon-name" } },
552
+ { "tag": "TEXT", "class": "btn-label", "content": "Button" }
553
+ ]
554
+ },
555
+ "componentProperties": {
556
+ "label": { "type": "TEXT", "layer": "btn-label", "defaultValue": "Button" },
557
+ "isIconLeft": { "type": "BOOLEAN", "layer": "btn-icon-left", "defaultValue": false }
558
+ },
559
+ "styles": { "btn": { "layoutMode": "HORIZONTAL", "fills": ["{accent.medium}"], ... } },
560
+ "adapters": {
561
+ "style=accent": { "btn": { "fills": ["{info.medium}"] } },
562
+ "size=sm": { "btn": { "paddingLeft": "12", ... } },
563
+ "state=hover": { "btn": { "opacity": "0.88" } }
564
+ }
565
+ }
566
+ ```
567
+
568
+ ### Что генерируется
569
+
570
+ Готовый Vue 3 SFC с тремя секциями:
571
+
572
+ **`<template>`** — HTML-структура из `structure`:
573
+ - `FRAME` → `<div>` / `<button>` (определяется по имени компонента)
574
+ - `TEXT` → `<span>` с привязкой к props или слотам
575
+ - `INSTANCE` → компонентный тег (`<WIcon>`, и т.д.)
576
+ - `BOOLEAN` свойства → `v-if` директивы
577
+ - `INSTANCE_SWAP` → динамический `:name` prop
578
+
579
+ **`<script setup lang="ts">`** — логика:
580
+ - `defineProps<Props>()` с типами из `props` и `componentProperties`
581
+ - `withDefaults()` со значениями по умолчанию
582
+ - `useCssModule()` для CSS Modules
583
+ - `computed()` для variant/size классов с exhaustive `Record<>` маппингом
584
+ - Prop `style` автоматически переименовывается в `variant` (конфликт с Vue)
585
+ - `state=disabled` → отдельный `disabled` boolean prop
586
+
587
+ **`<style module>`** — CSS:
588
+ - Базовые стили из `styles` с маппингом Figma → CSS
589
+ - Modifier-классы из `adapters` (`.btn--accent`, `.btn--sm`, и т.д.)
590
+ - `state=hover/active/focus` → CSS псевдоклассы (`:hover`, `:active`, `:focus`)
591
+ - `state=disabled` → `.btn--disabled` + `:disabled`
592
+ - Токен-ссылки `{accent.medium}` → `var(--accent-medium)`
593
+ - TEXT/INSTANCE слои: `fills` → `color` (не `background-color`)
594
+
595
+ ### Маппинг Figma-свойств → CSS
596
+
597
+ | Figma | CSS |
598
+ |-------|-----|
599
+ | `layoutMode: "HORIZONTAL"` | `display: flex` |
600
+ | `layoutMode: "VERTICAL"` | `display: flex; flex-direction: column` |
601
+ | `primaryAxisAlignItems` | `justify-content` |
602
+ | `counterAxisAlignItems` | `align-items` |
603
+ | `layoutSizingHorizontal: "HUG"` | `width: fit-content` |
604
+ | `layoutSizingHorizontal: "FILL"` | `flex: 1` |
605
+ | `itemSpacing` | `gap` |
606
+ | `padding*` | `padding` (shorthand) |
607
+ | `cornerRadius` | `border-radius` |
608
+ | `fills: ["{token}"]` | `background-color: var(--token)` / `color: var(--token)` |
609
+ | `strokeWeight` + `strokes` | `border` |
610
+ | `opacity` | `opacity` |
611
+ | `clipsContent` | `overflow: hidden` |
612
+ | `fontSize` | `font-size` |
613
+ | `fontFamily` | `font-family` |
614
+ | `fontWeight` | `font-weight` (с маппингом имён → числа) |
615
+ | `textAlignHorizontal` | `text-align` |
616
+
617
+ ### Пример результата
618
+
619
+ ```vue
620
+ <template>
621
+ <button :class="[$style['btn'], variantClass, sizeClass, { [$style['btn--disabled']]: disabled }]"
622
+ :disabled="disabled || undefined">
623
+ <WIcon v-if="isIconLeft" :class="$style['btn-icon-left']" :name="iconLeft" />
624
+ <span :class="$style['btn-label']">{{ label }}</span>
625
+ </button>
626
+ </template>
627
+
628
+ <script setup lang="ts">
629
+ import { computed, useCssModule } from 'vue'
630
+
631
+ type Props = {
632
+ variant?: 'accent' | 'secondary' | 'tertiary'
633
+ size?: 'sm' | 'md' | 'lg'
634
+ label?: string
635
+ isIconLeft?: boolean
636
+ iconLeft?: string
637
+ disabled?: boolean
638
+ }
639
+
640
+ const props = withDefaults(defineProps<Props>(), {
641
+ variant: 'accent',
642
+ size: 'sm',
643
+ label: 'Button',
644
+ isIconLeft: false,
645
+ iconLeft: '',
646
+ disabled: false,
647
+ })
648
+
649
+ const $style = useCssModule()
650
+
651
+ const variantClass = computed(() => {
652
+ const map: Record<NonNullable<Props['variant']>, string> = {
653
+ 'accent': $style['btn--accent'],
654
+ 'secondary': $style['btn--secondary'],
655
+ 'tertiary': $style['btn--tertiary'],
656
+ }
657
+ return map[props.variant ?? 'accent']
658
+ })
659
+ </script>
660
+
661
+ <style module>
662
+ .btn {
663
+ display: flex;
664
+ justify-content: center;
665
+ align-items: center;
666
+ gap: 8px;
667
+ padding: 8px 16px;
668
+ border-radius: 8px;
669
+ background-color: var(--accent-medium);
670
+ }
671
+
672
+ .btn--accent { background-color: var(--info-medium); }
673
+ .btn:hover { opacity: 0.88; }
674
+ .btn--disabled, .btn:disabled { opacity: 0.4; pointer-events: none; }
675
+ </style>
676
+ ```
677
+
678
+ ---
679
+
680
+ ## Справочник по настройке prefix
681
+
682
+ | Платформа | prefix | Результат именования |
683
+ |-----------|--------|---------------------|
684
+ | CSS | `""` | `--color-primary` |
685
+ | CSS | `"ds"` | `--ds-color-primary` |
686
+ | SwiftUI | `""` | `Color.colorPrimary`, `enum Spacing` |
687
+ | SwiftUI | `"DS"` | `Color.dsColorPrimary`, `enum DSSpacing` |
688
+ | Kotlin | `""` | `DSColors.ColorPrimary`, `DSSpacing.SpacingXs` |
689
+ | Kotlin | `"App"` | `AppColors.ColorPrimary`, `AppSpacing.SpacingXs` |
690
+
691
+ ---
692
+
693
+ ## Troubleshooting
694
+
695
+ ### Файл конфига не найден
696
+ ```
697
+ Config file not found: ...
698
+ Run with --init to generate a default config.
699
+ ```
700
+ Используйте `--config=путь/к/файлу.json` или `--init` для генерации.
701
+
702
+ ### Токены не попадают в выходной файл
703
+ Проверьте `fileMapping.sources` — пути указываются **относительно tokenDir**.
704
+
705
+ ### `var()` не появляются в CSS
706
+ Убедитесь что `"resolveAliases": false` установлен для платформы `css`.
707
+
708
+ ### Алиасы не разрешаются
709
+ Убедитесь что токен-источник существует в подключённых файлах (`include` / `sources`).
710
+
711
+ ### Эффекты не разделяются на отдельные переменные
712
+ Убедитесь что `"splitEffects": true` установлен для платформы `css`. По умолчанию `true`.
713
+
714
+ ### Описания ($description) не отображаются
715
+ Убедитесь что `"showDescriptions": true` установлен для нужной платформы. По умолчанию `true`.