Merge branch 'main' into codex/identify-json-compatible-components

This commit is contained in:
2026-01-18 12:25:09 +00:00
committed by GitHub
4 changed files with 263 additions and 299 deletions

View File

@@ -10,6 +10,25 @@ The JSON UI system allows you to define user interfaces using JSON schemas inste
- Configuration-driven UIs
- Rapid prototyping
## JSON Eligibility Checklist
Use this checklist during every conversion to determine whether a component can be safely represented in JSON.
### Core requirements (must all be true)
- **No side-effects**: The component should be render-only; it must not perform data fetching, subscriptions, timers, or imperative DOM work. Side-effects belong in wrappers or higher-level orchestration.
- **Supported bindings only**: Props must map to supported JSON bindings (static values, expression bindings, or schema-recognized props). Avoid custom callbacks, ref usage, or passing functions through JSON.
- **Stable props**: Props should be serializable and deterministic (strings, numbers, booleans, arrays, objects). Avoid non-serializable values like class instances, functions, or dates unless explicitly supported by the JSON schema.
### Derived state guidance
- **Prefer pure derivation**: If state can be derived from props or data bindings, compute it at render time in the renderer or via expression bindings rather than introducing component state.
- **Avoid local state in JSON components**: If a component needs internal state to function, wrap it in a React component and expose only the minimal, declarative props to JSON.
- **Keep calculations explicit**: Use named props (e.g., `selectedCount`, `isComplete`) rather than embedding complex logic that would need component state.
### Event handler guidance
- **Use schema-recognized actions**: Wire events to supported JSON actions or expression handlers (e.g., `onClick` -> `actions.navigate`, `actions.submit`), not raw functions.
- **Pass data, not closures**: Event handlers should reference IDs, routes, or action payloads so the runtime can resolve them.
- **Escalate complex logic**: If an event handler needs branching logic or side-effects, move that logic into an app-level action or wrapper component and keep JSON props declarative.
## Quick Start
### List All JSON-Compatible Components

View File

@@ -2,6 +2,13 @@
This task list captures the next steps for expanding JSON UI coverage, split between **component migrations** and **framework enablers**.
## Implementation Notes
- Component trees can live as JSON definitions.
- Custom behavior should be organized into hooks where appropriate.
- Types belong in `types` files; interfaces belong in dedicated `interfaces` files.
- Capture relevant conversion logs during work.
## Component Migration Tasks (Planned → Supported)
### Input Components

View File

@@ -326,6 +326,241 @@ With transformations:
}
```
## Component Pattern Templates
Use these patterns as starting points when authoring JSON schemas. Each example includes
recommended prop shapes and binding strategies for predictable rendering and data flow.
### Form Pattern (Create/Edit)
**Recommended prop shape**
- `name`: field identifier used in data mappings.
- `label`: user-facing label.
- `placeholder`: optional hint text.
- `type`: input type (`text`, `email`, `number`, `date`, etc.).
- `required`: boolean for validation UI.
**Schema example**
```typescript
{
id: 'profile-form',
type: 'form',
props: {
className: 'space-y-4'
},
children: [
{
id: 'first-name',
type: 'Input',
props: {
name: 'firstName',
label: 'First name',
placeholder: 'Ada',
required: true
},
bindings: {
value: { source: 'formState', path: 'firstName' }
},
events: [
{
event: 'change',
actions: [
{
type: 'set-value',
target: 'formState',
path: 'firstName',
compute: (data, event) => event.target.value
}
]
}
]
},
{
id: 'email',
type: 'Input',
props: {
name: 'email',
label: 'Email',
placeholder: 'ada@lovelace.dev',
type: 'email'
},
bindings: {
value: { source: 'formState', path: 'email' }
},
events: [
{
event: 'change',
actions: [
{
type: 'set-value',
target: 'formState',
path: 'email',
compute: (data, event) => event.target.value
}
]
}
]
},
{
id: 'save-profile',
type: 'Button',
props: { children: 'Save profile' },
events: [
{
event: 'click',
actions: [
{
type: 'create',
target: 'profiles',
compute: (data) => ({
id: Date.now(),
...data.formState
})
},
{
type: 'set-value',
target: 'formState',
value: { firstName: '', email: '' }
}
]
}
]
}
]
}
```
**Recommended bindings**
- Use `bindings.value` for inputs and update a single `formState` data source.
- Use `set-value` with `path` to update individual fields and avoid cloning the whole object.
### Card Pattern (Summary/Stat)
**Recommended prop shape**
- `title`: primary label.
- `description`: supporting copy.
- `badge`: optional status tag.
- `icon`: optional leading icon name or component id.
**Schema example**
```typescript
{
id: 'stats-card',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'card-header',
type: 'div',
props: { className: 'flex items-center justify-between' },
children: [
{
id: 'card-title',
type: 'h3',
bindings: {
children: { source: 'stats', path: 'title' }
},
props: { className: 'text-lg font-semibold' }
},
{
id: 'card-badge',
type: 'Badge',
bindings: {
children: { source: 'stats', path: 'status' },
variant: {
source: 'stats',
path: 'status',
transform: (value) => (value === 'Active' ? 'success' : 'secondary')
}
}
}
]
},
{
id: 'card-description',
type: 'p',
props: { className: 'text-sm text-muted-foreground' },
bindings: {
children: { source: 'stats', path: 'description' }
}
}
]
}
```
**Recommended bindings**
- Bind the card text fields directly to a `stats` data source.
- Use `transform` for simple presentation mappings (status to badge variant).
### List Pattern (Collection + Row Actions)
**Recommended prop shape**
- `items`: array data source bound at the list container.
- `keyField`: unique field for list keys.
- `primary`: main text content (usually `name` or `title`).
- `secondary`: supporting text (optional).
- `actions`: array of action configs for row-level events.
**Schema example**
```typescript
{
id: 'task-list',
type: 'div',
bindings: {
children: {
source: 'tasks',
transform: (items) =>
items.map((item) => ({
id: `task-${item.id}`,
type: 'div',
props: { className: 'flex items-center justify-between py-2' },
children: [
{
id: `task-name-${item.id}`,
type: 'span',
bindings: {
children: { source: 'item', path: 'name' }
}
},
{
id: `task-toggle-${item.id}`,
type: 'Button',
props: { size: 'sm', variant: 'outline' },
bindings: {
children: {
source: 'item',
path: 'completed',
transform: (value) => (value ? 'Undo' : 'Complete')
}
},
events: [
{
event: 'click',
actions: [
{
type: 'update',
target: 'tasks',
id: item.id,
compute: (data) => ({
...item,
completed: !item.completed
})
}
]
}
]
}
]
}))
}
}
}
```
**Recommended bindings**
- Use a `transform` to map collection items into child component schemas.
- Use `{ source: 'item', path: 'field' }` when binding inside the item loop for clarity and efficiency.
## Event Handling
### Simple Event

View File

@@ -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:20:47.049Z",
"categories": {
"layout": "Layout and container components",
"input": "Form inputs and interactive controls",
@@ -40,15 +40,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Card",
"name": "Card",
"category": "layout",
"canHaveChildren": true,
"description": "Container card with optional header, content, and footer",
"status": "supported",
"source": "ui"
},
{
"type": "CodeExplanationDialog",
"name": "CodeExplanationDialog",
@@ -125,15 +116,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Drawer",
"name": "Drawer",
"category": "layout",
"canHaveChildren": true,
"description": "Sliding panel overlay",
"status": "supported",
"source": "ui"
},
{
"type": "Flex",
"name": "Flex",
@@ -170,15 +152,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "HoverCard",
"name": "HoverCard",
"category": "layout",
"canHaveChildren": true,
"description": "Card shown on hover",
"status": "supported",
"source": "ui"
},
{
"type": "Modal",
"name": "Modal",
@@ -262,15 +235,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Button",
"name": "Button",
"category": "input",
"canHaveChildren": true,
"description": "Interactive button element",
"status": "supported",
"source": "ui"
},
{
"type": "ButtonGroup",
"name": "ButtonGroup",
@@ -289,15 +253,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Checkbox",
"name": "Checkbox",
"category": "input",
"canHaveChildren": false,
"description": "Checkbox toggle control",
"status": "supported",
"source": "ui"
},
{
"type": "ConfirmButton",
"name": "ConfirmButton",
@@ -352,15 +307,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Form",
"name": "Form",
"category": "input",
"canHaveChildren": true,
"description": "Form container component",
"status": "supported",
"source": "ui"
},
{
"type": "IconButton",
"name": "IconButton",
@@ -379,15 +325,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Input",
"name": "Input",
"category": "input",
"canHaveChildren": false,
"description": "Text input field",
"status": "supported",
"source": "ui"
},
{
"type": "InputOtp",
"name": "InputOtp",
@@ -460,15 +397,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Select",
"name": "Select",
"category": "input",
"canHaveChildren": false,
"description": "Dropdown select control",
"status": "supported",
"source": "ui"
},
{
"type": "Slider",
"name": "Slider",
@@ -478,24 +406,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Slider",
"name": "Slider",
"category": "input",
"canHaveChildren": false,
"description": "Numeric range slider",
"status": "supported",
"source": "ui"
},
{
"type": "Switch",
"name": "Switch",
"category": "input",
"canHaveChildren": false,
"description": "Toggle switch control",
"status": "supported",
"source": "atoms"
},
{
"type": "Switch",
"name": "Switch",
@@ -503,7 +413,7 @@
"canHaveChildren": false,
"description": "Toggle switch control",
"status": "supported",
"source": "ui"
"source": "atoms"
},
{
"type": "TextArea",
@@ -523,15 +433,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Toggle",
"name": "Toggle",
"category": "input",
"canHaveChildren": true,
"description": "Toggle button control",
"status": "supported",
"source": "ui"
},
{
"type": "ToggleGroup",
"name": "ToggleGroup",
@@ -569,15 +470,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Avatar",
"name": "Avatar",
"category": "display",
"canHaveChildren": false,
"description": "User avatar image",
"status": "supported",
"source": "ui"
},
{
"type": "AvatarGroup",
"name": "AvatarGroup",
@@ -596,15 +488,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Badge",
"name": "Badge",
"category": "display",
"canHaveChildren": true,
"description": "Small status or count indicator",
"status": "supported",
"source": "ui"
},
{
"type": "CircularProgress",
"name": "CircularProgress",
@@ -695,15 +578,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Label",
"name": "Label",
"category": "display",
"canHaveChildren": true,
"description": "Form label element",
"status": "supported",
"source": "ui"
},
{
"type": "Progress",
"name": "Progress",
@@ -741,15 +615,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Separator",
"name": "Separator",
"category": "display",
"canHaveChildren": false,
"description": "Visual divider line",
"status": "supported",
"source": "ui"
},
{
"type": "Skeleton",
"name": "Skeleton",
@@ -759,15 +624,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Skeleton",
"name": "Skeleton",
"category": "display",
"canHaveChildren": false,
"description": "Loading skeleton placeholder",
"status": "supported",
"source": "ui"
},
{
"type": "Spinner",
"name": "Spinner",
@@ -849,15 +705,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "ContextMenu",
"name": "ContextMenu",
"category": "navigation",
"canHaveChildren": true,
"description": "Right-click context menu",
"status": "supported",
"source": "ui"
},
{
"type": "DropdownMenu",
"name": "DropdownMenu",
@@ -924,16 +771,6 @@
"source": "molecules",
"jsonCompatible": true
},
{
"type": "NavigationMenu",
"name": "NavigationMenu",
"category": "navigation",
"canHaveChildren": true,
"description": "NavigationMenu component",
"status": "maybe-json-compatible",
"source": "organisms",
"jsonCompatible": true
},
{
"type": "NavigationMenu",
"name": "NavigationMenu",
@@ -961,15 +798,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Tabs",
"name": "Tabs",
"category": "navigation",
"canHaveChildren": true,
"description": "Tabbed interface container",
"status": "supported",
"source": "ui"
},
{
"type": "Alert",
"name": "Alert",
@@ -979,15 +807,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Alert",
"name": "Alert",
"category": "feedback",
"canHaveChildren": true,
"description": "Alert notification message",
"status": "supported",
"source": "ui"
},
{
"type": "CountBadge",
"name": "CountBadge",
@@ -1044,20 +863,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "EmptyState",
"name": "EmptyState",
"category": "feedback",
"canHaveChildren": true,
"description": "Empty state placeholder",
"status": "maybe-json-compatible",
"source": "molecules",
"jsonCompatible": true,
"metadata": {
"conversionPriority": "high",
"notes": "JSON-ready: presentational molecule without hooks/side effects; bindings supported in json-ui component types."
}
},
{
"type": "EmptyStateIcon",
"name": "EmptyStateIcon",
@@ -1133,16 +938,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "LoadingState",
"name": "LoadingState",
"category": "feedback",
"canHaveChildren": true,
"description": "LoadingState component",
"status": "json-compatible",
"source": "molecules",
"jsonCompatible": true
},
{
"type": "Notification",
"name": "Notification",
@@ -1320,20 +1115,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "StatCard",
"name": "StatCard",
"category": "data",
"canHaveChildren": false,
"description": "Statistic card display",
"status": "maybe-json-compatible",
"source": "molecules",
"jsonCompatible": true,
"metadata": {
"conversionPriority": "high",
"notes": "JSON-ready: pure props-driven display molecule; bindings supported in json-ui component types."
}
},
{
"type": "Table",
"name": "Table",
@@ -1343,15 +1124,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Table",
"name": "Table",
"category": "data",
"canHaveChildren": false,
"description": "Data table",
"status": "supported",
"source": "ui"
},
{
"type": "Timeline",
"name": "Timeline",
@@ -1390,15 +1162,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Accordion",
"name": "Accordion",
"category": "custom",
"canHaveChildren": true,
"description": "Collapsible content sections",
"status": "supported",
"source": "ui"
},
{
"type": "ActionBar",
"name": "ActionBar",
@@ -1479,15 +1242,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Calendar",
"name": "Calendar",
"category": "custom",
"canHaveChildren": true,
"description": "Calendar date selector",
"status": "supported",
"source": "ui"
},
{
"type": "CanvasRenderer",
"name": "CanvasRenderer",
@@ -1718,16 +1472,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "PageHeader",
"name": "PageHeader",
"category": "custom",
"canHaveChildren": true,
"description": "PageHeader component",
"status": "json-compatible",
"source": "organisms",
"jsonCompatible": true
},
{
"type": "PageHeaderContent",
"name": "PageHeaderContent",
@@ -1765,15 +1509,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "Popover",
"name": "Popover",
"category": "custom",
"canHaveChildren": true,
"description": "Popover overlay content",
"status": "supported",
"source": "ui"
},
{
"type": "PropertyEditor",
"name": "PropertyEditor",
@@ -1889,15 +1624,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "ScrollArea",
"name": "ScrollArea",
"category": "custom",
"canHaveChildren": true,
"description": "Scrollable container area",
"status": "supported",
"source": "ui"
},
{
"type": "SearchBar",
"name": "SearchBar",
@@ -1917,20 +1643,6 @@
"status": "supported",
"source": "atoms"
},
{
"type": "SearchInput",
"name": "SearchInput",
"category": "custom",
"canHaveChildren": false,
"description": "Search input with icon",
"status": "maybe-json-compatible",
"source": "molecules",
"jsonCompatible": true,
"metadata": {
"conversionPriority": "high",
"notes": "JSON-ready: controlled input component; event bindings supported in json-ui component types."
}
},
{
"type": "Sheet",
"name": "Sheet",
@@ -2031,15 +1743,6 @@
"description": "Tooltip overlay text",
"status": "supported",
"source": "atoms"
},
{
"type": "Tooltip",
"name": "Tooltip",
"category": "custom",
"canHaveChildren": true,
"description": "Tooltip overlay text",
"status": "supported",
"source": "ui"
}
],
"statistics": {