Skip to content

Commit 5d2cffd

Browse files
Copilothotlong
andcommitted
Add playground page with JSON editor and live preview
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 672a590 commit 5d2cffd

4 files changed

Lines changed: 390 additions & 0 deletions

File tree

apps/site/app/(home)/page.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export default function HomePage() {
2929
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
3030
</svg>
3131
</Link>
32+
<Link
33+
href="/playground"
34+
className="inline-flex items-center justify-center rounded-lg border-2 border-fd-primary bg-fd-primary/10 px-8 py-3.5 text-base font-semibold text-fd-primary transition-all hover:bg-fd-primary/20"
35+
>
36+
<svg className="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
38+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
39+
</svg>
40+
Try Playground
41+
</Link>
3242
<a
3343
href="https://github.com/objectstack-ai/objectui"
3444
target="_blank"

apps/site/app/playground/page.tsx

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
'use client';
2+
3+
import React, { useState, useEffect } from 'react';
4+
import { SchemaRenderer } from '@object-ui/react';
5+
import type { SchemaNode } from '@object-ui/core';
6+
import dynamic from 'next/dynamic';
7+
8+
// Dynamically import Monaco Editor to avoid SSR issues
9+
const Editor = dynamic(() => import('@monaco-editor/react'), { ssr: false });
10+
11+
// Load plugins
12+
const pluginsLoading = typeof window !== 'undefined'
13+
? Promise.all([
14+
import('@object-ui/plugin-editor'),
15+
import('@object-ui/plugin-charts'),
16+
import('@object-ui/plugin-kanban'),
17+
import('@object-ui/plugin-markdown'),
18+
import('@object-ui/plugin-object'),
19+
])
20+
: Promise.resolve([]);
21+
22+
// Default example schema
23+
const DEFAULT_SCHEMA = {
24+
type: "container",
25+
className: "space-y-4",
26+
children: [
27+
{
28+
type: "heading",
29+
level: 2,
30+
children: "Welcome to ObjectUI Playground"
31+
},
32+
{
33+
type: "text",
34+
children: "Edit the JSON schema on the left to see live updates here."
35+
},
36+
{
37+
type: "card",
38+
className: "p-6",
39+
children: {
40+
type: "container",
41+
className: "space-y-4",
42+
children: [
43+
{
44+
type: "heading",
45+
level: 3,
46+
children: "Quick Example"
47+
},
48+
{
49+
type: "text",
50+
children: "Try changing the text or adding new components!"
51+
},
52+
{
53+
type: "button",
54+
variant: "default",
55+
children: "Click me"
56+
}
57+
]
58+
}
59+
}
60+
]
61+
};
62+
63+
// Example schemas for users to try
64+
const EXAMPLE_SCHEMAS = {
65+
basic: DEFAULT_SCHEMA,
66+
form: {
67+
type: "container",
68+
className: "space-y-4 max-w-md",
69+
children: [
70+
{
71+
type: "heading",
72+
level: 2,
73+
children: "Contact Form"
74+
},
75+
{
76+
type: "input",
77+
name: "name",
78+
placeholder: "Your name"
79+
},
80+
{
81+
type: "input",
82+
name: "email",
83+
type: "email",
84+
placeholder: "your@email.com"
85+
},
86+
{
87+
type: "textarea",
88+
name: "message",
89+
placeholder: "Your message",
90+
rows: 4
91+
},
92+
{
93+
type: "button",
94+
variant: "default",
95+
children: "Submit"
96+
}
97+
]
98+
},
99+
dashboard: {
100+
type: "container",
101+
className: "space-y-6",
102+
children: [
103+
{
104+
type: "heading",
105+
level: 1,
106+
children: "Dashboard"
107+
},
108+
{
109+
type: "grid",
110+
columns: 3,
111+
gap: 4,
112+
children: [
113+
{
114+
type: "card",
115+
className: "p-6",
116+
children: {
117+
type: "container",
118+
className: "space-y-2",
119+
children: [
120+
{
121+
type: "text",
122+
className: "text-sm text-muted-foreground",
123+
children: "Total Users"
124+
},
125+
{
126+
type: "heading",
127+
level: 2,
128+
children: "1,234"
129+
}
130+
]
131+
}
132+
},
133+
{
134+
type: "card",
135+
className: "p-6",
136+
children: {
137+
type: "container",
138+
className: "space-y-2",
139+
children: [
140+
{
141+
type: "text",
142+
className: "text-sm text-muted-foreground",
143+
children: "Revenue"
144+
},
145+
{
146+
type: "heading",
147+
level: 2,
148+
children: "$12,345"
149+
}
150+
]
151+
}
152+
},
153+
{
154+
type: "card",
155+
className: "p-6",
156+
children: {
157+
type: "container",
158+
className: "space-y-2",
159+
children: [
160+
{
161+
type: "text",
162+
className: "text-sm text-muted-foreground",
163+
children: "Active Projects"
164+
},
165+
{
166+
type: "heading",
167+
level: 2,
168+
children: "42"
169+
}
170+
]
171+
}
172+
}
173+
]
174+
}
175+
]
176+
},
177+
list: {
178+
type: "container",
179+
className: "space-y-4 max-w-2xl",
180+
children: [
181+
{
182+
type: "heading",
183+
level: 2,
184+
children: "Task List"
185+
},
186+
{
187+
type: "card",
188+
className: "divide-y",
189+
children: [
190+
{
191+
type: "container",
192+
className: "p-4 flex items-center gap-3",
193+
children: [
194+
{
195+
type: "checkbox",
196+
name: "task1"
197+
},
198+
{
199+
type: "text",
200+
children: "Complete documentation"
201+
}
202+
]
203+
},
204+
{
205+
type: "container",
206+
className: "p-4 flex items-center gap-3",
207+
children: [
208+
{
209+
type: "checkbox",
210+
name: "task2"
211+
},
212+
{
213+
type: "text",
214+
children: "Review pull requests"
215+
}
216+
]
217+
},
218+
{
219+
type: "container",
220+
className: "p-4 flex items-center gap-3",
221+
children: [
222+
{
223+
type: "checkbox",
224+
name: "task3"
225+
},
226+
{
227+
type: "text",
228+
children: "Deploy to production"
229+
}
230+
]
231+
}
232+
]
233+
}
234+
]
235+
}
236+
};
237+
238+
export default function PlaygroundPage() {
239+
const [schema, setSchema] = useState<SchemaNode>(DEFAULT_SCHEMA);
240+
const [editorValue, setEditorValue] = useState(JSON.stringify(DEFAULT_SCHEMA, null, 2));
241+
const [error, setError] = useState<string | null>(null);
242+
const [pluginsLoaded, setPluginsLoaded] = useState(false);
243+
const [selectedExample, setSelectedExample] = useState<keyof typeof EXAMPLE_SCHEMAS>('basic');
244+
245+
useEffect(() => {
246+
// Wait for plugins to load before rendering
247+
pluginsLoading.then(() => {
248+
setPluginsLoaded(true);
249+
});
250+
}, []);
251+
252+
const handleEditorChange = (value: string | undefined) => {
253+
if (!value) return;
254+
255+
setEditorValue(value);
256+
257+
try {
258+
const parsed = JSON.parse(value);
259+
setSchema(parsed);
260+
setError(null);
261+
} catch (e) {
262+
setError(e instanceof Error ? e.message : 'Invalid JSON');
263+
}
264+
};
265+
266+
const loadExample = (exampleKey: keyof typeof EXAMPLE_SCHEMAS) => {
267+
const exampleSchema = EXAMPLE_SCHEMAS[exampleKey];
268+
setSelectedExample(exampleKey);
269+
setSchema(exampleSchema);
270+
setEditorValue(JSON.stringify(exampleSchema, null, 2));
271+
setError(null);
272+
};
273+
274+
return (
275+
<div className="flex flex-col h-screen bg-fd-background">
276+
{/* Header */}
277+
<div className="border-b border-fd-border bg-fd-card">
278+
<div className="container mx-auto px-4 py-4">
279+
<div className="flex items-center justify-between">
280+
<div>
281+
<h1 className="text-2xl font-bold text-fd-foreground">ObjectUI Playground</h1>
282+
<p className="text-sm text-fd-muted-foreground mt-1">
283+
Edit JSON schema and see live preview
284+
</p>
285+
</div>
286+
<div className="flex items-center gap-2">
287+
<span className="text-sm text-fd-muted-foreground">Examples:</span>
288+
{(Object.keys(EXAMPLE_SCHEMAS) as Array<keyof typeof EXAMPLE_SCHEMAS>).map((key) => (
289+
<button
290+
key={key}
291+
onClick={() => loadExample(key)}
292+
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
293+
selectedExample === key
294+
? 'bg-fd-primary text-fd-primary-foreground'
295+
: 'bg-fd-muted text-fd-muted-foreground hover:bg-fd-accent'
296+
}`}
297+
>
298+
{key.charAt(0).toUpperCase() + key.slice(1)}
299+
</button>
300+
))}
301+
</div>
302+
</div>
303+
</div>
304+
</div>
305+
306+
{/* Main Content */}
307+
<div className="flex-1 flex overflow-hidden">
308+
{/* Editor Panel */}
309+
<div className="w-1/2 border-r border-fd-border flex flex-col">
310+
<div className="bg-fd-muted px-4 py-2 border-b border-fd-border">
311+
<h2 className="text-sm font-semibold text-fd-foreground">JSON Schema</h2>
312+
{error && (
313+
<div className="mt-2 text-xs text-red-500 bg-red-50 dark:bg-red-950 p-2 rounded">
314+
{error}
315+
</div>
316+
)}
317+
</div>
318+
<div className="flex-1 overflow-hidden">
319+
<Editor
320+
height="100%"
321+
defaultLanguage="json"
322+
value={editorValue}
323+
onChange={handleEditorChange}
324+
theme="vs-dark"
325+
options={{
326+
minimap: { enabled: false },
327+
fontSize: 14,
328+
lineNumbers: 'on',
329+
scrollBeyondLastLine: false,
330+
automaticLayout: true,
331+
tabSize: 2,
332+
}}
333+
/>
334+
</div>
335+
</div>
336+
337+
{/* Preview Panel */}
338+
<div className="w-1/2 flex flex-col bg-fd-background">
339+
<div className="bg-fd-muted px-4 py-2 border-b border-fd-border">
340+
<h2 className="text-sm font-semibold text-fd-foreground">Live Preview</h2>
341+
</div>
342+
<div className="flex-1 overflow-auto p-6">
343+
{!pluginsLoaded ? (
344+
<div className="flex items-center justify-center h-full text-fd-muted-foreground">
345+
Loading plugins...
346+
</div>
347+
) : error ? (
348+
<div className="flex items-center justify-center h-full">
349+
<div className="text-center space-y-2">
350+
<div className="text-4xl">⚠️</div>
351+
<div className="text-fd-muted-foreground">
352+
Fix the JSON syntax to see preview
353+
</div>
354+
</div>
355+
</div>
356+
) : (
357+
<SchemaRenderer schema={schema} />
358+
)}
359+
</div>
360+
</div>
361+
</div>
362+
</div>
363+
);
364+
}

0 commit comments

Comments
 (0)