"use client";
import React, { type ComponentType, type ReactNode, useMemo } from "react";
import type {
UIElement,
UITree,
Action,
Catalog,
ComponentDefinition,
} from "@json-render/core";
import { useIsVisible } from "./contexts/visibility";
import { useActions } from "./contexts/actions";
import { useData } from "./contexts/data";
/**
* Props passed to component renderers
*/
export interface ComponentRenderProps
> {
/** The element being rendered */
element: UIElement;
/** Rendered children */
children?: ReactNode;
/** Execute an action */
onAction?: (action: Action) => void;
/** Whether the parent is loading */
loading?: boolean;
}
/**
* Component renderer type
*/
export type ComponentRenderer> = ComponentType<
ComponentRenderProps
>;
/**
* Registry of component renderers
*/
export type ComponentRegistry = Record>;
/**
* Props for the Renderer component
*/
export interface RendererProps {
/** The UI tree to render */
tree: UITree | null;
/** Component registry */
registry: ComponentRegistry;
/** Whether the tree is currently loading/streaming */
loading?: boolean;
/** Fallback component for unknown types */
fallback?: ComponentRenderer;
}
/**
* Element renderer component
*/
function ElementRenderer({
element,
tree,
registry,
loading,
fallback,
}: {
element: UIElement;
tree: UITree;
registry: ComponentRegistry;
loading?: boolean;
fallback?: ComponentRenderer;
}) {
const isVisible = useIsVisible(element.visible);
const { execute } = useActions();
// Don't render if not visible
if (!isVisible) {
return null;
}
// Get the component renderer
const Component = registry[element.type] ?? fallback;
if (!Component) {
console.warn(`No renderer for component type: ${element.type}`);
return null;
}
// Render children
const children = element.children?.map((childKey) => {
const childElement = tree.elements[childKey];
if (!childElement) {
return null;
}
return (
);
});
return (
{children}
);
}
/**
* Main renderer component
*/
export function Renderer({ tree, registry, loading, fallback }: RendererProps) {
if (!tree || !tree.root) {
return null;
}
const rootElement = tree.elements[tree.root];
if (!rootElement) {
return null;
}
return (
);
}
/**
* Props for JSONUIProvider
*/
export interface JSONUIProviderProps {
/** Component registry */
registry: ComponentRegistry;
/** Initial data model */
initialData?: Record;
/** Auth state */
authState?: { isSignedIn: boolean; user?: Record };
/** Action handlers */
actionHandlers?: Record<
string,
(params: Record) => Promise | unknown
>;
/** Navigation function */
navigate?: (path: string) => void;
/** Custom validation functions */
validationFunctions?: Record<
string,
(value: unknown, args?: Record) => boolean
>;
/** Callback when data changes */
onDataChange?: (path: string, value: unknown) => void;
children: ReactNode;
}
// Import the providers
import { DataProvider } from "./contexts/data";
import { VisibilityProvider } from "./contexts/visibility";
import { ActionProvider } from "./contexts/actions";
import { ValidationProvider } from "./contexts/validation";
import { ConfirmDialog } from "./contexts/actions";
/**
* Combined provider for all JSONUI contexts
*/
export function JSONUIProvider({
registry,
initialData,
authState,
actionHandlers,
navigate,
validationFunctions,
onDataChange,
children,
}: JSONUIProviderProps) {
return (
{children}
);
}
/**
* Renders the confirmation dialog when needed
*/
function ConfirmationDialogManager() {
const { pendingConfirmation, confirm, cancel } = useActions();
if (!pendingConfirmation?.action.confirm) {
return null;
}
return (
);
}
/**
* Helper to create a renderer component from a catalog
*/
export function createRendererFromCatalog<
C extends Catalog>,
>(
_catalog: C,
registry: ComponentRegistry,
): ComponentType> {
return function CatalogRenderer(props: Omit) {
return ;
};
}