@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.
@@ -0,0 +1,473 @@
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
+ /**
7
+ * Convert Figma color to CSS
8
+ */
9
+ function colorToCSS(color, opacity) {
10
+ const r = Math.round(color.r * 255);
11
+ const g = Math.round(color.g * 255);
12
+ const b = Math.round(color.b * 255);
13
+ const a = opacity !== undefined ? opacity : color.a;
14
+ if (a === 1) {
15
+ return `rgb(${r}, ${g}, ${b})`;
16
+ }
17
+ return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`;
18
+ }
19
+ /**
20
+ * Convert Figma gradient to CSS
21
+ */
22
+ function gradientToCSS(fill) {
23
+ const stops = fill.gradientStops
24
+ .map(stop => `${colorToCSS(stop.color)} ${(stop.position * 100).toFixed(1)}%`)
25
+ .join(', ');
26
+ switch (fill.type) {
27
+ case 'GRADIENT_LINEAR':
28
+ return `linear-gradient(180deg, ${stops})`;
29
+ case 'GRADIENT_RADIAL':
30
+ return `radial-gradient(circle, ${stops})`;
31
+ case 'GRADIENT_ANGULAR':
32
+ return `conic-gradient(${stops})`;
33
+ default:
34
+ return `linear-gradient(180deg, ${stops})`;
35
+ }
36
+ }
37
+ /**
38
+ * Convert Figma fill to CSS background
39
+ */
40
+ function fillToCSS(fill) {
41
+ switch (fill.type) {
42
+ case 'SOLID':
43
+ return colorToCSS(fill.color, fill.opacity);
44
+ case 'GRADIENT_LINEAR':
45
+ case 'GRADIENT_RADIAL':
46
+ case 'GRADIENT_ANGULAR':
47
+ case 'GRADIENT_DIAMOND':
48
+ return gradientToCSS(fill);
49
+ case 'IMAGE':
50
+ return `url(${fill.imageRef})`;
51
+ default:
52
+ return null;
53
+ }
54
+ }
55
+ /**
56
+ * Convert Figma effects to CSS
57
+ */
58
+ function effectsToCSS(effects) {
59
+ const styles = {};
60
+ const shadows = [];
61
+ const filters = [];
62
+ for (const effect of effects) {
63
+ if (!effect.visible)
64
+ continue;
65
+ switch (effect.type) {
66
+ case 'DROP_SHADOW': {
67
+ const { color, offset, radius, spread } = effect;
68
+ const shadowColor = colorToCSS(color);
69
+ shadows.push(`${offset.x}px ${offset.y}px ${radius}px ${spread || 0}px ${shadowColor}`);
70
+ break;
71
+ }
72
+ case 'INNER_SHADOW': {
73
+ const { color, offset, radius, spread } = effect;
74
+ const shadowColor = colorToCSS(color);
75
+ shadows.push(`inset ${offset.x}px ${offset.y}px ${radius}px ${spread || 0}px ${shadowColor}`);
76
+ break;
77
+ }
78
+ case 'LAYER_BLUR':
79
+ filters.push(`blur(${effect.radius}px)`);
80
+ break;
81
+ case 'BACKGROUND_BLUR':
82
+ styles.backdropFilter = `blur(${effect.radius}px)`;
83
+ break;
84
+ }
85
+ }
86
+ if (shadows.length > 0) {
87
+ styles.boxShadow = shadows.join(', ');
88
+ }
89
+ if (filters.length > 0) {
90
+ styles.filter = filters.join(' ');
91
+ }
92
+ return styles;
93
+ }
94
+ /**
95
+ * Convert layout mode to flexbox
96
+ */
97
+ function layoutToCSS(node) {
98
+ const styles = {};
99
+ if (node.layoutMode === 'HORIZONTAL' || node.layoutMode === 'VERTICAL') {
100
+ styles.display = 'flex';
101
+ styles.flexDirection = node.layoutMode === 'HORIZONTAL' ? 'row' : 'column';
102
+ // Primary axis alignment
103
+ if (node.primaryAxisAlignItems) {
104
+ const alignMap = {
105
+ MIN: 'flex-start',
106
+ CENTER: 'center',
107
+ MAX: 'flex-end',
108
+ SPACE_BETWEEN: 'space-between',
109
+ };
110
+ styles.justifyContent = alignMap[node.primaryAxisAlignItems] || 'flex-start';
111
+ }
112
+ // Counter axis alignment
113
+ if (node.counterAxisAlignItems) {
114
+ const alignMap = {
115
+ MIN: 'flex-start',
116
+ CENTER: 'center',
117
+ MAX: 'flex-end',
118
+ BASELINE: 'baseline',
119
+ };
120
+ styles.alignItems = alignMap[node.counterAxisAlignItems] || 'stretch';
121
+ }
122
+ // Gap
123
+ if (node.itemSpacing && node.itemSpacing > 0) {
124
+ styles.gap = `${node.itemSpacing}px`;
125
+ }
126
+ // Wrap
127
+ if (node.layoutWrap === 'WRAP') {
128
+ styles.flexWrap = 'wrap';
129
+ }
130
+ }
131
+ return styles;
132
+ }
133
+ /**
134
+ * Convert padding to CSS
135
+ */
136
+ function paddingToCSS(node) {
137
+ const styles = {};
138
+ const top = node.paddingTop || 0;
139
+ const right = node.paddingRight || 0;
140
+ const bottom = node.paddingBottom || 0;
141
+ const left = node.paddingLeft || 0;
142
+ if (top === right && right === bottom && bottom === left && top > 0) {
143
+ styles.padding = `${top}px`;
144
+ }
145
+ else if (top === bottom && left === right && (top > 0 || left > 0)) {
146
+ styles.padding = `${top}px ${left}px`;
147
+ }
148
+ else if (top > 0 || right > 0 || bottom > 0 || left > 0) {
149
+ styles.padding = `${top}px ${right}px ${bottom}px ${left}px`;
150
+ }
151
+ return styles;
152
+ }
153
+ /**
154
+ * Convert text style to CSS
155
+ */
156
+ function textStyleToCSS(node) {
157
+ const styles = {};
158
+ if (node.style) {
159
+ const { fontFamily, fontWeight, fontSize, lineHeightPx, letterSpacing, textAlignHorizontal, textDecoration, textCase } = node.style;
160
+ if (fontFamily)
161
+ styles.fontFamily = `"${fontFamily}", sans-serif`;
162
+ if (fontWeight)
163
+ styles.fontWeight = String(fontWeight);
164
+ if (fontSize)
165
+ styles.fontSize = `${fontSize}px`;
166
+ if (lineHeightPx)
167
+ styles.lineHeight = `${lineHeightPx}px`;
168
+ if (letterSpacing)
169
+ styles.letterSpacing = `${letterSpacing}px`;
170
+ if (textAlignHorizontal) {
171
+ const alignMap = {
172
+ LEFT: 'left',
173
+ CENTER: 'center',
174
+ RIGHT: 'right',
175
+ JUSTIFIED: 'justify',
176
+ };
177
+ styles.textAlign = alignMap[textAlignHorizontal] || 'left';
178
+ }
179
+ if (textDecoration === 'UNDERLINE')
180
+ styles.textDecoration = 'underline';
181
+ if (textDecoration === 'STRIKETHROUGH')
182
+ styles.textDecoration = 'line-through';
183
+ if (textCase === 'UPPER')
184
+ styles.textTransform = 'uppercase';
185
+ if (textCase === 'LOWER')
186
+ styles.textTransform = 'lowercase';
187
+ if (textCase === 'TITLE')
188
+ styles.textTransform = 'capitalize';
189
+ }
190
+ return styles;
191
+ }
192
+ /**
193
+ * Generate all CSS properties from Figma node
194
+ */
195
+ export function generateRawCSS(node) {
196
+ const styles = {};
197
+ // Dimensions
198
+ if (node.absoluteBoundingBox) {
199
+ const { width, height } = node.absoluteBoundingBox;
200
+ // Only set fixed dimensions if not using auto-layout sizing
201
+ if (node.layoutSizingHorizontal === 'FIXED' || !node.layoutSizingHorizontal) {
202
+ styles.width = `${width}px`;
203
+ }
204
+ else if (node.layoutSizingHorizontal === 'FILL') {
205
+ styles.width = '100%';
206
+ }
207
+ if (node.layoutSizingVertical === 'FIXED' || !node.layoutSizingVertical) {
208
+ styles.height = `${height}px`;
209
+ }
210
+ else if (node.layoutSizingVertical === 'FILL') {
211
+ styles.height = '100%';
212
+ }
213
+ // Min/max constraints
214
+ if (node.minWidth)
215
+ styles.minWidth = `${node.minWidth}px`;
216
+ if (node.maxWidth)
217
+ styles.maxWidth = `${node.maxWidth}px`;
218
+ if (node.minHeight)
219
+ styles.minHeight = `${node.minHeight}px`;
220
+ if (node.maxHeight)
221
+ styles.maxHeight = `${node.maxHeight}px`;
222
+ }
223
+ // Background
224
+ if (node.fills && node.fills.length > 0) {
225
+ const backgrounds = node.fills
226
+ .filter((fill) => fill.opacity === undefined || fill.opacity > 0)
227
+ .map(fillToCSS)
228
+ .filter(Boolean)
229
+ .reverse(); // Figma layers are bottom-up
230
+ if (backgrounds.length === 1) {
231
+ const bg = backgrounds[0];
232
+ if (bg.includes('gradient') || bg.includes('url(')) {
233
+ styles.background = bg;
234
+ }
235
+ else {
236
+ styles.backgroundColor = bg;
237
+ }
238
+ }
239
+ else if (backgrounds.length > 1) {
240
+ styles.background = backgrounds.join(', ');
241
+ }
242
+ }
243
+ // Border
244
+ if (node.strokes && node.strokes.length > 0 && node.strokeWeight) {
245
+ const stroke = node.strokes[0];
246
+ if (stroke.type === 'SOLID') {
247
+ const color = colorToCSS(stroke.color, stroke.opacity);
248
+ const align = node.strokeAlign || 'CENTER';
249
+ if (align === 'INSIDE') {
250
+ styles.boxShadow = `inset 0 0 0 ${node.strokeWeight}px ${color}`;
251
+ }
252
+ else {
253
+ styles.border = `${node.strokeWeight}px solid ${color}`;
254
+ }
255
+ }
256
+ }
257
+ // Border radius
258
+ if (node.cornerRadius && node.cornerRadius > 0) {
259
+ styles.borderRadius = `${node.cornerRadius}px`;
260
+ }
261
+ else if (node.rectangleCornerRadii) {
262
+ const [tl, tr, br, bl] = node.rectangleCornerRadii;
263
+ styles.borderRadius = `${tl}px ${tr}px ${br}px ${bl}px`;
264
+ }
265
+ // Opacity
266
+ if (node.opacity !== undefined && node.opacity < 1) {
267
+ styles.opacity = node.opacity.toFixed(2);
268
+ }
269
+ // Effects (shadows, blur)
270
+ if (node.effects && node.effects.length > 0) {
271
+ Object.assign(styles, effectsToCSS(node.effects));
272
+ }
273
+ // Overflow
274
+ if (node.clipsContent) {
275
+ styles.overflow = 'hidden';
276
+ }
277
+ // Layout (flexbox)
278
+ Object.assign(styles, layoutToCSS(node));
279
+ // Padding
280
+ Object.assign(styles, paddingToCSS(node));
281
+ // Text styles
282
+ if (node.type === 'TEXT') {
283
+ Object.assign(styles, textStyleToCSS(node));
284
+ // Text color from fills
285
+ if (node.fills && node.fills.length > 0) {
286
+ const textFill = node.fills[0];
287
+ if (textFill.type === 'SOLID') {
288
+ styles.color = colorToCSS(textFill.color, textFill.opacity);
289
+ delete styles.backgroundColor;
290
+ }
291
+ }
292
+ }
293
+ return styles;
294
+ }
295
+ /**
296
+ * Convert CSS object to inline style string
297
+ */
298
+ export function cssToInlineStyle(css) {
299
+ return Object.entries(css)
300
+ .map(([key, value]) => {
301
+ // Convert camelCase to kebab-case
302
+ const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
303
+ return `${kebabKey}: ${value}`;
304
+ })
305
+ .join('; ');
306
+ }
307
+ /**
308
+ * Convert CSS object to React style object string
309
+ */
310
+ export function cssToReactStyle(css) {
311
+ const entries = Object.entries(css).map(([key, value]) => `${key}: '${value}'`);
312
+ return `{ ${entries.join(', ')} }`;
313
+ }
314
+ /**
315
+ * Convert CSS object to SCSS/CSS block
316
+ */
317
+ export function cssToBlock(css, selector, indent = 0) {
318
+ const spaces = ' '.repeat(indent);
319
+ const lines = Object.entries(css).map(([key, value]) => {
320
+ const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
321
+ return `${spaces} ${kebabKey}: ${value};`;
322
+ });
323
+ return `${spaces}${selector} {\n${lines.join('\n')}\n${spaces}}`;
324
+ }
325
+ /**
326
+ * Generate Tailwind classes from CSS (approximation)
327
+ */
328
+ export function cssToTailwind(css) {
329
+ const classes = [];
330
+ // Width
331
+ if (css.width) {
332
+ if (css.width === '100%')
333
+ classes.push('w-full');
334
+ else if (css.width.endsWith('px')) {
335
+ const val = parseInt(css.width);
336
+ if (val <= 96)
337
+ classes.push(`w-${val / 4}`);
338
+ else
339
+ classes.push(`w-[${css.width}]`);
340
+ }
341
+ }
342
+ // Height
343
+ if (css.height) {
344
+ if (css.height === '100%')
345
+ classes.push('h-full');
346
+ else if (css.height.endsWith('px')) {
347
+ const val = parseInt(css.height);
348
+ if (val <= 96)
349
+ classes.push(`h-${val / 4}`);
350
+ else
351
+ classes.push(`h-[${css.height}]`);
352
+ }
353
+ }
354
+ // Display flex
355
+ if (css.display === 'flex') {
356
+ classes.push('flex');
357
+ if (css.flexDirection === 'column')
358
+ classes.push('flex-col');
359
+ if (css.flexWrap === 'wrap')
360
+ classes.push('flex-wrap');
361
+ }
362
+ // Justify content
363
+ const justifyMap = {
364
+ 'flex-start': 'justify-start',
365
+ 'center': 'justify-center',
366
+ 'flex-end': 'justify-end',
367
+ 'space-between': 'justify-between',
368
+ 'space-around': 'justify-around',
369
+ };
370
+ if (css.justifyContent && justifyMap[css.justifyContent]) {
371
+ classes.push(justifyMap[css.justifyContent]);
372
+ }
373
+ // Align items
374
+ const alignMap = {
375
+ 'flex-start': 'items-start',
376
+ 'center': 'items-center',
377
+ 'flex-end': 'items-end',
378
+ 'baseline': 'items-baseline',
379
+ 'stretch': 'items-stretch',
380
+ };
381
+ if (css.alignItems && alignMap[css.alignItems]) {
382
+ classes.push(alignMap[css.alignItems]);
383
+ }
384
+ // Gap
385
+ if (css.gap) {
386
+ const val = parseInt(css.gap);
387
+ if (val <= 96)
388
+ classes.push(`gap-${val / 4}`);
389
+ else
390
+ classes.push(`gap-[${css.gap}]`);
391
+ }
392
+ // Padding
393
+ if (css.padding) {
394
+ const parts = css.padding.split(' ');
395
+ if (parts.length === 1) {
396
+ const val = parseInt(parts[0]);
397
+ classes.push(`p-${val / 4}`);
398
+ }
399
+ else if (parts.length === 2) {
400
+ const py = parseInt(parts[0]) / 4;
401
+ const px = parseInt(parts[1]) / 4;
402
+ classes.push(`py-${py}`, `px-${px}`);
403
+ }
404
+ }
405
+ // Border radius
406
+ if (css.borderRadius) {
407
+ const val = parseInt(css.borderRadius);
408
+ if (val >= 9999)
409
+ classes.push('rounded-full');
410
+ else if (val >= 16)
411
+ classes.push('rounded-xl');
412
+ else if (val >= 12)
413
+ classes.push('rounded-lg');
414
+ else if (val >= 8)
415
+ classes.push('rounded-md');
416
+ else if (val >= 4)
417
+ classes.push('rounded');
418
+ else if (val >= 2)
419
+ classes.push('rounded-sm');
420
+ }
421
+ // Background color (simplified - would need color matching)
422
+ if (css.backgroundColor) {
423
+ classes.push(`bg-[${css.backgroundColor.replace(/\s/g, '')}]`);
424
+ }
425
+ // Text color
426
+ if (css.color) {
427
+ classes.push(`text-[${css.color.replace(/\s/g, '')}]`);
428
+ }
429
+ // Font size
430
+ if (css.fontSize) {
431
+ const val = parseInt(css.fontSize);
432
+ const sizeMap = {
433
+ 12: 'text-xs',
434
+ 14: 'text-sm',
435
+ 16: 'text-base',
436
+ 18: 'text-lg',
437
+ 20: 'text-xl',
438
+ 24: 'text-2xl',
439
+ 30: 'text-3xl',
440
+ };
441
+ if (sizeMap[val])
442
+ classes.push(sizeMap[val]);
443
+ else
444
+ classes.push(`text-[${val}px]`);
445
+ }
446
+ // Font weight
447
+ if (css.fontWeight) {
448
+ const weightMap = {
449
+ '400': 'font-normal',
450
+ '500': 'font-medium',
451
+ '600': 'font-semibold',
452
+ '700': 'font-bold',
453
+ };
454
+ if (weightMap[css.fontWeight])
455
+ classes.push(weightMap[css.fontWeight]);
456
+ }
457
+ // Box shadow
458
+ if (css.boxShadow) {
459
+ if (css.boxShadow.includes('inset'))
460
+ classes.push('shadow-inner');
461
+ else
462
+ classes.push('shadow-lg');
463
+ }
464
+ // Opacity
465
+ if (css.opacity) {
466
+ const val = Math.round(parseFloat(css.opacity) * 100);
467
+ classes.push(`opacity-${val}`);
468
+ }
469
+ // Overflow
470
+ if (css.overflow === 'hidden')
471
+ classes.push('overflow-hidden');
472
+ return classes;
473
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Ant Design Template
3
+ * Generates React components with Ant Design components
4
+ */
5
+ import type { IComponentAST } from '@promptui-lib/core';
6
+ /**
7
+ * Generate complete Ant Design component
8
+ */
9
+ export declare function generateAntdComponent(ast: IComponentAST): string;
10
+ /**
11
+ * Generate Ant Design ConfigProvider wrapper with theme
12
+ */
13
+ export declare function generateAntdThemeConfig(colors: Record<string, string>): string;
14
+ /**
15
+ * Generate complete page with Ant Design Layout
16
+ */
17
+ export declare function generateAntdLayout(ast: IComponentAST): string;
18
+ //# sourceMappingURL=antd.template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"antd.template.d.ts","sourceRoot":"","sources":["../../src/frameworks/antd.template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAmD,MAAM,oBAAoB,CAAC;AA4LzG;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAiChE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAoB9E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAwC7D"}