Merge branch 'main' into codex/integrate-breadcrumb-component-into-json

This commit is contained in:
2026-01-18 11:42:50 +00:00
committed by GitHub
9 changed files with 550 additions and 92 deletions

View File

@@ -0,0 +1,95 @@
# JSON Component Conversion Tasks
This task list captures the next steps for expanding JSON UI coverage, split between **component migrations** and **framework enablers**.
## Component Migration Tasks (Planned → Supported)
### Input Components
- [ ] **DatePicker**
- Add `DatePicker` to `ComponentType` in `src/types/json-ui.ts`.
- Register `DatePicker` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **FileUpload**
- Add `FileUpload` to `ComponentType` in `src/types/json-ui.ts`.
- Register `FileUpload` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
### Display Components
- [ ] **CircularProgress**
- Add `CircularProgress` to `ComponentType` in `src/types/json-ui.ts`.
- Register `CircularProgress` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **Divider**
- Add `Divider` to `ComponentType` in `src/types/json-ui.ts`.
- Register `Divider` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **ProgressBar**
- Add `ProgressBar` to `ComponentType` in `src/types/json-ui.ts`.
- Register `ProgressBar` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
### Navigation Components
- [ ] **Breadcrumb**
- Decide whether JSON should map to `BreadcrumbNav` (atoms) or `Breadcrumb` (molecules).
- Align props and bindings to a single JSON-friendly surface.
- Register a single `Breadcrumb` entry and set status to `supported` in `json-components-registry.json`.
### Feedback Components
- [ ] **ErrorBadge**
- Add `ErrorBadge` to `ComponentType` in `src/types/json-ui.ts`.
- Register `ErrorBadge` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **Notification**
- Add `Notification` to `ComponentType` in `src/types/json-ui.ts`.
- Register `Notification` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **StatusIcon**
- Add `StatusIcon` to `ComponentType` in `src/types/json-ui.ts`.
- Register `StatusIcon` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
### Data Components
- [ ] **DataList**
- Add `DataList` to `ComponentType` in `src/types/json-ui.ts`.
- Register `DataList` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **DataTable**
- Add `DataTable` to `ComponentType` in `src/types/json-ui.ts`.
- Register `DataTable` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **MetricCard**
- Add `MetricCard` to `ComponentType` in `src/types/json-ui.ts`.
- Register `MetricCard` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
- [ ] **Timeline**
- Add `Timeline` to `ComponentType` in `src/types/json-ui.ts`.
- Register `Timeline` in `src/lib/json-ui/component-registry.tsx`.
- Add metadata/defaults to `src/lib/component-definitions.ts`.
- Flip status to `supported` in `json-components-registry.json`.
## Framework Enablers
- [ ] **Event binding extensions**
- Expand event/action coverage to support richer interactions via JSON expressions.
- Confirm compatibility with existing `expression` and `valueTemplate` handling.
- [ ] **State binding system**
- Add support for stateful bindings needed by interactive components.
- Document and enforce which components require state binding.
- [ ] **JSON-friendly wrappers**
- Create wrapper components for hook-heavy/side-effect components.
- Register wrappers in the JSON registry instead of direct usage.
- [ ] **Registry normalization**
- Resolve duplicate component entries (e.g., multiple `Breadcrumb` variants) in `json-components-registry.json`.
- [ ] **Showcase schema coverage**
- Add JSON schema examples for each newly supported component to keep demos current.

View File

@@ -1069,7 +1069,7 @@
"category": "feedback",
"canHaveChildren": false,
"description": "Error state badge",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1145,7 +1145,7 @@
"category": "feedback",
"canHaveChildren": true,
"description": "Toast notification",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1182,7 +1182,7 @@
"category": "feedback",
"canHaveChildren": false,
"description": "Status indicator icon",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1200,7 +1200,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Styled data list",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1219,7 +1219,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Advanced data table with sorting and filtering",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1285,7 +1285,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Metric display card",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -1350,7 +1350,7 @@
"category": "data",
"canHaveChildren": false,
"description": "Timeline visualization",
"status": "planned",
"status": "supported",
"source": "atoms"
},
{
@@ -2023,10 +2023,10 @@
}
],
"statistics": {
"total": 217,
"supported": 151,
"planned": 12,
"jsonCompatible": 13,
"total": 219,
"supported": 154,
"planned": 10,
"jsonCompatible": 14,
"maybeJsonCompatible": 41,
"byCategory": {
"layout": 25,

View File

@@ -3,11 +3,13 @@ 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 pageSchemasJson from '@/schemas/page-schemas.json'
import todoListJson from '@/schemas/todo-list.json'
import newMoleculesShowcaseJson from '@/schemas/new-molecules-showcase.json'
const todoListSchema = hydrateSchema(todoListJson)
const newMoleculesShowcaseSchema = hydrateSchema(newMoleculesShowcaseJson)
const dataComponentsDemoSchema = hydrateSchema(pageSchemasJson.dataComponentsDemoSchema)
export function JSONUIShowcasePage() {
return (
@@ -24,7 +26,9 @@ export function JSONUIShowcasePage() {
</div>
<TabsList className="w-full justify-start">
<TabsTrigger value="atomic">Atomic Components</TabsTrigger>
<TabsTrigger value="feedback">Feedback Atoms</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>
@@ -34,10 +38,18 @@ export function JSONUIShowcasePage() {
<TabsContent value="atomic" className="h-full m-0 data-[state=active]:block">
<AtomicComponentDemo />
</TabsContent>
<TabsContent value="feedback" className="h-full m-0 data-[state=active]:block">
<PageRenderer schema={feedbackAtomsDemoSchema} />
</TabsContent>
<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

@@ -7,6 +7,23 @@ export interface ComponentDefinition {
icon: string
defaultProps?: Record<string, any>
canHaveChildren?: boolean
props?: ComponentPropDefinition[]
events?: ComponentEventDefinition[]
}
export interface ComponentPropDefinition {
name: string
type: string
description: string
required?: boolean
defaultValue?: string
options?: string[]
supportsBinding?: boolean
}
export interface ComponentEventDefinition {
name: string
description: string
}
export const componentDefinitions: ComponentDefinition[] = [
@@ -269,6 +286,107 @@ export const componentDefinitions: ComponentDefinition[] = [
icon: 'Circle',
defaultProps: { status: 'active', children: 'Active' }
},
{
type: 'ErrorBadge',
label: 'Error Badge',
category: 'feedback',
icon: 'WarningCircle',
defaultProps: { count: 3, variant: 'destructive', size: 'md' },
props: [
{
name: 'count',
type: 'number',
description: 'Number of errors to display. Hidden when set to 0.',
required: true,
supportsBinding: true,
},
{
name: 'variant',
type: 'string',
description: 'Visual variant for the badge.',
defaultValue: 'destructive',
options: ['default', 'destructive'],
},
{
name: 'size',
type: 'string',
description: 'Badge size.',
defaultValue: 'md',
options: ['sm', 'md'],
},
],
},
{
type: 'Notification',
label: 'Notification',
category: 'feedback',
icon: 'Info',
defaultProps: { type: 'info', title: 'Notification', message: 'Details go here.' },
props: [
{
name: 'type',
type: 'string',
description: 'Notification style variant.',
required: true,
options: ['info', 'success', 'warning', 'error'],
},
{
name: 'title',
type: 'string',
description: 'Primary notification title.',
required: true,
supportsBinding: true,
},
{
name: 'message',
type: 'string',
description: 'Optional supporting message text.',
supportsBinding: true,
},
{
name: 'className',
type: 'string',
description: 'Optional custom classes for spacing or layout tweaks.',
},
],
events: [
{
name: 'onClose',
description: 'Fires when the close button is clicked. Bind to dismiss or trigger an action.',
},
],
},
{
type: 'StatusIcon',
label: 'Status Icon',
category: 'feedback',
icon: 'CheckCircle',
defaultProps: { type: 'saved', size: 14, animate: false },
props: [
{
name: 'type',
type: 'string',
description: 'Status icon style.',
required: true,
supportsBinding: true,
options: ['saved', 'synced'],
},
{
name: 'size',
type: 'number',
description: 'Icon size in pixels.',
defaultValue: '14',
supportsBinding: true,
},
{
name: 'animate',
type: 'boolean',
description: 'Applies entry animation when true.',
defaultValue: 'false',
supportsBinding: true,
},
],
},
// Data Components
{
type: 'List',
@@ -277,6 +395,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',
@@ -284,6 +413,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',
@@ -298,6 +446,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,
}
const breadcrumbComponent = AtomComponents.Breadcrumb ?? AtomComponents.BreadcrumbNav
if (breadcrumbComponent) {

View File

@@ -0,0 +1,212 @@
{
"stateBindingsDemoSchema": {
"id": "state-bindings-demo",
"name": "State & Bindings Demo",
"layout": {
"type": "single"
},
"dataSources": [
{
"id": "statusItems",
"type": "static",
"defaultValue": ["KV Ready", "Components Loaded", "Sync Enabled"]
}
],
"components": [
{
"id": "state-demo-root",
"type": "div",
"props": {
"className": "space-y-4 rounded-lg border border-border bg-card p-6"
},
"children": [
{
"id": "state-demo-title",
"type": "Heading",
"props": {
"className": "text-xl font-semibold",
"children": "Renderer State Binding Demo"
}
},
{
"id": "state-demo-theme",
"type": "Text",
"props": {
"className": "text-sm text-muted-foreground"
},
"bindings": {
"children": {
"sourceType": "state",
"source": "settings",
"path": "settings.theme"
}
}
},
{
"id": "state-demo-list",
"type": "div",
"props": {
"className": "space-y-2"
},
"loop": {
"source": "statusItems",
"itemVar": "statusItem"
},
"children": [
{
"id": "state-demo-list-item",
"type": "Text",
"props": {
"className": "text-sm"
},
"bindings": {
"children": {
"sourceType": "bindings",
"source": "statusItem"
}
}
}
]
}
]
}
]
},
"dataComponentsDemoSchema": {
"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

@@ -1,75 +0,0 @@
import type { PageSchema } from '@/types/json-ui'
export const stateBindingsDemoSchema: PageSchema = {
id: 'state-bindings-demo',
name: 'State & Bindings Demo',
layout: {
type: 'single',
},
dataSources: [
{
id: 'statusItems',
type: 'static',
defaultValue: ['KV Ready', 'Components Loaded', 'Sync Enabled'],
},
],
components: [
{
id: 'state-demo-root',
type: 'div',
props: {
className: 'space-y-4 rounded-lg border border-border bg-card p-6',
},
children: [
{
id: 'state-demo-title',
type: 'Heading',
props: {
className: 'text-xl font-semibold',
children: 'Renderer State Binding Demo',
},
},
{
id: 'state-demo-theme',
type: 'Text',
props: {
className: 'text-sm text-muted-foreground',
},
bindings: {
children: {
sourceType: 'state',
source: 'settings',
path: 'settings.theme',
},
},
},
{
id: 'state-demo-list',
type: 'div',
props: {
className: 'space-y-2',
},
loop: {
source: 'statusItems',
itemVar: 'statusItem',
},
children: [
{
id: 'state-demo-list-item',
type: 'Text',
props: {
className: 'text-sm',
},
bindings: {
children: {
sourceType: 'bindings',
source: 'statusItem',
},
},
},
],
},
],
},
],
}

View File

@@ -6,7 +6,9 @@ export type ComponentType =
| 'Text' | 'Heading' | 'Label' | 'List' | 'Grid' | 'Stack' | 'Flex' | 'Container'
| 'Link' | 'Breadcrumb' | 'Image' | 'Avatar' | 'Code' | 'Tag' | 'Spinner' | 'Skeleton'
| 'Alert' | 'InfoBox' | 'EmptyState' | 'StatusBadge'
| 'ErrorBadge' | 'Notification' | 'StatusIcon'
| 'Table' | 'KeyValue' | 'StatCard' | 'DataCard' | 'SearchInput' | 'ActionBar'
| 'DataList' | 'DataTable' | 'MetricCard' | 'Timeline'
| 'AppBranding' | 'LabelWithBadge' | 'EmptyEditorState' | 'LoadingFallback' | 'LoadingState' | 'NavigationGroupHeader'
export type ActionType =