Целевой платформой по умолчанию для экспорта figmascope является Jetpack Compose. Это не случайно — модель вёрстки Compose (Column, Row, Box, Modifier) тесно соответствует видам IR-узлов (stack, overlay, absolute, leaf). Вертикальный стек в Figma — это Column в Compose. Перевод механический, что делает его хорошо подходящим для агентной кодогенерации.
В этом руководстве детально рассматривается маппинг IR → Compose, показывается реальный JSON-фрагмент с соответствующим Composable и объясняется слой маппинга токенов.
Почему Compose — целевая платформа по умолчанию
Три структурные причины:
- Auto-layout ↔ Column/Row. Фреймы с auto-layout в Figma (подавляющее большинство современных дизайнов Figma) экспортируются как узлы
kind: "stack". Узлы stack имеют свойствоaxis—verticalотображается наColumn,horizontal— наRow. Это маппинг 1:1 без шага интерпретации. - Токены отступов ↔ значения dp. Compose использует
Dpдля всех размеров вёрстки. Значения токенов вtokens.json— это целые числа без единиц измерения (например,spacing.16 = 16), напрямую отображающиеся на16.dp. Без конвертации, без масштабирования. - Цветовые токены ↔ Color composable. 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 домашнего экрана. Вертикальный стек с листом-заголовком и списком карточек:
{
"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" }
}
Точечная нотация отображается на ID строковых ресурсов Android с заменой точек на подчёркивания: 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 icon.
- Вложенные компоненты. IR включает
componentIdна узлах-экземплярах.components/inventory.jsonсопоставляет ID с именами. Реализуйте компонент отдельно и ссылайтесь на него по имени в родительском Composable.
Эти пробелы явные — они появляются в предупреждениях _meta.json и CONTEXT.md. Агент не угадывает через них молча.
Экспортируйте контекстный бандл из основного приложения figmascope, затем используйте его с Claude Code или Cursor для реализации Composable напрямую из IR. Никакого угадывания по скриншотам, никаких захардкоженных значений.