@promptui-lib/codegen 0.1.0 → 0.1.2
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 +416 -0
- 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/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/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./logo.png" alt="PromptUI Logo" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">PromptUI</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@promptui-lib/cli"><img src="https://img.shields.io/npm/v/@promptui-lib/cli.svg" alt="npm version" /></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@promptui-lib/cli"><img src="https://img.shields.io/npm/dm/@promptui-lib/cli.svg" alt="npm downloads" /></a>
|
|
10
|
+
<img src="https://img.shields.io/badge/license-proprietary-red.svg" alt="license" />
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<strong>Transform Figma designs into production-ready code in seconds.</strong>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
100% deterministic. No AI. Same input = same output, always.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## What is it?
|
|
24
|
+
|
|
25
|
+
**PromptUI** is a library that automatically converts your Figma designs into clean, production-ready code.
|
|
26
|
+
|
|
27
|
+
Supports multiple frameworks:
|
|
28
|
+
- **React + SCSS** (BEM methodology)
|
|
29
|
+
- **Material UI** (MUI)
|
|
30
|
+
- **Tailwind CSS**
|
|
31
|
+
- **Bootstrap**
|
|
32
|
+
- **Flutter** (Dart StatelessWidgets)
|
|
33
|
+
- **SwiftUI** (iOS/macOS Views)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @promptui-lib/cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## How It Works
|
|
46
|
+
|
|
47
|
+
### 1. Mark your components in Figma
|
|
48
|
+
|
|
49
|
+
In Figma, add `#` at the beginning of frame names you want to export:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
#Button → Will be exported as component
|
|
53
|
+
#CardProduct → Will be exported as component
|
|
54
|
+
#HeaderNav → Will be exported as component
|
|
55
|
+
Button → Ignored (no #)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Configure the project
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx @promptui-lib/cli init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Set your credentials:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
export FIGMA_TOKEN=your_token_here
|
|
68
|
+
export FIGMA_FILE_ID=file_id_here
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Generate components
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# React + SCSS
|
|
75
|
+
npx @promptui-lib/cli generate
|
|
76
|
+
|
|
77
|
+
# Bootstrap
|
|
78
|
+
npx @promptui-lib/cli generate bootstrap
|
|
79
|
+
|
|
80
|
+
# Material UI
|
|
81
|
+
npx @promptui-lib/cli generate mui
|
|
82
|
+
|
|
83
|
+
# Tailwind CSS
|
|
84
|
+
npx @promptui-lib/cli generate tailwind
|
|
85
|
+
|
|
86
|
+
# Flutter
|
|
87
|
+
npx @promptui-lib/cli generate flutter
|
|
88
|
+
|
|
89
|
+
# SwiftUI
|
|
90
|
+
npx @promptui-lib/cli generate swiftui
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
### Example 1: Button with React + SCSS
|
|
98
|
+
|
|
99
|
+
**In Figma:** Frame named `#Button`
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
#Button (Frame)
|
|
103
|
+
├── Padding: 12px 24px
|
|
104
|
+
├── Background: #3B82F6
|
|
105
|
+
├── Border Radius: 8px
|
|
106
|
+
└── label (Text: "Click me")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Command:**
|
|
110
|
+
```bash
|
|
111
|
+
npx @promptui-lib/cli generate
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Result:**
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// src/components/atoms/Button/button.tsx
|
|
118
|
+
import type { ReactNode } from 'react';
|
|
119
|
+
import './button.scss';
|
|
120
|
+
|
|
121
|
+
export interface IButtonProps {
|
|
122
|
+
children?: ReactNode;
|
|
123
|
+
className?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const Button = ({ children, className = '' }: IButtonProps) => {
|
|
127
|
+
return (
|
|
128
|
+
<button className={`button ${className}`.trim()}>
|
|
129
|
+
<span className="button__label">{children}</span>
|
|
130
|
+
</button>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```scss
|
|
136
|
+
// src/components/atoms/Button/button.scss
|
|
137
|
+
.button {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
padding: $spacing-sm $spacing-lg;
|
|
142
|
+
background-color: $color-primary;
|
|
143
|
+
border-radius: $radius-medium;
|
|
144
|
+
|
|
145
|
+
&__label {
|
|
146
|
+
color: $color-text-inverse;
|
|
147
|
+
font-weight: $font-weight-medium;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### Example 2: Card with Bootstrap
|
|
155
|
+
|
|
156
|
+
**In Figma:** Frame named `#CardProduct`
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
#CardProduct (Frame)
|
|
160
|
+
├── image (Rectangle)
|
|
161
|
+
├── content (Frame)
|
|
162
|
+
│ ├── title (Text: "Product Name")
|
|
163
|
+
│ ├── description (Text: "Lorem ipsum...")
|
|
164
|
+
│ └── price (Text: "$99.00")
|
|
165
|
+
└── button (Instance of #Button)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Command:**
|
|
169
|
+
```bash
|
|
170
|
+
npx @promptui-lib/cli generate bootstrap
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Result:**
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
// src/components/molecules/CardProduct/card-product.tsx
|
|
177
|
+
import type { ReactNode } from 'react';
|
|
178
|
+
|
|
179
|
+
export interface ICardProductProps {
|
|
180
|
+
children?: ReactNode;
|
|
181
|
+
className?: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const CardProduct = ({ children, className = '' }: ICardProductProps) => {
|
|
185
|
+
return (
|
|
186
|
+
<div className={`card ${className}`.trim()}>
|
|
187
|
+
<img className="card-img-top" alt="Product" />
|
|
188
|
+
<div className="card-body">
|
|
189
|
+
<h5 className="card-title fw-bold">Product Name</h5>
|
|
190
|
+
<p className="card-text text-muted">Lorem ipsum...</p>
|
|
191
|
+
<p className="h4 text-primary fw-bold">$99.00</p>
|
|
192
|
+
<button className="btn btn-primary w-100">Buy Now</button>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### Example 3: Header with Tailwind CSS
|
|
202
|
+
|
|
203
|
+
**In Figma:** Frame named `#HeaderNav`
|
|
204
|
+
|
|
205
|
+
**Command:**
|
|
206
|
+
```bash
|
|
207
|
+
npx @promptui-lib/cli generate tailwind
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Result:**
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
// src/components/organisms/HeaderNav/header-nav.tsx
|
|
214
|
+
export const HeaderNav = ({ className = '' }: IHeaderNavProps) => {
|
|
215
|
+
return (
|
|
216
|
+
<header className={`flex items-center justify-between px-6 py-4 bg-white shadow-sm ${className}`.trim()}>
|
|
217
|
+
<div className="flex items-center gap-2">
|
|
218
|
+
<img src="/logo.svg" className="h-8 w-8" alt="Logo" />
|
|
219
|
+
<span className="text-xl font-bold text-gray-900">Brand</span>
|
|
220
|
+
</div>
|
|
221
|
+
<nav className="flex items-center gap-8">
|
|
222
|
+
<a className="text-gray-600 hover:text-gray-900">Home</a>
|
|
223
|
+
<a className="text-gray-600 hover:text-gray-900">Products</a>
|
|
224
|
+
<a className="text-gray-600 hover:text-gray-900">About</a>
|
|
225
|
+
</nav>
|
|
226
|
+
<button className="px-4 py-2 bg-blue-500 text-white rounded-lg">
|
|
227
|
+
Sign In
|
|
228
|
+
</button>
|
|
229
|
+
</header>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### Example 4: Button with Flutter
|
|
237
|
+
|
|
238
|
+
**In Figma:** Frame named `#Button`
|
|
239
|
+
|
|
240
|
+
**Command:**
|
|
241
|
+
```bash
|
|
242
|
+
npx @promptui-lib/cli generate flutter
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Result:**
|
|
246
|
+
|
|
247
|
+
```dart
|
|
248
|
+
/// Button
|
|
249
|
+
/// Generated by PromptUI (Flutter)
|
|
250
|
+
|
|
251
|
+
import 'package:flutter/material.dart';
|
|
252
|
+
|
|
253
|
+
class Button extends StatelessWidget {
|
|
254
|
+
const Button({super.key});
|
|
255
|
+
|
|
256
|
+
@override
|
|
257
|
+
Widget build(BuildContext context) {
|
|
258
|
+
return Container(
|
|
259
|
+
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24),
|
|
260
|
+
decoration: BoxDecoration(
|
|
261
|
+
color: Theme.of(context).primaryColor,
|
|
262
|
+
borderRadius: BorderRadius.circular(8),
|
|
263
|
+
),
|
|
264
|
+
child: Text('Click me'),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### Example 5: Button with SwiftUI
|
|
273
|
+
|
|
274
|
+
**In Figma:** Frame named `#Button`
|
|
275
|
+
|
|
276
|
+
**Command:**
|
|
277
|
+
```bash
|
|
278
|
+
npx @promptui-lib/cli generate swiftui
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Result:**
|
|
282
|
+
|
|
283
|
+
```swift
|
|
284
|
+
/// Button
|
|
285
|
+
/// Generated by PromptUI (SwiftUI)
|
|
286
|
+
|
|
287
|
+
import SwiftUI
|
|
288
|
+
|
|
289
|
+
struct Button: View {
|
|
290
|
+
var body: some View {
|
|
291
|
+
Text("Click me")
|
|
292
|
+
.padding(.horizontal, 24)
|
|
293
|
+
.padding(.vertical, 8)
|
|
294
|
+
.background(.blue)
|
|
295
|
+
.foregroundColor(.white)
|
|
296
|
+
.cornerRadius(8)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#Preview {
|
|
301
|
+
Button()
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Commands
|
|
308
|
+
|
|
309
|
+
| Command | Description |
|
|
310
|
+
|---------|-------------|
|
|
311
|
+
| `npx @promptui-lib/cli init` | Configure the project |
|
|
312
|
+
| `npx @promptui-lib/cli generate` | Generate components (React + SCSS) |
|
|
313
|
+
| `npx @promptui-lib/cli generate bootstrap` | Generate with Bootstrap |
|
|
314
|
+
| `npx @promptui-lib/cli generate mui` | Generate with Material UI |
|
|
315
|
+
| `npx @promptui-lib/cli generate tailwind` | Generate with Tailwind CSS |
|
|
316
|
+
| `npx @promptui-lib/cli generate flutter` | Generate with Flutter/Dart |
|
|
317
|
+
| `npx @promptui-lib/cli generate swiftui` | Generate with SwiftUI |
|
|
318
|
+
| `npx @promptui-lib/cli sync tokens` | Sync design tokens |
|
|
319
|
+
|
|
320
|
+
## Options
|
|
321
|
+
|
|
322
|
+
| Option | Description |
|
|
323
|
+
|--------|-------------|
|
|
324
|
+
| `-p, --preview` | Preview without saving |
|
|
325
|
+
| `-o, --output <dir>` | Output directory |
|
|
326
|
+
| `-f, --force` | Overwrite existing files |
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Atomic Design
|
|
331
|
+
|
|
332
|
+
Components are automatically organized:
|
|
333
|
+
|
|
334
|
+
| Layer | Description | Examples |
|
|
335
|
+
|-------|-------------|----------|
|
|
336
|
+
| **atoms** | Simple components | Button, Input, Label, Icon |
|
|
337
|
+
| **molecules** | Medium compositions | Card, SearchBar, FormField |
|
|
338
|
+
| **organisms** | Complex compositions | Header, Footer, Sidebar |
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Configuration
|
|
343
|
+
|
|
344
|
+
Create `promptui.config.json`:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"figma": {
|
|
349
|
+
"fileId": "your-file-id"
|
|
350
|
+
},
|
|
351
|
+
"output": {
|
|
352
|
+
"basePath": "src/components"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## For Designers
|
|
360
|
+
|
|
361
|
+
### Figma Rules
|
|
362
|
+
|
|
363
|
+
1. **`#` prefix** - Add to frame name to export
|
|
364
|
+
2. **Auto Layout** - Always use to maintain structure
|
|
365
|
+
3. **Descriptive names** - `title`, `content`, not "Frame 1"
|
|
366
|
+
4. **PascalCase** - `#ButtonPrimary`, not `#button-primary`
|
|
367
|
+
|
|
368
|
+
### Correct structure example
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
#CardProduct (Frame, Auto Layout Vertical)
|
|
372
|
+
├── image (Rectangle, Aspect Ratio 16:9)
|
|
373
|
+
├── content (Frame, Auto Layout Vertical, Padding 16px)
|
|
374
|
+
│ ├── title (Text, Heading/H3)
|
|
375
|
+
│ ├── description (Text, Body/Small)
|
|
376
|
+
│ └── price (Text, Heading/H2)
|
|
377
|
+
└── actions (Frame, Auto Layout Horizontal)
|
|
378
|
+
└── button (Instance of #Button)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Environment Variables
|
|
384
|
+
|
|
385
|
+
| Variable | Description |
|
|
386
|
+
|----------|-------------|
|
|
387
|
+
| `FIGMA_TOKEN` | Figma access token |
|
|
388
|
+
| `FIGMA_FILE_ID` | Figma file ID |
|
|
389
|
+
|
|
390
|
+
### How to get Figma Token
|
|
391
|
+
|
|
392
|
+
1. Go to [Figma Account Settings](https://www.figma.com/settings)
|
|
393
|
+
2. Navigate to "Personal Access Tokens"
|
|
394
|
+
3. Create a new token
|
|
395
|
+
|
|
396
|
+
### How to get File ID
|
|
397
|
+
|
|
398
|
+
The File ID is in your Figma file URL:
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
https://www.figma.com/file/ABC123xyz/MyProject
|
|
402
|
+
^^^^^^^^^^^
|
|
403
|
+
This is the File ID
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Author
|
|
409
|
+
|
|
410
|
+
**Desiree Menezes** - [@desireemenezes](https://github.com/desireemenezes)
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## License
|
|
415
|
+
|
|
416
|
+
Proprietary - All rights reserved.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flutter Template
|
|
3
|
+
* Generates Flutter/Dart widgets from Figma designs
|
|
4
|
+
*/
|
|
5
|
+
import type { IComponentAST } from '@promptui-lib/core';
|
|
6
|
+
/**
|
|
7
|
+
* Generates Flutter StatelessWidget
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateFlutterComponent(ast: IComponentAST): string;
|
|
10
|
+
//# sourceMappingURL=flutter.template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flutter.template.d.ts","sourceRoot":"","sources":["../../src/frameworks/flutter.template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AA0NlF;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAwBnE"}
|
|
@@ -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';
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptui-lib/codegen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Code generator for PromptUI - generates React TSX and SCSS",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@promptui-lib/core": "0.1.
|
|
33
|
+
"@promptui-lib/core": "0.1.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^20.0.0",
|