@promptui-lib/codegen 0.1.4 → 0.1.5
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.
- package/dist/detection/component-detector.d.ts +34 -0
- package/dist/detection/component-detector.d.ts.map +1 -0
- package/dist/detection/component-detector.js +564 -0
- package/dist/detection/index.d.ts +7 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +6 -0
- package/dist/detection/raw-css-generator.d.ts +27 -0
- package/dist/detection/raw-css-generator.d.ts.map +1 -0
- package/dist/detection/raw-css-generator.js +473 -0
- package/dist/frameworks/antd.template.d.ts +18 -0
- package/dist/frameworks/antd.template.d.ts.map +1 -0
- package/dist/frameworks/antd.template.js +268 -0
- package/dist/frameworks/index.d.ts +2 -1
- package/dist/frameworks/index.d.ts.map +1 -1
- package/dist/frameworks/index.js +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/mappings/component-mappings.d.ts +6 -0
- package/dist/mappings/component-mappings.d.ts.map +1 -1
- package/dist/mappings/component-mappings.js +496 -0
- package/package.json +2 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intelligent Component Detector
|
|
3
|
+
* Detects component types from Figma nodes using multiple strategies:
|
|
4
|
+
* 1. Fuzzy name matching (multilingual)
|
|
5
|
+
* 2. Visual property heuristics
|
|
6
|
+
* 3. Structure-based detection
|
|
7
|
+
*/
|
|
8
|
+
import type { IFigmaNode } from '@promptui-lib/core';
|
|
9
|
+
/**
|
|
10
|
+
* Detected component type with confidence score
|
|
11
|
+
*/
|
|
12
|
+
export interface IDetectionResult {
|
|
13
|
+
componentType: string;
|
|
14
|
+
confidence: number;
|
|
15
|
+
strategy: 'name' | 'visual' | 'structure' | 'hybrid';
|
|
16
|
+
details?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Main detection function - combines all strategies
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectComponentType(node: IFigmaNode): IDetectionResult;
|
|
22
|
+
/**
|
|
23
|
+
* Detect all components in a tree
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectComponentsInTree(node: IFigmaNode, results?: Map<string, IDetectionResult>): Map<string, IDetectionResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Get component type with fallback to raw CSS generation
|
|
28
|
+
*/
|
|
29
|
+
export declare function getComponentTypeOrRaw(node: IFigmaNode): {
|
|
30
|
+
type: 'component' | 'raw';
|
|
31
|
+
componentType?: string;
|
|
32
|
+
confidence?: number;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=component-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-detector.d.ts","sourceRoot":"","sources":["../../src/detection/component-detector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmgBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,gBAAgB,CAkEtE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAa,GACjD,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAW/B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG;IACvD,IAAI,EAAE,WAAW,GAAG,KAAK,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAaA"}
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intelligent Component Detector
|
|
3
|
+
* Detects component types from Figma nodes using multiple strategies:
|
|
4
|
+
* 1. Fuzzy name matching (multilingual)
|
|
5
|
+
* 2. Visual property heuristics
|
|
6
|
+
* 3. Structure-based detection
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Name patterns for fuzzy matching (multilingual)
|
|
10
|
+
*/
|
|
11
|
+
const NAME_PATTERNS = {
|
|
12
|
+
button: [
|
|
13
|
+
/button/i, /btn/i, /botão/i, /botao/i, /cta/i, /submit/i, /enviar/i,
|
|
14
|
+
/action/i, /ação/i, /acao/i, /confirmar/i, /cancelar/i, /salvar/i, /save/i,
|
|
15
|
+
/click/i, /tap/i, /press/i,
|
|
16
|
+
],
|
|
17
|
+
input: [
|
|
18
|
+
/input/i, /text.?field/i, /entrada/i, /campo/i, /field/i,
|
|
19
|
+
/email/i, /password/i, /senha/i, /search/i, /busca/i, /pesquisa/i,
|
|
20
|
+
/nome/i, /name/i, /telefone/i, /phone/i, /cpf/i, /cnpj/i,
|
|
21
|
+
],
|
|
22
|
+
card: [
|
|
23
|
+
/card/i, /cartão/i, /cartao/i, /tile/i, /item/i, /panel/i, /painel/i,
|
|
24
|
+
/box/i, /container/i, /wrapper/i, /bloco/i, /block/i,
|
|
25
|
+
],
|
|
26
|
+
avatar: [
|
|
27
|
+
/avatar/i, /profile.?pic/i, /user.?image/i, /foto/i, /photo/i,
|
|
28
|
+
/imagem.?perfil/i, /user.?icon/i,
|
|
29
|
+
],
|
|
30
|
+
checkbox: [
|
|
31
|
+
/checkbox/i, /check.?box/i, /caixa.?seleção/i, /selecionar/i,
|
|
32
|
+
/marcar/i, /tick/i,
|
|
33
|
+
],
|
|
34
|
+
radio: [
|
|
35
|
+
/radio/i, /radio.?button/i, /opção/i, /opcao/i, /option/i,
|
|
36
|
+
],
|
|
37
|
+
switch: [
|
|
38
|
+
/switch/i, /toggle/i, /alternar/i, /on.?off/i, /liga.?desliga/i,
|
|
39
|
+
],
|
|
40
|
+
select: [
|
|
41
|
+
/select/i, /dropdown/i, /combo/i, /seleção/i, /selecao/i,
|
|
42
|
+
/escolher/i, /picker/i, /lista/i, /list/i,
|
|
43
|
+
],
|
|
44
|
+
modal: [
|
|
45
|
+
/modal/i, /dialog/i, /popup/i, /overlay/i, /alert/i, /alerta/i,
|
|
46
|
+
/confirm/i, /toast/i, /notification/i, /notificação/i,
|
|
47
|
+
],
|
|
48
|
+
tabs: [
|
|
49
|
+
/tab/i, /tabs/i, /aba/i, /abas/i, /navigation/i, /nav/i,
|
|
50
|
+
],
|
|
51
|
+
table: [
|
|
52
|
+
/table/i, /tabela/i, /grid/i, /data.?grid/i, /lista/i, /list/i,
|
|
53
|
+
/rows/i, /linhas/i,
|
|
54
|
+
],
|
|
55
|
+
icon: [
|
|
56
|
+
/icon/i, /ícone/i, /icone/i, /symbol/i, /símbolo/i, /simbolo/i,
|
|
57
|
+
/glyph/i, /svg/i,
|
|
58
|
+
],
|
|
59
|
+
image: [
|
|
60
|
+
/image/i, /imagem/i, /img/i, /photo/i, /foto/i, /picture/i,
|
|
61
|
+
/banner/i, /thumb/i, /thumbnail/i,
|
|
62
|
+
],
|
|
63
|
+
text: [
|
|
64
|
+
/text/i, /texto/i, /label/i, /título/i, /titulo/i, /title/i,
|
|
65
|
+
/heading/i, /paragraph/i, /parágrafo/i, /paragrafo/i,
|
|
66
|
+
/description/i, /descrição/i, /descricao/i,
|
|
67
|
+
],
|
|
68
|
+
badge: [
|
|
69
|
+
/badge/i, /tag/i, /chip/i, /etiqueta/i, /label/i, /status/i,
|
|
70
|
+
/indicator/i, /indicador/i,
|
|
71
|
+
],
|
|
72
|
+
progress: [
|
|
73
|
+
/progress/i, /progresso/i, /loading/i, /carregando/i, /spinner/i,
|
|
74
|
+
/loader/i, /bar/i, /barra/i,
|
|
75
|
+
],
|
|
76
|
+
divider: [
|
|
77
|
+
/divider/i, /divisor/i, /separator/i, /separador/i, /line/i, /linha/i,
|
|
78
|
+
/hr/i,
|
|
79
|
+
],
|
|
80
|
+
header: [
|
|
81
|
+
/header/i, /cabeçalho/i, /cabecalho/i, /topbar/i, /top.?bar/i,
|
|
82
|
+
/navbar/i, /nav.?bar/i, /app.?bar/i,
|
|
83
|
+
],
|
|
84
|
+
footer: [
|
|
85
|
+
/footer/i, /rodapé/i, /rodape/i, /bottom/i, /fundo/i,
|
|
86
|
+
],
|
|
87
|
+
sidebar: [
|
|
88
|
+
/sidebar/i, /side.?bar/i, /menu.?lateral/i, /drawer/i, /nav/i,
|
|
89
|
+
/navigation/i, /navegação/i, /navegacao/i,
|
|
90
|
+
],
|
|
91
|
+
form: [
|
|
92
|
+
/form/i, /formulário/i, /formulario/i, /cadastro/i, /registro/i,
|
|
93
|
+
/register/i, /login/i, /signin/i, /sign.?in/i, /signup/i, /sign.?up/i,
|
|
94
|
+
],
|
|
95
|
+
breadcrumb: [
|
|
96
|
+
/breadcrumb/i, /migalha/i, /caminho/i, /path/i, /trail/i,
|
|
97
|
+
],
|
|
98
|
+
pagination: [
|
|
99
|
+
/pagination/i, /paginação/i, /paginacao/i, /page/i, /páginas/i, /paginas/i,
|
|
100
|
+
],
|
|
101
|
+
tooltip: [
|
|
102
|
+
/tooltip/i, /dica/i, /hint/i, /popover/i, /info/i,
|
|
103
|
+
],
|
|
104
|
+
accordion: [
|
|
105
|
+
/accordion/i, /collapse/i, /expandir/i, /expand/i, /acordeão/i, /acordeao/i,
|
|
106
|
+
],
|
|
107
|
+
carousel: [
|
|
108
|
+
/carousel/i, /slider/i, /slideshow/i, /galeria/i, /gallery/i,
|
|
109
|
+
/swiper/i, /scroll/i,
|
|
110
|
+
],
|
|
111
|
+
rating: [
|
|
112
|
+
/rating/i, /avaliação/i, /avaliacao/i, /stars/i, /estrelas/i,
|
|
113
|
+
/review/i, /score/i,
|
|
114
|
+
],
|
|
115
|
+
stepper: [
|
|
116
|
+
/stepper/i, /steps/i, /wizard/i, /etapas/i, /passos/i,
|
|
117
|
+
],
|
|
118
|
+
menu: [
|
|
119
|
+
/menu/i, /dropdown.?menu/i, /context.?menu/i, /options/i, /opções/i, /opcoes/i,
|
|
120
|
+
],
|
|
121
|
+
datepicker: [
|
|
122
|
+
/date/i, /data/i, /calendar/i, /calendário/i, /calendario/i,
|
|
123
|
+
/picker/i,
|
|
124
|
+
],
|
|
125
|
+
timepicker: [
|
|
126
|
+
/time/i, /hora/i, /horário/i, /horario/i, /clock/i, /relógio/i, /relogio/i,
|
|
127
|
+
],
|
|
128
|
+
upload: [
|
|
129
|
+
/upload/i, /file/i, /arquivo/i, /anexo/i, /attachment/i,
|
|
130
|
+
/drop.?zone/i, /drag/i, /arrastar/i,
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
const VISUAL_HEURISTICS = {
|
|
134
|
+
button: {
|
|
135
|
+
minWidth: 60,
|
|
136
|
+
maxWidth: 400,
|
|
137
|
+
minHeight: 28,
|
|
138
|
+
maxHeight: 80,
|
|
139
|
+
hasCornerRadius: true,
|
|
140
|
+
minCornerRadius: 2,
|
|
141
|
+
hasFill: true,
|
|
142
|
+
hasText: true,
|
|
143
|
+
childrenCount: { min: 0, max: 3 },
|
|
144
|
+
},
|
|
145
|
+
input: {
|
|
146
|
+
minWidth: 100,
|
|
147
|
+
maxWidth: 600,
|
|
148
|
+
minHeight: 28,
|
|
149
|
+
maxHeight: 80,
|
|
150
|
+
hasCornerRadius: true,
|
|
151
|
+
hasStroke: true,
|
|
152
|
+
childrenCount: { min: 0, max: 4 },
|
|
153
|
+
},
|
|
154
|
+
card: {
|
|
155
|
+
minWidth: 150,
|
|
156
|
+
minHeight: 100,
|
|
157
|
+
hasCornerRadius: true,
|
|
158
|
+
hasFill: true,
|
|
159
|
+
hasDropShadow: true,
|
|
160
|
+
childrenCount: { min: 1, max: 20 },
|
|
161
|
+
},
|
|
162
|
+
avatar: {
|
|
163
|
+
aspectRatio: { min: 0.8, max: 1.2 },
|
|
164
|
+
minWidth: 24,
|
|
165
|
+
maxWidth: 200,
|
|
166
|
+
minCornerRadius: 12, // Usually circular
|
|
167
|
+
hasFill: true,
|
|
168
|
+
},
|
|
169
|
+
checkbox: {
|
|
170
|
+
minWidth: 14,
|
|
171
|
+
maxWidth: 32,
|
|
172
|
+
minHeight: 14,
|
|
173
|
+
maxHeight: 32,
|
|
174
|
+
aspectRatio: { min: 0.8, max: 1.2 },
|
|
175
|
+
hasStroke: true,
|
|
176
|
+
},
|
|
177
|
+
switch: {
|
|
178
|
+
minWidth: 36,
|
|
179
|
+
maxWidth: 80,
|
|
180
|
+
minHeight: 16,
|
|
181
|
+
maxHeight: 40,
|
|
182
|
+
aspectRatio: { min: 1.5, max: 3 },
|
|
183
|
+
hasCornerRadius: true,
|
|
184
|
+
minCornerRadius: 8,
|
|
185
|
+
hasFill: true,
|
|
186
|
+
},
|
|
187
|
+
icon: {
|
|
188
|
+
minWidth: 12,
|
|
189
|
+
maxWidth: 64,
|
|
190
|
+
minHeight: 12,
|
|
191
|
+
maxHeight: 64,
|
|
192
|
+
aspectRatio: { min: 0.5, max: 2 },
|
|
193
|
+
childrenCount: { min: 0, max: 5 },
|
|
194
|
+
},
|
|
195
|
+
badge: {
|
|
196
|
+
minWidth: 16,
|
|
197
|
+
maxWidth: 120,
|
|
198
|
+
minHeight: 16,
|
|
199
|
+
maxHeight: 40,
|
|
200
|
+
hasCornerRadius: true,
|
|
201
|
+
minCornerRadius: 4,
|
|
202
|
+
hasFill: true,
|
|
203
|
+
hasText: true,
|
|
204
|
+
childrenCount: { min: 0, max: 2 },
|
|
205
|
+
},
|
|
206
|
+
divider: {
|
|
207
|
+
minWidth: 50,
|
|
208
|
+
minHeight: 1,
|
|
209
|
+
maxHeight: 4,
|
|
210
|
+
},
|
|
211
|
+
modal: {
|
|
212
|
+
minWidth: 280,
|
|
213
|
+
minHeight: 150,
|
|
214
|
+
hasCornerRadius: true,
|
|
215
|
+
hasFill: true,
|
|
216
|
+
hasDropShadow: true,
|
|
217
|
+
childrenCount: { min: 1, max: 30 },
|
|
218
|
+
},
|
|
219
|
+
progress: {
|
|
220
|
+
minWidth: 80,
|
|
221
|
+
minHeight: 4,
|
|
222
|
+
maxHeight: 24,
|
|
223
|
+
hasCornerRadius: true,
|
|
224
|
+
hasFill: true,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Check if node has a fill
|
|
229
|
+
*/
|
|
230
|
+
function hasFill(node) {
|
|
231
|
+
if (!node.fills || node.fills.length === 0)
|
|
232
|
+
return false;
|
|
233
|
+
return node.fills.some((fill) => fill.type === 'SOLID' || fill.type === 'IMAGE');
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if node has a stroke
|
|
237
|
+
*/
|
|
238
|
+
function hasStroke(node) {
|
|
239
|
+
return !!(node.strokes && node.strokes.length > 0 && node.strokeWeight && node.strokeWeight > 0);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if node has drop shadow
|
|
243
|
+
*/
|
|
244
|
+
function hasDropShadow(node) {
|
|
245
|
+
if (!node.effects)
|
|
246
|
+
return false;
|
|
247
|
+
return node.effects.some((effect) => effect.type === 'DROP_SHADOW' && effect.visible);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if node has text content (direct or in children)
|
|
251
|
+
*/
|
|
252
|
+
function hasText(node) {
|
|
253
|
+
if (node.type === 'TEXT' && node.characters)
|
|
254
|
+
return true;
|
|
255
|
+
if (node.children) {
|
|
256
|
+
return node.children.some(child => hasText(child));
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Count direct children
|
|
262
|
+
*/
|
|
263
|
+
function countChildren(node) {
|
|
264
|
+
return node.children?.length || 0;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get node dimensions
|
|
268
|
+
*/
|
|
269
|
+
function getDimensions(node) {
|
|
270
|
+
if (!node.absoluteBoundingBox)
|
|
271
|
+
return null;
|
|
272
|
+
return {
|
|
273
|
+
width: node.absoluteBoundingBox.width,
|
|
274
|
+
height: node.absoluteBoundingBox.height,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Strategy 1: Fuzzy name matching
|
|
279
|
+
*/
|
|
280
|
+
function detectByName(name) {
|
|
281
|
+
const normalizedName = name.toLowerCase().trim();
|
|
282
|
+
for (const [componentType, patterns] of Object.entries(NAME_PATTERNS)) {
|
|
283
|
+
for (const pattern of patterns) {
|
|
284
|
+
if (pattern.test(normalizedName)) {
|
|
285
|
+
// Calculate confidence based on match specificity
|
|
286
|
+
const matchStr = normalizedName.match(pattern)?.[0] || '';
|
|
287
|
+
const confidence = matchStr.length / normalizedName.length;
|
|
288
|
+
return {
|
|
289
|
+
componentType,
|
|
290
|
+
confidence: Math.min(0.9, 0.5 + confidence * 0.4), // 0.5-0.9 range
|
|
291
|
+
strategy: 'name',
|
|
292
|
+
details: `Matched pattern: ${pattern.source}`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Strategy 2: Visual property heuristics
|
|
301
|
+
*/
|
|
302
|
+
function detectByVisual(node) {
|
|
303
|
+
const dimensions = getDimensions(node);
|
|
304
|
+
if (!dimensions)
|
|
305
|
+
return null;
|
|
306
|
+
const { width, height } = dimensions;
|
|
307
|
+
const aspectRatio = width / height;
|
|
308
|
+
const cornerRadius = node.cornerRadius || 0;
|
|
309
|
+
const childCount = countChildren(node);
|
|
310
|
+
const scores = [];
|
|
311
|
+
for (const [componentType, heuristics] of Object.entries(VISUAL_HEURISTICS)) {
|
|
312
|
+
let score = 0;
|
|
313
|
+
let checks = 0;
|
|
314
|
+
// Width check
|
|
315
|
+
if (heuristics.minWidth !== undefined) {
|
|
316
|
+
checks++;
|
|
317
|
+
if (width >= heuristics.minWidth)
|
|
318
|
+
score++;
|
|
319
|
+
}
|
|
320
|
+
if (heuristics.maxWidth !== undefined) {
|
|
321
|
+
checks++;
|
|
322
|
+
if (width <= heuristics.maxWidth)
|
|
323
|
+
score++;
|
|
324
|
+
}
|
|
325
|
+
// Height check
|
|
326
|
+
if (heuristics.minHeight !== undefined) {
|
|
327
|
+
checks++;
|
|
328
|
+
if (height >= heuristics.minHeight)
|
|
329
|
+
score++;
|
|
330
|
+
}
|
|
331
|
+
if (heuristics.maxHeight !== undefined) {
|
|
332
|
+
checks++;
|
|
333
|
+
if (height <= heuristics.maxHeight)
|
|
334
|
+
score++;
|
|
335
|
+
}
|
|
336
|
+
// Aspect ratio check
|
|
337
|
+
if (heuristics.aspectRatio) {
|
|
338
|
+
checks++;
|
|
339
|
+
if (aspectRatio >= heuristics.aspectRatio.min && aspectRatio <= heuristics.aspectRatio.max) {
|
|
340
|
+
score++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Corner radius checks
|
|
344
|
+
if (heuristics.hasCornerRadius !== undefined) {
|
|
345
|
+
checks++;
|
|
346
|
+
if ((cornerRadius > 0) === heuristics.hasCornerRadius)
|
|
347
|
+
score++;
|
|
348
|
+
}
|
|
349
|
+
if (heuristics.minCornerRadius !== undefined) {
|
|
350
|
+
checks++;
|
|
351
|
+
if (cornerRadius >= heuristics.minCornerRadius)
|
|
352
|
+
score++;
|
|
353
|
+
}
|
|
354
|
+
if (heuristics.maxCornerRadius !== undefined) {
|
|
355
|
+
checks++;
|
|
356
|
+
if (cornerRadius <= heuristics.maxCornerRadius)
|
|
357
|
+
score++;
|
|
358
|
+
}
|
|
359
|
+
// Fill check
|
|
360
|
+
if (heuristics.hasFill !== undefined) {
|
|
361
|
+
checks++;
|
|
362
|
+
if (hasFill(node) === heuristics.hasFill)
|
|
363
|
+
score++;
|
|
364
|
+
}
|
|
365
|
+
// Stroke check
|
|
366
|
+
if (heuristics.hasStroke !== undefined) {
|
|
367
|
+
checks++;
|
|
368
|
+
if (hasStroke(node) === heuristics.hasStroke)
|
|
369
|
+
score++;
|
|
370
|
+
}
|
|
371
|
+
// Drop shadow check
|
|
372
|
+
if (heuristics.hasDropShadow !== undefined) {
|
|
373
|
+
checks++;
|
|
374
|
+
if (hasDropShadow(node) === heuristics.hasDropShadow)
|
|
375
|
+
score++;
|
|
376
|
+
}
|
|
377
|
+
// Text check
|
|
378
|
+
if (heuristics.hasText !== undefined) {
|
|
379
|
+
checks++;
|
|
380
|
+
if (hasText(node) === heuristics.hasText)
|
|
381
|
+
score++;
|
|
382
|
+
}
|
|
383
|
+
// Children count check
|
|
384
|
+
if (heuristics.childrenCount) {
|
|
385
|
+
checks++;
|
|
386
|
+
if (childCount >= heuristics.childrenCount.min && childCount <= heuristics.childrenCount.max) {
|
|
387
|
+
score++;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Layout mode check
|
|
391
|
+
if (heuristics.layoutMode !== undefined) {
|
|
392
|
+
checks++;
|
|
393
|
+
if (node.layoutMode === heuristics.layoutMode)
|
|
394
|
+
score++;
|
|
395
|
+
}
|
|
396
|
+
if (checks > 0) {
|
|
397
|
+
scores.push({ type: componentType, score: score / checks });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Get best match
|
|
401
|
+
scores.sort((a, b) => b.score - a.score);
|
|
402
|
+
if (scores.length > 0 && scores[0].score >= 0.5) {
|
|
403
|
+
return {
|
|
404
|
+
componentType: scores[0].type,
|
|
405
|
+
confidence: scores[0].score * 0.8, // Max 0.8 for visual-only
|
|
406
|
+
strategy: 'visual',
|
|
407
|
+
details: `Visual score: ${(scores[0].score * 100).toFixed(0)}%`,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Strategy 3: Structure-based detection
|
|
414
|
+
*/
|
|
415
|
+
function detectByStructure(node) {
|
|
416
|
+
const childCount = countChildren(node);
|
|
417
|
+
const hasLayout = node.layoutMode && node.layoutMode !== 'NONE';
|
|
418
|
+
// Form detection: multiple input-like children
|
|
419
|
+
if (childCount >= 2 && hasLayout && node.layoutMode === 'VERTICAL') {
|
|
420
|
+
const inputLikeChildren = node.children?.filter(child => {
|
|
421
|
+
const childDim = getDimensions(child);
|
|
422
|
+
return childDim && childDim.width > 100 && childDim.height >= 28 && childDim.height <= 60;
|
|
423
|
+
}) || [];
|
|
424
|
+
if (inputLikeChildren.length >= 2) {
|
|
425
|
+
return {
|
|
426
|
+
componentType: 'form',
|
|
427
|
+
confidence: 0.6,
|
|
428
|
+
strategy: 'structure',
|
|
429
|
+
details: `${inputLikeChildren.length} input-like children in vertical layout`,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Table detection: grid-like structure
|
|
434
|
+
if (childCount >= 3 && node.layoutMode === 'VERTICAL') {
|
|
435
|
+
const firstChild = node.children?.[0];
|
|
436
|
+
if (firstChild?.layoutMode === 'HORIZONTAL' && firstChild.children?.length) {
|
|
437
|
+
const cellCount = firstChild.children.length;
|
|
438
|
+
const rowsWithSameCells = node.children?.filter(child => child.layoutMode === 'HORIZONTAL' && child.children?.length === cellCount).length || 0;
|
|
439
|
+
if (rowsWithSameCells >= 2) {
|
|
440
|
+
return {
|
|
441
|
+
componentType: 'table',
|
|
442
|
+
confidence: 0.7,
|
|
443
|
+
strategy: 'structure',
|
|
444
|
+
details: `Grid-like structure with ${rowsWithSameCells} rows of ${cellCount} cells`,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Tabs detection: horizontal layout with similar-sized children
|
|
450
|
+
if (childCount >= 2 && node.layoutMode === 'HORIZONTAL') {
|
|
451
|
+
const childWidths = node.children?.map(child => getDimensions(child)?.width || 0) || [];
|
|
452
|
+
const avgWidth = childWidths.reduce((a, b) => a + b, 0) / childWidths.length;
|
|
453
|
+
const similarWidths = childWidths.every(w => Math.abs(w - avgWidth) < avgWidth * 0.3);
|
|
454
|
+
if (similarWidths && avgWidth > 50) {
|
|
455
|
+
return {
|
|
456
|
+
componentType: 'tabs',
|
|
457
|
+
confidence: 0.5,
|
|
458
|
+
strategy: 'structure',
|
|
459
|
+
details: `${childCount} similarly-sized horizontal items`,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Navigation/menu detection: vertical list with many items
|
|
464
|
+
if (childCount >= 4 && node.layoutMode === 'VERTICAL') {
|
|
465
|
+
const textChildren = node.children?.filter(child => hasText(child)).length || 0;
|
|
466
|
+
if (textChildren >= 3) {
|
|
467
|
+
return {
|
|
468
|
+
componentType: 'menu',
|
|
469
|
+
confidence: 0.5,
|
|
470
|
+
strategy: 'structure',
|
|
471
|
+
details: `Vertical list with ${textChildren} text items`,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Main detection function - combines all strategies
|
|
479
|
+
*/
|
|
480
|
+
export function detectComponentType(node) {
|
|
481
|
+
// Try all strategies
|
|
482
|
+
const nameResult = detectByName(node.name);
|
|
483
|
+
const visualResult = detectByVisual(node);
|
|
484
|
+
const structureResult = detectByStructure(node);
|
|
485
|
+
// Combine results
|
|
486
|
+
const results = [nameResult, visualResult, structureResult].filter(Boolean);
|
|
487
|
+
if (results.length === 0) {
|
|
488
|
+
// Fallback: use node type
|
|
489
|
+
if (node.type === 'TEXT') {
|
|
490
|
+
return {
|
|
491
|
+
componentType: 'text',
|
|
492
|
+
confidence: 0.8,
|
|
493
|
+
strategy: 'visual',
|
|
494
|
+
details: 'Figma TEXT node',
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
// Default to container/div
|
|
498
|
+
return {
|
|
499
|
+
componentType: 'container',
|
|
500
|
+
confidence: 0.3,
|
|
501
|
+
strategy: 'visual',
|
|
502
|
+
details: 'No specific component detected',
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
// If multiple strategies agree, boost confidence
|
|
506
|
+
const typeVotes = {};
|
|
507
|
+
for (const result of results) {
|
|
508
|
+
if (!typeVotes[result.componentType]) {
|
|
509
|
+
typeVotes[result.componentType] = { count: 0, totalConfidence: 0, results: [] };
|
|
510
|
+
}
|
|
511
|
+
typeVotes[result.componentType].count++;
|
|
512
|
+
typeVotes[result.componentType].totalConfidence += result.confidence;
|
|
513
|
+
typeVotes[result.componentType].results.push(result);
|
|
514
|
+
}
|
|
515
|
+
// Find best type
|
|
516
|
+
let bestType = '';
|
|
517
|
+
let bestScore = 0;
|
|
518
|
+
for (const [type, data] of Object.entries(typeVotes)) {
|
|
519
|
+
// Score = average confidence * (1 + 0.2 * agreement bonus)
|
|
520
|
+
const avgConfidence = data.totalConfidence / data.count;
|
|
521
|
+
const agreementBonus = (data.count - 1) * 0.2;
|
|
522
|
+
const score = avgConfidence * (1 + agreementBonus);
|
|
523
|
+
if (score > bestScore) {
|
|
524
|
+
bestScore = score;
|
|
525
|
+
bestType = type;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const bestData = typeVotes[bestType];
|
|
529
|
+
const strategies = bestData.results.map(r => r.strategy).join('+');
|
|
530
|
+
return {
|
|
531
|
+
componentType: bestType,
|
|
532
|
+
confidence: Math.min(0.95, bestScore),
|
|
533
|
+
strategy: bestData.count > 1 ? 'hybrid' : bestData.results[0].strategy,
|
|
534
|
+
details: `${strategies}: ${bestData.results.map(r => r.details).join('; ')}`,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Detect all components in a tree
|
|
539
|
+
*/
|
|
540
|
+
export function detectComponentsInTree(node, results = new Map()) {
|
|
541
|
+
const detection = detectComponentType(node);
|
|
542
|
+
results.set(node.id, detection);
|
|
543
|
+
if (node.children) {
|
|
544
|
+
for (const child of node.children) {
|
|
545
|
+
detectComponentsInTree(child, results);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return results;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Get component type with fallback to raw CSS generation
|
|
552
|
+
*/
|
|
553
|
+
export function getComponentTypeOrRaw(node) {
|
|
554
|
+
const result = detectComponentType(node);
|
|
555
|
+
// If confidence is too low, suggest raw CSS generation
|
|
556
|
+
if (result.confidence < 0.4) {
|
|
557
|
+
return { type: 'raw' };
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
type: 'component',
|
|
561
|
+
componentType: result.componentType,
|
|
562
|
+
confidence: result.confidence,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Detection Module
|
|
3
|
+
* Intelligent detection of UI components from Figma nodes
|
|
4
|
+
*/
|
|
5
|
+
export { detectComponentType, detectComponentsInTree, getComponentTypeOrRaw, type IDetectionResult, } from './component-detector.js';
|
|
6
|
+
export { generateRawCSS, cssToInlineStyle, cssToReactStyle, cssToBlock, cssToTailwind, } from './raw-css-generator.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/detection/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,aAAa,GACd,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Detection Module
|
|
3
|
+
* Intelligent detection of UI components from Figma nodes
|
|
4
|
+
*/
|
|
5
|
+
export { detectComponentType, detectComponentsInTree, getComponentTypeOrRaw, } from './component-detector.js';
|
|
6
|
+
export { generateRawCSS, cssToInlineStyle, cssToReactStyle, cssToBlock, cssToTailwind, } from './raw-css-generator.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw CSS Generator
|
|
3
|
+
* Generates pixel-perfect CSS from Figma node properties
|
|
4
|
+
* Used when no specific component type is detected
|
|
5
|
+
*/
|
|
6
|
+
import type { IFigmaNode } from '@promptui-lib/core';
|
|
7
|
+
/**
|
|
8
|
+
* Generate all CSS properties from Figma node
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateRawCSS(node: IFigmaNode): Record<string, string>;
|
|
11
|
+
/**
|
|
12
|
+
* Convert CSS object to inline style string
|
|
13
|
+
*/
|
|
14
|
+
export declare function cssToInlineStyle(css: Record<string, string>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Convert CSS object to React style object string
|
|
17
|
+
*/
|
|
18
|
+
export declare function cssToReactStyle(css: Record<string, string>): string;
|
|
19
|
+
/**
|
|
20
|
+
* Convert CSS object to SCSS/CSS block
|
|
21
|
+
*/
|
|
22
|
+
export declare function cssToBlock(css: Record<string, string>, selector: string, indent?: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Generate Tailwind classes from CSS (approximation)
|
|
25
|
+
*/
|
|
26
|
+
export declare function cssToTailwind(css: Record<string, string>): string[];
|
|
27
|
+
//# sourceMappingURL=raw-css-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-css-generator.d.ts","sourceRoot":"","sources":["../../src/detection/raw-css-generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,oBAAoB,CAAC;AA4M5F;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0GvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAQpE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAGnE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,MAAM,CAQpG;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CA0InE"}
|