create-visualbuild-app 0.1.0

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.
Files changed (44) hide show
  1. package/README.md +123 -0
  2. package/package.json +85 -0
  3. package/scripts/create-builder-app.mjs +501 -0
  4. package/scripts/vb-dev.mjs +32 -0
  5. package/scripts/vb-generate.mjs +224 -0
  6. package/templates/default/app/README.md +21 -0
  7. package/templates/default/app/eslint.config.js +25 -0
  8. package/templates/default/app/index.html +15 -0
  9. package/templates/default/app/src/index.css +23 -0
  10. package/templates/default/app/src/main.tsx +10 -0
  11. package/templates/default/app/tsconfig.app.json +21 -0
  12. package/templates/default/app/tsconfig.json +7 -0
  13. package/templates/default/app/tsconfig.node.json +19 -0
  14. package/templates/default/app/visualbuild/generated-files.json +3 -0
  15. package/templates/default/app/visualbuild/pages.json +12 -0
  16. package/templates/default/app/vite.config.ts +7 -0
  17. package/templates/default/builder/README.md +21 -0
  18. package/templates/default/builder/eslint.config.js +25 -0
  19. package/templates/default/builder/tsconfig.app.json +21 -0
  20. package/templates/default/builder/tsconfig.json +7 -0
  21. package/templates/default/builder/tsconfig.node.json +22 -0
  22. package/visual-app-builder/index.html +15 -0
  23. package/visual-app-builder/server/parseReactPage.ts +571 -0
  24. package/visual-app-builder/shared/generateReactSource.d.mts +37 -0
  25. package/visual-app-builder/shared/generateReactSource.mjs +443 -0
  26. package/visual-app-builder/src/App.tsx +874 -0
  27. package/visual-app-builder/src/components/Canvas.tsx +1059 -0
  28. package/visual-app-builder/src/components/CodePanel.tsx +812 -0
  29. package/visual-app-builder/src/components/ComponentSidebar.tsx +302 -0
  30. package/visual-app-builder/src/components/PageSwitcher.tsx +161 -0
  31. package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -0
  32. package/visual-app-builder/src/components/PropertiesPanel.tsx +692 -0
  33. package/visual-app-builder/src/components/Topbar.tsx +128 -0
  34. package/visual-app-builder/src/data/componentRegistry.tsx +292 -0
  35. package/visual-app-builder/src/data/primitiveRegistry.ts +368 -0
  36. package/visual-app-builder/src/index.css +111 -0
  37. package/visual-app-builder/src/main.tsx +10 -0
  38. package/visual-app-builder/src/stores/useAppStore.ts +1265 -0
  39. package/visual-app-builder/src/tailwindGeneratedSafelist.ts +36 -0
  40. package/visual-app-builder/src/types/index.ts +261 -0
  41. package/visual-app-builder/src/utils/codegen.ts +66 -0
  42. package/visual-app-builder/src/utils/projectFiles.ts +146 -0
  43. package/visual-app-builder/src/utils/projectPersistence.ts +177 -0
  44. package/visual-app-builder/vite.config.ts +1479 -0
@@ -0,0 +1,368 @@
1
+ import type { PrimitiveDefinition, PrimitiveType, PropSchema } from '../types'
2
+
3
+ const commonProps = {
4
+ id: { type: 'string', label: 'ID', default: '' },
5
+ className: { type: 'string', label: 'Tailwind classes', default: '' },
6
+ } as const
7
+
8
+ const textProp = { type: 'string', label: 'Text', default: 'Text' } as const
9
+ const textAreaProp = { type: 'text', label: 'Text', default: 'Text' } as const
10
+
11
+ function node(
12
+ type: PrimitiveType,
13
+ category: string,
14
+ description: string,
15
+ defaultProps: Record<string, unknown> = {},
16
+ propSchema: Record<string, PropSchema> = commonProps,
17
+ acceptsChildren = true
18
+ ): PrimitiveDefinition {
19
+ return {
20
+ type,
21
+ label: type,
22
+ category,
23
+ icon: type,
24
+ description,
25
+ defaultProps,
26
+ propSchema,
27
+ acceptsChildren,
28
+ }
29
+ }
30
+
31
+ function textNode(
32
+ type: PrimitiveType,
33
+ category: string,
34
+ description: string,
35
+ text: string,
36
+ className = ''
37
+ ): PrimitiveDefinition {
38
+ return node(
39
+ type,
40
+ category,
41
+ description,
42
+ { text, className },
43
+ {
44
+ text: { ...textProp, default: text },
45
+ ...commonProps,
46
+ },
47
+ false
48
+ )
49
+ }
50
+
51
+ export const primitiveRegistry: Record<PrimitiveType, PrimitiveDefinition> = {
52
+ header: node('header', 'Semantic', 'Introductory page or section content', {
53
+ className: 'w-full',
54
+ }),
55
+ nav: node('nav', 'Semantic', 'Navigation region', {
56
+ className: 'flex items-center justify-between px-6 py-3 bg-gray-900 text-white',
57
+ }),
58
+ main: node('main', 'Semantic', 'Primary page content', {
59
+ className: 'w-full min-h-screen',
60
+ }),
61
+ section: node('section', 'Semantic', 'Standalone page section', {
62
+ className: 'w-full px-6 py-10',
63
+ }),
64
+ article: node('article', 'Semantic', 'Self-contained content region', {
65
+ className: 'w-full bg-white border border-gray-200 p-5 shadow-sm',
66
+ }),
67
+ aside: node('aside', 'Semantic', 'Secondary sidebar content', {
68
+ className: 'w-full',
69
+ }),
70
+ footer: node('footer', 'Semantic', 'Footer region', {
71
+ className: 'w-full bg-gray-900 text-gray-400 text-center py-4 text-xs',
72
+ }),
73
+ address: textNode(
74
+ 'address',
75
+ 'Semantic',
76
+ 'Contact information',
77
+ 'Contact information',
78
+ 'not-italic'
79
+ ),
80
+ hgroup: node('hgroup', 'Semantic', 'Grouped heading content', {
81
+ className: 'space-y-2',
82
+ }),
83
+
84
+ div: node('div', 'Layout', 'Generic layout box', {
85
+ className: 'w-full min-h-12',
86
+ }),
87
+ span: textNode('span', 'Layout', 'Inline text wrapper', 'Inline text', 'text-sm'),
88
+
89
+ h1: textNode('h1', 'Text', 'Main heading', 'Heading', 'text-3xl font-bold'),
90
+ h2: textNode(
91
+ 'h2',
92
+ 'Text',
93
+ 'Section heading',
94
+ 'Section heading',
95
+ 'text-2xl font-semibold'
96
+ ),
97
+ h3: textNode('h3', 'Text', 'Small heading', 'Small heading', 'text-xl font-semibold'),
98
+ h4: textNode('h4', 'Text', 'Fourth-level heading', 'Heading', 'text-lg font-semibold'),
99
+ h5: textNode('h5', 'Text', 'Fifth-level heading', 'Heading', 'text-base font-semibold'),
100
+ h6: textNode('h6', 'Text', 'Sixth-level heading', 'Heading', 'text-sm font-semibold'),
101
+ p: node(
102
+ 'p',
103
+ 'Text',
104
+ 'Paragraph text',
105
+ { text: 'Paragraph text', className: 'text-sm text-gray-600' },
106
+ {
107
+ text: { ...textAreaProp, default: 'Paragraph text' },
108
+ ...commonProps,
109
+ },
110
+ false
111
+ ),
112
+ br: node('br', 'Text', 'Line break', {}, commonProps, false),
113
+ hr: node('hr', 'Text', 'Thematic divider', { className: 'border-gray-200' }, commonProps, false),
114
+ pre: textNode('pre', 'Text', 'Preformatted text', 'Preformatted text', 'font-mono text-sm'),
115
+ blockquote: textNode(
116
+ 'blockquote',
117
+ 'Text',
118
+ 'Quoted block',
119
+ 'Quote text',
120
+ 'border-l-4 border-gray-300 pl-4 italic'
121
+ ),
122
+ strong: textNode('strong', 'Text', 'Strong importance', 'Strong text', 'font-bold'),
123
+ b: textNode('b', 'Text', 'Bold text', 'Bold text', 'font-bold'),
124
+ em: textNode('em', 'Text', 'Emphasized text', 'Emphasized text', 'italic'),
125
+ i: textNode('i', 'Text', 'Italic text', 'Italic text', 'italic'),
126
+ u: textNode('u', 'Text', 'Underlined text', 'Underlined text', 'underline'),
127
+ s: textNode('s', 'Text', 'Struck-through text', 'Struck text', 'line-through'),
128
+ small: textNode('small', 'Text', 'Small print text', 'Small text', 'text-xs'),
129
+ mark: textNode('mark', 'Text', 'Highlighted text', 'Highlighted text', 'bg-yellow-200'),
130
+ sub: textNode('sub', 'Text', 'Subscript text', 'subscript'),
131
+ sup: textNode('sup', 'Text', 'Superscript text', 'superscript'),
132
+ code: textNode('code', 'Text', 'Inline code', 'code', 'font-mono text-sm'),
133
+ kbd: textNode('kbd', 'Text', 'Keyboard input', 'Ctrl K', 'font-mono text-xs'),
134
+ samp: textNode('samp', 'Text', 'Sample output', 'Sample output', 'font-mono text-sm'),
135
+ var: textNode('var', 'Text', 'Variable text', 'variable', 'italic'),
136
+ abbr: textNode('abbr', 'Text', 'Abbreviation', 'HTML'),
137
+ cite: textNode('cite', 'Text', 'Citation title', 'Citation', 'italic'),
138
+ q: textNode('q', 'Text', 'Inline quotation', 'Inline quote'),
139
+ time: textNode('time', 'Text', 'Time or date', '2026-07-10'),
140
+
141
+ a: node(
142
+ 'a',
143
+ 'Links & Lists',
144
+ 'Anchor link',
145
+ { text: 'Link', href: '#', target: '_self', className: 'text-sm text-blue-600' },
146
+ {
147
+ text: { type: 'string', label: 'Text', default: 'Link' },
148
+ href: { type: 'string', label: 'Href', default: '#' },
149
+ target: {
150
+ type: 'select',
151
+ label: 'Target',
152
+ default: '_self',
153
+ options: ['_self', '_blank'],
154
+ },
155
+ ...commonProps,
156
+ },
157
+ false
158
+ ),
159
+ ul: node('ul', 'Links & Lists', 'Unordered list', { className: 'list-disc pl-6' }),
160
+ ol: node('ol', 'Links & Lists', 'Ordered list', { className: 'list-decimal pl-6' }),
161
+ li: textNode('li', 'Links & Lists', 'List item', 'List item'),
162
+ dl: node('dl', 'Links & Lists', 'Description list', { className: 'space-y-2' }),
163
+ dt: textNode('dt', 'Links & Lists', 'Description term', 'Term', 'font-semibold'),
164
+ dd: textNode('dd', 'Links & Lists', 'Description details', 'Description', 'ml-4'),
165
+ menu: node('menu', 'Links & Lists', 'Menu list', { className: 'flex gap-2' }),
166
+
167
+ img: node(
168
+ 'img',
169
+ 'Media',
170
+ 'Image element',
171
+ { src: '', alt: '', className: 'max-w-full' },
172
+ {
173
+ src: { type: 'string', label: 'Source', default: '' },
174
+ alt: { type: 'string', label: 'Alt text', default: '' },
175
+ ...commonProps,
176
+ },
177
+ false
178
+ ),
179
+ picture: node('picture', 'Media', 'Responsive image wrapper'),
180
+ source: node(
181
+ 'source',
182
+ 'Media',
183
+ 'Media source',
184
+ { src: '', type: '' },
185
+ {
186
+ src: { type: 'string', label: 'Source', default: '' },
187
+ type: { type: 'string', label: 'MIME type', default: '' },
188
+ ...commonProps,
189
+ },
190
+ false
191
+ ),
192
+ figure: node('figure', 'Media', 'Figure with optional caption', {
193
+ className: 'w-full',
194
+ }),
195
+ figcaption: textNode('figcaption', 'Media', 'Figure caption', 'Caption', 'text-sm text-gray-500'),
196
+ video: node(
197
+ 'video',
198
+ 'Media',
199
+ 'Video player',
200
+ { src: '', controls: true, className: 'w-full' },
201
+ {
202
+ src: { type: 'string', label: 'Source', default: '' },
203
+ controls: { type: 'boolean', label: 'Controls', default: true },
204
+ ...commonProps,
205
+ },
206
+ false
207
+ ),
208
+ audio: node(
209
+ 'audio',
210
+ 'Media',
211
+ 'Audio player',
212
+ { src: '', controls: true },
213
+ {
214
+ src: { type: 'string', label: 'Source', default: '' },
215
+ controls: { type: 'boolean', label: 'Controls', default: true },
216
+ ...commonProps,
217
+ },
218
+ false
219
+ ),
220
+ iframe: node(
221
+ 'iframe',
222
+ 'Media',
223
+ 'Embedded frame',
224
+ { src: '', title: 'Embedded content', className: 'w-full aspect-video' },
225
+ {
226
+ src: { type: 'string', label: 'Source', default: '' },
227
+ title: { type: 'string', label: 'Title', default: 'Embedded content' },
228
+ ...commonProps,
229
+ },
230
+ false
231
+ ),
232
+ canvas: node('canvas', 'Media', 'Canvas drawing surface', { className: 'w-full min-h-48' }, commonProps, false),
233
+ svg: node('svg', 'Media', 'Inline SVG container', { className: 'w-6 h-6' }),
234
+
235
+ form: node('form', 'Forms', 'Form container', { className: 'space-y-4' }),
236
+ label: node(
237
+ 'label',
238
+ 'Forms',
239
+ 'Form label',
240
+ { text: 'Label', htmlFor: '', className: 'text-sm font-medium' },
241
+ {
242
+ text: { type: 'string', label: 'Text', default: 'Label' },
243
+ htmlFor: { type: 'string', label: 'For', default: '' },
244
+ ...commonProps,
245
+ },
246
+ false
247
+ ),
248
+ input: node(
249
+ 'input',
250
+ 'Forms',
251
+ 'Text input',
252
+ { type: 'text', name: '', placeholder: 'Enter text', className: 'w-full border border-gray-300 px-3 py-2' },
253
+ {
254
+ type: { type: 'string', label: 'Type', default: 'text' },
255
+ name: { type: 'string', label: 'Name', default: '' },
256
+ placeholder: { type: 'string', label: 'Placeholder', default: 'Enter text' },
257
+ ...commonProps,
258
+ },
259
+ false
260
+ ),
261
+ textarea: node(
262
+ 'textarea',
263
+ 'Forms',
264
+ 'Multiline text input',
265
+ { text: '', name: '', placeholder: 'Enter text', className: 'w-full min-h-24 border border-gray-300 px-3 py-2' },
266
+ {
267
+ text: { ...textAreaProp, default: '' },
268
+ name: { type: 'string', label: 'Name', default: '' },
269
+ placeholder: { type: 'string', label: 'Placeholder', default: 'Enter text' },
270
+ ...commonProps,
271
+ },
272
+ false
273
+ ),
274
+ button: node(
275
+ 'button',
276
+ 'Forms',
277
+ 'Native button',
278
+ { text: 'Button', type: 'button', className: 'px-4 py-2 rounded bg-gray-900 text-white text-sm' },
279
+ {
280
+ text: { type: 'string', label: 'Text', default: 'Button' },
281
+ type: {
282
+ type: 'select',
283
+ label: 'Type',
284
+ default: 'button',
285
+ options: ['button', 'submit', 'reset'],
286
+ },
287
+ ...commonProps,
288
+ },
289
+ false
290
+ ),
291
+ select: node('select', 'Forms', 'Select input', {
292
+ className: 'w-full border border-gray-300 px-3 py-2',
293
+ }),
294
+ option: node(
295
+ 'option',
296
+ 'Forms',
297
+ 'Select option',
298
+ { text: 'Option', value: '' },
299
+ {
300
+ text: { type: 'string', label: 'Text', default: 'Option' },
301
+ value: { type: 'string', label: 'Value', default: '' },
302
+ ...commonProps,
303
+ },
304
+ false
305
+ ),
306
+ optgroup: node(
307
+ 'optgroup',
308
+ 'Forms',
309
+ 'Option group',
310
+ { label: 'Group' },
311
+ {
312
+ label: { type: 'string', label: 'Label', default: 'Group' },
313
+ ...commonProps,
314
+ }
315
+ ),
316
+ fieldset: node('fieldset', 'Forms', 'Grouped form fields', {
317
+ className: 'border border-gray-300 p-4',
318
+ }),
319
+ legend: textNode('legend', 'Forms', 'Fieldset legend', 'Legend', 'px-1 text-sm font-medium'),
320
+ datalist: node('datalist', 'Forms', 'Autocomplete options'),
321
+ output: textNode('output', 'Forms', 'Calculation output', 'Output'),
322
+ progress: node(
323
+ 'progress',
324
+ 'Forms',
325
+ 'Progress indicator',
326
+ { value: 40, max: 100, className: 'w-full' },
327
+ {
328
+ value: { type: 'number', label: 'Value', default: 40 },
329
+ max: { type: 'number', label: 'Max', default: 100 },
330
+ ...commonProps,
331
+ },
332
+ false
333
+ ),
334
+ meter: node(
335
+ 'meter',
336
+ 'Forms',
337
+ 'Scalar measurement',
338
+ { value: 40, min: 0, max: 100, className: 'w-full' },
339
+ {
340
+ value: { type: 'number', label: 'Value', default: 40 },
341
+ min: { type: 'number', label: 'Min', default: 0 },
342
+ max: { type: 'number', label: 'Max', default: 100 },
343
+ ...commonProps,
344
+ },
345
+ false
346
+ ),
347
+
348
+ table: node('table', 'Tables', 'Table', { className: 'w-full border-collapse' }),
349
+ caption: textNode('caption', 'Tables', 'Table caption', 'Caption', 'caption-top text-sm'),
350
+ thead: node('thead', 'Tables', 'Table header group'),
351
+ tbody: node('tbody', 'Tables', 'Table body group'),
352
+ tfoot: node('tfoot', 'Tables', 'Table footer group'),
353
+ tr: node('tr', 'Tables', 'Table row'),
354
+ th: textNode('th', 'Tables', 'Header cell', 'Header', 'border px-3 py-2 text-left font-semibold'),
355
+ td: textNode('td', 'Tables', 'Table cell', 'Cell', 'border px-3 py-2'),
356
+ colgroup: node('colgroup', 'Tables', 'Column group'),
357
+ col: node('col', 'Tables', 'Table column', {}, commonProps, false),
358
+
359
+ details: node('details', 'Interactive', 'Disclosure container', {
360
+ className: 'w-full',
361
+ }),
362
+ summary: textNode('summary', 'Interactive', 'Disclosure summary', 'Summary', 'cursor-pointer'),
363
+ dialog: node('dialog', 'Interactive', 'Dialog element', {
364
+ className: 'rounded border border-gray-200 p-4',
365
+ }),
366
+ }
367
+
368
+ export const primitives = Object.values(primitiveRegistry)
@@ -0,0 +1,111 @@
1
+ @import "tailwindcss" source(none);
2
+ @source ".";
3
+ @source "../.visualbuild-runtime-classes.html";
4
+
5
+ *,
6
+ *::before,
7
+ *::after {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ html,
12
+ body,
13
+ #root {
14
+ width: 100%;
15
+ height: 100%;
16
+ overflow: hidden;
17
+ }
18
+
19
+ body {
20
+ font-family: "Inter", system-ui, sans-serif;
21
+ -webkit-font-smoothing: antialiased;
22
+ }
23
+
24
+ [data-vb-editor-node="true"] {
25
+ cursor: pointer;
26
+ }
27
+
28
+ [data-vb-editor-node="true"]:hover,
29
+ [data-vb-hovered="true"] {
30
+ outline: 2px solid rgb(96 165 250 / 0.95) !important;
31
+ outline-offset: -2px;
32
+ box-shadow: 0 0 0 1px rgb(96 165 250 / 0.25);
33
+ }
34
+
35
+ [data-vb-selected="true"] {
36
+ outline: 2px solid rgb(37 99 235) !important;
37
+ outline-offset: -2px;
38
+ box-shadow: 0 0 0 1px rgb(37 99 235 / 0.25);
39
+ }
40
+
41
+ [data-vb-drop-target="true"] {
42
+ outline: 2px solid rgb(59 130 246) !important;
43
+ outline-offset: -2px;
44
+ }
45
+
46
+ [data-vb-dragging="true"] {
47
+ cursor: grabbing;
48
+ }
49
+
50
+ .vb-empty-node-placeholder {
51
+ pointer-events: none;
52
+ display: flex;
53
+ min-height: 2rem;
54
+ width: 100%;
55
+ align-items: center;
56
+ justify-content: center;
57
+ border: 1px dashed rgb(147 197 253 / 0.85);
58
+ border-radius: 6px;
59
+ background: rgb(239 246 255 / 0.72);
60
+ color: rgb(37 99 235);
61
+ font-size: 11px;
62
+ font-weight: 500;
63
+ line-height: 1;
64
+ }
65
+
66
+ .vb-canvas-editor-frame {
67
+ outline: 1px solid rgb(229 231 235);
68
+ outline-offset: -1px;
69
+ }
70
+
71
+ .vb-canvas-editor-frame[data-vb-canvas-over="true"] {
72
+ outline: 2px solid rgb(59 130 246);
73
+ outline-offset: -2px;
74
+ box-shadow: inset 0 0 0 2px rgb(147 197 253 / 0.45);
75
+ }
76
+
77
+ select option {
78
+ color: #111827;
79
+ background: #ffffff;
80
+ }
81
+
82
+ /* Scrollbars */
83
+
84
+ ::-webkit-scrollbar {
85
+ width: 4px;
86
+ height: 4px;
87
+ }
88
+
89
+ ::-webkit-scrollbar-track {
90
+ background: transparent;
91
+ }
92
+
93
+ ::-webkit-scrollbar-thumb {
94
+ background: #2d3139;
95
+ border-radius: 999px;
96
+ }
97
+
98
+ ::-webkit-scrollbar-thumb:hover {
99
+ background: #3d4249;
100
+ }
101
+
102
+
103
+ @theme {
104
+ --color-canvas: #f8f9fa;
105
+ --color-sidebar: #1a1d23;
106
+ --color-panel: #1a1d23;
107
+ --color-topbar: #13151a;
108
+
109
+ --color-accent: #4f6ef7;
110
+ --color-accent-hover: #3d5ce0;
111
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )