mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 21:54:56 +00:00
Merge branch 'main' into codex/update-json-ui-components-and-registry-u03e4j
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
16
e2e/visual-regression.spec.ts
Normal file
16
e2e/visual-regression.spec.ts
Normal 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 })
|
||||
})
|
||||
})
|
||||
@@ -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",
|
||||
|
||||
9
src/components/JSONConversionShowcase.tsx
Normal file
9
src/components/JSONConversionShowcase.tsx
Normal 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} />
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
341
src/config/pages/json-conversion-showcase.json
Normal file
341
src/config/pages/json-conversion-showcase.json
Normal 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": []
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
47
src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx
Normal file
47
src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx
Normal 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',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
55
src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx
Normal file
55
src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
43
src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx
Normal file
43
src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx
Normal 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',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
64
src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx
Normal file
64
src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx
Normal 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),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
210
src/lib/json-ui/wrappers/definitions/github-build-status.json
Normal file
210
src/lib/json-ui/wrappers/definitions/github-build-status.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user