diff --git a/json-components-registry.json b/json-components-registry.json index 62e9f19..1e9d707 100644 --- a/json-components-registry.json +++ b/json-components-registry.json @@ -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", diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 6d9315f..f60ceac 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -265,12 +265,20 @@ 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]) } export function getDeprecatedComponentInfo(type: string): DeprecatedComponentInfo | null { 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 new file mode 100644 index 0000000..1912823 --- /dev/null +++ b/src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx @@ -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) => { + const fieldId = event.currentTarget?.dataset?.fieldId || event.target?.dataset?.fieldId + if (!fieldId) return + onBindingChange?.(fieldId, event.target.value) + } + + return ( + + ) +} 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..72530c7 --- /dev/null +++ b/src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx @@ -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) => { + const fieldId = event.currentTarget?.dataset?.fieldId || event.target?.dataset?.fieldId + if (!fieldId) return + onFieldChange?.(fieldId, event.target.value) + } + + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx new file mode 100644 index 0000000..112037a --- /dev/null +++ b/src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx @@ -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 ( + 0, + footerLinkLabel, + footerLinkUrl, + className: cn(className), + }} + /> + ) +} 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" + } + } + ] + } + ] + } + ] +} 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 +}