From c345e892f945d58f23d3e42d78fb16eedb3a0c3f Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 12:38:39 +0000 Subject: [PATCH 1/7] Add JSON wrapper components for hook-based UI --- json-components-registry.json | 170 +++++++++++++++--- src/lib/json-ui/component-registry.ts | 19 ++ .../ComponentBindingDialogWrapper.tsx | 75 ++++++++ .../json-ui/wrappers/ComponentTreeWrapper.tsx | 55 ++++++ .../DataSourceEditorDialogWrapper.tsx | 59 ++++++ .../wrappers/GitHubBuildStatusWrapper.tsx | 127 +++++++++++++ src/lib/json-ui/wrappers/index.ts | 4 + src/lib/json-ui/wrappers/interfaces.ts | 73 ++++++++ 8 files changed, 562 insertions(+), 20 deletions(-) create mode 100644 src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx diff --git a/json-components-registry.json b/json-components-registry.json index 6794fef..dfd535a 100644 --- a/json-components-registry.json +++ b/json-components-registry.json @@ -2,7 +2,7 @@ "$schema": "./schemas/json-components-registry-schema.json", "version": "2.0.0", "description": "Registry of all components in the application", - "lastUpdated": "2026-01-18T12:05:00.000Z", + "lastUpdated": "2026-01-18T12:29:35.000Z", "categories": { "layout": "Layout and container components", "input": "Form inputs and interactive controls", @@ -74,9 +74,22 @@ "category": "layout", "canHaveChildren": true, "description": "ComponentBindingDialog component", - "status": "maybe-json-compatible", + "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", @@ -103,9 +116,22 @@ "category": "layout", "canHaveChildren": true, "description": "DataSourceEditorDialog component", - "status": "maybe-json-compatible", + "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", @@ -1080,7 +1106,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", @@ -1236,30 +1275,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", @@ -1302,10 +1380,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", @@ -1565,9 +1656,22 @@ "category": "custom", "canHaveChildren": true, "description": "ComponentTree component", - "status": "maybe-json-compatible", + "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", @@ -1809,10 +1913,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", @@ -1979,10 +2096,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", diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 2912d38..8b3d05c 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -21,6 +21,10 @@ import * as AtomComponents from '@/components/atoms' import * as MoleculeComponents from '@/components/molecules' import { BreadcrumbWrapper, + ComponentBindingDialogWrapper, + ComponentTreeWrapper, + DataSourceEditorDialogWrapper, + GitHubBuildStatusWrapper, LazyBarChartWrapper, LazyD3BarChartWrapper, LazyLineChartWrapper, @@ -168,12 +172,27 @@ export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames( export const jsonWrapperComponents: UIComponentRegistry = { Breadcrumb: BreadcrumbWrapper, + BreadcrumbWrapper: BreadcrumbWrapper, SaveIndicator: SaveIndicatorWrapper, + SaveIndicatorWrapper: SaveIndicatorWrapper, LazyBarChart: LazyBarChartWrapper, + LazyBarChartWrapper: LazyBarChartWrapper, LazyLineChart: LazyLineChartWrapper, + LazyLineChartWrapper: LazyLineChartWrapper, LazyD3BarChart: LazyD3BarChartWrapper, + LazyD3BarChartWrapper: LazyD3BarChartWrapper, SeedDataManager: SeedDataManagerWrapper, + SeedDataManagerWrapper: SeedDataManagerWrapper, StorageSettings: StorageSettingsWrapper, + StorageSettingsWrapper: StorageSettingsWrapper, + GitHubBuildStatus: GitHubBuildStatusWrapper, + GitHubBuildStatusWrapper: GitHubBuildStatusWrapper, + ComponentBindingDialog: ComponentBindingDialogWrapper, + ComponentBindingDialogWrapper: ComponentBindingDialogWrapper, + DataSourceEditorDialog: DataSourceEditorDialogWrapper, + DataSourceEditorDialogWrapper: DataSourceEditorDialogWrapper, + ComponentTree: ComponentTreeWrapper, + ComponentTreeWrapper: ComponentTreeWrapper, } export const iconComponents: UIComponentRegistry = { diff --git a/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx new file mode 100644 index 0000000..d741482 --- /dev/null +++ b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx @@ -0,0 +1,75 @@ +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' +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) { + return ( + + + + {title} + {description} + + + {(componentType || componentId) && ( +
+ {componentType && ( +
+ Component: + {componentType} +
+ )} + {componentId && ( +
+ ID: + {componentId} +
+ )} +
+ )} + +
+ {bindings.length === 0 ? ( +

No bindings configured.

+ ) : ( + bindings.map((binding) => ( +
+ + onBindingChange?.(binding.id, event.target.value)} + /> +
+ )) + )} +
+ + + + + +
+
+ ) +} diff --git a/src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx b/src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx new file mode 100644 index 0000000..36bc508 --- /dev/null +++ b/src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx @@ -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 ( +
+ + {hasChildren && ( +
+ {renderTreeNodes(component.children as UIComponent[], depth + 1, selectedId, onSelect)} +
+ )} +
+ ) + }) +} + +export function ComponentTreeWrapper({ + components = [], + selectedId = null, + emptyMessage = 'No components available.', + onSelect, + className, +}: ComponentTreeWrapperProps) { + return ( +
+ {components.length === 0 ? ( +

{emptyMessage}

+ ) : ( +
{renderTreeNodes(components, 0, selectedId, onSelect)}
+ )} +
+ ) +} diff --git a/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx new file mode 100644 index 0000000..eedd4bb --- /dev/null +++ b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx @@ -0,0 +1,59 @@ +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' +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) { + return ( + + + + {title} + {description} + + +
+ {fields.length === 0 ? ( +

No fields configured.

+ ) : ( + fields.map((field) => ( +
+ + onFieldChange?.(field.id, event.target.value)} + /> + {field.helperText && ( +

{field.helperText}

+ )} +
+ )) + )} +
+ + + + + +
+
+ ) +} diff --git a/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx new file mode 100644 index 0000000..5343004 --- /dev/null +++ b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx @@ -0,0 +1,127 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' +import { + ArrowSquareOut, + CheckCircle, + Clock, + WarningCircle, + XCircle, +} from '@phosphor-icons/react' +import type { GitHubBuildStatusWrapperProps, GitHubBuildStatusWorkflowItem } from './interfaces' + +const getStatusBadge = (workflow: GitHubBuildStatusWorkflowItem) => { + if (workflow.status === 'completed') { + if (workflow.conclusion === 'success') { + return Success + } + if (workflow.conclusion === 'failure') { + return Failed + } + if (workflow.conclusion === 'cancelled') { + return Cancelled + } + } + + return ( + + Running + + ) +} + +const getStatusIcon = (workflow: GitHubBuildStatusWorkflowItem) => { + if (workflow.status === 'completed') { + if (workflow.conclusion === 'success') { + return + } + if (workflow.conclusion === 'failure') { + return + } + if (workflow.conclusion === 'cancelled') { + return + } + } + + return +} + +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) { + return ( + + + + + {title} + + {description} + + + {isLoading &&

Loading workflows…

} + + {!isLoading && errorMessage && ( +
+ + {errorMessage} +
+ )} + + {!isLoading && !errorMessage && workflows.length === 0 && ( +

{emptyMessage}

+ )} + + {!isLoading && !errorMessage && workflows.length > 0 && ( +
+ {workflows.map((workflow) => ( +
+
+ {getStatusIcon(workflow)} +
+
+

{workflow.name}

+ {getStatusBadge(workflow)} +
+
+ {[workflow.branch, workflow.updatedAt, workflow.event] + .filter(Boolean) + .join(' • ')} +
+
+
+ {workflow.url && ( + + )} +
+ ))} +
+ )} + + {footerLinkUrl && ( + + )} +
+
+ ) +} diff --git a/src/lib/json-ui/wrappers/index.ts b/src/lib/json-ui/wrappers/index.ts index 3e06399..b0273a2 100644 --- a/src/lib/json-ui/wrappers/index.ts +++ b/src/lib/json-ui/wrappers/index.ts @@ -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' diff --git a/src/lib/json-ui/wrappers/interfaces.ts b/src/lib/json-ui/wrappers/interfaces.ts index 3096b76..f7914c9 100644 --- a/src/lib/json-ui/wrappers/interfaces.ts +++ b/src/lib/json-ui/wrappers/interfaces.ts @@ -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 +} From 6df9c0c3dd593b99fd3fbe9a3041551ef6ed6724 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 13:03:05 +0000 Subject: [PATCH 2/7] Resolve wrapper components via registry metadata --- src/lib/json-ui/component-registry.ts | 69 ++++++++++++--------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 8b3d05c..7bc676a 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -19,19 +19,7 @@ import { Progress } from '@/components/ui/progress' import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import * as AtomComponents from '@/components/atoms' import * as MoleculeComponents from '@/components/molecules' -import { - BreadcrumbWrapper, - ComponentBindingDialogWrapper, - ComponentTreeWrapper, - DataSourceEditorDialogWrapper, - GitHubBuildStatusWrapper, - LazyBarChartWrapper, - LazyD3BarChartWrapper, - LazyLineChartWrapper, - SaveIndicatorWrapper, - SeedDataManagerWrapper, - StorageSettingsWrapper, -} from '@/lib/json-ui/wrappers' +import * as WrapperComponents from '@/lib/json-ui/wrappers' import jsonComponentsRegistry from '../../../json-components-registry.json' import { ArrowLeft, ArrowRight, Check, X, Plus, Minus, MagnifyingGlass, @@ -51,6 +39,8 @@ interface JsonRegistryEntry { type?: string export?: string source?: string + wrapperRequired?: boolean + wrapperComponent?: string } interface JsonComponentRegistry { @@ -81,6 +71,19 @@ const moleculeRegistryNames = jsonRegistryEntries .filter((entry) => entry.source === 'molecules') .map((entry) => entry.export ?? entry.name ?? entry.type) .filter((name): name is string => Boolean(name)) +const wrapperRegistryNames = jsonRegistryEntries + .filter((entry) => entry.source === 'wrappers') + .map((entry) => entry.export ?? entry.name ?? entry.type) + .filter((name): name is string => Boolean(name)) + +const registryEntryByType = new Map( + jsonRegistryEntries + .map((entry) => { + const key = entry.type ?? entry.name ?? entry.export + return key ? [key, entry] : null + }) + .filter((entry): entry is [string, JsonRegistryEntry] => Boolean(entry)) +) export const primitiveComponents: UIComponentRegistry = { div: 'div' as any, @@ -170,30 +173,10 @@ export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames( MoleculeComponents as Record> ) -export const jsonWrapperComponents: UIComponentRegistry = { - Breadcrumb: BreadcrumbWrapper, - BreadcrumbWrapper: BreadcrumbWrapper, - SaveIndicator: SaveIndicatorWrapper, - SaveIndicatorWrapper: SaveIndicatorWrapper, - LazyBarChart: LazyBarChartWrapper, - LazyBarChartWrapper: LazyBarChartWrapper, - LazyLineChart: LazyLineChartWrapper, - LazyLineChartWrapper: LazyLineChartWrapper, - LazyD3BarChart: LazyD3BarChartWrapper, - LazyD3BarChartWrapper: LazyD3BarChartWrapper, - SeedDataManager: SeedDataManagerWrapper, - SeedDataManagerWrapper: SeedDataManagerWrapper, - StorageSettings: StorageSettingsWrapper, - StorageSettingsWrapper: StorageSettingsWrapper, - GitHubBuildStatus: GitHubBuildStatusWrapper, - GitHubBuildStatusWrapper: GitHubBuildStatusWrapper, - ComponentBindingDialog: ComponentBindingDialogWrapper, - ComponentBindingDialogWrapper: ComponentBindingDialogWrapper, - DataSourceEditorDialog: DataSourceEditorDialogWrapper, - DataSourceEditorDialogWrapper: DataSourceEditorDialogWrapper, - ComponentTree: ComponentTreeWrapper, - ComponentTreeWrapper: ComponentTreeWrapper, -} +export const jsonWrapperComponents: UIComponentRegistry = buildRegistryFromNames( + wrapperRegistryNames, + WrapperComponents as Record> +) export const iconComponents: UIComponentRegistry = { ArrowLeft, @@ -249,10 +232,18 @@ export function registerComponent(name: string, component: ComponentType) { uiComponentRegistry[name] = component } +const resolveWrapperComponent = (type: string): ComponentType | null => { + const entry = registryEntryByType.get(type) + if (entry?.wrapperRequired && entry.wrapperComponent) { + return uiComponentRegistry[entry.wrapperComponent] || null + } + return null +} + export function getUIComponent(type: string): ComponentType | 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]) } From 21ef3d1d3e01a8715b96fe87b6a7b2cd2bd6005e Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 13:03:14 +0000 Subject: [PATCH 3/7] Render JSON wrappers from schema definitions --- src/lib/json-ui/component-renderer.tsx | 13 +- .../ComponentBindingDialogWrapper.tsx | 84 +-- .../DataSourceEditorDialogWrapper.tsx | 68 +-- .../wrappers/GitHubBuildStatusWrapper.tsx | 135 ++--- src/lib/json-ui/wrappers/definitions.ts | 555 ++++++++++++++++++ 5 files changed, 656 insertions(+), 199 deletions(-) create mode 100644 src/lib/json-ui/wrappers/definitions.ts diff --git a/src/lib/json-ui/component-renderer.tsx b/src/lib/json-ui/component-renderer.tsx index 672539c..6a10850 100644 --- a/src/lib/json-ui/component-renderer.tsx +++ b/src/lib/json-ui/component-renderer.tsx @@ -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 ( - {renderChildren(component.children, loopContext)} + {renderChildren(resolvedChildren as UIComponent[] | string | undefined, loopContext)} ) }) @@ -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) + ) } diff --git a/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx index d741482..1adfe31 100644 --- a/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx +++ b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx @@ -1,8 +1,7 @@ -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' +import type { ChangeEvent } from 'react' +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' import { cn } from '@/lib/utils' +import { componentBindingDialogDefinition } from './definitions' import type { ComponentBindingDialogWrapperProps } from './interfaces' export function ComponentBindingDialogWrapper({ @@ -18,58 +17,31 @@ export function ComponentBindingDialogWrapper({ onOpenChange, className, }: ComponentBindingDialogWrapperProps) { + const handleBindingFieldChange = (event: ChangeEvent) => { + const fieldId = event.currentTarget?.dataset?.fieldId || event.target?.dataset?.fieldId + if (!fieldId) return + onBindingChange?.(fieldId, event.target.value) + } + return ( - - - - {title} - {description} - - - {(componentType || componentId) && ( -
- {componentType && ( -
- Component: - {componentType} -
- )} - {componentId && ( -
- ID: - {componentId} -
- )} -
- )} - -
- {bindings.length === 0 ? ( -

No bindings configured.

- ) : ( - bindings.map((binding) => ( -
- - onBindingChange?.(binding.id, event.target.value)} - /> -
- )) - )} -
- - - - - -
-
+ ) } diff --git a/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx index eedd4bb..6bab82e 100644 --- a/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx +++ b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx @@ -1,8 +1,7 @@ -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' +import type { ChangeEvent } from 'react' +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' import { cn } from '@/lib/utils' +import { dataSourceEditorDialogDefinition } from './definitions' import type { DataSourceEditorDialogWrapperProps } from './interfaces' export function DataSourceEditorDialogWrapper({ @@ -16,44 +15,29 @@ export function DataSourceEditorDialogWrapper({ onOpenChange, className, }: DataSourceEditorDialogWrapperProps) { + const handleFieldChange = (event: ChangeEvent) => { + const fieldId = event.currentTarget?.dataset?.fieldId || event.target?.dataset?.fieldId + if (!fieldId) return + onFieldChange?.(fieldId, event.target.value) + } + return ( - - - - {title} - {description} - - -
- {fields.length === 0 ? ( -

No fields configured.

- ) : ( - fields.map((field) => ( -
- - onFieldChange?.(field.id, event.target.value)} - /> - {field.helperText && ( -

{field.helperText}

- )} -
- )) - )} -
- - - - - -
-
+ ) } diff --git a/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx index 5343004..faee8a0 100644 --- a/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx +++ b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx @@ -1,50 +1,25 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Button } from '@/components/ui/button' +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' import { cn } from '@/lib/utils' -import { - ArrowSquareOut, - CheckCircle, - Clock, - WarningCircle, - XCircle, -} from '@phosphor-icons/react' +import { gitHubBuildStatusDefinition } from './definitions' import type { GitHubBuildStatusWrapperProps, GitHubBuildStatusWorkflowItem } from './interfaces' -const getStatusBadge = (workflow: GitHubBuildStatusWorkflowItem) => { +const getWorkflowStatus = (workflow: GitHubBuildStatusWorkflowItem) => { if (workflow.status === 'completed') { if (workflow.conclusion === 'success') { - return Success + return { + label: 'Success', + className: 'bg-green-500/10 text-green-600 border-green-500/20', + } } if (workflow.conclusion === 'failure') { - return Failed + return { label: 'Failed', className: 'bg-red-500/10 text-red-600 border-red-500/20' } } if (workflow.conclusion === 'cancelled') { - return Cancelled + return { label: 'Cancelled', className: 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20' } } } - return ( - - Running - - ) -} - -const getStatusIcon = (workflow: GitHubBuildStatusWorkflowItem) => { - if (workflow.status === 'completed') { - if (workflow.conclusion === 'success') { - return - } - if (workflow.conclusion === 'failure') { - return - } - if (workflow.conclusion === 'cancelled') { - return - } - } - - return + return { label: 'Running', className: 'border-blue-500/50 text-blue-500' } } export function GitHubBuildStatusWrapper({ @@ -58,70 +33,32 @@ export function GitHubBuildStatusWrapper({ 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 ( - - - - - {title} - - {description} - - - {isLoading &&

Loading workflows…

} - - {!isLoading && errorMessage && ( -
- - {errorMessage} -
- )} - - {!isLoading && !errorMessage && workflows.length === 0 && ( -

{emptyMessage}

- )} - - {!isLoading && !errorMessage && workflows.length > 0 && ( -
- {workflows.map((workflow) => ( -
-
- {getStatusIcon(workflow)} -
-
-

{workflow.name}

- {getStatusBadge(workflow)} -
-
- {[workflow.branch, workflow.updatedAt, workflow.event] - .filter(Boolean) - .join(' • ')} -
-
-
- {workflow.url && ( - - )} -
- ))} -
- )} - - {footerLinkUrl && ( - - )} -
-
+ 0, + footerLinkLabel, + footerLinkUrl, + className: cn(className), + }} + /> ) } diff --git a/src/lib/json-ui/wrappers/definitions.ts b/src/lib/json-ui/wrappers/definitions.ts new file mode 100644 index 0000000..1a7d422 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions.ts @@ -0,0 +1,555 @@ +import type { UIComponent } from '@/types/json-ui' + +export const componentBindingDialogDefinition: UIComponent = { + 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', + }, + }, + ], + }, + ], + }, + ], +} + +export const dataSourceEditorDialogDefinition: UIComponent = { + 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', + }, + }, + ], + }, + ], + }, + ], +} + +export const gitHubBuildStatusDefinition: UIComponent = { + 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', + }, + }, + ], + }, + ], + }, + ], +} From 3c96b733b238c8922c7f8e66e291f971f5f4f62c Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 13:03:24 +0000 Subject: [PATCH 4/7] Move wrapper layouts into JSON definition files --- .../ComponentBindingDialogWrapper.tsx | 2 +- .../DataSourceEditorDialogWrapper.tsx | 2 +- .../wrappers/GitHubBuildStatusWrapper.tsx | 2 +- src/lib/json-ui/wrappers/definitions.ts | 555 ------------------ .../definitions/component-binding-dialog.json | 200 +++++++ .../data-source-editor-dialog.json | 141 +++++ .../definitions/github-build-status.json | 210 +++++++ 7 files changed, 554 insertions(+), 558 deletions(-) delete mode 100644 src/lib/json-ui/wrappers/definitions.ts create mode 100644 src/lib/json-ui/wrappers/definitions/component-binding-dialog.json create mode 100644 src/lib/json-ui/wrappers/definitions/data-source-editor-dialog.json create mode 100644 src/lib/json-ui/wrappers/definitions/github-build-status.json diff --git a/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx index 1adfe31..1912823 100644 --- a/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx +++ b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx @@ -1,7 +1,7 @@ import type { ChangeEvent } from 'react' import { ComponentRenderer } from '@/lib/json-ui/component-renderer' import { cn } from '@/lib/utils' -import { componentBindingDialogDefinition } from './definitions' +import componentBindingDialogDefinition from './definitions/component-binding-dialog.json' import type { ComponentBindingDialogWrapperProps } from './interfaces' export function ComponentBindingDialogWrapper({ diff --git a/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx index 6bab82e..72530c7 100644 --- a/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx +++ b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx @@ -1,7 +1,7 @@ import type { ChangeEvent } from 'react' import { ComponentRenderer } from '@/lib/json-ui/component-renderer' import { cn } from '@/lib/utils' -import { dataSourceEditorDialogDefinition } from './definitions' +import dataSourceEditorDialogDefinition from './definitions/data-source-editor-dialog.json' import type { DataSourceEditorDialogWrapperProps } from './interfaces' export function DataSourceEditorDialogWrapper({ diff --git a/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx index faee8a0..112037a 100644 --- a/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx +++ b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx @@ -1,6 +1,6 @@ import { ComponentRenderer } from '@/lib/json-ui/component-renderer' import { cn } from '@/lib/utils' -import { gitHubBuildStatusDefinition } from './definitions' +import gitHubBuildStatusDefinition from './definitions/github-build-status.json' import type { GitHubBuildStatusWrapperProps, GitHubBuildStatusWorkflowItem } from './interfaces' const getWorkflowStatus = (workflow: GitHubBuildStatusWorkflowItem) => { diff --git a/src/lib/json-ui/wrappers/definitions.ts b/src/lib/json-ui/wrappers/definitions.ts deleted file mode 100644 index 1a7d422..0000000 --- a/src/lib/json-ui/wrappers/definitions.ts +++ /dev/null @@ -1,555 +0,0 @@ -import type { UIComponent } from '@/types/json-ui' - -export const componentBindingDialogDefinition: UIComponent = { - 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', - }, - }, - ], - }, - ], - }, - ], -} - -export const dataSourceEditorDialogDefinition: UIComponent = { - 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', - }, - }, - ], - }, - ], - }, - ], -} - -export const gitHubBuildStatusDefinition: UIComponent = { - 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', - }, - }, - ], - }, - ], - }, - ], -} diff --git a/src/lib/json-ui/wrappers/definitions/component-binding-dialog.json b/src/lib/json-ui/wrappers/definitions/component-binding-dialog.json new file mode 100644 index 0000000..a59d0f2 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/component-binding-dialog.json @@ -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" + } + } + ] + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/data-source-editor-dialog.json b/src/lib/json-ui/wrappers/definitions/data-source-editor-dialog.json new file mode 100644 index 0000000..edf37a4 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/data-source-editor-dialog.json @@ -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" + } + } + ] + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/github-build-status.json b/src/lib/json-ui/wrappers/definitions/github-build-status.json new file mode 100644 index 0000000..18393d1 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/github-build-status.json @@ -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" + } + } + ] + } + ] + } + ] +} From 0a491528f3006c4fe28e7a836ddfecd99eb70bae Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 13:03:37 +0000 Subject: [PATCH 5/7] Add progress components to JSON UI schemas --- src/lib/component-definitions.json | 12 +++-- src/lib/json-ui/component-registry.ts | 4 ++ src/schemas/page-schemas.json | 76 +++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/lib/component-definitions.json b/src/lib/component-definitions.json index 9731f20..784eb4d 100644 --- a/src/lib/component-definitions.json +++ b/src/lib/component-definitions.json @@ -185,14 +185,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", @@ -220,7 +226,7 @@ "label": "Divider", "category": "display", "icon": "Minus", - "defaultProps": { "orientation": "horizontal", "decorative": true } + "defaultProps": { "orientation": "horizontal", "decorative": true, "className": "" } }, { "type": "Link", diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 90df225..6d9315f 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -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' @@ -175,6 +176,9 @@ export const atomComponents: UIComponentRegistry = { atomRegistryNames, AtomComponents as Record> ), + CircularProgress, + Divider, + ProgressBar, DataList: (AtomComponents as Record>).DataList, DataTable: (AtomComponents as Record>).DataTable, MetricCard: (AtomComponents as Record>).MetricCard, diff --git a/src/schemas/page-schemas.json b/src/schemas/page-schemas.json index 0a38f9a..8521334 100644 --- a/src/schemas/page-schemas.json +++ b/src/schemas/page-schemas.json @@ -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" } + } + } + ] + } + ] } } From 156e471f0b9f411ce6c55b68e65c0e99dc1a07f2 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 13:06:22 +0000 Subject: [PATCH 6/7] Update new molecules JSON showcase examples --- JSON_COMPATIBILITY_IMPLEMENTATION.md | 4 +- src/schemas/new-molecules-showcase.json | 76 +++++++++++++++++++------ 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/JSON_COMPATIBILITY_IMPLEMENTATION.md b/JSON_COMPATIBILITY_IMPLEMENTATION.md index 68469ea..8068b0f 100644 --- a/JSON_COMPATIBILITY_IMPLEMENTATION.md +++ b/JSON_COMPATIBILITY_IMPLEMENTATION.md @@ -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 diff --git a/src/schemas/new-molecules-showcase.json b/src/schemas/new-molecules-showcase.json index 16bcd81..f4d6ae2 100644 --- a/src/schemas/new-molecules-showcase.json +++ b/src/schemas/new-molecules-showcase.json @@ -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" } } } ] From 31d6334a657c577329e813e65e6b0dc8acc200cb Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 13:11:20 +0000 Subject: [PATCH 7/7] Remove visual regression snapshot --- component-registry.json | 9 + e2e/visual-regression.spec.ts | 16 + src/components/JSONConversionShowcase.tsx | 9 + .../molecules/DataSourceEditorDialog.tsx | 3 +- src/config/default-pages.json | 10 + .../orchestration/component-registry.ts | 2 + src/config/pages.json | 9 + .../pages/json-conversion-showcase.json | 341 ++++++++++++++++++ src/lib/navigation-config.tsx | 11 + 9 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 e2e/visual-regression.spec.ts create mode 100644 src/components/JSONConversionShowcase.tsx create mode 100644 src/config/pages/json-conversion-showcase.json diff --git a/component-registry.json b/component-registry.json index 6187ba9..281ba4a 100644 --- a/component-registry.json +++ b/component-registry.json @@ -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", diff --git a/e2e/visual-regression.spec.ts b/e2e/visual-regression.spec.ts new file mode 100644 index 0000000..9ba8ccc --- /dev/null +++ b/e2e/visual-regression.spec.ts @@ -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 }) + }) +}) diff --git a/src/components/JSONConversionShowcase.tsx b/src/components/JSONConversionShowcase.tsx new file mode 100644 index 0000000..ec08a57 --- /dev/null +++ b/src/components/JSONConversionShowcase.tsx @@ -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 +} diff --git a/src/components/molecules/DataSourceEditorDialog.tsx b/src/components/molecules/DataSourceEditorDialog.tsx index ceec274..20de4ae 100644 --- a/src/components/molecules/DataSourceEditorDialog.tsx +++ b/src/components/molecules/DataSourceEditorDialog.tsx @@ -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 diff --git a/src/config/default-pages.json b/src/config/default-pages.json index 10ec0c9..e0cc663 100644 --- a/src/config/default-pages.json +++ b/src/config/default-pages.json @@ -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", diff --git a/src/config/orchestration/component-registry.ts b/src/config/orchestration/component-registry.ts index ff93835..cc58a1c 100644 --- a/src/config/orchestration/component-registry.ts +++ b/src/config/orchestration/component-registry.ts @@ -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> = { Button, @@ -61,6 +62,7 @@ export const ComponentRegistry: Record> = { FaviconDesigner, FeatureIdeaCloud, JSONUIShowcase, + JSONConversionShowcase, } export function getComponent(name: string): ComponentType | null { diff --git a/src/config/pages.json b/src/config/pages.json index 99dc30b..6727b1b 100644 --- a/src/config/pages.json +++ b/src/config/pages.json @@ -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", diff --git a/src/config/pages/json-conversion-showcase.json b/src/config/pages/json-conversion-showcase.json new file mode 100644 index 0000000..c3acaf1 --- /dev/null +++ b/src/config/pages/json-conversion-showcase.json @@ -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": [] +} diff --git a/src/lib/navigation-config.tsx b/src/lib/navigation-config.tsx index 4a0dd42..306b62a 100644 --- a/src/lib/navigation-config.tsx +++ b/src/lib/navigation-config.tsx @@ -153,6 +153,11 @@ export const tabInfo: Record = { icon: , description: 'JSON-driven UI examples', }, + 'json-conversion-showcase': { + title: 'JSON Conversion Showcase', + icon: , + description: 'JSON conversion showcase overview', + }, 'atomic-library': { title: 'Atomic Components', icon: , @@ -332,6 +337,12 @@ export const navigationGroups: NavigationGroup[] = [ value: 'docs', featureKey: 'documentation', }, + { + id: 'json-conversion-showcase', + label: 'JSON Conversion Showcase', + icon: , + value: 'json-conversion-showcase', + }, { id: 'settings', label: 'Settings',