@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.
- package/README.en.md +638 -0
- package/README.md +715 -0
- package/config.example.json +45 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +160 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/loader.d.ts +8 -0
- package/dist/core/loader.js +105 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/parser.d.ts +5 -0
- package/dist/core/parser.js +186 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/types.d.ts +83 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/writer.d.ts +6 -0
- package/dist/core/writer.js +124 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/transformers/css.d.ts +2 -0
- package/dist/transformers/css.js +485 -0
- package/dist/transformers/css.js.map +1 -0
- package/dist/transformers/kotlin.d.ts +2 -0
- package/dist/transformers/kotlin.js +445 -0
- package/dist/transformers/kotlin.js.map +1 -0
- package/dist/transformers/swiftui.d.ts +2 -0
- package/dist/transformers/swiftui.js +436 -0
- package/dist/transformers/swiftui.js.map +1 -0
- package/dist/transformers/vue3.d.ts +28 -0
- package/dist/transformers/vue3.js +534 -0
- package/dist/transformers/vue3.js.map +1 -0
- package/dist/utils/color.d.ts +11 -0
- package/dist/utils/color.js +101 -0
- package/dist/utils/color.js.map +1 -0
- package/dist/utils/dimension.d.ts +7 -0
- package/dist/utils/dimension.js +62 -0
- package/dist/utils/dimension.js.map +1 -0
- package/dist/utils/naming.d.ts +13 -0
- package/dist/utils/naming.js +58 -0
- package/dist/utils/naming.js.map +1 -0
- package/package.json +40 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# SXL Studio — Token Transformer
|
|
2
|
+
|
|
3
|
+
A tool for transforming design tokens from JSON (DTCG format) into valid code for three platforms:
|
|
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
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Install dependencies
|
|
15
|
+
npm install
|
|
16
|
+
|
|
17
|
+
# Run with default config
|
|
18
|
+
npx tsx src/cli.ts
|
|
19
|
+
|
|
20
|
+
# Run with specific config
|
|
21
|
+
npx tsx src/cli.ts --config=config.example.json
|
|
22
|
+
|
|
23
|
+
# Generate a default config
|
|
24
|
+
npx tsx src/cli.ts --init
|
|
25
|
+
|
|
26
|
+
# Transform composition → Vue 3
|
|
27
|
+
npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Project Structure
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Transformer/
|
|
36
|
+
├── src/
|
|
37
|
+
│ ├── cli.ts # CLI entry point
|
|
38
|
+
│ ├── core/
|
|
39
|
+
│ │ ├── types.ts # Types and interfaces
|
|
40
|
+
│ │ ├── parser.ts # JSON parsing, alias resolution, math expressions
|
|
41
|
+
│ │ ├── loader.ts # Token loading from files
|
|
42
|
+
│ │ └── writer.ts # Transformation and file writing
|
|
43
|
+
│ ├── transformers/
|
|
44
|
+
│ │ ├── css.ts # CSS Custom Properties generator
|
|
45
|
+
│ │ ├── swiftui.ts # SwiftUI code generator
|
|
46
|
+
│ │ ├── kotlin.ts # Kotlin Compose code generator
|
|
47
|
+
│ │ └── vue3.ts # Composition JSON → Vue 3 SFC
|
|
48
|
+
│ └── utils/
|
|
49
|
+
│ ├── naming.ts # Naming utilities (kebab, camel, pascal)
|
|
50
|
+
│ ├── color.ts # Color parsing and formatting
|
|
51
|
+
│ └── dimension.ts # Dimension parsing and formatting
|
|
52
|
+
├── config.example.json # Config for test tokens
|
|
53
|
+
├── config.admin-ui.json # Config for admin-ui project
|
|
54
|
+
├── config.site-ui.json # Config for site-ui project
|
|
55
|
+
├── project-style/ # Output files (generated)
|
|
56
|
+
└── package.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
The config is a JSON file with the following structure:
|
|
64
|
+
|
|
65
|
+
### Full Schema
|
|
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
|
+
### Field Descriptions
|
|
133
|
+
|
|
134
|
+
#### `source` — Token Source
|
|
135
|
+
|
|
136
|
+
| Field | Type | Default | Description |
|
|
137
|
+
|-------|------|---------|-------------|
|
|
138
|
+
| `tokenDir` | `string` | required | Path to the JSON token directory |
|
|
139
|
+
| `configFile` | `string` | `"config.json"` | Path to plugin config.json (inside tokenDir) |
|
|
140
|
+
| `include` | `string[]` | all `**/*.json` | Glob patterns for files to include |
|
|
141
|
+
| `exclude` | `string[]` | `["config.json"]` | Glob patterns for files to exclude |
|
|
142
|
+
|
|
143
|
+
#### `platforms` — Platform Settings
|
|
144
|
+
|
|
145
|
+
Each platform (`css`, `swiftui`, `kotlin`) is configured independently:
|
|
146
|
+
|
|
147
|
+
| Field | Type | Default | Description |
|
|
148
|
+
|-------|------|---------|-------------|
|
|
149
|
+
| `outputDir` | `string` | required | Output directory for generated files |
|
|
150
|
+
| `prefix` | `string` | `""` | Prefix for variable names |
|
|
151
|
+
| `resolveAliases` | `boolean` | CSS: `false`, Swift/Kotlin: `true` | Whether to resolve aliases to final values |
|
|
152
|
+
| `splitEffects` | `boolean` | `true` | Split effects into separate variables (CSS only) |
|
|
153
|
+
| `showDescriptions` | `boolean` | `true` | Include `$description` as comments in output |
|
|
154
|
+
| `codeSyntax` | `object` | — | Code format settings |
|
|
155
|
+
| `fileMapping` | `array` | — | Rules for merging tokens into files |
|
|
156
|
+
|
|
157
|
+
#### `resolveAliases` — Alias Control
|
|
158
|
+
|
|
159
|
+
The key setting that determines how alias references (`{token.path}`) are handled in composite tokens.
|
|
160
|
+
|
|
161
|
+
**CSS (`resolveAliases: false` — default):**
|
|
162
|
+
```css
|
|
163
|
+
/* Aliases preserved as 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
|
+
/* All values fully resolved */
|
|
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
|
+
- Pure aliases (entire token = reference) → same-scope constant reference
|
|
177
|
+
- Composite tokens with aliased fields → `/// @ref` / `/** @ref */` comments with paths
|
|
178
|
+
|
|
179
|
+
```swift
|
|
180
|
+
/// @ref {color.hex-full}
|
|
181
|
+
static let primary = hexFull // ← direct reference
|
|
182
|
+
|
|
183
|
+
/// @ref {fontFamily.sans}, {fontWeight.bold}, {fontSize.3xl}, {lineHeight.relaxed}
|
|
184
|
+
static let headingXl: Font = .system(size: 30, weight: .bold) // ← values + ref comment
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**SwiftUI/Kotlin (`resolveAliases: true` — default):**
|
|
188
|
+
```swift
|
|
189
|
+
static let primary = Color(red: 1, green: 0.3333, blue: 0, opacity: 1) // ← fully resolved
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `splitEffects` — Effects Splitting (CSS only)
|
|
193
|
+
|
|
194
|
+
Controls how mixed effects (shadow + blur + backdrop-blur) are handled.
|
|
195
|
+
|
|
196
|
+
**`splitEffects: true` (default):**
|
|
197
|
+
|
|
198
|
+
Mixed effects are split into separate variables with suffixes. Each variable maps to a specific CSS property:
|
|
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
|
+
Usage:
|
|
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
|
+
All effects are output as a single variable, shadow part only:
|
|
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` — Token Descriptions
|
|
227
|
+
|
|
228
|
+
Controls whether `$description` from JSON is included as comments. Works on all platforms.
|
|
229
|
+
|
|
230
|
+
**`showDescriptions: true` (default):**
|
|
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` — File Merging Rules
|
|
260
|
+
|
|
261
|
+
Allows combining tokens from multiple JSON files into a single output file.
|
|
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
|
+
| Field | Description |
|
|
276
|
+
|-------|-------------|
|
|
277
|
+
| `sources` | Array of JSON file paths (supports `*` wildcards) |
|
|
278
|
+
| `output` | Output file name (relative to `outputDir`) |
|
|
279
|
+
| `filter.types` | Filter by token type |
|
|
280
|
+
| `filter.paths` | Include only paths starting with specified prefixes |
|
|
281
|
+
| `filter.excludePaths` | Exclude paths starting with specified prefixes |
|
|
282
|
+
|
|
283
|
+
#### `settings` — Global Settings
|
|
284
|
+
|
|
285
|
+
| Field | Type | Default | Description |
|
|
286
|
+
|-------|------|---------|-------------|
|
|
287
|
+
| `remBase` | `number` | `16` | Base value for rem calculations |
|
|
288
|
+
| `verbose` | `boolean` | `false` | Verbose logging output |
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Supported Token Types
|
|
293
|
+
|
|
294
|
+
### Simple Types
|
|
295
|
+
|
|
296
|
+
| Type | 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` | numeric | numeric |
|
|
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
|
+
### Composite Types
|
|
317
|
+
|
|
318
|
+
| Type | 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` | see below | `.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 Handling (CSS)
|
|
334
|
+
|
|
335
|
+
In CSS, different effect types map to different CSS properties. The transformer automatically creates separate variables for each type:
|
|
336
|
+
|
|
337
|
+
### Single Effect Type
|
|
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
|
+
### Mixed Effects (shadow + blur + backdrop-blur)
|
|
351
|
+
|
|
352
|
+
When a token contains multiple effect types, they are split into separate variables with suffixes:
|
|
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
|
+
Usage in 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
|
+
## Aliases in Composite Tokens
|
|
375
|
+
|
|
376
|
+
All composite token types (typography, shadow, border, fill, gradient, effects) support alias references `{token.path}` in any sub-property.
|
|
377
|
+
|
|
378
|
+
### JSON Example
|
|
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 Output (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 Output (resolveAliases: true)
|
|
403
|
+
|
|
404
|
+
```css
|
|
405
|
+
--typography-heading-xl: 700 30px/24px Inter;
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## codeSyntax (Figma)
|
|
411
|
+
|
|
412
|
+
If a token has `$extensions.figma.codeSyntax`, the transformer uses the specified variable names:
|
|
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
|
+
## Math Expressions
|
|
432
|
+
|
|
433
|
+
Tokens support mathematical expressions that are evaluated during transformation:
|
|
434
|
+
|
|
435
|
+
```json
|
|
436
|
+
{
|
|
437
|
+
"spacing": {
|
|
438
|
+
"sm": { "$value": "{spacing.base} * 2" },
|
|
439
|
+
"md": { "$value": "{spacing.base} * 4" }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Supported operations: `+`, `-`, `*`, `/`, `round()`.
|
|
445
|
+
|
|
446
|
+
Expressions are evaluated recursively, including sub-properties of composite tokens.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Usage Examples
|
|
451
|
+
|
|
452
|
+
### Transform All Projects
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
# Test tokens (example)
|
|
456
|
+
npx tsx src/cli.ts --config=config.example.json
|
|
457
|
+
|
|
458
|
+
# Production projects
|
|
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
|
+
### Example Config for a New Project
|
|
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
|
+
The transformer supports converting composition tokens from JSON into ready-to-use Vue 3 Single File Components (`.vue`).
|
|
525
|
+
|
|
526
|
+
### Usage
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
| Parameter | Description |
|
|
533
|
+
|-----------|-------------|
|
|
534
|
+
| `--composition` | Path to the composition JSON file |
|
|
535
|
+
| `--output` | Output directory for the `.vue` file (defaults to source directory) |
|
|
536
|
+
|
|
537
|
+
### Input JSON Format
|
|
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": { "tag": "FRAME", "class": "btn", "children": [...] },
|
|
549
|
+
"componentProperties": { "label": { "type": "TEXT", "layer": "btn-label", "defaultValue": "Button" } },
|
|
550
|
+
"styles": { "btn": { "layoutMode": "HORIZONTAL", "fills": ["{accent.medium}"], ... } },
|
|
551
|
+
"adapters": { "style=accent": { "btn": { "fills": ["{info.medium}"] } } }
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### What Gets Generated
|
|
556
|
+
|
|
557
|
+
A complete Vue 3 SFC with three sections:
|
|
558
|
+
|
|
559
|
+
**`<template>`** — HTML structure from `structure`:
|
|
560
|
+
- `FRAME` → `<div>` / `<button>` (inferred from component name)
|
|
561
|
+
- `TEXT` → `<span>` with prop or slot binding
|
|
562
|
+
- `INSTANCE` → component tag (`<WIcon>`, etc.)
|
|
563
|
+
- `BOOLEAN` properties → `v-if` directives
|
|
564
|
+
- `INSTANCE_SWAP` → dynamic `:name` prop
|
|
565
|
+
|
|
566
|
+
**`<script setup lang="ts">`** — logic:
|
|
567
|
+
- `defineProps<Props>()` with types from `props` and `componentProperties`
|
|
568
|
+
- `withDefaults()` with default values
|
|
569
|
+
- `useCssModule()` for CSS Modules
|
|
570
|
+
- `computed()` for variant/size classes with exhaustive `Record<>` mapping
|
|
571
|
+
- `style` prop auto-renamed to `variant` (Vue reserved word conflict)
|
|
572
|
+
- `state=disabled` → separate `disabled` boolean prop
|
|
573
|
+
|
|
574
|
+
**`<style module>`** — CSS:
|
|
575
|
+
- Base styles from `styles` with Figma → CSS property mapping
|
|
576
|
+
- Modifier classes from `adapters` (`.btn--accent`, `.btn--sm`, etc.)
|
|
577
|
+
- `state=hover/active/focus` → CSS pseudo-classes (`:hover`, `:active`, `:focus`)
|
|
578
|
+
- `state=disabled` → `.btn--disabled` + `:disabled`
|
|
579
|
+
- Token references `{accent.medium}` → `var(--accent-medium)`
|
|
580
|
+
- TEXT/INSTANCE layers: `fills` → `color` (not `background-color`)
|
|
581
|
+
|
|
582
|
+
### Figma → CSS Property Mapping
|
|
583
|
+
|
|
584
|
+
| Figma | CSS |
|
|
585
|
+
|-------|-----|
|
|
586
|
+
| `layoutMode: "HORIZONTAL"` | `display: flex` |
|
|
587
|
+
| `layoutMode: "VERTICAL"` | `display: flex; flex-direction: column` |
|
|
588
|
+
| `primaryAxisAlignItems` | `justify-content` |
|
|
589
|
+
| `counterAxisAlignItems` | `align-items` |
|
|
590
|
+
| `layoutSizingHorizontal: "HUG"` | `width: fit-content` |
|
|
591
|
+
| `layoutSizingHorizontal: "FILL"` | `flex: 1` |
|
|
592
|
+
| `itemSpacing` | `gap` |
|
|
593
|
+
| `padding*` | `padding` (shorthand) |
|
|
594
|
+
| `cornerRadius` | `border-radius` |
|
|
595
|
+
| `fills: ["{token}"]` | `background-color: var(--token)` / `color: var(--token)` |
|
|
596
|
+
| `strokeWeight` + `strokes` | `border` |
|
|
597
|
+
| `opacity` | `opacity` |
|
|
598
|
+
| `fontSize` | `font-size` |
|
|
599
|
+
| `fontWeight` | `font-weight` (name → number mapping) |
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Prefix Reference
|
|
604
|
+
|
|
605
|
+
| Platform | Prefix | Naming Result |
|
|
606
|
+
|----------|--------|---------------|
|
|
607
|
+
| CSS | `""` | `--color-primary` |
|
|
608
|
+
| CSS | `"ds"` | `--ds-color-primary` |
|
|
609
|
+
| SwiftUI | `""` | `Color.colorPrimary`, `enum Spacing` |
|
|
610
|
+
| SwiftUI | `"DS"` | `Color.dsColorPrimary`, `enum DSSpacing` |
|
|
611
|
+
| Kotlin | `""` | `DSColors.ColorPrimary`, `DSSpacing.SpacingXs` |
|
|
612
|
+
| Kotlin | `"App"` | `AppColors.ColorPrimary`, `AppSpacing.SpacingXs` |
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Troubleshooting
|
|
617
|
+
|
|
618
|
+
### Config file not found
|
|
619
|
+
```
|
|
620
|
+
Config file not found: ...
|
|
621
|
+
Run with --init to generate a default config.
|
|
622
|
+
```
|
|
623
|
+
Use `--config=path/to/file.json` or `--init` to generate one.
|
|
624
|
+
|
|
625
|
+
### Tokens missing from output
|
|
626
|
+
Check `fileMapping.sources` — paths are **relative to tokenDir**.
|
|
627
|
+
|
|
628
|
+
### `var()` not appearing in CSS
|
|
629
|
+
Make sure `"resolveAliases": false` is set for the `css` platform.
|
|
630
|
+
|
|
631
|
+
### Aliases not resolving
|
|
632
|
+
Ensure the source token exists in the included files (`include` / `sources`).
|
|
633
|
+
|
|
634
|
+
### Effects not splitting into separate variables
|
|
635
|
+
Make sure `"splitEffects": true` is set for the `css` platform. Default is `true`.
|
|
636
|
+
|
|
637
|
+
### Descriptions ($description) not showing
|
|
638
|
+
Make sure `"showDescriptions": true` is set for the desired platform. Default is `true`.
|