create-visualbuild-app 0.1.0 → 1.0.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 +306 -123
  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
@@ -1,128 +1,257 @@
1
- export type SaveState = 'idle' | 'saving' | 'saved' | 'error'
2
-
3
- export default function Topbar({
4
- showCode,
5
- previewMode,
6
- saveState,
7
- onToggleCode,
8
- onTogglePreview,
9
- onSave,
10
- }: {
11
- showCode: boolean
12
- previewMode: boolean
13
- saveState: SaveState
14
- onToggleCode: () => void
15
- onTogglePreview: () => void
16
- onSave: () => void
17
- }) {
18
- return (
19
- <header className="flex items-center justify-between px-4 h-12 bg-topbar border-b border-white/[0.06] shrink-0 z-10">
20
- <div className="flex items-center gap-2">
21
- <div className="w-6 h-6 rounded bg-accent flex items-center justify-center">
22
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
23
- <rect x="1" y="1" width="5" height="5" rx="1" fill="white" />
24
- <rect
25
- x="8"
26
- y="1"
27
- width="5"
28
- height="5"
29
- rx="1"
30
- fill="white"
31
- opacity="0.6"
32
- />
33
- <rect
34
- x="1"
35
- y="8"
36
- width="5"
37
- height="5"
38
- rx="1"
39
- fill="white"
40
- opacity="0.6"
41
- />
42
- <rect
43
- x="8"
44
- y="8"
45
- width="5"
46
- height="5"
47
- rx="1"
48
- fill="white"
49
- opacity="0.3"
50
- />
51
- </svg>
52
- </div>
53
- <span className="text-sm font-semibold tracking-tight text-white">
54
- VisualBuild
55
- </span>
56
- </div>
57
-
58
- <div className="flex items-center gap-1">
59
- <button
60
- type="button"
61
- onClick={onTogglePreview}
62
- className={`px-3 py-1.5 text-xs rounded transition-colors cursor-pointer select-none ${
63
- previewMode
64
- ? 'text-white bg-white/[0.1]'
65
- : 'text-gray-400 hover:text-white hover:bg-white/[0.06]'
66
- }`}
67
- >
68
- Preview
69
- </button>
70
- <button
71
- type="button"
72
- onClick={onToggleCode}
73
- className={`px-3 py-1.5 text-xs rounded transition-colors cursor-pointer select-none ${
74
- !previewMode && showCode
75
- ? 'text-white bg-white/[0.1]'
76
- : 'text-gray-400 hover:text-white hover:bg-white/[0.06]'
77
- }`}
78
- >
79
- Code
80
- </button>
81
- <DisabledButton>Undo</DisabledButton>
82
- <DisabledButton>Redo</DisabledButton>
83
- </div>
84
-
85
- <div className="flex items-center gap-2">
86
- <button
87
- type="button"
88
- disabled={saveState === 'saving'}
89
- onClick={onSave}
90
- className={`px-3 py-1.5 text-xs bg-white/[0.06] hover:bg-white/[0.1] disabled:opacity-60 rounded transition-colors cursor-pointer select-none ${
91
- saveState === 'error' ? 'text-red-300' : 'text-gray-300'
92
- }`}
93
- >
94
- {saveState === 'saving'
95
- ? 'Saving...'
96
- : saveState === 'saved'
97
- ? 'Saved'
98
- : saveState === 'error'
99
- ? 'Save failed'
100
- : 'Save'}
101
- </button>
102
- <DisabledButton emphasized>Export ZIP</DisabledButton>
103
- </div>
104
- </header>
105
- )
106
- }
107
-
108
- function DisabledButton({
109
- children,
110
- emphasized = false,
111
- }: {
112
- children: React.ReactNode
113
- emphasized?: boolean
114
- }) {
115
- return (
116
- <button
117
- type="button"
118
- disabled
119
- className={`px-3 py-1.5 text-xs rounded cursor-pointer select-none ${
120
- emphasized
121
- ? 'text-white/45 bg-accent/35'
122
- : 'text-gray-700 bg-transparent'
123
- }`}
124
- >
125
- {children}
126
- </button>
127
- )
128
- }
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { editorThemeOptions } from '../theme'
3
+ import type { EditorThemeChoice } from '../types'
4
+
5
+ export type SaveState = 'idle' | 'saving' | 'saved' | 'error'
6
+
7
+ export default function Topbar({
8
+ showCode,
9
+ previewMode,
10
+ saveState,
11
+ editorTheme,
12
+ onToggleCode,
13
+ onTogglePreview,
14
+ onSave,
15
+ onDeploy,
16
+ onThemeChange,
17
+ }: {
18
+ showCode: boolean
19
+ previewMode: boolean
20
+ saveState: SaveState
21
+ editorTheme: EditorThemeChoice
22
+ onToggleCode: () => void
23
+ onTogglePreview: () => void
24
+ onSave: () => void
25
+ onDeploy: () => void
26
+ onThemeChange: (theme: EditorThemeChoice) => void
27
+ }) {
28
+ return (
29
+ <header data-vb-editor-chrome className="flex items-center justify-between px-4 h-12 bg-topbar border-b border-white/[0.06] shrink-0 z-30">
30
+ <div className="flex items-center gap-2">
31
+ <div className="w-6 h-6 rounded bg-accent flex items-center justify-center">
32
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
33
+ <rect x="1" y="1" width="5" height="5" rx="1" fill="white" />
34
+ <rect
35
+ x="8"
36
+ y="1"
37
+ width="5"
38
+ height="5"
39
+ rx="1"
40
+ fill="white"
41
+ opacity="0.6"
42
+ />
43
+ <rect
44
+ x="1"
45
+ y="8"
46
+ width="5"
47
+ height="5"
48
+ rx="1"
49
+ fill="white"
50
+ opacity="0.6"
51
+ />
52
+ <rect
53
+ x="8"
54
+ y="8"
55
+ width="5"
56
+ height="5"
57
+ rx="1"
58
+ fill="white"
59
+ opacity="0.3"
60
+ />
61
+ </svg>
62
+ </div>
63
+ <span className="text-sm font-semibold tracking-tight text-white">
64
+ VisualBuild
65
+ </span>
66
+ </div>
67
+
68
+ <div className="flex items-center gap-1">
69
+ <button
70
+ type="button"
71
+ onClick={onTogglePreview}
72
+ className={`px-3 py-1.5 text-xs rounded transition-colors cursor-pointer select-none ${
73
+ previewMode
74
+ ? 'text-white bg-white/[0.1]'
75
+ : 'text-gray-400 hover:text-white hover:bg-white/[0.06]'
76
+ }`}
77
+ >
78
+ Preview
79
+ </button>
80
+ <button
81
+ type="button"
82
+ onClick={onToggleCode}
83
+ className={`px-3 py-1.5 text-xs rounded transition-colors cursor-pointer select-none ${
84
+ !previewMode && showCode
85
+ ? 'text-white bg-white/[0.1]'
86
+ : 'text-gray-400 hover:text-white hover:bg-white/[0.06]'
87
+ }`}
88
+ >
89
+ Code
90
+ </button>
91
+ </div>
92
+
93
+ <div className="flex items-center gap-2">
94
+ <ThemePicker value={editorTheme} onChange={onThemeChange} />
95
+ <button
96
+ type="button"
97
+ disabled={saveState === 'saving'}
98
+ onClick={onSave}
99
+ className={`px-3 py-1.5 text-xs bg-white/[0.06] hover:bg-white/[0.1] disabled:opacity-60 rounded transition-colors cursor-pointer select-none ${
100
+ saveState === 'error' ? 'text-red-300' : 'text-gray-300'
101
+ }`}
102
+ >
103
+ {saveState === 'saving'
104
+ ? 'Saving...'
105
+ : saveState === 'saved'
106
+ ? 'Saved'
107
+ : saveState === 'error'
108
+ ? 'Save failed'
109
+ : 'Save'}
110
+ </button>
111
+ <button
112
+ type="button"
113
+ onClick={onDeploy}
114
+ className="flex cursor-pointer items-center gap-1.5 rounded bg-accent px-3 py-1.5 text-xs font-medium text-white transition-[filter] hover:brightness-110"
115
+ >
116
+ <span className="hidden sm:inline">Deploy</span>
117
+ <VercelMark />
118
+ </button>
119
+ </div>
120
+ </header>
121
+ )
122
+ }
123
+
124
+ function VercelMark() {
125
+ return (
126
+ <svg width="10" height="9" viewBox="0 0 10 9" aria-hidden="true">
127
+ <path d="M5 0 10 9H0L5 0Z" fill="currentColor" />
128
+ </svg>
129
+ )
130
+ }
131
+
132
+ function ThemePicker({
133
+ value,
134
+ onChange,
135
+ }: {
136
+ value: EditorThemeChoice
137
+ onChange: (theme: EditorThemeChoice) => void
138
+ }) {
139
+ const [open, setOpen] = useState(false)
140
+ const containerRef = useRef<HTMLDivElement>(null)
141
+ const activeTheme =
142
+ editorThemeOptions.find((theme) => theme.id === value) ??
143
+ editorThemeOptions[0]
144
+
145
+ useEffect(() => {
146
+ if (!open) return
147
+
148
+ const handlePointerDown = (event: PointerEvent) => {
149
+ if (!containerRef.current?.contains(event.target as Node)) {
150
+ setOpen(false)
151
+ }
152
+ }
153
+ const handleKeyDown = (event: KeyboardEvent) => {
154
+ if (event.key === 'Escape') setOpen(false)
155
+ }
156
+
157
+ window.addEventListener('pointerdown', handlePointerDown)
158
+ window.addEventListener('keydown', handleKeyDown)
159
+ return () => {
160
+ window.removeEventListener('pointerdown', handlePointerDown)
161
+ window.removeEventListener('keydown', handleKeyDown)
162
+ }
163
+ }, [open])
164
+
165
+ return (
166
+ <div ref={containerRef} className="relative">
167
+ <button
168
+ type="button"
169
+ onClick={() => setOpen((current) => !current)}
170
+ aria-haspopup="listbox"
171
+ aria-expanded={open}
172
+ title={`Editor theme: ${activeTheme.label}`}
173
+ className="flex h-7 cursor-pointer items-center gap-2 rounded border border-white/[0.08] bg-white/[0.05] px-2 text-xs text-gray-300 transition-colors hover:bg-white/[0.1] hover:text-white"
174
+ >
175
+ <PaletteIcon />
176
+ <span className="hidden max-w-28 truncate lg:block">
177
+ {activeTheme.label}
178
+ </span>
179
+ <span className="text-[9px] text-gray-500">v</span>
180
+ </button>
181
+
182
+ {open && (
183
+ <div
184
+ role="listbox"
185
+ aria-label="Editor theme"
186
+ className="absolute right-0 top-full z-50 mt-2 w-72 overflow-hidden rounded-lg border border-white/10 bg-[#181b21] p-1.5 shadow-2xl"
187
+ >
188
+ <p className="px-2 pb-1.5 pt-1 text-[9px] font-semibold uppercase tracking-[0.16em] text-gray-500">
189
+ Editor appearance
190
+ </p>
191
+ {editorThemeOptions.map((theme) => {
192
+ const selected = theme.id === value
193
+ return (
194
+ <button
195
+ key={theme.id}
196
+ type="button"
197
+ role="option"
198
+ aria-selected={selected}
199
+ onClick={() => {
200
+ onChange(theme.id)
201
+ setOpen(false)
202
+ }}
203
+ className={`flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-2 text-left transition-colors ${
204
+ selected
205
+ ? 'bg-accent/20 text-white'
206
+ : 'text-gray-300 hover:bg-white/[0.07] hover:text-white'
207
+ }`}
208
+ >
209
+ <span className="flex overflow-hidden rounded border border-white/10">
210
+ {theme.swatches.map((color) => (
211
+ <span
212
+ key={color}
213
+ className="h-7 w-3"
214
+ style={{ backgroundColor: color }}
215
+ />
216
+ ))}
217
+ </span>
218
+ <span className="min-w-0 flex-1">
219
+ <span className="block text-xs font-medium">
220
+ {theme.label}
221
+ </span>
222
+ <span className="mt-0.5 block truncate text-[10px] text-gray-500">
223
+ {theme.description}
224
+ </span>
225
+ </span>
226
+ {selected && (
227
+ <span className="text-xs font-semibold text-accent">✓</span>
228
+ )}
229
+ </button>
230
+ )
231
+ })}
232
+ </div>
233
+ )}
234
+ </div>
235
+ )
236
+ }
237
+
238
+ function PaletteIcon() {
239
+ return (
240
+ <svg
241
+ width="13"
242
+ height="13"
243
+ viewBox="0 0 16 16"
244
+ fill="none"
245
+ aria-hidden="true"
246
+ >
247
+ <path
248
+ d="M8 2a6 6 0 1 0 0 12h1.1c.8 0 1.2-.9.7-1.5-.5-.7 0-1.6.8-1.6H12A2 2 0 0 0 14 9a7 7 0 0 0-6-7Z"
249
+ stroke="currentColor"
250
+ strokeWidth="1.3"
251
+ />
252
+ <circle cx="5" cy="6" r=".8" fill="currentColor" />
253
+ <circle cx="8" cy="4.7" r=".8" fill="currentColor" />
254
+ <circle cx="11" cy="6.2" r=".8" fill="currentColor" />
255
+ </svg>
256
+ )
257
+ }