Last active
March 8, 2023 06:46
-
-
Save pmark/f7f8c628543ddbecb64a7ceee953fb51 to your computer and use it in GitHub Desktop.
A Figma API client for declaratively automating design token style sync and docs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FigmaFile { | |
private fileId: string; | |
constructor(fileId: string) { | |
this.fileId = fileId; | |
} | |
async getColors(): Promise<FigmaColor[]> { | |
const response = await fetch(`https://api.figma.com/v1/files/${this.fileId}/`); | |
const json = await response.json(); | |
return json.document.colors; | |
} | |
async getFonts(): Promise<FigmaFont[]> { | |
const response = await fetch(`https://api.figma.com/v1/files/${this.fileId}/`); | |
const json = await response.json(); | |
return json.document.fonts; | |
} | |
async getEffects(): Promise<FigmaEffect[]> { | |
const response = await fetch(`https://api.figma.com/v1/files/${this.fileId}/`); | |
const json = await response.json(); | |
return json.document.effects; | |
} | |
colors(): FigmaQueryBuilder<FigmaColor> { | |
return new FigmaQueryBuilder<FigmaColor>(this.getColors()); | |
} | |
fonts(): FigmaQueryBuilder<FigmaFont> { | |
return new FigmaQueryBuilder<FigmaFont>(this.getFonts()); | |
} | |
effects(): FigmaQueryBuilder<FigmaEffect> { | |
return new FigmaQueryBuilder<FigmaEffect>(this.getEffects()); | |
} | |
} | |
class FigmaQueryBuilder<T> { | |
private items: Promise<T[]>; | |
constructor(items: Promise<T[]>) { | |
this.items = items; | |
} | |
filtered(predicate: (item: T) => boolean): FigmaQueryBuilder<T> { | |
this.items = this.items.then((items) => items.filter(predicate)); | |
return this; | |
} | |
async result(): Promise<T[]> { | |
return await this.items; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function findTokenStyleDifferences(fileId: string, tokens: DesignToken[]): Promise<TokenStyleDifference[]> { | |
const figmaFile = new FigmaFile(fileId); | |
// Get all styles in the file | |
const allStyles = await figmaFile.styles().result(); | |
const differences: TokenStyleDifference[] = []; | |
for (const token of tokens) { | |
// Find the style that the token references | |
const referencedStyle = allStyles.find((style) => style.id === token.styleId); | |
if (!referencedStyle) { | |
// Style not found, skip this token | |
continue; | |
} | |
// Compare the token value to the style properties | |
const styleProps = referencedStyle.style; | |
const tokenValue = token.value; | |
const propsToCompare = ['color', 'fontSize', 'fontWeight', 'letterSpacing', 'lineHeight']; | |
const propertyDifferences = propsToCompare.filter((prop) => styleProps[prop] !== tokenValue[prop]); | |
if (propertyDifferences.length > 0) { | |
differences.push({ | |
token: token.name, | |
style: referencedStyle.name, | |
differences: propertyDifferences, | |
}); | |
} | |
} | |
return differences; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface FigmaStyle { | |
id: string; | |
name: string; | |
type: string; | |
} | |
interface FigmaColor extends FigmaStyle { | |
r: number; | |
g: number; | |
b: number; | |
a: number; | |
visible: boolean; | |
description?: string; | |
mixMode: string; | |
opacity?: number; | |
blendMode?: string; | |
parent?: string; | |
gradientHandlePositions?: Vector[]; | |
gradientStops?: GradientStop[]; | |
scaleMode?: string; | |
imageRef?: string; | |
format?: string; | |
effects?: Effect[]; | |
shortcut?: string; | |
shade?: number; | |
} | |
interface FigmaFont extends FigmaStyle { | |
family: string; | |
style: string; | |
postScriptName: string; | |
category: string; | |
fontFiles: { | |
[key: string]: { | |
url: string; | |
size: number; | |
}; | |
}; | |
fontWeight: number; | |
italic: boolean; | |
width?: string; | |
stretch?: string; | |
letterSpacing?: { | |
value: number; | |
unit: string; | |
}; | |
lineHeightPx?: number; | |
lineHeightPercent?: number; | |
lineHeightUnit?: string; | |
fontSize?: { | |
value: number; | |
unit: string; | |
}; | |
fontPostScriptName?: string; | |
fontVariationSettings?: { | |
[key: string]: number; | |
}; | |
} | |
interface FigmaEffect { | |
type: string; | |
visible: boolean; | |
radius: number; | |
color: FigmaColor; | |
blendMode: string; | |
offset: Vector; | |
} | |
type FigmaItemType<T> = T extends FigmaColor ? 'FigmaColor' : T extends FigmaFont ? 'FigmaFont' : T extends FigmaEffect ? 'FigmaEffect' : never; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function findTokenStyleDifferences(fileId: string, tokens: DesignToken[]): Promise<TokenStyleDifference[]> { | |
const figmaFile = new FigmaFile(fileId); | |
// Get all styles in the file | |
const allStyles = await figmaFile.styles().result(); | |
const differences: TokenStyleDifference[] = []; | |
for (const token of tokens) { | |
// Find the style that the token references | |
const referencedStyle = allStyles.find((style) => style.id === token.styleId); | |
if (!referencedStyle) { | |
// Style not found, skip this token | |
continue; | |
} | |
// Compare the token value to the style properties | |
const styleProps = referencedStyle.style; | |
const tokenValue = token.value; | |
const propsToCompare = ['color', 'fontSize', 'fontWeight', 'letterSpacing', 'lineHeight']; | |
const propertyDifferences = propsToCompare.filter((prop) => styleProps[prop] !== tokenValue[prop]); | |
if (propertyDifferences.length > 0) { | |
differences.push({ | |
token: token.name, | |
style: referencedStyle.name, | |
differences: propertyDifferences, | |
}); | |
} | |
} | |
return differences; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function updateTokenStyles(fileId: string, tokens: DesignToken[]): Promise<void> { | |
const figmaFile = new FigmaFile(fileId); | |
// Get all styles in the file | |
const allStyles = await figmaFile.styles().result(); | |
// Create a mapping of style IDs to styles for fast lookups | |
const styleIdMap = new Map<string, FigmaStyle>(); | |
allStyles.forEach((style) => { | |
styleIdMap.set(style.id, style); | |
}); | |
// Update the styles that the tokens reference | |
for (const token of tokens) { | |
const referencedStyle = styleIdMap.get(token.styleId); | |
if (!referencedStyle) { | |
// Style not found, skip this token | |
continue; | |
} | |
// Update the properties of the referenced style to match the token value | |
const styleProps = referencedStyle.style; | |
const tokenValue = token.value; | |
const updatedStyle: FigmaStyle = { | |
...referencedStyle, | |
style: { | |
...styleProps, | |
color: tokenValue.color, | |
fontSize: tokenValue.fontSize, | |
fontWeight: tokenValue.fontWeight, | |
letterSpacing: tokenValue.letterSpacing, | |
lineHeight: tokenValue.lineHeight, | |
}, | |
}; | |
await figmaFile.styles().update(updatedStyle); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const figmaFile = new FigmaFile('fileId123') | |
// Select only colors in the primary tier and exclude any that start with 'background' | |
const primaryColors = figmaFile.colors.tier(1).filtered(color => !color.name.startsWith('background')) | |
// Select only fonts that are marked as display fonts | |
const displayFonts = figmaFile.fonts.filtered(font => font.category === 'display') | |
// Select only shadow effects that have a blur radius of 16 or greater | |
const largeShadows = figmaFile.effects.filtered(effect => effect.type === 'shadow' && effect.blurRadius >= 16) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment