mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 04:18:15 +08:00
Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25001ac0e2 | ||
|
|
2e72fdb599 | ||
|
|
11bfd1073d | ||
|
|
ef3e2dbeb1 | ||
|
|
16b64e63f1 | ||
|
|
3a487f0558 | ||
|
|
ffcb188f87 | ||
|
|
52aaa303c9 | ||
|
|
6b7fdd966a | ||
|
|
46b549e4a1 | ||
|
|
ec51ddfa1b | ||
|
|
13a01aa4d9 | ||
|
|
83f33e3590 | ||
|
|
a17f8187e6 | ||
|
|
847b9828bf | ||
|
|
34afc7de80 | ||
|
|
1d86165fc9 | ||
|
|
1d8f4764cc | ||
|
|
dadaa0070b | ||
|
|
59dfdfcdcc | ||
|
|
36242cd8ed | ||
|
|
5a89d426be | ||
|
|
d8218351d2 | ||
|
|
f2a36a0961 | ||
|
|
08113f2848 | ||
|
|
0d89afe4a5 | ||
|
|
a60552a739 | ||
|
|
46acfac24b | ||
|
|
895eaedca9 | ||
|
|
c3c34d47cf | ||
|
|
75eaf1ee8e | ||
|
|
ecf2396288 | ||
|
|
3986cf43c9 | ||
|
|
a4d7a156c9 | ||
|
|
2afe371671 | ||
|
|
aa721103f3 | ||
|
|
bad0b5b3e8 | ||
|
|
0cb9dd4094 | ||
|
|
f7c1eaa92e | ||
|
|
11adca259b | ||
|
|
342f78c9b1 | ||
|
|
f18153f22b | ||
|
|
b80f210b95 | ||
|
|
7ed07725b7 | ||
|
|
056710ce1c | ||
|
|
c426040841 | ||
|
|
23ac3cd9f7 | ||
|
|
185ebdc086 | ||
|
|
d26564c176 | ||
|
|
aa62208e91 | ||
|
|
f776f14e8c | ||
|
|
58c59b7e35 | ||
|
|
047df9e942 | ||
|
|
d17c729719 | ||
|
|
e39ba6811d | ||
|
|
85234407dc | ||
|
|
18ea9e701f | ||
|
|
5f7545342d |
@@ -1,25 +1,22 @@
|
||||
# json-render
|
||||
|
||||
**The framework for User-Generated Interfaces (UGI).**
|
||||
**Predictable. Guardrailed. Fast.**
|
||||
|
||||
Dynamic, personalized UIs per user without sacrificing reliability. Predefined components and actions for safe, predictable output.
|
||||
Let end users generate dashboards, widgets, apps, and videos from prompts — safely constrained to components you define.
|
||||
|
||||
```bash
|
||||
npm install @json-render/core @json-render/react
|
||||
# or for mobile
|
||||
npm install @json-render/core @json-render/react-native
|
||||
# or for video
|
||||
npm install @json-render/core @json-render/remotion
|
||||
```
|
||||
|
||||
## Why json-render?
|
||||
|
||||
json-render enables **User-Generated Interfaces**: dynamic UIs that end users create through natural language prompts, powered by Generative UI. You define the guardrails, AI generates within them:
|
||||
When users prompt for UI, you need guarantees. json-render gives AI a **constrained vocabulary** so output is always predictable:
|
||||
|
||||
- **Guardrailed** - AI can only use components in your catalog
|
||||
- **Predictable** - JSON output matches your schema, every time
|
||||
- **Fast** - Stream and render progressively as the model responds
|
||||
- **Cross-Platform** - React (web) and React Native (mobile) from the same catalog
|
||||
- **Guardrailed** — AI can only use components in your catalog
|
||||
- **Predictable** — JSON output matches your schema, every time
|
||||
- **Fast** — Stream and render progressively as the model responds
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -78,8 +75,8 @@ const { registry } = defineRegistry(catalog, {
|
||||
<span>{format(props.value, props.format)}</span>
|
||||
</div>
|
||||
),
|
||||
Button: ({ props, emit }) => (
|
||||
<button onClick={() => emit?.("press")}>
|
||||
Button: ({ props, onAction }) => (
|
||||
<button onClick={() => onAction?.({ name: props.action })}>
|
||||
{props.label}
|
||||
</button>
|
||||
),
|
||||
@@ -132,27 +129,6 @@ const { registry } = defineRegistry(catalog, { components });
|
||||
<Renderer spec={spec} registry={registry} />
|
||||
```
|
||||
|
||||
### React Native (Mobile)
|
||||
|
||||
```tsx
|
||||
import { defineCatalog } from "@json-render/core";
|
||||
import { schema } from "@json-render/react-native/schema";
|
||||
import {
|
||||
standardComponentDefinitions,
|
||||
standardActionDefinitions,
|
||||
} from "@json-render/react-native/catalog";
|
||||
import { defineRegistry, Renderer } from "@json-render/react-native";
|
||||
|
||||
// 25+ standard components included
|
||||
const catalog = defineCatalog(schema, {
|
||||
components: { ...standardComponentDefinitions },
|
||||
actions: standardActionDefinitions,
|
||||
});
|
||||
|
||||
const { registry } = defineRegistry(catalog, { components: {} });
|
||||
<Renderer spec={spec} registry={registry} />
|
||||
```
|
||||
|
||||
### Remotion (Video)
|
||||
|
||||
```tsx
|
||||
@@ -238,8 +214,8 @@ Any prop value can be data-driven using expressions:
|
||||
|
||||
Two expression forms:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** - reads a value from the data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a condition (same syntax as visibility conditions) and picks a branch
|
||||
- **`{ "$path": "/state/key" }`** -- reads a value from the data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a condition (same syntax as visibility conditions) and picks a branch
|
||||
|
||||
### Actions
|
||||
|
||||
@@ -253,7 +229,7 @@ Components can trigger actions, including the built-in `setState` action:
|
||||
}
|
||||
```
|
||||
|
||||
The `setState` action updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions.
|
||||
The `setState` action updates the data model directly, which re-evaluates visibility conditions and dynamic prop expressions.
|
||||
|
||||
---
|
||||
|
||||
@@ -266,10 +242,9 @@ pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- http://localhost:3000 - Docs & Playground
|
||||
- http://localhost:3001 - Example Dashboard
|
||||
- http://localhost:3002 - Remotion Video Example
|
||||
- React Native example: run `npx expo start` in `examples/react-native`
|
||||
- http://localhost:3000 — Docs & Playground
|
||||
- http://localhost:3001 — Example Dashboard
|
||||
- http://localhost:3002 — Remotion Video Example
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -284,10 +259,10 @@ flowchart LR
|
||||
D -.- G([streamed])
|
||||
```
|
||||
|
||||
1. **Define the guardrails** - what components, actions, and data bindings AI can use
|
||||
2. **Users generate** - end users describe what they want in natural language
|
||||
3. **AI generates JSON** - output is always predictable, constrained to your catalog
|
||||
4. **Render fast** - stream and render progressively as the model responds
|
||||
1. **Define the guardrails** — what components, actions, and data bindings AI can use
|
||||
2. **Users prompt** — end users describe what they want in natural language
|
||||
3. **AI generates JSON** — output is always predictable, constrained to your catalog
|
||||
4. **Render fast** — stream and render progressively as the model responds
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
export const metadata = { title: "@json-render/react-native API" }
|
||||
|
||||
# @json-render/react-native
|
||||
|
||||
React Native renderer with standard components, providers, and hooks.
|
||||
|
||||
## Standard Components
|
||||
|
||||
### Layout
|
||||
|
||||
| Component | Props | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `Container` | `padding`, `background`, `borderRadius`, `borderColor`, `flex` | Basic wrapper with styling |
|
||||
| `Row` | `gap`, `align`, `justify`, `flex`, `wrap` | Horizontal flex layout |
|
||||
| `Column` | `gap`, `align`, `justify`, `flex` | Vertical flex layout |
|
||||
| `ScrollContainer` | `direction` | Scrollable area (vertical or horizontal) |
|
||||
| `SafeArea` | `edges` | Safe area insets for notch/home indicator |
|
||||
| `Pressable` | `action`, `actionParams` | Touchable wrapper that triggers actions |
|
||||
| `Spacer` | `size`, `flex` | Fixed or flexible spacing |
|
||||
| `Divider` | `color`, `thickness` | Thin line separator |
|
||||
|
||||
### Content
|
||||
|
||||
| Component | Props | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `Heading` | `text`, `level`, `align`, `color` | Heading text (levels 1-6) |
|
||||
| `Paragraph` | `text`, `align`, `color` | Body text |
|
||||
| `Label` | `text`, `color`, `bold` | Small label text |
|
||||
| `Image` | `uri`, `width`, `height`, `resizeMode`, `borderRadius` | Image display |
|
||||
| `Avatar` | `uri`, `size`, `fallback` | Circular avatar |
|
||||
| `Badge` | `label`, `color`, `textColor` | Status badge |
|
||||
| `Chip` | `label`, `selected`, `color` | Tag/chip |
|
||||
|
||||
### Input
|
||||
|
||||
| Component | Props | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `Button` | `label`, `variant`, `size`, `disabled`, `action`, `actionParams` | Pressable button |
|
||||
| `TextInput` | `placeholder`, `statePath`, `secure`, `keyboardType`, `multiline` | Text input field |
|
||||
| `Switch` | `statePath`, `label` | Toggle switch |
|
||||
| `Checkbox` | `statePath`, `label` | Checkbox with label |
|
||||
| `Slider` | `statePath`, `min`, `max`, `step` | Range slider |
|
||||
| `SearchBar` | `placeholder`, `statePath` | Search input |
|
||||
|
||||
### Feedback
|
||||
|
||||
| Component | Props | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `Spinner` | `size`, `color` | Loading indicator |
|
||||
| `ProgressBar` | `progress`, `color`, `trackColor` | Progress indicator |
|
||||
|
||||
### Composite
|
||||
|
||||
| Component | Props | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `Card` | `title`, `subtitle`, `padding` | Card container |
|
||||
| `ListItem` | `title`, `subtitle`, `leading`, `trailing`, `action`, `actionParams` | List row |
|
||||
| `Modal` | `visible`, `title` | Bottom sheet modal |
|
||||
|
||||
## Providers
|
||||
|
||||
### StateProvider
|
||||
|
||||
```tsx
|
||||
<StateProvider initialState={object}>
|
||||
{children}
|
||||
</StateProvider>
|
||||
```
|
||||
|
||||
### ActionProvider
|
||||
|
||||
```tsx
|
||||
<ActionProvider handlers={Record<string, ActionHandler>}>
|
||||
{children}
|
||||
</ActionProvider>
|
||||
```
|
||||
|
||||
### VisibilityProvider
|
||||
|
||||
```tsx
|
||||
<VisibilityProvider>
|
||||
{children}
|
||||
</VisibilityProvider>
|
||||
```
|
||||
|
||||
### ValidationProvider
|
||||
|
||||
```tsx
|
||||
<ValidationProvider>
|
||||
{children}
|
||||
</ValidationProvider>
|
||||
```
|
||||
|
||||
## defineRegistry
|
||||
|
||||
Create a type-safe component registry. Standard components are built-in; only register custom components.
|
||||
|
||||
```tsx
|
||||
import { defineRegistry, type Components } from '@json-render/react-native';
|
||||
|
||||
const { registry } = defineRegistry(catalog, {
|
||||
components: {
|
||||
Icon: ({ props }) => <Ionicons name={props.name} size={props.size ?? 24} />,
|
||||
} as Components<typeof catalog>,
|
||||
});
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### useUIStream
|
||||
|
||||
```typescript
|
||||
const {
|
||||
spec, // Spec | null - current UI state
|
||||
isStreaming, // boolean - true while streaming
|
||||
error, // Error | null
|
||||
send, // (prompt: string) => Promise<void>
|
||||
clear, // () => void - reset spec and error
|
||||
} = useUIStream({
|
||||
api: string,
|
||||
onComplete?: (spec: Spec) => void,
|
||||
onError?: (error: Error) => void,
|
||||
});
|
||||
```
|
||||
|
||||
### useStateStore
|
||||
|
||||
```typescript
|
||||
const { state, get, set, update } = useStateStore();
|
||||
```
|
||||
|
||||
### useStateValue
|
||||
|
||||
```typescript
|
||||
const value = useStateValue(path: string);
|
||||
```
|
||||
|
||||
### useStateBinding
|
||||
|
||||
```typescript
|
||||
const [value, setValue] = useStateBinding(path: string);
|
||||
```
|
||||
|
||||
### useActions
|
||||
|
||||
```typescript
|
||||
const { execute } = useActions();
|
||||
```
|
||||
|
||||
### useIsVisible
|
||||
|
||||
```typescript
|
||||
const isVisible = useIsVisible(condition?: VisibilityCondition);
|
||||
```
|
||||
|
||||
## Catalog Exports
|
||||
|
||||
```typescript
|
||||
import { standardComponentDefinitions, standardActionDefinitions } from "@json-render/react-native/catalog";
|
||||
import { schema } from "@json-render/react-native/schema";
|
||||
```
|
||||
|
||||
| Export | Purpose |
|
||||
|--------|---------|
|
||||
| `standardComponentDefinitions` | Catalog definitions for all 25+ standard components |
|
||||
| `standardActionDefinitions` | Catalog definitions for standard actions (setState, navigate) |
|
||||
| `schema` | React Native element tree schema |
|
||||
@@ -49,7 +49,7 @@ type ValidatorFn = (value: unknown, args?: object) => boolean | Promise<boolean>
|
||||
|
||||
## defineRegistry
|
||||
|
||||
Create a type-safe component registry from a catalog. Components receive `props`, `children`, `emit`, and `loading` with catalog-inferred types.
|
||||
Create a type-safe component registry from a catalog. Components receive `props`, `children`, `onAction`, and `loading` with catalog-inferred types.
|
||||
|
||||
```tsx
|
||||
import { defineRegistry } from '@json-render/react';
|
||||
@@ -57,8 +57,8 @@ import { defineRegistry } from '@json-render/react';
|
||||
const { registry } = defineRegistry(catalog, {
|
||||
components: {
|
||||
Card: ({ props, children }) => <div>{props.title}{children}</div>,
|
||||
Button: ({ props, emit }) => (
|
||||
<button onClick={() => emit?.("press")}>
|
||||
Button: ({ props, onAction }) => (
|
||||
<button onClick={() => onAction?.({ name: props.action })}>
|
||||
{props.label}
|
||||
</button>
|
||||
),
|
||||
@@ -90,7 +90,7 @@ type Registry = Record<string, React.ComponentType<ComponentRenderProps>>;
|
||||
interface ComponentContext<P> {
|
||||
props: P; // Typed props from catalog
|
||||
children?: React.ReactNode; // Rendered children (for slot components)
|
||||
emit?: (event: string) => void; // Emit a named event
|
||||
onAction?: (action: { name: string; params?: object }) => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,125 +4,6 @@ export const metadata = { title: "Changelog" }
|
||||
|
||||
Notable changes and updates to json-render.
|
||||
|
||||
## v0.5.0
|
||||
|
||||
February 2026
|
||||
|
||||
### New: @json-render/react-native
|
||||
|
||||
Full React Native renderer with 25+ standard components, data binding, visibility, actions, and dynamic props. Build AI-generated native mobile UIs with the same catalog-driven approach as web.
|
||||
|
||||
```tsx
|
||||
import { defineCatalog } from "@json-render/core";
|
||||
import { schema } from "@json-render/react-native/schema";
|
||||
import {
|
||||
standardComponentDefinitions,
|
||||
standardActionDefinitions,
|
||||
} from "@json-render/react-native/catalog";
|
||||
import { defineRegistry, Renderer } from "@json-render/react-native";
|
||||
|
||||
const catalog = defineCatalog(schema, {
|
||||
components: { ...standardComponentDefinitions },
|
||||
actions: standardActionDefinitions,
|
||||
});
|
||||
|
||||
const { registry } = defineRegistry(catalog, { components: {} });
|
||||
|
||||
<Renderer spec={spec} registry={registry} />
|
||||
```
|
||||
|
||||
Includes standard components for layout (Container, Row, Column, ScrollContainer, SafeArea, Pressable, Spacer, Divider), content (Heading, Paragraph, Label, Image, Avatar, Badge, Chip), input (Button, TextInput, Switch, Checkbox, Slider, SearchBar), feedback (Spinner, ProgressBar), and composite (Card, ListItem, Modal).
|
||||
|
||||
### New: Event System
|
||||
|
||||
Components now use `emit` to fire named events instead of directly dispatching actions. The element's `on` field maps events to action bindings, decoupling component logic from action handling.
|
||||
|
||||
```tsx
|
||||
// Component emits a named event
|
||||
Button: ({ props, emit }) => (
|
||||
<button onClick={() => emit?.("press")}>{props.label}</button>
|
||||
),
|
||||
|
||||
// Element spec maps events to actions
|
||||
{
|
||||
"type": "Button",
|
||||
"props": { "label": "Submit" },
|
||||
"on": { "press": { "action": "submit", "params": { "formId": "main" } } }
|
||||
}
|
||||
```
|
||||
|
||||
### New: Repeat/List Rendering
|
||||
|
||||
Elements can now iterate over state arrays using the `repeat` field. Child elements use `$item` and `$index` tokens in `$path` expressions to reference the current item.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Column",
|
||||
"repeat": { "path": "/posts", "key": "id" },
|
||||
"children": ["post-card"]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Card",
|
||||
"props": { "title": { "$path": "$item/title" } }
|
||||
}
|
||||
```
|
||||
|
||||
### New: User Prompt Builder
|
||||
|
||||
Build structured user prompts with optional spec refinement and state context:
|
||||
|
||||
```typescript
|
||||
import { buildUserPrompt } from "@json-render/core";
|
||||
|
||||
// Fresh generation
|
||||
buildUserPrompt({ prompt: "create a todo app" });
|
||||
|
||||
// Refinement (patch-only mode)
|
||||
buildUserPrompt({ prompt: "add a toggle", currentSpec: spec });
|
||||
|
||||
// With runtime state
|
||||
buildUserPrompt({ prompt: "show data", state: { todos: [] } });
|
||||
```
|
||||
|
||||
### New: Spec Validation
|
||||
|
||||
Validate spec structure and auto-fix common issues:
|
||||
|
||||
```typescript
|
||||
import { validateSpec, autoFixSpec } from "@json-render/core";
|
||||
|
||||
const { valid, issues } = validateSpec(spec, catalog);
|
||||
const fixed = autoFixSpec(spec);
|
||||
```
|
||||
|
||||
### Improved: State Management
|
||||
|
||||
`DataProvider` has been renamed to `StateProvider` with a clearer API. State is now a first-class part of specs. Elements can bind to state via `$path` expressions, and the built-in `setState` action updates state directly.
|
||||
|
||||
### Improved: AI Prompts
|
||||
|
||||
Schema prompts now include streaming best practices, repeat/list examples, and state patching guidance. Schemas can also define `defaultRules` that are always included in generated prompts.
|
||||
|
||||
### Improved: Documentation
|
||||
|
||||
- All documentation pages migrated to MDX
|
||||
- AI-powered documentation chat
|
||||
- Dynamic Open Graph images for all docs pages
|
||||
- Improved playground
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `DataProvider` renamed to `StateProvider`
|
||||
- `useData` renamed to `useStateStore`, `useDataValue` to `useStateValue`, `useDataBinding` to `useStateBinding`
|
||||
- `onAction` renamed to `emit` in component context
|
||||
- `DataModel` type renamed to `StateModel`
|
||||
- `Action` type renamed to `ActionBinding` (old name still available but deprecated)
|
||||
|
||||
---
|
||||
|
||||
## v0.4.0
|
||||
|
||||
February 2026
|
||||
|
||||
@@ -8,10 +8,6 @@ Install the core package plus your renderer of choice.
|
||||
|
||||
<PackageInstall packages="@json-render/core @json-render/react" />
|
||||
|
||||
## For React Native
|
||||
|
||||
<PackageInstall packages="@json-render/core @json-render/react-native" />
|
||||
|
||||
## For Remotion Video
|
||||
|
||||
<PackageInstall packages="@json-render/core @json-render/remotion remotion @remotion/player" />
|
||||
|
||||
@@ -2,19 +2,17 @@ export const metadata = { title: "Introduction" }
|
||||
|
||||
# Introduction
|
||||
|
||||
The framework for User-Generated Interfaces (UGI). Dynamic, personalized UIs per user without sacrificing reliability.
|
||||
Predictable. Guardrailed. Fast. Let users generate dashboards, widgets, apps, and data visualizations from prompts.
|
||||
|
||||
## What is json-render?
|
||||
|
||||
json-render is the framework for **User-Generated Interfaces**: dynamic UIs that end users generate through natural language prompts, powered by Generative UI. You define the guardrails: what components exist, what props they take, what actions are available. AI generates JSON that matches your schema, and your components render it natively on web or mobile.
|
||||
|
||||
Every interface is unique to the user, but every interface is safe and predictable.
|
||||
json-render lets end users generate UI from natural language prompts — safely constrained to components you define. You set the guardrails: what components exist, what props they take, what actions are available. AI generates JSON that matches your schema, and your components render it natively.
|
||||
|
||||
## Why json-render?
|
||||
|
||||
### Guardrailed
|
||||
|
||||
AI can only use components in your catalog. No arbitrary code generation. Predefined components and actions for safe, predictable output.
|
||||
AI can only use components in your catalog. No arbitrary code generation.
|
||||
|
||||
### Predictable
|
||||
|
||||
@@ -24,13 +22,9 @@ JSON output matches your schema, every time. Actions are declared by name, you c
|
||||
|
||||
Stream and render progressively as the model responds. No waiting for completion.
|
||||
|
||||
### Cross-Platform
|
||||
|
||||
Render on web with React and on mobile with React Native from the same catalog and spec format.
|
||||
|
||||
## How it works
|
||||
|
||||
1. Define the guardrails - what components, actions, and data bindings AI can use
|
||||
2. Users generate - end users describe what they want in natural language
|
||||
3. AI generates JSON - output is always predictable, constrained to your catalog
|
||||
4. Render fast - stream and render progressively as the model responds
|
||||
1. Define the guardrails — what components, actions, and data bindings AI can use
|
||||
2. Users prompt — end users describe what they want in natural language
|
||||
3. AI generates JSON — output is always predictable, constrained to your catalog
|
||||
4. Render fast — stream and render progressively as the model responds
|
||||
|
||||
@@ -53,7 +53,7 @@ export const catalog = defineCatalog(schema, {
|
||||
|
||||
## 2. Define your components
|
||||
|
||||
Use `defineRegistry` to map catalog types to React components. Each component receives type-safe `props`, `children`, and `emit`:
|
||||
Use `defineRegistry` to map catalog types to React components. Each component receives type-safe `props`, `children`, and `onAction`:
|
||||
|
||||
```tsx
|
||||
// lib/registry.tsx
|
||||
@@ -71,10 +71,10 @@ export const { registry } = defineRegistry(catalog, {
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
Button: ({ props, emit }) => (
|
||||
Button: ({ props, onAction }) => (
|
||||
<button
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => emit?.("press")}
|
||||
onClick={() => onAction?.({ name: props.action })}
|
||||
>
|
||||
{props.label}
|
||||
</button>
|
||||
|
||||
@@ -9,15 +9,12 @@ export default function Home() {
|
||||
<>
|
||||
{/* Hero */}
|
||||
<section className="max-w-5xl mx-auto px-6 pt-24 pb-16 text-center">
|
||||
<p className="text-xs sm:text-sm font-medium text-muted-foreground tracking-widest uppercase mb-4">
|
||||
The framework for User-Generated Interfaces
|
||||
</p>
|
||||
<h1 className="text-4xl sm:text-6xl md:text-7xl font-bold tracking-tighter mb-6">
|
||||
<h1 className="text-5xl sm:text-6xl md:text-7xl font-bold tracking-tighter mb-6">
|
||||
AI → json-render → UI
|
||||
</h1>
|
||||
<p className="text-lg text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed">
|
||||
Dynamic, personalized UIs per user without sacrificing reliability.
|
||||
Predefined components and actions for safe, predictable output.
|
||||
Define a component catalog. Users prompt. AI outputs JSON constrained
|
||||
to your catalog. Your components render it.
|
||||
</p>
|
||||
|
||||
<Demo />
|
||||
@@ -68,10 +65,10 @@ export default function Home() {
|
||||
<div className="text-xs text-muted-foreground font-mono mb-3">
|
||||
02
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Users Generate</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">Users Prompt</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
End users describe what they want. AI generates JSON constrained
|
||||
to your catalog. Every interface is unique to the user.
|
||||
to your catalog.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -247,10 +244,6 @@ export default function Page() {
|
||||
<h2 className="text-2xl font-semibold mb-12 text-center">Features</h2>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
title: "User-Generated Interfaces",
|
||||
desc: "Dynamic, personalized UIs per user powered by Generative UI",
|
||||
},
|
||||
{
|
||||
title: "Guardrails",
|
||||
desc: "AI can only use components you define in the catalog",
|
||||
@@ -260,16 +253,20 @@ export default function Page() {
|
||||
desc: "Progressive rendering as JSON streams from the model",
|
||||
},
|
||||
{
|
||||
title: "React & React Native",
|
||||
desc: "Render on web and mobile from the same catalog and spec format",
|
||||
title: "Code Export",
|
||||
desc: "Export as standalone React code with no runtime dependencies",
|
||||
},
|
||||
{
|
||||
title: "Data Binding",
|
||||
desc: "Two-way state binding with dynamic prop expressions",
|
||||
desc: "Two-way binding with JSON Pointer paths",
|
||||
},
|
||||
{
|
||||
title: "Code Export",
|
||||
desc: "Export as standalone React code with no runtime dependencies",
|
||||
title: "Actions",
|
||||
desc: "Named actions handled by your application",
|
||||
},
|
||||
{
|
||||
title: "Visibility",
|
||||
desc: "Conditional show/hide based on data or auth",
|
||||
},
|
||||
].map((feature) => (
|
||||
<div key={feature.title}>
|
||||
|
||||
+6
-10
@@ -22,15 +22,11 @@ export const metadata: Metadata = {
|
||||
template: "%s | json-render",
|
||||
},
|
||||
description:
|
||||
"The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
|
||||
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.",
|
||||
keywords: [
|
||||
"json-render",
|
||||
"UGI",
|
||||
"User-Generated Interfaces",
|
||||
"AI UI generation",
|
||||
"generative UI",
|
||||
"React components",
|
||||
"React Native",
|
||||
"guardrails",
|
||||
"structured output",
|
||||
"dashboard builder",
|
||||
@@ -42,23 +38,23 @@ export const metadata: Metadata = {
|
||||
locale: "en_US",
|
||||
url: "https://json-render.dev",
|
||||
siteName: "json-render",
|
||||
title: "json-render | The framework for User-Generated Interfaces",
|
||||
title: "json-render | AI-generated UI with guardrails",
|
||||
description:
|
||||
"The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
|
||||
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.",
|
||||
images: [
|
||||
{
|
||||
url: "/og",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "json-render - The framework for User-Generated Interfaces",
|
||||
alt: "json-render - AI-generated UI with guardrails",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "json-render | The framework for User-Generated Interfaces",
|
||||
title: "json-render | AI-generated UI with guardrails",
|
||||
description:
|
||||
"The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
|
||||
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.",
|
||||
images: ["/og"],
|
||||
creator: "@verabornnot",
|
||||
},
|
||||
|
||||
@@ -66,27 +66,23 @@ export async function renderOgImage(title: string) {
|
||||
style={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{title.split("\n").map((line, i) => (
|
||||
<span
|
||||
key={i}
|
||||
style={{
|
||||
fontSize: 72,
|
||||
fontFamily: "Geist",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
letterSpacing: "-0.02em",
|
||||
textAlign: "center",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{line}
|
||||
</span>
|
||||
))}
|
||||
<span
|
||||
style={{
|
||||
fontSize: 72,
|
||||
fontFamily: "Geist",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
letterSpacing: "-0.02em",
|
||||
textAlign: "center",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
|
||||
@@ -39,11 +39,6 @@ export const docsNavigation: NavSection[] = [
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/dashboard",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "React Native",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/react-native",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "Remotion",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/remotion",
|
||||
@@ -74,7 +69,6 @@ export const docsNavigation: NavSection[] = [
|
||||
items: [
|
||||
{ title: "@json-render/core", href: "/docs/api/core" },
|
||||
{ title: "@json-render/react", href: "/docs/api/react" },
|
||||
{ title: "@json-render/react-native", href: "/docs/api/react-native" },
|
||||
{ title: "@json-render/remotion", href: "/docs/api/remotion" },
|
||||
{ title: "@json-render/codegen", href: "/docs/api/codegen" },
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
export const PAGE_TITLES: Record<string, string> = {
|
||||
// Home (no slug)
|
||||
"": "The framework for\nUser-Generated Interfaces",
|
||||
"": "AI-generated UI with guardrails",
|
||||
|
||||
// Top-level
|
||||
playground: "Playground",
|
||||
@@ -36,7 +36,6 @@ export const PAGE_TITLES: Record<string, string> = {
|
||||
// API references
|
||||
"docs/api/core": "@json-render/core API",
|
||||
"docs/api/react": "@json-render/react API",
|
||||
"docs/api/react-native": "@json-render/react-native API",
|
||||
"docs/api/codegen": "@json-render/codegen API",
|
||||
"docs/api/remotion": "@json-render/remotion API",
|
||||
};
|
||||
|
||||
@@ -823,12 +823,7 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
const [localValue, setLocalValue] = useState<string>("");
|
||||
const value = props.statePath ? (boundValue ?? "") : localValue;
|
||||
const setValue = props.statePath ? setBoundValue! : setLocalValue;
|
||||
const rawOptions = props.options ?? [];
|
||||
// Coerce options to strings – AI may produce objects/numbers instead of
|
||||
// plain strings which would cause duplicate `[object Object]` keys.
|
||||
const options = rawOptions.map((opt) =>
|
||||
typeof opt === "string" ? opt : String(opt ?? ""),
|
||||
);
|
||||
const options = props.options ?? [];
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
@@ -844,11 +839,8 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
<SelectValue placeholder={props.placeholder ?? "Select..."} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((opt, idx) => (
|
||||
<SelectItem
|
||||
key={`${idx}-${opt}`}
|
||||
value={opt || `option-${idx}`}
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -884,10 +876,7 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
},
|
||||
|
||||
Radio: ({ props, emit }) => {
|
||||
const rawOptions = props.options ?? [];
|
||||
const options = rawOptions.map((opt) =>
|
||||
typeof opt === "string" ? opt : String(opt ?? ""),
|
||||
);
|
||||
const options = props.options ?? [];
|
||||
const [boundValue, setBoundValue] = props.statePath
|
||||
? useStateBinding<string>(props.statePath) // eslint-disable-line react-hooks/rules-of-hooks
|
||||
: [undefined, undefined];
|
||||
@@ -905,17 +894,11 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
emit?.("change");
|
||||
}}
|
||||
>
|
||||
{options.map((opt, idx) => (
|
||||
<div
|
||||
key={`${idx}-${opt}`}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={opt || `option-${idx}`}
|
||||
id={`${props.name}-${idx}-${opt}`}
|
||||
/>
|
||||
{options.map((opt) => (
|
||||
<div key={opt} className="flex items-center space-x-2">
|
||||
<RadioGroupItem value={opt} id={`${props.name}-${opt}`} />
|
||||
<Label
|
||||
htmlFor={`${props.name}-${idx}-${opt}`}
|
||||
htmlFor={`${props.name}-${opt}`}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{opt}
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
# @json-render/codegen
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @json-render/core@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3d2d1ad: Add @json-render/react-native package, event system (emit replaces onAction), repeat/list rendering, user prompt builder, spec validation, and rename DataProvider to StateProvider.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3d2d1ad]
|
||||
- @json-render/core@0.5.0
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dd17549: remove key/parentKey from flat specs, RFC 6902 compliance for SpecStream
|
||||
- Updated dependencies [dd17549]
|
||||
- @json-render/core@0.4.4
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/codegen",
|
||||
"version": "0.5.1",
|
||||
"version": "0.4.3",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Utilities for generating code from json-render UI trees",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
# @json-render/core
|
||||
|
||||
## 0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3d2d1ad: Add @json-render/react-native package, event system (emit replaces onAction), repeat/list rendering, user prompt builder, spec validation, and rename DataProvider to StateProvider.
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dd17549: remove key/parentKey from flat specs, RFC 6902 compliance for SpecStream
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -213,7 +213,7 @@ Any prop value can be a dynamic expression that resolves based on data state at
|
||||
|
||||
### Data Binding (`$path`)
|
||||
|
||||
Read a value directly from the state model:
|
||||
Read a value directly from the data model:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/core",
|
||||
"version": "0.5.1",
|
||||
"version": "0.4.3",
|
||||
"license": "Apache-2.0",
|
||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# @json-render/react-native
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
|
||||
- @json-render/core@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3d2d1ad: Add @json-render/react-native package, event system (emit replaces onAction), repeat/list rendering, user prompt builder, spec validation, and rename DataProvider to StateProvider.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3d2d1ad]
|
||||
- @json-render/core@0.5.0
|
||||
@@ -59,7 +59,7 @@ export const { registry } = defineRegistry(catalog, {
|
||||
});
|
||||
```
|
||||
|
||||
Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default. You only need to register custom ones.
|
||||
Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default -- you only need to register custom ones.
|
||||
|
||||
### 3. Render Specs
|
||||
|
||||
@@ -158,7 +158,7 @@ The `Pressable` component wraps children and triggers an action on press. It's e
|
||||
|
||||
## Built-in Actions
|
||||
|
||||
The `setState` action is handled automatically by `ActionProvider`. It updates the state model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
|
||||
The `setState` action is handled automatically by `ActionProvider`. It updates the data model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-native",
|
||||
"version": "0.5.1",
|
||||
"version": "0.4.3",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, {
|
||||
type ComponentType,
|
||||
type ErrorInfo,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
@@ -82,51 +81,6 @@ export interface RendererProps {
|
||||
fallback?: ComponentRenderer;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ElementErrorBoundary – catches rendering errors in individual elements so
|
||||
// a single bad component never crashes the whole page.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface ElementErrorBoundaryProps {
|
||||
elementType: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface ElementErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class ElementErrorBoundary extends React.Component<
|
||||
ElementErrorBoundaryProps,
|
||||
ElementErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ElementErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(): ElementErrorBoundaryState {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||
console.error(
|
||||
`[json-render] Rendering error in <${this.props.elementType}>:`,
|
||||
error,
|
||||
info.componentStack,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Render nothing – the element silently disappears rather than
|
||||
// crashing the entire application.
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Element renderer component
|
||||
*/
|
||||
@@ -260,11 +214,9 @@ function ElementRenderer({
|
||||
);
|
||||
|
||||
return (
|
||||
<ElementErrorBoundary elementType={resolvedElement.type}>
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
</ElementErrorBoundary>
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,5 @@
|
||||
# @json-render/react
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
|
||||
- @json-render/core@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3d2d1ad: Add @json-render/react-native package, event system (emit replaces onAction), repeat/list rendering, user prompt builder, spec validation, and rename DataProvider to StateProvider.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3d2d1ad]
|
||||
- @json-render/core@0.5.0
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dd17549: remove key/parentKey from flat specs, RFC 6902 compliance for SpecStream
|
||||
- Updated dependencies [dd17549]
|
||||
- @json-render/core@0.4.4
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -63,8 +63,8 @@ export const { registry } = defineRegistry(catalog, {
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
Button: ({ props, emit }) => (
|
||||
<button onClick={() => emit?.("press")}>
|
||||
Button: ({ props, onAction }) => (
|
||||
<button onClick={() => onAction?.({ name: props.action })}>
|
||||
{props.label}
|
||||
</button>
|
||||
),
|
||||
@@ -274,7 +274,7 @@ See [@json-render/core](../core/README.md) for full expression syntax.
|
||||
|
||||
## Built-in Actions
|
||||
|
||||
The `setState` action is handled automatically by `ActionProvider`. It updates the state model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
|
||||
The `setState` action is handled automatically by `ActionProvider`. It updates the data model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -295,7 +295,7 @@ When using `defineRegistry`, components receive these props:
|
||||
interface ComponentContext<P> {
|
||||
props: P; // Typed props from the catalog
|
||||
children?: React.ReactNode; // Rendered children
|
||||
emit?: (event: string) => void; // Emit a named event
|
||||
onAction?: (action: { name: string; params?: Record<string, unknown> }) => void;
|
||||
loading?: boolean; // Whether the parent is loading
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react",
|
||||
"version": "0.5.1",
|
||||
"version": "0.4.3",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||
"keywords": [
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import React, {
|
||||
type ComponentType,
|
||||
type ErrorInfo,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
@@ -78,51 +77,6 @@ export interface RendererProps {
|
||||
fallback?: ComponentRenderer;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ElementErrorBoundary – catches rendering errors in individual elements so
|
||||
// a single bad component never crashes the whole page.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface ElementErrorBoundaryProps {
|
||||
elementType: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface ElementErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class ElementErrorBoundary extends React.Component<
|
||||
ElementErrorBoundaryProps,
|
||||
ElementErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ElementErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(): ElementErrorBoundaryState {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||
console.error(
|
||||
`[json-render] Rendering error in <${this.props.elementType}>:`,
|
||||
error,
|
||||
info.componentStack,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Render nothing – the element silently disappears rather than
|
||||
// crashing the entire application.
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Element renderer component
|
||||
*/
|
||||
@@ -256,11 +210,9 @@ function ElementRenderer({
|
||||
);
|
||||
|
||||
return (
|
||||
<ElementErrorBoundary elementType={resolvedElement.type}>
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
</ElementErrorBoundary>
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,5 @@
|
||||
# @json-render/remotion
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
|
||||
- @json-render/core@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3d2d1ad: Add @json-render/react-native package, event system (emit replaces onAction), repeat/list rendering, user prompt builder, spec validation, and rename DataProvider to StateProvider.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3d2d1ad]
|
||||
- @json-render/core@0.5.0
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dd17549: remove key/parentKey from flat specs, RFC 6902 compliance for SpecStream
|
||||
- Updated dependencies [dd17549]
|
||||
- @json-render/core@0.4.4
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/remotion",
|
||||
"version": "0.5.1",
|
||||
"version": "0.4.3",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import React, { type ErrorInfo, type ReactNode } from "react";
|
||||
import { AbsoluteFill, Sequence } from "remotion";
|
||||
import type { TimelineSpec, ComponentRegistry, Clip } from "./types";
|
||||
|
||||
@@ -34,50 +33,6 @@ export const standardComponents: ComponentRegistry = {
|
||||
VideoClip,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ClipErrorBoundary – catches rendering errors in individual clips so
|
||||
// a single bad clip never crashes the entire composition.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface ClipErrorBoundaryProps {
|
||||
clipId: string;
|
||||
component: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface ClipErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class ClipErrorBoundary extends React.Component<
|
||||
ClipErrorBoundaryProps,
|
||||
ClipErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ClipErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(): ClipErrorBoundaryState {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||
console.error(
|
||||
`[json-render/remotion] Rendering error in clip "${this.props.clipId}" (<${this.props.component}>):`,
|
||||
error,
|
||||
info.componentStack,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
interface RendererProps {
|
||||
/** The timeline spec to render */
|
||||
spec: TimelineSpec;
|
||||
@@ -147,9 +102,7 @@ export function Renderer({
|
||||
from={clip.from}
|
||||
durationInFrames={clip.durationInFrames}
|
||||
>
|
||||
<ClipErrorBoundary clipId={clip.id} component={clip.component}>
|
||||
<Component clip={clip} />
|
||||
</ClipErrorBoundary>
|
||||
<Component clip={clip} />
|
||||
</Sequence>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -81,8 +81,8 @@ const finalSpec = compiler.getResult();
|
||||
|
||||
Any prop value can be a dynamic expression resolved at render time:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** - reads a value from the state model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a visibility condition and picks a branch
|
||||
- **`{ "$path": "/state/key" }`** -- reads a value from the data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a visibility condition and picks a branch
|
||||
|
||||
`$cond` uses the same syntax as visibility conditions (`eq`, `neq`, `path`, `and`, `or`, `not`). `$then` and `$else` can themselves be expressions (recursive).
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ Use `Pressable` with the built-in `setState` action for interactive UIs like tab
|
||||
|
||||
Any prop value can be a data-driven expression resolved at render time:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** - reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||
- **`{ "$path": "/state/key" }`** -- reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -123,11 +123,11 @@ Any prop value can be a data-driven expression resolved at render time:
|
||||
}
|
||||
```
|
||||
|
||||
Components receive already-resolved props. No changes needed to component implementations.
|
||||
Components receive already-resolved props -- no changes needed to component implementations.
|
||||
|
||||
## Built-in Actions
|
||||
|
||||
The `setState` action is handled automatically by `ActionProvider` and updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions:
|
||||
The `setState` action is handled automatically by `ActionProvider` and updates the data model directly, which re-evaluates visibility conditions and dynamic prop expressions:
|
||||
|
||||
```json
|
||||
{ "action": "setState", "actionParams": { "path": "/activeTab", "value": "home" } }
|
||||
|
||||
@@ -85,7 +85,7 @@ The React schema uses an element tree format:
|
||||
| Provider | Purpose |
|
||||
|----------|---------|
|
||||
| `StateProvider` | Share state across components (JSON Pointer paths) |
|
||||
| `ActionProvider` | Handle actions dispatched via the event system |
|
||||
| `ActionProvider` | Handle actions dispatched from components |
|
||||
| `VisibilityProvider` | Enable conditional rendering based on state |
|
||||
| `ValidationProvider` | Form field validation |
|
||||
|
||||
@@ -93,8 +93,8 @@ The React schema uses an element tree format:
|
||||
|
||||
Any prop value can be a data-driven expression resolved by the renderer before components receive props:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** - reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||
- **`{ "$path": "/state/key" }`** -- reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -106,30 +106,11 @@ Any prop value can be a data-driven expression resolved by the renderer before c
|
||||
}
|
||||
```
|
||||
|
||||
Components receive already-resolved props. No changes needed to component implementations.
|
||||
|
||||
## Event System
|
||||
|
||||
Components use `emit` to fire named events. The element's `on` field maps events to action bindings:
|
||||
|
||||
```tsx
|
||||
// Component emits a named event
|
||||
Button: ({ props, emit }) => (
|
||||
<button onClick={() => emit?.("press")}>{props.label}</button>
|
||||
),
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Button",
|
||||
"props": { "label": "Submit" },
|
||||
"on": { "press": { "action": "submit" } }
|
||||
}
|
||||
```
|
||||
Components receive already-resolved props -- no changes needed to component implementations.
|
||||
|
||||
## Built-in Actions
|
||||
|
||||
The `setState` action is handled automatically by `ActionProvider` and updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions:
|
||||
The `setState` action is handled automatically by `ActionProvider` and updates the data model directly, which re-evaluates visibility conditions and dynamic prop expressions:
|
||||
|
||||
```json
|
||||
{ "action": "setState", "actionParams": { "path": "/activeTab", "value": "home" } }
|
||||
@@ -142,9 +123,9 @@ The `setState` action is handled automatically by `ActionProvider` and updates t
|
||||
| `defineRegistry` | Create a type-safe component registry from a catalog |
|
||||
| `Renderer` | Render a spec using a registry |
|
||||
| `schema` | Element tree schema |
|
||||
| `useStateStore` | Access state context |
|
||||
| `useStateValue` | Get single value from state |
|
||||
| `useStateBinding` | Two-way state binding |
|
||||
| `useStateStore` | Access data context |
|
||||
| `useStateValue` | Get single value from data |
|
||||
| `useStateBinding` | Two-way data binding |
|
||||
| `useActions` | Access actions context |
|
||||
| `useAction` | Get a single action dispatch function |
|
||||
| `useUIStream` | Stream specs from an API endpoint |
|
||||
|
||||
Reference in New Issue
Block a user