Merge pull request #114 from johndoe6345789/codex/implement-json-ui-component-integration

JSON UI: Add support for DataList / DataTable / MetricCard / Timeline
This commit is contained in:
2026-01-18 11:36:19 +00:00
committed by GitHub
7 changed files with 251 additions and 12 deletions

View File

@@ -1219,7 +1219,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Styled data list",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1238,7 +1238,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Advanced data table with sorting and filtering",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1304,7 +1304,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Metric display card",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1369,7 +1369,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Timeline visualization",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -2043,8 +2043,8 @@
],
"statistics": {
"total": 219,
"supported": 150,
"planned": 14,
"supported": 154,
"planned": 10,
"jsonCompatible": 14,
"maybeJsonCompatible": 41,
"byCategory": {

View File

@@ -3,6 +3,7 @@ import { AtomicComponentDemo } from '@/components/AtomicComponentDemo'
import { DashboardDemoPage } from '@/components/DashboardDemoPage'
import { PageRenderer } from '@/lib/json-ui/page-renderer'
import { hydrateSchema } from '@/schemas/schema-loader'
import { dataComponentsDemoSchema } from '@/schemas/page-schemas'
import todoListJson from '@/schemas/todo-list.json'
import newMoleculesShowcaseJson from '@/schemas/new-molecules-showcase.json'
@@ -25,6 +26,7 @@ export function JSONUIShowcasePage() {
<TabsList className="w-full justify-start">
<TabsTrigger value="atomic">Atomic Components</TabsTrigger>
<TabsTrigger value="molecules">New Molecules</TabsTrigger>
<TabsTrigger value="data-components">Data Components</TabsTrigger>
<TabsTrigger value="dashboard">JSON Dashboard</TabsTrigger>
<TabsTrigger value="todos">JSON Todo List</TabsTrigger>
</TabsList>
@@ -38,6 +40,10 @@ export function JSONUIShowcasePage() {
<TabsContent value="molecules" className="h-full m-0 data-[state=active]:block">
<PageRenderer schema={newMoleculesShowcaseSchema} />
</TabsContent>
<TabsContent value="data-components" className="h-full m-0 data-[state=active]:block">
<PageRenderer schema={dataComponentsDemoSchema} />
</TabsContent>
<TabsContent value="dashboard" className="h-full m-0 data-[state=active]:block">
<DashboardDemoPage />

View File

@@ -3,10 +3,11 @@ import { cn } from '@/lib/utils'
export interface DataListProps {
items: any[]
renderItem: (item: any, index: number) => ReactNode
renderItem?: (item: any, index: number) => ReactNode
emptyMessage?: string
className?: string
itemClassName?: string
itemKey?: string
}
export function DataList({
@@ -15,6 +16,7 @@ export function DataList({
emptyMessage = 'No items',
className,
itemClassName,
itemKey,
}: DataListProps) {
if (items.length === 0) {
return (
@@ -24,11 +26,28 @@ export function DataList({
)
}
const renderFallbackItem = (item: any) => {
if (itemKey && item && typeof item === 'object') {
const value = item[itemKey]
if (value !== undefined && value !== null) {
return typeof value === 'string' || typeof value === 'number'
? value
: JSON.stringify(value)
}
}
if (typeof item === 'string' || typeof item === 'number') {
return item
}
return JSON.stringify(item)
}
return (
<div className={cn('space-y-2', className)}>
{items.map((item, index) => (
<div key={index} className={cn('transition-colors', itemClassName)}>
{renderItem(item, index)}
{renderItem ? renderItem(item, index) : renderFallbackItem(item)}
</div>
))}
</div>

View File

@@ -264,6 +264,17 @@ export const componentDefinitions: ComponentDefinition[] = [
icon: 'List',
defaultProps: { items: [], emptyMessage: 'No items' }
},
{
type: 'DataList',
label: 'Data List',
category: 'data',
icon: 'List',
defaultProps: {
items: ['Daily summary', 'New signups', 'Pending approvals'],
emptyMessage: 'No updates',
itemClassName: 'rounded-md border border-border bg-card/50 px-4 py-2'
}
},
{
type: 'Table',
label: 'Table',
@@ -271,6 +282,25 @@ export const componentDefinitions: ComponentDefinition[] = [
icon: 'Table',
defaultProps: { data: [], columns: [] }
},
{
type: 'DataTable',
label: 'Data Table',
category: 'data',
icon: 'Table',
defaultProps: {
columns: [
{ key: 'name', header: 'Name' },
{ key: 'status', header: 'Status' },
{ key: 'owner', header: 'Owner' },
],
data: [
{ name: 'Launch Plan', status: 'In Progress', owner: 'Avery' },
{ name: 'Design Review', status: 'Scheduled', owner: 'Jordan' },
{ name: 'QA Checklist', status: 'Done', owner: 'Riley' },
],
emptyMessage: 'No records available',
}
},
{
type: 'KeyValue',
label: 'Key Value',
@@ -285,6 +315,45 @@ export const componentDefinitions: ComponentDefinition[] = [
icon: 'ChartBar',
defaultProps: { title: 'Metric', value: '0' }
},
{
type: 'MetricCard',
label: 'Metric Card',
category: 'data',
icon: 'ChartBar',
defaultProps: {
label: 'Active Users',
value: '1,248',
trend: { value: 12.4, direction: 'up' },
}
},
{
type: 'Timeline',
label: 'Timeline',
category: 'data',
icon: 'Clock',
defaultProps: {
items: [
{
title: 'Planning',
description: 'Finalize milestones',
timestamp: 'Mon 9:00 AM',
status: 'completed',
},
{
title: 'Execution',
description: 'Kick off delivery',
timestamp: 'Tue 11:00 AM',
status: 'current',
},
{
title: 'Review',
description: 'Collect feedback',
timestamp: 'Wed 3:00 PM',
status: 'pending',
},
],
}
},
// Custom Components
{
type: 'DataCard',

View File

@@ -136,10 +136,16 @@ export const shadcnComponents: UIComponentRegistry = {
AvatarImage,
}
export const atomComponents: UIComponentRegistry = buildRegistryFromNames(
atomRegistryNames,
AtomComponents as Record<string, ComponentType<any>>
)
export const atomComponents: UIComponentRegistry = {
...buildRegistryFromNames(
atomRegistryNames,
AtomComponents as Record<string, ComponentType<any>>
),
DataList: (AtomComponents as Record<string, ComponentType<any>>).DataList,
DataTable: (AtomComponents as Record<string, ComponentType<any>>).DataTable,
MetricCard: (AtomComponents as Record<string, ComponentType<any>>).MetricCard,
Timeline: (AtomComponents as Record<string, ComponentType<any>>).Timeline,
}
export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames(
moleculeRegistryNames,

View File

@@ -73,3 +73,141 @@ export const stateBindingsDemoSchema: PageSchema = {
},
],
}
export const dataComponentsDemoSchema: PageSchema = {
id: 'data-components-demo',
name: 'Data Components Demo',
layout: {
type: 'single',
},
dataSources: [
{
id: 'metricCards',
type: 'static',
defaultValue: [
{ label: 'Active Users', value: 1248, trend: { value: 12.4, direction: 'up' } },
{ label: 'Churn Rate', value: '3.2%', trend: { value: 1.1, direction: 'down' } },
{ label: 'Net Revenue', value: '$48.3k', trend: { value: 6.8, direction: 'up' } },
],
},
{
id: 'tableColumns',
type: 'static',
defaultValue: [
{ key: 'initiative', header: 'Initiative' },
{ key: 'owner', header: 'Owner' },
{ key: 'status', header: 'Status' },
],
},
{
id: 'tableRows',
type: 'static',
defaultValue: [
{ initiative: 'Landing Page', owner: 'Avery', status: 'In Progress' },
{ initiative: 'Retention Emails', owner: 'Jordan', status: 'Review' },
{ initiative: 'Billing Update', owner: 'Riley', status: 'Done' },
],
},
{
id: 'listItems',
type: 'static',
defaultValue: ['Prepare briefing deck', 'Confirm stakeholder approvals', 'Publish roadmap update'],
},
{
id: 'timelineItems',
type: 'static',
defaultValue: [
{
title: 'Kickoff',
description: 'Align on scope and milestones',
timestamp: 'Mon 9:00 AM',
status: 'completed',
},
{
title: 'Execution',
description: 'Deliver initial workstream',
timestamp: 'Tue 11:00 AM',
status: 'current',
},
{
title: 'Review',
description: 'Stakeholder walkthrough',
timestamp: 'Thu 3:00 PM',
status: 'pending',
},
],
},
],
components: [
{
id: 'data-components-root',
type: 'div',
props: {
className: 'space-y-6 rounded-lg border border-border bg-card p-6',
},
children: [
{
id: 'data-components-title',
type: 'Heading',
props: {
className: 'text-xl font-semibold',
children: 'Data Components Showcase',
},
},
{
id: 'data-components-metrics-grid',
type: 'div',
props: {
className: 'grid gap-4 md:grid-cols-3',
},
loop: {
source: 'metricCards',
itemVar: 'metricCard',
},
children: [
{
id: 'data-components-metric-card',
type: 'MetricCard',
bindings: {
label: { sourceType: 'bindings', source: 'metricCard', path: 'label' },
value: { sourceType: 'bindings', source: 'metricCard', path: 'value' },
trend: { sourceType: 'bindings', source: 'metricCard', path: 'trend' },
},
},
],
},
{
id: 'data-components-table',
type: 'DataTable',
props: {
className: 'bg-background',
emptyMessage: 'No initiatives found',
},
bindings: {
columns: { source: 'tableColumns', sourceType: 'data' },
data: { source: 'tableRows', sourceType: 'data' },
},
},
{
id: 'data-components-list',
type: 'DataList',
props: {
className: 'space-y-3',
itemClassName: 'rounded-md border border-border bg-card/50 px-4 py-2 text-sm',
emptyMessage: 'No action items',
},
bindings: {
items: { source: 'listItems', sourceType: 'data' },
},
},
{
id: 'data-components-timeline',
type: 'Timeline',
bindings: {
items: { source: 'timelineItems', sourceType: 'data' },
},
},
],
},
],
}

View File

@@ -7,6 +7,7 @@ export type ComponentType =
| 'Link' | 'Image' | 'Avatar' | 'Code' | 'Tag' | 'Spinner' | 'Skeleton'
| 'Alert' | 'InfoBox' | 'EmptyState' | 'StatusBadge'
| 'Table' | 'KeyValue' | 'StatCard' | 'DataCard' | 'SearchInput' | 'ActionBar'
| 'DataList' | 'DataTable' | 'MetricCard' | 'Timeline'
| 'AppBranding' | 'LabelWithBadge' | 'EmptyEditorState' | 'LoadingFallback' | 'LoadingState' | 'NavigationGroupHeader'
export type ActionType =