Merge branch 'main' into codex/update-json-ui-components-and-registry-u03e4j

This commit is contained in:
2026-01-18 13:13:48 +00:00
committed by GitHub
25 changed files with 1567 additions and 47 deletions

View File

@@ -32,7 +32,7 @@ The following components have been successfully integrated into the JSON UI syst
### 4. Created Showcase Schema
**File: `src/schemas/page-schemas.ts`**
**File: `src/schemas/new-molecules-showcase.json`**
- Created `newMoleculesShowcaseSchema` - A comprehensive demonstration page
- Showcases each new component with realistic use cases
- Includes data bindings and multiple variants
@@ -166,7 +166,7 @@ Here's how to use the new components in JSON schemas:
- Implementation summary: `JSON_COMPATIBILITY_IMPLEMENTATION.md` (this file)
- Component registry: `src/lib/json-ui/component-registry.tsx`
- Type definitions: `src/types/json-ui.ts`
- Showcase schema: `src/schemas/page-schemas.ts`
- Showcase schema: `src/schemas/new-molecules-showcase.json`
- Live demo: Navigate to JSON UI Showcase → "New Molecules" tab
## ✨ Key Achievements

View File

@@ -236,6 +236,15 @@
"category": "showcase",
"description": "JSON UI system demonstration"
},
{
"name": "JSONConversionShowcase",
"path": "@/components/JSONConversionShowcase",
"export": "JSONConversionShowcase",
"type": "feature",
"preload": false,
"category": "showcase",
"description": "JSON conversion showcase overview"
},
{
"name": "SchemaEditor",
"path": "@/components/SchemaEditorPage",

View File

@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test'
test.describe('visual regression', () => {
test('json conversion showcase', async ({ page }) => {
await page.goto('/json-conversion-showcase')
await page.waitForLoadState('networkidle')
await page.waitForFunction(() => {
const root = document.querySelector('#root')
return root && root.textContent && root.textContent.length > 0
})
await page.addStyleTag({
content: '* { transition: none !important; animation: none !important; }',
})
await expect(page).toHaveScreenshot('json-conversion-showcase.png', { fullPage: true })
})
})

View File

@@ -67,7 +67,20 @@
"description": "ComponentBindingDialog component",
"status": "supported",
"source": "molecules",
"jsonCompatible": true
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "ComponentBindingDialogWrapper"
},
{
"type": "ComponentBindingDialogWrapper",
"name": "ComponentBindingDialogWrapper",
"category": "layout",
"canHaveChildren": true,
"description": "JSON wrapper for component binding dialog with props-driven bindings",
"status": "json-compatible",
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "ComponentBindingDialog"
},
{
"type": "Container",
@@ -96,7 +109,20 @@
"description": "DataSourceEditorDialog component",
"status": "supported",
"source": "molecules",
"jsonCompatible": true
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "DataSourceEditorDialogWrapper"
},
{
"type": "DataSourceEditorDialogWrapper",
"name": "DataSourceEditorDialogWrapper",
"category": "layout",
"canHaveChildren": true,
"description": "JSON wrapper for data source editor dialog with props-driven fields",
"status": "json-compatible",
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "DataSourceEditorDialog"
},
{
"type": "Dialog",
@@ -893,7 +919,20 @@
"description": "GitHubBuildStatus component",
"status": "supported",
"source": "molecules",
"jsonCompatible": false
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "GitHubBuildStatusWrapper"
},
{
"type": "GitHubBuildStatusWrapper",
"name": "GitHubBuildStatusWrapper",
"category": "feedback",
"canHaveChildren": false,
"description": "JSON wrapper for props-driven GitHub build status summary",
"status": "json-compatible",
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "GitHubBuildStatus"
},
{
"type": "InfoBox",
@@ -1039,30 +1078,69 @@
"name": "LazyBarChart",
"category": "data",
"canHaveChildren": true,
"description": "Lazy-loaded Recharts bar chart with runtime library loading",
"status": "supported",
"source": "molecules",
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "LazyBarChartWrapper"
},
{
"type": "LazyBarChartWrapper",
"name": "LazyBarChartWrapper",
"category": "data",
"canHaveChildren": true,
"description": "JSON wrapper for a props-driven bar chart (no lazy hooks)",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "LazyBarChart"
},
{
"type": "LazyD3BarChart",
"name": "LazyD3BarChart",
"category": "data",
"canHaveChildren": true,
"description": "Lazy-loaded D3 bar chart with runtime library loading",
"status": "supported",
"source": "molecules",
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "LazyD3BarChartWrapper"
},
{
"type": "LazyD3BarChartWrapper",
"name": "LazyD3BarChartWrapper",
"category": "data",
"canHaveChildren": true,
"description": "JSON wrapper for a simple SVG bar chart (no D3 hooks)",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "LazyD3BarChart"
},
{
"type": "LazyLineChart",
"name": "LazyLineChart",
"category": "data",
"canHaveChildren": true,
"description": "Lazy-loaded Recharts line chart with runtime library loading",
"status": "supported",
"source": "molecules",
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "LazyLineChartWrapper"
},
{
"type": "LazyLineChartWrapper",
"name": "LazyLineChartWrapper",
"category": "data",
"canHaveChildren": true,
"description": "JSON wrapper for a props-driven line chart (no lazy hooks)",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "LazyLineChart"
},
{
"type": "List",
@@ -1105,10 +1183,23 @@
"name": "SeedDataManager",
"category": "data",
"canHaveChildren": true,
"description": "Seed data management with app-level hook state",
"status": "supported",
"source": "molecules",
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "SeedDataManagerWrapper"
},
{
"type": "SeedDataManagerWrapper",
"name": "SeedDataManagerWrapper",
"category": "data",
"canHaveChildren": true,
"description": "JSON wrapper for seed data management with props-driven state",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "SeedDataManager"
},
{
"type": "StatCard",
@@ -1337,7 +1428,20 @@
"description": "ComponentTree component",
"status": "supported",
"source": "molecules",
"jsonCompatible": true
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "ComponentTreeWrapper"
},
{
"type": "ComponentTreeWrapper",
"name": "ComponentTreeWrapper",
"category": "custom",
"canHaveChildren": true,
"description": "JSON wrapper for a props-driven component tree view",
"status": "json-compatible",
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "ComponentTree"
},
{
"type": "ComponentTreeNode",
@@ -1564,10 +1668,23 @@
"name": "SaveIndicator",
"category": "custom",
"canHaveChildren": true,
"description": "Save status indicator with hook-driven state",
"status": "supported",
"source": "molecules",
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "SaveIndicatorWrapper"
},
{
"type": "SaveIndicatorWrapper",
"name": "SaveIndicatorWrapper",
"category": "custom",
"canHaveChildren": true,
"description": "JSON wrapper for save status indicator with pure-props API",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "SaveIndicator"
},
{
"type": "SchemaEditorCanvas",
@@ -1715,10 +1832,23 @@
"name": "StorageSettings",
"category": "custom",
"canHaveChildren": true,
"description": "Storage settings controls with hook-driven state",
"status": "supported",
"source": "molecules",
"jsonCompatible": false,
"wrapperRequired": true,
"wrapperComponent": "StorageSettingsWrapper"
},
{
"type": "StorageSettingsWrapper",
"name": "StorageSettingsWrapper",
"category": "custom",
"canHaveChildren": true,
"description": "JSON wrapper for storage settings controls with props-driven state",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
"source": "wrappers",
"jsonCompatible": true,
"wrapperFor": "StorageSettings"
},
{
"type": "Timestamp",

View File

@@ -0,0 +1,9 @@
import { PageRenderer } from '@/lib/json-ui/page-renderer'
import conversionShowcaseSchema from '@/config/pages/json-conversion-showcase.json'
import { PageSchema } from '@/types/json-ui'
export function JSONConversionShowcase() {
const schema = conversionShowcaseSchema as PageSchema
return <PageRenderer schema={schema} />
}

View File

@@ -6,9 +6,8 @@ import { DataSourceIdField } from '@/components/molecules/data-source-editor/Dat
import { KvSourceFields } from '@/components/molecules/data-source-editor/KvSourceFields'
import { StaticSourceFields } from '@/components/molecules/data-source-editor/StaticSourceFields'
import { ComputedSourceFields } from '@/components/molecules/data-source-editor/ComputedSourceFields'
import { useDataSourceEditor } from '@/hooks/data/use-data-source-editor'
import dataSourceEditorCopy from '@/data/data-source-editor-dialog.json'
import { useDataSourceEditor } from '@/hooks/use-data-source-editor'
import { useDataSourceEditor } from '@/hooks/data/use-data-source-editor'
interface DataSourceEditorDialogProps {
open: boolean

View File

@@ -235,6 +235,16 @@
"type": "single"
}
},
{
"id": "json-conversion-showcase",
"title": "JSON Conversion Showcase",
"description": "JSON conversion showcase overview",
"icon": "BookOpen",
"component": "JSONConversionShowcase",
"layout": {
"type": "single"
}
},
{
"id": "sass",
"title": "Sass Styles",

View File

@@ -27,6 +27,7 @@ import { PWASettings } from '@/components/PWASettings'
import { FaviconDesigner } from '@/components/FaviconDesigner'
import { FeatureIdeaCloud } from '@/components/FeatureIdeaCloud'
import { JSONUIShowcase } from '@/components/JSONUIShowcase'
import { JSONConversionShowcase } from '@/components/JSONConversionShowcase'
export const ComponentRegistry: Record<string, ComponentType<any>> = {
Button,
@@ -61,6 +62,7 @@ export const ComponentRegistry: Record<string, ComponentType<any>> = {
FaviconDesigner,
FeatureIdeaCloud,
JSONUIShowcase,
JSONConversionShowcase,
}
export function getComponent(name: string): ComponentType<any> | null {

View File

@@ -365,6 +365,15 @@
"order": 22,
"props": {}
},
{
"id": "json-conversion-showcase",
"title": "JSON Conversion Showcase",
"icon": "BookOpen",
"component": "JSONConversionShowcase",
"enabled": true,
"order": 22.1,
"props": {}
},
{
"id": "schema-editor",
"title": "Schema Editor",

View File

@@ -0,0 +1,341 @@
{
"id": "json-conversion-showcase",
"name": "JSON Conversion Showcase",
"layout": {
"type": "single"
},
"dataSources": [
{
"id": "batches",
"type": "static",
"defaultValue": [
{
"title": "Phase 1",
"summary": "Models, Component Trees, Workflows",
"stats": [
{ "label": "Pages Converted", "value": "3" },
{ "label": "JSON Schema Lines", "value": "~900" },
{ "label": "Code Reduction", "value": "~60%" }
],
"pages": [
{
"name": "Models Designer",
"pageId": "models-json",
"schema": "src/config/pages/model-designer.json",
"dataStore": "app-models"
},
{
"name": "Component Trees Manager",
"pageId": "component-trees-json",
"schema": "src/config/pages/component-tree.json",
"dataStore": "app-component-trees"
},
{
"name": "Workflows Designer",
"pageId": "workflows-json",
"schema": "src/config/pages/workflow-designer.json",
"dataStore": "app-workflows"
}
],
"seedHighlights": [
"3 model records (User, Post, Comment)",
"2 component trees (Dashboard, Profile)",
"3 workflow definitions (Registration, Processing, Payment)"
]
},
{
"title": "Phase 2",
"summary": "Lambdas, Styling, Flask API",
"stats": [
{ "label": "Pages Converted", "value": "3" },
{ "label": "JSON Schema Lines", "value": "~2,100" },
{ "label": "Seed Records", "value": "8" }
],
"pages": [
{
"name": "Lambda Designer",
"pageId": "lambdas-json",
"schema": "src/config/pages/lambda-designer.json",
"dataStore": "app-lambdas"
},
{
"name": "Style Designer",
"pageId": "styling-json",
"schema": "src/config/pages/style-designer.json",
"dataStore": "app-theme"
},
{
"name": "Flask API Designer",
"pageId": "flask-json",
"schema": "src/config/pages/flask-designer.json",
"dataStore": "app-flask-config"
}
],
"seedHighlights": [
"3 serverless functions seeded with triggers",
"2 theme variants with custom palettes",
"3 Flask blueprints covering 7 endpoints"
]
}
]
},
{
"id": "patterns",
"type": "static",
"defaultValue": [
"Sidebar + detail layout pattern",
"Computed counters for live totals",
"KV-backed data sources for persistence",
"Declarative empty states and actions"
]
}
],
"components": [
{
"id": "root",
"type": "div",
"props": {
"className": "h-full overflow-auto bg-background p-6"
},
"children": [
{
"id": "header",
"type": "div",
"props": { "className": "space-y-2" },
"children": [
{
"id": "title",
"type": "Heading",
"props": {
"className": "text-3xl font-bold",
"children": "JSON Conversion Showcase"
}
},
{
"id": "subtitle",
"type": "Text",
"props": {
"className": "text-muted-foreground",
"children": "One-page overview of JSON conversion phases and their deliverables."
}
}
]
},
{
"id": "phase-grid",
"type": "div",
"props": { "className": "mt-8 space-y-8" },
"loop": {
"source": "batches",
"itemVar": "batch"
},
"children": [
{
"id": "phase-card",
"type": "Card",
"children": [
{
"id": "phase-header",
"type": "CardHeader",
"children": [
{
"id": "phase-title",
"type": "CardTitle",
"bindings": {
"children": { "source": "batch", "sourceType": "bindings", "path": "title" }
}
},
{
"id": "phase-description",
"type": "CardDescription",
"bindings": {
"children": { "source": "batch", "sourceType": "bindings", "path": "summary" }
}
}
]
},
{
"id": "phase-content",
"type": "CardContent",
"props": { "className": "space-y-6" },
"children": [
{
"id": "stats-grid",
"type": "div",
"props": {
"className": "grid gap-3 md:grid-cols-3"
},
"loop": {
"source": "batch.stats",
"itemVar": "stat"
},
"children": [
{
"id": "stat-item",
"type": "div",
"props": { "className": "rounded-lg border border-border p-3" },
"children": [
{
"id": "stat-label",
"type": "Text",
"props": { "className": "text-xs text-muted-foreground" },
"bindings": {
"children": { "source": "stat", "sourceType": "bindings", "path": "label" }
}
},
{
"id": "stat-value",
"type": "Text",
"props": { "className": "text-lg font-semibold" },
"bindings": {
"children": { "source": "stat", "sourceType": "bindings", "path": "value" }
}
}
]
}
]
},
{
"id": "pages-section",
"type": "div",
"props": { "className": "space-y-3" },
"children": [
{
"id": "pages-title",
"type": "Heading",
"props": { "className": "text-base font-semibold", "children": "Converted Pages" }
},
{
"id": "pages-grid",
"type": "div",
"props": { "className": "grid gap-3 md:grid-cols-3" },
"loop": {
"source": "batch.pages",
"itemVar": "page"
},
"children": [
{
"id": "page-item",
"type": "Card",
"children": [
{
"id": "page-item-header",
"type": "CardHeader",
"children": [
{
"id": "page-item-title",
"type": "CardTitle",
"bindings": {
"children": { "source": "page", "sourceType": "bindings", "path": "name" }
}
},
{
"id": "page-item-description",
"type": "CardDescription",
"bindings": {
"children": { "source": "page", "sourceType": "bindings", "path": "schema" }
}
}
]
},
{
"id": "page-item-content",
"type": "CardContent",
"props": { "className": "space-y-2" },
"children": [
{
"id": "page-id",
"type": "Badge",
"bindings": {
"children": { "source": "page", "sourceType": "bindings", "path": "pageId" }
}
},
{
"id": "page-store",
"type": "Text",
"props": { "className": "text-xs text-muted-foreground" },
"bindings": {
"children": { "source": "page", "sourceType": "bindings", "path": "dataStore" }
}
}
]
}
]
}
]
}
]
},
{
"id": "seed-section",
"type": "div",
"props": { "className": "space-y-2" },
"children": [
{
"id": "seed-title",
"type": "Heading",
"props": { "className": "text-base font-semibold", "children": "Seed Data Highlights" }
},
{
"id": "seed-list",
"type": "div",
"props": { "className": "space-y-1" },
"loop": {
"source": "batch.seedHighlights",
"itemVar": "seed"
},
"children": [
{
"id": "seed-item",
"type": "Text",
"props": { "className": "text-sm" },
"bindings": {
"children": { "source": "seed", "sourceType": "bindings" }
}
}
]
}
]
}
]
}
]
}
]
},
{
"id": "patterns-section",
"type": "div",
"props": { "className": "mt-8" },
"children": [
{
"id": "patterns-title",
"type": "Heading",
"props": { "className": "text-xl font-semibold", "children": "Reusable Patterns" }
},
{
"id": "patterns-list",
"type": "div",
"props": { "className": "mt-3 space-y-2" },
"loop": {
"source": "patterns",
"itemVar": "pattern"
},
"children": [
{
"id": "pattern-item",
"type": "Text",
"props": { "className": "text-sm" },
"bindings": {
"children": { "source": "pattern", "sourceType": "bindings" }
}
}
]
}
]
}
]
}
],
"globalActions": []
}

View File

@@ -199,14 +199,20 @@
"label": "Progress Bar",
"category": "display",
"icon": "ChartBar",
"defaultProps": { "value": 65, "size": "md", "variant": "default", "showLabel": false }
"defaultProps": {
"value": 65,
"max": 100,
"size": "md",
"variant": "default",
"showLabel": false
}
},
{
"type": "CircularProgress",
"label": "Circular Progress",
"category": "display",
"icon": "CircleNotch",
"defaultProps": { "value": 65, "size": "md", "showLabel": true }
"defaultProps": { "value": 65, "max": 100, "size": "md", "showLabel": true }
},
{
"type": "Spinner",
@@ -234,7 +240,7 @@
"label": "Divider",
"category": "display",
"icon": "Minus",
"defaultProps": { "orientation": "horizontal", "decorative": true }
"defaultProps": { "orientation": "horizontal", "decorative": true, "className": "" }
},
{
"type": "Link",

View File

@@ -17,6 +17,7 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
import { Skeleton as ShadcnSkeleton } from '@/components/ui/skeleton'
import { Progress } from '@/components/ui/progress'
import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { CircularProgress, Divider, ProgressBar } from '@/components/atoms'
import * as AtomComponents from '@/components/atoms'
import * as MoleculeComponents from '@/components/molecules'
import * as OrganismComponents from '@/components/organisms'
@@ -178,10 +179,13 @@ export const atomComponents: UIComponentRegistry = {
),
DatePicker: atomComponentMap.DatePicker,
FileUpload: atomComponentMap.FileUpload,
DataList: atomComponentMap.DataList,
DataTable: atomComponentMap.DataTable,
MetricCard: atomComponentMap.MetricCard,
Timeline: atomComponentMap.Timeline,
CircularProgress,
Divider,
ProgressBar,
DataList: (AtomComponents as Record<string, ComponentType<any>>).DataList,
DataTable: (AtomComponents as Record<string, ComponentType<any>>).DataTable,
MetricCard: (AtomComponents as Record<string, ComponentType<any>>).MetricCard,
Timeline: (AtomComponents as Record<string, ComponentType<any>>).Timeline,
}
const breadcrumbComponent = AtomComponents.Breadcrumb ?? AtomComponents.BreadcrumbNav
@@ -264,12 +268,20 @@ export function registerComponent(name: string, component: ComponentType<any>) {
uiComponentRegistry[name] = component
}
const resolveWrapperComponent = (type: string): ComponentType<any> | null => {
const entry = registryEntryByType.get(type)
if (entry?.wrapperRequired && entry.wrapperComponent) {
return uiComponentRegistry[entry.wrapperComponent] || null
}
return null
}
export function getUIComponent(type: string): ComponentType<any> | string | null {
return uiComponentRegistry[type] || null
return resolveWrapperComponent(type) ?? uiComponentRegistry[type] ?? null
}
export function hasComponent(type: string): boolean {
return type in uiComponentRegistry
return Boolean(resolveWrapperComponent(type) ?? uiComponentRegistry[type])
}
export function getDeprecatedComponentInfo(type: string): DeprecatedComponentInfo | null {

View File

@@ -205,6 +205,11 @@ export function ComponentRenderer({ component, data, context = {}, state, onEven
return null
}
const resolvedChildren = component.children ?? resolvedProps.children
if (resolvedChildren !== undefined && resolvedChildren !== component.children) {
delete resolvedProps.children
}
if (component.loop) {
const items = resolveDataBinding(component.loop.source, data, context, { state, bindings: context }) || []
const loopChildren = items.map((item: unknown, index: number) => {
@@ -232,7 +237,7 @@ export function ComponentRenderer({ component, data, context = {}, state, onEven
return (
<Fragment key={`${component.id}-${index}`}>
{renderChildren(component.children, loopContext)}
{renderChildren(resolvedChildren as UIComponent[] | string | undefined, loopContext)}
</Fragment>
)
})
@@ -254,5 +259,9 @@ export function ComponentRenderer({ component, data, context = {}, state, onEven
}
}
return createElement(Component, resolvedProps, renderChildren(component.children, context))
return createElement(
Component,
resolvedProps,
renderChildren(resolvedChildren as UIComponent[] | string | undefined, context)
)
}

View File

@@ -0,0 +1,47 @@
import type { ChangeEvent } from 'react'
import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
import { cn } from '@/lib/utils'
import componentBindingDialogDefinition from './definitions/component-binding-dialog.json'
import type { ComponentBindingDialogWrapperProps } from './interfaces'
export function ComponentBindingDialogWrapper({
open = false,
title = 'Component Bindings',
description = 'Connect component props to data sources.',
componentType,
componentId,
bindings = [],
onBindingChange,
onSave,
onCancel,
onOpenChange,
className,
}: ComponentBindingDialogWrapperProps) {
const handleBindingFieldChange = (event: ChangeEvent<HTMLInputElement>) => {
const fieldId = event.currentTarget?.dataset?.fieldId || event.target?.dataset?.fieldId
if (!fieldId) return
onBindingChange?.(fieldId, event.target.value)
}
return (
<ComponentRenderer
component={componentBindingDialogDefinition}
data={{
open,
title,
description,
componentType,
componentId,
bindingFields: bindings,
emptyMessage: 'No bindings configured.',
contentClassName: cn('max-w-2xl', className),
onBindingFieldChange: handleBindingFieldChange,
onSave,
onCancel,
onOpenChange,
cancelLabel: 'Cancel',
saveLabel: 'Save',
}}
/>
)
}

View File

@@ -0,0 +1,55 @@
import { cn } from '@/lib/utils'
import type { UIComponent } from '@/types/json-ui'
import type { ComponentTreeWrapperProps } from './interfaces'
const renderTreeNodes = (
components: UIComponent[],
depth: number,
selectedId: string | null,
onSelect?: (id: string) => void
) => {
return components.map((component) => {
const hasChildren = Array.isArray(component.children) && component.children.length > 0
const isSelected = selectedId === component.id
return (
<div key={component.id}>
<button
type="button"
onClick={() => onSelect?.(component.id)}
className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1 text-left text-sm transition-colors',
isSelected ? 'bg-accent/40 text-foreground' : 'hover:bg-muted'
)}
style={{ paddingLeft: `${depth * 16 + 8}px` }}
>
<span className="font-medium">{component.type}</span>
<span className="text-xs text-muted-foreground">{component.id}</span>
</button>
{hasChildren && (
<div className="mt-1">
{renderTreeNodes(component.children as UIComponent[], depth + 1, selectedId, onSelect)}
</div>
)}
</div>
)
})
}
export function ComponentTreeWrapper({
components = [],
selectedId = null,
emptyMessage = 'No components available.',
onSelect,
className,
}: ComponentTreeWrapperProps) {
return (
<div className={cn('space-y-2', className)}>
{components.length === 0 ? (
<p className="text-sm text-muted-foreground">{emptyMessage}</p>
) : (
<div className="space-y-1">{renderTreeNodes(components, 0, selectedId, onSelect)}</div>
)}
</div>
)
}

View File

@@ -0,0 +1,43 @@
import type { ChangeEvent } from 'react'
import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
import { cn } from '@/lib/utils'
import dataSourceEditorDialogDefinition from './definitions/data-source-editor-dialog.json'
import type { DataSourceEditorDialogWrapperProps } from './interfaces'
export function DataSourceEditorDialogWrapper({
open = false,
title = 'Data Source',
description = 'Update data source details and fields.',
fields = [],
onFieldChange,
onSave,
onCancel,
onOpenChange,
className,
}: DataSourceEditorDialogWrapperProps) {
const handleFieldChange = (event: ChangeEvent<HTMLInputElement>) => {
const fieldId = event.currentTarget?.dataset?.fieldId || event.target?.dataset?.fieldId
if (!fieldId) return
onFieldChange?.(fieldId, event.target.value)
}
return (
<ComponentRenderer
component={dataSourceEditorDialogDefinition}
data={{
open,
title,
description,
fields,
emptyMessage: 'No fields configured.',
contentClassName: cn('max-w-2xl', className),
onFieldChange: handleFieldChange,
onSave,
onCancel,
onOpenChange,
cancelLabel: 'Cancel',
saveLabel: 'Save',
}}
/>
)
}

View File

@@ -0,0 +1,64 @@
import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
import { cn } from '@/lib/utils'
import gitHubBuildStatusDefinition from './definitions/github-build-status.json'
import type { GitHubBuildStatusWrapperProps, GitHubBuildStatusWorkflowItem } from './interfaces'
const getWorkflowStatus = (workflow: GitHubBuildStatusWorkflowItem) => {
if (workflow.status === 'completed') {
if (workflow.conclusion === 'success') {
return {
label: 'Success',
className: 'bg-green-500/10 text-green-600 border-green-500/20',
}
}
if (workflow.conclusion === 'failure') {
return { label: 'Failed', className: 'bg-red-500/10 text-red-600 border-red-500/20' }
}
if (workflow.conclusion === 'cancelled') {
return { label: 'Cancelled', className: 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20' }
}
}
return { label: 'Running', className: 'border-blue-500/50 text-blue-500' }
}
export function GitHubBuildStatusWrapper({
title = 'GitHub Build Status',
description = 'Latest workflow runs and status badges.',
workflows = [],
isLoading = false,
errorMessage,
emptyMessage = 'No workflows to display yet.',
footerLinkLabel = 'View on GitHub',
footerLinkUrl,
className,
}: GitHubBuildStatusWrapperProps) {
const normalizedWorkflows = workflows.map((workflow) => {
const status = getWorkflowStatus(workflow)
return {
...workflow,
statusLabel: status.label,
statusClass: status.className,
summaryLine: [workflow.branch, workflow.updatedAt, workflow.event].filter(Boolean).join(' • '),
}
})
return (
<ComponentRenderer
component={gitHubBuildStatusDefinition}
data={{
title,
description,
workflows: normalizedWorkflows,
isLoading,
errorMessage,
emptyMessage,
loadingMessage: 'Loading workflows…',
hasWorkflows: normalizedWorkflows.length > 0,
footerLinkLabel,
footerLinkUrl,
className: cn(className),
}}
/>
)
}

View File

@@ -0,0 +1,200 @@
{
"id": "component-binding-dialog",
"type": "Dialog",
"bindings": {
"open": "open",
"onOpenChange": "onOpenChange"
},
"children": [
{
"id": "component-binding-dialog-content",
"type": "DialogContent",
"bindings": {
"className": "contentClassName"
},
"children": [
{
"id": "component-binding-dialog-header",
"type": "DialogHeader",
"children": [
{
"id": "component-binding-dialog-title",
"type": "DialogTitle",
"bindings": {
"children": "title"
}
},
{
"id": "component-binding-dialog-description",
"type": "DialogDescription",
"bindings": {
"children": "description"
}
}
]
},
{
"id": "component-binding-dialog-info",
"type": "div",
"props": {
"className": "rounded-md border border-border bg-muted/30 p-3 text-sm"
},
"conditional": {
"if": "componentType || componentId"
},
"children": [
{
"id": "component-binding-dialog-type",
"type": "div",
"props": {
"className": "flex items-center gap-2"
},
"conditional": {
"if": "componentType"
},
"children": [
{
"id": "component-binding-dialog-type-label",
"type": "span",
"props": {
"className": "text-muted-foreground"
},
"children": "Component:"
},
{
"id": "component-binding-dialog-type-value",
"type": "span",
"props": {
"className": "font-mono font-medium"
},
"bindings": {
"children": "componentType"
}
}
]
},
{
"id": "component-binding-dialog-id",
"type": "div",
"props": {
"className": "flex items-center gap-2"
},
"conditional": {
"if": "componentId"
},
"children": [
{
"id": "component-binding-dialog-id-label",
"type": "span",
"props": {
"className": "text-muted-foreground"
},
"children": "ID:"
},
{
"id": "component-binding-dialog-id-value",
"type": "span",
"props": {
"className": "font-mono text-xs"
},
"bindings": {
"children": "componentId"
}
}
]
}
]
},
{
"id": "component-binding-dialog-body",
"type": "div",
"props": {
"className": "space-y-4"
},
"children": [
{
"id": "component-binding-dialog-empty",
"type": "p",
"props": {
"className": "text-sm text-muted-foreground"
},
"bindings": {
"children": "emptyMessage"
},
"conditional": {
"if": "!bindingFields || bindingFields.length === 0"
}
},
{
"id": "component-binding-dialog-fields",
"type": "div",
"props": {
"className": "space-y-4"
},
"conditional": {
"if": "bindingFields && bindingFields.length > 0"
},
"loop": {
"source": "bindingFields",
"itemVar": "field"
},
"children": [
{
"id": "component-binding-dialog-field",
"type": "div",
"props": {
"className": "space-y-2"
},
"children": [
{
"id": "component-binding-dialog-field-label",
"type": "Label",
"bindings": {
"children": "field.label"
}
},
{
"id": "component-binding-dialog-field-input",
"type": "Input",
"bindings": {
"value": "field.value",
"placeholder": "field.placeholder",
"onChange": "onBindingFieldChange",
"data-field-id": "field.id"
}
}
]
}
]
}
]
},
{
"id": "component-binding-dialog-footer",
"type": "DialogFooter",
"children": [
{
"id": "component-binding-dialog-cancel",
"type": "Button",
"props": {
"variant": "outline"
},
"bindings": {
"onClick": "onCancel",
"children": "cancelLabel"
}
},
{
"id": "component-binding-dialog-save",
"type": "Button",
"bindings": {
"onClick": "onSave",
"children": "saveLabel"
}
}
]
}
]
}
]
}

View File

@@ -0,0 +1,141 @@
{
"id": "data-source-editor-dialog",
"type": "Dialog",
"bindings": {
"open": "open",
"onOpenChange": "onOpenChange"
},
"children": [
{
"id": "data-source-editor-dialog-content",
"type": "DialogContent",
"bindings": {
"className": "contentClassName"
},
"children": [
{
"id": "data-source-editor-dialog-header",
"type": "DialogHeader",
"children": [
{
"id": "data-source-editor-dialog-title",
"type": "DialogTitle",
"bindings": {
"children": "title"
}
},
{
"id": "data-source-editor-dialog-description",
"type": "DialogDescription",
"bindings": {
"children": "description"
}
}
]
},
{
"id": "data-source-editor-dialog-body",
"type": "div",
"props": {
"className": "space-y-4"
},
"children": [
{
"id": "data-source-editor-dialog-empty",
"type": "p",
"props": {
"className": "text-sm text-muted-foreground"
},
"bindings": {
"children": "emptyMessage"
},
"conditional": {
"if": "!fields || fields.length === 0"
}
},
{
"id": "data-source-editor-dialog-fields",
"type": "div",
"props": {
"className": "space-y-4"
},
"conditional": {
"if": "fields && fields.length > 0"
},
"loop": {
"source": "fields",
"itemVar": "field"
},
"children": [
{
"id": "data-source-editor-dialog-field",
"type": "div",
"props": {
"className": "space-y-2"
},
"children": [
{
"id": "data-source-editor-dialog-field-label",
"type": "Label",
"bindings": {
"children": "field.label"
}
},
{
"id": "data-source-editor-dialog-field-input",
"type": "Input",
"bindings": {
"value": "field.value",
"placeholder": "field.placeholder",
"onChange": "onFieldChange",
"data-field-id": "field.id"
}
},
{
"id": "data-source-editor-dialog-field-helper",
"type": "p",
"props": {
"className": "text-xs text-muted-foreground"
},
"bindings": {
"children": "field.helperText"
},
"conditional": {
"if": "field.helperText"
}
}
]
}
]
}
]
},
{
"id": "data-source-editor-dialog-footer",
"type": "DialogFooter",
"children": [
{
"id": "data-source-editor-dialog-cancel",
"type": "Button",
"props": {
"variant": "outline"
},
"bindings": {
"onClick": "onCancel",
"children": "cancelLabel"
}
},
{
"id": "data-source-editor-dialog-save",
"type": "Button",
"bindings": {
"onClick": "onSave",
"children": "saveLabel"
}
}
]
}
]
}
]
}

View File

@@ -0,0 +1,210 @@
{
"id": "github-build-status-card",
"type": "Card",
"bindings": {
"className": "className"
},
"children": [
{
"id": "github-build-status-header",
"type": "CardHeader",
"children": [
{
"id": "github-build-status-title",
"type": "CardTitle",
"props": {
"className": "flex items-center gap-2"
},
"bindings": {
"children": "title"
}
},
{
"id": "github-build-status-description",
"type": "CardDescription",
"bindings": {
"children": "description"
}
}
]
},
{
"id": "github-build-status-content",
"type": "CardContent",
"props": {
"className": "space-y-4"
},
"children": [
{
"id": "github-build-status-loading",
"type": "p",
"props": {
"className": "text-sm text-muted-foreground"
},
"bindings": {
"children": "loadingMessage"
},
"conditional": {
"if": "isLoading"
}
},
{
"id": "github-build-status-error",
"type": "div",
"props": {
"className": "flex items-center gap-2 text-sm text-red-500"
},
"conditional": {
"if": "errorMessage"
},
"children": [
{
"id": "github-build-status-error-text",
"type": "span",
"bindings": {
"children": "errorMessage"
}
}
]
},
{
"id": "github-build-status-empty",
"type": "p",
"props": {
"className": "text-sm text-muted-foreground"
},
"bindings": {
"children": "emptyMessage"
},
"conditional": {
"if": "!isLoading && !errorMessage && !hasWorkflows"
}
},
{
"id": "github-build-status-list",
"type": "div",
"props": {
"className": "space-y-3"
},
"conditional": {
"if": "hasWorkflows"
},
"loop": {
"source": "workflows",
"itemVar": "workflow"
},
"children": [
{
"id": "github-build-status-item",
"type": "div",
"props": {
"className": "flex items-center justify-between gap-3 rounded-lg border border-border p-3"
},
"children": [
{
"id": "github-build-status-item-info",
"type": "div",
"props": {
"className": "min-w-0"
},
"children": [
{
"id": "github-build-status-item-row",
"type": "div",
"props": {
"className": "flex items-center gap-2"
},
"children": [
{
"id": "github-build-status-item-name",
"type": "p",
"props": {
"className": "text-sm font-medium truncate"
},
"bindings": {
"children": "workflow.name"
}
},
{
"id": "github-build-status-item-badge",
"type": "Badge",
"bindings": {
"className": "workflow.statusClass",
"children": "workflow.statusLabel"
}
}
]
},
{
"id": "github-build-status-item-meta",
"type": "div",
"props": {
"className": "text-xs text-muted-foreground truncate"
},
"bindings": {
"children": "workflow.summaryLine"
}
}
]
},
{
"id": "github-build-status-item-link",
"type": "Button",
"props": {
"variant": "ghost",
"size": "sm",
"asChild": true
},
"conditional": {
"if": "workflow.url"
},
"children": [
{
"id": "github-build-status-item-anchor",
"type": "a",
"bindings": {
"href": "workflow.url"
},
"props": {
"target": "_blank",
"rel": "noopener noreferrer"
},
"children": "View"
}
]
}
]
}
]
},
{
"id": "github-build-status-footer",
"type": "Button",
"props": {
"variant": "outline",
"size": "sm",
"asChild": true,
"className": "w-full"
},
"conditional": {
"if": "footerLinkUrl"
},
"children": [
{
"id": "github-build-status-footer-anchor",
"type": "a",
"bindings": {
"href": "footerLinkUrl",
"children": "footerLinkLabel"
},
"props": {
"target": "_blank",
"rel": "noopener noreferrer"
}
}
]
}
]
}
]
}

View File

@@ -5,3 +5,7 @@ export { LazyLineChartWrapper } from './LazyLineChartWrapper'
export { LazyD3BarChartWrapper } from './LazyD3BarChartWrapper'
export { SeedDataManagerWrapper } from './SeedDataManagerWrapper'
export { StorageSettingsWrapper } from './StorageSettingsWrapper'
export { GitHubBuildStatusWrapper } from './GitHubBuildStatusWrapper'
export { ComponentBindingDialogWrapper } from './ComponentBindingDialogWrapper'
export { DataSourceEditorDialogWrapper } from './DataSourceEditorDialogWrapper'
export { ComponentTreeWrapper } from './ComponentTreeWrapper'

View File

@@ -1,4 +1,5 @@
import type { StorageBackendKey } from '@/components/storage/storageSettingsConfig'
import type { UIComponent } from '@/types/json-ui'
export interface BreadcrumbItem {
label: string
@@ -89,3 +90,75 @@ export interface StorageSettingsWrapperProps {
onExport?: () => void
onImport?: () => void
}
export interface GitHubBuildStatusWorkflowItem {
id: string
name: string
status?: string
conclusion?: string | null
branch?: string
updatedAt?: string
event?: string
url?: string
}
export interface GitHubBuildStatusWrapperProps {
title?: string
description?: string
workflows?: GitHubBuildStatusWorkflowItem[]
isLoading?: boolean
errorMessage?: string
emptyMessage?: string
footerLinkLabel?: string
footerLinkUrl?: string
className?: string
}
export interface ComponentBindingField {
id: string
label: string
value?: string
placeholder?: string
}
export interface ComponentBindingDialogWrapperProps {
open?: boolean
title?: string
description?: string
componentType?: string
componentId?: string
bindings?: ComponentBindingField[]
onBindingChange?: (id: string, value: string) => void
onSave?: () => void
onCancel?: () => void
onOpenChange?: (open: boolean) => void
className?: string
}
export interface DataSourceField {
id: string
label: string
value?: string
placeholder?: string
helperText?: string
}
export interface DataSourceEditorDialogWrapperProps {
open?: boolean
title?: string
description?: string
fields?: DataSourceField[]
onFieldChange?: (id: string, value: string) => void
onSave?: () => void
onCancel?: () => void
onOpenChange?: (open: boolean) => void
className?: string
}
export interface ComponentTreeWrapperProps {
components?: UIComponent[]
selectedId?: string | null
emptyMessage?: string
onSelect?: (id: string) => void
className?: string
}

View File

@@ -153,6 +153,11 @@ export const tabInfo: Record<string, TabInfo> = {
icon: <Code size={24} weight="duotone" />,
description: 'JSON-driven UI examples',
},
'json-conversion-showcase': {
title: 'JSON Conversion Showcase',
icon: <BookOpen size={24} weight="duotone" />,
description: 'JSON conversion showcase overview',
},
'atomic-library': {
title: 'Atomic Components',
icon: <Atom size={24} weight="duotone" />,
@@ -332,6 +337,12 @@ export const navigationGroups: NavigationGroup[] = [
value: 'docs',
featureKey: 'documentation',
},
{
id: 'json-conversion-showcase',
label: 'JSON Conversion Showcase',
icon: <BookOpen size={18} />,
value: 'json-conversion-showcase',
},
{
id: 'settings',
label: 'Settings',

View File

@@ -5,15 +5,54 @@
"type": "single"
},
"dataSources": [
{
"id": "appTitle",
"type": "static",
"defaultValue": "Nova Workspace"
},
{
"id": "appSubtitle",
"type": "static",
"defaultValue": "Operational Control Center"
},
{
"id": "itemCount",
"type": "static",
"defaultValue": 42
},
{
"id": "overdueCount",
"type": "static",
"defaultValue": 3
},
{
"id": "isLoading",
"type": "static",
"defaultValue": false
},
{
"id": "loadingMessage",
"type": "static",
"defaultValue": "Syncing workspace assets..."
},
{
"id": "loadingSize",
"type": "static",
"defaultValue": "lg"
},
{
"id": "fallbackMessage",
"type": "static",
"defaultValue": "Fetching editor state..."
},
{
"id": "navGroup",
"type": "static",
"defaultValue": {
"label": "Workspace",
"count": 12,
"isExpanded": true
}
}
],
"components": [
@@ -80,9 +119,9 @@
{
"id": "branding-demo",
"type": "AppBranding",
"props": {
"title": "My Amazing App",
"subtitle": "Built with JSON-Powered Components"
"bindings": {
"title": { "source": "appTitle" },
"subtitle": { "source": "appSubtitle" }
}
}
]
@@ -118,7 +157,8 @@
"id": "label-badge-demo-1",
"type": "LabelWithBadge",
"props": {
"label": "Total Items"
"label": "Total Items",
"badgeVariant": "secondary"
},
"bindings": {
"badge": { "source": "itemCount" }
@@ -128,9 +168,11 @@
"id": "label-badge-demo-2",
"type": "LabelWithBadge",
"props": {
"label": "Warning",
"badge": "3",
"label": "Overdue Tasks",
"badgeVariant": "destructive"
},
"bindings": {
"badge": { "source": "overdueCount" }
}
},
{
@@ -212,8 +254,8 @@
{
"id": "loading-fallback-demo",
"type": "LoadingFallback",
"props": {
"message": "Loading your data..."
"bindings": {
"message": { "source": "fallbackMessage" }
}
}
]
@@ -251,16 +293,18 @@
"id": "loading-state-demo",
"type": "LoadingState",
"props": {
"message": "Processing request...",
"size": "sm"
},
"bindings": {
"message": { "source": "loadingMessage" }
}
},
{
"id": "loading-state-demo-lg",
"type": "LoadingState",
"props": {
"message": "Syncing workspace...",
"size": "lg"
"bindings": {
"message": { "source": "loadingMessage" },
"size": { "source": "loadingSize" }
}
}
]
@@ -295,10 +339,10 @@
{
"id": "nav-header-demo",
"type": "NavigationGroupHeader",
"props": {
"label": "Components",
"count": 24,
"isExpanded": true
"bindings": {
"label": { "source": "navGroup", "path": "label" },
"count": { "source": "navGroup", "path": "count" },
"isExpanded": { "source": "navGroup", "path": "isExpanded" }
}
}
]

View File

@@ -208,5 +208,81 @@
]
}
]
},
"progressComponentsDemoSchema": {
"id": "progress-components-demo",
"name": "Progress Components Demo",
"layout": {
"type": "single"
},
"dataSources": [
{
"id": "progressValues",
"type": "static",
"defaultValue": {
"linear": 72,
"circular": 48,
"max": 100
}
}
],
"components": [
{
"id": "progress-components-root",
"type": "div",
"props": {
"className": "space-y-6 rounded-lg border border-border bg-card p-6"
},
"children": [
{
"id": "progress-components-title",
"type": "Heading",
"props": {
"className": "text-xl font-semibold",
"children": "Progress Indicators"
}
},
{
"id": "progress-components-divider-top",
"type": "Divider",
"props": {
"orientation": "horizontal"
}
},
{
"id": "progress-components-linear",
"type": "ProgressBar",
"props": {
"size": "md",
"variant": "accent",
"showLabel": true
},
"bindings": {
"value": { "source": "progressValues", "sourceType": "data", "path": "linear" },
"max": { "source": "progressValues", "sourceType": "data", "path": "max" }
}
},
{
"id": "progress-components-divider-middle",
"type": "Divider",
"props": {
"orientation": "horizontal"
}
},
{
"id": "progress-components-circular",
"type": "CircularProgress",
"props": {
"size": "md",
"showLabel": true
},
"bindings": {
"value": { "source": "progressValues", "sourceType": "data", "path": "circular" },
"max": { "source": "progressValues", "sourceType": "data", "path": "max" }
}
}
]
}
]
}
}