Introduktion: Figma til reaktion

Hvordan vi brugte Figmas web API til at konvertere design til React code

Vi ansætter! Find en komplet liste over åbne positioner og fotos af vores luftige kontor i San Francisco på Figmas karriereside.

Siden lanceringen af ​​Figma API har mange mennesker spekuleret i muligheden for automatisk at omdanne Figma-dokumenter til React-komponenter. Nogle af jer har faktisk lavet fungerende prototyper, og Pagedraw har endda bygget et helt produkt bygget omkring det!

Vi elsker entusiasmen og troede, at vi ville dele vores eget forsøg på en React-konverter, diskutere nogle af de subtile designvalg og tekniske løsninger, der gik ind i dens udvikling, og uddybe den vision, der motiverede dens skabelse. Hvis du vil følge med, har vi åbnet koden på GitHub. (For spændt på at læse dette blogindlæg og vil lege med vores læse API lige nu? Tjek vores side om udviklere!)

Vi ønskede at løse to hovedproblemer, når vi bygger Figma til reaktion. Det ene var at have designet til komponenter, vi genererer, for at bo så meget i Figma som muligt. Hvor fantastisk ville det være at opdatere et design i Figma og derefter klikke på en knap for at synkronisere disse designændringer til dit websted? Dette betyder også, at vi er nødt til at sikre, at opdatering af dit design ikke overskriver nogen brugerdefineret kode, som vi har skrevet for at gøre webstedet eller appen funktionel, og at det naturligvis egner sig til at have Figma-genereret designkode og funktionskode leve separat i pæne rum.

Det andet, men beslægtede mål var at gøre det let at knytte eksisterende funktionel kode til nye design. For eksempel i det sorterede listeeksempel ovenfor ville det være rart, hvis vi kunne designe en ny sorteret liste og nemt vedhæfte den kode, vi allerede havde skrevet til den. Med andre ord ville det være dejligt, at vores funktionelle kode kan genbruges på tværs af design, ligesom hvordan du muligvis genbruger React-komponenter.

Hvor fantastisk ville det være at opdatere et design i Figma og derefter klikke på en knap for at synkronisere disse designændringer til dit websted?

Fra Figma til CSS

Den første hindring for Figma at React var at oprette React-komponenter, der ligner de design, de repræsenterer - ellers er der ikke meget pointe til noget af dette. Lad os bruge eksemplet med sorterbar liste ovenfra:

Kilde Figma-fil

Der er mange forskellige måder, vi kan gå på med at gengive udseendet på denne liste i HTML. For eksempel kunne vi gengive et billede af hele rammen og oprette en komponent, der bare tegner dette billede. Denne tilgang er enkel, men meget begrænset; for eksempel er det næsten umuligt at gøre noget interaktivt her som at klikke på en knap for at sortere.

Den første hindring for Figma at React var at oprette React-komponenter, der ligner de design, de repræsenterer - ellers er der ikke meget pointe til noget af dette.

Brug absolut layout til at placere noder

En bedre fremgangsmåde ville være at bryde rammen ned i dens bestanddele, konvertere hvert stykke til et DOM-element som en

eller en og derefter komponere disse DOM-elementer sammen. Komponering af disse elementer er en proces kaldet layout, hvor vi specificerer, hvor hvert element skal placeres, og hvordan det skal dimensioneres i forhold til hvert andet element.

Figma API giver et solidt fundament til bestemmelse af layout. Hver knude i et dokument har en egenskab kaldet absoluteBoundingBox. Ved hjælp af absoluteBoundingBox kan vi finde ud af nøjagtigt, hvor hver knude i øjeblikket bor på lærredet, og hvor meget plads den besætter. Ud fra dette ville en idé være at tage hver node, gengive den som et element og derefter bruge CSS absolut positionering til at placere den på siden. Hvis vi gør det, får vi muligvis noget lignende (bemærk at rammen stadig sidder fast i øverste venstre hjørne):

Dette ser stort set det samme ud, som hvis vi havde gengivet hele rammen som et billede, men vi har allerede gjort meget fremskridt. Vi kan nu vælge teksten på listen, vi kan potentielt erstatte teksten med noget dynamisk, og vi kan knytte begivenheder til at klikke på forskellige ting i scenen (som sorteringspile). På trods af dette er der stadig mange mangler. Hvis vi tilføjer en masse tekst til dette listevareboks, vil teksten spildes uden for rektanglet. At se på disse absolutte grænser viser sig at være for begrænsende. Vi er nødt til at løsne vores greb over layout og lade elementer dynamisk ændre størrelsen som designet.

Brug Figma-begrænsninger til at ændre størrelsen på noder dynamisk

Figma og andre designværktøjer har et koncept med begrænsninger, der kan anvendes på et lag, der får dem til at ændre størrelse i henhold til deres overordnede ramme. Disse begrænsninger afspejles i API'en som egenskaberne for begrænsninger, der er vist her for den forrige tekstknude:

"begrænsninger": {
  "lodret": "TOP",
  "vandret": "LEFT_RIGHT"
}

Dette siger, at dette bestemte tekstelement skal placeres i forhold til toppen af ​​overordnet og skal strække sig med overordnet horisontalt, så venstre og højre margen bevares. Forælderen i dette tilfælde er rektanglet omkring "Listepost 1". At anvende begrænsninger ud af boksen på absolut positionering er en direkte kortlægning til venstre, højre, top og bund attributter i CSS. Dette omhandler, at listebeholderen ændrer størrelse og form, men ikke med, at selve listens indhold ændres. Hvordan ville vi for eksempel tilpasse os, hvis vi ville tilføje et andet listeelement dynamisk, eller hvis teksten, der går i det listeelement, overstiger dimensionerne i det originale felt?

Layout: top-down eller bottom-up?

Ovenstående problemer er tilfældet, hvad HTML selv er dygtig til at løse. Hvis du stakler to div-elementer på hinanden, skifter du højden på den første div automatisk automatisk den anden div ned. Denne opførsel fungerer ikke i vores situation, fordi alt er absolut placeret, og derfor er de låst på plads!

Stakning dividerer hinanden oven på den anden baseret på hvor meget indhold der er i hver enkelt en tilgang, som vi kalder bottom-up-layout, hvilket betyder, at vi starter fra de laveste byggesten (f.eks. Tekststykker) og bygger strukturen op ved at komponere disse blokke sammen for at få formen på lagene på højere niveau. I modsætning hertil er det, vi hidtil har gjort, top-down layout, hvor vi specificerer, hvor meget plads det øverste lag skal tage, og derefter placere elementerne, der udgør dette højere lag inden i det. Hvad hvis vi tager en kombination af top-down og bottom-up tilgange?

Lad os beslutte, hvordan vi vil reagere, når indhold i et af vores elementer ændres. I nogle tilfælde er svaret indlysende:

Forestil dig, at de stiplede bokse er de elementer, der bor inden i en ydre ramme. Hvis vi tilføjer tekst til det øverste element, skal vi skubbe både billedet og den nederste tekst ned lige med. Men der er andre situationer, hvor den rigtige løsning muligvis ikke er så klar:

Hvad gør vi i tilfælde A og B ovenfor, hvis vi vil tilføje tekst til den første boks? Den løsning, jeg endte med at gå med, var pragmatisk, men i mange tilfælde ufuldstændig. Den vigtigste indsigt er, at de fleste websteder er arrangeret lodret, da webstedets konvention er, at du ruller fra top til bund for at se alt tilgængeligt indhold. I betragtning af det besluttede jeg at behandle børnelementer af en knude, som om de havde en lineær rækkefølge fra top til bund.

Hvis vi anvender det på det første eksempel ovenfor, kan bestillingen muligvis se sådan ud:

Hvis vi nu ønsker at udvide det første tekstfelt, skubber vi alt, hvad der kommer efter det, i rækkefølgen, så det matcher det beløb, det blev udvidet med:

Bemærk, at i ovenstående eksempel er resultatet mærkeligt, men grænseoverskridende rimeligt. I modsætning hertil er dette resultatet i B:

Dette er sandsynligvis ikke det, vi ønskede: de fleste ville forvente, at billedet og andre tekstbokse forbliver på linje. Der er mange måder, hvorpå vi kunne løse dette problem. Vi kunne introducere heuristikker som "elementer, der er lodret justeret skal forblive lodret justeret" eller markere knudepunkterne i Figma på en bestemt måde for at bemærke, at de skal have deres lodrette position. Prøv din egen tilgang ved at ændre Figma til React-kode og fortæl os, hvis du har nogen nye ideer!

3-stack tilgangen

Bevæbnet med vores beslutning kan vi nu vende tilbage til at tackle det generelle layoutproblem. Ved at tage et sæt noder, der er TOP-justeret, kan vi placere dem i forhold til hinanden ved at bestemme afstanden mellem bunden af ​​det forrige element og toppen af ​​det aktuelle element:

Lineært marginbaseret layout

Bemærk, at denne margen kan være negativ. Derefter indstiller vi blot margin-top CSS egenskaben for det aktuelle element til denne forskel. Hvis et element nu ændrer sig, vil de andre flyde op og ned på siden, som man kunne forvente. Det samme kan gøres af BOTTOM-justerede elementer, der behandler dem som en separat gruppe. Resultatet er, at vi for enhver knude kan opdele dets børnelementer i tre grupper:

  • En gruppe af TOP-justerede elementer med bottom-up layout
  • En gruppe af elementer, der er CENTER, SCALE eller TOP_BOTTOM justeret, placeret med absolut (eller top-down) layout
  • En gruppe af BOTTOM-justerede elementer, igen med bund-op-layout
3-stack layout

Hvis vi anvender disse ideer på den sorterbare liste, kan vi nu udsende en fuldt reaktiv komponent, der både ændrer størrelse til at passe til beholderen og tilpasser sig det skiftende indhold i sig selv, som vist nedenfor.

Indpakning og begrænsninger i handling

Én ting at påpege i dette klip er, at sidefoden holder op med at bevæge sig på et bestemt punkt - med andre ord, dokumentet holder op med at blive kortere på et fast punkt. Dette er vigtigt for at sikre, at fodteksten f.eks. Ikke løber ind i komponentens hovedindhold. Dette kan imidlertid ikke kun implementeres som minimumshøjde på elementet: Når teksten ændrer størrelse, skal minimumshøjden vokse for at rumme den ekstra tekst. Løsningen her er at tilføje en bundmarge til de top-justerede elementer (og en top-margin til de bundlinjede elementer), så de støder mod den modsatte ende af deres overordnede og "prop" deres forælder åben til en bestemt størrelse .

Skub ud og ind på samme tid

Endelig, lad os se tilbage på, hvordan bund-up og top-down begrænsninger interagerer med hinanden i tilfældet med listen ovenfor. I dette næste eksempel fremhæver vi tre elementer, der er medvirkende til, at listerne ændres korrekt i størrelsen: den indeholdende ramme (ydre stiplet boks), tekstfeltet (indre stiplet boks) og den runde rektangulære kant (skitseret i massivt blåt).

Visualiserede begrænsninger

Forholdet mellem disse elementer er som følger: tekstelementet og det rektangulære trin er begge børn af rammen. Tekstelementet har LEFT_RIGHT og TOP begrænsninger på rammen, og rektanglet har LEFT_RIGHT og TOP_BOTTOM begrænsninger. Når rammen indsnævres, skal teksten klemmes på en anden linje og øges højden. Fordi dens margener forbliver konstante, forårsager dette også, at den indeholdende ramme forstørres, hvilket får den næste ramme på listen til at blive skubbet ned. På samme tid, fordi rektanglet (igen, i blåt) har en TOP_BOTTOM-begrænsning til den ramme, er den også nødt til at ændre størrelsen og blive større for at tilfredsstille denne begrænsning. Så vi har en bund-op-begrænsning, hvor den indre tekst gør den ydre ramme større, og derefter en top-down begrænsningen, hvor den ydre ramme gør det indre rektangel større. Jeg føler virkelig, at dette er en af ​​disse interaktioner, hvor resultatet er helt umærkeligt (dette er nøjagtigt den opførsel, du ville forvente), men rejsen for at komme dertil er ret kreativ.

Få listen til at fungere som en liste

Nu hvor vi har fået os noget der ligner en liste, hvordan får vi denne liste til at gøre ting på listen? Hvordan kan vi få det til at indlæse vilkårlige data, for eksempel? Eller sortere disse data?

Vores React-konverter skal spytte nogle filer ud med React-komponenter i dem. Hver gang vi ændrer design, bliver vi i det mindste nødt til at ændre noget af indholdet i nogle af disse filer for at repræsentere ændringerne i designet. For at få komponenterne til at gøre ting, vil vi sandsynligvis skrive en kode et eller andet sted, der kan interagere med de genererede komponenter. Til sidst bør den kode, vi skriver, aldrig overskrives af generatoren og ideelt set kunne bæres fra en komponent til en anden.

Du har måske bemærket på det tidspunkt, at det foregående afsnit om layout ikke overhovedet afhænger af React. Du har ret - vi kunne have fået vores konverter til at generere ren HTML og CSS, og det ville have fungeret lige så godt indtil videre. Det skyldes, at en ren konvertering af en Figma-ramme resulterer i en statisk komponent, og React frembyder ingen væsentlige fordele, når det kommer til at gøre et statisk sted udover kodelighedens komposibilitet.

Nu hvor vi har fået os noget der ligner en liste, hvordan får vi denne liste til at gøre ting på listen?

Men nu bliver vi nødt til at læne os mere ind på React. Jeg introducerer kun to grundlæggende koncepter, som jeg er udtænkt for at løse funktionalitetsproblemet. Deres fordel er, at de er enkle, men alligevel overraskende kraftfulde, men på ingen måde antyder jeg, at disse koncepter er de eneste måder at gøre ting på - måske kan du tilbyde eller implementere dine egne løsninger på dette problem.

Gadgets: genanvendelige kodeklodser, der holder sig til design

Det første koncept, jeg vil introducere, er en gadget (kaldet “komponenter” i koden). En gadget er en indpakning, der går rundt om enhver knude i et Figma-design og tilføjer et stykke funktionalitet til det - enhver funktionalitet overhovedet. Du kan oprette eller vedhæfte en gadget til en Figma-knude ved at sætte et hash-symbol (‘#’) foran navnet.

Hvis du f.eks. Vil sige, at en ramme skal opføre sig som et ur, kan du navngive det #Clock. Dette gør ikke noget specielt i selve filen, men vil udløse den knude, der skal gadgetiseres i konverteren - dette vil oprette en Gadget-fil kaldet CClock.js, som du kan udfylde med funktionalitet. Måske er den nemmeste måde at forklare dette på at vise et eksempel:

Prøve genereret kode

I denne scene har vi en container med to rammer inde. Den ene ramme indeholder et billede af en cirkel og den anden et billede af en firkant. Lad os sige, at vi ønsker, at begge rammer skal udvise en vis brugerdefineret (men samme) opførsel, ligesom vi ønsker at animere begge snurrede rundt. Vi kan pakke hver ramme i den samme gadget (kaldet #Spinner). Dette genererer en tilpassbar kodefil CSpinner.js. Den genererede venstre side refererer til denne Gadget-komponent hver gang en knude, der er kommenteret som #Spinner, vises i nodetræet. Koden overfører også en nodeID til den gadget, som den kan bruge til at slå dens indhold i hvert tilfælde - Gadgets magt er, at de kan anvendes til en hvilken som helst knude, så indholdet af noden kan variere fra instans til instans.

En gadget er en indpakning, der går rundt om enhver knude i et Figma-design og tilføjer et stykke funktionalitet til det - enhver funktionalitet overhovedet.

Det betyder, at hvis vi koder CSpinner.js for at gøre indholdet animeret og dreje rundt, kan vi få enhver knude til at dreje rundt ved at navngive det #Spinner og dermed vedhæfte Gadget-koden til det. Hvis vi anvender animationskoden på CSpinner, får vi denne:

#Spinner i aktion

Bemærk, at CSpinner-komponenten i sin renderfunktion blot henviser til komponent, der er opnået fra getComponentById. CSpinner er ikke opmærksom på, hvad den indpakker - en fuldstændig adskillelse af funktion fra design. Bemærk også, at når CSpinner.js først er genereret, vil vi aldrig overskrive det: eventuelle ændringer, der er foretaget, fortsætter uanset hvor mange gange du regenererer designene.

Variabler: Skift pladsholdertekst ud med dynamiske værdier

Variabler er det andet koncept, vi introducerer. En variabel er simpelthen en tekstknude, hvis navn starter med $. En sådan knude viser som standard teksten i designet, men kan tilsidesættes af React-rekvisitter for at vise vilkårlig tekst. Den egenskab, der tilsidesætter teksten, er den samme som navnet på noden minus $. Så som et eksempel, hvis jeg har en knude, der kaldes $ kylling, og rekvisitter, der kommer ind i det element, ser ud som {kylling: “poptarts”}, vil teksten til den knude blive erstattet med strengen “poptarts”. Du kan sende disse egenskaber ned ved at indpakke noder med variabler i en Gadget.

Lad os sammensætte dette alt sammen: til vores sorterede liste, ønsker vi noget, der kan tage en datakilde og udfylde en listeindgang for hvert element i datakilden. I betragtning af at vi ønsker at tage et listepunkt og kopiere det mange gange, er det fornuftigt at placere en gadget omkring den knude, der svarer til listeposten. En sådan gadget kan se sådan ud:

Vi kan foretage flere observationer af dette kodestykket:

  • Vi læser ikke fra datakilden direkte her. Vi forventer snarere at få en liste over emner, der allerede er behandlet. Årsagen til dette vil fremgå senere.
  • Gadget-navnet starter med en C, hvilket er tilfældet for alle Gadget-skabeloner, der er genereret med konverteren. Dette er for at sikre, at vi altid kan starte med et stort bogstav, som er React-konvention for komponentnavne (en anden måde ville være bare at aktivere det første bogstav).
  • Vi viser som standard, hvad der er i Figma-dokumentet, hvis der ikke findes nogen listeItemer. Dette anbefales, så siden kan fungere uden at skulle angive datakilder.
  • Vi kan bruge Komponent, som er den knude, Gadget indpakker, flere gange i render-funktionen! Dette er, hvordan vi kan duplikere listeposten.
  • Vi er nødt til at indpakke hver komponent i en div. Dette er for at anvende positionen: relativ stil, som er nødvendig i tilfælde af vores fil. Detaljerne om, hvorfor dette ikke er vigtigt, men det er rart, at vi kan gøre det. Bemærk, at du lige så let kunne vedhæfte en klasse her og style denne i CSS. Reager afskrækker faktisk indlagte stilarter i dens stilguide. Du kan forestille dig at implementere en konverter, der udsender en CSS-fil uden for meget yderligere besvær.

Så hvorfor kan vi ikke bare indlæse datakilden direkte i denne komponent? Årsagen er, at vi ønsker at oprette en sorterbar liste, og kontrollerne til sortering er uden for denne komponent. Da ListItems-komponenten og knapperne til sortering af stigende og faldende er på helt forskellige undertræer, kan de kun kommunikere mellem hinanden gennem en fælles forælder. De er nødt til at tale gennem denne forælder, så vi kan lige så godt have, at forælderen er ansvarlig for at være den kanoniske sandhedskilde for listeelementerne.

Der er en række måder omkring dette. Du kan nemt knytte Redux til hver komponent og kommunikere gennem handlinger og den globale butik. Dette ville også have fordelen ved at være mere vedligeholdelig. Men for kodens enkelhed viser jeg, hvordan jeg kun opnår det samme slutresultat med React.

Indlæs brugerdefinerede data på listen

Herefter konfigureres den fælles overordnede komponent:

Hvis vi videregiver en listSource til denne gadget, vil den forsøge at indlæse den URL, der er gemt i listSource og gemme det resulterende parsede JSON-objekt i listItems. Vi definerer også to sorteringsfunktioner og passerer dem som egenskaber på komponenten. Nu kan enhver knude, der er afkom fra CSortableList, kalde disse sorteringsfunktioner, og hvis vi sætter en CListItems-gadget nedstrøms for dette, vil den være i stand til at gengive listen fra datakilden!

Endelig viser vi kort den gadget, der udløser sorteringen:

Denne gadget er viklet omkring knappen, der udløser sortering af listen i stigende rækkefølge. Fordi en af ​​dets forfædre er en CSortableList, er vi i stand til at kalde ind i funktionen props.sortAscending (), som får staten i CSortableList til at ændre, udløse en omdirigering af CListItems-gadgeten og omarrangere listeposterne deri. Lad os knytte alle disse gadgets til vores originale design, oprette en CSortableList-komponent med listSource indstillet til /shapes.json, og se hvad der sker:

Sortering af brugerdefinerede data

Genbrug af kode

Dette særlige eksempel er nu funktionelt! Endnu mere spændende er det, at nu, hvor vi har denne kode, er det nemt at knytte den til alt, hvad vi vil gøre, til en sorterbar liste ved at navngive noderne i Figma til det, vi kaldte vores gadgets. Det er lykkedes os at indkapsle funktionalitet i disse Gadget-filer, der vilkårligt kan plunkkes på enhver Figma-knude. Er det sådan, man skal bygge grænseflader? Sandsynligvis ikke nøjagtigt. Er der lektioner, der kan læres her, og indsigt, der kan samles i retning af at fremme, hvordan vi tænker på interaktionen mellem design og kode? Vi håber det.

Er det sådan, man skal bygge grænseflader? Sandsynligvis ikke nøjagtigt.

Fremtidens arbejde: prototype, CSS Grid, Layout Grids

Et par ideer til at udvide Figma til at reagere:

  • Respekter prototype-links, så at klikke på et element overfører appen til en anden tilstand
  • Implementer svævertilstande
  • Generer et stilark, der bruger CSS-gitter til layout af elementerne
  • Respekter layoutkolonner og rækker i Figma
  • Implementér support til roterede noder (lige nu vil enhver knude med rotation eller skæv gengives ikke korrekt)

Gå mod Vest

Her har vi præsenteret, hvad vi håber at være en diamant i det uslebne. Vi lagde vores strategi til kortlægning af begrænsninger til HTML og til at knytte genanvendelig kode til design. Hvis du gik glip af linket i introduktionen, har vi åbnet koden til Figma To React over på Github.

Grænsefladesign kan drage fordel af at omfatte kode i stedet for at have design og kode live i to separate verdener, der efterligner hinanden. Ved at smelte dem sammen i et delt bibliotek, kan designere og udviklere skære ned på menialt arbejde og bruge tid på det, der betyder noget: at løse større udfordringer.

Spændt at bygge noget eget med vores API? Gå til vores udviklere-side for inspiration, og Show & Tell-kanalen på Spectrum for et samfund af kollegaer. Fremtiden er din at smede.