Domyślnym celem eksportu figmascope jest Jetpack Compose. To nie jest przypadek — model układu Compose (Column, Row, Box, Modifier) ściśle mapuje na rodzaje węzłów IR (stack, overlay, absolute, leaf). Pionowy stack w Figmie to Column w Compose. Tłumaczenie jest mechaniczne, co czyni go dobrze dostosowanym do codegenu sterowanego przez agenta.
Ten przewodnik szczegółowo omawia mapowanie IR-na-Compose, pokazuje prawdziwy fragment JSON z odpowiednim Composable i wyjaśnia warstwę mapowania tokenów.
Dlaczego Compose jest domyślnym celem
Trzy strukturalne powody:
- Auto-layout ↔ Column/Row. Ramki auto-layout Figmy (zdecydowana większość nowoczesnych projektów Figma) eksportują jako węzły
kind: "stack". Węzły stack mają właściwośćaxis—verticalmapuje naColumn,horizontalmapuje naRow. To mapowanie 1:1 bez kroku interpretacji. - Tokeny odstępów ↔ wartości dp. Compose używa
Dpdla wszystkich wymiarów układu. Wartości tokenów wtokens.jsonto bezjednostkowe liczby całkowite (np.spacing.16 = 16), które mapują bezpośrednio na16.dp. Bez konwersji, bez skalowania. - Tokeny kolorów ↔ Color composable. Wartości hex Figmy w
tokens.jsonmapują naColor(0xFFrrggbb)z jedną transformacją. Klucz tokenu staje się semantyczną nazwą zmiennej w Twoim motywie.
Rodzaje węzłów IR i ich mapowania Compose
| Rodzaj IR | Właściwości | Prymityw Compose |
|---|---|---|
stack | axis: "vertical" | Column |
stack | axis: "horizontal" | Row |
overlay | warstwowane dzieci | Box |
absolute | x, y, width, height | Box z Modifier.offset(x.dp, y.dp) |
leaf | type: "text" | Text z TextStyle |
leaf | type: "rectangle" z wypełnieniem | Box(Modifier.background(Color(...))) |
Wszystkie rodzaje węzłów mogą nosić właściwość spacing (przerwa między dziećmi) i obiekt padding (góra/prawo/dół/lewo). Obie odwołują się do kluczy tokenów.
Mapowanie tokenów w szczegółach
Plik tokens.json wygląda tak:
{
"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 }
}
}
Reguły mapowania:
spacing.16→16.dpradius.12→RoundedCornerShape(12.dp)color.7f5cfe→Color(0xFF7F5CFE)(dodaj0xFFdla pełnej alfy)typography.heading.24→TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, lineHeight = 28.8.sp)
Klucze tokenów są celowo nieprzezroczyste (ciągi hex dla koloru, ciągi numeryczne dla odstępów), żeby nie nastawiały modelu na żadną konkretną konwencję nazewnictwa. Twój motyw Compose może aliasować je do semantycznych nazw — colorPrimary, spacingMd — niezależnie od IR.
Realny przykład: ekran główny JSON na Composable
Oto uproszczony IR ekranu głównego. Pionowy stack z nagłówkiem leaf i listą kart:
{
"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"
}
]
}
]
}
]
}
Odpowiadający Composable — co agent powinien wyprodukować z tego 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)
)
)
}
}
}
}
Każda wartość w Composable daje się prześledzić do klucza tokenu w IR. Nic nie jest zahardkodowane — 16.dp pochodzi z spacing.16, 24.dp z spacing.24, Color(0xFF7F5CFE) z color.7f5cfe.
Referencje ciągów — mapowanie stringResource
Każdy węzeł tekstowy w IR niesie stringRef z kluczem w notacji z kropką. Plik strings.json mapuje klucze na wartości wyświetlane i fallbacki:
{
"home.title": { "value": "Dzień dobry", "fallback": "Good morning" },
"home.card.label": { "value": "Dzisiejsze podsumowanie", "fallback": "Summary" }
}
Notacja z kropką mapuje na ID zasobów stringów Androida z kropkami zamienionymi na podkreślniki: home.title → R.string.home_title. Pole fallback to to, co zahardkodowujesz jako literał, jeśli zasób nie istnieje jeszcze w strings.xml:
text = stringResource(R.string.home_title, "Good morning")
Powiedz agentowi, by zawsze używał pola fallback — nie pustego ciągu — żeby ekran był czytelny podczas dewelopmentu, zanim strings.xml zostanie wypełniony.
Pozycjonowanie absolutne
Węzły z kind: "absolute" używają bezpośrednio współrzędnych Figmy. Pojawiają się w projektach z nakładającymi się elementami lub elementami zakotwiczonymi na konkretnych pozycjach. Mapowanie Compose używa Box jako rodzica i Modifier.offset na dzieciach:
// 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)
) {
// dzieci
}
}
Pozycjonowanie absolutne jest rzadkie w dobrze ustrukturyzowanych plikach Figma (większość nowoczesnych plików używa auto-layout). Gdy je widzisz, sprawdź, czy intencją projektu naprawdę jest pozycjonowanie absolutne, czy projektant po prostu nie zastosował ograniczeń auto-layout — IR nie może wnioskować intencji.
Uczciwe luki
Pakiet dobrze pokrywa typowy przypadek. Kilka rzeczy, których nie pokrywa:
- Wypełnienia gradientowe. IR eksportuje ostrzeżenie, gdy natrafia na gradient. Wypełnienie tła węzła jest pomijane. Zostaw
// TODO: gradienti zaimplementuj ręcznie. - Złożone efekty. Cienie i rozmycia nie są reprezentowane w IR. Pojawiają się w ostrzeżeniach
_meta.jsonjeśli są obecne. - Ikony wektorowe. IR przechowuje ID referencyjne dla węzłów ikon, nie dane ścieżki. Musisz rozwiązać ikonę na faktyczny drawable lub ikonę Compose oddzielnie.
- Zagnieżdżone komponenty. IR zawiera
componentIdna węzłach instancji.components/inventory.jsonmapuje ID na nazwy. Zaimplementuj komponent osobno i odwołuj się do niego przez nazwę w rodzicielskim Composable.
Te luki są explicite — pojawiają się w ostrzeżeniach _meta.json i CONTEXT.md. Agent nie zgaduje przez nie po cichu.
Wyeksportuj pakiet kontekstowy z głównej aplikacji figmascope, a następnie użyj go z Claude Code lub Cursor, by implementować Composable bezpośrednio z IR. Bez zgadywania ze zrzutów ekranu, bez zahardkodowanych wartości.