From e45692d90d5c39b8ff323ed5d56dac23de90e49c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 06:55:25 +0000 Subject: [PATCH 1/6] Initial plan From 49c73740c3d0465bed806620e952a7efc5493435 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 07:01:46 +0000 Subject: [PATCH 2/6] feat: Add FormRenderer component with basic field support Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/react/package.json | 6 +- .../src/components/form/FieldFactory.tsx | 242 ++++++++++++++++++ .../src/components/form/FormRenderer.tsx | 205 +++++++++++++++ packages/react/src/components/form/index.ts | 12 + packages/react/src/index.ts | 1 + pnpm-lock.yaml | 21 ++ 6 files changed, 486 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/components/form/FieldFactory.tsx create mode 100644 packages/react/src/components/form/FormRenderer.tsx create mode 100644 packages/react/src/components/form/index.ts diff --git a/packages/react/package.json b/packages/react/package.json index 029d4c3ac..ac502019c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -21,7 +21,11 @@ "lint": "eslint ." }, "dependencies": { - "@object-ui/core": "workspace:*" + "@object-ui/core": "workspace:*", + "@objectstack/spec": "^0.3.2", + "react-hook-form": "^7.71.1", + "zod": "^3.22.4", + "@hookform/resolvers": "^3.9.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", diff --git a/packages/react/src/components/form/FieldFactory.tsx b/packages/react/src/components/form/FieldFactory.tsx new file mode 100644 index 000000000..b39dcec38 --- /dev/null +++ b/packages/react/src/components/form/FieldFactory.tsx @@ -0,0 +1,242 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { UseFormReturn } from 'react-hook-form'; +import type { FormField } from '@objectstack/spec/ui'; + +export interface FieldFactoryProps { + /** + * Field configuration from FormFieldSchema + */ + field: FormField; + /** + * React Hook Form methods + */ + methods: UseFormReturn; + /** + * Whether the field is disabled + */ + disabled?: boolean; +} + +/** + * FieldFactory component that renders different input types based on + * the widget property or field type + */ +export const FieldFactory: React.FC = ({ + field, + methods, + disabled = false, +}) => { + const { register, formState: { errors }, watch } = methods; + + // Determine the widget type + const widgetType = field.widget || 'text'; + const fieldName = field.field; + const error = errors[fieldName]; + + // Handle conditional visibility + const shouldShow = React.useMemo(() => { + if (field.visibleOn) { + // Evaluate visibility expression + // For now, simple implementation - can be enhanced + try { + const formData = watch(); + // Simple expression evaluation: "fieldName == 'value'" + // This is a placeholder - real implementation would use a proper expression evaluator + return true; + } catch (e) { + return true; + } + } + return true; + }, [field.visibleOn, watch]); + + if (!shouldShow) { + return null; + } + + // Common field wrapper + const renderField = (input: React.ReactNode) => ( +
+ {field.label && ( + + )} + {input} + {field.helpText && ( +

{field.helpText}

+ )} + {error && ( +

{error.message as string}

+ )} +
+ ); + + // Render based on widget type + switch (widgetType.toLowerCase()) { + case 'text': + case 'string': + case 'email': + case 'password': + case 'url': + case 'tel': + return renderField( + + ); + + case 'number': + case 'integer': + case 'float': + return renderField( + + ); + + case 'checkbox': + case 'boolean': + return ( +
+ +
+ {field.label && ( + + )} + {field.helpText && ( +

{field.helpText}

+ )} + {error && ( +

{error.message as string}

+ )} +
+
+ ); + + case 'textarea': + return renderField( +