page.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import Link from "next/link";
  2. import { Code } from "@/components/code";
  3. export const metadata = {
  4. title: "Registry | json-render",
  5. };
  6. export default function RegistryPage() {
  7. return (
  8. <article>
  9. <h1 className="text-3xl font-bold mb-4">Registry</h1>
  10. <p className="text-muted-foreground mb-8">
  11. Register React components and action handlers to bring your catalog to
  12. life.
  13. </p>
  14. {/* defineRegistry */}
  15. <h2 className="text-xl font-semibold mt-12 mb-4">defineRegistry</h2>
  16. <p className="text-sm text-muted-foreground mb-4">
  17. Use <code>defineRegistry</code> to create a type-safe registry from your
  18. catalog. Pass your components, actions, or both in a single call:
  19. </p>
  20. <Code lang="tsx">{`import { defineRegistry } from '@json-render/react';
  21. import { myCatalog } from './catalog';
  22. export const { registry, handlers, executeAction } = defineRegistry(myCatalog, {
  23. components: {
  24. Card: ({ props, children }) => (
  25. <div className="card">
  26. <h2>{props.title}</h2>
  27. {props.description && <p>{props.description}</p>}
  28. {children}
  29. </div>
  30. ),
  31. Button: ({ props, onAction }) => (
  32. <button onClick={() => onAction?.({ name: props.action })}>
  33. {props.label}
  34. </button>
  35. ),
  36. },
  37. actions: {
  38. submit_form: async (params, setData) => {
  39. const res = await fetch('/api/submit', {
  40. method: 'POST',
  41. body: JSON.stringify(params),
  42. });
  43. const result = await res.json();
  44. setData((prev) => ({ ...prev, formResult: result }));
  45. },
  46. export_data: async (params) => {
  47. const blob = await generateExport(params.format);
  48. downloadBlob(blob, \`export.\${params.format}\`);
  49. },
  50. },
  51. });`}</Code>
  52. <p className="text-sm text-muted-foreground mt-4 mb-4">
  53. The returned object contains:
  54. </p>
  55. <ul className="list-disc list-inside text-sm text-muted-foreground mb-4 space-y-1">
  56. <li>
  57. <code>registry</code> - component registry for{" "}
  58. <code>{"<Renderer />"}</code>
  59. </li>
  60. <li>
  61. <code>handlers</code> - factory for ActionProvider-compatible handlers
  62. </li>
  63. <li>
  64. <code>executeAction</code> - imperative action dispatch (for use
  65. outside the React tree)
  66. </li>
  67. </ul>
  68. {/* Component Props */}
  69. <h2 className="text-xl font-semibold mt-12 mb-4">Component Props</h2>
  70. <p className="text-sm text-muted-foreground mb-4">
  71. Each component in the registry receives a <code>ComponentContext</code>{" "}
  72. object:
  73. </p>
  74. <Code lang="typescript">{`interface ComponentContext {
  75. props: T; // Type-safe props from your catalog
  76. children?: React.ReactNode; // Rendered children (for slot components)
  77. onAction?: (action: ActionTrigger) => void; // Dispatch an action
  78. loading?: boolean; // Whether the renderer is in a loading state
  79. }`}</Code>
  80. <p className="text-sm text-muted-foreground mt-4 mb-4">
  81. Props are automatically inferred from your catalog, so{" "}
  82. <code>props.title</code> is typed as <code>string</code> if your catalog
  83. defines it that way.
  84. </p>
  85. {/* Action Handlers */}
  86. <h2 className="text-xl font-semibold mt-12 mb-4">Action Handlers</h2>
  87. <p className="text-sm text-muted-foreground mb-4">
  88. Instead of AI generating arbitrary code, it declares <em>intent</em> by
  89. name. Your application provides the implementation. This is a core
  90. guardrail.
  91. </p>
  92. <h3 className="text-lg font-medium mt-8 mb-3">Defining Actions</h3>
  93. <p className="text-sm text-muted-foreground mb-4">
  94. Define available actions in your catalog:
  95. </p>
  96. <Code lang="typescript">{`import { defineCatalog } from '@json-render/core';
  97. import { schema } from '@json-render/react';
  98. import { z } from 'zod';
  99. const catalog = defineCatalog(schema, {
  100. components: { /* ... */ },
  101. actions: {
  102. submit_form: {
  103. params: z.object({
  104. formId: z.string(),
  105. }),
  106. description: 'Submit a form',
  107. },
  108. export_data: {
  109. params: z.object({
  110. format: z.enum(['csv', 'pdf', 'json']),
  111. }),
  112. },
  113. navigate: {
  114. params: z.object({
  115. url: z.string(),
  116. }),
  117. },
  118. },
  119. });`}</Code>
  120. <h3 className="text-lg font-medium mt-8 mb-3">
  121. Implementing Action Handlers
  122. </h3>
  123. <p className="text-sm text-muted-foreground mb-4">
  124. Action handlers receive <code>(params, setData, data)</code> and are
  125. defined inside <code>defineRegistry</code>:
  126. </p>
  127. <Code lang="tsx">{`export const { handlers, executeAction } = defineRegistry(catalog, {
  128. actions: {
  129. submit_form: async (params, setData) => {
  130. const response = await fetch('/api/submit', {
  131. method: 'POST',
  132. body: JSON.stringify({ formId: params.formId }),
  133. });
  134. const result = await response.json();
  135. setData((prev) => ({ ...prev, formResult: result }));
  136. },
  137. export_data: async (params) => {
  138. const blob = await generateExport(params.format);
  139. downloadBlob(blob, \`export.\${params.format}\`);
  140. },
  141. navigate: (params) => {
  142. window.location.href = params.url;
  143. },
  144. },
  145. });`}</Code>
  146. {/* Using Data Binding */}
  147. <h2 className="text-xl font-semibold mt-12 mb-4">Using Data Binding</h2>
  148. <p className="text-sm text-muted-foreground mb-4">
  149. Use hooks inside your registry components to read and write data:
  150. </p>
  151. <Code lang="tsx">{`import { useData } from '@json-render/react';
  152. import { getByPath } from '@json-render/core';
  153. // Inside defineRegistry components:
  154. Metric: ({ props }) => {
  155. const { data } = useData();
  156. const value = getByPath(data, props.valuePath);
  157. return (
  158. <div className="metric">
  159. <span className="label">{props.label}</span>
  160. <span className="value">{formatValue(value)}</span>
  161. </div>
  162. );
  163. },
  164. TextField: ({ props }) => {
  165. const { data, set } = useData();
  166. const value = getByPath(data, props.valuePath) as string;
  167. return (
  168. <input
  169. value={value || ''}
  170. onChange={(e) => set(props.valuePath, e.target.value)}
  171. placeholder={props.placeholder}
  172. />
  173. );
  174. },`}</Code>
  175. {/* Renderer Section */}
  176. <h2 className="text-xl font-semibold mt-12 mb-4">Using the Renderer</h2>
  177. <p className="text-sm text-muted-foreground mb-4">
  178. Wire everything together with providers and the{" "}
  179. <code>{"<Renderer />"}</code> component:
  180. </p>
  181. <Code lang="tsx">{`import { useMemo, useRef } from 'react';
  182. import {
  183. Renderer,
  184. DataProvider,
  185. VisibilityProvider,
  186. ActionProvider,
  187. } from '@json-render/react';
  188. import { registry, handlers } from './registry';
  189. function App({ spec, data, setData }) {
  190. const dataRef = useRef(data);
  191. const setDataRef = useRef(setData);
  192. dataRef.current = data;
  193. setDataRef.current = setData;
  194. const actionHandlers = useMemo(
  195. () => handlers(() => setDataRef.current, () => dataRef.current),
  196. [],
  197. );
  198. return (
  199. <DataProvider initialData={data}>
  200. <VisibilityProvider>
  201. <ActionProvider handlers={actionHandlers}>
  202. <Renderer spec={spec} registry={registry} />
  203. </ActionProvider>
  204. </VisibilityProvider>
  205. </DataProvider>
  206. );
  207. }`}</Code>
  208. <h2 className="text-xl font-semibold mt-12 mb-4">Next</h2>
  209. <p className="text-sm text-muted-foreground">
  210. Learn about{" "}
  211. <Link
  212. href="/docs/data-binding"
  213. className="text-foreground hover:underline"
  214. >
  215. data binding
  216. </Link>{" "}
  217. for dynamic values.
  218. </p>
  219. </article>
  220. );
  221. }