/**
* Figma API Client
* Docs: https://www.figma.com/developers/api
*/
export interface FigmaNode {
id: string;
name: string;
type: string;
children?: FigmaNode[];
absoluteBoundingBox?: {
x: number;
y: number;
width: number;
height: number;
};
fills?: FigmaFill[];
strokes?: FigmaStroke[];
strokeWeight?: number;
cornerRadius?: number;
rectangleCornerRadii?: number[];
paddingLeft?: number;
paddingRight?: number;
paddingTop?: number;
paddingBottom?: number;
itemSpacing?: number;
layoutMode?: 'NONE' | 'HORIZONTAL' | 'VERTICAL';
primaryAxisAlignItems?: string;
counterAxisAlignItems?: string;
style?: FigmaTextStyle;
characters?: string;
}
export interface FigmaFill {
type: 'SOLID' | 'GRADIENT_LINEAR' | 'GRADIENT_RADIAL' | 'IMAGE';
color?: {
r: number;
g: number;
b: number;
a: number;
};
opacity?: number;
visible?: boolean;
}
export interface FigmaStroke {
type: 'SOLID';
color?: {
r: number;
g: number;
b: number;
a: number;
};
}
export interface FigmaTextStyle {
fontFamily?: string;
fontPostScriptName?: string;
fontWeight?: number;
fontSize?: number;
textAlignHorizontal?: string;
textAlignVertical?: string;
letterSpacing?: number;
lineHeightPx?: number;
lineHeightPercent?: number;
lineHeightUnit?: string;
}
export interface FigmaFileResponse {
name: string;
document: FigmaNode;
components: Record<string, { key: string; name: string }>;
styles: Record<string, { key: string; name: string; styleType: string }>;
}
export interface FigmaNodesResponse {
nodes: Record<string, { document: FigmaNode }>;
}
export class FigmaClient {
private baseUrl = 'https://api.figma.com/v1';
private token: string;
constructor(token: string) {
this.token = token;
}
private async request<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: {
'X-Figma-Token': this.token
}
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Figma API error: ${response.status} - ${error}`);
}
return response.json();
}
async getFile(fileKey: string): Promise<FigmaFileResponse> {
return this.request<FigmaFileResponse>(`/files/${fileKey}`);
}
async getNodes(fileKey: string, nodeIds: string[]): Promise<FigmaNodesResponse> {
const ids = nodeIds.join(',');
return this.request<FigmaNodesResponse>(`/files/${fileKey}/nodes?ids=${ids}`);
}
async getNode(fileKey: string, nodeId: string): Promise<FigmaNode | null> {
const response = await this.getNodes(fileKey, [nodeId]);
return response.nodes[nodeId]?.document || null;
}
}
/**
* Convert Figma color (0-1) to hex
*/
export function figmaColorToHex(color: { r: number; g: number; b: number; a?: number }): string {
const r = Math.round(color.r * 255);
const g = Math.round(color.g * 255);
const b = Math.round(color.b * 255);
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
}
/**
* Convert Figma color to rgba string
*/
export function figmaColorToRgba(color: { r: number; g: number; b: number; a?: number }): string {
const r = Math.round(color.r * 255);
const g = Math.round(color.g * 255);
const b = Math.round(color.b * 255);
const a = color.a ?? 1;
if (a === 1) {
return `rgb(${r}, ${g}, ${b})`;
}
return `rgba(${r}, ${g}, ${b}, ${a})`;
}