Bladeren bron

update registry page (#68)

Chris Tate 5 maanden geleden
bovenliggende
commit
90c59c674d
1 gewijzigde bestanden met toevoegingen van 139 en 112 verwijderingen
  1. 139 112
      apps/web/app/(main)/docs/registry/page.tsx

+ 139 - 112
apps/web/app/(main)/docs/registry/page.tsx

@@ -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';
-
-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({})}>
+      <Code lang="tsx">{`import { defineRegistry } from '@json-render/react';
+import { myCatalog } from './catalog';
+
+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)
-}
-
-// 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>
+  onAction?: (action: ActionTrigger) => void;  // Dispatch an action
+  loading?: boolean;           // Whether the renderer is in a loading state
+}`}</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>
 
-  return (
-    <ActionProvider handlers={handlers}>
-      {/* Your UI */}
-    </ActionProvider>
-  );
-}`}</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';
 
-      <h3 className="text-lg font-medium mt-8 mb-3">
-        Using Actions in Components
-      </h3>
-      <Code lang="tsx">{`import { useAction } from '@json-render/react';
+// Inside defineRegistry components:
+
+Metric: ({ props }) => {
+  const { data } = useData();
+  const value = getByPath(data, props.valuePath);
 
-// Using the useAction hook (recommended)
-const Button = ({ props }) => {
-  const executeAction = useAction(props.action);
-  
   return (
-    <button onClick={() => executeAction({})}>
-      {props.label}
-    </button>
+    <div className="metric">
+      <span className="label">{props.label}</span>
+      <span className="value">{formatValue(value)}</span>
+    </div>
   );
-};
+},
+
+TextField: ({ props }) => {
+  const { data, set } = useData();
+  const value = getByPath(data, props.valuePath) as string;
 
-// Or for standalone use
-function SubmitButton() {
-  const submitForm = useAction('submit_form');
-  
   return (
-    <button onClick={() => submitForm({ formId: 'contact' })}>
-      Submit
-    </button>
+    <input
+      value={value || ''}
+      onChange={(e) => set(props.valuePath, e.target.value)}
+      placeholder={props.placeholder}
+    />
   );
-}`}</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>