create-visualbuild-app 0.1.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.
Files changed (44) hide show
  1. package/README.md +919 -58
  2. package/docs/user-guide.md +759 -0
  3. package/package.json +92 -85
  4. package/scripts/create-builder-app.mjs +513 -501
  5. package/scripts/vb-dev.mjs +41 -32
  6. package/scripts/vb-generate.mjs +332 -224
  7. package/templates/default/app/.vercelignore +6 -0
  8. package/templates/default/app/README.md +56 -21
  9. package/templates/default/app/visualbuild/components.json +3 -0
  10. package/templates/default/app/visualbuild/config.d.ts +48 -0
  11. package/templates/default/app/visualbuild.config.ts +38 -0
  12. package/templates/default/builder/README.md +37 -21
  13. package/visual-app-builder/server/parseReactPage.ts +776 -571
  14. package/visual-app-builder/shared/createReactComponentSource.d.mts +2 -0
  15. package/visual-app-builder/shared/createReactComponentSource.mjs +45 -0
  16. package/visual-app-builder/shared/generateReactSource.d.mts +57 -37
  17. package/visual-app-builder/shared/generateReactSource.mjs +608 -443
  18. package/visual-app-builder/shared/npmBuildCommand.d.mts +17 -0
  19. package/visual-app-builder/shared/npmBuildCommand.mjs +26 -0
  20. package/visual-app-builder/shared/syncCustomComponentSource.d.mts +13 -0
  21. package/visual-app-builder/shared/syncCustomComponentSource.mjs +345 -0
  22. package/visual-app-builder/shared/vercelDeployment.d.mts +31 -0
  23. package/visual-app-builder/shared/vercelDeployment.mjs +266 -0
  24. package/visual-app-builder/shared/visualbuildConfig.d.mts +144 -0
  25. package/visual-app-builder/shared/visualbuildConfig.mjs +578 -0
  26. package/visual-app-builder/src/App.tsx +1090 -874
  27. package/visual-app-builder/src/components/Canvas.tsx +1116 -1059
  28. package/visual-app-builder/src/components/CodePanel.tsx +1147 -812
  29. package/visual-app-builder/src/components/ComponentSidebar.tsx +365 -302
  30. package/visual-app-builder/src/components/DeploymentModal.tsx +295 -0
  31. package/visual-app-builder/src/components/PageSwitcher.tsx +133 -133
  32. package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -1054
  33. package/visual-app-builder/src/components/PropertiesPanel.tsx +792 -692
  34. package/visual-app-builder/src/components/Topbar.tsx +257 -128
  35. package/visual-app-builder/src/data/componentRegistry.tsx +613 -292
  36. package/visual-app-builder/src/data/tailwindClassCatalog.ts +95 -0
  37. package/visual-app-builder/src/index.css +383 -111
  38. package/visual-app-builder/src/stores/useAppStore.ts +2385 -1265
  39. package/visual-app-builder/src/theme.ts +71 -0
  40. package/visual-app-builder/src/types/index.ts +350 -261
  41. package/visual-app-builder/src/utils/codegen.ts +90 -66
  42. package/visual-app-builder/src/utils/deployment.ts +54 -0
  43. package/visual-app-builder/src/utils/projectPersistence.ts +218 -177
  44. package/visual-app-builder/vite.config.ts +1946 -1479
@@ -0,0 +1,71 @@
1
+ import type { EditorTheme, EditorThemeChoice, ResolvedEditorTheme } from './types'
2
+
3
+ export const editorThemeStorageKey = 'visualbuild:editor-theme'
4
+
5
+ export function createEditorThemeStorageKey(projectName: string) {
6
+ return `${editorThemeStorageKey}:${encodeURIComponent(projectName)}`
7
+ }
8
+
9
+ export const editorThemeOptions: Array<{
10
+ id: EditorThemeChoice
11
+ label: string
12
+ description: string
13
+ swatches: [string, string, string]
14
+ }> = [
15
+ {
16
+ id: 'default',
17
+ label: 'VisualBuild Default',
18
+ description: 'The original deep navy editor',
19
+ swatches: ['#13151a', '#1a1d23', '#4f6ef7'],
20
+ },
21
+ {
22
+ id: 'codex-dark',
23
+ label: 'Codex Dark',
24
+ description: 'Soft neutral charcoal surfaces',
25
+ swatches: ['#181818', '#202020', '#8b8bff'],
26
+ },
27
+ {
28
+ id: 'vs-light',
29
+ label: 'VS Code Light',
30
+ description: 'Bright editor with crisp borders',
31
+ swatches: ['#f3f3f3', '#ffffff', '#007acc'],
32
+ },
33
+ {
34
+ id: 'vs-dark',
35
+ label: 'VS Code Dark',
36
+ description: 'Classic Visual Studio Code dark',
37
+ swatches: ['#181818', '#252526', '#007acc'],
38
+ },
39
+ {
40
+ id: 'system',
41
+ label: 'System',
42
+ description: 'VS Code Light or Dark automatically',
43
+ swatches: ['#f3f3f3', '#1e1e1e', '#007acc'],
44
+ },
45
+ ]
46
+
47
+ const themeChoices = new Set<EditorThemeChoice>(
48
+ editorThemeOptions.map((theme) => theme.id)
49
+ )
50
+
51
+ export function normalizeEditorTheme(theme: EditorTheme): EditorThemeChoice {
52
+ if (theme === 'dark') return 'default'
53
+ if (theme === 'light') return 'vs-light'
54
+ return theme
55
+ }
56
+
57
+ export function parseStoredEditorTheme(
58
+ value: string | null
59
+ ): EditorThemeChoice | null {
60
+ return value && themeChoices.has(value as EditorThemeChoice)
61
+ ? (value as EditorThemeChoice)
62
+ : null
63
+ }
64
+
65
+ export function resolveEditorTheme(
66
+ theme: EditorThemeChoice,
67
+ prefersLight: boolean
68
+ ): ResolvedEditorTheme {
69
+ if (theme === 'system') return prefersLight ? 'vs-light' : 'vs-dark'
70
+ return theme
71
+ }
@@ -1,261 +1,350 @@
1
- import type { ReactNode } from 'react'
2
-
3
- export type ComponentType =
4
- | 'Navbar'
5
- | 'Hero'
6
- | 'Card'
7
- | 'Button'
8
- | 'Footer'
9
-
10
- export type PrimitiveType =
11
- | 'header'
12
- | 'main'
13
- | 'div'
14
- | 'section'
15
- | 'nav'
16
- | 'article'
17
- | 'aside'
18
- | 'footer'
19
- | 'address'
20
- | 'hgroup'
21
- | 'h1'
22
- | 'h2'
23
- | 'h3'
24
- | 'h4'
25
- | 'h5'
26
- | 'h6'
27
- | 'p'
28
- | 'br'
29
- | 'hr'
30
- | 'pre'
31
- | 'blockquote'
32
- | 'span'
33
- | 'strong'
34
- | 'b'
35
- | 'em'
36
- | 'i'
37
- | 'u'
38
- | 's'
39
- | 'small'
40
- | 'mark'
41
- | 'sub'
42
- | 'sup'
43
- | 'code'
44
- | 'kbd'
45
- | 'samp'
46
- | 'var'
47
- | 'abbr'
48
- | 'cite'
49
- | 'q'
50
- | 'time'
51
- | 'a'
52
- | 'ul'
53
- | 'ol'
54
- | 'li'
55
- | 'dl'
56
- | 'dt'
57
- | 'dd'
58
- | 'menu'
59
- | 'button'
60
- | 'img'
61
- | 'picture'
62
- | 'source'
63
- | 'figure'
64
- | 'figcaption'
65
- | 'video'
66
- | 'audio'
67
- | 'iframe'
68
- | 'canvas'
69
- | 'svg'
70
- | 'form'
71
- | 'label'
72
- | 'input'
73
- | 'textarea'
74
- | 'select'
75
- | 'option'
76
- | 'optgroup'
77
- | 'fieldset'
78
- | 'legend'
79
- | 'datalist'
80
- | 'output'
81
- | 'progress'
82
- | 'meter'
83
- | 'table'
84
- | 'caption'
85
- | 'thead'
86
- | 'tbody'
87
- | 'tfoot'
88
- | 'tr'
89
- | 'th'
90
- | 'td'
91
- | 'colgroup'
92
- | 'col'
93
- | 'details'
94
- | 'summary'
95
- | 'dialog'
96
-
97
- export type AppShellSlot = 'header' | 'footer'
98
-
99
- export type Viewport = 'mobile' | 'tablet' | 'desktop'
100
-
101
- export type ProjectRefreshResult =
102
- | 'updated'
103
- | 'skipped-dirty'
104
- | 'failed'
105
-
106
- export type PropType =
107
- | 'string'
108
- | 'text'
109
- | 'number'
110
- | 'boolean'
111
- | 'color'
112
- | 'select'
113
- | 'array'
114
-
115
- export interface PropSchema {
116
- type: PropType
117
- label: string
118
- default: unknown
119
- options?: string[]
120
- }
121
-
122
- export interface ComponentDefinition {
123
- type: ComponentType
124
- label: string
125
- category: string
126
- icon: string
127
- description: string
128
- defaultProps: Record<string, unknown>
129
- propSchema: Record<string, PropSchema>
130
- renderer: (
131
- props: Record<string, unknown>,
132
- context: ComponentRenderContext
133
- ) => ReactNode
134
- generator: (props: Record<string, unknown>) => string
135
- source: () => string
136
- }
137
-
138
- export interface PrimitiveDefinition {
139
- type: PrimitiveType
140
- label: string
141
- category: string
142
- icon: string
143
- description: string
144
- defaultProps: Record<string, unknown>
145
- propSchema: Record<string, PropSchema>
146
- acceptsChildren: boolean
147
- }
148
-
149
- export interface ComponentRenderContext {
150
- pages: Page[]
151
- activePageId: string
152
- setActivePage: (id: string) => void
153
- }
154
-
155
- export interface VisualNode {
156
- id: string
157
- type: PrimitiveType
158
- label?: string
159
- props: Record<string, unknown>
160
- children: VisualNode[]
161
- }
162
-
163
- export interface Page {
164
- id: string
165
- name: string
166
- route: string
167
- sourcePath: string
168
- root: VisualNode
169
- components: VisualNode[]
170
- }
171
-
172
- export interface AppShell {
173
- header: VisualNode | null
174
- footer: VisualNode | null
175
- }
176
-
177
- export interface AppState {
178
- projectName: string
179
- appShell: AppShell
180
- pages: Page[]
181
- activePageId: string
182
- selectedComponentId: string | null
183
- viewport: Viewport
184
- previewMode: boolean
185
- isProjectLoaded: boolean
186
- // Actions
187
- loadProject: () => Promise<void>
188
- refreshProjectFromDisk: (
189
- force?: boolean
190
- ) => Promise<ProjectRefreshResult>
191
- saveProject: () => Promise<void>
192
- addPage: (name: string, route: string) => Page
193
- registerPage: (page: Page) => void
194
- deletePage: (id: string) => void
195
- setActivePage: (id: string) => void
196
- setSelectedComponent: (id: string | null) => void
197
- setViewport: (viewport: Viewport) => void
198
- togglePreviewMode: () => void
199
- addComponent: (pageId: string, type: ComponentType) => void
200
- addComponentAt: (
201
- pageId: string,
202
- type: ComponentType,
203
- index: number
204
- ) => void
205
- addPrimitive: (pageId: string, type: PrimitiveType) => void
206
- addPrimitiveAt: (
207
- pageId: string,
208
- type: PrimitiveType,
209
- index: number
210
- ) => void
211
- addPrimitiveToNode: (
212
- parentNodeId: string,
213
- type: PrimitiveType
214
- ) => void
215
- addPrimitiveToNodeAt: (
216
- parentNodeId: string,
217
- type: PrimitiveType,
218
- index: number
219
- ) => void
220
- addComponentToNode: (
221
- parentNodeId: string,
222
- type: ComponentType
223
- ) => void
224
- addComponentToNodeAt: (
225
- parentNodeId: string,
226
- type: ComponentType,
227
- index: number
228
- ) => void
229
- removeNode: (nodeId: string) => void
230
- moveNode: (
231
- pageId: string,
232
- nodeId: string,
233
- targetParentNodeId: string | null,
234
- targetIndex: number
235
- ) => void
236
- moveComponent: (
237
- pageId: string,
238
- nodeId: string,
239
- targetIndex: number
240
- ) => void
241
- removeComponent: (pageId: string, nodeId: string) => void
242
- setAppShellComponent: (slot: AppShellSlot, type: ComponentType) => void
243
- removeAppShellComponent: (slot: AppShellSlot) => void
244
- updateComponentProps: (
245
- pageId: string,
246
- nodeId: string,
247
- props: Record<string, unknown>
248
- ) => void
249
- updateNodeProps: (nodeId: string, props: Record<string, unknown>) => void
250
- updatePageRoot: (
251
- pageId: string,
252
- updates: {
253
- type?: PrimitiveType
254
- props?: Record<string, unknown>
255
- }
256
- ) => void
257
- updateAppShellProps: (
258
- slot: AppShellSlot,
259
- props: Record<string, unknown>
260
- ) => void
261
- }
1
+ import type { ReactNode } from 'react'
2
+
3
+ export type ComponentType =
4
+ | 'Navbar'
5
+ | 'Hero'
6
+ | 'Card'
7
+ | 'Button'
8
+ | 'Footer'
9
+ | 'Grid'
10
+ | 'Testimonials'
11
+ | 'Pricing'
12
+ | 'Form'
13
+ | 'Image'
14
+ | 'Features'
15
+ | 'Gallery'
16
+ | 'FAQ'
17
+ | 'ContactForm'
18
+ | 'LoginForm'
19
+
20
+ export type PrimitiveType =
21
+ | 'header'
22
+ | 'main'
23
+ | 'div'
24
+ | 'section'
25
+ | 'nav'
26
+ | 'article'
27
+ | 'aside'
28
+ | 'footer'
29
+ | 'address'
30
+ | 'hgroup'
31
+ | 'h1'
32
+ | 'h2'
33
+ | 'h3'
34
+ | 'h4'
35
+ | 'h5'
36
+ | 'h6'
37
+ | 'p'
38
+ | 'br'
39
+ | 'hr'
40
+ | 'pre'
41
+ | 'blockquote'
42
+ | 'span'
43
+ | 'strong'
44
+ | 'b'
45
+ | 'em'
46
+ | 'i'
47
+ | 'u'
48
+ | 's'
49
+ | 'small'
50
+ | 'mark'
51
+ | 'sub'
52
+ | 'sup'
53
+ | 'code'
54
+ | 'kbd'
55
+ | 'samp'
56
+ | 'var'
57
+ | 'abbr'
58
+ | 'cite'
59
+ | 'q'
60
+ | 'time'
61
+ | 'a'
62
+ | 'ul'
63
+ | 'ol'
64
+ | 'li'
65
+ | 'dl'
66
+ | 'dt'
67
+ | 'dd'
68
+ | 'menu'
69
+ | 'button'
70
+ | 'img'
71
+ | 'picture'
72
+ | 'source'
73
+ | 'figure'
74
+ | 'figcaption'
75
+ | 'video'
76
+ | 'audio'
77
+ | 'iframe'
78
+ | 'canvas'
79
+ | 'svg'
80
+ | 'form'
81
+ | 'label'
82
+ | 'input'
83
+ | 'textarea'
84
+ | 'select'
85
+ | 'option'
86
+ | 'optgroup'
87
+ | 'fieldset'
88
+ | 'legend'
89
+ | 'datalist'
90
+ | 'output'
91
+ | 'progress'
92
+ | 'meter'
93
+ | 'table'
94
+ | 'caption'
95
+ | 'thead'
96
+ | 'tbody'
97
+ | 'tfoot'
98
+ | 'tr'
99
+ | 'th'
100
+ | 'td'
101
+ | 'colgroup'
102
+ | 'col'
103
+ | 'details'
104
+ | 'summary'
105
+ | 'dialog'
106
+
107
+ export type AppShellSlot = 'header' | 'footer'
108
+
109
+ export type Viewport = 'mobile' | 'tablet' | 'desktop'
110
+ export type EditorThemeChoice =
111
+ | 'default'
112
+ | 'codex-dark'
113
+ | 'vs-light'
114
+ | 'vs-dark'
115
+ | 'system'
116
+ export type EditorTheme = EditorThemeChoice | 'dark' | 'light'
117
+ export type ResolvedEditorTheme = Exclude<EditorThemeChoice, 'system'>
118
+
119
+ export interface VisualBuildRuntimeConfig {
120
+ paths: {
121
+ sourceDir: string
122
+ pagesDir: string
123
+ layoutsDir: string
124
+ appFile: string
125
+ schemaFile: string
126
+ componentRegistryFile: string
127
+ generatedManifestFile: string
128
+ }
129
+ editor: {
130
+ theme: EditorTheme
131
+ defaultViewport: Viewport
132
+ responsiveWidths: {
133
+ mobile: number
134
+ tablet: number
135
+ desktop: number | 'fluid'
136
+ }
137
+ panels: {
138
+ leftOpen: boolean
139
+ leftTab: 'files' | 'elements'
140
+ rightOpen: boolean
141
+ rightTab: 'code' | 'properties'
142
+ codeView: 'page' | 'app' | 'layout' | 'schema' | 'file'
143
+ }
144
+ }
145
+ generator: {
146
+ emitApp: boolean
147
+ emitLayout: boolean
148
+ cleanStaleFiles: boolean
149
+ }
150
+ deployment: {
151
+ provider: 'vercel'
152
+ outputDirectory: string
153
+ projectName?: string
154
+ teamId?: string
155
+ }
156
+ }
157
+
158
+ export type ProjectRefreshResult =
159
+ | 'updated'
160
+ | 'skipped-dirty'
161
+ | 'failed'
162
+
163
+ export type PropType =
164
+ | 'string'
165
+ | 'text'
166
+ | 'number'
167
+ | 'boolean'
168
+ | 'color'
169
+ | 'select'
170
+ | 'array'
171
+ | 'attributes'
172
+
173
+ export interface PropSchema {
174
+ type: PropType
175
+ label: string
176
+ default: unknown
177
+ options?: string[]
178
+ }
179
+
180
+ export interface CustomComponentDefinition {
181
+ name: string
182
+ label: string
183
+ category: string
184
+ icon: string
185
+ description: string
186
+ importPath: string
187
+ exportName: 'default' | string
188
+ defaultProps: Record<string, unknown>
189
+ propSchema: Record<string, PropSchema>
190
+ acceptsChildren: boolean
191
+ preview?: {
192
+ rootTag: string
193
+ rootProps: Record<string, unknown>
194
+ children: VisualNode[]
195
+ rendersChildren: boolean
196
+ }
197
+ }
198
+
199
+ export interface ComponentDefinition {
200
+ type: ComponentType
201
+ label: string
202
+ category: string
203
+ icon: string
204
+ description: string
205
+ defaultProps: Record<string, unknown>
206
+ propSchema: Record<string, PropSchema>
207
+ renderer: (
208
+ props: Record<string, unknown>,
209
+ context: ComponentRenderContext
210
+ ) => ReactNode
211
+ generator: (props: Record<string, unknown>) => string
212
+ source: () => string
213
+ }
214
+
215
+ export interface PrimitiveDefinition {
216
+ type: PrimitiveType
217
+ label: string
218
+ category: string
219
+ icon: string
220
+ description: string
221
+ defaultProps: Record<string, unknown>
222
+ propSchema: Record<string, PropSchema>
223
+ acceptsChildren: boolean
224
+ }
225
+
226
+ export interface ComponentRenderContext {
227
+ pages: Page[]
228
+ activePageId: string
229
+ setActivePage: (id: string) => void
230
+ }
231
+
232
+ export interface VisualNode {
233
+ id: string
234
+ type: PrimitiveType | string
235
+ label?: string
236
+ props: Record<string, unknown>
237
+ children: VisualNode[]
238
+ }
239
+
240
+ export interface Page {
241
+ id: string
242
+ name: string
243
+ route: string
244
+ sourcePath: string
245
+ root: VisualNode
246
+ components: VisualNode[]
247
+ }
248
+
249
+ export interface AppShell {
250
+ header: VisualNode | null
251
+ footer: VisualNode | null
252
+ }
253
+
254
+ export interface AppState {
255
+ projectName: string
256
+ appShell: AppShell
257
+ pages: Page[]
258
+ activePageId: string
259
+ selectedComponentId: string | null
260
+ viewport: Viewport
261
+ previewMode: boolean
262
+ isProjectLoaded: boolean
263
+ customComponents: CustomComponentDefinition[]
264
+ projectConfig: VisualBuildRuntimeConfig
265
+ // Actions
266
+ loadProject: () => Promise<void>
267
+ refreshCustomComponents: () => Promise<void>
268
+ refreshProjectFromDisk: (
269
+ force?: boolean
270
+ ) => Promise<ProjectRefreshResult>
271
+ saveProject: () => Promise<void>
272
+ addPage: (name: string, route: string) => Page
273
+ registerPage: (page: Page) => void
274
+ deletePage: (id: string) => void
275
+ setActivePage: (id: string) => void
276
+ setSelectedComponent: (id: string | null) => void
277
+ setViewport: (viewport: Viewport) => void
278
+ togglePreviewMode: () => void
279
+ addComponent: (pageId: string, type: ComponentType) => void
280
+ addComponentAt: (
281
+ pageId: string,
282
+ type: ComponentType,
283
+ index: number
284
+ ) => void
285
+ addPrimitive: (pageId: string, type: PrimitiveType) => void
286
+ addPrimitiveAt: (
287
+ pageId: string,
288
+ type: PrimitiveType,
289
+ index: number
290
+ ) => void
291
+ addPrimitiveToNode: (
292
+ parentNodeId: string,
293
+ type: PrimitiveType
294
+ ) => void
295
+ addPrimitiveToNodeAt: (
296
+ parentNodeId: string,
297
+ type: PrimitiveType,
298
+ index: number
299
+ ) => void
300
+ addComponentToNode: (
301
+ parentNodeId: string,
302
+ type: ComponentType
303
+ ) => void
304
+ addComponentToNodeAt: (
305
+ parentNodeId: string,
306
+ type: ComponentType,
307
+ index: number
308
+ ) => void
309
+ addCustomComponent: (pageId: string, name: string) => void
310
+ addCustomComponentAt: (pageId: string, name: string, index: number) => void
311
+ addCustomComponentToNode: (parentNodeId: string, name: string) => void
312
+ addCustomComponentToNodeAt: (
313
+ parentNodeId: string,
314
+ name: string,
315
+ index: number
316
+ ) => void
317
+ registerCustomComponent: (definition: CustomComponentDefinition) => void
318
+ removeNode: (nodeId: string) => void
319
+ moveNode: (
320
+ pageId: string,
321
+ nodeId: string,
322
+ targetParentNodeId: string | null,
323
+ targetIndex: number
324
+ ) => void
325
+ moveComponent: (
326
+ pageId: string,
327
+ nodeId: string,
328
+ targetIndex: number
329
+ ) => void
330
+ removeComponent: (pageId: string, nodeId: string) => void
331
+ setAppShellComponent: (slot: AppShellSlot, type: ComponentType) => void
332
+ removeAppShellComponent: (slot: AppShellSlot) => void
333
+ updateComponentProps: (
334
+ pageId: string,
335
+ nodeId: string,
336
+ props: Record<string, unknown>
337
+ ) => void
338
+ updateNodeProps: (nodeId: string, props: Record<string, unknown>) => void
339
+ updatePageRoot: (
340
+ pageId: string,
341
+ updates: {
342
+ type?: PrimitiveType
343
+ props?: Record<string, unknown>
344
+ }
345
+ ) => void
346
+ updateAppShellProps: (
347
+ slot: AppShellSlot,
348
+ props: Record<string, unknown>
349
+ ) => void
350
+ }