diff --git a/packages/react/package.json b/packages/react/package.json index 029d4c3ac..75e314488 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -21,7 +21,9 @@ "lint": "eslint ." }, "dependencies": { - "@object-ui/core": "workspace:*" + "@object-ui/core": "workspace:*", + "@objectstack/spec": "^0.3.2", + "react-hook-form": "^7.71.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..10003262f --- /dev/null +++ b/packages/react/src/components/form/FieldFactory.tsx @@ -0,0 +1,231 @@ +/** + * 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 } } = methods; + + // Determine the widget type + const widgetType = field.widget || 'text'; + const fieldName = field.field; + const error = errors[fieldName]; + + // Handle conditional visibility + // Note: visibleOn expression evaluation is not yet implemented + // Fields are always visible unless explicitly hidden + // Skip if explicitly hidden + if (field.hidden) { + 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( +