Skip to content

Commit 36eab71

Browse files
authored
Merge pull request #168 from objectstack-ai/copilot/add-playground-for-json-schema
2 parents 9c80f06 + 33e9e4a commit 36eab71

5 files changed

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

apps/site/lib/layout.shared.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,20 @@ export function baseOptions(): BaseLayoutProps {
1616
</div>
1717
),
1818
},
19+
links: [
20+
{
21+
text: 'Documentation',
22+
url: '/docs',
23+
},
24+
{
25+
text: 'Playground',
26+
url: '/playground',
27+
},
28+
{
29+
text: 'GitHub',
30+
url: 'https://github.com/objectstack-ai/objectui',
31+
external: true,
32+
},
33+
],
1934
};
2035
}

apps/site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"postinstall": "fumadocs-mdx"
1111
},
1212
"dependencies": {
13+
"@monaco-editor/react": "^4.6.0",
1314
"@object-ui/components": "workspace:*",
1415
"@object-ui/core": "workspace:*",
1516
"@object-ui/plugin-charts": "workspace:*",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)