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.
- package/README.md +306 -123
- package/docs/user-guide.md +759 -0
- package/package.json +92 -85
- package/scripts/create-builder-app.mjs +513 -501
- package/scripts/vb-dev.mjs +41 -32
- package/scripts/vb-generate.mjs +332 -224
- package/templates/default/app/.vercelignore +6 -0
- package/templates/default/app/README.md +56 -21
- package/templates/default/app/visualbuild/components.json +3 -0
- package/templates/default/app/visualbuild/config.d.ts +48 -0
- package/templates/default/app/visualbuild.config.ts +38 -0
- package/templates/default/builder/README.md +37 -21
- package/visual-app-builder/server/parseReactPage.ts +776 -571
- package/visual-app-builder/shared/createReactComponentSource.d.mts +2 -0
- package/visual-app-builder/shared/createReactComponentSource.mjs +45 -0
- package/visual-app-builder/shared/generateReactSource.d.mts +57 -37
- package/visual-app-builder/shared/generateReactSource.mjs +608 -443
- package/visual-app-builder/shared/npmBuildCommand.d.mts +17 -0
- package/visual-app-builder/shared/npmBuildCommand.mjs +26 -0
- package/visual-app-builder/shared/syncCustomComponentSource.d.mts +13 -0
- package/visual-app-builder/shared/syncCustomComponentSource.mjs +345 -0
- package/visual-app-builder/shared/vercelDeployment.d.mts +31 -0
- package/visual-app-builder/shared/vercelDeployment.mjs +266 -0
- package/visual-app-builder/shared/visualbuildConfig.d.mts +144 -0
- package/visual-app-builder/shared/visualbuildConfig.mjs +578 -0
- package/visual-app-builder/src/App.tsx +1090 -874
- package/visual-app-builder/src/components/Canvas.tsx +1116 -1059
- package/visual-app-builder/src/components/CodePanel.tsx +1147 -812
- package/visual-app-builder/src/components/ComponentSidebar.tsx +365 -302
- package/visual-app-builder/src/components/DeploymentModal.tsx +295 -0
- package/visual-app-builder/src/components/PageSwitcher.tsx +133 -133
- package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -1054
- package/visual-app-builder/src/components/PropertiesPanel.tsx +792 -692
- package/visual-app-builder/src/components/Topbar.tsx +257 -128
- package/visual-app-builder/src/data/componentRegistry.tsx +613 -292
- package/visual-app-builder/src/data/tailwindClassCatalog.ts +95 -0
- package/visual-app-builder/src/index.css +383 -111
- package/visual-app-builder/src/stores/useAppStore.ts +2385 -1265
- package/visual-app-builder/src/theme.ts +71 -0
- package/visual-app-builder/src/types/index.ts +350 -261
- package/visual-app-builder/src/utils/codegen.ts +90 -66
- package/visual-app-builder/src/utils/deployment.ts +54 -0
- package/visual-app-builder/src/utils/projectPersistence.ts +218 -177
- package/visual-app-builder/vite.config.ts +1946 -1479
|
@@ -1,128 +1,257 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<rect
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
}
|