Skip to content

Commit 8182434

Browse files
committed
Refine Studio UI and Canvas visual styles
Improves the Studio page layout, toolbar, and preview modes with enhanced styling, spacing, and feedback for actions like copy/export. Refactors the Canvas component's selection, hover, and drag outlines for better clarity and modernizes the zoom toolbar. These changes provide a more polished, user-friendly, and visually consistent design experience.
1 parent ac2ca08 commit 8182434

2 files changed

Lines changed: 213 additions & 113 deletions

File tree

apps/playground/src/pages/Studio.tsx

Lines changed: 171 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -89,88 +89,116 @@ const StudioToolbarContext = ({
8989
};
9090

9191
return (
92-
<header className="h-14 border-b bg-white flex items-center px-4 justify-between flex-shrink-0 z-20 relative">
92+
<header className="h-16 border-b bg-white flex items-center px-4 justify-between flex-shrink-0 z-50 relative shadow-sm">
9393
<div className="flex items-center gap-4">
94-
<button
95-
onClick={() => navigate('/')}
96-
className="p-2 hover:bg-gray-100 rounded-full text-gray-500 transition-colors"
97-
title="Back to Gallery"
98-
>
99-
<ArrowLeft className="w-5 h-5" />
100-
</button>
101-
102-
<div>
103-
<h1 className="font-semibold text-sm text-gray-900">
104-
{exampleTitle}
105-
</h1>
106-
<div className="text-xs text-gray-500 flex items-center gap-1">
107-
<span className={`w-2 h-2 rounded-full ${jsonError ? 'bg-red-500' : 'bg-green-500'}`}></span>
108-
{jsonError ? 'Invalid JSON' : 'Ready'}
94+
<div className="flex items-center gap-2">
95+
<button
96+
onClick={() => navigate('/')}
97+
className="p-2 -ml-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-full transition-all"
98+
title="Back to Gallery"
99+
>
100+
<ArrowLeft className="w-5 h-5" />
101+
</button>
102+
<div className="h-6 w-px bg-gray-200 mx-1"></div>
103+
<div>
104+
<div className="flex items-center gap-2">
105+
<span className="font-bold text-gray-900 tracking-tight">Object Studio</span>
106+
<span className="text-gray-300">/</span>
107+
<span className="text-gray-600 font-medium">{exampleTitle}</span>
108+
</div>
109+
<div className="flex items-center gap-1.5 mt-0.5">
110+
<span className={`w-1.5 h-1.5 rounded-full ${jsonError ? 'bg-red-500' : 'bg-green-500'} ring-2 ring-opacity-20 ${jsonError ? 'ring-red-500' : 'ring-green-500'}`}></span>
111+
<span className="text-[10px] uppercase tracking-wider font-semibold text-gray-400">
112+
{jsonError ? 'Error' : 'Ready'}
113+
</span>
114+
</div>
109115
</div>
110116
</div>
111117
</div>
112118

113119
{/* Center: View Mode Switcher */}
114-
<div className="flex p-1 bg-gray-100 rounded-lg absolute left-1/2 transform -translate-x-1/2">
115-
<button
116-
onClick={() => setViewMode('design')}
117-
className={`flex items-center gap-2 px-3 py-1.5 text-xs font-medium rounded-md transition-all ${viewMode === 'design' ? 'bg-white shadow text-indigo-600' : 'text-gray-600 hover:text-gray-900'}`}
118-
>
119-
<PenTool className="w-3.5 h-3.5" />
120-
Design
121-
</button>
122-
<button
123-
onClick={() => setViewMode('preview')}
124-
className={`flex items-center gap-2 px-3 py-1.5 text-xs font-medium rounded-md transition-all ${viewMode === 'preview' ? 'bg-white shadow text-indigo-600' : 'text-gray-600 hover:text-gray-900'}`}
125-
>
126-
<Monitor className="w-3.5 h-3.5" />
127-
Preview
128-
</button>
129-
<button
130-
onClick={() => setViewMode('code')}
131-
className={`flex items-center gap-2 px-3 py-1.5 text-xs font-medium rounded-md transition-all ${viewMode === 'code' ? 'bg-white shadow text-indigo-600' : 'text-gray-600 hover:text-gray-900'}`}
132-
>
133-
<Code2 className="w-3.5 h-3.5" />
134-
Code
135-
</button>
120+
<div className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
121+
<div className="flex p-1 bg-gray-100/80 backdrop-blur-sm rounded-lg border border-gray-200/50">
122+
<button
123+
onClick={() => setViewMode('design')}
124+
className={`flex items-center gap-2 px-4 py-1.5 text-xs font-medium rounded-md transition-all duration-200 ${
125+
viewMode === 'design'
126+
? 'bg-white text-gray-900 shadow-sm ring-1 ring-black/5'
127+
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-200/50'
128+
}`}
129+
>
130+
<PenTool className="w-3.5 h-3.5" />
131+
Design
132+
</button>
133+
<button
134+
onClick={() => setViewMode('preview')}
135+
className={`flex items-center gap-2 px-4 py-1.5 text-xs font-medium rounded-md transition-all duration-200 ${
136+
viewMode === 'preview'
137+
? 'bg-white text-gray-900 shadow-sm ring-1 ring-black/5'
138+
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-200/50'
139+
}`}
140+
>
141+
<Monitor className="w-3.5 h-3.5" />
142+
Preview
143+
</button>
144+
<button
145+
onClick={() => setViewMode('code')}
146+
className={`flex items-center gap-2 px-4 py-1.5 text-xs font-medium rounded-md transition-all duration-200 ${
147+
viewMode === 'code'
148+
? 'bg-white text-gray-900 shadow-sm ring-1 ring-black/5'
149+
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-200/50'
150+
}`}
151+
>
152+
<Code2 className="w-3.5 h-3.5" />
153+
Code
154+
</button>
155+
</div>
136156
</div>
137157

138158
{/* Right: Actions */}
139159
<div className="flex items-center gap-2">
140160
{viewMode === 'design' && (
141-
<>
161+
<div className="flex items-center bg-gray-50 rounded-lg border border-gray-200 p-0.5 mr-2">
142162
<button
143163
onClick={undo}
144164
disabled={!canUndo}
145-
className={`p-2 rounded-md transition-colors ${!canUndo ? 'text-gray-300' : 'text-gray-600 hover:bg-gray-100'}`}
165+
className={`p-1.5 rounded-md transition-all ${!canUndo ? 'text-gray-300 cursor-not-allowed' : 'text-gray-600 hover:bg-white hover:text-gray-900 hover:shadow-sm'}`}
166+
title="Undo (Cmd+Z)"
146167
>
147168
<Undo className="w-4 h-4" />
148169
</button>
149170
<button
150171
onClick={redo}
151172
disabled={!canRedo}
152-
className={`p-2 rounded-md transition-colors ${!canRedo ? 'text-gray-300' : 'text-gray-600 hover:bg-gray-100'}`}
173+
className={`p-1.5 rounded-md transition-all ${!canRedo ? 'text-gray-300 cursor-not-allowed' : 'text-gray-600 hover:bg-white hover:text-gray-900 hover:shadow-sm'}`}
174+
title="Redo (Cmd+Y)"
153175
>
154176
<Redo className="w-4 h-4" />
155177
</button>
156-
<div className="w-px h-4 bg-gray-200 mx-1"></div>
157-
</>
178+
</div>
158179
)}
159180

181+
<div className="h-6 w-px bg-gray-200 mx-2"></div>
182+
160183
<button
161184
onClick={handleExport}
162-
className="p-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
163-
title="Export JSON"
185+
className="flex items-center gap-2 px-3 py-2 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-900 transition-colors"
186+
title="Download JSON"
164187
>
165188
<Download className="w-4 h-4" />
189+
Export
166190
</button>
167191

168192
<button
169193
onClick={handleCopy}
170-
className="flex items-center gap-2 px-3 py-1.5 text-xs rounded-md bg-indigo-600 text-white hover:bg-indigo-700 transition-colors shadow-sm ml-2"
194+
className={`flex items-center gap-2 px-3 py-2 text-xs font-medium rounded-lg transition-all shadow-sm ${
195+
copied
196+
? 'bg-green-600 text-white hover:bg-green-700'
197+
: 'bg-indigo-600 text-white hover:bg-indigo-700'
198+
}`}
171199
>
172-
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
173-
{copied ? 'Copied' : 'Copy JSON'}
200+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
201+
{copied ? 'Copied' : 'Copy'}
174202
</button>
175203
</div>
176204
</header>
@@ -259,31 +287,31 @@ const StudioEditor = ({ exampleId, initialJson }: { exampleId: ExampleKey, initi
259287
</div>
260288
) : viewMode === 'preview' ? (
261289
<div className="h-full flex flex-col bg-gray-50 overflow-hidden absolute inset-0">
262-
<div className="border-b px-4 py-2 bg-white flex items-center justify-center shadow-sm z-10">
290+
<div className="border-b px-4 py-2 bg-white/80 backdrop-blur-sm flex items-center justify-center z-10 sticky top-0">
263291
{/* Viewport Size Toggles */}
264-
<div className="flex items-center gap-1 bg-gray-100 rounded-md p-1">
292+
<div className="flex items-center gap-1 bg-gray-100/80 p-1 rounded-lg border border-gray-200">
265293
<button
266294
onClick={() => setViewportSize('desktop')}
267-
className={`p-1.5 rounded transition-colors ${
268-
viewportSize === 'desktop' ? 'bg-white shadow-sm text-indigo-600' : 'text-gray-500 hover:text-gray-700'
295+
className={`p-1.5 rounded-md transition-all duration-200 ${
296+
viewportSize === 'desktop' ? 'bg-white shadow-sm text-indigo-600 ring-1 ring-black/5' : 'text-gray-400 hover:text-gray-700'
269297
}`}
270298
title="Desktop View"
271299
>
272300
<Monitor className="h-4 w-4" />
273301
</button>
274302
<button
275303
onClick={() => setViewportSize('tablet')}
276-
className={`p-1.5 rounded transition-colors ${
277-
viewportSize === 'tablet' ? 'bg-white shadow-sm text-indigo-600' : 'text-gray-500 hover:text-gray-700'
304+
className={`p-1.5 rounded-md transition-all duration-200 ${
305+
viewportSize === 'tablet' ? 'bg-white shadow-sm text-indigo-600 ring-1 ring-black/5' : 'text-gray-400 hover:text-gray-700'
278306
}`}
279307
title="Tablet View"
280308
>
281309
<Tablet className="h-4 w-4" />
282310
</button>
283311
<button
284312
onClick={() => setViewportSize('mobile')}
285-
className={`p-1.5 rounded transition-colors ${
286-
viewportSize === 'mobile' ? 'bg-white shadow-sm text-indigo-600' : 'text-gray-500 hover:text-gray-700'
313+
className={`p-1.5 rounded-md transition-all duration-200 ${
314+
viewportSize === 'mobile' ? 'bg-white shadow-sm text-indigo-600 ring-1 ring-black/5' : 'text-gray-400 hover:text-gray-700'
287315
}`}
288316
title="Mobile View"
289317
>
@@ -292,62 +320,118 @@ const StudioEditor = ({ exampleId, initialJson }: { exampleId: ExampleKey, initi
292320
</div>
293321
</div>
294322

295-
<div className="flex-1 overflow-auto p-8 flex justify-center bg-gray-100/50">
296-
<div className={`${viewportStyles[viewportSize]} transition-all duration-300`}>
297-
<div className="rounded-xl border shadow-sm bg-background p-6 min-h-[500px] h-full ring-1 ring-black/5">
298-
{schema && !jsonError ? (
299-
<SchemaRenderer schema={schema} />
300-
) : (
301-
<div className="text-center py-12 text-muted-foreground">
302-
{jsonError ? (
303-
<div className="space-y-2">
304-
<p className="text-red-500 font-semibold">Invalid JSON</p>
305-
<p className="text-sm">Fix the syntax error to see the preview</p>
306-
</div>
307-
) : (
308-
<p>Loading...</p>
309-
)}
323+
<div
324+
className="flex-1 overflow-auto p-8 flex justify-center bg-slate-50 relative"
325+
style={{
326+
backgroundImage: 'radial-gradient(#cbd5e1 1px, transparent 1px)',
327+
backgroundSize: '24px 24px'
328+
}}
329+
>
330+
<div className={`${viewportStyles[viewportSize]} transition-all duration-300 ease-in-out`}>
331+
<div
332+
className={`
333+
bg-background h-full min-h-[500px]
334+
${viewportSize === 'mobile'
335+
? 'rounded-[3rem] border-[8px] border-slate-800 shadow-2xl'
336+
: viewportSize === 'tablet'
337+
? 'rounded-[2rem] border-[8px] border-slate-800 shadow-2xl'
338+
: 'rounded-xl border border-gray-200 shadow-xl'
339+
}
340+
${viewportSize !== 'desktop' ? 'overflow-hidden' : 'p-6'}
341+
transition-all duration-300
342+
`}
343+
>
344+
{/* Mobile/Tablet Bar */}
345+
{viewportSize !== 'desktop' && (
346+
<div className="h-6 bg-slate-800 w-full absolute top-0 left-0 z-50 flex justify-center items-center">
347+
<div className="w-16 h-1 bg-slate-700 rounded-full"></div>
310348
</div>
311349
)}
350+
351+
<div className={`h-full w-full ${viewportSize !== 'desktop' ? 'mt-0 pt-2 bg-white overflow-auto h-[calc(100%-0px)]' : ''}`}>
352+
{viewportSize !== 'desktop' && <div className="h-6 w-full flex-shrink-0"></div>} {/* Notch spacer */}
353+
354+
<div className={viewportSize !== 'desktop' ? 'p-4' : ''}>
355+
{schema && !jsonError ? (
356+
<SchemaRenderer schema={schema} />
357+
) : (
358+
<div className="text-center py-12 text-muted-foreground flex flex-col items-center justify-center h-full">
359+
{jsonError ? (
360+
<div className="space-y-2 p-4 bg-red-50 rounded-lg border border-red-100">
361+
<p className="text-red-600 font-semibold flex items-center gap-2">
362+
<span className="w-2 h-2 rounded-full bg-red-500"></span>
363+
Invalid JSON
364+
</p>
365+
<p className="text-xs text-red-500 font-mono text-left">{jsonError}</p>
366+
</div>
367+
) : (
368+
<div className="flex flex-col items-center gap-3">
369+
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
370+
<p className="text-sm text-gray-400">Rendering...</p>
371+
</div>
372+
)}
373+
</div>
374+
)}
375+
</div>
376+
</div>
312377
</div>
313378
</div>
314379
</div>
315380
</div>
316381
) : (
317382
<div className="flex h-full overflow-hidden absolute inset-0">
318383
{/* Code Editor */}
319-
<div className="w-1/2 h-full border-r flex flex-col relative z-10">
384+
<div className="w-1/2 h-full flex flex-col relative z-10 shadow-xl border-r border-[#333]">
320385
{jsonError && (
321-
<div className="px-4 py-2 bg-red-50 border-b border-red-200 text-red-700 text-sm">
322-
<strong>JSON Error:</strong> {jsonError}
386+
<div className="px-4 py-2 bg-red-900/20 border-b border-red-900/50 text-red-400 text-xs font-mono flex items-center gap-2">
387+
<span className="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span>
388+
<strong>Error:</strong> {jsonError}
323389
</div>
324390
)}
325391

326-
<div className="flex-1 overflow-hidden">
392+
<div className="h-9 bg-[#252526] flex items-center px-4 text-xs text-[#969696] select-none border-b border-[#333]">
393+
<Code2 className="w-3.5 h-3.5 mr-2" />
394+
<span>schema.json</span>
395+
</div>
396+
397+
<div className="flex-1 overflow-hidden relative bg-[#1e1e1e]">
327398
<textarea
328399
value={code}
329400
onChange={(e) => updateCode(e.target.value)}
330-
className="w-full h-full p-4 font-mono text-sm resize-none focus:outline-none border-0 bg-background text-foreground"
401+
className="w-full h-full p-6 font-mono text-sm resize-none focus:outline-none border-0 bg-[#1e1e1e] text-[#d4d4d4]"
331402
spellCheck={false}
332403
style={{
333404
tabSize: 2,
334-
lineHeight: '1.6'
405+
lineHeight: '1.6',
406+
fontFamily: '"Menlo", "Monaco", "Courier New", monospace'
335407
}}
336408
/>
337409
</div>
338410
</div>
339411

340412
{/* Side Preview */}
341-
<div className="w-1/2 h-full flex flex-col bg-gray-50">
342-
<div className="flex-1 overflow-auto p-8">
343-
<div className="max-w-full rounded-lg border shadow-sm bg-background p-6">
344-
{schema && !jsonError ? (
345-
<SchemaRenderer schema={schema} />
346-
) : (
347-
<div className="text-center py-12 text-muted-foreground">
348-
Invalid Schema
349-
</div>
350-
)}
413+
<div className="w-1/2 h-full flex flex-col bg-slate-50 relative"
414+
style={{
415+
backgroundImage: 'radial-gradient(#cbd5e1 1px, transparent 1px)',
416+
backgroundSize: '24px 24px'
417+
}}
418+
>
419+
<div className="flex-1 overflow-auto p-8 flex items-center justify-center">
420+
<div className="w-full max-w-xl mx-auto rounded-xl shadow-xl bg-background border ring-1 ring-black/5 overflow-hidden">
421+
<div className="h-9 bg-white border-b flex items-center px-3 gap-1.5">
422+
<div className="w-3 h-3 rounded-full bg-red-400/80"></div>
423+
<div className="w-3 h-3 rounded-full bg-amber-400/80"></div>
424+
<div className="w-3 h-3 rounded-full bg-green-400/80"></div>
425+
</div>
426+
<div className="p-6">
427+
{schema && !jsonError ? (
428+
<SchemaRenderer schema={schema} />
429+
) : (
430+
<div className="text-center py-12 text-muted-foreground text-sm">
431+
{jsonError ? 'Waiting for valid JSON...' : 'Rendering...'}
432+
</div>
433+
)}
434+
</div>
351435
</div>
352436
</div>
353437
</div>

0 commit comments

Comments
 (0)