|
|
5 luni în urmă | |
|---|---|---|
| .. | ||
| src | 5 luni în urmă | |
| README.md | 5 luni în urmă | |
| package.json | 5 luni în urmă | |
| tsconfig.json | 5 luni în urmă | |
| tsup.config.ts | 5 luni în urmă | |
Predictable. Guardrailed. Fast. React renderer for user-prompted dashboards, widgets, apps, and data visualizations.
npm install @json-render/react @json-render/core
# or
pnpm add @json-render/react @json-render/core
import { JSONUIProvider, Renderer, useUIStream } from '@json-render/react';
// Define your component registry
const registry = {
Card: ({ element, children }) => (
<div className="card">
<h3>{element.props.title}</h3>
{children}
</div>
),
Button: ({ element, onAction }) => (
<button onClick={() => onAction?.(element.props.action)}>
{element.props.label}
</button>
),
};
// Action handlers
const actionHandlers = {
submit: async (params) => {
await api.submit(params);
},
export: (params) => {
download(params.format);
},
};
function App() {
const { tree, isStreaming, send, clear } = useUIStream({
api: '/api/generate',
});
return (
<JSONUIProvider
registry={registry}
initialData={{ user: { name: 'John' } }}
authState={{ isSignedIn: true }}
actionHandlers={actionHandlers}
>
<input
placeholder="Describe the UI..."
onKeyDown={(e) => e.key === 'Enter' && send(e.target.value)}
/>
<Renderer tree={tree} registry={registry} loading={isStreaming} />
</JSONUIProvider>
);
}
import {
DataProvider,
VisibilityProvider,
ActionProvider,
ValidationProvider,
useData,
useVisibility,
useActions,
useFieldValidation,
} from '@json-render/react';
// Data context
function MyComponent() {
const { data, get, set } = useData();
const value = get('/user/name');
return (
<input
value={value}
onChange={(e) => set('/user/name', e.target.value)}
/>
);
}
// Visibility context
function ConditionalComponent({ visible }) {
const { isVisible } = useVisibility();
if (!isVisible(visible)) {
return null;
}
return <div>Visible content</div>;
}
// Action context
function ActionButton({ action }) {
const { execute, loadingActions } = useActions();
return (
<button
onClick={() => execute(action)}
disabled={loadingActions.has(action.name)}
>
{action.name}
</button>
);
}
// Validation context
function ValidatedInput({ path, checks }) {
const { errors, validate, touch } = useFieldValidation(path, { checks });
const [value, setValue] = useDataBinding(path);
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={() => { touch(); validate(); }}
/>
{errors.map((err) => <span key={err}>{err}</span>)}
</div>
);
}
import { useUIStream } from '@json-render/react';
function StreamingDemo() {
const {
tree, // Current UI tree
isStreaming, // Whether currently streaming
error, // Error if any
send, // Send a prompt
clear, // Clear the tree
} = useUIStream({
api: '/api/generate',
onComplete: (tree) => console.log('Done:', tree),
onError: (err) => console.error('Error:', err),
});
return (
<div>
<button onClick={() => send('Create a dashboard')}>
Generate
</button>
{isStreaming && <span>Generating...</span>}
{tree && <Renderer tree={tree} registry={registry} />}
</div>
);
}
JSONUIProvider - Combined provider for all contextsDataProvider - Data model contextVisibilityProvider - Visibility evaluation contextActionProvider - Action execution contextValidationProvider - Validation contextuseData() - Access data modeluseDataValue(path) - Get a single valueuseDataBinding(path) - Two-way binding like useStateuseVisibility() - Access visibility evaluationuseIsVisible(condition) - Check if condition is visibleuseActions() - Access action executionuseAction(action) - Execute a specific actionuseValidation() - Access validation contextuseFieldValidation(path, config) - Field-level validationRenderer - Render a UI treeConfirmDialog - Default confirmation dialoguseUIStream(options) - Hook for streaming UI generationflatToTree(elements) - Convert flat list to treeComponents in your registry receive these props:
interface ComponentRenderProps<P = Record<string, unknown>> {
element: UIElement<string, P>; // The element definition
children?: ReactNode; // Rendered children
onAction?: (action: Action) => void; // Action callback
loading?: boolean; // Streaming in progress
}
function MetricComponent({ element }: ComponentRenderProps) {
const { label, valuePath, format } = element.props;
const value = useDataValue(valuePath);
const formatted = format === 'currency'
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value)
: String(value);
return (
<div className="metric">
<span className="label">{label}</span>
<span className="value">{formatted}</span>
</div>
);
}