diff --git a/SCHEMA_CONVERSION_SUMMARY.md b/SCHEMA_CONVERSION_SUMMARY.md
new file mode 100644
index 0000000..e25d15b
--- /dev/null
+++ b/SCHEMA_CONVERSION_SUMMARY.md
@@ -0,0 +1,116 @@
+# Schema Conversion Summary
+
+## Overview
+Successfully converted TypeScript schema files to JSON format with extracted compute functions.
+
+## Files Created
+
+### JSON Schemas
+1. **`src/schemas/analytics-dashboard.json`** - Converted from `dashboard-schema.ts`
+ - Contains the analytics dashboard with user management
+ - Compute functions: `computeFilteredUsers`, `computeStats`, `updateFilterQuery`, `transformFilteredUsers`, `transformUserList`
+
+2. **`src/schemas/todo-list.json`** - Split from `page-schemas.ts`
+ - Todo list application schema
+ - Compute functions: `computeTodoStats`, `updateNewTodo`, `computeAddTodo`, `checkCanAddTodo`
+
+3. **`src/schemas/dashboard-simple.json`** - Split from `page-schemas.ts`
+ - Simple dashboard with static stats
+ - No compute functions (pure static data)
+
+4. **`src/schemas/new-molecules-showcase.json`** - Split from `page-schemas.ts`
+ - Showcase of new molecular components
+ - No compute functions (pure static data)
+
+### TypeScript Support Files
+5. **`src/schemas/compute-functions.ts`** - Exported compute functions
+ - `computeFilteredUsers` - Filters users by search query
+ - `computeStats` - Calculates user statistics (total, active, inactive)
+ - `computeTodoStats` - Calculates todo statistics (total, completed, remaining)
+ - `computeAddTodo` - Creates new todo item
+ - `updateFilterQuery` - Event handler for filter input
+ - `updateNewTodo` - Event handler for todo input
+ - `checkCanAddTodo` - Condition checker for add button
+ - `transformFilteredUsers` - Transform function for badge display
+ - `transformUserList` - Transform function for rendering user cards
+
+6. **`src/schemas/schema-loader.ts`** - Hydration utility
+ - `hydrateSchema()` - Converts JSON schemas to runtime schemas
+ - Replaces string function identifiers with actual functions
+ - Handles compute functions in dataSources, events, actions, and bindings
+
+## Updated Files
+
+### Component Files
+- **`src/components/DashboardDemoPage.tsx`**
+ - Changed from importing TS schema to importing JSON + hydration
+
+- **`src/components/JSONUIShowcasePage.tsx`**
+ - Changed from importing TS schemas to importing JSON + hydration
+
+### Configuration
+- **`tsconfig.json`**
+ - Added `"resolveJsonModule": true` to enable JSON imports
+
+### Documentation
+- **`docs/ARCHITECTURE.md`** - Updated file structure and example code
+- **`docs/JSON_UI_GUIDE.md`** - Updated references to schema files
+- **`docs/IMPLEMENTATION_SUMMARY.md`** - Updated file list
+- **`docs/JSON_UI_ENHANCEMENT_SUMMARY.md`** - Updated schema file name
+
+## How It Works
+
+### 1. JSON Schema Format
+Compute functions are represented as string identifiers in JSON:
+```json
+{
+ "id": "stats",
+ "type": "computed",
+ "compute": "computeStats",
+ "dependencies": ["users"]
+}
+```
+
+### 2. Hydration Process
+The `hydrateSchema()` function replaces string identifiers with actual functions:
+```typescript
+import { hydrateSchema } from '@/schemas/schema-loader'
+import analyticsDashboardJson from '@/schemas/analytics-dashboard.json'
+
+const schema = hydrateSchema(analyticsDashboardJson)
+```
+
+### 3. Usage in Components
+```typescript
+export function DashboardDemoPage() {
+ return
+}
+```
+
+## Benefits
+
+1. **Pure JSON** - Schemas are now pure JSON files, making them easier to:
+ - Store in databases
+ - Transmit over APIs
+ - Edit with JSON tools
+ - Version control and diff
+
+2. **Separation of Concerns** - Logic is separated from structure:
+ - JSON defines the UI structure
+ - TypeScript contains the compute logic
+ - Schema loader connects them at runtime
+
+3. **Type Safety** - TypeScript functions remain type-safe and testable
+
+4. **Maintainability** - Compute functions are centralized and reusable
+
+## Old Files (Can be removed)
+- `src/schemas/dashboard-schema.ts` (replaced by `analytics-dashboard.json`)
+- `src/schemas/page-schemas.ts` (split into 3 JSON files)
+
+Note: Keep `src/schemas/ui-schema.ts` as it contains Zod validation schemas, not UI schemas.
+
+## Testing
+- Build completed successfully with `npm run build`
+- All TypeScript errors resolved
+- JSON imports working correctly
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index b1e9e52..f182df8 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -45,7 +45,10 @@ This project demonstrates a comprehensive JSON-driven UI architecture with atomi
```typescript
import { PageRenderer } from '@/lib/json-ui/page-renderer'
-import { dashboardSchema } from '@/schemas/dashboard-schema'
+import { hydrateSchema } from '@/schemas/schema-loader'
+import analyticsDashboardJson from '@/schemas/analytics-dashboard.json'
+
+const dashboardSchema = hydrateSchema(analyticsDashboardJson)
export function DashboardPage() {
return
@@ -439,8 +442,12 @@ src/
│ ├── component-renderer.tsx
│ └── component-registry.tsx
├── schemas/ # JSON page schemas
-│ ├── dashboard-schema.ts
-│ └── page-schemas.ts
+│ ├── analytics-dashboard.json
+│ ├── todo-list.json
+│ ├── dashboard-simple.json
+│ ├── new-molecules-showcase.json
+│ ├── compute-functions.ts
+│ └── schema-loader.ts
└── types/
└── json-ui.ts # TypeScript types
```
diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md
index 7462fb6..4bba010 100644
--- a/docs/IMPLEMENTATION_SUMMARY.md
+++ b/docs/IMPLEMENTATION_SUMMARY.md
@@ -163,7 +163,12 @@ src/
│ └── json-ui/
│ └── component-registry.tsx [MODIFIED]
├── schemas/
-│ └── dashboard-schema.ts [NEW]
+│ ├── analytics-dashboard.json [NEW]
+│ ├── todo-list.json [NEW]
+│ ├── dashboard-simple.json [NEW]
+│ ├── new-molecules-showcase.json [NEW]
+│ ├── compute-functions.ts [NEW]
+│ └── schema-loader.ts [NEW]
├── types/
│ └── json-ui.ts [MODIFIED]
├── App.simple-json-demo.tsx [NEW]
diff --git a/docs/JSON_UI_ENHANCEMENT_SUMMARY.md b/docs/JSON_UI_ENHANCEMENT_SUMMARY.md
index 4f3bc81..200fe01 100644
--- a/docs/JSON_UI_ENHANCEMENT_SUMMARY.md
+++ b/docs/JSON_UI_ENHANCEMENT_SUMMARY.md
@@ -59,7 +59,7 @@ Enhanced the JSON-driven UI system by creating additional custom hooks, atomic c
## JSON Page Schema Created
-### Analytics Dashboard Schema (/src/schemas/dashboard-schema.ts)
+### Analytics Dashboard Schema (/src/schemas/analytics-dashboard.json)
Comprehensive JSON-driven page demonstrating:
- **Data Sources**:
diff --git a/docs/JSON_UI_GUIDE.md b/docs/JSON_UI_GUIDE.md
index c89d4bf..de33a1e 100644
--- a/docs/JSON_UI_GUIDE.md
+++ b/docs/JSON_UI_GUIDE.md
@@ -524,7 +524,7 @@ events: [{
## Example: Complete Todo App
-See `/src/schemas/page-schemas.ts` for a full working example with:
+See `/src/schemas/todo-list.json` for a full working example with:
- KV persistence
- Computed statistics
- CRUD operations
@@ -577,7 +577,9 @@ See `/src/schemas/page-schemas.ts` for a full working example with:
## Resources
- **Type Definitions**: `/src/types/json-ui.ts`
-- **Page Schemas**: `/src/schemas/page-schemas.ts`
+- **JSON Schemas**: `/src/schemas/*.json`
+- **Compute Functions**: `/src/schemas/compute-functions.ts`
+- **Schema Loader**: `/src/schemas/schema-loader.ts`
- **Custom Hooks**: `/src/hooks/data/` and `/src/hooks/ui/`
- **Atomic Components**: `/src/components/atoms/`
- **Component Registry**: `/src/lib/json-ui/component-registry.ts`
diff --git a/src/components/DashboardDemoPage.tsx b/src/components/DashboardDemoPage.tsx
index e19d349..dd1585c 100644
--- a/src/components/DashboardDemoPage.tsx
+++ b/src/components/DashboardDemoPage.tsx
@@ -1,5 +1,8 @@
import { PageRenderer } from '@/lib/json-ui/page-renderer'
-import { dashboardSchema } from '@/schemas/dashboard-schema'
+import { hydrateSchema } from '@/schemas/schema-loader'
+import analyticsDashboardJson from '@/schemas/analytics-dashboard.json'
+
+const dashboardSchema = hydrateSchema(analyticsDashboardJson)
export function DashboardDemoPage() {
return
diff --git a/src/components/JSONUIShowcasePage.tsx b/src/components/JSONUIShowcasePage.tsx
index 6b8d7ea..2b92f00 100644
--- a/src/components/JSONUIShowcasePage.tsx
+++ b/src/components/JSONUIShowcasePage.tsx
@@ -2,7 +2,12 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { AtomicComponentDemo } from '@/components/AtomicComponentDemo'
import { DashboardDemoPage } from '@/components/DashboardDemoPage'
import { PageRenderer } from '@/lib/json-ui/page-renderer'
-import { todoListSchema, newMoleculesShowcaseSchema } from '@/schemas/page-schemas'
+import { hydrateSchema } from '@/schemas/schema-loader'
+import todoListJson from '@/schemas/todo-list.json'
+import newMoleculesShowcaseJson from '@/schemas/new-molecules-showcase.json'
+
+const todoListSchema = hydrateSchema(todoListJson)
+const newMoleculesShowcaseSchema = hydrateSchema(newMoleculesShowcaseJson)
export function JSONUIShowcasePage() {
return (
diff --git a/src/schemas/analytics-dashboard.json b/src/schemas/analytics-dashboard.json
new file mode 100644
index 0000000..441f7fb
--- /dev/null
+++ b/src/schemas/analytics-dashboard.json
@@ -0,0 +1,256 @@
+{
+ "id": "analytics-dashboard",
+ "name": "Analytics Dashboard",
+ "layout": {
+ "type": "single"
+ },
+ "dataSources": [
+ {
+ "id": "users",
+ "type": "kv",
+ "key": "dashboard-users",
+ "defaultValue": [
+ { "id": 1, "name": "Alice Johnson", "email": "alice@example.com", "status": "active", "joined": "2024-01-15" },
+ { "id": 2, "name": "Bob Smith", "email": "bob@example.com", "status": "active", "joined": "2024-02-20" },
+ { "id": 3, "name": "Charlie Brown", "email": "charlie@example.com", "status": "inactive", "joined": "2023-12-10" }
+ ]
+ },
+ {
+ "id": "filterQuery",
+ "type": "static",
+ "defaultValue": ""
+ },
+ {
+ "id": "filteredUsers",
+ "type": "computed",
+ "compute": "computeFilteredUsers",
+ "dependencies": ["users", "filterQuery"]
+ },
+ {
+ "id": "stats",
+ "type": "computed",
+ "compute": "computeStats",
+ "dependencies": ["users"]
+ }
+ ],
+ "components": [
+ {
+ "id": "root",
+ "type": "div",
+ "props": {
+ "className": "h-full overflow-auto p-6 bg-gradient-to-br from-background via-background to-accent/5"
+ },
+ "children": [
+ {
+ "id": "header",
+ "type": "div",
+ "props": { "className": "mb-8" },
+ "children": [
+ {
+ "id": "title",
+ "type": "Heading",
+ "props": {
+ "className": "text-4xl font-bold mb-2 bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent",
+ "children": "Analytics Dashboard"
+ }
+ },
+ {
+ "id": "subtitle",
+ "type": "Text",
+ "props": {
+ "className": "text-muted-foreground text-lg",
+ "children": "Monitor your user activity and key metrics"
+ }
+ }
+ ]
+ },
+ {
+ "id": "metrics-row",
+ "type": "div",
+ "props": { "className": "grid grid-cols-1 md:grid-cols-3 gap-6 mb-8" },
+ "children": [
+ {
+ "id": "metric-total",
+ "type": "Card",
+ "props": { "className": "bg-gradient-to-br from-primary/10 to-primary/5 border-primary/20" },
+ "children": [
+ {
+ "id": "metric-total-content",
+ "type": "CardContent",
+ "props": { "className": "pt-6" },
+ "children": [
+ {
+ "id": "metric-total-label",
+ "type": "div",
+ "props": { "className": "text-sm font-medium text-muted-foreground mb-2", "children": "Total Users" }
+ },
+ {
+ "id": "metric-total-value",
+ "type": "div",
+ "props": { "className": "text-4xl font-bold text-primary" },
+ "bindings": {
+ "children": { "source": "stats", "path": "total" }
+ }
+ },
+ {
+ "id": "metric-total-description",
+ "type": "div",
+ "props": { "className": "text-xs text-muted-foreground mt-2", "children": "Registered accounts" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "metric-active",
+ "type": "Card",
+ "props": { "className": "bg-gradient-to-br from-green-500/10 to-green-500/5 border-green-500/20" },
+ "children": [
+ {
+ "id": "metric-active-content",
+ "type": "CardContent",
+ "props": { "className": "pt-6" },
+ "children": [
+ {
+ "id": "metric-active-label",
+ "type": "div",
+ "props": { "className": "text-sm font-medium text-muted-foreground mb-2", "children": "Active Users" }
+ },
+ {
+ "id": "metric-active-value",
+ "type": "div",
+ "props": { "className": "text-4xl font-bold text-green-600" },
+ "bindings": {
+ "children": { "source": "stats", "path": "active" }
+ }
+ },
+ {
+ "id": "metric-active-description",
+ "type": "div",
+ "props": { "className": "text-xs text-muted-foreground mt-2", "children": "Currently engaged" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "metric-inactive",
+ "type": "Card",
+ "props": { "className": "bg-gradient-to-br from-orange-500/10 to-orange-500/5 border-orange-500/20" },
+ "children": [
+ {
+ "id": "metric-inactive-content",
+ "type": "CardContent",
+ "props": { "className": "pt-6" },
+ "children": [
+ {
+ "id": "metric-inactive-label",
+ "type": "div",
+ "props": { "className": "text-sm font-medium text-muted-foreground mb-2", "children": "Inactive Users" }
+ },
+ {
+ "id": "metric-inactive-value",
+ "type": "div",
+ "props": { "className": "text-4xl font-bold text-orange-600" },
+ "bindings": {
+ "children": { "source": "stats", "path": "inactive" }
+ }
+ },
+ {
+ "id": "metric-inactive-description",
+ "type": "div",
+ "props": { "className": "text-xs text-muted-foreground mt-2", "children": "Need re-engagement" }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "users-section",
+ "type": "Card",
+ "props": { "className": "bg-card/50 backdrop-blur" },
+ "children": [
+ {
+ "id": "users-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "users-title-row",
+ "type": "div",
+ "props": { "className": "flex items-center justify-between" },
+ "children": [
+ {
+ "id": "users-title",
+ "type": "CardTitle",
+ "props": { "children": "User Directory" }
+ },
+ {
+ "id": "users-badge",
+ "type": "Badge",
+ "props": { "variant": "secondary" },
+ "bindings": {
+ "children": {
+ "source": "filteredUsers",
+ "transform": "transformFilteredUsers"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "users-description",
+ "type": "CardDescription",
+ "props": { "children": "Manage and filter your user base" }
+ }
+ ]
+ },
+ {
+ "id": "users-content",
+ "type": "CardContent",
+ "children": [
+ {
+ "id": "filter-row",
+ "type": "div",
+ "props": { "className": "mb-6" },
+ "children": [
+ {
+ "id": "filter-input",
+ "type": "Input",
+ "props": { "placeholder": "Search users by name or email..." },
+ "events": [
+ {
+ "event": "onChange",
+ "actions": [
+ {
+ "id": "update-filter",
+ "type": "set-value",
+ "target": "filterQuery",
+ "compute": "updateFilterQuery"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "users-list",
+ "type": "div",
+ "props": { "className": "space-y-4" },
+ "bindings": {
+ "children": {
+ "source": "filteredUsers",
+ "transform": "transformUserList"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/schemas/compute-functions.ts b/src/schemas/compute-functions.ts
new file mode 100644
index 0000000..f34056e
--- /dev/null
+++ b/src/schemas/compute-functions.ts
@@ -0,0 +1,88 @@
+export const computeFilteredUsers = (data: any) => {
+ const query = (data.filterQuery || '').toLowerCase()
+ if (!query) return data.users || []
+ return (data.users || []).filter((user: any) =>
+ user.name.toLowerCase().includes(query) ||
+ user.email.toLowerCase().includes(query)
+ )
+}
+
+export const computeStats = (data: any) => ({
+ total: data.users?.length || 0,
+ active: data.users?.filter((u: any) => u.status === 'active').length || 0,
+ inactive: data.users?.filter((u: any) => u.status === 'inactive').length || 0,
+})
+
+export const computeTodoStats = (data: any) => ({
+ total: data.todos?.length || 0,
+ completed: data.todos?.filter((t: any) => t.completed).length || 0,
+ remaining: data.todos?.filter((t: any) => !t.completed).length || 0,
+})
+
+export const computeAddTodo = (data: any) => ({
+ id: Date.now(),
+ text: data.newTodo,
+ completed: false,
+})
+
+export const updateFilterQuery = (_: any, event: any) => event.target.value
+
+export const updateNewTodo = (data: any, event: any) => event.target.value
+
+export const checkCanAddTodo = (data: any) => data.newTodo?.trim().length > 0
+
+export const transformFilteredUsers = (users: any[]) => `${users.length} users`
+
+export const transformUserList = (users: any[]) => users.map((user: any) => ({
+ type: 'Card',
+ id: `user-${user.id}`,
+ props: {
+ className: 'bg-background/50 hover:bg-background/80 transition-colors border-l-4 border-l-primary',
+ },
+ children: [
+ {
+ type: 'CardContent',
+ id: `user-content-${user.id}`,
+ props: { className: 'pt-6' },
+ children: [
+ {
+ type: 'div',
+ id: `user-row-${user.id}`,
+ props: { className: 'flex items-start justify-between' },
+ children: [
+ {
+ type: 'div',
+ id: `user-info-${user.id}`,
+ props: { className: 'flex-1' },
+ children: [
+ {
+ type: 'div',
+ id: `user-name-${user.id}`,
+ props: { className: 'font-semibold text-lg mb-1', children: user.name },
+ },
+ {
+ type: 'div',
+ id: `user-email-${user.id}`,
+ props: { className: 'text-sm text-muted-foreground', children: user.email },
+ },
+ {
+ type: 'div',
+ id: `user-joined-${user.id}`,
+ props: { className: 'text-xs text-muted-foreground mt-2', children: `Joined ${user.joined}` },
+ },
+ ],
+ },
+ {
+ type: 'Badge',
+ id: `user-status-${user.id}`,
+ props: {
+ variant: user.status === 'active' ? 'default' : 'secondary',
+ children: user.status,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+}))
diff --git a/src/schemas/dashboard-simple.json b/src/schemas/dashboard-simple.json
new file mode 100644
index 0000000..c7c4428
--- /dev/null
+++ b/src/schemas/dashboard-simple.json
@@ -0,0 +1,23 @@
+{
+ "id": "dashboard",
+ "name": "Dashboard",
+ "layout": {
+ "type": "grid",
+ "columns": 2,
+ "gap": 4
+ },
+ "dataSources": [
+ {
+ "id": "stats",
+ "type": "static",
+ "defaultValue": {
+ "users": 1247,
+ "revenue": 45230,
+ "orders": 892,
+ "conversion": 3.2
+ }
+ }
+ ],
+ "components": [],
+ "globalActions": []
+}
diff --git a/src/schemas/new-molecules-showcase.json b/src/schemas/new-molecules-showcase.json
new file mode 100644
index 0000000..53beb9e
--- /dev/null
+++ b/src/schemas/new-molecules-showcase.json
@@ -0,0 +1,303 @@
+{
+ "id": "new-molecules-showcase",
+ "name": "New Molecules Showcase",
+ "layout": {
+ "type": "single"
+ },
+ "dataSources": [
+ {
+ "id": "itemCount",
+ "type": "static",
+ "defaultValue": 42
+ },
+ {
+ "id": "isLoading",
+ "type": "static",
+ "defaultValue": false
+ }
+ ],
+ "components": [
+ {
+ "id": "root",
+ "type": "div",
+ "props": {
+ "className": "h-full overflow-auto p-8 bg-background"
+ },
+ "children": [
+ {
+ "id": "page-header",
+ "type": "div",
+ "props": { "className": "mb-8" },
+ "children": [
+ {
+ "id": "page-title",
+ "type": "Heading",
+ "props": {
+ "level": 1,
+ "className": "text-4xl font-bold mb-2",
+ "children": "New JSON-Compatible Molecules"
+ }
+ },
+ {
+ "id": "page-description",
+ "type": "Text",
+ "props": {
+ "className": "text-muted-foreground text-lg",
+ "children": "Showcasing the newly added molecular components"
+ }
+ }
+ ]
+ },
+ {
+ "id": "showcase-grid",
+ "type": "Grid",
+ "props": { "cols": 2, "gap": "lg", "className": "max-w-5xl" },
+ "children": [
+ {
+ "id": "branding-card",
+ "type": "Card",
+ "children": [
+ {
+ "id": "branding-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "branding-title",
+ "type": "CardTitle",
+ "props": { "children": "AppBranding" }
+ },
+ {
+ "id": "branding-description",
+ "type": "CardDescription",
+ "props": { "children": "Application branding with logo, title, and subtitle" }
+ }
+ ]
+ },
+ {
+ "id": "branding-content",
+ "type": "CardContent",
+ "children": [
+ {
+ "id": "branding-demo",
+ "type": "AppBranding",
+ "props": {
+ "title": "My Amazing App",
+ "subtitle": "Built with JSON-Powered Components"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "label-badge-card",
+ "type": "Card",
+ "children": [
+ {
+ "id": "label-badge-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "label-badge-title",
+ "type": "CardTitle",
+ "props": { "children": "LabelWithBadge" }
+ },
+ {
+ "id": "label-badge-description",
+ "type": "CardDescription",
+ "props": { "children": "Label with optional badge indicator" }
+ }
+ ]
+ },
+ {
+ "id": "label-badge-content",
+ "type": "CardContent",
+ "props": { "className": "space-y-3" },
+ "children": [
+ {
+ "id": "label-badge-demo-1",
+ "type": "LabelWithBadge",
+ "props": {
+ "label": "Total Items"
+ },
+ "bindings": {
+ "badge": { "source": "itemCount" }
+ }
+ },
+ {
+ "id": "label-badge-demo-2",
+ "type": "LabelWithBadge",
+ "props": {
+ "label": "Warning",
+ "badge": "3",
+ "badgeVariant": "destructive"
+ }
+ },
+ {
+ "id": "label-badge-demo-3",
+ "type": "LabelWithBadge",
+ "props": {
+ "label": "Success",
+ "badge": "New",
+ "badgeVariant": "default"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "empty-state-card",
+ "type": "Card",
+ "children": [
+ {
+ "id": "empty-state-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "empty-state-title",
+ "type": "CardTitle",
+ "props": { "children": "EmptyEditorState" }
+ },
+ {
+ "id": "empty-state-description",
+ "type": "CardDescription",
+ "props": { "children": "Empty state display for editor contexts" }
+ }
+ ]
+ },
+ {
+ "id": "empty-state-content",
+ "type": "CardContent",
+ "props": { "className": "h-48" },
+ "children": [
+ {
+ "id": "empty-state-demo",
+ "type": "EmptyEditorState",
+ "props": {}
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "loading-states-card",
+ "type": "Card",
+ "children": [
+ {
+ "id": "loading-states-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "loading-states-title",
+ "type": "CardTitle",
+ "props": { "children": "Loading States" }
+ },
+ {
+ "id": "loading-states-description",
+ "type": "CardDescription",
+ "props": { "children": "LoadingFallback and LoadingState components" }
+ }
+ ]
+ },
+ {
+ "id": "loading-states-content",
+ "type": "CardContent",
+ "props": { "className": "space-y-4" },
+ "children": [
+ {
+ "id": "loading-fallback-wrapper",
+ "type": "div",
+ "props": { "className": "h-24 border border-border rounded-md" },
+ "children": [
+ {
+ "id": "loading-fallback-demo",
+ "type": "LoadingFallback",
+ "props": {
+ "message": "Loading your data..."
+ }
+ }
+ ]
+ },
+ {
+ "id": "loading-state-demo",
+ "type": "LoadingState",
+ "props": {
+ "message": "Processing request...",
+ "size": "sm"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "nav-header-card",
+ "type": "Card",
+ "props": { "className": "col-span-2" },
+ "children": [
+ {
+ "id": "nav-header-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "nav-header-title",
+ "type": "CardTitle",
+ "props": { "children": "NavigationGroupHeader" }
+ },
+ {
+ "id": "nav-header-description",
+ "type": "CardDescription",
+ "props": { "children": "Collapsible navigation group header (Note: requires Collapsible wrapper in production)" }
+ }
+ ]
+ },
+ {
+ "id": "nav-header-content",
+ "type": "CardContent",
+ "children": [
+ {
+ "id": "nav-header-demo",
+ "type": "NavigationGroupHeader",
+ "props": {
+ "label": "Components",
+ "count": 24,
+ "isExpanded": true
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "info-section",
+ "type": "Alert",
+ "props": {
+ "className": "max-w-5xl mt-8"
+ },
+ "children": [
+ {
+ "id": "info-title",
+ "type": "div",
+ "props": {
+ "className": "font-semibold mb-2",
+ "children": "✅ Successfully Added to JSON Registry"
+ }
+ },
+ {
+ "id": "info-text",
+ "type": "div",
+ "props": {
+ "className": "text-sm",
+ "children": "All components shown above are now available in the JSON UI component registry and can be used in JSON schemas."
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "globalActions": []
+}
diff --git a/src/schemas/schema-loader.ts b/src/schemas/schema-loader.ts
new file mode 100644
index 0000000..13f3867
--- /dev/null
+++ b/src/schemas/schema-loader.ts
@@ -0,0 +1,89 @@
+import { PageSchema } from '@/types/json-ui'
+import * as computeFunctions from './compute-functions'
+
+type ComputeFunctionMap = typeof computeFunctions
+
+export function hydrateSchema(jsonSchema: any): PageSchema {
+ const schema = { ...jsonSchema }
+
+ if (schema.dataSources) {
+ schema.dataSources = schema.dataSources.map((ds: any) => {
+ if (ds.type === 'computed' && typeof ds.compute === 'string') {
+ const functionName = ds.compute as keyof ComputeFunctionMap
+ const computeFunction = computeFunctions[functionName]
+ if (!computeFunction) {
+ console.warn(`Compute function "${functionName}" not found`)
+ }
+ return {
+ ...ds,
+ compute: computeFunction || (() => null)
+ }
+ }
+ return ds
+ })
+ }
+
+ if (schema.components) {
+ schema.components = hydrateComponents(schema.components)
+ }
+
+ return schema as PageSchema
+}
+
+function hydrateComponents(components: any[]): any[] {
+ return components.map(component => {
+ const hydratedComponent = { ...component }
+
+ if (component.events) {
+ hydratedComponent.events = component.events.map((event: any) => {
+ const hydratedEvent = { ...event }
+
+ if (event.condition && typeof event.condition === 'string') {
+ const functionName = event.condition as keyof ComputeFunctionMap
+ const conditionFunction = computeFunctions[functionName]
+ hydratedEvent.condition = conditionFunction || (() => false)
+ }
+
+ if (event.actions) {
+ hydratedEvent.actions = event.actions.map((action: any) => {
+ if (action.compute && typeof action.compute === 'string') {
+ const functionName = action.compute as keyof ComputeFunctionMap
+ const computeFunction = computeFunctions[functionName]
+ return {
+ ...action,
+ compute: computeFunction || (() => null)
+ }
+ }
+ return action
+ })
+ }
+
+ return hydratedEvent
+ })
+ }
+
+ if (component.bindings) {
+ const hydratedBindings: Record = {}
+ for (const [key, binding] of Object.entries(component.bindings)) {
+ const b = binding as any
+ if (b.transform && typeof b.transform === 'string') {
+ const functionName = b.transform as keyof ComputeFunctionMap
+ const transformFunction = computeFunctions[functionName]
+ hydratedBindings[key] = {
+ ...b,
+ transform: transformFunction || ((x: any) => x)
+ }
+ } else {
+ hydratedBindings[key] = b
+ }
+ }
+ hydratedComponent.bindings = hydratedBindings
+ }
+
+ if (component.children) {
+ hydratedComponent.children = hydrateComponents(component.children)
+ }
+
+ return hydratedComponent
+ })
+}
diff --git a/src/schemas/todo-list.json b/src/schemas/todo-list.json
new file mode 100644
index 0000000..bd44f7a
--- /dev/null
+++ b/src/schemas/todo-list.json
@@ -0,0 +1,255 @@
+{
+ "id": "todo-list",
+ "name": "Todo List",
+ "layout": {
+ "type": "single"
+ },
+ "dataSources": [
+ {
+ "id": "todos",
+ "type": "kv",
+ "key": "app-todos",
+ "defaultValue": [
+ { "id": 1, "text": "Learn JSON-driven UI", "completed": true },
+ { "id": 2, "text": "Build atomic components", "completed": false },
+ { "id": 3, "text": "Create custom hooks", "completed": false }
+ ]
+ },
+ {
+ "id": "newTodo",
+ "type": "static",
+ "defaultValue": ""
+ },
+ {
+ "id": "stats",
+ "type": "computed",
+ "compute": "computeTodoStats",
+ "dependencies": ["todos"]
+ }
+ ],
+ "components": [
+ {
+ "id": "root",
+ "type": "div",
+ "props": {
+ "className": "h-full overflow-auto p-6 bg-gradient-to-br from-background via-background to-primary/5"
+ },
+ "children": [
+ {
+ "id": "header",
+ "type": "div",
+ "props": { "className": "mb-6" },
+ "children": [
+ {
+ "id": "title",
+ "type": "Heading",
+ "props": {
+ "className": "text-4xl font-bold mb-2 bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent",
+ "children": "Task Manager"
+ }
+ },
+ {
+ "id": "subtitle",
+ "type": "Text",
+ "props": {
+ "className": "text-muted-foreground",
+ "children": "Built entirely from JSON schema"
+ }
+ }
+ ]
+ },
+ {
+ "id": "stats-row",
+ "type": "div",
+ "props": { "className": "grid grid-cols-1 md:grid-cols-3 gap-4 mb-6 max-w-3xl" },
+ "children": [
+ {
+ "id": "stat-total",
+ "type": "Card",
+ "props": { "className": "bg-card/50 backdrop-blur" },
+ "children": [
+ {
+ "id": "stat-total-content",
+ "type": "CardContent",
+ "props": { "className": "pt-6" },
+ "children": [
+ {
+ "id": "stat-total-label",
+ "type": "div",
+ "props": { "className": "text-sm text-muted-foreground mb-1", "children": "Total Tasks" }
+ },
+ {
+ "id": "stat-total-value",
+ "type": "div",
+ "props": { "className": "text-3xl font-bold" },
+ "bindings": {
+ "children": { "source": "stats", "path": "total" }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "stat-completed",
+ "type": "Card",
+ "props": { "className": "bg-accent/10 backdrop-blur border-accent/20" },
+ "children": [
+ {
+ "id": "stat-completed-content",
+ "type": "CardContent",
+ "props": { "className": "pt-6" },
+ "children": [
+ {
+ "id": "stat-completed-label",
+ "type": "div",
+ "props": { "className": "text-sm text-muted-foreground mb-1", "children": "Completed" }
+ },
+ {
+ "id": "stat-completed-value",
+ "type": "div",
+ "props": { "className": "text-3xl font-bold text-accent" },
+ "bindings": {
+ "children": { "source": "stats", "path": "completed" }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "stat-remaining",
+ "type": "Card",
+ "props": { "className": "bg-primary/5 backdrop-blur border-primary/20" },
+ "children": [
+ {
+ "id": "stat-remaining-content",
+ "type": "CardContent",
+ "props": { "className": "pt-6" },
+ "children": [
+ {
+ "id": "stat-remaining-label",
+ "type": "div",
+ "props": { "className": "text-sm text-muted-foreground mb-1", "children": "Remaining" }
+ },
+ {
+ "id": "stat-remaining-value",
+ "type": "div",
+ "props": { "className": "text-3xl font-bold text-primary" },
+ "bindings": {
+ "children": { "source": "stats", "path": "remaining" }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "main-card",
+ "type": "Card",
+ "props": { "className": "max-w-3xl" },
+ "children": [
+ {
+ "id": "card-header",
+ "type": "CardHeader",
+ "children": [
+ {
+ "id": "card-title",
+ "type": "CardTitle",
+ "props": { "children": "Your Tasks" }
+ },
+ {
+ "id": "card-description",
+ "type": "CardDescription",
+ "props": { "children": "Manage your daily tasks efficiently" }
+ }
+ ]
+ },
+ {
+ "id": "card-content",
+ "type": "CardContent",
+ "props": { "className": "space-y-4" },
+ "children": [
+ {
+ "id": "input-group",
+ "type": "div",
+ "props": { "className": "flex gap-2" },
+ "children": [
+ {
+ "id": "todo-input",
+ "type": "Input",
+ "props": {
+ "placeholder": "What needs to be done?"
+ },
+ "bindings": {
+ "value": { "source": "newTodo" }
+ },
+ "events": [
+ {
+ "event": "change",
+ "actions": [
+ {
+ "id": "update-input",
+ "type": "set-value",
+ "target": "newTodo",
+ "compute": "updateNewTodo"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "add-button",
+ "type": "Button",
+ "props": { "children": "Add Task" },
+ "events": [
+ {
+ "event": "click",
+ "actions": [
+ {
+ "id": "add-todo",
+ "type": "create",
+ "target": "todos",
+ "compute": "computeAddTodo"
+ },
+ {
+ "id": "clear-input",
+ "type": "set-value",
+ "target": "newTodo",
+ "value": ""
+ },
+ {
+ "id": "show-success",
+ "type": "show-toast",
+ "message": "Task added successfully!",
+ "variant": "success"
+ }
+ ],
+ "condition": "checkCanAddTodo"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "separator",
+ "type": "Separator",
+ "props": { "className": "my-4" }
+ },
+ {
+ "id": "todo-list",
+ "type": "div",
+ "props": { "className": "space-y-2" },
+ "children": []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "globalActions": []
+}
diff --git a/tsconfig.json b/tsconfig.json
index 9b370cd..f98bcad 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,7 @@
"module": "ESNext",
"skipLibCheck": true,
"strictNullChecks": true,
+ "resolveJsonModule": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,