Стандартна ціль експорту figmascope — Jetpack Compose. Це не випадково: модель компонування Compose (Column, Row, Box, Modifier) тісно відповідає видам вузлів IR (stack, overlay, absolute, leaf). Вертикальний stack у Figma — це Column у Compose. Трансляція механічна, що робить її добре придатною для кодогенерації агентами.
Це покрокове керівництво детально розглядає маппінг IR → Compose, показує реальний JSON-фрагмент із відповідним Composable і пояснює шар маппінгу токенів.
Чому Compose є ціллю за замовчуванням
Три структурні причини:
- Auto-layout ↔ Column/Row. Авто-layout фрейми Figma (переважна більшість сучасних дизайнів) експортуються як вузли
kind: "stack". Stack-вузли мають властивістьaxis—verticalвідповідаєColumn,horizontal—Row. Це відображення 1:1 без кроку інтерпретації. - Токени відступів ↔ значення dp. Compose використовує
Dpдля всіх розмірів розкладки. Значення токенів уtokens.json— цілі числа без одиниць (наприклад,spacing.16 = 16), що напряму відповідають16.dp. Без конверсії, без масштабування. - Токени кольорів ↔ Color у Compose. Hex-значення Figma у
tokens.jsonвідображаються наColor(0xFFrrggbb)з однією трансформацією. Ключ токена стає семантичною змінною у вашій темі.
Вузлові типи IR і їх відповідники в Compose
| Тип IR | Властивості | Примітив Compose |
|---|---|---|
stack | axis: "vertical" | Column |
stack | axis: "horizontal" | Row |
overlay | дочірні шари накладаються | Box |
absolute | x, y, width, height | Box з Modifier.offset(x.dp, y.dp) |
leaf | type: "text" | Text з TextStyle |
leaf | type: "rectangle" із заливкою | Box(Modifier.background(Color(...))) |
Усі вузлові типи можуть мати властивість spacing (відступ між дочірніми елементами) та об'єкт padding (верх/право/низ/ліво). Обидва посилаються на ключі токенів.
Детальний маппінг токенів
Файл tokens.json виглядає так:
{
"spacing": {
"4": 4, "8": 8, "12": 12, "16": 16,
"20": 20, "24": 24, "32": 32, "48": 48
},
"radius": {
"4": 4, "8": 8, "12": 12, "16": 16, "full": 9999
},
"color": {
"7f5cfe": "#7F5CFE",
"1a1a2e": "#1A1A2E",
"f6f2ea": "#F6F2EA",
"ffffff": "#FFFFFF",
"e53935": "#E53935"
},
"typography": {
"heading.24": { "size": 24, "weight": 700, "lineHeight": 1.2 },
"body.14": { "size": 14, "weight": 400, "lineHeight": 1.5 },
"label.12": { "size": 12, "weight": 500, "lineHeight": 1.4 }
}
}
Правила маппінгу:
spacing.16→16.dpradius.12→RoundedCornerShape(12.dp)color.7f5cfe→Color(0xFF7F5CFE)(додаємо0xFFдля повного альфа-каналу)typography.heading.24→TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, lineHeight = 28.8.sp)
Ключі токенів навмисно непрозорі (hex-рядки для кольорів, числові рядки для відступів), щоб не нав'язувати модель жодній конвенції іменування. Ваша тема Compose може алясувати їх до семантичних назв — colorPrimary, spacingMd — незалежно від IR.
Реальний приклад: JSON екрану до Composable
Ось спрощений IR екрану. Вертикальний stack із заголовком-листом і списком карток:
{
"name": "home",
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.24",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"fill": "color.f6f2ea",
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.title",
"typography": "typography.heading.24",
"fill": "color.1a1a2e"
},
{
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.12",
"children": [
{
"kind": "overlay",
"radius": "radius.12",
"fill": "color.ffffff",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.card.label",
"typography": "typography.label.12",
"fill": "color.7f5cfe"
}
]
}
]
}
]
}
Відповідний Composable — те, що агент повинен створити з цього IR:
@Composable
fun HomeScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF6F2EA))
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
text = stringResource(R.string.home_title),
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
lineHeight = 28.8.sp,
color = Color(0xFF1A1A2E)
)
)
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFFFFFFF))
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_card_label),
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
lineHeight = 16.8.sp,
color = Color(0xFF7F5CFE)
)
)
}
}
}
}
Кожне значення у Composable прослідковується до ключа токена у IR. Нічого не захардкоджено — 16.dp походить із spacing.16, 24.dp — із spacing.24, Color(0xFF7F5CFE) — із color.7f5cfe.
Посилання на рядки — маппінг stringResource
Кожен текстовий вузол у IR містить stringRef із ключем у нотації через крапку. Файл strings.json відображає ключі на значення для відображення та резервні значення:
{
"home.title": { "value": "Good morning", "fallback": "Good morning" },
"home.card.label": { "value": "Today's summary", "fallback": "Summary" }
}
Нотація через крапку відповідає ідентифікаторам Android string-ресурсів із заміною крапок на підкреслення: home.title → R.string.home_title. Поле fallback — це те, що хардкодите як літеральний рядок, якщо ресурс ще не існує у strings.xml:
text = stringResource(R.string.home_title, "Good morning")
Скажіть агенту завжди використовувати поле fallback — не порожній рядок — щоб екран був читабельним під час розробки до того, як strings.xml заповнений.
Абсолютне позиціонування
Вузли з kind: "absolute" використовують координати Figma напряму. Вони з'являються в дизайнах із елементами, що накладаються, або прив'язані до конкретних позицій. Маппінг Compose використовує Box як батьківський і Modifier.offset на дочірніх елементах:
// IR: { "kind": "absolute", "x": 24, "y": 80, "width": 120, "height": 40 }
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.offset(x = 24.dp, y = 80.dp)
.size(width = 120.dp, height = 40.dp)
) {
// дочірні елементи
}
}
Абсолютне позиціонування рідкісне в добре структурованих файлах Figma (більшість сучасних файлів використовують auto-layout). Коли ви це бачите — перевірте, чи справді дизайнерський намір є абсолютним, або дизайнер просто не застосував auto-layout обмеження: IR не може вивести намір.
Чесні прогалини
Бандл добре покриває загальний випадок. Кілька речей, які він не охоплює:
- Градієнтні заливки. IR видає попередження при зустрічі з градієнтом. Заливка фону вузла опускається. Залиште
// TODO: gradientі реалізуйте вручну. - Складні ефекти. Тіні та розмиття не представлені у IR. Вони з'являються у попередженнях
_meta.json, якщо присутні. - Векторні іконки. IR зберігає ID-посилання для вузлів іконок, а не дані шляху. Вам потрібно буде окремо перетворити іконку на drawable або Compose-іконку.
- Вкладені компоненти. IR включає
componentIdна вузлах інстанцій.components/inventory.jsonвідображає ID на назви. Реалізуйте компонент окремо і посилайтеся на нього за назвою в батьківському Composable.
Ці прогалини є явними — вони з'являються у попередженнях _meta.json та CONTEXT.md. Агент не намагається безмовно заповнити їх здогадками.
Експортуйте контекстний бандл із головного застосунку figmascope, потім використовуйте його з Claude Code або Cursor для реалізації Composables безпосередньо з IR. Без здогадок за скриншотом, без захардкоджених значень.