@promptui-lib/figma-parser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Figma REST API Client
3
+ * Cliente para comunicação com a API do Figma
4
+ */
5
+ import type { IFigmaNode, IFigmaFile } from '@promptui-lib/core';
6
+ export interface IFigmaClientConfig {
7
+ token: string;
8
+ timeout?: number;
9
+ }
10
+ export interface IGetFileNodesResponse {
11
+ name: string;
12
+ lastModified: string;
13
+ nodes: Record<string, {
14
+ document: IFigmaNode;
15
+ }>;
16
+ }
17
+ export declare class FigmaClient {
18
+ private baseUrl;
19
+ private token;
20
+ private timeout;
21
+ constructor(config: IFigmaClientConfig);
22
+ /**
23
+ * Faz requisição autenticada para a API do Figma
24
+ */
25
+ private request;
26
+ /**
27
+ * Busca arquivo completo do Figma
28
+ */
29
+ getFile(fileId: string): Promise<IFigmaFile>;
30
+ /**
31
+ * Busca nodes específicos de um arquivo
32
+ */
33
+ getFileNodes(fileId: string, nodeIds: string[]): Promise<IGetFileNodesResponse>;
34
+ /**
35
+ * Busca um único node
36
+ */
37
+ getNode(fileId: string, nodeId: string): Promise<IFigmaNode | null>;
38
+ /**
39
+ * Busca imagens de nodes
40
+ */
41
+ getImages(fileId: string, nodeIds: string[], format?: 'jpg' | 'png' | 'svg' | 'pdf', scale?: number): Promise<Record<string, string>>;
42
+ /**
43
+ * Busca componentes de um arquivo
44
+ */
45
+ getComponents(fileId: string): Promise<IFigmaFile['components']>;
46
+ /**
47
+ * Busca estilos de um arquivo
48
+ */
49
+ getStyles(fileId: string): Promise<IFigmaFile['styles']>;
50
+ /**
51
+ * Lista todos os frames/componentes de nível superior
52
+ */
53
+ listTopLevelFrames(fileId: string): Promise<IFigmaNode[]>;
54
+ }
55
+ /**
56
+ * Erro específico da API do Figma
57
+ */
58
+ export declare class FigmaApiError extends Error {
59
+ statusCode: number;
60
+ constructor(message: string, statusCode: number);
61
+ }
62
+ /**
63
+ * Cria instância do cliente Figma
64
+ */
65
+ export declare function createFigmaClient(config: IFigmaClientConfig): FigmaClient;
66
+ //# sourceMappingURL=figma-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-client.d.ts","sourceRoot":"","sources":["../../src/client/figma-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;CACjD;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,kBAAkB;IAKtC;;OAEG;YACW,OAAO;IA0BrB;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAIlD;;OAEG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,qBAAqB,CAAC;IAOjC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAMzE;;OAEG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,GAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAa,EAC7C,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAQlC;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAKtE;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAK9D;;OAEG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAsBhE;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAG7B,UAAU,EAAE,MAAM;gBADzB,OAAO,EAAE,MAAM,EACR,UAAU,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAEzE"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Figma REST API Client
3
+ * Cliente para comunicação com a API do Figma
4
+ */
5
+ export class FigmaClient {
6
+ baseUrl = 'https://api.figma.com/v1';
7
+ token;
8
+ timeout;
9
+ constructor(config) {
10
+ this.token = config.token;
11
+ this.timeout = config.timeout ?? 30000;
12
+ }
13
+ /**
14
+ * Faz requisição autenticada para a API do Figma
15
+ */
16
+ async request(endpoint) {
17
+ const controller = new AbortController();
18
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
19
+ try {
20
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
21
+ headers: {
22
+ 'X-Figma-Token': this.token,
23
+ },
24
+ signal: controller.signal,
25
+ });
26
+ if (!response.ok) {
27
+ const error = await response.text();
28
+ throw new FigmaApiError(`Figma API error: ${response.status} - ${error}`, response.status);
29
+ }
30
+ return response.json();
31
+ }
32
+ finally {
33
+ clearTimeout(timeoutId);
34
+ }
35
+ }
36
+ /**
37
+ * Busca arquivo completo do Figma
38
+ */
39
+ async getFile(fileId) {
40
+ return this.request(`/files/${fileId}`);
41
+ }
42
+ /**
43
+ * Busca nodes específicos de um arquivo
44
+ */
45
+ async getFileNodes(fileId, nodeIds) {
46
+ const ids = nodeIds.join(',');
47
+ return this.request(`/files/${fileId}/nodes?ids=${encodeURIComponent(ids)}`);
48
+ }
49
+ /**
50
+ * Busca um único node
51
+ */
52
+ async getNode(fileId, nodeId) {
53
+ const response = await this.getFileNodes(fileId, [nodeId]);
54
+ const nodeData = response.nodes[nodeId];
55
+ return nodeData?.document ?? null;
56
+ }
57
+ /**
58
+ * Busca imagens de nodes
59
+ */
60
+ async getImages(fileId, nodeIds, format = 'png', scale = 2) {
61
+ const ids = nodeIds.join(',');
62
+ const response = await this.request(`/images/${fileId}?ids=${encodeURIComponent(ids)}&format=${format}&scale=${scale}`);
63
+ return response.images;
64
+ }
65
+ /**
66
+ * Busca componentes de um arquivo
67
+ */
68
+ async getComponents(fileId) {
69
+ const file = await this.getFile(fileId);
70
+ return file.components;
71
+ }
72
+ /**
73
+ * Busca estilos de um arquivo
74
+ */
75
+ async getStyles(fileId) {
76
+ const file = await this.getFile(fileId);
77
+ return file.styles;
78
+ }
79
+ /**
80
+ * Lista todos os frames/componentes de nível superior
81
+ */
82
+ async listTopLevelFrames(fileId) {
83
+ const file = await this.getFile(fileId);
84
+ const frames = [];
85
+ // Percorre as páginas (CANVAS) do documento
86
+ for (const page of file.document.children ?? []) {
87
+ if (page.type === 'CANVAS') {
88
+ // Adiciona frames de nível superior da página
89
+ for (const child of page.children ?? []) {
90
+ if (child.type === 'FRAME' ||
91
+ child.type === 'COMPONENT' ||
92
+ child.type === 'COMPONENT_SET') {
93
+ frames.push(child);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return frames;
99
+ }
100
+ }
101
+ /**
102
+ * Erro específico da API do Figma
103
+ */
104
+ export class FigmaApiError extends Error {
105
+ statusCode;
106
+ constructor(message, statusCode) {
107
+ super(message);
108
+ this.statusCode = statusCode;
109
+ this.name = 'FigmaApiError';
110
+ }
111
+ }
112
+ /**
113
+ * Cria instância do cliente Figma
114
+ */
115
+ export function createFigmaClient(config) {
116
+ return new FigmaClient(config);
117
+ }
@@ -0,0 +1,3 @@
1
+ export { FigmaClient, FigmaApiError, createFigmaClient } from './figma-client.js';
2
+ export type { IFigmaClientConfig, IGetFileNodesResponse } from './figma-client.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAClF,YAAY,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1 @@
1
+ export { FigmaClient, FigmaApiError, createFigmaClient } from './figma-client.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @promptui-lib/figma-parser
3
+ * Figma API client and parser for PromptUI
4
+ */
5
+ export * from './client/index.js';
6
+ export * from './parser/index.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @promptui-lib/figma-parser
3
+ * Figma API client and parser for PromptUI
4
+ */
5
+ export * from './client/index.js';
6
+ export * from './parser/index.js';
@@ -0,0 +1,9 @@
1
+ export { parseLayout, parseSizing, parseLayoutAndSizing } from './layout-parser.js';
2
+ export type { ILayoutProperties } from './layout-parser.js';
3
+ export { parseStyles, parseBackgroundColor, parseBorder, parseBorderRadius, parseBoxShadow } from './style-parser.js';
4
+ export type { IStyleProperties } from './style-parser.js';
5
+ export { parseTextStyles, extractTextContent } from './text-parser.js';
6
+ export type { ITextProperties } from './text-parser.js';
7
+ export { parseNode, parseNodes, parseFrameName } from './node-parser.js';
8
+ export type { IParseOptions } from './node-parser.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACtH,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { parseLayout, parseSizing, parseLayoutAndSizing } from './layout-parser.js';
2
+ export { parseStyles, parseBackgroundColor, parseBorder, parseBorderRadius, parseBoxShadow } from './style-parser.js';
3
+ export { parseTextStyles, extractTextContent } from './text-parser.js';
4
+ export { parseNode, parseNodes, parseFrameName } from './node-parser.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Layout Parser
3
+ * Converte AutoLayout do Figma para propriedades CSS flexbox
4
+ */
5
+ import type { IFigmaNode, IStyleProperty } from '@promptui-lib/core';
6
+ export interface ILayoutProperties {
7
+ display?: IStyleProperty;
8
+ flexDirection?: IStyleProperty;
9
+ justifyContent?: IStyleProperty;
10
+ alignItems?: IStyleProperty;
11
+ gap?: IStyleProperty;
12
+ padding?: IStyleProperty;
13
+ width?: IStyleProperty;
14
+ height?: IStyleProperty;
15
+ flex?: IStyleProperty;
16
+ flexWrap?: IStyleProperty;
17
+ }
18
+ /**
19
+ * Parseia AutoLayout de um node
20
+ */
21
+ export declare function parseLayout(node: IFigmaNode): ILayoutProperties;
22
+ /**
23
+ * Parseia sizing de um node
24
+ */
25
+ export declare function parseSizing(node: IFigmaNode): ILayoutProperties;
26
+ /**
27
+ * Combina layout e sizing
28
+ */
29
+ export declare function parseLayoutAndSizing(node: IFigmaNode): IStyleProperty[];
30
+ //# sourceMappingURL=layout-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-parser.d.ts","sourceRoot":"","sources":["../../src/parser/layout-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGrE,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CA2F/D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAwD/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,EAAE,CASvE"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Layout Parser
3
+ * Converte AutoLayout do Figma para propriedades CSS flexbox
4
+ */
5
+ import { findGapToken, generatePaddingToken } from '@promptui-lib/core';
6
+ /**
7
+ * Parseia AutoLayout de um node
8
+ */
9
+ export function parseLayout(node) {
10
+ const properties = {};
11
+ // Se não tem AutoLayout, retorna vazio
12
+ if (!node.layoutMode || node.layoutMode === 'NONE') {
13
+ return properties;
14
+ }
15
+ // Display flex
16
+ properties.display = {
17
+ property: 'display',
18
+ value: 'flex',
19
+ };
20
+ // Flex direction
21
+ properties.flexDirection = {
22
+ property: 'flex-direction',
23
+ value: node.layoutMode === 'HORIZONTAL' ? 'row' : 'column',
24
+ };
25
+ // Justify content (primary axis)
26
+ if (node.primaryAxisAlignItems) {
27
+ const justifyMap = {
28
+ MIN: 'flex-start',
29
+ CENTER: 'center',
30
+ MAX: 'flex-end',
31
+ SPACE_BETWEEN: 'space-between',
32
+ };
33
+ properties.justifyContent = {
34
+ property: 'justify-content',
35
+ value: justifyMap[node.primaryAxisAlignItems] ?? 'flex-start',
36
+ };
37
+ }
38
+ // Align items (counter axis)
39
+ if (node.counterAxisAlignItems) {
40
+ const alignMap = {
41
+ MIN: 'flex-start',
42
+ CENTER: 'center',
43
+ MAX: 'flex-end',
44
+ BASELINE: 'baseline',
45
+ };
46
+ properties.alignItems = {
47
+ property: 'align-items',
48
+ value: alignMap[node.counterAxisAlignItems] ?? 'flex-start',
49
+ };
50
+ }
51
+ // Gap
52
+ if (node.itemSpacing !== undefined && node.itemSpacing > 0) {
53
+ const gapToken = findGapToken(node.itemSpacing);
54
+ properties.gap = {
55
+ property: 'gap',
56
+ value: gapToken,
57
+ token: gapToken.startsWith('$') ? gapToken : undefined,
58
+ };
59
+ }
60
+ // Padding
61
+ const haspadding = node.paddingTop ||
62
+ node.paddingRight ||
63
+ node.paddingBottom ||
64
+ node.paddingLeft;
65
+ if (haspadding) {
66
+ const paddingToken = generatePaddingToken(node.paddingTop ?? 0, node.paddingRight ?? 0, node.paddingBottom ?? 0, node.paddingLeft ?? 0);
67
+ properties.padding = {
68
+ property: 'padding',
69
+ value: paddingToken,
70
+ token: paddingToken.startsWith('$') ? paddingToken : undefined,
71
+ };
72
+ }
73
+ // Flex wrap
74
+ if (node.layoutWrap === 'WRAP') {
75
+ properties.flexWrap = {
76
+ property: 'flex-wrap',
77
+ value: 'wrap',
78
+ };
79
+ }
80
+ return properties;
81
+ }
82
+ /**
83
+ * Parseia sizing de um node
84
+ */
85
+ export function parseSizing(node) {
86
+ const properties = {};
87
+ // Width
88
+ if (node.layoutSizingHorizontal) {
89
+ switch (node.layoutSizingHorizontal) {
90
+ case 'FILL':
91
+ properties.width = {
92
+ property: 'width',
93
+ value: '100%',
94
+ };
95
+ break;
96
+ case 'HUG':
97
+ properties.width = {
98
+ property: 'width',
99
+ value: 'fit-content',
100
+ };
101
+ break;
102
+ case 'FIXED':
103
+ if (node.absoluteBoundingBox?.width) {
104
+ properties.width = {
105
+ property: 'width',
106
+ value: `${node.absoluteBoundingBox.width}px`,
107
+ };
108
+ }
109
+ break;
110
+ }
111
+ }
112
+ // Height
113
+ if (node.layoutSizingVertical) {
114
+ switch (node.layoutSizingVertical) {
115
+ case 'FILL':
116
+ properties.height = {
117
+ property: 'height',
118
+ value: '100%',
119
+ };
120
+ break;
121
+ case 'HUG':
122
+ properties.height = {
123
+ property: 'height',
124
+ value: 'fit-content',
125
+ };
126
+ break;
127
+ case 'FIXED':
128
+ if (node.absoluteBoundingBox?.height) {
129
+ properties.height = {
130
+ property: 'height',
131
+ value: `${node.absoluteBoundingBox.height}px`,
132
+ };
133
+ }
134
+ break;
135
+ }
136
+ }
137
+ return properties;
138
+ }
139
+ /**
140
+ * Combina layout e sizing
141
+ */
142
+ export function parseLayoutAndSizing(node) {
143
+ const layout = parseLayout(node);
144
+ const sizing = parseSizing(node);
145
+ const combined = { ...layout, ...sizing };
146
+ return Object.values(combined).filter((prop) => prop !== undefined);
147
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Node Parser
3
+ * Parser principal que converte node do Figma em AST do componente
4
+ */
5
+ import type { IFigmaNode, IComponentAST, ComponentLayer, IParsedFrameName } from '@promptui-lib/core';
6
+ export interface IParseOptions {
7
+ forceLayer?: ComponentLayer;
8
+ componentName?: string;
9
+ }
10
+ /**
11
+ * Parseia o nome do frame para extrair informações
12
+ */
13
+ export declare function parseFrameName(name: string): IParsedFrameName;
14
+ /**
15
+ * Parser principal - converte node do Figma em AST do componente
16
+ */
17
+ export declare function parseNode(node: IFigmaNode, options?: IParseOptions): IComponentAST;
18
+ /**
19
+ * Parseia múltiplos nodes
20
+ */
21
+ export declare function parseNodes(nodes: IFigmaNode[], options?: IParseOptions): IComponentAST[];
22
+ //# sourceMappingURL=node-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-parser.d.ts","sourceRoot":"","sources":["../../src/parser/node-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EAKb,cAAc,EACd,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAc5B,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAwB7D;AA4ID;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,aAAkB,GAC1B,aAAa,CA6Cf;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,GAAE,aAAkB,GAC1B,aAAa,EAAE,CAEjB"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Node Parser
3
+ * Parser principal que converte node do Figma em AST do componente
4
+ */
5
+ import { generateComponentName, generateFileName, generateBEMBlock, generateBEMElement, classifyComponent, extractBaseName, extractVariants, } from '@promptui-lib/core';
6
+ import { parseLayoutAndSizing } from './layout-parser.js';
7
+ import { parseStyles } from './style-parser.js';
8
+ import { parseTextStyles } from './text-parser.js';
9
+ /**
10
+ * Parseia o nome do frame para extrair informações
11
+ */
12
+ export function parseFrameName(name) {
13
+ const isExportable = name.startsWith('#');
14
+ const isInternal = name.startsWith('_');
15
+ // Remove prefixos especiais
16
+ let cleanName = name.replace(/^[#_]/, '');
17
+ // Extrai props do formato $prop:name
18
+ const propMatches = cleanName.match(/\$prop:(\w+)/g) ?? [];
19
+ const extractedProps = propMatches.map((match) => {
20
+ const propName = match.replace('$prop:', '');
21
+ return { name: propName, type: 'string' };
22
+ });
23
+ // Remove props do nome
24
+ cleanName = cleanName.replace(/\$prop:\w+/g, '').trim();
25
+ return {
26
+ baseName: extractBaseName(cleanName),
27
+ variants: extractVariants(cleanName),
28
+ isExportable,
29
+ isInternal,
30
+ extractedProps,
31
+ };
32
+ }
33
+ /**
34
+ * Converte um node em elemento JSX
35
+ */
36
+ function nodeToJSX(node, blockName, depth = 0) {
37
+ const isRoot = depth === 0;
38
+ const elementName = isRoot ? '' : node.name.toLowerCase().replace(/\s+/g, '-');
39
+ const className = isRoot
40
+ ? blockName
41
+ : generateBEMElement(blockName, elementName);
42
+ // Determina a tag HTML
43
+ let tag = 'div';
44
+ if (node.type === 'TEXT') {
45
+ tag = 'span';
46
+ }
47
+ // Processa filhos
48
+ const children = [];
49
+ if (node.type === 'TEXT' && node.characters) {
50
+ children.push({ type: 'text', value: node.characters });
51
+ }
52
+ else if (node.children) {
53
+ for (const child of node.children) {
54
+ // Ignora nodes invisíveis ou internos
55
+ if (child.visible === false || child.name.startsWith('_')) {
56
+ continue;
57
+ }
58
+ children.push(nodeToJSX(child, blockName, depth + 1));
59
+ }
60
+ }
61
+ return {
62
+ tag,
63
+ className,
64
+ props: {},
65
+ children,
66
+ selfClosing: children.length === 0 && node.type !== 'TEXT',
67
+ };
68
+ }
69
+ /**
70
+ * Coleta estilos de um node recursivamente
71
+ */
72
+ function collectStyles(node, blockName, depth = 0) {
73
+ const blocks = [];
74
+ const isRoot = depth === 0;
75
+ const elementName = isRoot ? '' : node.name.toLowerCase().replace(/\s+/g, '-');
76
+ const selector = isRoot
77
+ ? `.${blockName}`
78
+ : `.${generateBEMElement(blockName, elementName)}`;
79
+ // Coleta propriedades deste node
80
+ const properties = [];
81
+ // Layout
82
+ if (node.type === 'FRAME' || node.type === 'COMPONENT' || node.type === 'INSTANCE') {
83
+ properties.push(...parseLayoutAndSizing(node));
84
+ }
85
+ // Estilos visuais
86
+ properties.push(...parseStyles(node));
87
+ // Estilos de texto
88
+ if (node.type === 'TEXT') {
89
+ properties.push(...parseTextStyles(node));
90
+ }
91
+ // Só adiciona bloco se tiver propriedades
92
+ if (properties.length > 0) {
93
+ blocks.push({
94
+ selector,
95
+ properties,
96
+ });
97
+ }
98
+ // Processa filhos
99
+ if (node.children) {
100
+ for (const child of node.children) {
101
+ if (child.visible === false || child.name.startsWith('_')) {
102
+ continue;
103
+ }
104
+ blocks.push(...collectStyles(child, blockName, depth + 1));
105
+ }
106
+ }
107
+ return blocks;
108
+ }
109
+ /**
110
+ * Extrai props do componente baseado na estrutura
111
+ */
112
+ function extractProps(node, parsedName) {
113
+ const props = [];
114
+ // Props extraídas do nome
115
+ for (const prop of parsedName.extractedProps) {
116
+ props.push({
117
+ name: prop.name,
118
+ type: prop.type,
119
+ required: false,
120
+ });
121
+ }
122
+ // Prop children se tiver filhos de texto
123
+ const hasTextChildren = node.children?.some((child) => child.type === 'TEXT');
124
+ if (hasTextChildren) {
125
+ props.push({
126
+ name: 'children',
127
+ type: 'ReactNode',
128
+ required: false,
129
+ });
130
+ }
131
+ // Prop className padrão
132
+ props.push({
133
+ name: 'className',
134
+ type: 'string',
135
+ required: false,
136
+ defaultValue: "''",
137
+ });
138
+ return props;
139
+ }
140
+ /**
141
+ * Parser principal - converte node do Figma em AST do componente
142
+ */
143
+ export function parseNode(node, options = {}) {
144
+ const parsedName = parseFrameName(node.name);
145
+ // Nome do componente
146
+ const name = options.componentName ?? generateComponentName(node.name);
147
+ const fileName = generateFileName(name);
148
+ // Classificação
149
+ const layer = classifyComponent(node, options.forceLayer);
150
+ // Classe BEM base
151
+ const blockName = generateBEMBlock(name);
152
+ // Props
153
+ const props = extractProps(node, parsedName);
154
+ // JSX
155
+ const jsx = nodeToJSX(node, blockName);
156
+ // Styles
157
+ const styles = collectStyles(node, blockName);
158
+ // Imports
159
+ const imports = [
160
+ {
161
+ from: 'react',
162
+ named: props.some((p) => p.type === 'ReactNode') ? ['ReactNode'] : undefined,
163
+ typeOnly: true,
164
+ },
165
+ {
166
+ from: `./${fileName}.scss`,
167
+ default: undefined,
168
+ named: undefined,
169
+ },
170
+ ].filter((imp) => imp.named || imp.default || imp.from.endsWith('.scss'));
171
+ return {
172
+ name,
173
+ fileName,
174
+ layer,
175
+ props,
176
+ jsx,
177
+ styles,
178
+ imports,
179
+ };
180
+ }
181
+ /**
182
+ * Parseia múltiplos nodes
183
+ */
184
+ export function parseNodes(nodes, options = {}) {
185
+ return nodes.map((node) => parseNode(node, options));
186
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Style Parser
3
+ * Converte fills, strokes, effects do Figma para CSS
4
+ */
5
+ import type { IFigmaNode, IStyleProperty } from '@promptui-lib/core';
6
+ export interface IStyleProperties {
7
+ backgroundColor?: IStyleProperty;
8
+ color?: IStyleProperty;
9
+ borderRadius?: IStyleProperty;
10
+ border?: IStyleProperty;
11
+ boxShadow?: IStyleProperty;
12
+ opacity?: IStyleProperty;
13
+ }
14
+ /**
15
+ * Parseia background color de um node
16
+ */
17
+ export declare function parseBackgroundColor(node: IFigmaNode): IStyleProperty | null;
18
+ /**
19
+ * Parseia border de um node
20
+ */
21
+ export declare function parseBorder(node: IFigmaNode): IStyleProperty | null;
22
+ /**
23
+ * Parseia border-radius de um node
24
+ */
25
+ export declare function parseBorderRadius(node: IFigmaNode): IStyleProperty | null;
26
+ /**
27
+ * Parseia box-shadow de um node
28
+ */
29
+ export declare function parseBoxShadow(node: IFigmaNode): IStyleProperty | null;
30
+ /**
31
+ * Parseia opacity de um node
32
+ */
33
+ export declare function parseOpacity(node: IFigmaNode): IStyleProperty | null;
34
+ /**
35
+ * Parseia todos os estilos visuais de um node
36
+ */
37
+ export declare function parseStyles(node: IFigmaNode): IStyleProperty[];
38
+ //# sourceMappingURL=style-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-parser.d.ts","sourceRoot":"","sources":["../../src/parser/style-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,UAAU,EAIV,cAAc,EACf,MAAM,oBAAoB,CAAC;AAS5B,MAAM,WAAW,gBAAgB;IAC/B,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAWD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAsB5E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAuBnE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAyBzE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAmCtE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CASpE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,EAAE,CAmB9D"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Style Parser
3
+ * Converte fills, strokes, effects do Figma para CSS
4
+ */
5
+ import { rgbaToHex, findColorToken, findRadiusToken, findShadowToken, generateBorderRadius, } from '@promptui-lib/core';
6
+ /**
7
+ * Extrai cor de um fill sólido
8
+ */
9
+ function extractSolidColor(fill) {
10
+ const { r, g, b, a } = fill.color;
11
+ const opacity = fill.opacity ?? 1;
12
+ return rgbaToHex(r, g, b, a * opacity);
13
+ }
14
+ /**
15
+ * Parseia background color de um node
16
+ */
17
+ export function parseBackgroundColor(node) {
18
+ if (!node.fills || node.fills.length === 0) {
19
+ return null;
20
+ }
21
+ // Pega o primeiro fill visível sólido
22
+ const solidFill = node.fills.find((fill) => fill.type === 'SOLID');
23
+ if (!solidFill) {
24
+ return null;
25
+ }
26
+ const hex = extractSolidColor(solidFill);
27
+ const token = findColorToken(hex);
28
+ return {
29
+ property: 'background-color',
30
+ value: token ?? hex,
31
+ token: token ?? undefined,
32
+ };
33
+ }
34
+ /**
35
+ * Parseia border de um node
36
+ */
37
+ export function parseBorder(node) {
38
+ if (!node.strokes || node.strokes.length === 0 || !node.strokeWeight) {
39
+ return null;
40
+ }
41
+ const stroke = node.strokes[0];
42
+ if (stroke.type !== 'SOLID') {
43
+ return null;
44
+ }
45
+ const hex = rgbaToHex(stroke.color.r, stroke.color.g, stroke.color.b, stroke.opacity ?? 1);
46
+ const token = findColorToken(hex);
47
+ return {
48
+ property: 'border',
49
+ value: `${node.strokeWeight}px solid ${token ?? hex}`,
50
+ token: token ?? undefined,
51
+ };
52
+ }
53
+ /**
54
+ * Parseia border-radius de um node
55
+ */
56
+ export function parseBorderRadius(node) {
57
+ // Verifica se tem corner radius diferentes
58
+ if (node.rectangleCornerRadii) {
59
+ const [tl, tr, br, bl] = node.rectangleCornerRadii;
60
+ const radius = generateBorderRadius(tl, tr, br, bl);
61
+ return {
62
+ property: 'border-radius',
63
+ value: radius,
64
+ token: radius.startsWith('$') ? radius : undefined,
65
+ };
66
+ }
67
+ // Corner radius uniforme
68
+ if (node.cornerRadius !== undefined && node.cornerRadius > 0) {
69
+ const token = findRadiusToken(node.cornerRadius);
70
+ return {
71
+ property: 'border-radius',
72
+ value: token,
73
+ token: token.startsWith('$') ? token : undefined,
74
+ };
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * Parseia box-shadow de um node
80
+ */
81
+ export function parseBoxShadow(node) {
82
+ if (!node.effects || node.effects.length === 0) {
83
+ return null;
84
+ }
85
+ // Filtra apenas drop shadows visíveis
86
+ const shadows = node.effects.filter((effect) => effect.type === 'DROP_SHADOW' && effect.visible);
87
+ if (shadows.length === 0) {
88
+ return null;
89
+ }
90
+ // Usa o primeiro shadow
91
+ const shadow = shadows[0];
92
+ const token = findShadowToken(shadow.radius);
93
+ if (token) {
94
+ return {
95
+ property: 'box-shadow',
96
+ value: token,
97
+ token,
98
+ };
99
+ }
100
+ // Gera box-shadow customizado
101
+ const { offset, radius, spread, color } = shadow;
102
+ const hex = rgbaToHex(color.r, color.g, color.b, color.a);
103
+ return {
104
+ property: 'box-shadow',
105
+ value: `${offset.x}px ${offset.y}px ${radius}px ${spread ?? 0}px ${hex}`,
106
+ };
107
+ }
108
+ /**
109
+ * Parseia opacity de um node
110
+ */
111
+ export function parseOpacity(node) {
112
+ if (node.opacity !== undefined && node.opacity < 1) {
113
+ return {
114
+ property: 'opacity',
115
+ value: `${node.opacity}`,
116
+ };
117
+ }
118
+ return null;
119
+ }
120
+ /**
121
+ * Parseia todos os estilos visuais de um node
122
+ */
123
+ export function parseStyles(node) {
124
+ const properties = [];
125
+ const background = parseBackgroundColor(node);
126
+ if (background)
127
+ properties.push(background);
128
+ const border = parseBorder(node);
129
+ if (border)
130
+ properties.push(border);
131
+ const borderRadius = parseBorderRadius(node);
132
+ if (borderRadius)
133
+ properties.push(borderRadius);
134
+ const boxShadow = parseBoxShadow(node);
135
+ if (boxShadow)
136
+ properties.push(boxShadow);
137
+ const opacity = parseOpacity(node);
138
+ if (opacity)
139
+ properties.push(opacity);
140
+ return properties;
141
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Text Parser
3
+ * Converte estilos de texto do Figma para CSS
4
+ */
5
+ import type { IFigmaNode, IStyleProperty, ITextStyle } from '@promptui-lib/core';
6
+ export interface ITextProperties {
7
+ fontFamily?: IStyleProperty;
8
+ fontSize?: IStyleProperty;
9
+ fontWeight?: IStyleProperty;
10
+ lineHeight?: IStyleProperty;
11
+ letterSpacing?: IStyleProperty;
12
+ textAlign?: IStyleProperty;
13
+ textDecoration?: IStyleProperty;
14
+ textTransform?: IStyleProperty;
15
+ color?: IStyleProperty;
16
+ }
17
+ /**
18
+ * Parseia font-family
19
+ */
20
+ export declare function parseFontFamily(style: ITextStyle): IStyleProperty;
21
+ /**
22
+ * Parseia font-size
23
+ */
24
+ export declare function parseFontSize(style: ITextStyle): IStyleProperty;
25
+ /**
26
+ * Parseia font-weight
27
+ */
28
+ export declare function parseFontWeight(style: ITextStyle): IStyleProperty;
29
+ /**
30
+ * Parseia line-height
31
+ */
32
+ export declare function parseLineHeight(style: ITextStyle): IStyleProperty | null;
33
+ /**
34
+ * Parseia letter-spacing
35
+ */
36
+ export declare function parseLetterSpacing(style: ITextStyle): IStyleProperty | null;
37
+ /**
38
+ * Parseia text-align
39
+ */
40
+ export declare function parseTextAlign(style: ITextStyle): IStyleProperty | null;
41
+ /**
42
+ * Parseia text-decoration
43
+ */
44
+ export declare function parseTextDecoration(style: ITextStyle): IStyleProperty | null;
45
+ /**
46
+ * Parseia text-transform
47
+ */
48
+ export declare function parseTextTransform(style: ITextStyle): IStyleProperty | null;
49
+ /**
50
+ * Parseia cor do texto
51
+ */
52
+ export declare function parseTextColor(node: IFigmaNode): IStyleProperty | null;
53
+ /**
54
+ * Parseia todos os estilos de texto de um node
55
+ */
56
+ export declare function parseTextStyles(node: IFigmaNode): IStyleProperty[];
57
+ /**
58
+ * Extrai conteúdo de texto de um node
59
+ */
60
+ export declare function extractTextContent(node: IFigmaNode): string | null;
61
+ //# sourceMappingURL=text-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-parser.d.ts","sourceRoot":"","sources":["../../src/parser/text-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUjF,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAQjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAQ/D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAQjE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAyBxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAS3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAevE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAc5E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAe3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAoBtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,EAAE,CA0ClE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAMlE"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Text Parser
3
+ * Converte estilos de texto do Figma para CSS
4
+ */
5
+ import { rgbaToHex, findColorToken, findFontSizeToken, findFontWeightToken, findLineHeightToken, findFontFamilyToken, } from '@promptui-lib/core';
6
+ /**
7
+ * Parseia font-family
8
+ */
9
+ export function parseFontFamily(style) {
10
+ const token = findFontFamilyToken(style.fontFamily);
11
+ return {
12
+ property: 'font-family',
13
+ value: token,
14
+ token: token.startsWith('$') ? token : undefined,
15
+ };
16
+ }
17
+ /**
18
+ * Parseia font-size
19
+ */
20
+ export function parseFontSize(style) {
21
+ const token = findFontSizeToken(style.fontSize);
22
+ return {
23
+ property: 'font-size',
24
+ value: token,
25
+ token: token.startsWith('$') ? token : undefined,
26
+ };
27
+ }
28
+ /**
29
+ * Parseia font-weight
30
+ */
31
+ export function parseFontWeight(style) {
32
+ const token = findFontWeightToken(style.fontWeight);
33
+ return {
34
+ property: 'font-weight',
35
+ value: token,
36
+ token: token.startsWith('$') ? token : undefined,
37
+ };
38
+ }
39
+ /**
40
+ * Parseia line-height
41
+ */
42
+ export function parseLineHeight(style) {
43
+ if (style.lineHeightPx) {
44
+ // Calcula ratio baseado no fontSize
45
+ const ratio = style.lineHeightPx / style.fontSize;
46
+ const token = findLineHeightToken(ratio);
47
+ return {
48
+ property: 'line-height',
49
+ value: token,
50
+ token: token.startsWith('$') ? token : undefined,
51
+ };
52
+ }
53
+ if (style.lineHeightPercent) {
54
+ const ratio = style.lineHeightPercent / 100;
55
+ const token = findLineHeightToken(ratio);
56
+ return {
57
+ property: 'line-height',
58
+ value: token,
59
+ token: token.startsWith('$') ? token : undefined,
60
+ };
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * Parseia letter-spacing
66
+ */
67
+ export function parseLetterSpacing(style) {
68
+ if (style.letterSpacing && style.letterSpacing !== 0) {
69
+ return {
70
+ property: 'letter-spacing',
71
+ value: `${style.letterSpacing}px`,
72
+ };
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Parseia text-align
78
+ */
79
+ export function parseTextAlign(style) {
80
+ if (!style.textAlignHorizontal || style.textAlignHorizontal === 'LEFT') {
81
+ return null;
82
+ }
83
+ const alignMap = {
84
+ CENTER: 'center',
85
+ RIGHT: 'right',
86
+ JUSTIFIED: 'justify',
87
+ };
88
+ return {
89
+ property: 'text-align',
90
+ value: alignMap[style.textAlignHorizontal] ?? 'left',
91
+ };
92
+ }
93
+ /**
94
+ * Parseia text-decoration
95
+ */
96
+ export function parseTextDecoration(style) {
97
+ if (!style.textDecoration || style.textDecoration === 'NONE') {
98
+ return null;
99
+ }
100
+ const decorationMap = {
101
+ UNDERLINE: 'underline',
102
+ STRIKETHROUGH: 'line-through',
103
+ };
104
+ return {
105
+ property: 'text-decoration',
106
+ value: decorationMap[style.textDecoration] ?? 'none',
107
+ };
108
+ }
109
+ /**
110
+ * Parseia text-transform
111
+ */
112
+ export function parseTextTransform(style) {
113
+ if (!style.textCase || style.textCase === 'ORIGINAL') {
114
+ return null;
115
+ }
116
+ const transformMap = {
117
+ UPPER: 'uppercase',
118
+ LOWER: 'lowercase',
119
+ TITLE: 'capitalize',
120
+ };
121
+ return {
122
+ property: 'text-transform',
123
+ value: transformMap[style.textCase] ?? 'none',
124
+ };
125
+ }
126
+ /**
127
+ * Parseia cor do texto
128
+ */
129
+ export function parseTextColor(node) {
130
+ if (!node.fills || node.fills.length === 0) {
131
+ return null;
132
+ }
133
+ const solidFill = node.fills.find((fill) => fill.type === 'SOLID');
134
+ if (!solidFill || solidFill.type !== 'SOLID') {
135
+ return null;
136
+ }
137
+ const { r, g, b, a } = solidFill.color;
138
+ const opacity = solidFill.opacity ?? 1;
139
+ const hex = rgbaToHex(r, g, b, a * opacity);
140
+ const token = findColorToken(hex);
141
+ return {
142
+ property: 'color',
143
+ value: token ?? hex,
144
+ token: token ?? undefined,
145
+ };
146
+ }
147
+ /**
148
+ * Parseia todos os estilos de texto de um node
149
+ */
150
+ export function parseTextStyles(node) {
151
+ if (node.type !== 'TEXT' || !node.style) {
152
+ return [];
153
+ }
154
+ const style = node.style;
155
+ const properties = [];
156
+ // Font family
157
+ properties.push(parseFontFamily(style));
158
+ // Font size
159
+ properties.push(parseFontSize(style));
160
+ // Font weight
161
+ properties.push(parseFontWeight(style));
162
+ // Line height
163
+ const lineHeight = parseLineHeight(style);
164
+ if (lineHeight)
165
+ properties.push(lineHeight);
166
+ // Letter spacing
167
+ const letterSpacing = parseLetterSpacing(style);
168
+ if (letterSpacing)
169
+ properties.push(letterSpacing);
170
+ // Text align
171
+ const textAlign = parseTextAlign(style);
172
+ if (textAlign)
173
+ properties.push(textAlign);
174
+ // Text decoration
175
+ const textDecoration = parseTextDecoration(style);
176
+ if (textDecoration)
177
+ properties.push(textDecoration);
178
+ // Text transform
179
+ const textTransform = parseTextTransform(style);
180
+ if (textTransform)
181
+ properties.push(textTransform);
182
+ // Color
183
+ const color = parseTextColor(node);
184
+ if (color)
185
+ properties.push(color);
186
+ return properties;
187
+ }
188
+ /**
189
+ * Extrai conteúdo de texto de um node
190
+ */
191
+ export function extractTextContent(node) {
192
+ if (node.type === 'TEXT' && node.characters) {
193
+ return node.characters;
194
+ }
195
+ return null;
196
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@promptui-lib/figma-parser",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Figma API client and parser for PromptUI",
6
+ "license": "UNLICENSED",
7
+ "author": {
8
+ "name": "Desiree Menezes",
9
+ "url": "https://github.com/desireemenezes"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/desireemenezes/promptUI.git",
14
+ "directory": "packages/figma-parser"
15
+ },
16
+ "homepage": "https://github.com/desireemenezes/promptUI#readme",
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "dependencies": {
33
+ "@promptui-lib/core": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "typescript": "^5.3.0",
38
+ "rimraf": "^5.0.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "dev": "tsc --watch",
43
+ "clean": "rimraf dist"
44
+ }
45
+ }