From 83864189a5d00c3388acdab62d27f1a9b8e9c000 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 16:08:03 +0000 Subject: [PATCH] Replace legacy schema renderer usage --- src/components/JSONLambdaDesigner.tsx | 13 +- src/components/organisms/JSONUIShowcase.tsx | 172 -------------- src/components/organisms/index.ts | 2 +- src/config/schemas/json-ui-dashboard.json | 236 -------------------- src/hooks/ui/index.ts | 4 - src/hooks/ui/use-component-registry.ts | 37 --- src/hooks/ui/use-data-binding.ts | 74 ------ src/hooks/ui/use-event-handlers.ts | 63 ------ src/hooks/ui/use-schema-loader.ts | 60 ----- src/lib/schema-renderer.tsx | 179 --------------- src/schemas/ui-schema.ts | 57 ----- 11 files changed, 4 insertions(+), 893 deletions(-) delete mode 100644 src/components/organisms/JSONUIShowcase.tsx delete mode 100644 src/config/schemas/json-ui-dashboard.json delete mode 100644 src/hooks/ui/use-component-registry.ts delete mode 100644 src/hooks/ui/use-data-binding.ts delete mode 100644 src/hooks/ui/use-event-handlers.ts delete mode 100644 src/hooks/ui/use-schema-loader.ts delete mode 100644 src/lib/schema-renderer.tsx delete mode 100644 src/schemas/ui-schema.ts diff --git a/src/components/JSONLambdaDesigner.tsx b/src/components/JSONLambdaDesigner.tsx index ddf3da2..6e6fed5 100644 --- a/src/components/JSONLambdaDesigner.tsx +++ b/src/components/JSONLambdaDesigner.tsx @@ -1,16 +1,9 @@ -import { PageRenderer } from '@/lib/schema-renderer' +import { PageRenderer } from '@/lib/json-ui/page-renderer' import lambdaDesignerSchema from '@/config/pages/lambda-designer.json' -import { useKV } from '@/hooks/use-kv' -import { Component as ComponentSchema } from '@/schemas/ui-schema' +import { PageSchema } from '@/types/json-ui' export function JSONLambdaDesigner() { - const [lambdas] = useKV('app-lambdas', []) - return ( - + ) } diff --git a/src/components/organisms/JSONUIShowcase.tsx b/src/components/organisms/JSONUIShowcase.tsx deleted file mode 100644 index 56c6b1d..0000000 --- a/src/components/organisms/JSONUIShowcase.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useState, useEffect } from 'react' -import { PageRenderer } from '@/lib/schema-renderer' -import { useSchemaLoader } from '@/hooks/ui' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Alert, AlertDescription } from '@/components/ui/alert' -import { Button } from '@/components/ui/button' -import { Code, FileText, Database } from '@phosphor-icons/react' -import dashboardSchema from '@/config/schemas/json-ui-dashboard.json' - -interface JSONUIShowcaseProps { - files?: any[] - models?: any[] - components?: any[] -} - -export function JSONUIShowcase({ - files = [], - models = [], - components = [] -}: JSONUIShowcaseProps) { - const [showJSON, setShowJSON] = useState(false) - const {schema: loadedSchema, loading, error} = useSchemaLoader({ - schema: dashboardSchema as any - }) - - const data = { - files: files.length > 0 ? files : [ - { name: 'App.tsx', type: 'TypeScript' }, - { name: 'index.css', type: 'CSS' }, - { name: 'schema-renderer.tsx', type: 'TypeScript' }, - { name: 'use-data-binding.ts', type: 'Hook' }, - { name: 'dashboard.json', type: 'JSON' }, - ], - models: models.length > 0 ? models : [ - { name: 'User', fields: 5 }, - { name: 'Post', fields: 8 }, - { name: 'Comment', fields: 4 }, - ], - components: components.length > 0 ? components : [ - { name: 'Button', type: 'atom' }, - { name: 'Card', type: 'molecule' }, - { name: 'Dashboard', type: 'organism' }, - ], - } - - const functions = { - handleClick: () => { - console.log('Button clicked from JSON!') - }, - } - - if (loading) { - return ( -
-
-
-

Loading schema...

-
-
- ) - } - - if (error) { - return ( -
- - - Failed to load schema: {error.message} - - -
- ) - } - - if (!loadedSchema) { - return ( -
- - No schema loaded - -
- ) - } - - return ( -
-
- - - - - JSON-Driven UI System - - - Complete UI rendering from declarative JSON schemas with data bindings and event handlers - - - -
- -
- {showJSON && ( -
-                {JSON.stringify(loadedSchema, null, 2)}
-              
- )} -
-
- -
- - - - - Schema-Driven - - - -

- UI structure defined in JSON, making it easy to modify without code changes -

-
-
- - - - - - Data Bindings - - - -

- Dynamic expressions in JSON connect UI to application state seamlessly -

-
-
- - - - - - Atomic Design - - - -

- Modular components composed from atoms to organisms following best practices -

-
-
-
- -
-

Rendered from JSON

-

- The content below is entirely generated from the JSON schema above, demonstrating data bindings, - loops, and component composition. -

- -
-
-
- ) -} diff --git a/src/components/organisms/index.ts b/src/components/organisms/index.ts index f4a4e6f..3f2192c 100644 --- a/src/components/organisms/index.ts +++ b/src/components/organisms/index.ts @@ -12,4 +12,4 @@ export { SchemaEditorLayout } from './SchemaEditorLayout' export { EmptyCanvasState } from './EmptyCanvasState' export { SchemaEditorStatusBar } from './SchemaEditorStatusBar' export { SchemaCodeViewer } from './SchemaCodeViewer' -export { JSONUIShowcase } from './JSONUIShowcase' +export { JSONUIShowcase } from '../JSONUIShowcase' diff --git a/src/config/schemas/json-ui-dashboard.json b/src/config/schemas/json-ui-dashboard.json deleted file mode 100644 index 2667312..0000000 --- a/src/config/schemas/json-ui-dashboard.json +++ /dev/null @@ -1,236 +0,0 @@ -{ - "id": "json-ui-dashboard", - "title": "JSON-Driven Dashboard", - "description": "A complete UI page rendered from JSON declarations", - "layout": { - "type": "flex", - "direction": "column", - "gap": "6", - "className": "h-full overflow-auto p-6" - }, - "components": [ - { - "id": "page-header", - "type": "div", - "props": { - "className": "mb-6" - }, - "children": [ - { - "id": "page-title", - "type": "div", - "props": { - "className": "text-3xl font-bold mb-2" - }, - "children": [ - { - "id": "title-text", - "type": "div", - "props": { - "children": "JSON-Driven UI System" - } - } - ] - }, - { - "id": "page-description", - "type": "div", - "props": { - "className": "text-muted-foreground", - "children": "This entire page is rendered from JSON schemas with data bindings" - } - } - ] - }, - { - "id": "stat-cards-grid", - "type": "div", - "props": { - "className": "grid gap-4 md:grid-cols-2 lg:grid-cols-3" - }, - "children": [ - { - "id": "stat-card-1", - "type": "Card", - "children": [ - { - "id": "stat-card-1-header", - "type": "CardHeader", - "children": [ - { - "id": "stat-card-1-title", - "type": "CardTitle", - "props": { - "className": "flex items-center gap-2" - }, - "children": [ - { - "id": "stat-1-text", - "type": "div", - "props": { - "children": "{{files.length}} Files" - } - } - ] - } - ] - }, - { - "id": "stat-card-1-content", - "type": "CardContent", - "children": [ - { - "id": "stat-1-value", - "type": "div", - "props": { - "className": "text-2xl font-bold", - "children": "{{files.length}}" - } - } - ] - } - ] - }, - { - "id": "stat-card-2", - "type": "Card", - "children": [ - { - "id": "stat-card-2-header", - "type": "CardHeader", - "children": [ - { - "id": "stat-card-2-title", - "type": "CardTitle", - "props": { - "children": "{{models.length}} Models" - } - } - ] - }, - { - "id": "stat-card-2-content", - "type": "CardContent", - "children": [ - { - "id": "stat-2-value", - "type": "div", - "props": { - "className": "text-2xl font-bold", - "children": "{{models.length}}" - } - } - ] - } - ] - }, - { - "id": "stat-card-3", - "type": "Card", - "children": [ - { - "id": "stat-card-3-header", - "type": "CardHeader", - "children": [ - { - "id": "stat-card-3-title", - "type": "CardTitle", - "props": { - "children": "{{components.length}} Components" - } - } - ] - }, - { - "id": "stat-card-3-content", - "type": "CardContent", - "children": [ - { - "id": "stat-3-value", - "type": "div", - "props": { - "className": "text-2xl font-bold", - "children": "{{components.length}}" - } - } - ] - } - ] - } - ] - }, - { - "id": "files-section", - "type": "Card", - "props": { - "className": "mt-6" - }, - "children": [ - { - "id": "files-header", - "type": "CardHeader", - "children": [ - { - "id": "files-title", - "type": "CardTitle", - "props": { - "children": "Recent Files" - } - }, - { - "id": "files-description", - "type": "CardDescription", - "props": { - "children": "Files loaded from your project" - } - } - ] - }, - { - "id": "files-content", - "type": "CardContent", - "children": [ - { - "id": "files-list", - "type": "div", - "props": { - "className": "space-y-2" - }, - "repeat": { - "items": "files.slice(0, 5)", - "itemVar": "file" - }, - "children": [ - { - "id": "file-item", - "type": "div", - "props": { - "className": "flex items-center justify-between p-2 border border-border rounded-md hover:bg-accent/50 transition-colors" - }, - "children": [ - { - "id": "file-name", - "type": "div", - "props": { - "className": "font-mono text-sm", - "children": "{{file.name}}" - } - }, - { - "id": "file-badge", - "type": "Badge", - "props": { - "variant": "outline", - "children": "{{file.type}}" - } - } - ] - } - ] - } - ] - } - ] - } - ] -} diff --git a/src/hooks/ui/index.ts b/src/hooks/ui/index.ts index 3b715ed..6931aec 100644 --- a/src/hooks/ui/index.ts +++ b/src/hooks/ui/index.ts @@ -1,7 +1,3 @@ -export { useDataBinding } from './use-data-binding' -export { useEventHandlers } from './use-event-handlers' -export { useSchemaLoader } from './use-schema-loader' -export { useComponentRegistry } from './use-component-registry' export { useDashboardMetrics } from './use-dashboard-metrics' export { useDashboardTips } from './use-dashboard-tips' export { useToggle } from './use-toggle' diff --git a/src/hooks/ui/use-component-registry.ts b/src/hooks/ui/use-component-registry.ts deleted file mode 100644 index 6c0d309..0000000 --- a/src/hooks/ui/use-component-registry.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createElement, useMemo } from 'react' -import { uiComponentRegistry, iconComponents } from '@/lib/json-ui/component-registry' -import * as Icons from '@phosphor-icons/react' - -interface ComponentRegistryOptions { - customComponents?: Record> -} - -export function useComponentRegistry({ customComponents = {} }: ComponentRegistryOptions = {}) { - const registry = useMemo( - () => ({ - ...uiComponentRegistry, - ...customComponents, - }), - [customComponents] - ) - - const getComponent = (type: string): React.ComponentType | null => { - return registry[type as keyof typeof registry] || null - } - - const getIcon = (iconName: string, props?: any): React.ReactElement | null => { - const registryIcon = registry[iconName as keyof typeof registry] - const IconComponent = - (registryIcon && typeof registryIcon !== 'string' ? registryIcon : null) || - iconComponents[iconName as keyof typeof iconComponents] || - (Icons as any)[iconName] - if (!IconComponent || typeof IconComponent === 'string') return null - return createElement(IconComponent, { size: 24, weight: "duotone", ...props }) - } - - return { - registry, - getComponent, - getIcon, - } -} diff --git a/src/hooks/ui/use-data-binding.ts b/src/hooks/ui/use-data-binding.ts deleted file mode 100644 index 3bb0157..0000000 --- a/src/hooks/ui/use-data-binding.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useCallback, useMemo } from 'react' - -interface UseDataBindingOptions { - data: Record - onError?: (error: Error, expression: string) => void -} - -export function useDataBinding({ data, onError }: UseDataBindingOptions) { - const resolveBinding = useCallback( - (expression: string, fallback?: any): any => { - if (!expression) return fallback - - try { - const keys = Object.keys(data) - const values = Object.values(data) - const func = new Function(...keys, `"use strict"; return (${expression})`) - return func(...values) - } catch (error) { - if (onError) { - onError(error as Error, expression) - } - console.warn(`Failed to resolve binding: ${expression}`, error) - return fallback - } - }, - [data, onError] - ) - - const resolveCondition = useCallback( - (condition: string): boolean => { - try { - const result = resolveBinding(condition, false) - return Boolean(result) - } catch { - return false - } - }, - [resolveBinding] - ) - - const resolveProps = useCallback( - (props: Record): Record => { - if (!props) return {} - - const resolved: Record = {} - - for (const [key, value] of Object.entries(props)) { - if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) { - const expression = value.slice(2, -2).trim() - resolved[key] = resolveBinding(expression) - } else if (typeof value === 'object' && value !== null && value.type === 'binding') { - resolved[key] = resolveBinding(value.expression, value.fallback) - } else { - resolved[key] = value - } - } - - return resolved - }, - [resolveBinding] - ) - - const context = useMemo( - () => ({ - resolveBinding, - resolveCondition, - resolveProps, - data, - }), - [resolveBinding, resolveCondition, resolveProps, data] - ) - - return context -} diff --git a/src/hooks/ui/use-event-handlers.ts b/src/hooks/ui/use-event-handlers.ts deleted file mode 100644 index 0387985..0000000 --- a/src/hooks/ui/use-event-handlers.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useCallback, useMemo } from 'react' - -interface UseEventHandlersOptions { - functions?: Record any> - onError?: (error: Error, functionName: string) => void -} - -export function useEventHandlers({ functions = {}, onError }: UseEventHandlersOptions) { - const createHandler = useCallback( - (functionName: string) => { - return (...args: any[]) => { - const handler = functions[functionName] - - if (!handler) { - const error = new Error(`Function "${functionName}" not found`) - if (onError) { - onError(error, functionName) - } else { - console.error(error) - } - return - } - - try { - return handler(...args) - } catch (error) { - if (onError) { - onError(error as Error, functionName) - } else { - console.error(`Error executing function "${functionName}":`, error) - } - } - } - }, - [functions, onError] - ) - - const resolveEvents = useCallback( - (events?: Record): Record any> => { - if (!events) return {} - - const resolved: Record any> = {} - - for (const [eventName, functionName] of Object.entries(events)) { - resolved[eventName] = createHandler(functionName) - } - - return resolved - }, - [createHandler] - ) - - const context = useMemo( - () => ({ - createHandler, - resolveEvents, - functions, - }), - [createHandler, resolveEvents, functions] - ) - - return context -} diff --git a/src/hooks/ui/use-schema-loader.ts b/src/hooks/ui/use-schema-loader.ts deleted file mode 100644 index 241ff15..0000000 --- a/src/hooks/ui/use-schema-loader.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useState, useCallback, useEffect } from 'react' -import { PageSchemaType } from '@/schemas/ui-schema' - -interface UseSchemaLoaderOptions { - schemaUrl?: string - schema?: PageSchemaType - onError?: (error: Error) => void -} - -export function useSchemaLoader({ schemaUrl, schema: initialSchema, onError }: UseSchemaLoaderOptions) { - const [schema, setSchema] = useState(initialSchema || null) - const [loading, setLoading] = useState(!!schemaUrl && !initialSchema) - const [error, setError] = useState(null) - - const loadSchema = useCallback( - async (url: string) => { - setLoading(true) - setError(null) - - try { - const response = await fetch(url) - if (!response.ok) { - throw new Error(`Failed to load schema: ${response.statusText}`) - } - - const data = await response.json() - setSchema(data) - } catch (err) { - const error = err instanceof Error ? err : new Error('Unknown error loading schema') - setError(error) - if (onError) { - onError(error) - } - } finally { - setLoading(false) - } - }, - [onError] - ) - - useEffect(() => { - if (schemaUrl && !initialSchema) { - loadSchema(schemaUrl) - } - }, [schemaUrl, initialSchema, loadSchema]) - - const reloadSchema = useCallback(() => { - if (schemaUrl) { - loadSchema(schemaUrl) - } - }, [schemaUrl, loadSchema]) - - return { - schema, - loading, - error, - reloadSchema, - setSchema, - } -} diff --git a/src/lib/schema-renderer.tsx b/src/lib/schema-renderer.tsx deleted file mode 100644 index 02ed0fc..0000000 --- a/src/lib/schema-renderer.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { createElement, type ComponentType, type ReactNode } from 'react' -import { cn } from '@/lib/utils' -import { Component as ComponentSchema, Layout } from '@/schemas/ui-schema' -import { useDataBinding, useEventHandlers, useComponentRegistry } from '@/hooks/ui' -import { getDeprecatedComponentInfo } from '@/lib/json-ui/component-registry' - -interface SchemaRendererProps { - schema: ComponentSchema - data: Record - functions?: Record any> - componentRegistry?: Record> -} - -interface LayoutRendererProps { - layout: Layout - children: ReactNode -} - -const warnedDeprecatedComponents = new Set() - -const warnDeprecatedComponent = (schema: ComponentSchema) => { - const deprecatedInfo = getDeprecatedComponentInfo(schema.type) - if (!deprecatedInfo || warnedDeprecatedComponents.has(schema.type)) { - return - } - - const idSuffix = schema.id ? ` (id: ${schema.id})` : '' - const replacementHint = deprecatedInfo.replacedBy - ? ` Replace with "${deprecatedInfo.replacedBy}".` - : '' - const extraMessage = deprecatedInfo.message ? ` ${deprecatedInfo.message}` : '' - - console.warn( - `[SchemaRenderer] Deprecated component "${schema.type}" detected in schema${idSuffix}.${replacementHint}${extraMessage}` - ) - warnedDeprecatedComponents.add(schema.type) -} - -function LayoutRenderer({ layout, children }: LayoutRendererProps) { - const getLayoutClasses = () => { - const classes: string[] = [] - - if (layout.type === 'flex') { - classes.push('flex') - if (layout.direction) { - classes.push(layout.direction === 'column' ? 'flex-col' : 'flex-row') - } - } else if (layout.type === 'grid') { - classes.push('grid') - if (layout.columns) { - const { base = 1, sm, md, lg, xl } = layout.columns - classes.push(`grid-cols-${base}`) - if (sm) classes.push(`sm:grid-cols-${sm}`) - if (md) classes.push(`md:grid-cols-${md}`) - if (lg) classes.push(`lg:grid-cols-${lg}`) - if (xl) classes.push(`xl:grid-cols-${xl}`) - } - } else if (layout.type === 'stack') { - classes.push('flex flex-col') - } - - if (layout.gap) { - classes.push(`gap-${layout.gap}`) - } - - if (layout.className) { - classes.push(layout.className) - } - - return cn(...classes) - } - - return
{children}
-} - -export function SchemaRenderer({ schema, data, functions = {}, componentRegistry }: SchemaRendererProps) { - const { resolveCondition, resolveProps, resolveBinding } = useDataBinding({ data }) - const { resolveEvents } = useEventHandlers({ functions }) - const { getComponent, getIcon } = useComponentRegistry({ customComponents: componentRegistry }) - - if (schema.condition && !resolveCondition(schema.condition)) { - return null - } - - if (schema.repeat) { - const items = resolveBinding(schema.repeat.items, []) as any[] - return ( - <> - {items.map((item, index) => { - const itemData = { - ...data, - [schema.repeat!.itemVar]: item, - ...(schema.repeat!.indexVar ? { [schema.repeat!.indexVar]: index } : {}), - } - return ( - - ) - })} - - ) - } - - warnDeprecatedComponent(schema) - - const props = resolveProps(schema.props || {}) - const events = resolveEvents(schema.events) - const combinedProps = { ...props, ...events } - - if (schema.binding) { - const iconName = resolveBinding(schema.binding) - if (typeof iconName === 'string' && schema.type === 'Icon') { - const IconComponent = getComponent(iconName) - if (IconComponent) { - return createElement(IconComponent, combinedProps) - } - return getIcon(iconName, combinedProps) - } - } - - const Component = getComponent(schema.type) - - if (!Component) { - console.warn(`Component type "${schema.type}" not found in registry`) - return ( -
-

- Unknown component: {schema.type} -

-
- ) - } - - const children = schema.children?.map((child, index) => ( - - )) - - return {children} -} - -interface PageRendererProps { - schema: { - id: string - title?: string - description?: string - layout: Layout - components: ComponentSchema[] - } - data: Record - functions?: Record any> - componentRegistry?: Record> -} - -export function PageRenderer({ schema, data, functions = {}, componentRegistry }: PageRendererProps) { - return ( - - {schema.components.map((component) => ( - - ))} - - ) -} diff --git a/src/schemas/ui-schema.ts b/src/schemas/ui-schema.ts deleted file mode 100644 index 490bbe4..0000000 --- a/src/schemas/ui-schema.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { z } from 'zod' - -export const BindingSchema = z.object({ - type: z.literal('binding'), - expression: z.string(), - fallback: z.any().optional(), -}) - -export const ComponentSchema: z.ZodType = z.lazy(() => - z.object({ - id: z.string(), - type: z.string(), - props: z.record(z.string(), z.any()).optional(), - children: z.array(ComponentSchema).optional(), - binding: z.string().optional(), - condition: z.string().optional(), - repeat: z - .object({ - items: z.string(), - itemVar: z.string(), - indexVar: z.string().optional(), - }) - .optional(), - events: z.record(z.string(), z.string()).optional(), - }) -) - -export const LayoutSchema = z.object({ - type: z.enum(['flex', 'grid', 'stack', 'custom']), - direction: z.enum(['row', 'column']).optional(), - gap: z.string().optional(), - columns: z - .object({ - base: z.number().optional(), - sm: z.number().optional(), - md: z.number().optional(), - lg: z.number().optional(), - xl: z.number().optional(), - }) - .optional(), - className: z.string().optional(), -}) - -export const PageSchema = z.object({ - id: z.string(), - title: z.string(), - description: z.string().optional(), - layout: LayoutSchema, - components: z.array(ComponentSchema), - dataBindings: z.array(z.string()).optional(), - functions: z.record(z.string(), z.string()).optional(), -}) - -export type Binding = z.infer -export type Component = z.infer -export type Layout = z.infer -export type PageSchemaType = z.infer