Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions examples/react/src/basic-ui/components/Forms/NativeComplexForm.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>;
}

/**
* 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 (
<div style={{ marginBottom: '16px', gridColumn: '1 / -1' }}>
<button
type="button"
data-test-id="social-name-toggle"
onClick={() => setOpenSocialName(!openSocialName)}
style={{
background: 'none',
border: 'none',
color: '#2563eb',
cursor: 'pointer',
padding: 0,
fontSize: '14px',
fontWeight: 500,
}}
>
{openSocialName ? `- Remove ${label}` : `+ Add ${label}`}
</button>

{openSocialName && (
<div style={{ marginTop: '8px', width: '49%' }}>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '14px', fontWeight: 500 }}>
{label}
</label>
<input
type="text"
data-test-id="social-name-input"
placeholder={placeholder}
{...(register ? register(name) : { name })}
style={{
display: 'block',
width: '100%',
padding: '8px 12px',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '14px',
}}
/>
</div>
)}
</div>
);
};

export const NativeComplexForm = ({ schema }: FormProps) => {
const [submittedValues, setSubmittedValues] = useState<Record<
string,
any
> | 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<string, any>) => {
console.log("Form submitted:", values);
setSubmittedValues(values);
};

return (
<>
<div
style={{
marginBottom: "24px",
padding: "20px",
background: "#f9fafb",
border: "1px solid #e5e7eb",
borderRadius: "8px",
}}
>
<h3 style={{ marginTop: 0, marginBottom: "16px", fontSize: "18px", fontWeight: 600 }}>
What you can see in this form:
</h3>
<ul style={{ margin: 0, paddingLeft: "20px", fontSize: "14px", lineHeight: "1.8" }}>
<li style={{ marginBottom: "8px" }}>
<strong>Custom Components (x-custom):</strong> Social Name field with toggle behavior
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Template Expressions:</strong> Conditional visibility (spouseName appears when maritalStatus is 'married')
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Disabled Fields:</strong> Enrollment field is disabled
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Required Fields:</strong> Email, Phone, First Name, Last Name, Accept Terms
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Grid Layout:</strong> 2-column grid with full-width fields (socialName, spouseName)
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Input Types:</strong> Text, Phone, Select, Date, Textarea, Checkbox
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Sections:</strong> Organized with section containers and titles
<ul style={{ marginTop: "4px", marginBottom: "4px" }}>
<li>
<strong>User Information</strong> contains two subsections:
<ul style={{ marginTop: "4px" }}>
<li><strong>basicInfo:</strong> enrollment, firstName, lastName, socialName, userType, birthDate, maritalStatus, spouseName</li>
<li><strong>additionalInfo:</strong> bio, acceptTerms</li>
</ul>
</li>
</ul>
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Field Ordering:</strong> Custom order via x-ui.order
</li>
<li style={{ marginBottom: "8px" }}>
<strong>Initial Values:</strong> Pre-filled enrollment value
</li>
<li style={{ marginBottom: "8px" }}>
<strong>External Context:</strong> State management for custom components
</li>
</ul>
</div>
<div
style={{
border: "1px solid #ddd",
padding: "24px",
borderRadius: "8px",
}}
>
<FormFactory
schema={schema}
initialValues={initialValues}
customComponents={customComponents}
externalContext={{
openSocialName,
setOpenSocialName,
}}
onSubmit={handleSubmit}
debug={true}
/>
</div>
{submittedValues && (
<div
style={{
marginTop: "24px",
padding: "16px",
background: "#f9fafb",
border: "1px solid #e5e7eb",
borderRadius: "8px",
}}
>
<h3 style={{ marginTop: 0 }}>Submitted Values:</h3>
<pre
style={{
background: "white",
padding: "12px",
borderRadius: "4px",
overflow: "auto",
fontSize: "13px",
}}
>
{JSON.stringify(submittedValues, null, 2)}
</pre>
</div>
)}
</>
);
};
13 changes: 3 additions & 10 deletions examples/react/src/basic-ui/pages/BasicFormPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -32,14 +29,13 @@ export function BasicFormPage() {
<Tab data-test-id="modal-form-tab" label="Modal Form" />
<Tab data-test-id="rhf-form-tab" label="with React Hook Form" />
<Tab data-test-id="formik-form-tab" label="with Formik" />
<Tab data-test-id="expressions-example-tab" label="Expressions Example" />
</Tabs>

<TabPanel value={tabValue} index={0}>
<NativeForm schema={simpleSchema} />
</TabPanel>
<TabPanel value={tabValue} index={1}>
<NativeForm schema={complexSchema} initialValues={initialValues} />
<NativeComplexForm schema={complexSchema} />
</TabPanel>
<TabPanel value={tabValue} index={2}>
<ModalForm schema={simpleSchema} />
Expand All @@ -50,9 +46,6 @@ export function BasicFormPage() {
<TabPanel value={tabValue} index={4}>
<FormWithFormik schema={simpleSchema} />
</TabPanel>
<TabPanel value={tabValue} index={5}>
<p>Expressions Example</p>
</TabPanel>
</Paper>
</>
);
Expand Down
2 changes: 0 additions & 2 deletions examples/react/src/chakra-ui/pages/ChakraFormPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export function ChakraFormPage() {
<TabList>
<Tab>SIMPLE FORM</Tab>
<Tab>COMPLEX FORM</Tab>
<Tab>EXPRESSIONS EXAMPLE</Tab>
</TabList>
<TabPanels>
<TabPanel>
Expand All @@ -37,7 +36,6 @@ export function ChakraFormPage() {
<TabPanel>
<Form schema={complexSchema} />
</TabPanel>
<TabPanel>EXPRESSIONS EXAMPLE</TabPanel>
</TabPanels>
</Tabs>
</Box>
Expand Down
40 changes: 34 additions & 6 deletions instances/form/complex-form.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
"basicInfo": {
"type": "object",
"x-component": "FormSectionGroup",
"x-component-props": {
"columns": "repeat(2, 1fr)"
},
"properties": {
"enrollment": {
"type": "object",
Expand All @@ -110,7 +113,7 @@
"type": "object",
"x-component": "FormField",
"x-ui": {
"order": 2
"order": 3
},
"properties": {
"firstName": {
Expand All @@ -128,7 +131,7 @@
"type": "object",
"x-component": "FormField",
"x-ui": {
"order": 3
"order": 4
},
"properties": {
"lastName": {
Expand All @@ -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": {
Expand All @@ -167,7 +192,7 @@
"type": "object",
"x-component": "FormField",
"x-ui": {
"order": 4
"order": 6
},
"properties": {
"birthDate": {
Expand All @@ -183,7 +208,7 @@
"type": "object",
"x-component": "FormField",
"x-ui": {
"order": 5
"order": 7
},
"properties": {
"maritalStatus": {
Expand All @@ -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",
Expand Down
Loading