@promptui-lib/codegen 0.1.0 → 0.1.3
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/README.md +502 -0
- package/dist/frameworks/bootstrap.template.d.ts +2 -2
- package/dist/frameworks/bootstrap.template.d.ts.map +1 -1
- package/dist/frameworks/bootstrap.template.js +30 -5
- package/dist/frameworks/flutter.template.d.ts +10 -0
- package/dist/frameworks/flutter.template.d.ts.map +1 -0
- package/dist/frameworks/flutter.template.js +217 -0
- package/dist/frameworks/index.d.ts +4 -2
- package/dist/frameworks/index.d.ts.map +1 -1
- package/dist/frameworks/index.js +19 -1
- package/dist/frameworks/mui.template.d.ts +2 -2
- package/dist/frameworks/mui.template.d.ts.map +1 -1
- package/dist/frameworks/mui.template.js +36 -7
- package/dist/frameworks/swiftui.template.d.ts +10 -0
- package/dist/frameworks/swiftui.template.d.ts.map +1 -0
- package/dist/frameworks/swiftui.template.js +247 -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 +61 -0
- package/dist/mappings/component-mappings.d.ts.map +1 -0
- package/dist/mappings/component-mappings.js +1093 -0
- package/dist/mappings/index.d.ts +6 -0
- package/dist/mappings/index.d.ts.map +1 -0
- package/dist/mappings/index.js +5 -0
- package/package.json +2 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flutter Template
|
|
3
|
+
* Generates Flutter/Dart widgets from Figma designs
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Mapping of tokens to Flutter values
|
|
7
|
+
*/
|
|
8
|
+
const TOKEN_TO_FLUTTER = {
|
|
9
|
+
// Colors
|
|
10
|
+
'$color-primary': 'Theme.of(context).primaryColor',
|
|
11
|
+
'$color-secondary': 'Theme.of(context).colorScheme.secondary',
|
|
12
|
+
'$color-success': 'Colors.green',
|
|
13
|
+
'$color-error': 'Colors.red',
|
|
14
|
+
'$color-warning': 'Colors.orange',
|
|
15
|
+
'$color-bg-primary': 'Colors.white',
|
|
16
|
+
'$color-bg-secondary': 'Colors.grey[100]',
|
|
17
|
+
'$color-text-primary': 'Colors.black87',
|
|
18
|
+
'$color-text-secondary': 'Colors.black54',
|
|
19
|
+
'$color-text-inverse': 'Colors.white',
|
|
20
|
+
// Spacing
|
|
21
|
+
'$spacing-xs': '4',
|
|
22
|
+
'$spacing-sm': '8',
|
|
23
|
+
'$spacing-md': '16',
|
|
24
|
+
'$spacing-lg': '24',
|
|
25
|
+
'$spacing-xl': '32',
|
|
26
|
+
'$spacing-2xl': '48',
|
|
27
|
+
// Border radius
|
|
28
|
+
'$radius-none': '0',
|
|
29
|
+
'$radius-small': '4',
|
|
30
|
+
'$radius-medium': '8',
|
|
31
|
+
'$radius-large': '12',
|
|
32
|
+
'$radius-xl': '16',
|
|
33
|
+
'$radius-full': '999',
|
|
34
|
+
// Font sizes
|
|
35
|
+
'$font-size-xs': '10',
|
|
36
|
+
'$font-size-sm': '12',
|
|
37
|
+
'$font-size-md': '14',
|
|
38
|
+
'$font-size-lg': '16',
|
|
39
|
+
'$font-size-xl': '20',
|
|
40
|
+
'$font-size-2xl': '24',
|
|
41
|
+
'$font-size-h1': '32',
|
|
42
|
+
'$font-size-h2': '28',
|
|
43
|
+
// Font weights
|
|
44
|
+
'$font-weight-regular': 'FontWeight.w400',
|
|
45
|
+
'$font-weight-medium': 'FontWeight.w500',
|
|
46
|
+
'$font-weight-semibold': 'FontWeight.w600',
|
|
47
|
+
'$font-weight-bold': 'FontWeight.w700',
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Converts CSS value to Flutter
|
|
51
|
+
*/
|
|
52
|
+
function cssToFlutter(property, value) {
|
|
53
|
+
// Token mapping
|
|
54
|
+
if (value.startsWith('$')) {
|
|
55
|
+
return TOKEN_TO_FLUTTER[value] ?? value;
|
|
56
|
+
}
|
|
57
|
+
// Parse numeric values
|
|
58
|
+
const numValue = parseFloat(value);
|
|
59
|
+
if (!isNaN(numValue)) {
|
|
60
|
+
return numValue.toString();
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generates EdgeInsets from padding/margin
|
|
66
|
+
*/
|
|
67
|
+
function generateEdgeInsets(styles) {
|
|
68
|
+
const padding = {};
|
|
69
|
+
for (const style of styles) {
|
|
70
|
+
const value = cssToFlutter(style.property, style.token ?? style.value);
|
|
71
|
+
if (style.property === 'padding') {
|
|
72
|
+
return `EdgeInsets.all(${value})`;
|
|
73
|
+
}
|
|
74
|
+
if (style.property === 'padding-top')
|
|
75
|
+
padding.top = value;
|
|
76
|
+
if (style.property === 'padding-bottom')
|
|
77
|
+
padding.bottom = value;
|
|
78
|
+
if (style.property === 'padding-left')
|
|
79
|
+
padding.left = value;
|
|
80
|
+
if (style.property === 'padding-right')
|
|
81
|
+
padding.right = value;
|
|
82
|
+
}
|
|
83
|
+
if (Object.keys(padding).length > 0) {
|
|
84
|
+
const top = padding.top ?? '0';
|
|
85
|
+
const right = padding.right ?? '0';
|
|
86
|
+
const bottom = padding.bottom ?? '0';
|
|
87
|
+
const left = padding.left ?? '0';
|
|
88
|
+
if (top === bottom && left === right) {
|
|
89
|
+
if (top === left) {
|
|
90
|
+
return `EdgeInsets.all(${top})`;
|
|
91
|
+
}
|
|
92
|
+
return `EdgeInsets.symmetric(vertical: ${top}, horizontal: ${left})`;
|
|
93
|
+
}
|
|
94
|
+
return `EdgeInsets.only(top: ${top}, right: ${right}, bottom: ${bottom}, left: ${left})`;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Gets Flutter widget for element type
|
|
100
|
+
*/
|
|
101
|
+
function getFlutterWidget(node) {
|
|
102
|
+
switch (node.tag) {
|
|
103
|
+
case 'button':
|
|
104
|
+
return 'ElevatedButton';
|
|
105
|
+
case 'img':
|
|
106
|
+
return 'Image.network';
|
|
107
|
+
case 'input':
|
|
108
|
+
return 'TextField';
|
|
109
|
+
case 'span':
|
|
110
|
+
case 'p':
|
|
111
|
+
case 'h1':
|
|
112
|
+
case 'h2':
|
|
113
|
+
case 'h3':
|
|
114
|
+
return 'Text';
|
|
115
|
+
default:
|
|
116
|
+
return 'Container';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Generates Flutter widget code
|
|
121
|
+
*/
|
|
122
|
+
function generateFlutterWidget(node, styles, indent = 4) {
|
|
123
|
+
const spaces = ' '.repeat(indent);
|
|
124
|
+
const nodeStyles = styles.get(`.${node.className}`) ?? [];
|
|
125
|
+
// Determine widget type
|
|
126
|
+
const widget = getFlutterWidget(node);
|
|
127
|
+
// Build properties
|
|
128
|
+
const props = [];
|
|
129
|
+
// Padding
|
|
130
|
+
const padding = generateEdgeInsets(nodeStyles);
|
|
131
|
+
if (padding) {
|
|
132
|
+
props.push(`padding: ${padding}`);
|
|
133
|
+
}
|
|
134
|
+
// Background color
|
|
135
|
+
const bgColor = nodeStyles.find(s => s.property === 'background-color');
|
|
136
|
+
if (bgColor) {
|
|
137
|
+
const color = cssToFlutter('background-color', bgColor.token ?? bgColor.value);
|
|
138
|
+
props.push(`color: ${color}`);
|
|
139
|
+
}
|
|
140
|
+
// Border radius
|
|
141
|
+
const borderRadius = nodeStyles.find(s => s.property === 'border-radius');
|
|
142
|
+
if (borderRadius) {
|
|
143
|
+
const radius = cssToFlutter('border-radius', borderRadius.token ?? borderRadius.value);
|
|
144
|
+
props.push(`borderRadius: BorderRadius.circular(${radius})`);
|
|
145
|
+
}
|
|
146
|
+
// Children
|
|
147
|
+
const children = node.children.map(child => {
|
|
148
|
+
if (typeof child === 'string' || (typeof child === 'object' && 'type' in child && child.type === 'text')) {
|
|
149
|
+
const text = typeof child === 'string' ? child : child.value;
|
|
150
|
+
return `${spaces} Text('${text}')`;
|
|
151
|
+
}
|
|
152
|
+
return generateFlutterWidget(child, styles, indent + 2);
|
|
153
|
+
});
|
|
154
|
+
// Build widget
|
|
155
|
+
if (widget === 'Container') {
|
|
156
|
+
const decoration = [];
|
|
157
|
+
if (bgColor) {
|
|
158
|
+
decoration.push(`color: ${cssToFlutter('background-color', bgColor.token ?? bgColor.value)}`);
|
|
159
|
+
}
|
|
160
|
+
if (borderRadius) {
|
|
161
|
+
decoration.push(`borderRadius: BorderRadius.circular(${cssToFlutter('border-radius', borderRadius.token ?? borderRadius.value)})`);
|
|
162
|
+
}
|
|
163
|
+
const hasDecoration = decoration.length > 0;
|
|
164
|
+
const containerProps = [];
|
|
165
|
+
if (padding)
|
|
166
|
+
containerProps.push(`padding: ${padding}`);
|
|
167
|
+
if (hasDecoration) {
|
|
168
|
+
containerProps.push(`decoration: BoxDecoration(\n${spaces} ${decoration.join(',\n' + spaces + ' ')},\n${spaces} )`);
|
|
169
|
+
}
|
|
170
|
+
if (children.length === 1) {
|
|
171
|
+
containerProps.push(`child: ${children[0].trim()}`);
|
|
172
|
+
}
|
|
173
|
+
else if (children.length > 1) {
|
|
174
|
+
// Use Column or Row based on flex-direction
|
|
175
|
+
const flexDir = nodeStyles.find(s => s.property === 'flex-direction');
|
|
176
|
+
const layout = flexDir?.value === 'row' ? 'Row' : 'Column';
|
|
177
|
+
containerProps.push(`child: ${layout}(\n${spaces} children: [\n${children.join(',\n')},\n${spaces} ],\n${spaces} )`);
|
|
178
|
+
}
|
|
179
|
+
return `${spaces}Container(\n${spaces} ${containerProps.join(',\n' + spaces + ' ')},\n${spaces})`;
|
|
180
|
+
}
|
|
181
|
+
if (widget === 'Text') {
|
|
182
|
+
const text = node.children[0];
|
|
183
|
+
const textContent = typeof text === 'string' ? text : text?.value ?? '';
|
|
184
|
+
return `${spaces}Text('${textContent}')`;
|
|
185
|
+
}
|
|
186
|
+
if (widget === 'ElevatedButton') {
|
|
187
|
+
const buttonChild = children.length > 0 ? children[0].trim() : "Text('Button')";
|
|
188
|
+
return `${spaces}ElevatedButton(\n${spaces} onPressed: () {},\n${spaces} child: ${buttonChild},\n${spaces})`;
|
|
189
|
+
}
|
|
190
|
+
return `${spaces}Container()`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Generates Flutter StatelessWidget
|
|
194
|
+
*/
|
|
195
|
+
export function generateFlutterComponent(ast) {
|
|
196
|
+
// Create styles map
|
|
197
|
+
const stylesMap = new Map();
|
|
198
|
+
for (const block of ast.styles) {
|
|
199
|
+
stylesMap.set(block.selector, block.properties);
|
|
200
|
+
}
|
|
201
|
+
// Widget code
|
|
202
|
+
const widgetBody = generateFlutterWidget(ast.jsx, stylesMap);
|
|
203
|
+
return `/// ${ast.name}
|
|
204
|
+
/// Generated by PromptUI (Flutter)
|
|
205
|
+
|
|
206
|
+
import 'package:flutter/material.dart';
|
|
207
|
+
|
|
208
|
+
class ${ast.name} extends StatelessWidget {
|
|
209
|
+
const ${ast.name}({super.key});
|
|
210
|
+
|
|
211
|
+
@override
|
|
212
|
+
Widget build(BuildContext context) {
|
|
213
|
+
return ${widgetBody.trim()};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework Templates
|
|
3
|
-
* Templates
|
|
3
|
+
* Templates for generating different UI frameworks
|
|
4
4
|
*/
|
|
5
|
-
export type FrameworkType = 'react' | 'mui' | 'tailwind' | 'bootstrap';
|
|
5
|
+
export type FrameworkType = 'react' | 'mui' | 'tailwind' | 'bootstrap' | 'flutter' | 'swiftui';
|
|
6
6
|
export interface IFrameworkConfig {
|
|
7
7
|
name: FrameworkType;
|
|
8
8
|
displayName: string;
|
|
@@ -16,4 +16,6 @@ export declare const FRAMEWORKS: Record<FrameworkType, IFrameworkConfig>;
|
|
|
16
16
|
export { generateMuiComponent, generateMuiStyles } from './mui.template.js';
|
|
17
17
|
export { generateTailwindComponent, generateTailwindClasses } from './tailwind.template.js';
|
|
18
18
|
export { generateBootstrapComponent } from './bootstrap.template.js';
|
|
19
|
+
export { generateFlutterComponent } from './flutter.template.js';
|
|
20
|
+
export { generateSwiftUIComponent } from './swiftui.template.js';
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,aAAa,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAqD9D,CAAC;AAEF,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAC5F,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/frameworks/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework Templates
|
|
3
|
-
* Templates
|
|
3
|
+
* Templates for generating different UI frameworks
|
|
4
4
|
*/
|
|
5
5
|
export const FRAMEWORKS = {
|
|
6
6
|
react: {
|
|
@@ -39,7 +39,25 @@ export const FRAMEWORKS = {
|
|
|
39
39
|
styleExtension: null, // Bootstrap classes
|
|
40
40
|
imports: ["import type { ReactNode } from 'react';"],
|
|
41
41
|
},
|
|
42
|
+
flutter: {
|
|
43
|
+
name: 'flutter',
|
|
44
|
+
displayName: 'Flutter',
|
|
45
|
+
description: 'Flutter/Dart StatelessWidget components',
|
|
46
|
+
fileExtension: 'dart',
|
|
47
|
+
styleExtension: null, // Inline styles
|
|
48
|
+
imports: ["import 'package:flutter/material.dart';"],
|
|
49
|
+
},
|
|
50
|
+
swiftui: {
|
|
51
|
+
name: 'swiftui',
|
|
52
|
+
displayName: 'SwiftUI',
|
|
53
|
+
description: 'SwiftUI View structs for iOS/macOS',
|
|
54
|
+
fileExtension: 'swift',
|
|
55
|
+
styleExtension: null, // Inline modifiers
|
|
56
|
+
imports: ['import SwiftUI'],
|
|
57
|
+
},
|
|
42
58
|
};
|
|
43
59
|
export { generateMuiComponent, generateMuiStyles } from './mui.template.js';
|
|
44
60
|
export { generateTailwindComponent, generateTailwindClasses } from './tailwind.template.js';
|
|
45
61
|
export { generateBootstrapComponent } from './bootstrap.template.js';
|
|
62
|
+
export { generateFlutterComponent } from './flutter.template.js';
|
|
63
|
+
export { generateSwiftUIComponent } from './swiftui.template.js';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Material UI Template
|
|
3
|
-
*
|
|
3
|
+
* Generates React components with MUI components and sx props
|
|
4
4
|
*/
|
|
5
5
|
import type { IComponentAST } from '@promptui-lib/core';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Generates React component with MUI
|
|
8
8
|
*/
|
|
9
9
|
export declare function generateMuiComponent(ast: IComponentAST): string;
|
|
10
10
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mui.template.d.ts","sourceRoot":"","sources":["../../src/frameworks/mui.template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"mui.template.d.ts","sourceRoot":"","sources":["../../src/frameworks/mui.template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AA2RlF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAgE/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAwB5D"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Material UI Template
|
|
3
|
-
*
|
|
3
|
+
* Generates React components with MUI components and sx props
|
|
4
4
|
*/
|
|
5
|
+
import { getComponentMapping, extractComponentType } from '../mappings/index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Mapeamento de tokens SCSS → MUI theme
|
|
7
8
|
*/
|
|
@@ -232,22 +233,47 @@ function generateMuiJSX(node, styles, indent = 2) {
|
|
|
232
233
|
].join('\n');
|
|
233
234
|
}
|
|
234
235
|
/**
|
|
235
|
-
*
|
|
236
|
+
* Gets MUI component mapping based on component name
|
|
237
|
+
*/
|
|
238
|
+
function getMuiMapping(componentName) {
|
|
239
|
+
const componentType = extractComponentType(componentName);
|
|
240
|
+
if (!componentType)
|
|
241
|
+
return null;
|
|
242
|
+
const mapping = getComponentMapping(componentType);
|
|
243
|
+
if (!mapping)
|
|
244
|
+
return null;
|
|
245
|
+
return mapping.mui;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Generates React component with MUI
|
|
236
249
|
*/
|
|
237
250
|
export function generateMuiComponent(ast) {
|
|
238
|
-
//
|
|
251
|
+
// Create styles map by selector
|
|
239
252
|
const stylesMap = new Map();
|
|
240
253
|
for (const block of ast.styles) {
|
|
241
254
|
stylesMap.set(block.selector, block.properties);
|
|
242
255
|
}
|
|
256
|
+
// Check for component mapping
|
|
257
|
+
const mapping = getMuiMapping(ast.name);
|
|
243
258
|
// Props interface
|
|
244
259
|
const propsInterface = `export interface I${ast.name}Props {
|
|
245
260
|
children?: ReactNode;
|
|
246
261
|
className?: string;
|
|
247
262
|
sx?: SxProps<Theme>;
|
|
248
263
|
}`;
|
|
249
|
-
// JSX
|
|
250
|
-
|
|
264
|
+
// JSX - use mapping if available
|
|
265
|
+
let jsx;
|
|
266
|
+
if (mapping) {
|
|
267
|
+
const propsStr = mapping.props
|
|
268
|
+
? Object.entries(mapping.props).map(([k, v]) => `${k}="${v}"`).join(' ')
|
|
269
|
+
: '';
|
|
270
|
+
jsx = ` <${mapping.component} ${propsStr}>
|
|
271
|
+
{children}
|
|
272
|
+
</${mapping.component}>`;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
jsx = generateMuiJSX(ast.jsx, stylesMap);
|
|
276
|
+
}
|
|
251
277
|
// Component
|
|
252
278
|
const component = `export const ${ast.name} = ({
|
|
253
279
|
children,
|
|
@@ -258,12 +284,15 @@ export function generateMuiComponent(ast) {
|
|
|
258
284
|
${jsx}
|
|
259
285
|
);
|
|
260
286
|
};`;
|
|
261
|
-
// Imports
|
|
262
|
-
const
|
|
287
|
+
// Imports - include component-specific imports
|
|
288
|
+
const baseImports = [
|
|
263
289
|
"import type { ReactNode } from 'react';",
|
|
264
290
|
"import { Box, Stack, Typography, Button } from '@mui/material';",
|
|
265
291
|
"import type { SxProps, Theme } from '@mui/material/styles';",
|
|
266
292
|
];
|
|
293
|
+
const imports = mapping?.imports
|
|
294
|
+
? [...new Set([...baseImports, ...mapping.imports])]
|
|
295
|
+
: baseImports;
|
|
267
296
|
return [
|
|
268
297
|
'/**',
|
|
269
298
|
` * ${ast.name}`,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwiftUI Template
|
|
3
|
+
* Generates SwiftUI views from Figma designs
|
|
4
|
+
*/
|
|
5
|
+
import type { IComponentAST } from '@promptui-lib/core';
|
|
6
|
+
/**
|
|
7
|
+
* Generates SwiftUI View struct
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateSwiftUIComponent(ast: IComponentAST): string;
|
|
10
|
+
//# sourceMappingURL=swiftui.template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swiftui.template.d.ts","sourceRoot":"","sources":["../../src/frameworks/swiftui.template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AA0PlF;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAyBnE"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwiftUI Template
|
|
3
|
+
* Generates SwiftUI views from Figma designs
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Mapping of tokens to SwiftUI values
|
|
7
|
+
*/
|
|
8
|
+
const TOKEN_TO_SWIFTUI = {
|
|
9
|
+
// Colors
|
|
10
|
+
'$color-primary': '.blue',
|
|
11
|
+
'$color-secondary': '.gray',
|
|
12
|
+
'$color-success': '.green',
|
|
13
|
+
'$color-error': '.red',
|
|
14
|
+
'$color-warning': '.orange',
|
|
15
|
+
'$color-bg-primary': '.white',
|
|
16
|
+
'$color-bg-secondary': 'Color(.systemGray6)',
|
|
17
|
+
'$color-text-primary': '.primary',
|
|
18
|
+
'$color-text-secondary': '.secondary',
|
|
19
|
+
'$color-text-inverse': '.white',
|
|
20
|
+
// Spacing
|
|
21
|
+
'$spacing-xs': '4',
|
|
22
|
+
'$spacing-sm': '8',
|
|
23
|
+
'$spacing-md': '16',
|
|
24
|
+
'$spacing-lg': '24',
|
|
25
|
+
'$spacing-xl': '32',
|
|
26
|
+
'$spacing-2xl': '48',
|
|
27
|
+
// Border radius
|
|
28
|
+
'$radius-none': '0',
|
|
29
|
+
'$radius-small': '4',
|
|
30
|
+
'$radius-medium': '8',
|
|
31
|
+
'$radius-large': '12',
|
|
32
|
+
'$radius-xl': '16',
|
|
33
|
+
'$radius-full': '999',
|
|
34
|
+
// Font sizes
|
|
35
|
+
'$font-size-xs': '.caption2',
|
|
36
|
+
'$font-size-sm': '.caption',
|
|
37
|
+
'$font-size-md': '.body',
|
|
38
|
+
'$font-size-lg': '.headline',
|
|
39
|
+
'$font-size-xl': '.title2',
|
|
40
|
+
'$font-size-2xl': '.title',
|
|
41
|
+
'$font-size-h1': '.largeTitle',
|
|
42
|
+
'$font-size-h2': '.title',
|
|
43
|
+
// Font weights
|
|
44
|
+
'$font-weight-regular': '.regular',
|
|
45
|
+
'$font-weight-medium': '.medium',
|
|
46
|
+
'$font-weight-semibold': '.semibold',
|
|
47
|
+
'$font-weight-bold': '.bold',
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Converts CSS value to SwiftUI
|
|
51
|
+
*/
|
|
52
|
+
function cssToSwiftUI(property, value) {
|
|
53
|
+
// Token mapping
|
|
54
|
+
if (value.startsWith('$')) {
|
|
55
|
+
return TOKEN_TO_SWIFTUI[value] ?? value;
|
|
56
|
+
}
|
|
57
|
+
// Parse numeric values
|
|
58
|
+
const numValue = parseFloat(value);
|
|
59
|
+
if (!isNaN(numValue)) {
|
|
60
|
+
return numValue.toString();
|
|
61
|
+
}
|
|
62
|
+
// Color hex to SwiftUI
|
|
63
|
+
if (value.startsWith('#')) {
|
|
64
|
+
return `Color(hex: "${value}")`;
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Gets SwiftUI view for element type
|
|
70
|
+
*/
|
|
71
|
+
function getSwiftUIView(node) {
|
|
72
|
+
switch (node.tag) {
|
|
73
|
+
case 'button':
|
|
74
|
+
return 'Button';
|
|
75
|
+
case 'img':
|
|
76
|
+
return 'Image';
|
|
77
|
+
case 'input':
|
|
78
|
+
return 'TextField';
|
|
79
|
+
case 'span':
|
|
80
|
+
case 'p':
|
|
81
|
+
case 'h1':
|
|
82
|
+
case 'h2':
|
|
83
|
+
case 'h3':
|
|
84
|
+
return 'Text';
|
|
85
|
+
default:
|
|
86
|
+
return 'VStack'; // Default container
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Generates SwiftUI modifiers from styles
|
|
91
|
+
*/
|
|
92
|
+
function generateModifiers(styles, indent) {
|
|
93
|
+
const spaces = ' '.repeat(indent);
|
|
94
|
+
const modifiers = [];
|
|
95
|
+
for (const style of styles) {
|
|
96
|
+
const value = cssToSwiftUI(style.property, style.token ?? style.value);
|
|
97
|
+
switch (style.property) {
|
|
98
|
+
case 'padding':
|
|
99
|
+
modifiers.push(`${spaces}.padding(${value})`);
|
|
100
|
+
break;
|
|
101
|
+
case 'padding-top':
|
|
102
|
+
modifiers.push(`${spaces}.padding(.top, ${value})`);
|
|
103
|
+
break;
|
|
104
|
+
case 'padding-bottom':
|
|
105
|
+
modifiers.push(`${spaces}.padding(.bottom, ${value})`);
|
|
106
|
+
break;
|
|
107
|
+
case 'padding-left':
|
|
108
|
+
modifiers.push(`${spaces}.padding(.leading, ${value})`);
|
|
109
|
+
break;
|
|
110
|
+
case 'padding-right':
|
|
111
|
+
modifiers.push(`${spaces}.padding(.trailing, ${value})`);
|
|
112
|
+
break;
|
|
113
|
+
case 'background-color':
|
|
114
|
+
modifiers.push(`${spaces}.background(${value})`);
|
|
115
|
+
break;
|
|
116
|
+
case 'border-radius':
|
|
117
|
+
modifiers.push(`${spaces}.cornerRadius(${value})`);
|
|
118
|
+
break;
|
|
119
|
+
case 'color':
|
|
120
|
+
modifiers.push(`${spaces}.foregroundColor(${value})`);
|
|
121
|
+
break;
|
|
122
|
+
case 'font-size':
|
|
123
|
+
modifiers.push(`${spaces}.font(${value})`);
|
|
124
|
+
break;
|
|
125
|
+
case 'font-weight':
|
|
126
|
+
modifiers.push(`${spaces}.fontWeight(${value})`);
|
|
127
|
+
break;
|
|
128
|
+
case 'width':
|
|
129
|
+
if (value !== 'auto') {
|
|
130
|
+
modifiers.push(`${spaces}.frame(width: ${value})`);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case 'height':
|
|
134
|
+
if (value !== 'auto') {
|
|
135
|
+
modifiers.push(`${spaces}.frame(height: ${value})`);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return modifiers;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Generates SwiftUI view code
|
|
144
|
+
*/
|
|
145
|
+
function generateSwiftUIView(node, styles, indent = 8) {
|
|
146
|
+
const spaces = ' '.repeat(indent);
|
|
147
|
+
const nodeStyles = styles.get(`.${node.className}`) ?? [];
|
|
148
|
+
// Determine view type
|
|
149
|
+
const view = getSwiftUIView(node);
|
|
150
|
+
// Get flex direction for layout
|
|
151
|
+
const flexDir = nodeStyles.find(s => s.property === 'flex-direction');
|
|
152
|
+
const layout = flexDir?.value === 'row' ? 'HStack' : 'VStack';
|
|
153
|
+
// Get alignment
|
|
154
|
+
const alignItems = nodeStyles.find(s => s.property === 'align-items');
|
|
155
|
+
let alignment = '';
|
|
156
|
+
if (alignItems) {
|
|
157
|
+
switch (alignItems.value) {
|
|
158
|
+
case 'center':
|
|
159
|
+
alignment = ', alignment: .center';
|
|
160
|
+
break;
|
|
161
|
+
case 'flex-start':
|
|
162
|
+
alignment = flexDir?.value === 'row' ? ', alignment: .top' : ', alignment: .leading';
|
|
163
|
+
break;
|
|
164
|
+
case 'flex-end':
|
|
165
|
+
alignment = flexDir?.value === 'row' ? ', alignment: .bottom' : ', alignment: .trailing';
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Get gap/spacing
|
|
170
|
+
const gap = nodeStyles.find(s => s.property === 'gap');
|
|
171
|
+
const spacing = gap ? cssToSwiftUI('gap', gap.token ?? gap.value) : '0';
|
|
172
|
+
// Generate modifiers
|
|
173
|
+
const modifiers = generateModifiers(nodeStyles, indent);
|
|
174
|
+
// Children
|
|
175
|
+
const children = node.children.map(child => {
|
|
176
|
+
if (typeof child === 'string' || (typeof child === 'object' && 'type' in child && child.type === 'text')) {
|
|
177
|
+
const text = typeof child === 'string' ? child : child.value;
|
|
178
|
+
return `${spaces} Text("${text}")`;
|
|
179
|
+
}
|
|
180
|
+
return generateSwiftUIView(child, styles, indent + 4);
|
|
181
|
+
});
|
|
182
|
+
// Build view
|
|
183
|
+
if (view === 'Text') {
|
|
184
|
+
const text = node.children[0];
|
|
185
|
+
const textContent = typeof text === 'string' ? text : text?.value ?? '';
|
|
186
|
+
const textView = `${spaces}Text("${textContent}")`;
|
|
187
|
+
if (modifiers.length > 0) {
|
|
188
|
+
return textView + '\n' + modifiers.join('\n');
|
|
189
|
+
}
|
|
190
|
+
return textView;
|
|
191
|
+
}
|
|
192
|
+
if (view === 'Button') {
|
|
193
|
+
const buttonLabel = children.length > 0 ? children[0].trim() : 'Text("Button")';
|
|
194
|
+
const buttonView = `${spaces}Button(action: {}) {\n${spaces} ${buttonLabel}\n${spaces}}`;
|
|
195
|
+
if (modifiers.length > 0) {
|
|
196
|
+
return buttonView + '\n' + modifiers.join('\n');
|
|
197
|
+
}
|
|
198
|
+
return buttonView;
|
|
199
|
+
}
|
|
200
|
+
if (view === 'Image') {
|
|
201
|
+
return `${spaces}Image(systemName: "photo")\n${spaces} .resizable()\n${spaces} .aspectRatio(contentMode: .fit)`;
|
|
202
|
+
}
|
|
203
|
+
// Container (VStack/HStack)
|
|
204
|
+
if (children.length === 0) {
|
|
205
|
+
const emptyView = `${spaces}${layout}(spacing: ${spacing}${alignment}) {}`;
|
|
206
|
+
if (modifiers.length > 0) {
|
|
207
|
+
return emptyView + '\n' + modifiers.join('\n');
|
|
208
|
+
}
|
|
209
|
+
return emptyView;
|
|
210
|
+
}
|
|
211
|
+
const containerView = [
|
|
212
|
+
`${spaces}${layout}(spacing: ${spacing}${alignment}) {`,
|
|
213
|
+
...children,
|
|
214
|
+
`${spaces}}`,
|
|
215
|
+
].join('\n');
|
|
216
|
+
if (modifiers.length > 0) {
|
|
217
|
+
return containerView + '\n' + modifiers.join('\n');
|
|
218
|
+
}
|
|
219
|
+
return containerView;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Generates SwiftUI View struct
|
|
223
|
+
*/
|
|
224
|
+
export function generateSwiftUIComponent(ast) {
|
|
225
|
+
// Create styles map
|
|
226
|
+
const stylesMap = new Map();
|
|
227
|
+
for (const block of ast.styles) {
|
|
228
|
+
stylesMap.set(block.selector, block.properties);
|
|
229
|
+
}
|
|
230
|
+
// View body
|
|
231
|
+
const viewBody = generateSwiftUIView(ast.jsx, stylesMap);
|
|
232
|
+
return `/// ${ast.name}
|
|
233
|
+
/// Generated by PromptUI (SwiftUI)
|
|
234
|
+
|
|
235
|
+
import SwiftUI
|
|
236
|
+
|
|
237
|
+
struct ${ast.name}: View {
|
|
238
|
+
var body: some View {
|
|
239
|
+
${viewBody}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#Preview {
|
|
244
|
+
${ast.name}()
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED