Compare commits

...
Author SHA1 Message Date
Chris Tate d944a211cf update registry page 2026-02-06 14:24:18 -06:00
+139 -112
View File
@@ -14,82 +14,86 @@ export default function RegistryPage() {
life.
</p>
{/* Components Section */}
<h2 className="text-xl font-semibold mt-12 mb-4">Component Registry</h2>
{/* defineRegistry */}
<h2 className="text-xl font-semibold mt-12 mb-4">defineRegistry</h2>
<p className="text-sm text-muted-foreground mb-4">
Create a registry that maps catalog component types to React components:
Use <code>defineRegistry</code> to create a type-safe registry from your
catalog. Pass your components, actions, or both in a single call:
</p>
<Code lang="tsx">{`import { useAction } from '@json-render/react';
<Code lang="tsx">{`import { defineRegistry } from '@json-render/react';
import { myCatalog } from './catalog';
const registry = {
Card: ({ props, children }) => (
<div className="card">
<h2>{props.title}</h2>
{props.description && <p>{props.description}</p>}
{children}
</div>
),
Button: ({ props }) => {
const executeAction = useAction(props.action);
return (
<button onClick={() => executeAction({})}>
export const { registry, handlers, executeAction } = defineRegistry(myCatalog, {
components: {
Card: ({ props, children }) => (
<div className="card">
<h2>{props.title}</h2>
{props.description && <p>{props.description}</p>}
{children}
</div>
),
Button: ({ props, onAction }) => (
<button onClick={() => onAction?.({ name: props.action })}>
{props.label}
</button>
);
),
},
};`}</Code>
actions: {
submit_form: async (params, setData) => {
const res = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(params),
});
const result = await res.json();
setData((prev) => ({ ...prev, formResult: result }));
},
export_data: async (params) => {
const blob = await generateExport(params.format);
downloadBlob(blob, \`export.\${params.format}\`);
},
},
});`}</Code>
<p className="text-sm text-muted-foreground mt-4 mb-4">
The returned object contains:
</p>
<ul className="list-disc list-inside text-sm text-muted-foreground mb-4 space-y-1">
<li>
<code>registry</code> - component registry for{" "}
<code>{"<Renderer />"}</code>
</li>
<li>
<code>handlers</code> - factory for ActionProvider-compatible handlers
</li>
<li>
<code>executeAction</code> - imperative action dispatch (for use
outside the React tree)
</li>
</ul>
{/* Component Props */}
<h2 className="text-xl font-semibold mt-12 mb-4">Component Props</h2>
<p className="text-sm text-muted-foreground mb-4">
Each component receives these props:
Each component in the registry receives a <code>ComponentContext</code>{" "}
object:
</p>
<Code lang="typescript">{`interface ComponentProps<T = Record<string, unknown>> {
props: T; // Component props from the spec
<Code lang="typescript">{`interface ComponentContext {
props: T; // Type-safe props from your catalog
children?: React.ReactNode; // Rendered children (for slot components)
}
onAction?: (action: ActionTrigger) => void; // Dispatch an action
loading?: boolean; // Whether the renderer is in a loading state
}`}</Code>
// Type-safe props by extracting from your catalog
type CardProps = ComponentProps<{
title: string;
description: string | null;
}>;
// Use hooks for actions and data within components
import { useAction, useDataValue } from '@json-render/react';`}</Code>
<h2 className="text-xl font-semibold mt-12 mb-4">Using Data Binding</h2>
<p className="text-sm text-muted-foreground mb-4">
Use hooks to read and write data:
<p className="text-sm text-muted-foreground mt-4 mb-4">
Props are automatically inferred from your catalog, so{" "}
<code>props.title</code> is typed as <code>string</code> if your catalog
defines it that way.
</p>
<Code lang="tsx">{`import { useDataValue, useDataBinding } from '@json-render/react';
const Metric = ({ props }) => {
// Read-only value from data context
const value = useDataValue(props.valuePath);
return (
<div className="metric">
<span className="label">{props.label}</span>
<span className="value">{formatValue(value)}</span>
</div>
);
};
const TextField = ({ props }) => {
// Two-way binding to data context
const [value, setValue] = useDataBinding(props.valuePath);
return (
<input
value={value || ''}
onChange={(e) => setValue(e.target.value)}
placeholder={props.placeholder}
/>
);
};`}</Code>
{/* Actions Section */}
{/* Action Handlers */}
<h2 className="text-xl font-semibold mt-12 mb-4">Action Handlers</h2>
<p className="text-sm text-muted-foreground mb-4">
Instead of AI generating arbitrary code, it declares <em>intent</em> by
@@ -117,9 +121,6 @@ const catalog = defineCatalog(schema, {
export_data: {
params: z.object({
format: z.enum(['csv', 'pdf', 'json']),
filters: z.object({
dateRange: z.string().nullable(),
}).nullable(),
}),
},
navigate: {
@@ -130,78 +131,104 @@ const catalog = defineCatalog(schema, {
},
});`}</Code>
<h3 className="text-lg font-medium mt-8 mb-3">ActionProvider</h3>
<h3 className="text-lg font-medium mt-8 mb-3">
Implementing Action Handlers
</h3>
<p className="text-sm text-muted-foreground mb-4">
Provide action handlers to your app:
Action handlers receive <code>(params, setData, data)</code> and are
defined inside <code>defineRegistry</code>:
</p>
<Code lang="tsx">{`import { ActionProvider } from '@json-render/react';
function App() {
const handlers = {
submit_form: async (params) => {
<Code lang="tsx">{`export const { handlers, executeAction } = defineRegistry(catalog, {
actions: {
submit_form: async (params, setData) => {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ formId: params.formId }),
});
return response.json();
const result = await response.json();
setData((prev) => ({ ...prev, formResult: result }));
},
export_data: async (params) => {
const blob = await generateExport(params.format, params.filters);
const blob = await generateExport(params.format);
downloadBlob(blob, \`export.\${params.format}\`);
},
navigate: (params) => {
window.location.href = params.url;
},
};
},
});`}</Code>
{/* Using Data Binding */}
<h2 className="text-xl font-semibold mt-12 mb-4">Using Data Binding</h2>
<p className="text-sm text-muted-foreground mb-4">
Use hooks inside your registry components to read and write data:
</p>
<Code lang="tsx">{`import { useData } from '@json-render/react';
import { getByPath } from '@json-render/core';
// Inside defineRegistry components:
Metric: ({ props }) => {
const { data } = useData();
const value = getByPath(data, props.valuePath);
return (
<ActionProvider handlers={handlers}>
{/* Your UI */}
</ActionProvider>
<div className="metric">
<span className="label">{props.label}</span>
<span className="value">{formatValue(value)}</span>
</div>
);
}`}</Code>
},
<h3 className="text-lg font-medium mt-8 mb-3">
Using Actions in Components
</h3>
<Code lang="tsx">{`import { useAction } from '@json-render/react';
TextField: ({ props }) => {
const { data, set } = useData();
const value = getByPath(data, props.valuePath) as string;
// Using the useAction hook (recommended)
const Button = ({ props }) => {
const executeAction = useAction(props.action);
return (
<button onClick={() => executeAction({})}>
{props.label}
</button>
<input
value={value || ''}
onChange={(e) => set(props.valuePath, e.target.value)}
placeholder={props.placeholder}
/>
);
};
// Or for standalone use
function SubmitButton() {
const submitForm = useAction('submit_form');
return (
<button onClick={() => submitForm({ formId: 'contact' })}>
Submit
</button>
);
}`}</Code>
},`}</Code>
{/* Renderer Section */}
<h2 className="text-xl font-semibold mt-12 mb-4">Using the Renderer</h2>
<Code lang="tsx">{`import { Renderer, ActionProvider } from '@json-render/react';
<p className="text-sm text-muted-foreground mb-4">
Wire everything together with providers and the{" "}
<code>{"<Renderer />"}</code> component:
</p>
<Code lang="tsx">{`import { useMemo, useRef } from 'react';
import {
Renderer,
DataProvider,
VisibilityProvider,
ActionProvider,
} from '@json-render/react';
import { registry, handlers } from './registry';
function App({ spec, data, setData }) {
const dataRef = useRef(data);
const setDataRef = useRef(setData);
dataRef.current = data;
setDataRef.current = setData;
const actionHandlers = useMemo(
() => handlers(() => setDataRef.current, () => dataRef.current),
[],
);
function App() {
return (
<ActionProvider handlers={actionHandlers}>
<Renderer
spec={uiSpec}
registry={registry}
/>
</ActionProvider>
<DataProvider initialData={data}>
<VisibilityProvider>
<ActionProvider handlers={actionHandlers}>
<Renderer spec={spec} registry={registry} />
</ActionProvider>
</VisibilityProvider>
</DataProvider>
);
}`}</Code>