diff --git a/JSON_COMPONENTS.md b/JSON_COMPONENTS.md index f2e943e..b4961ae 100644 --- a/JSON_COMPONENTS.md +++ b/JSON_COMPONENTS.md @@ -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 diff --git a/docs/JSON_COMPONENT_CONVERSION_TASKS.md b/docs/JSON_COMPONENT_CONVERSION_TASKS.md index f312ed4..c304dc4 100644 --- a/docs/JSON_COMPONENT_CONVERSION_TASKS.md +++ b/docs/JSON_COMPONENT_CONVERSION_TASKS.md @@ -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 diff --git a/docs/JSON_UI_GUIDE.md b/docs/JSON_UI_GUIDE.md index de33a1e..272034f 100644 --- a/docs/JSON_UI_GUIDE.md +++ b/docs/JSON_UI_GUIDE.md @@ -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 diff --git a/json-components-registry.json b/json-components-registry.json index 8ed9958..94efde8 100644 --- a/json-components-registry.json +++ b/json-components-registry.json @@ -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": {