Skip to content

Commit b76f864

Browse files
committed
Add Page layout component and schema
Introduces a new Page layout component with corresponding PageSchema type for top-level page containers. Updates the Home page UI to include 'New Design' and 'Start New Design' actions, and registers the PageRenderer in the component registry.
1 parent 15ef741 commit b76f864

5 files changed

Lines changed: 119 additions & 4 deletions

File tree

apps/playground/src/pages/Home.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState } from 'react';
22
import { useNavigate } from 'react-router-dom';
33
import { exampleCategories } from '../data/examples';
4-
import { LayoutTemplate, ArrowRight, Component, Layers, Database, Shield, Box, FolderOpen } from 'lucide-react';
4+
import { LayoutTemplate, ArrowRight, Component, Layers, Database, Shield, Box, FolderOpen, Plus } from 'lucide-react';
55

66
const CategoryIcon = ({ name }: { name: string }) => {
77
switch (name) {
@@ -30,6 +30,13 @@ export const Home = () => {
3030
<span className="font-bold text-xl tracking-tight bg-gradient-to-r from-gray-900 to-gray-600 bg-clip-text text-transparent">Object UI Studio</span>
3131
</div>
3232
<div className="flex items-center gap-3">
33+
<button
34+
onClick={() => navigate('/studio/new')}
35+
className="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-indigo-600 bg-indigo-50 hover:bg-indigo-100 border border-indigo-200 rounded-lg transition-all shadow-sm hover:shadow"
36+
>
37+
<Plus className="w-4 h-4" />
38+
New Design
39+
</button>
3340
<button
3441
onClick={() => navigate('/my-designs')}
3542
className="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-gray-700 hover:text-gray-900 bg-white/50 hover:bg-white border border-gray-200 rounded-lg transition-all shadow-sm hover:shadow"
@@ -71,11 +78,28 @@ export const Home = () => {
7178
Build Stunning Interfaces,<br />
7279
<span className="bg-gradient-to-r from-indigo-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">Purely from JSON.</span>
7380
</h1>
74-
<p className="max-w-2xl mx-auto text-xl text-gray-600 leading-relaxed">
81+
<p className="max-w-2xl mx-auto text-xl text-gray-600 leading-relaxed mb-8">
7582
Object UI transforms JSON schemas into fully functional, accessible, and responsive React applications.
7683
<br className="hidden sm:block" />
77-
<span className="font-semibold text-gray-700">Select a template below to start building.</span>
84+
<span className="font-semibold text-gray-700">Select a template below or start from scratch.</span>
7885
</p>
86+
87+
<div className="flex justify-center gap-4">
88+
<button
89+
onClick={() => navigate('/studio/new')}
90+
className="flex items-center gap-2 px-6 py-3 text-lg font-bold text-white bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-xl shadow-lg shadow-indigo-300/50 transition-all transform hover:scale-105"
91+
>
92+
<Plus className="w-5 h-5" />
93+
Start New Design
94+
</button>
95+
<button
96+
onClick={() => navigate('/my-designs')}
97+
className="flex items-center gap-2 px-6 py-3 text-lg font-bold text-gray-700 bg-white hover:bg-gray-50 border-2 border-gray-200 rounded-xl shadow-lg transition-all transform hover:scale-105"
98+
>
99+
<FolderOpen className="w-5 h-5" />
100+
Open Saved
101+
</button>
102+
</div>
79103
</div>
80104

81105
{/* Category Filter */}

packages/components/src/renderers/layout/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ import './tabs';
33
import './grid';
44
import './flex';
55
import './container';
6+
import './page';
7+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
import { PageSchema } from '@object-ui/types';
3+
import { SchemaRenderer } from '@object-ui/react';
4+
import { ComponentRegistry } from '@object-ui/core';
5+
import { cn } from '../../lib/utils'; // Keep internal import for utils
6+
7+
export const PageRenderer: React.FC<{ schema: PageSchema }> = ({ schema }) => {
8+
// Support both body (legacy/playground) and children
9+
const content = schema.body || schema.children;
10+
const nodes = Array.isArray(content) ? content : (content ? [content] : []);
11+
12+
return (
13+
<div className={cn("min-h-full w-full bg-background p-6 md:p-8", schema.className)}>
14+
<div className="mx-auto max-w-7xl space-y-8">
15+
{(schema.title || schema.description) && (
16+
<div className="space-y-2">
17+
{schema.title && (
18+
<h1 className="text-3xl font-bold tracking-tight text-foreground">
19+
{schema.title}
20+
</h1>
21+
)}
22+
{schema.description && (
23+
<p className="text-muted-foreground">
24+
{schema.description}
25+
</p>
26+
)}
27+
</div>
28+
)}
29+
30+
<div className="space-y-6">
31+
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
32+
{nodes.map((node: any, index: number) => (
33+
<SchemaRenderer
34+
key={node?.id || index}
35+
schema={node}
36+
/>
37+
))}
38+
</div>
39+
</div>
40+
</div>
41+
);
42+
};
43+
44+
ComponentRegistry.register(
45+
'page',
46+
PageRenderer,
47+
{
48+
label: 'Page',
49+
icon: 'Layout',
50+
category: 'layout',
51+
inputs: [
52+
{ name: 'title', type: 'string', label: 'Title' },
53+
{ name: 'description', type: 'string', label: 'Description' },
54+
{
55+
name: 'body',
56+
type: 'array',
57+
label: 'Content',
58+
// @ts-ignore - itemType is experimental/extended metadata
59+
itemType: 'component'
60+
}
61+
]
62+
}
63+
);

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export type {
7474
ResizableSchema,
7575
ResizablePanel,
7676
LayoutSchema,
77+
PageSchema,
7778
} from './layout';
7879

7980
// ============================================================================

packages/types/src/layout.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,30 @@ export interface ResizablePanel {
368368
content: SchemaNode | SchemaNode[];
369369
}
370370

371+
/**
372+
* Page layout component
373+
* Top-level container for a page route
374+
*/
375+
export interface PageSchema extends BaseSchema {
376+
type: 'page';
377+
/**
378+
* Page title
379+
*/
380+
title?: string;
381+
/**
382+
* Page description
383+
*/
384+
description?: string;
385+
/**
386+
* Main content array
387+
*/
388+
body?: SchemaNode[];
389+
/**
390+
* Alternative content prop
391+
*/
392+
children?: SchemaNode | SchemaNode[];
393+
}
394+
371395
/**
372396
* Union type of all layout schemas
373397
*/
@@ -384,4 +408,5 @@ export type LayoutSchema =
384408
| CardSchema
385409
| TabsSchema
386410
| ScrollAreaSchema
387-
| ResizableSchema;
411+
| ResizableSchema
412+
| PageSchema;

0 commit comments

Comments
 (0)