mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Add JSON wrapper components for hook-based UI
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
75
src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx
Normal file
75
src/lib/json-ui/wrappers/ComponentBindingDialogWrapper.tsx
Normal file
@@ -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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={cn('max-w-2xl', className)}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{(componentType || componentId) && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3 text-sm">
|
||||
{componentType && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground">Component:</span>
|
||||
<span className="font-mono font-medium">{componentType}</span>
|
||||
</div>
|
||||
)}
|
||||
{componentId && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground">ID:</span>
|
||||
<span className="font-mono text-xs">{componentId}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
{bindings.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No bindings configured.</p>
|
||||
) : (
|
||||
bindings.map((binding) => (
|
||||
<div key={binding.id} className="space-y-2">
|
||||
<Label htmlFor={`binding-${binding.id}`}>{binding.label}</Label>
|
||||
<Input
|
||||
id={`binding-${binding.id}`}
|
||||
value={binding.value ?? ''}
|
||||
placeholder={binding.placeholder}
|
||||
onChange={(event) => onBindingChange?.(binding.id, event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
55
src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx
Normal file
55
src/lib/json-ui/wrappers/ComponentTreeWrapper.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { UIComponent } from '@/types/json-ui'
|
||||
import type { ComponentTreeWrapperProps } from './interfaces'
|
||||
|
||||
const renderTreeNodes = (
|
||||
components: UIComponent[],
|
||||
depth: number,
|
||||
selectedId: string | null,
|
||||
onSelect?: (id: string) => void
|
||||
) => {
|
||||
return components.map((component) => {
|
||||
const hasChildren = Array.isArray(component.children) && component.children.length > 0
|
||||
const isSelected = selectedId === component.id
|
||||
|
||||
return (
|
||||
<div key={component.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelect?.(component.id)}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-md px-2 py-1 text-left text-sm transition-colors',
|
||||
isSelected ? 'bg-accent/40 text-foreground' : 'hover:bg-muted'
|
||||
)}
|
||||
style={{ paddingLeft: `${depth * 16 + 8}px` }}
|
||||
>
|
||||
<span className="font-medium">{component.type}</span>
|
||||
<span className="text-xs text-muted-foreground">{component.id}</span>
|
||||
</button>
|
||||
{hasChildren && (
|
||||
<div className="mt-1">
|
||||
{renderTreeNodes(component.children as UIComponent[], depth + 1, selectedId, onSelect)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function ComponentTreeWrapper({
|
||||
components = [],
|
||||
selectedId = null,
|
||||
emptyMessage = 'No components available.',
|
||||
onSelect,
|
||||
className,
|
||||
}: ComponentTreeWrapperProps) {
|
||||
return (
|
||||
<div className={cn('space-y-2', className)}>
|
||||
{components.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">{emptyMessage}</p>
|
||||
) : (
|
||||
<div className="space-y-1">{renderTreeNodes(components, 0, selectedId, onSelect)}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
59
src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx
Normal file
59
src/lib/json-ui/wrappers/DataSourceEditorDialogWrapper.tsx
Normal file
@@ -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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={cn('max-w-2xl', className)}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{fields.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No fields configured.</p>
|
||||
) : (
|
||||
fields.map((field) => (
|
||||
<div key={field.id} className="space-y-2">
|
||||
<Label htmlFor={`field-${field.id}`}>{field.label}</Label>
|
||||
<Input
|
||||
id={`field-${field.id}`}
|
||||
value={field.value ?? ''}
|
||||
placeholder={field.placeholder}
|
||||
onChange={(event) => onFieldChange?.(field.id, event.target.value)}
|
||||
/>
|
||||
{field.helperText && (
|
||||
<p className="text-xs text-muted-foreground">{field.helperText}</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
127
src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx
Normal file
127
src/lib/json-ui/wrappers/GitHubBuildStatusWrapper.tsx
Normal file
@@ -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 <Badge className="bg-green-500/10 text-green-600 border-green-500/20">Success</Badge>
|
||||
}
|
||||
if (workflow.conclusion === 'failure') {
|
||||
return <Badge variant="destructive">Failed</Badge>
|
||||
}
|
||||
if (workflow.conclusion === 'cancelled') {
|
||||
return <Badge variant="secondary">Cancelled</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge variant="outline" className="border-blue-500/50 text-blue-500">
|
||||
Running
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
const getStatusIcon = (workflow: GitHubBuildStatusWorkflowItem) => {
|
||||
if (workflow.status === 'completed') {
|
||||
if (workflow.conclusion === 'success') {
|
||||
return <CheckCircle size={18} className="text-green-500" weight="fill" />
|
||||
}
|
||||
if (workflow.conclusion === 'failure') {
|
||||
return <XCircle size={18} className="text-red-500" weight="fill" />
|
||||
}
|
||||
if (workflow.conclusion === 'cancelled') {
|
||||
return <WarningCircle size={18} className="text-yellow-500" weight="fill" />
|
||||
}
|
||||
}
|
||||
|
||||
return <Clock size={18} className="text-blue-500" weight="duotone" />
|
||||
}
|
||||
|
||||
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 (
|
||||
<Card className={cn(className)}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ArrowSquareOut size={18} weight="duotone" />
|
||||
{title}
|
||||
</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading workflows…</p>}
|
||||
|
||||
{!isLoading && errorMessage && (
|
||||
<div className="flex items-center gap-2 text-sm text-red-500">
|
||||
<WarningCircle size={16} weight="fill" />
|
||||
<span>{errorMessage}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !errorMessage && workflows.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">{emptyMessage}</p>
|
||||
)}
|
||||
|
||||
{!isLoading && !errorMessage && workflows.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
{workflows.map((workflow) => (
|
||||
<div
|
||||
key={workflow.id}
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-border p-3"
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
{getStatusIcon(workflow)}
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-medium truncate">{workflow.name}</p>
|
||||
{getStatusBadge(workflow)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{[workflow.branch, workflow.updatedAt, workflow.event]
|
||||
.filter(Boolean)
|
||||
.join(' • ')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{workflow.url && (
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<a href={workflow.url} target="_blank" rel="noopener noreferrer">
|
||||
<ArrowSquareOut size={14} />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{footerLinkUrl && (
|
||||
<Button variant="outline" size="sm" asChild className="w-full">
|
||||
<a href={footerLinkUrl} target="_blank" rel="noopener noreferrer">
|
||||
{footerLinkLabel}
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -5,3 +5,7 @@ export { LazyLineChartWrapper } from './LazyLineChartWrapper'
|
||||
export { LazyD3BarChartWrapper } from './LazyD3BarChartWrapper'
|
||||
export { SeedDataManagerWrapper } from './SeedDataManagerWrapper'
|
||||
export { StorageSettingsWrapper } from './StorageSettingsWrapper'
|
||||
export { GitHubBuildStatusWrapper } from './GitHubBuildStatusWrapper'
|
||||
export { ComponentBindingDialogWrapper } from './ComponentBindingDialogWrapper'
|
||||
export { DataSourceEditorDialogWrapper } from './DataSourceEditorDialogWrapper'
|
||||
export { ComponentTreeWrapper } from './ComponentTreeWrapper'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { StorageBackendKey } from '@/components/storage/storageSettingsConfig'
|
||||
import type { UIComponent } from '@/types/json-ui'
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
label: string
|
||||
@@ -89,3 +90,75 @@ export interface StorageSettingsWrapperProps {
|
||||
onExport?: () => void
|
||||
onImport?: () => void
|
||||
}
|
||||
|
||||
export interface GitHubBuildStatusWorkflowItem {
|
||||
id: string
|
||||
name: string
|
||||
status?: string
|
||||
conclusion?: string | null
|
||||
branch?: string
|
||||
updatedAt?: string
|
||||
event?: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
export interface GitHubBuildStatusWrapperProps {
|
||||
title?: string
|
||||
description?: string
|
||||
workflows?: GitHubBuildStatusWorkflowItem[]
|
||||
isLoading?: boolean
|
||||
errorMessage?: string
|
||||
emptyMessage?: string
|
||||
footerLinkLabel?: string
|
||||
footerLinkUrl?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface ComponentBindingField {
|
||||
id: string
|
||||
label: string
|
||||
value?: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export interface ComponentBindingDialogWrapperProps {
|
||||
open?: boolean
|
||||
title?: string
|
||||
description?: string
|
||||
componentType?: string
|
||||
componentId?: string
|
||||
bindings?: ComponentBindingField[]
|
||||
onBindingChange?: (id: string, value: string) => void
|
||||
onSave?: () => void
|
||||
onCancel?: () => void
|
||||
onOpenChange?: (open: boolean) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface DataSourceField {
|
||||
id: string
|
||||
label: string
|
||||
value?: string
|
||||
placeholder?: string
|
||||
helperText?: string
|
||||
}
|
||||
|
||||
export interface DataSourceEditorDialogWrapperProps {
|
||||
open?: boolean
|
||||
title?: string
|
||||
description?: string
|
||||
fields?: DataSourceField[]
|
||||
onFieldChange?: (id: string, value: string) => void
|
||||
onSave?: () => void
|
||||
onCancel?: () => void
|
||||
onOpenChange?: (open: boolean) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface ComponentTreeWrapperProps {
|
||||
components?: UIComponent[]
|
||||
selectedId?: string | null
|
||||
emptyMessage?: string
|
||||
onSelect?: (id: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user