From 8b0cc856daefaab08871c72c108cbf9ad3e25998 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 00:44:03 +0000 Subject: [PATCH] Refactor JSON demo and showcase configs --- src/components/JSONDemoPage.tsx | 154 +---------- src/components/JSONPageRenderer.tsx | 226 ++-------------- src/components/JSONUIShowcase.tsx | 164 ++++-------- src/components/json-demo/schema.ts | 101 +++++++ .../json-page-renderer/SectionRenderer.tsx | 250 ++++++++++++++++++ src/components/json-page-renderer/types.ts | 32 +++ src/components/json-page-renderer/utils.tsx | 16 ++ .../json-ui-showcase/ShowcaseFooter.tsx | 27 ++ .../json-ui-showcase/ShowcaseHeader.tsx | 24 ++ .../json-ui-showcase/ShowcaseTabs.tsx | 81 ++++++ src/components/json-ui-showcase/types.ts | 26 ++ src/config/json-demo.json | 102 +++++++ src/config/json-page-renderer.json | 3 + src/config/ui-examples/showcase.json | 59 +++++ 14 files changed, 797 insertions(+), 468 deletions(-) create mode 100644 src/components/json-demo/schema.ts create mode 100644 src/components/json-page-renderer/SectionRenderer.tsx create mode 100644 src/components/json-page-renderer/types.ts create mode 100644 src/components/json-page-renderer/utils.tsx create mode 100644 src/components/json-ui-showcase/ShowcaseFooter.tsx create mode 100644 src/components/json-ui-showcase/ShowcaseHeader.tsx create mode 100644 src/components/json-ui-showcase/ShowcaseTabs.tsx create mode 100644 src/components/json-ui-showcase/types.ts create mode 100644 src/config/json-demo.json create mode 100644 src/config/json-page-renderer.json create mode 100644 src/config/ui-examples/showcase.json diff --git a/src/components/JSONDemoPage.tsx b/src/components/JSONDemoPage.tsx index 68dd627..5a87a5c 100644 --- a/src/components/JSONDemoPage.tsx +++ b/src/components/JSONDemoPage.tsx @@ -1,16 +1,11 @@ import { JSONUIRenderer } from '@/lib/json-ui' -import { UIComponent } from '@/lib/json-ui/schema' import { toast } from 'sonner' import { useKV } from '@/hooks/use-kv' import { useState } from 'react' +import { buildDemoPageSchema, demoCopy, demoInitialTodos } from '@/components/json-demo/schema' export function JSONDemoPage() { - const [todos, setTodos] = useKV('json-demo-todos', [ - { id: 1, text: 'Design JSON schema', completed: true }, - { id: 2, text: 'Build atomic components', completed: false }, - { id: 3, text: 'Create custom hooks', completed: false }, - ]) - + const [todos, setTodos] = useKV('json-demo-todos', demoInitialTodos) const [newTodo, setNewTodo] = useState('') const handleAction = (handler: any, event?: any) => { @@ -19,13 +14,13 @@ export function JSONDemoPage() { if (newTodo.trim()) { setTodos((current: any) => [ ...current, - { id: Date.now(), text: newTodo, completed: false } + { id: Date.now(), text: newTodo, completed: false }, ]) setNewTodo('') - toast.success('Todo added!') + toast.success(demoCopy.toastAdded) } break - + case 'toggle-todo': setTodos((current: any) => current.map((todo: any) => @@ -35,152 +30,21 @@ export function JSONDemoPage() { ) ) break - + case 'delete-todo': setTodos((current: any) => current.filter((todo: any) => todo.id !== handler.params?.id) ) - toast.success('Todo deleted') + toast.success(demoCopy.toastDeleted) break - + case 'update-input': setNewTodo(event.target.value) break } } - const pageSchema: UIComponent = { - id: 'json-demo-page', - type: 'div', - className: 'h-full overflow-auto p-6', - children: [ - { - id: 'header', - type: 'div', - className: 'mb-6', - children: [ - { - id: 'title', - type: 'h1', - className: 'text-3xl font-bold mb-2', - children: 'JSON-Driven UI Demo' - }, - { - id: 'description', - type: 'p', - className: 'text-muted-foreground', - children: 'This entire page is built from a JSON schema - no JSX required!' - } - ] - }, - { - id: 'main-card', - type: 'Card', - className: 'max-w-2xl', - children: [ - { - id: 'card-header', - type: 'CardHeader', - children: [ - { - id: 'card-title', - type: 'CardTitle', - children: 'Todo List' - }, - { - id: 'card-description', - type: 'CardDescription', - children: 'Manage your tasks with JSON-powered UI' - } - ] - }, - { - id: 'card-content', - type: 'CardContent', - className: 'space-y-4', - children: [ - { - id: 'input-group', - type: 'div', - className: 'flex gap-2', - children: [ - { - id: 'todo-input', - type: 'Input', - props: { - placeholder: 'Enter a new todo...', - value: newTodo - }, - events: { - onChange: { action: 'update-input' } - } - }, - { - id: 'add-button', - type: 'Button', - props: { - children: 'Add' - }, - events: { - onClick: { action: 'add-todo' } - } - } - ] - }, - { - id: 'todo-list', - type: 'div', - className: 'space-y-2', - children: todos.map((todo: any) => ({ - id: `todo-${todo.id}`, - type: 'div', - className: 'flex items-center gap-2 p-3 rounded-lg border bg-card', - children: [ - { - id: `checkbox-${todo.id}`, - type: 'Checkbox', - props: { - checked: todo.completed - }, - events: { - onCheckedChange: { - action: 'toggle-todo', - params: { id: todo.id } - } - } - }, - { - id: `text-${todo.id}`, - type: 'span', - className: todo.completed - ? 'flex-1 line-through text-muted-foreground' - : 'flex-1', - children: todo.text - }, - { - id: `delete-${todo.id}`, - type: 'Button', - props: { - variant: 'ghost', - size: 'sm', - children: '×' - }, - events: { - onClick: { - action: 'delete-todo', - params: { id: todo.id } - } - } - } - ] - })) - } - ] - } - ] - } - ] - } + const pageSchema = buildDemoPageSchema(todos, newTodo) return ( - schema?: LegacyPageSchema - data?: Record - functions?: Record any> -} - -function resolveBinding(binding: string, data: Record): any { - try { - const func = new Function(...Object.keys(data), `return ${binding}`) - return func(...Object.values(data)) - } catch { - return binding - } -} - -function getIcon(iconName: string, props?: any) { - const IconComponent = (Icons as any)[iconName] - if (!IconComponent) return null - return -} - -export function JSONPageRenderer({ config, schema, data = {}, functions = {} }: ComponentRendererProps) { +export function JSONPageRenderer({ + config, + schema, + data = {}, + functions = {}, +}: ComponentRendererProps) { const pageSchema = config || schema if (!pageSchema) { - return
No schema provided
- } - - const renderSection = (section: PageSectionConfig, index: number): ReactNode => { - switch (section.type) { - case 'header': - return ( -
-

{section.title}

- {section.description && ( -

{section.description}

- )} -
- ) - - case 'cards': { - const cards = pageSchema[section.items as string] || [] - return ( -
- {cards.map((card: any) => renderCard(card))} -
- ) - } - - case 'grid': { - const gridItems = pageSchema[section.items as string] || [] - const { sm = 1, md = 2, lg = 3 } = section.columns || {} - return ( -
- {gridItems.map((item: any) => renderStatCard(item))} -
- ) - } - - default: - return null - } - } - - const renderCard = (card: any): ReactNode => { - const icon = card.icon ? getIcon(card.icon) : null - - if (card.type === 'gradient-card') { - const computeFn = functions[card.dataSource?.compute] - const computedData = computeFn ? computeFn(data) : {} - - return ( - - - - {icon && {icon}} - {card.title} - - {card.description} - - - {card.components?.map((comp: any, idx: number) => - renderSubComponent(comp, computedData, idx) - )} - - - ) - } - - return ( - - - {card.title} - - - {card.component && renderCustomComponent(card.component, card.props || {})} - - - ) - } - - const renderSubComponent = (comp: any, dataContext: any, key: number): ReactNode => { - const value = dataContext[comp.binding] - - switch (comp.type) { - case 'metric': - return ( -
- - {comp.format === 'percentage' ? `${value}%` : value} - -
- ) - - case 'badge': { - const variant = value === 'ready' ? comp.variants?.ready : comp.variants?.inProgress - return ( - - {variant?.label} - - ) - } - - case 'progress': - return - - case 'text': - return ( -

- {value} -

- ) - - default: - return null - } - } - - const renderStatCard = (stat: any): ReactNode => { - const icon = stat.icon ? getIcon(stat.icon) : undefined - const value = resolveBinding(stat.dataBinding, data) - const description = `${value} ${stat.description}` - - return ( - - ) - } - - const renderCustomComponent = (componentName: string, props: any): ReactNode => { - return
Custom component: {componentName}
+ return
{pageRendererCopy.fallbackText}
} return (
- {pageSchema.layout.sections?.map((section, index) => renderSection(section, index))} + {pageSchema.layout.sections?.map((section, index) => ( + + ))}
) } + +export type { ComponentRendererProps } from '@/components/json-page-renderer/types' diff --git a/src/components/JSONUIShowcase.tsx b/src/components/JSONUIShowcase.tsx index 6c57b10..d2ec68d 100644 --- a/src/components/JSONUIShowcase.tsx +++ b/src/components/JSONUIShowcase.tsx @@ -1,138 +1,64 @@ -import { useState } from 'react' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { JSONUIPage } from '@/components/JSONUIPage' -import { Separator } from '@/components/ui/separator' -import { Badge } from '@/components/ui/badge' +import { useMemo, useState } from 'react' +import showcaseCopy from '@/config/ui-examples/showcase.json' import dashboardExample from '@/config/ui-examples/dashboard.json' import formExample from '@/config/ui-examples/form.json' import tableExample from '@/config/ui-examples/table.json' import settingsExample from '@/config/ui-examples/settings.json' -import { FileCode, Eye, Code, ChartBar, ListBullets, Table, Gear } from '@phosphor-icons/react' +import { FileCode, ChartBar, ListBullets, Table, Gear } from '@phosphor-icons/react' +import { ShowcaseHeader } from '@/components/json-ui-showcase/ShowcaseHeader' +import { ShowcaseTabs } from '@/components/json-ui-showcase/ShowcaseTabs' +import { ShowcaseFooter } from '@/components/json-ui-showcase/ShowcaseFooter' +import { ShowcaseExample } from '@/components/json-ui-showcase/types' + +const exampleConfigs = { + dashboard: dashboardExample, + form: formExample, + table: tableExample, + settings: settingsExample, +} + +const exampleIcons = { + ChartBar, + ListBullets, + Table, + Gear, +} export function JSONUIShowcase() { - const [selectedExample, setSelectedExample] = useState('dashboard') + const [selectedExample, setSelectedExample] = useState(showcaseCopy.defaultExampleKey) const [showJSON, setShowJSON] = useState(false) - const examples = { - dashboard: { - name: 'Dashboard', - description: 'Complete dashboard with stats, activity feed, and quick actions', - icon: ChartBar, - config: dashboardExample, - }, - form: { - name: 'Form', - description: 'Dynamic form with validation and data binding', - icon: ListBullets, - config: formExample, - }, - table: { - name: 'Data Table', - description: 'Interactive table with row actions and looping', - icon: Table, - config: tableExample, - }, - settings: { - name: 'Settings', - description: 'Tabbed settings panel with switches and selections', - icon: Gear, - config: settingsExample, - }, - } + const examples = useMemo(() => { + return showcaseCopy.examples.map((example) => { + const icon = exampleIcons[example.icon as keyof typeof exampleIcons] || FileCode + const config = exampleConfigs[example.configKey as keyof typeof exampleConfigs] - const currentExample = examples[selectedExample as keyof typeof examples] + return { + key: example.key, + name: example.name, + description: example.description, + icon, + config, + } + }) + }, []) return (
-
-
-
-

JSON UI System

-

- Build complex UIs from declarative JSON configurations -

-
- - EXPERIMENTAL - -
-
+
- -
-
- - {Object.entries(examples).map(([key, example]) => { - const Icon = example.icon - return ( - - - {example.name} - - ) - })} - - -
-
- -
- {Object.entries(examples).map(([key, example]) => ( - - {showJSON ? ( -
- - - JSON Configuration - - {example.description} - - - -
-                          {JSON.stringify(example.config, null, 2)}
-                        
-
-
-
- ) : ( - - )} -
- ))} -
-
+
-
-
-
-
- Fully declarative - no React code needed -
- -
-
- Data binding with automatic updates -
- -
-
- Event handlers and actions -
-
-
+
) } diff --git a/src/components/json-demo/schema.ts b/src/components/json-demo/schema.ts new file mode 100644 index 0000000..0a6628e --- /dev/null +++ b/src/components/json-demo/schema.ts @@ -0,0 +1,101 @@ +import demoConfig from '@/config/json-demo.json' +import { UIComponent } from '@/lib/json-ui/schema' + +type TodoItem = { + id: number + text: string + completed: boolean +} + +type DemoCopy = { + toastAdded: string + toastDeleted: string + deleteButtonLabel: string +} + +const baseSchema = demoConfig.schema as UIComponent + +const cloneSchema = () => JSON.parse(JSON.stringify(baseSchema)) as UIComponent + +const findComponentById = (component: UIComponent, id: string): UIComponent | null => { + if (component.id === id) { + return component + } + + if (!Array.isArray(component.children)) { + return null + } + + for (const child of component.children) { + if (typeof child === 'object' && child && 'id' in child) { + const match = findComponentById(child as UIComponent, id) + if (match) { + return match + } + } + } + + return null +} + +const buildTodoItem = (todo: TodoItem, copy: DemoCopy): UIComponent => ({ + id: `todo-${todo.id}`, + type: 'div', + className: 'flex items-center gap-2 p-3 rounded-lg border bg-card', + children: [ + { + id: `checkbox-${todo.id}`, + type: 'Checkbox', + props: { + checked: todo.completed, + }, + events: { + onCheckedChange: { + action: 'toggle-todo', + params: { id: todo.id }, + }, + }, + }, + { + id: `text-${todo.id}`, + type: 'span', + className: todo.completed + ? 'flex-1 line-through text-muted-foreground' + : 'flex-1', + children: todo.text, + }, + { + id: `delete-${todo.id}`, + type: 'Button', + props: { + variant: 'ghost', + size: 'sm', + children: copy.deleteButtonLabel, + }, + events: { + onClick: { + action: 'delete-todo', + params: { id: todo.id }, + }, + }, + }, + ], +}) + +export const demoCopy = demoConfig.copy as DemoCopy +export const demoInitialTodos = demoConfig.initialTodos as TodoItem[] + +export const buildDemoPageSchema = (todos: TodoItem[], newTodo: string): UIComponent => { + const schema = cloneSchema() + const inputComponent = findComponentById(schema, 'todo-input') + if (inputComponent?.props) { + inputComponent.props.value = newTodo + } + + const todoList = findComponentById(schema, 'todo-list') + if (todoList) { + todoList.children = todos.map((todo) => buildTodoItem(todo, demoCopy)) + } + + return schema +} diff --git a/src/components/json-page-renderer/SectionRenderer.tsx b/src/components/json-page-renderer/SectionRenderer.tsx new file mode 100644 index 0000000..e698c50 --- /dev/null +++ b/src/components/json-page-renderer/SectionRenderer.tsx @@ -0,0 +1,250 @@ +import { ReactNode } from 'react' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Progress } from '@/components/ui/progress' +import { StatCard } from '@/components/atoms' +import { cn } from '@/lib/utils' +import { getIcon, resolveBinding } from './utils' +import { LegacyPageSchema, PageSectionConfig } from './types' + +interface PageSectionRendererProps { + index: number + section: PageSectionConfig + pageSchema: LegacyPageSchema + data: Record + functions: Record any> +} + +export function PageSectionRenderer({ + index, + section, + pageSchema, + data, + functions, +}: PageSectionRendererProps): ReactNode { + switch (section.type) { + case 'header': + return ( + + ) + + case 'cards': + return ( + + ) + + case 'grid': + return ( + + ) + + default: + return null + } +} + +interface HeaderSectionProps { + title?: string + description?: string +} + +function HeaderSection({ title, description }: HeaderSectionProps) { + return ( +
+

{title}

+ {description && ( +

{description}

+ )} +
+ ) +} + +interface CardSectionProps { + cards: any[] + spacing?: string + data: Record + functions: Record any> +} + +function CardSection({ cards, spacing, data, functions }: CardSectionProps) { + return ( +
+ {cards.map((card) => ( + + ))} +
+ ) +} + +interface PageCardProps { + card: any + data: Record + functions: Record any> +} + +function PageCard({ card, data, functions }: PageCardProps) { + const icon = card.icon ? getIcon(card.icon) : null + + if (card.type === 'gradient-card') { + const computeFn = functions[card.dataSource?.compute] + const computedData = computeFn ? computeFn(data) : {} + + return ( + + + + {icon && {icon}} + {card.title} + + {card.description} + + + {card.components?.map((comp: any, idx: number) => ( + + ))} + + + ) + } + + return ( + + + {card.title} + + + + + + ) +} + +interface CardSubComponentProps { + component: any + dataContext: Record +} + +function CardSubComponent({ component, dataContext }: CardSubComponentProps) { + const value = dataContext[component.binding] + + switch (component.type) { + case 'metric': + return ( +
+ + {component.format === 'percentage' ? `${value}%` : value} + +
+ ) + + case 'badge': { + const variant = + value === 'ready' ? component.variants?.ready : component.variants?.inProgress + return ( + + {variant?.label} + + ) + } + + case 'progress': + return + + case 'text': + return

{value}

+ + default: + return null + } +} + +interface GridSectionProps { + items: any[] + columns?: { + sm?: number + md?: number + lg?: number + } + gap?: string + data: Record +} + +function GridSection({ items, columns, gap, data }: GridSectionProps) { + const { sm = 1, md = 2, lg = 3 } = columns || {} + + return ( +
+ {items.map((item) => ( + + ))} +
+ ) +} + +interface StatCardRendererProps { + stat: any + data: Record +} + +function StatCardRenderer({ stat, data }: StatCardRendererProps) { + const icon = stat.icon ? getIcon(stat.icon) : undefined + const value = resolveBinding(stat.dataBinding, data) + const description = `${value} ${stat.description}` + + return ( + + ) +} + +interface CustomComponentPlaceholderProps { + componentName?: string + props: Record +} + +function CustomComponentPlaceholder({ componentName }: CustomComponentPlaceholderProps) { + return
Custom component: {componentName}
+} diff --git a/src/components/json-page-renderer/types.ts b/src/components/json-page-renderer/types.ts new file mode 100644 index 0000000..deda4d7 --- /dev/null +++ b/src/components/json-page-renderer/types.ts @@ -0,0 +1,32 @@ +export interface PageComponentConfig { + id: string + type: string + [key: string]: any +} + +export interface PageLayoutConfig { + type: string + spacing?: string + sections?: PageSectionConfig[] + [key: string]: any +} + +export interface PageSectionConfig { + type: string + [key: string]: any +} + +export interface LegacyPageSchema { + id: string + layout: PageLayoutConfig + dashboardCards?: any[] + statCards?: any[] + [key: string]: any +} + +export interface ComponentRendererProps { + config?: Record + schema?: LegacyPageSchema + data?: Record + functions?: Record any> +} diff --git a/src/components/json-page-renderer/utils.tsx b/src/components/json-page-renderer/utils.tsx new file mode 100644 index 0000000..3729351 --- /dev/null +++ b/src/components/json-page-renderer/utils.tsx @@ -0,0 +1,16 @@ +import * as Icons from '@phosphor-icons/react' + +export function resolveBinding(binding: string, data: Record): any { + try { + const func = new Function(...Object.keys(data), `return ${binding}`) + return func(...Object.values(data)) + } catch { + return binding + } +} + +export function getIcon(iconName: string, props?: any) { + const IconComponent = (Icons as any)[iconName] + if (!IconComponent) return null + return +} diff --git a/src/components/json-ui-showcase/ShowcaseFooter.tsx b/src/components/json-ui-showcase/ShowcaseFooter.tsx new file mode 100644 index 0000000..e14c36b --- /dev/null +++ b/src/components/json-ui-showcase/ShowcaseFooter.tsx @@ -0,0 +1,27 @@ +import { Fragment } from 'react' +import { Separator } from '@/components/ui/separator' +import { ShowcaseFooterItem } from './types' + +interface ShowcaseFooterProps { + items: ShowcaseFooterItem[] +} + +export function ShowcaseFooter({ items }: ShowcaseFooterProps) { + return ( +
+
+ {items.map((item, index) => ( + +
+
+ {item.label} +
+ {index < items.length - 1 && ( + + )} + + ))} +
+
+ ) +} diff --git a/src/components/json-ui-showcase/ShowcaseHeader.tsx b/src/components/json-ui-showcase/ShowcaseHeader.tsx new file mode 100644 index 0000000..e06311c --- /dev/null +++ b/src/components/json-ui-showcase/ShowcaseHeader.tsx @@ -0,0 +1,24 @@ +import { Badge } from '@/components/ui/badge' +import { ShowcaseHeaderCopy } from './types' + +interface ShowcaseHeaderProps { + copy: ShowcaseHeaderCopy +} + +export function ShowcaseHeader({ copy }: ShowcaseHeaderProps) { + return ( +
+
+
+

{copy.title}

+

+ {copy.description} +

+
+ + {copy.badge} + +
+
+ ) +} diff --git a/src/components/json-ui-showcase/ShowcaseTabs.tsx b/src/components/json-ui-showcase/ShowcaseTabs.tsx new file mode 100644 index 0000000..7b92b7a --- /dev/null +++ b/src/components/json-ui-showcase/ShowcaseTabs.tsx @@ -0,0 +1,81 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { JSONUIPage } from '@/components/JSONUIPage' +import { Eye, Code } from '@phosphor-icons/react' +import { ShowcaseExample, ShowcaseTabsCopy } from './types' + +interface ShowcaseTabsProps { + examples: ShowcaseExample[] + copy: ShowcaseTabsCopy + selectedExample: string + onSelectedExampleChange: (value: string) => void + showJSON: boolean + onShowJSONChange: (value: boolean) => void +} + +export function ShowcaseTabs({ + examples, + copy, + selectedExample, + onSelectedExampleChange, + showJSON, + onShowJSONChange, +}: ShowcaseTabsProps) { + return ( + +
+
+ + {examples.map((example) => { + const Icon = example.icon + return ( + + + {example.name} + + ) + })} + + +
+
+ +
+ {examples.map((example) => ( + + {showJSON ? ( +
+ + + {copy.jsonTitle} + {example.description} + + +
+                      {JSON.stringify(example.config, null, 2)}
+                    
+
+
+
+ ) : ( + + )} +
+ ))} +
+
+ ) +} diff --git a/src/components/json-ui-showcase/types.ts b/src/components/json-ui-showcase/types.ts new file mode 100644 index 0000000..9d551b7 --- /dev/null +++ b/src/components/json-ui-showcase/types.ts @@ -0,0 +1,26 @@ +import { ComponentType } from 'react' + +export interface ShowcaseExample { + key: string + name: string + description: string + icon: ComponentType<{ size?: number }> + config: Record +} + +export interface ShowcaseTabsCopy { + showJsonLabel: string + showPreviewLabel: string + jsonTitle: string +} + +export interface ShowcaseHeaderCopy { + title: string + description: string + badge: string +} + +export interface ShowcaseFooterItem { + label: string + colorClass: string +} diff --git a/src/config/json-demo.json b/src/config/json-demo.json new file mode 100644 index 0000000..f1dadb7 --- /dev/null +++ b/src/config/json-demo.json @@ -0,0 +1,102 @@ +{ + "copy": { + "toastAdded": "Todo added!", + "toastDeleted": "Todo deleted", + "deleteButtonLabel": "×" + }, + "initialTodos": [ + { "id": 1, "text": "Design JSON schema", "completed": true }, + { "id": 2, "text": "Build atomic components", "completed": false }, + { "id": 3, "text": "Create custom hooks", "completed": false } + ], + "schema": { + "id": "json-demo-page", + "type": "div", + "className": "h-full overflow-auto p-6", + "children": [ + { + "id": "header", + "type": "div", + "className": "mb-6", + "children": [ + { + "id": "title", + "type": "h1", + "className": "text-3xl font-bold mb-2", + "children": "JSON-Driven UI Demo" + }, + { + "id": "description", + "type": "p", + "className": "text-muted-foreground", + "children": "This entire page is built from a JSON schema - no JSX required!" + } + ] + }, + { + "id": "main-card", + "type": "Card", + "className": "max-w-2xl", + "children": [ + { + "id": "card-header", + "type": "CardHeader", + "children": [ + { + "id": "card-title", + "type": "CardTitle", + "children": "Todo List" + }, + { + "id": "card-description", + "type": "CardDescription", + "children": "Manage your tasks with JSON-powered UI" + } + ] + }, + { + "id": "card-content", + "type": "CardContent", + "className": "space-y-4", + "children": [ + { + "id": "input-group", + "type": "div", + "className": "flex gap-2", + "children": [ + { + "id": "todo-input", + "type": "Input", + "props": { + "placeholder": "Enter a new todo...", + "value": "" + }, + "events": { + "onChange": { "action": "update-input" } + } + }, + { + "id": "add-button", + "type": "Button", + "props": { + "children": "Add" + }, + "events": { + "onClick": { "action": "add-todo" } + } + } + ] + }, + { + "id": "todo-list", + "type": "div", + "className": "space-y-2", + "children": [] + } + ] + } + ] + } + ] + } +} diff --git a/src/config/json-page-renderer.json b/src/config/json-page-renderer.json new file mode 100644 index 0000000..1caa493 --- /dev/null +++ b/src/config/json-page-renderer.json @@ -0,0 +1,3 @@ +{ + "fallbackText": "No schema provided" +} diff --git a/src/config/ui-examples/showcase.json b/src/config/ui-examples/showcase.json new file mode 100644 index 0000000..e6c380f --- /dev/null +++ b/src/config/ui-examples/showcase.json @@ -0,0 +1,59 @@ +{ + "defaultExampleKey": "dashboard", + "header": { + "title": "JSON UI System", + "description": "Build complex UIs from declarative JSON configurations", + "badge": "EXPERIMENTAL" + }, + "tabs": { + "showJsonLabel": "Show JSON", + "showPreviewLabel": "Show Preview", + "jsonTitle": "JSON Configuration" + }, + "examples": [ + { + "key": "dashboard", + "name": "Dashboard", + "description": "Complete dashboard with stats, activity feed, and quick actions", + "icon": "ChartBar", + "configKey": "dashboard" + }, + { + "key": "form", + "name": "Form", + "description": "Dynamic form with validation and data binding", + "icon": "ListBullets", + "configKey": "form" + }, + { + "key": "table", + "name": "Data Table", + "description": "Interactive table with row actions and looping", + "icon": "Table", + "configKey": "table" + }, + { + "key": "settings", + "name": "Settings", + "description": "Tabbed settings panel with switches and selections", + "icon": "Gear", + "configKey": "settings" + } + ], + "footer": { + "items": [ + { + "label": "Fully declarative - no React code needed", + "colorClass": "bg-green-500" + }, + { + "label": "Data binding with automatic updates", + "colorClass": "bg-blue-500" + }, + { + "label": "Event handlers and actions", + "colorClass": "bg-purple-500" + } + ] + } +}