@protomarkdown/parser 1.0.0 → 1.0.1
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 +216 -6
- package/dist/ShadcnCodeGenerator.d.ts +2 -0
- package/dist/ShadcnCodeGenerator.d.ts.map +1 -1
- package/dist/index.esm.js +191 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +191 -4
- package/dist/index.js.map +1 -1
- package/dist/parser/MarkdownParser.d.ts +2 -0
- package/dist/parser/MarkdownParser.d.ts.map +1 -1
- package/dist/parser/types.d.ts +3 -1
- package/dist/parser/types.d.ts.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @protomarkdown/parser
|
|
2
2
|
|
|
3
|
-
Parser and
|
|
3
|
+
Parser and renderer for Proto Markdown syntax - A UI prototyping markdown language for rapid React component generation.
|
|
4
4
|
|
|
5
5
|
[Proto Markdown Syntax Documentation](https://www.protomarkdown.org/documentation)
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🎨 Parse Proto Markdown syntax into an Abstract Syntax Tree (AST)
|
|
10
|
+
- ⚛️ Generate React components with Shadcn UI
|
|
11
|
+
- 🔄 Multi-screen workflow navigation system
|
|
12
|
+
- 📋 Form elements (inputs, dropdowns, checkboxes, textareas)
|
|
13
|
+
- 🎯 Cards, grids, and flexible layouts
|
|
14
|
+
- 📊 Tables and data display
|
|
15
|
+
- ✨ Text formatting (bold, italic)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @protomarkdown/parser
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
8
24
|
|
|
9
25
|
```ts
|
|
10
|
-
import { MarkdownParser } from "@protomarkdown/parser";
|
|
11
|
-
import { ShadcnCodeGenerator } from "@protomarkdown/parser";
|
|
26
|
+
import { MarkdownParser, ShadcnCodeGenerator } from "@protomarkdown/parser";
|
|
12
27
|
|
|
13
28
|
const parser = new MarkdownParser();
|
|
14
29
|
const codeGenerator = new ShadcnCodeGenerator();
|
|
@@ -22,4 +37,199 @@ Password __*
|
|
|
22
37
|
|
|
23
38
|
const ast = parser.parse(markdown);
|
|
24
39
|
const code = codeGenerator.generate(ast.nodes);
|
|
25
|
-
```
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Workflows - Multi-Screen Navigation
|
|
43
|
+
|
|
44
|
+
Create complete multi-screen workflows with clickable navigation between screens:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const workflowMarkdown = `
|
|
48
|
+
[workflow
|
|
49
|
+
[screen welcome
|
|
50
|
+
# Welcome to My App
|
|
51
|
+
Get started with your journey!
|
|
52
|
+
[(Get Started) -> login]
|
|
53
|
+
[Skip to Dashboard -> dashboard]
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[screen login
|
|
57
|
+
# Login
|
|
58
|
+
Email ___
|
|
59
|
+
Password __*
|
|
60
|
+
Remember me __[]
|
|
61
|
+
[(Login) -> dashboard]
|
|
62
|
+
[Back -> welcome]
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[screen dashboard
|
|
66
|
+
# Dashboard
|
|
67
|
+
Welcome to your dashboard!
|
|
68
|
+
|
|
69
|
+
[grid cols-2 gap-4
|
|
70
|
+
[-- Stats
|
|
71
|
+
Total Users: 150
|
|
72
|
+
--]
|
|
73
|
+
[-- Activity
|
|
74
|
+
Recent activity here
|
|
75
|
+
--]
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[Logout -> welcome]
|
|
79
|
+
]
|
|
80
|
+
]`;
|
|
81
|
+
|
|
82
|
+
const ast = parser.parse(workflowMarkdown);
|
|
83
|
+
const reactCode = codeGenerator.generate(ast.nodes);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Button Navigation Syntax
|
|
87
|
+
|
|
88
|
+
- **Default button with navigation:** `[(Button Text) -> targetScreen]`
|
|
89
|
+
- **Outline button with navigation:** `[Button Text -> targetScreen]`
|
|
90
|
+
- **Multiple navigation buttons:** `[(Next) -> step2][Back -> step1]`
|
|
91
|
+
|
|
92
|
+
The generated component includes:
|
|
93
|
+
- `useState` hook for screen state management
|
|
94
|
+
- Conditional rendering of screens
|
|
95
|
+
- `onClick` handlers for seamless navigation
|
|
96
|
+
|
|
97
|
+
## Supported Elements
|
|
98
|
+
|
|
99
|
+
### Form Fields
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
Email ___ # Text input
|
|
103
|
+
Password __* # Password input
|
|
104
|
+
Description |___| # Textarea
|
|
105
|
+
Country __> [USA, Canada, Mexico] # Dropdown with options
|
|
106
|
+
Remember me __[] # Checkbox
|
|
107
|
+
Gender __() [Male, Female, Other] # Radio group
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Layouts
|
|
111
|
+
|
|
112
|
+
```markdown
|
|
113
|
+
[-- Card Title # Card
|
|
114
|
+
Content here
|
|
115
|
+
--]
|
|
116
|
+
|
|
117
|
+
[grid cols-2 gap-4 # Grid layout
|
|
118
|
+
[-- Card 1 --]
|
|
119
|
+
[-- Card 2 --]
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
[ flex gap-2 # Custom div with classes
|
|
123
|
+
Content
|
|
124
|
+
]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Buttons
|
|
128
|
+
|
|
129
|
+
```markdown
|
|
130
|
+
[(Submit)] # Default button
|
|
131
|
+
[Cancel] # Outline button
|
|
132
|
+
[(Save)][Reset] # Multiple buttons
|
|
133
|
+
[(Next) -> step2] # Navigation button (workflows)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Tables
|
|
137
|
+
|
|
138
|
+
```markdown
|
|
139
|
+
| Name | Age | City |
|
|
140
|
+
|------|-----|------|
|
|
141
|
+
| John | 30 | NYC |
|
|
142
|
+
| Jane | 25 | LA |
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Text Formatting
|
|
146
|
+
|
|
147
|
+
```markdown
|
|
148
|
+
This is *bold* text
|
|
149
|
+
This is _italic_ text
|
|
150
|
+
This is _*bold and italic*_ text
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### MarkdownParser
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const parser = new MarkdownParser(options?: ParserOptions);
|
|
159
|
+
const result = parser.parse(markdown: string);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Options:**
|
|
163
|
+
- `strict?: boolean` - Enable strict parsing mode
|
|
164
|
+
- `preserveWhitespace?: boolean` - Preserve leading/trailing whitespace
|
|
165
|
+
|
|
166
|
+
**Returns:** `ParseResult` containing `nodes` (AST) and optional `errors`
|
|
167
|
+
|
|
168
|
+
### ShadcnCodeGenerator
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const generator = new ShadcnCodeGenerator();
|
|
172
|
+
const code = generator.generate(nodes: MarkdownNode[]);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Returns:** Complete React component code as a string with necessary Shadcn UI imports
|
|
176
|
+
|
|
177
|
+
## Examples
|
|
178
|
+
|
|
179
|
+
### Login Form
|
|
180
|
+
|
|
181
|
+
```markdown
|
|
182
|
+
[-- Login
|
|
183
|
+
# Welcome Back
|
|
184
|
+
Email ___
|
|
185
|
+
Password __*
|
|
186
|
+
Remember me __[]
|
|
187
|
+
[(Login)][Forgot Password]
|
|
188
|
+
--]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Multi-Step Form Workflow
|
|
192
|
+
|
|
193
|
+
```markdown
|
|
194
|
+
[workflow
|
|
195
|
+
[screen step1
|
|
196
|
+
# Step 1: Personal Info
|
|
197
|
+
First Name ___ Last Name ___
|
|
198
|
+
Email ___
|
|
199
|
+
[(Next) -> step2]
|
|
200
|
+
]
|
|
201
|
+
[screen step2
|
|
202
|
+
# Step 2: Address
|
|
203
|
+
Street ___
|
|
204
|
+
City ___ State ___
|
|
205
|
+
[(Back) -> step1][(Next) -> step3]
|
|
206
|
+
]
|
|
207
|
+
[screen step3
|
|
208
|
+
# Step 3: Review
|
|
209
|
+
Please review your information
|
|
210
|
+
[(Submit) -> confirmation][(Back) -> step2]
|
|
211
|
+
]
|
|
212
|
+
[screen confirmation
|
|
213
|
+
# Success!
|
|
214
|
+
Your form has been submitted.
|
|
215
|
+
[Start Over -> step1]
|
|
216
|
+
]
|
|
217
|
+
]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Testing
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm test # Run all tests
|
|
224
|
+
npm run build # Build the library
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
Apache-2.0
|
|
230
|
+
|
|
231
|
+
## Links
|
|
232
|
+
|
|
233
|
+
- [Proto Markdown Documentation](https://www.protomarkdown.org/documentation)
|
|
234
|
+
- [GitHub Repository](https://github.com/georgii-sh/proto-markdown-parser)
|
|
235
|
+
- [Shadcn UI](https://ui.shadcn.com/)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShadcnCodeGenerator.d.ts","sourceRoot":"","sources":["../src/ShadcnCodeGenerator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,eAAe,CAAqB;IAE5C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"ShadcnCodeGenerator.d.ts","sourceRoot":"","sources":["../src/ShadcnCodeGenerator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,eAAe,CAAqB;IAE5C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM;IAgCvC;;OAEG;IACH,OAAO,CAAC,eAAe;IAkCvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,gBAAgB;IAoCxB,OAAO,CAAC,cAAc;CAWvB"}
|
package/dist/index.esm.js
CHANGED
|
@@ -56,6 +56,13 @@ class MarkdownParser {
|
|
|
56
56
|
});
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
|
+
// Check for workflow start
|
|
60
|
+
if (line === '[workflow' || line.startsWith('[workflow ')) {
|
|
61
|
+
const result = this.parseWorkflow(lines, i);
|
|
62
|
+
nodes.push(result.node);
|
|
63
|
+
i = result.nextIndex;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
59
66
|
// Check for card start
|
|
60
67
|
const cardMatch = line.match(/^\[--\s*(.*)$/);
|
|
61
68
|
if (cardMatch) {
|
|
@@ -91,6 +98,100 @@ class MarkdownParser {
|
|
|
91
98
|
}
|
|
92
99
|
return { nodes, errors: errors.length > 0 ? errors : undefined };
|
|
93
100
|
}
|
|
101
|
+
parseWorkflow(lines, startIndex) {
|
|
102
|
+
const screens = [];
|
|
103
|
+
let i = startIndex + 1;
|
|
104
|
+
let depth = 1;
|
|
105
|
+
let initialScreen;
|
|
106
|
+
// Parse workflow content until we find the matching closing ]
|
|
107
|
+
while (i < lines.length && depth > 0) {
|
|
108
|
+
const workflowLine = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
|
|
109
|
+
// Check for workflow closing
|
|
110
|
+
if (workflowLine === "]") {
|
|
111
|
+
depth--;
|
|
112
|
+
if (depth === 0) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Check for screen opening ([screen id)
|
|
117
|
+
const screenMatch = workflowLine.match(/^\[screen\s+(.+)$/);
|
|
118
|
+
if (screenMatch) {
|
|
119
|
+
const screenId = screenMatch[1].trim();
|
|
120
|
+
const result = this.parseScreen(lines, i, screenId);
|
|
121
|
+
screens.push(result.node);
|
|
122
|
+
// First screen becomes the initial screen
|
|
123
|
+
if (!initialScreen) {
|
|
124
|
+
initialScreen = screenId;
|
|
125
|
+
}
|
|
126
|
+
i = result.nextIndex;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
node: {
|
|
133
|
+
type: "workflow",
|
|
134
|
+
children: screens,
|
|
135
|
+
initialScreen: initialScreen || (screens[0]?.id),
|
|
136
|
+
},
|
|
137
|
+
nextIndex: i + 1,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
parseScreen(lines, startIndex, screenId) {
|
|
141
|
+
const screenChildren = [];
|
|
142
|
+
let i = startIndex + 1;
|
|
143
|
+
let depth = 1;
|
|
144
|
+
// Parse screen content until we find the matching closing ]
|
|
145
|
+
while (i < lines.length && depth > 0) {
|
|
146
|
+
const screenLine = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
|
|
147
|
+
// Check for screen closing
|
|
148
|
+
if (screenLine === "]") {
|
|
149
|
+
depth--;
|
|
150
|
+
if (depth === 0) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Check for nested card opening
|
|
155
|
+
if (screenLine.match(/^\[--\s*(.*)$/)) {
|
|
156
|
+
const nestedTitle = screenLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
|
|
157
|
+
const result = this.parseCard(lines, i, nestedTitle);
|
|
158
|
+
screenChildren.push(result.node);
|
|
159
|
+
i = result.nextIndex;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Check for nested grid opening
|
|
163
|
+
if (screenLine.match(/^\[grid\s+(.*)$/)) {
|
|
164
|
+
const nestedConfig = screenLine.match(/^\[grid\s+(.*)$/)?.[1] || '';
|
|
165
|
+
const result = this.parseContainer(lines, i, 'grid', nestedConfig);
|
|
166
|
+
screenChildren.push(result.node);
|
|
167
|
+
i = result.nextIndex;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// Check for nested div opening
|
|
171
|
+
if (screenLine.match(/^\[\s*(.*)$/) && !screenLine.includes("]")) {
|
|
172
|
+
const nestedConfig = screenLine.match(/^\[\s*(.*)$/)?.[1] || '';
|
|
173
|
+
const result = this.parseContainer(lines, i, 'div', nestedConfig);
|
|
174
|
+
screenChildren.push(result.node);
|
|
175
|
+
i = result.nextIndex;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (screenLine) {
|
|
179
|
+
const childNode = this.parseLine(screenLine);
|
|
180
|
+
if (childNode) {
|
|
181
|
+
screenChildren.push(childNode);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
node: {
|
|
188
|
+
type: "screen",
|
|
189
|
+
id: screenId,
|
|
190
|
+
children: screenChildren,
|
|
191
|
+
},
|
|
192
|
+
nextIndex: i + 1,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
94
195
|
parseCard(lines, startIndex, title) {
|
|
95
196
|
const cardChildren = [];
|
|
96
197
|
let i = startIndex + 1;
|
|
@@ -355,13 +456,26 @@ class MarkdownParser {
|
|
|
355
456
|
};
|
|
356
457
|
}
|
|
357
458
|
// Parse multiple buttons on one line ([btn1][(btn2)])
|
|
358
|
-
const multiButtonMatch = line.match(/^(\[\(?[^\[\]
|
|
459
|
+
const multiButtonMatch = line.match(/^(\[\(?[^\[\]]+\)?\]\s*)+$/);
|
|
359
460
|
if (multiButtonMatch) {
|
|
360
|
-
const buttons = line.match(/\[(\(?)[^\[\]
|
|
461
|
+
const buttons = line.match(/\[(\(?)[^\[\]]+?(\)?)\]/g);
|
|
361
462
|
if (buttons && buttons.length > 1) {
|
|
362
463
|
return {
|
|
363
464
|
type: "container",
|
|
364
465
|
children: buttons.map((btn) => {
|
|
466
|
+
// Check for navigation syntax: [(text) -> target] or [text -> target]
|
|
467
|
+
const navMatch = btn.match(/\[(\(?)(.+?)(\)?)\s*->\s*([^\]]+)\]/);
|
|
468
|
+
if (navMatch) {
|
|
469
|
+
const isDefault = navMatch[1] === "(" && navMatch[3] === ")";
|
|
470
|
+
const content = navMatch[2].trim();
|
|
471
|
+
const navigateTo = navMatch[4].trim();
|
|
472
|
+
return {
|
|
473
|
+
type: "button",
|
|
474
|
+
content,
|
|
475
|
+
variant: isDefault ? "default" : "outline",
|
|
476
|
+
navigateTo,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
365
479
|
const innerMatch = btn.match(/\[(\(?)(.+?)(\)?)\]/);
|
|
366
480
|
if (innerMatch) {
|
|
367
481
|
const isDefault = innerMatch[1] === "(" && innerMatch[3] === ")";
|
|
@@ -381,6 +495,18 @@ class MarkdownParser {
|
|
|
381
495
|
};
|
|
382
496
|
}
|
|
383
497
|
}
|
|
498
|
+
// Parse default button with navigation [(button text) -> target]
|
|
499
|
+
const defaultButtonNavMatch = line.match(/^\[\((.+?)\)\s*->\s*([^\]]+)\]$/);
|
|
500
|
+
if (defaultButtonNavMatch) {
|
|
501
|
+
const content = defaultButtonNavMatch[1].trim();
|
|
502
|
+
const navigateTo = defaultButtonNavMatch[2].trim();
|
|
503
|
+
return {
|
|
504
|
+
type: "button",
|
|
505
|
+
content,
|
|
506
|
+
variant: "default",
|
|
507
|
+
navigateTo,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
384
510
|
// Parse default button [(button text)] or [(button text) | classes]
|
|
385
511
|
const defaultButtonMatch = line.match(/^\[\((.+?)\)(?:\s*\|\s*(.+))?\]$/);
|
|
386
512
|
if (defaultButtonMatch) {
|
|
@@ -393,6 +519,18 @@ class MarkdownParser {
|
|
|
393
519
|
...(className && { className }),
|
|
394
520
|
};
|
|
395
521
|
}
|
|
522
|
+
// Parse outline button with navigation [button text -> target]
|
|
523
|
+
const buttonNavMatch = line.match(/^\[([^|]+?)\s*->\s*([^\]]+)\]$/);
|
|
524
|
+
if (buttonNavMatch) {
|
|
525
|
+
const content = buttonNavMatch[1].trim();
|
|
526
|
+
const navigateTo = buttonNavMatch[2].trim();
|
|
527
|
+
return {
|
|
528
|
+
type: "button",
|
|
529
|
+
content,
|
|
530
|
+
variant: "outline",
|
|
531
|
+
navigateTo,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
396
534
|
// Parse outline button [button text] or [button text | classes]
|
|
397
535
|
const buttonMatch = line.match(/^\[([^|]+?)(?:\s*\|\s*(.+))?\]$/);
|
|
398
536
|
if (buttonMatch) {
|
|
@@ -501,6 +639,8 @@ class ShadcnCodeGenerator {
|
|
|
501
639
|
generate(nodes) {
|
|
502
640
|
this.indentLevel = 0;
|
|
503
641
|
this.requiredImports.clear();
|
|
642
|
+
// Check if the component contains a workflow
|
|
643
|
+
const hasWorkflow = nodes.some(node => node.type === 'workflow');
|
|
504
644
|
// Generate component body
|
|
505
645
|
const componentBody = this.generateNodes(nodes);
|
|
506
646
|
// Add base indentation (6 spaces = 3 levels for proper JSX nesting)
|
|
@@ -510,7 +650,8 @@ class ShadcnCodeGenerator {
|
|
|
510
650
|
.join('\n');
|
|
511
651
|
// Collect imports
|
|
512
652
|
const imports = this.generateImports();
|
|
513
|
-
|
|
653
|
+
const reactImport = hasWorkflow ? "import { useState } from 'react';\n" : "";
|
|
654
|
+
return `${reactImport}${imports}
|
|
514
655
|
|
|
515
656
|
export function GeneratedComponent() {
|
|
516
657
|
return (
|
|
@@ -598,6 +739,10 @@ ${indentedBody}
|
|
|
598
739
|
return this.generateItalic(node, index);
|
|
599
740
|
case "image":
|
|
600
741
|
return this.generateImage(node, index);
|
|
742
|
+
case "workflow":
|
|
743
|
+
return this.generateWorkflow(node, index);
|
|
744
|
+
case "screen":
|
|
745
|
+
return this.generateScreen(node, index);
|
|
601
746
|
default:
|
|
602
747
|
return "";
|
|
603
748
|
}
|
|
@@ -713,7 +858,9 @@ ${this.indent()}</div>`;
|
|
|
713
858
|
this.requiredImports.add("Button");
|
|
714
859
|
const variant = node.variant || "default";
|
|
715
860
|
const className = node.className ? ` className="${node.className}"` : "";
|
|
716
|
-
|
|
861
|
+
// Add onClick handler if button has navigation
|
|
862
|
+
const onClick = node.navigateTo ? ` onClick={() => setCurrentScreen('${node.navigateTo}')}` : "";
|
|
863
|
+
return `${this.indent()}<Button key={${index}} variant="${variant}"${className}${onClick}>${this.escapeJSX(node.content || "")}</Button>`;
|
|
717
864
|
}
|
|
718
865
|
generateContainer(node, index) {
|
|
719
866
|
this.indentLevel++;
|
|
@@ -814,6 +961,46 @@ ${this.indent()}</div>`;
|
|
|
814
961
|
const alt = node.alt || "";
|
|
815
962
|
return `${this.indent()}<img key={${index}} src="${src}" alt="${this.escapeJSX(alt)}" className="max-w-full h-auto" />`;
|
|
816
963
|
}
|
|
964
|
+
generateWorkflow(node, index) {
|
|
965
|
+
const screens = node.children || [];
|
|
966
|
+
const initialScreen = node.initialScreen || screens[0]?.id || "home";
|
|
967
|
+
// Generate state management
|
|
968
|
+
const stateDeclaration = `${this.indent()}const [currentScreen, setCurrentScreen] = useState('${initialScreen}');`;
|
|
969
|
+
// Generate screen rendering
|
|
970
|
+
this.indentLevel++;
|
|
971
|
+
const screenCases = screens.map((screen, i) => {
|
|
972
|
+
const screenId = screen.id || `screen-${i}`;
|
|
973
|
+
const screenContent = screen.children ? this.generateNodes(screen.children) : "";
|
|
974
|
+
return `${this.indent()}${i === 0 ? '' : 'else '}if (currentScreen === '${screenId}') {
|
|
975
|
+
${this.indent()} return (
|
|
976
|
+
${this.indent()} <div className="space-y-2">
|
|
977
|
+
${screenContent}
|
|
978
|
+
${this.indent()} </div>
|
|
979
|
+
${this.indent()} );
|
|
980
|
+
${this.indent()}}`;
|
|
981
|
+
}).join('\n');
|
|
982
|
+
this.indentLevel--;
|
|
983
|
+
// Return fallback if no screen matches
|
|
984
|
+
const fallback = `${this.indent()}return <div>Screen not found</div>;`;
|
|
985
|
+
return `${this.indent()}<div key={${index}}>
|
|
986
|
+
${this.indent()} {(() => {
|
|
987
|
+
${stateDeclaration}
|
|
988
|
+
|
|
989
|
+
${screenCases}
|
|
990
|
+
${fallback}
|
|
991
|
+
${this.indent()} })()}
|
|
992
|
+
${this.indent()}</div>`;
|
|
993
|
+
}
|
|
994
|
+
generateScreen(node, index) {
|
|
995
|
+
// Screens are handled within workflow generation
|
|
996
|
+
// This method is for standalone screen nodes (if used outside workflow)
|
|
997
|
+
this.indentLevel++;
|
|
998
|
+
const children = node.children ? this.generateNodes(node.children) : "";
|
|
999
|
+
this.indentLevel--;
|
|
1000
|
+
return `${this.indent()}<div key={${index}} data-screen-id="${node.id || index}" className="space-y-2">
|
|
1001
|
+
${children}
|
|
1002
|
+
${this.indent()}</div>`;
|
|
1003
|
+
}
|
|
817
1004
|
}
|
|
818
1005
|
|
|
819
1006
|
export { MarkdownParser, ShadcnCodeGenerator };
|