page.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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. {/* Components Section */}
  15. <h2 className="text-xl font-semibold mt-12 mb-4">Component Registry</h2>
  16. <p className="text-sm text-muted-foreground mb-4">
  17. Create a registry that maps catalog component types to React components:
  18. </p>
  19. <Code lang="tsx">{`import { useAction } from '@json-render/react';
  20. const registry = {
  21. Card: ({ props, children }) => (
  22. <div className="card">
  23. <h2>{props.title}</h2>
  24. {props.description && <p>{props.description}</p>}
  25. {children}
  26. </div>
  27. ),
  28. Button: ({ props }) => {
  29. const executeAction = useAction(props.action);
  30. return (
  31. <button onClick={() => executeAction({})}>
  32. {props.label}
  33. </button>
  34. );
  35. },
  36. };`}</Code>
  37. <h2 className="text-xl font-semibold mt-12 mb-4">Component Props</h2>
  38. <p className="text-sm text-muted-foreground mb-4">
  39. Each component receives these props:
  40. </p>
  41. <Code lang="typescript">{`interface ComponentProps<T = Record<string, unknown>> {
  42. props: T; // Component props from the spec
  43. children?: React.ReactNode; // Rendered children (for slot components)
  44. }
  45. // Type-safe props by extracting from your catalog
  46. type CardProps = ComponentProps<{
  47. title: string;
  48. description: string | null;
  49. }>;
  50. // Use hooks for actions and data within components
  51. import { useAction, useDataValue } from '@json-render/react';`}</Code>
  52. <h2 className="text-xl font-semibold mt-12 mb-4">Using Data Binding</h2>
  53. <p className="text-sm text-muted-foreground mb-4">
  54. Use hooks to read and write data:
  55. </p>
  56. <Code lang="tsx">{`import { useDataValue, useDataBinding } from '@json-render/react';
  57. const Metric = ({ props }) => {
  58. // Read-only value from data context
  59. const value = useDataValue(props.valuePath);
  60. return (
  61. <div className="metric">
  62. <span className="label">{props.label}</span>
  63. <span className="value">{formatValue(value)}</span>
  64. </div>
  65. );
  66. };
  67. const TextField = ({ props }) => {
  68. // Two-way binding to data context
  69. const [value, setValue] = useDataBinding(props.valuePath);
  70. return (
  71. <input
  72. value={value || ''}
  73. onChange={(e) => setValue(e.target.value)}
  74. placeholder={props.placeholder}
  75. />
  76. );
  77. };`}</Code>
  78. {/* Actions Section */}
  79. <h2 className="text-xl font-semibold mt-12 mb-4">Action Handlers</h2>
  80. <p className="text-sm text-muted-foreground mb-4">
  81. Instead of AI generating arbitrary code, it declares <em>intent</em> by
  82. name. Your application provides the implementation. This is a core
  83. guardrail.
  84. </p>
  85. <h3 className="text-lg font-medium mt-8 mb-3">Defining Actions</h3>
  86. <p className="text-sm text-muted-foreground mb-4">
  87. Define available actions in your catalog:
  88. </p>
  89. <Code lang="typescript">{`import { defineCatalog } from '@json-render/core';
  90. import { schema } from '@json-render/react';
  91. import { z } from 'zod';
  92. const catalog = defineCatalog(schema, {
  93. components: { /* ... */ },
  94. actions: {
  95. submit_form: {
  96. params: z.object({
  97. formId: z.string(),
  98. }),
  99. description: 'Submit a form',
  100. },
  101. export_data: {
  102. params: z.object({
  103. format: z.enum(['csv', 'pdf', 'json']),
  104. filters: z.object({
  105. dateRange: z.string().nullable(),
  106. }).nullable(),
  107. }),
  108. },
  109. navigate: {
  110. params: z.object({
  111. url: z.string(),
  112. }),
  113. },
  114. },
  115. });`}</Code>
  116. <h3 className="text-lg font-medium mt-8 mb-3">ActionProvider</h3>
  117. <p className="text-sm text-muted-foreground mb-4">
  118. Provide action handlers to your app:
  119. </p>
  120. <Code lang="tsx">{`import { ActionProvider } from '@json-render/react';
  121. function App() {
  122. const handlers = {
  123. submit_form: async (params) => {
  124. const response = await fetch('/api/submit', {
  125. method: 'POST',
  126. body: JSON.stringify({ formId: params.formId }),
  127. });
  128. return response.json();
  129. },
  130. export_data: async (params) => {
  131. const blob = await generateExport(params.format, params.filters);
  132. downloadBlob(blob, \`export.\${params.format}\`);
  133. },
  134. navigate: (params) => {
  135. window.location.href = params.url;
  136. },
  137. };
  138. return (
  139. <ActionProvider handlers={handlers}>
  140. {/* Your UI */}
  141. </ActionProvider>
  142. );
  143. }`}</Code>
  144. <h3 className="text-lg font-medium mt-8 mb-3">
  145. Using Actions in Components
  146. </h3>
  147. <Code lang="tsx">{`import { useAction } from '@json-render/react';
  148. // Using the useAction hook (recommended)
  149. const Button = ({ props }) => {
  150. const executeAction = useAction(props.action);
  151. return (
  152. <button onClick={() => executeAction({})}>
  153. {props.label}
  154. </button>
  155. );
  156. };
  157. // Or for standalone use
  158. function SubmitButton() {
  159. const submitForm = useAction('submit_form');
  160. return (
  161. <button onClick={() => submitForm({ formId: 'contact' })}>
  162. Submit
  163. </button>
  164. );
  165. }`}</Code>
  166. {/* Renderer Section */}
  167. <h2 className="text-xl font-semibold mt-12 mb-4">Using the Renderer</h2>
  168. <Code lang="tsx">{`import { Renderer, ActionProvider } from '@json-render/react';
  169. function App() {
  170. return (
  171. <ActionProvider handlers={actionHandlers}>
  172. <Renderer
  173. spec={uiSpec}
  174. registry={registry}
  175. />
  176. </ActionProvider>
  177. );
  178. }`}</Code>
  179. <h2 className="text-xl font-semibold mt-12 mb-4">Next</h2>
  180. <p className="text-sm text-muted-foreground">
  181. Learn about{" "}
  182. <Link
  183. href="/docs/data-binding"
  184. className="text-foreground hover:underline"
  185. >
  186. data binding
  187. </Link>{" "}
  188. for dynamic values.
  189. </p>
  190. </article>
  191. );
  192. }