Generated by Spark: Load more of UI from JSON declarations and break up large components into atomic and create hooks as needed

This commit is contained in:
2026-01-17 12:01:34 +00:00
committed by GitHub
parent 165c8d3847
commit 944e164deb
13 changed files with 1035 additions and 25 deletions

View File

@@ -1,7 +1,4 @@
export { useDialog } from './use-dialog'
export { useActionExecutor } from './use-action-executor'
export { useToggle } from './use-toggle'
export { useForm } from './use-form'
export type { UseDialogReturn } from './use-dialog'
export type { UseToggleOptions } from './use-toggle'
export type { UseFormOptions, FormField } from './use-form'
export { useDataBinding } from './use-data-binding'
export { useEventHandlers } from './use-event-handlers'
export { useSchemaLoader } from './use-schema-loader'
export { useComponentRegistry } from './use-component-registry'

View File

@@ -0,0 +1,49 @@
import { useMemo } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Separator } from '@/components/ui/separator'
import { Progress } from '@/components/ui/progress'
import * as Icons from '@phosphor-icons/react'
interface ComponentRegistryOptions {
customComponents?: Record<string, React.ComponentType<any>>
}
export function useComponentRegistry({ customComponents = {} }: ComponentRegistryOptions = {}) {
const registry = useMemo(
() => ({
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
Button,
Badge,
Input,
Label,
Separator,
Progress,
...customComponents,
}),
[customComponents]
)
const getComponent = (type: string): React.ComponentType<any> | null => {
return registry[type as keyof typeof registry] || null
}
const getIcon = (iconName: string, props?: any) => {
const IconComponent = (Icons as any)[iconName]
if (!IconComponent) return null
return <IconComponent size={24} weight="duotone" {...props} />
}
return {
registry,
getComponent,
getIcon,
}
}

View File

@@ -0,0 +1,74 @@
import { useCallback, useMemo } from 'react'
interface UseDataBindingOptions {
data: Record<string, any>
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<string, any>): Record<string, any> => {
if (!props) return {}
const resolved: Record<string, any> = {}
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
}

View File

@@ -0,0 +1,63 @@
import { useCallback, useMemo } from 'react'
interface UseEventHandlersOptions {
functions?: Record<string, (...args: any[]) => 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<string, string>): Record<string, (...args: any[]) => any> => {
if (!events) return {}
const resolved: Record<string, (...args: any[]) => 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
}

View File

@@ -0,0 +1,60 @@
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<PageSchemaType | null>(initialSchema || null)
const [loading, setLoading] = useState(!!schemaUrl && !initialSchema)
const [error, setError] = useState<Error | null>(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,
}
}