Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,5 +207,3 @@ Licensed under the [MIT License](https://www.google.com/search?q=LICENSE).
<div align="center">
<sub>Built with ❤️ by the Object Ecosystem Team.</sub>
</div>

```
24 changes: 24 additions & 0 deletions apps/playground/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
69 changes: 69 additions & 0 deletions apps/playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Object UI Playground

A live, interactive playground to showcase Object UI's schema-driven rendering capabilities.

## Features

- **Split View Editor**: JSON editor with syntax validation on the left, live preview on the right
- **Real-time Rendering**: See your changes instantly as you edit JSON schemas
- **Example Gallery**: Curated examples organized by category (Primitives, Layouts, Forms)
- **Responsive Preview**: Toggle between desktop, tablet, and mobile viewports
- **Copy Schema**: One-click copy to clipboard for easy integration
- **Error Highlighting**: Clear JSON syntax error messages

## Examples Included

### Primitives
- Simple page layouts
- Input component states (required, disabled, email)
- Button variants (destructive, outline, ghost, etc.)

### Layouts
- Responsive grid layouts
- Analytics dashboard with KPI cards
- Tabs component demonstration

### Forms
- User registration form with various input types
- Grid-based form layouts

## Running the Playground

From the monorepo root:

```bash
pnpm install
pnpm --filter @apps/playground dev
```

Or from this directory:

```bash
pnpm install
pnpm dev
```

The playground will be available at `http://localhost:5174`

## Building for Production

```bash
pnpm build
```

## Purpose

This playground serves as:

1. **Product Demo**: Show what Object UI can do without any backend
2. **Learning Tool**: Help developers understand schema structure
3. **Testing Ground**: Experiment with different configurations
4. **Documentation**: Live, interactive examples are better than static code samples

## Key Selling Points Demonstrated

- ✅ **Tailwind Native**: Edit `className` properties and see instant results
- ✅ **Schema-Driven**: Everything is pure JSON - no JSX needed
- ✅ **Responsive**: Built-in responsive grid layouts
- ✅ **Complete**: From simple buttons to complex dashboards
- ✅ **Standalone**: No backend required - works with any data source
13 changes: 13 additions & 0 deletions apps/playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Object UI - Live Playground</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions apps/playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@apps/playground",
"private": true,
"license": "MIT",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@object-ui/components": "workspace:*",
"@object-ui/core": "workspace:*",
"@object-ui/react": "workspace:*",
"lucide-react": "^0.469.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.23",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.19",
"tailwindcss-animate": "^1.0.7",
"typescript": "~5.9.3",
"vite": "^7.2.4"
}
}
6 changes: 6 additions & 0 deletions apps/playground/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
192 changes: 192 additions & 0 deletions apps/playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { useState, useEffect } from 'react';
import { SchemaRenderer } from '@object-ui/react';
import '@object-ui/components';
import { examples, exampleCategories, ExampleKey } from './data/examples';
import { Monitor, Tablet, Smartphone, Copy, Check, Code2 } from 'lucide-react';

type ViewportSize = 'desktop' | 'tablet' | 'mobile';

export default function Playground() {
const [selectedExample, setSelectedExample] = useState<ExampleKey>('dashboard');
const [code, setCode] = useState(examples['dashboard']);
const [schema, setSchema] = useState<any>(null);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema state uses any type which bypasses TypeScript's type checking. Consider using a more specific type from @object-ui/types or define an interface for the schema structure to maintain type safety.

Copilot uses AI. Check for mistakes.
const [jsonError, setJsonError] = useState<string | null>(null);
const [viewportSize, setViewportSize] = useState<ViewportSize>('desktop');
const [copied, setCopied] = useState(false);

// Real-time JSON parsing
useEffect(() => {
try {
const parsed = JSON.parse(code);
setSchema(parsed);
setJsonError(null);
} catch (e) {
setJsonError((e as Error).message);
// Keep previous schema on error
}
}, [code]);

const handleExampleChange = (key: ExampleKey) => {
setSelectedExample(key);
setCode(examples[key]);
};

const handleCopySchema = async () => {
try {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};

const viewportStyles: Record<ViewportSize, string> = {
desktop: 'w-full',
tablet: 'max-w-3xl mx-auto',
mobile: 'max-w-md mx-auto'
};

return (
<div className="flex h-screen w-screen bg-background">
{/* 1. Sidebar: Example Selector */}
<aside className="w-64 bg-muted/30 border-r overflow-auto">
<div className="p-6 border-b">
<h1 className="text-xl font-bold">Object UI</h1>
<p className="text-sm text-muted-foreground mt-1">Live Playground</p>
</div>

<div className="p-4 space-y-6">
{Object.entries(exampleCategories).map(([category, keys]) => (
<div key={category}>
<h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
{category}
</h2>
<div className="space-y-1">
{keys.map((key) => (
<button
key={key}
onClick={() => handleExampleChange(key as ExampleKey)}
className={`
block w-full text-left px-3 py-2 rounded-md text-sm transition-colors
${key === selectedExample
? 'bg-primary text-primary-foreground font-medium'
: 'hover:bg-muted text-foreground'
}
`}
>
{key.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline string transformation logic for converting kebab-case to Title Case is complex and duplicates formatting logic. Extract this into a utility function (e.g., formatExampleName) to improve readability and maintainability.

Copilot uses AI. Check for mistakes.
</button>
))}
</div>
</div>
))}
</div>
</aside>

{/* 2. Middle: Code Editor */}
<div className="w-1/2 h-full border-r flex flex-col">
<div className="border-b px-4 py-3 bg-muted/20 flex items-center justify-between">
<div className="flex items-center gap-2">
<Code2 className="h-4 w-4 text-muted-foreground" />
<h2 className="text-sm font-semibold">JSON Schema</h2>
</div>
<button
onClick={handleCopySchema}
className="flex items-center gap-2 px-3 py-1.5 text-xs rounded-md bg-background hover:bg-muted transition-colors border"
>
{copied ? (
<>
<Check className="h-3 w-3" />
Copied!
</>
) : (
<>
<Copy className="h-3 w-3" />
Copy Schema
</>
)}
</button>
</div>

{jsonError && (
<div className="px-4 py-2 bg-red-50 border-b border-red-200 text-red-700 text-sm">
<strong>JSON Error:</strong> {jsonError}
</div>
)}

<div className="flex-1 overflow-hidden">
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
className="w-full h-full p-4 font-mono text-sm resize-none focus:outline-none border-0 bg-background"
spellCheck={false}
style={{
tabSize: 2,
lineHeight: '1.6'
}}
/>
</div>
</div>

{/* 3. Right: Live Preview */}
<div className="w-1/2 h-full flex flex-col bg-muted/10">
<div className="border-b px-4 py-3 bg-muted/20 flex items-center justify-between">
<h2 className="text-sm font-semibold">Preview</h2>

{/* Viewport Size Toggles */}
<div className="flex items-center gap-1 bg-background rounded-md p-1 border">
<button
onClick={() => setViewportSize('desktop')}
className={`p-1.5 rounded transition-colors ${
viewportSize === 'desktop' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
}`}
title="Desktop View"
>
<Monitor className="h-4 w-4" />
</button>
<button
onClick={() => setViewportSize('tablet')}
className={`p-1.5 rounded transition-colors ${
viewportSize === 'tablet' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
}`}
title="Tablet View"
>
<Tablet className="h-4 w-4" />
</button>
<button
onClick={() => setViewportSize('mobile')}
className={`p-1.5 rounded transition-colors ${
viewportSize === 'mobile' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
}`}
title="Mobile View"
>
<Smartphone className="h-4 w-4" />
</button>
</div>
</div>

<div className="flex-1 overflow-auto p-8">
<div className={`${viewportStyles[viewportSize]} transition-all duration-300`}>
<div className="rounded-lg border shadow-sm bg-background p-6">
{schema && !jsonError ? (
<SchemaRenderer schema={schema} />
) : (
<div className="text-center py-12 text-muted-foreground">
{jsonError ? (
<div className="space-y-2">
<p className="text-red-500 font-semibold">Invalid JSON</p>
<p className="text-sm">Fix the syntax error to see the preview</p>
</div>
) : (
<p>Select an example to get started</p>
)}
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
}
Loading
Loading