From 32ed16a4ca7c32f88c2a629a1f0719d76d669450 Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:30 +0000 Subject: [PATCH 1/7] feat(core): add x-custom to schema and schema types --- packages/core/src/schema/schema-types.ts | 13 +++++ .../factories/src/schemas/form-schema.json | 52 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/packages/core/src/schema/schema-types.ts b/packages/core/src/schema/schema-types.ts index 61fea33..7d448ea 100644 --- a/packages/core/src/schema/schema-types.ts +++ b/packages/core/src/schema/schema-types.ts @@ -20,6 +20,7 @@ export interface FormSectionContainer { "x-ui": xUi properties: FormSectionContainerProperties "x-component": "FormSectionContainer" + "x-custom"?: boolean "x-component-props"?: FormSectionContainerComponentProps } export interface xUi { @@ -40,6 +41,7 @@ export interface FormSectionTitle { "x-slots"?: XSlots "x-content": string "x-component": "FormSectionTitle" + "x-custom"?: boolean "x-component-props"?: FormSectionTitleComponentProps } export interface XSlots { @@ -52,6 +54,7 @@ export interface FormSectionGroupContainer { type: "object" properties: FormSectionGroupContainerProperties "x-component": "FormSectionGroupContainer" + "x-custom"?: boolean "x-component-props"?: FormSectionGroupContainerComponentProps } export interface FormSectionGroupContainerProperties { @@ -65,6 +68,7 @@ export interface FormSectionGroup { type: "object" properties: FormSectionGroupProperties "x-component": "FormSectionGroup" + "x-custom"?: boolean "x-component-props"?: FormSectionGroupComponentProps } export interface FormSectionGroupProperties { @@ -79,6 +83,7 @@ export interface FormSectionGroupProperties { export interface FormField { type: "object" "x-component": "FormField" + "x-custom"?: boolean "x-ui": xUi properties: FormFieldProperties "x-component-props"?: FormFieldComponentProps @@ -101,6 +106,7 @@ export interface FormFieldProperties { export interface InputText { type: "string" "x-component": "InputText" + "x-custom"?: boolean "x-component-props"?: InputTextComponentProps } export interface InputTextComponentProps { @@ -111,6 +117,7 @@ export interface InputTextComponentProps { export interface InputSelect { type: "string" "x-component": "InputSelect" + "x-custom"?: boolean "x-component-props"?: InputSelectComponentProps } export interface InputSelectComponentProps { @@ -121,6 +128,7 @@ export interface InputSelectComponentProps { export interface InputCheckbox { type: "boolean" "x-component": "InputCheckbox" + "x-custom"?: boolean "x-component-props"?: InputCheckboxComponentProps } export interface InputCheckboxComponentProps { @@ -131,6 +139,7 @@ export interface InputCheckboxComponentProps { export interface InputDate { type: "string" "x-component": "InputDate" + "x-custom"?: boolean "x-component-props"?: InputDateComponentProps } export interface InputDateComponentProps { @@ -141,6 +150,7 @@ export interface InputDateComponentProps { export interface InputPhone { type: "string" "x-component": "InputPhone" + "x-custom"?: boolean "x-component-props"?: InputPhoneComponentProps } export interface InputPhoneComponentProps { @@ -151,6 +161,7 @@ export interface InputPhoneComponentProps { export interface InputAutocomplete { type: "string" "x-component": "InputAutocomplete" + "x-custom"?: boolean "x-component-props"?: InputAutocompleteComponentProps } export interface InputAutocompleteComponentProps { @@ -161,6 +172,7 @@ export interface InputAutocompleteComponentProps { export interface InputTextarea { type: "string" "x-component": "InputTextarea" + "x-custom"?: boolean "x-component-props"?: InputTextareaComponentProps } export interface InputTextareaComponentProps { @@ -169,6 +181,7 @@ export interface InputTextareaComponentProps { export interface InputNumber { type: "number" "x-component": "InputNumber" + "x-custom"?: boolean "x-component-props"?: InputNumberComponentProps } export interface InputNumberComponentProps { diff --git a/packages/factories/src/schemas/form-schema.json b/packages/factories/src/schemas/form-schema.json index 621498f..58196c3 100644 --- a/packages/factories/src/schemas/form-schema.json +++ b/packages/factories/src/schemas/form-schema.json @@ -22,6 +22,10 @@ "x-component": { "const": "FormField" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-ui": { "title": "xUi", "type": "object", @@ -96,6 +100,10 @@ "x-component": { "const": "InputText" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputTextComponentProps", "type": "object", @@ -122,6 +130,10 @@ "x-component": { "const": "InputDate" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputDateComponentProps", "type": "object", @@ -148,6 +160,10 @@ "x-component": { "const": "InputPhone" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputPhoneComponentProps", "type": "object", @@ -174,6 +190,10 @@ "x-component": { "const": "InputSelect" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputSelectComponentProps", "type": "object", @@ -200,6 +220,10 @@ "x-component": { "const": "InputCheckbox" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputCheckboxComponentProps", "type": "object", @@ -226,6 +250,10 @@ "x-component": { "const": "InputTextarea" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputTextareaComponentProps", "type": "object", @@ -248,6 +276,10 @@ "x-component": { "const": "InputNumber" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputNumberComponentProps", "type": "object", @@ -281,6 +313,10 @@ "x-component": { "const": "FormSectionGroup" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "FormSectionGroupComponentProps", "type": "object", @@ -311,6 +347,10 @@ "x-component": { "const": "FormSectionTitle" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "FormSectionTitleComponentProps", "type": "object", @@ -333,6 +373,10 @@ "x-component": { "const": "InputAutocomplete" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "InputAutocompleteComponentProps", "type": "object", @@ -394,6 +438,10 @@ "x-component": { "const": "FormSectionContainer" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "FormSectionContainerComponentProps", "type": "object", @@ -427,6 +475,10 @@ "x-component": { "const": "FormSectionGroupContainer" }, + "x-custom": { + "type": "boolean", + "description": "If true, the component will be replaced by a custom component with the same key name" + }, "x-component-props": { "title": "FormSectionGroupContainerComponentProps", "type": "object", From d64ebb92957f80e1939a3b9dd59557aea18c5bf8 Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:31 +0000 Subject: [PATCH 2/7] feat(core): add custom field support in schema traversal --- packages/core/src/validation/schema-traversal.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/validation/schema-traversal.ts b/packages/core/src/validation/schema-traversal.ts index 3013985..bd2ab05 100644 --- a/packages/core/src/validation/schema-traversal.ts +++ b/packages/core/src/validation/schema-traversal.ts @@ -11,6 +11,8 @@ import type { FormFieldProperties, FormSchema } from '../schema/schema-types'; * Represents a field node extracted from the schema */ export interface FieldNode { + /** Custom flag from x-custom */ + custom: boolean; /** Field name (leaf key in the schema, e.g., 'firstName') */ name: string; /** Full path to the field (e.g., 'personalInfo.firstName') */ @@ -162,6 +164,7 @@ export function traverseFormSchema(schema: FormSchema, visitor: FieldVisitor): v const props = (value as any)['x-component-props'] || {}; // Get x-ui from input (can override FormField's x-ui) const inputXUi = (value as any)['x-ui'] || {}; + const inputXCustom = (value as any)['x-custom'] || false; // Check if it's an input component if (inputComponent && isInputComponent(inputComponent)) { @@ -181,6 +184,7 @@ export function traverseFormSchema(schema: FormSchema, visitor: FieldVisitor): v component: inputComponent, label: props.label, visible, + custom: inputXCustom, props, }; visitor(field); From 43c2e9c3985c81a4a5fa281383ba018c49223cb8 Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:31 +0000 Subject: [PATCH 3/7] feat(core): add customComponents resolution in renderer orchestrator --- .../src/orchestrator/renderer-orchestrator.ts | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/core/src/orchestrator/renderer-orchestrator.ts b/packages/core/src/orchestrator/renderer-orchestrator.ts index aaa85e7..5e850f6 100644 --- a/packages/core/src/orchestrator/renderer-orchestrator.ts +++ b/packages/core/src/orchestrator/renderer-orchestrator.ts @@ -33,6 +33,7 @@ export type ResolutionResult = ResolutionSuccess | null; */ export interface FactorySetupResult { components: Record; + customComponents?: Record; renderers?: Partial>; externalContext: Record; state: Record; @@ -96,7 +97,8 @@ export function createRendererOrchestrator( isDirectRootProperty: boolean = false ): any { const { - components, + components, + customComponents, renderers: localRenderers, externalContext, state, @@ -126,6 +128,71 @@ export function createRendererOrchestrator( if (xUi.visible === false) { return null; } + + // Check for x-custom flag - if true, use custom component instead + const isCustomComponent = processedSchema['x-custom'] === true; + + if (isCustomComponent && customComponents) { + // Look up custom component by the property key name + const customSpec = customComponents[componentKey]; + + if (customSpec) { + // Get renderer for the custom component type + const customRendererFn = getRendererForType( + customSpec.type || 'field', + undefined, + localRenderers as any, + debug?.isEnabled + ); + + // Build props for custom component + const customProps = { + ...customSpec.defaultProps, + // Pass the original schema so custom component can access x-component-props, etc. + schema: processedSchema, + // Pass the component key + componentKey, + // Pass the name path for form binding + name: parentProps.name ? `${parentProps.name}.${componentKey}` : componentKey, + // Pass external context + externalContext, + // Pass x-component-props if present + ...(processedSchema['x-component-props'] || {}), + }; + + // Render children if schema has properties + const customChildren: any[] = []; + if (processedSchema.properties && typeof processedSchema.properties === 'object') { + const childParentProps = { + ...customProps, + name: customProps.name, + }; + + const sortedEntries = Object.entries(processedSchema.properties).sort( + ([, a], [, b]) => { + const orderA = (a as any)?.['x-ui']?.order ?? Infinity; + const orderB = (b as any)?.['x-ui']?.order ?? Infinity; + return orderA - orderB; + } + ); + + for (const [key, childSchema] of sortedEntries) { + const childResult = render(key, childSchema as any, childParentProps, namePath, false); + if (childResult !== null && childResult !== undefined) { + customChildren.push(childResult); + } + } + } + + // Render the custom component + return customRendererFn(customSpec, customProps, runtime, customChildren.length > 0 ? customChildren : undefined); + } else { + // Custom component not found - log warning and fall back to normal rendering + if (debug?.isEnabled) { + console.warn(`Custom component not found for key: ${componentKey}. Falling back to standard component.`); + } + } + } // Parse schema (now using processed schema) const { 'x-component-props': componentProps = {} } = processedSchema; From 7d8c4cf7ea0c28235220cd3dd341b68ad0045c8a Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:32 +0000 Subject: [PATCH 4/7] feat(adapters): add customComponents to ScheptaProvider --- packages/adapters/react/src/provider.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/adapters/react/src/provider.tsx b/packages/adapters/react/src/provider.tsx index 684dc5d..06ed48b 100644 --- a/packages/adapters/react/src/provider.tsx +++ b/packages/adapters/react/src/provider.tsx @@ -18,6 +18,7 @@ import { getRendererRegistry } from '@schepta/core'; export interface ScheptaProviderProps { children: React.ReactNode; components?: Record; + customComponents?: Record; renderers?: Partial>; middlewares?: MiddlewareFn[]; debug?: DebugConfig; @@ -30,6 +31,7 @@ export interface ScheptaProviderProps { */ export interface ScheptaContextType { components: Record; + customComponents: Record; renderers: Record; middlewares: MiddlewareFn[]; debug: DebugConfig; @@ -61,6 +63,7 @@ const ScheptaContext = createContext(null); export function ScheptaProvider({ children, components = {}, + customComponents = {}, renderers = {}, middlewares = [], debug = defaultDebugConfig, @@ -78,6 +81,7 @@ export function ScheptaProvider({ return { components: { ...parentContext.components, ...components }, + customComponents: { ...parentContext.customComponents, ...customComponents }, renderers: mergedRenderers, middlewares: [...parentContext.middlewares, ...middlewares], debug: { ...parentContext.debug, ...debug }, @@ -92,13 +96,14 @@ export function ScheptaProvider({ return { components, + customComponents, renderers: mergedRenderers, middlewares, debug: mergedDebug, schema, externalContext, }; - }, [parentContext, components, renderers, middlewares, debug, schema, externalContext]); + }, [parentContext, components, customComponents, renderers, middlewares, debug, schema, externalContext]); return ( From bc3923abcd37064fda9799e320f9bed97d76b0e0 Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:35 +0000 Subject: [PATCH 5/7] feat(factories): add customComponents to FormFactory and field renderer --- packages/factories/react/src/form-factory.tsx | 7 ++++++- packages/factories/react/src/hooks/use-merged-config.ts | 8 ++++++++ packages/factories/react/src/renderers/field-renderer.ts | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/factories/react/src/form-factory.tsx b/packages/factories/react/src/form-factory.tsx index 9442c57..dcf75b7 100644 --- a/packages/factories/react/src/form-factory.tsx +++ b/packages/factories/react/src/form-factory.tsx @@ -4,7 +4,7 @@ * Factory component for rendering forms from JSON schemas. */ -import React, { useMemo, forwardRef, useImperativeHandle } from 'react'; +import { useMemo, forwardRef, useImperativeHandle } from 'react'; import type { FormSchema, ComponentSpec, MiddlewareFn, FormAdapter } from '@schepta/core'; import { createReactRuntimeAdapter } from '@schepta/adapter-react'; import { @@ -64,6 +64,7 @@ export interface FormFactoryRef { export interface FormFactoryProps { schema: FormSchema; components?: Record; + customComponents?: Record; renderers?: Partial>; externalContext?: Record; middlewares?: MiddlewareFn[]; @@ -76,6 +77,7 @@ export interface FormFactoryProps { export const FormFactory = forwardRef(function FormFactory({ schema, components, + customComponents, renderers, externalContext, middlewares, @@ -92,6 +94,7 @@ export const FormFactory = forwardRef(function // Merge provider config with local props const mergedConfig = useMergedScheptaConfig({ components, + customComponents, renderers, externalContext, middlewares, @@ -180,6 +183,7 @@ export const FormFactory = forwardRef(function return { components: mergedConfig.components, + customComponents: mergedConfig.customComponents, renderers: customRenderers, externalContext: { ...mergedConfig.externalContext, @@ -195,6 +199,7 @@ export const FormFactory = forwardRef(function return createRendererOrchestrator(getFactorySetup, runtime); }, [ mergedConfig.components, + mergedConfig.customComponents, mergedConfig.renderers, mergedConfig.externalContext, mergedConfig.baseMiddlewares, diff --git a/packages/factories/react/src/hooks/use-merged-config.ts b/packages/factories/react/src/hooks/use-merged-config.ts index c08d84d..0e6e0fd 100644 --- a/packages/factories/react/src/hooks/use-merged-config.ts +++ b/packages/factories/react/src/hooks/use-merged-config.ts @@ -11,6 +11,7 @@ import { useScheptaContext } from '@schepta/adapter-react'; export interface MergedConfigInput { components?: Record; + customComponents?: Record; renderers?: Partial>; externalContext?: Record; middlewares?: MiddlewareFn[]; @@ -19,6 +20,7 @@ export interface MergedConfigInput { export interface MergedConfig { components: Record; + customComponents: Record; renderers: Partial>; externalContext: Record; baseMiddlewares: MiddlewareFn[]; @@ -39,6 +41,10 @@ export function useMergedScheptaConfig(props: MergedConfigInput): MergedConfig { ...(providerConfig?.components || {}), ...(props.components || {}), }, + customComponents: { + ...(providerConfig?.customComponents || {}), + ...(props.customComponents || {}), + }, renderers: { ...(providerConfig?.renderers || {}), ...(props.renderers || {}), @@ -56,11 +62,13 @@ export function useMergedScheptaConfig(props: MergedConfigInput): MergedConfig { : (providerConfig?.debug?.enabled || false), }), [ providerConfig?.components, + providerConfig?.customComponents, providerConfig?.renderers, providerConfig?.externalContext, providerConfig?.middlewares, providerConfig?.debug?.enabled, props.components, + props.customComponents, props.renderers, props.externalContext, props.middlewares, diff --git a/packages/factories/react/src/renderers/field-renderer.ts b/packages/factories/react/src/renderers/field-renderer.ts index 839f201..0891594 100644 --- a/packages/factories/react/src/renderers/field-renderer.ts +++ b/packages/factories/react/src/renderers/field-renderer.ts @@ -61,6 +61,9 @@ export function createFieldRenderer(options: FieldRendererOptions = {}): Rendere const componentProps = { ...xComponentProps, name, // Ensure name is passed + ...(props.externalContext ? { externalContext: props.externalContext } : {}), + ...(props.schema ? { schema: props.schema } : {}), + ...(props.componentKey ? { componentKey: props.componentKey } : {}), }; // Create FieldWrapper using React.createElement directly since we're in React context From 0e7ef4822113888b1196dd02305bbf6075253e3a Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:36 +0000 Subject: [PATCH 6/7] feat(examples): add NativeComplexForm with x-custom and features list --- .../components/Forms/NativeComplexForm.tsx | 212 ++++++++++++++++++ .../src/basic-ui/pages/BasicFormPage.tsx | 13 +- .../src/chakra-ui/pages/ChakraFormPage.tsx | 2 - instances/form/complex-form.json | 40 +++- 4 files changed, 249 insertions(+), 18 deletions(-) create mode 100644 examples/react/src/basic-ui/components/Forms/NativeComplexForm.tsx diff --git a/examples/react/src/basic-ui/components/Forms/NativeComplexForm.tsx b/examples/react/src/basic-ui/components/Forms/NativeComplexForm.tsx new file mode 100644 index 0000000..d6e4685 --- /dev/null +++ b/examples/react/src/basic-ui/components/Forms/NativeComplexForm.tsx @@ -0,0 +1,212 @@ +import React, { useState, useMemo } from "react"; +import { FormFactory, useScheptaFormAdapter } from "@schepta/factory-react"; +import { FormSchema, generateValidationSchema, createComponentSpec } from "@schepta/core"; + +interface FormProps { + schema: FormSchema; + initialValues?: Record; +} + +/** + * Custom component for Social Name input with toggle behavior + * - When openSocialName is false: shows a button to add + * - When openSocialName is true: shows the input and a button to remove + */ +interface SocialNameInputProps { + name: string; + schema: any; + externalContext: { + openSocialName: boolean; + setOpenSocialName: (value: boolean) => void; + }; +} + +const SocialNameInput = ({ name, schema, externalContext }: SocialNameInputProps) => { + const { openSocialName, setOpenSocialName } = externalContext; + const label = schema['x-component-props']?.label || 'Social Name'; + const placeholder = schema['x-component-props']?.placeholder || ''; + + // Get form adapter to register the field + const adapter = useScheptaFormAdapter(); + const register = adapter?.register; + + return ( +
+ + + {openSocialName && ( +
+ + +
+ )} +
+ ); +}; + +export const NativeComplexForm = ({ schema }: FormProps) => { + const [submittedValues, setSubmittedValues] = useState | null>(null); + const [openSocialName, setOpenSocialName] = useState(false); + + const initialValues = useMemo(() => { + const { initialValues: schemaInitialValues } = + generateValidationSchema(schema); + return { + ...schemaInitialValues, + ...{ + userInfo: { + enrollment: "8743", + }, + }, + }; + }, [schema]); + + // Register custom components for x-custom fields + const customComponents = useMemo(() => ({ + socialName: createComponentSpec({ + id: 'socialName', // Same as the key name in the schema + type: 'field', + factory: () => SocialNameInput, + }), + }), []); + + const handleSubmit = (values: Record) => { + console.log("Form submitted:", values); + setSubmittedValues(values); + }; + + return ( + <> +
+

+ What you can see in this form: +

+
    +
  • + Custom Components (x-custom): Social Name field with toggle behavior +
  • +
  • + Template Expressions: Conditional visibility (spouseName appears when maritalStatus is 'married') +
  • +
  • + Disabled Fields: Enrollment field is disabled +
  • +
  • + Required Fields: Email, Phone, First Name, Last Name, Accept Terms +
  • +
  • + Grid Layout: 2-column grid with full-width fields (socialName, spouseName) +
  • +
  • + Input Types: Text, Phone, Select, Date, Textarea, Checkbox +
  • +
  • + Sections: Organized with section containers and titles +
      +
    • + User Information contains two subsections: +
        +
      • basicInfo: enrollment, firstName, lastName, socialName, userType, birthDate, maritalStatus, spouseName
      • +
      • additionalInfo: bio, acceptTerms
      • +
      +
    • +
    +
  • +
  • + Field Ordering: Custom order via x-ui.order +
  • +
  • + Initial Values: Pre-filled enrollment value +
  • +
  • + External Context: State management for custom components +
  • +
+
+
+ +
+ {submittedValues && ( +
+

Submitted Values:

+
+            {JSON.stringify(submittedValues, null, 2)}
+          
+
+ )} + + ); +}; diff --git a/examples/react/src/basic-ui/pages/BasicFormPage.tsx b/examples/react/src/basic-ui/pages/BasicFormPage.tsx index c1538c7..a0d6ceb 100644 --- a/examples/react/src/basic-ui/pages/BasicFormPage.tsx +++ b/examples/react/src/basic-ui/pages/BasicFormPage.tsx @@ -8,16 +8,13 @@ import { ModalForm } from "../components/Forms/ModalForm"; import { FormWithRHF } from "../components/Forms/FormWithRHF"; import { FormWithFormik } from "../components/Forms/FormWithFormik"; import { FormSchema } from "@schepta/core"; +import { NativeComplexForm } from "../components/Forms/NativeComplexForm"; export function BasicFormPage() { const [tabValue, setTabValue] = useState(0); const simpleSchema = simpleFormSchema as FormSchema; const complexSchema = complexFormSchema as FormSchema; - const initialValues = { - userInfo: { - enrollment: '8743', - } - } + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); @@ -32,14 +29,13 @@ export function BasicFormPage() { - - + @@ -50,9 +46,6 @@ export function BasicFormPage() { - -

Expressions Example

-
); diff --git a/examples/react/src/chakra-ui/pages/ChakraFormPage.tsx b/examples/react/src/chakra-ui/pages/ChakraFormPage.tsx index 47d7a2b..3157027 100644 --- a/examples/react/src/chakra-ui/pages/ChakraFormPage.tsx +++ b/examples/react/src/chakra-ui/pages/ChakraFormPage.tsx @@ -28,7 +28,6 @@ export function ChakraFormPage() { SIMPLE FORM COMPLEX FORM - EXPRESSIONS EXAMPLE @@ -37,7 +36,6 @@ export function ChakraFormPage() {
- EXPRESSIONS EXAMPLE diff --git a/instances/form/complex-form.json b/instances/form/complex-form.json index 3ef1a7d..f34aa61 100644 --- a/instances/form/complex-form.json +++ b/instances/form/complex-form.json @@ -87,6 +87,9 @@ "basicInfo": { "type": "object", "x-component": "FormSectionGroup", + "x-component-props": { + "columns": "repeat(2, 1fr)" + }, "properties": { "enrollment": { "type": "object", @@ -110,7 +113,7 @@ "type": "object", "x-component": "FormField", "x-ui": { - "order": 2 + "order": 3 }, "properties": { "firstName": { @@ -128,7 +131,7 @@ "type": "object", "x-component": "FormField", "x-ui": { - "order": 3 + "order": 4 }, "properties": { "lastName": { @@ -142,11 +145,33 @@ } } }, + "socialName": { + "type": "object", + "x-component": "FormField", + "x-custom": true, + "x-ui": { + "order": 5 + }, + "x-component-props": { + "style": { "gridColumn": "1 / -1" } + }, + "properties": { + "socialName": { + "type": "string", + "x-component": "InputText", + "x-custom": true, + "x-component-props": { + "label": "Social Name", + "placeholder": "Enter your social name" + } + } + } + }, "userType": { "type": "object", "x-component": "FormField", "x-ui": { - "order": 3 + "order": 2 }, "properties": { "userType": { @@ -167,7 +192,7 @@ "type": "object", "x-component": "FormField", "x-ui": { - "order": 4 + "order": 6 }, "properties": { "birthDate": { @@ -183,7 +208,7 @@ "type": "object", "x-component": "FormField", "x-ui": { - "order": 5 + "order": 7 }, "properties": { "maritalStatus": { @@ -206,9 +231,12 @@ "type": "object", "x-component": "FormField", "x-ui": { - "order": 6, + "order": 8, "visible": "{{ $formValues.userInfo.maritalStatus === 'married' }}" }, + "x-component-props": { + "style": { "gridColumn": "1 / -1" } + }, "properties": { "spouseName": { "type": "string", From be958d0629e7593794fb1a4c4843ed076bf1cc75 Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 10:05:37 +0000 Subject: [PATCH 7/7] test(e2e): add social name custom field visibility test --- tests/e2e/react.spec.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/e2e/react.spec.ts b/tests/e2e/react.spec.ts index f2762e5..2821cb9 100644 --- a/tests/e2e/react.spec.ts +++ b/tests/e2e/react.spec.ts @@ -24,7 +24,7 @@ test.describe('React Form Factory', () => { test('should render complex form with all field types', async ({ page, baseURL }) => { await page.click('[data-test-id*="complex-form-tab"]'); - const fields = extractFieldsFromSchema(complexFormSchema as FormSchema).filter(field => field.visible === true).map(field => field.name); + const fields = extractFieldsFromSchema(complexFormSchema as FormSchema).filter(field => field.visible === true && field.custom === false).map(field => field.name); // Wait for form to be rendered await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 }); @@ -38,6 +38,7 @@ test.describe('React Form Factory', () => { test('should fill form fields', async ({ page }) => { const fields = extractFieldsFromSchema(complexFormSchema as FormSchema).filter(field => field.props.disabled !== true && field.visible === true + && field.custom === false ); const inputValues = { @@ -108,6 +109,44 @@ test.describe('React Form Factory', () => { await page.locator('[data-test-id*="maritalStatus"]').selectOption('married'); await expect(page.locator('[data-test-id*="spouseName"]')).toBeVisible(); }); + + test('should toggle social name custom field visibility', async ({ page }) => { + await page.click('[data-test-id*="complex-form-tab"]'); + await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 }); + + // Get the toggle button and input locators + const toggleButton = page.locator('[data-test-id="social-name-toggle"]'); + const socialNameInput = page.locator('[data-test-id="social-name-input"]'); + + // Initially, the "Add Social Name" button should be visible + await expect(toggleButton).toBeVisible(); + await expect(toggleButton).toHaveText(/Add Social Name/i); + + // The input should not be visible initially + await expect(socialNameInput).not.toBeVisible(); + + // Click to show the input + await toggleButton.click(); + + // Now the input should be visible + await expect(socialNameInput).toBeVisible(); + + // The button text should change to "Remove Social Name" + await expect(toggleButton).toHaveText(/Remove Social Name/i); + + // Fill the input + await socialNameInput.fill('John Social'); + await expect(socialNameInput).toHaveValue('John Social'); + + // Click to hide the input + await toggleButton.click(); + + // The input should be hidden again + await expect(socialNameInput).not.toBeVisible(); + + // The button should be back to "Add Social Name" + await expect(toggleButton).toHaveText(/Add Social Name/i); + }); }); test.describe('React Hook Form Integration', () => {