mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Convert TypeScript schemas to JSON with compute functions
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
116
SCHEMA_CONVERSION_SUMMARY.md
Normal file
116
SCHEMA_CONVERSION_SUMMARY.md
Normal file
@@ -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 <PageRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
@@ -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 <PageRenderer schema={dashboardSchema} />
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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**:
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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 <PageRenderer schema={dashboardSchema} />
|
||||
|
||||
@@ -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 (
|
||||
|
||||
256
src/schemas/analytics-dashboard.json
Normal file
256
src/schemas/analytics-dashboard.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
88
src/schemas/compute-functions.ts
Normal file
88
src/schemas/compute-functions.ts
Normal file
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}))
|
||||
23
src/schemas/dashboard-simple.json
Normal file
23
src/schemas/dashboard-simple.json
Normal file
@@ -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": []
|
||||
}
|
||||
303
src/schemas/new-molecules-showcase.json
Normal file
303
src/schemas/new-molecules-showcase.json
Normal file
@@ -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": []
|
||||
}
|
||||
89
src/schemas/schema-loader.ts
Normal file
89
src/schemas/schema-loader.ts
Normal file
@@ -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<string, any> = {}
|
||||
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
|
||||
})
|
||||
}
|
||||
255
src/schemas/todo-list.json
Normal file
255
src/schemas/todo-list.json
Normal file
@@ -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": []
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"resolveJsonModule": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
|
||||
Reference in New Issue
Block a user