| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- "use client";
- import React, { useMemo, type ReactNode } from "react";
- import type { Spec } from "@json-render/core";
- import {
- Renderer,
- StateProvider,
- VisibilityProvider,
- ValidationProvider,
- ActionProvider,
- type ComponentRegistry,
- type ComponentRenderProps,
- } from "@json-render/react";
- import { useNextApp } from "./provider";
- /**
- * Props for PageRenderer.
- */
- export interface PageRendererProps {
- /** Page element tree spec */
- spec: Spec;
- /** Initial state (merged from spec.state, global state, and loader data) */
- initialState?: Record<string, unknown>;
- /** Optional layout element tree spec */
- layoutSpec?: Spec | null;
- /** Whether the spec is currently loading/streaming */
- loading?: boolean;
- }
- /**
- * Built-in Slot component that renders children passed into layouts.
- */
- function SlotComponent({ children }: ComponentRenderProps) {
- return <>{children}</>;
- }
- /**
- * Client component that renders a json-render page within the Next.js app.
- *
- * Reads the component registry and action handlers from NextAppProvider context.
- * Handles layout rendering by injecting page content into a Slot component.
- */
- export function PageRenderer({
- spec,
- initialState,
- layoutSpec,
- loading,
- }: PageRendererProps) {
- const { registry, handlers, navigate } = useNextApp();
- const augmentedRegistry: ComponentRegistry = useMemo(() => {
- return {
- ...registry,
- Slot: SlotComponent,
- };
- }, [registry]);
- const actionHandlers = useMemo(() => {
- const base: Record<
- string,
- (params: Record<string, unknown>) => Promise<unknown> | unknown
- > = { ...handlers };
- base.navigate = (params: Record<string, unknown>) => {
- const href = params.href as string | undefined;
- if (href) navigate(href);
- };
- return base;
- }, [handlers, navigate]);
- const pageContent = (
- <Renderer spec={spec} registry={augmentedRegistry} loading={loading} />
- );
- if (layoutSpec) {
- return (
- <StateProvider initialState={initialState}>
- <VisibilityProvider>
- <ValidationProvider>
- <ActionProvider handlers={actionHandlers} navigate={navigate}>
- <LayoutWithSlot
- layoutSpec={layoutSpec}
- registry={augmentedRegistry}
- loading={loading}
- >
- {pageContent}
- </LayoutWithSlot>
- </ActionProvider>
- </ValidationProvider>
- </VisibilityProvider>
- </StateProvider>
- );
- }
- return (
- <StateProvider initialState={initialState}>
- <VisibilityProvider>
- <ValidationProvider>
- <ActionProvider handlers={actionHandlers} navigate={navigate}>
- {pageContent}
- </ActionProvider>
- </ValidationProvider>
- </VisibilityProvider>
- </StateProvider>
- );
- }
- /**
- * Renders a layout spec and injects children into the Slot.
- */
- function LayoutWithSlot({
- layoutSpec,
- registry,
- loading,
- children,
- }: {
- layoutSpec: Spec;
- registry: ComponentRegistry;
- loading?: boolean;
- children: ReactNode;
- }) {
- const layoutRegistry: ComponentRegistry = useMemo(() => {
- return {
- ...registry,
- Slot: function LayoutSlot() {
- return <>{children}</>;
- },
- };
- }, [registry, children]);
- return (
- <Renderer spec={layoutSpec} registry={layoutRegistry} loading={loading} />
- );
- }
|