Skip to content

Commit d5cdf55

Browse files
authored
feat(validation): add schema validation system with AJV support (#11)
- Add schema parser and traversal utilities for form validation - Add form validator for Formik and native forms - Refactor RHF and Formik containers to use validation system - Update form schemas and test cases - Remove deprecated build-initial-values utility - Integrate validation with useSchepta hook for schema access
1 parent 0180b99 commit d5cdf55

25 files changed

Lines changed: 1724 additions & 1061 deletions

examples/react/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"@chakra-ui/react": "^2.8.2",
1212
"@emotion/react": "^11.11.1",
1313
"@emotion/styled": "^11.11.0",
14+
"@hookform/resolvers": "^3.9.0",
1415
"@mui/material": "^5.15.0",
1516
"@schepta/adapter-react": "workspace:*",
1617
"@schepta/core": "workspace:*",

examples/react/src/basic-ui/components/Forms/FormWithFormik.tsx

Lines changed: 26 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,20 @@
22
* Form with Formik
33
*
44
* Example component demonstrating how to use Schepta with Formik.
5-
* This shows how to inject custom Formik components via the component registry.
5+
* This shows how to inject custom Formik components via the component registry
6+
* with AJV validation using createFormikValidator.
67
*/
78

8-
import React, { useState } from 'react';
9+
import React, { useState, useMemo } from 'react';
910
import { FormFactory } from '@schepta/factory-react';
10-
import { createComponentSpec, FormSchema } from '@schepta/core';
11+
import {
12+
createComponentSpec,
13+
FormSchema,
14+
} from '@schepta/core';
1115
import { FormikFieldWrapper } from '../formik/FormikFieldWrapper';
1216
import { FormikFormContainer } from '../formik/FormikFormContainer';
1317

14-
// Import the same input components from basic-ui
15-
import { InputText } from '../Inputs/InputText';
16-
import { InputSelect } from '../Inputs/InputSelect';
17-
import { InputCheckbox } from '../Inputs/InputCheckbox';
18-
import { InputTextarea } from '../Inputs/InputTextarea';
19-
import { InputNumber } from '../Inputs/InputNumber';
20-
import { InputDate } from '../Inputs/InputDate';
21-
22-
/**
23-
* Formik-specific component registry.
24-
* Registers the Formik FieldWrapper and FormContainer to use
25-
* Formik for form state management.
26-
*/
27-
const formikComponents = {
28-
// Register Formik FieldWrapper - this makes all fields use Formik's context
29-
FieldWrapper: createComponentSpec({
30-
id: 'FieldWrapper',
31-
type: 'field-wrapper',
32-
factory: () => FormikFieldWrapper,
33-
}),
34-
// Register Formik FormContainer - this provides the Formik context
35-
FormContainer: createComponentSpec({
36-
id: 'FormContainer',
37-
type: 'FormContainer',
38-
factory: () => FormikFormContainer,
39-
}),
40-
// Standard input components (same as basic-ui)
41-
InputText: createComponentSpec({
42-
id: 'InputText',
43-
type: 'field',
44-
factory: () => InputText,
45-
}),
46-
InputSelect: createComponentSpec({
47-
id: 'InputSelect',
48-
type: 'field',
49-
factory: () => InputSelect,
50-
}),
51-
InputCheckbox: createComponentSpec({
52-
id: 'InputCheckbox',
53-
type: 'field',
54-
factory: () => InputCheckbox,
55-
}),
56-
InputTextarea: createComponentSpec({
57-
id: 'InputTextarea',
58-
type: 'field',
59-
factory: () => InputTextarea,
60-
}),
61-
InputNumber: createComponentSpec({
62-
id: 'InputNumber',
63-
type: 'field',
64-
factory: () => InputNumber,
65-
}),
66-
InputDate: createComponentSpec({
67-
id: 'InputDate',
68-
type: 'field',
69-
factory: () => InputDate,
70-
}),
71-
InputPhone: createComponentSpec({
72-
id: 'InputPhone',
73-
type: 'field',
74-
factory: () => InputText,
75-
defaultProps: { type: 'tel' },
76-
}),
77-
};
18+
import { components } from '../ComponentRegistry';
7819

7920
interface FormWithFormikProps {
8021
schema: FormSchema;
@@ -83,12 +24,28 @@ interface FormWithFormikProps {
8324
/**
8425
* FormWithFormik Component
8526
*
86-
* Renders a form using Formik for state management.
27+
* Renders a form using Formik for state management with AJV validation.
8728
* Demonstrates how to integrate external form libraries with Schepta.
8829
*/
8930
export const FormWithFormik: React.FC<FormWithFormikProps> = ({ schema }) => {
9031
const [submittedValues, setSubmittedValues] = useState<Record<string, any> | null>(null);
9132

33+
const formikComponents = useMemo(() => ({
34+
// Register Formik FieldWrapper - this makes all fields use Formik's context
35+
FieldWrapper: createComponentSpec({
36+
id: 'FieldWrapper',
37+
type: 'field-wrapper',
38+
factory: () => FormikFieldWrapper,
39+
}),
40+
// Register Formik FormContainer with validation - this provides the Formik context
41+
FormContainer: createComponentSpec({
42+
id: 'FormContainer',
43+
type: 'FormContainer',
44+
factory: () => FormikFormContainer,
45+
}),
46+
...components,
47+
}), []);
48+
9249
const handleSubmit = (values: Record<string, any>) => {
9350
console.log('Form submitted (Formik):', values);
9451
setSubmittedValues(values);
@@ -110,7 +67,7 @@ export const FormWithFormik: React.FC<FormWithFormikProps> = ({ schema }) => {
11067
borderRadius: '4px',
11168
fontSize: '14px',
11269
}}>
113-
This form uses <strong>Formik</strong> for state management.
70+
This form uses <strong>Formik</strong> with <strong>AJV validation</strong>.
11471
The FieldWrapper and FormContainer are custom Formik implementations.
11572
</div>
11673
<FormFactory

examples/react/src/basic-ui/components/Forms/FormWithRHF.tsx

Lines changed: 24 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,17 @@
22
* Form with React Hook Form
33
*
44
* Example component demonstrating how to use Schepta with react-hook-form.
5-
* This shows how to inject custom RHF components via the component registry.
5+
* This shows how to inject custom RHF components via the component registry
6+
* with AJV validation using @hookform/resolvers/ajv.
67
*/
78

8-
import React, { useState } from 'react';
9+
import React, { useState, useMemo } from 'react';
910
import { FormFactory } from '@schepta/factory-react';
1011
import { createComponentSpec, FormSchema } from '@schepta/core';
11-
import { RHFFieldWrapper } from '../rhf/RHFFieldWrapper';
1212
import { RHFFormContainer } from '../rhf/RHFFormContainer';
13+
import { RHFFieldWrapper } from '../rhf/RHFFieldWrapper';
1314

14-
// Import the same input components from basic-ui
15-
import { InputText } from '../Inputs/InputText';
16-
import { InputSelect } from '../Inputs/InputSelect';
17-
import { InputCheckbox } from '../Inputs/InputCheckbox';
18-
import { InputTextarea } from '../Inputs/InputTextarea';
19-
import { InputNumber } from '../Inputs/InputNumber';
20-
import { InputDate } from '../Inputs/InputDate';
21-
22-
/**
23-
* RHF-specific component registry.
24-
* Registers the RHF FieldWrapper and FormContainer to use
25-
* react-hook-form for form state management.
26-
*/
27-
const rhfComponents = {
28-
// Register RHF FieldWrapper - this makes all fields use RHF's Controller
29-
FieldWrapper: createComponentSpec({
30-
id: 'FieldWrapper',
31-
type: 'field-wrapper',
32-
factory: () => RHFFieldWrapper,
33-
}),
34-
// Register RHF FormContainer - this provides the FormProvider context
35-
FormContainer: createComponentSpec({
36-
id: 'FormContainer',
37-
type: 'FormContainer',
38-
factory: () => RHFFormContainer,
39-
}),
40-
// Standard input components (same as basic-ui)
41-
InputText: createComponentSpec({
42-
id: 'InputText',
43-
type: 'field',
44-
factory: () => InputText,
45-
}),
46-
InputSelect: createComponentSpec({
47-
id: 'InputSelect',
48-
type: 'field',
49-
factory: () => InputSelect,
50-
}),
51-
InputCheckbox: createComponentSpec({
52-
id: 'InputCheckbox',
53-
type: 'field',
54-
factory: () => InputCheckbox,
55-
}),
56-
InputTextarea: createComponentSpec({
57-
id: 'InputTextarea',
58-
type: 'field',
59-
factory: () => InputTextarea,
60-
}),
61-
InputNumber: createComponentSpec({
62-
id: 'InputNumber',
63-
type: 'field',
64-
factory: () => InputNumber,
65-
}),
66-
InputDate: createComponentSpec({
67-
id: 'InputDate',
68-
type: 'field',
69-
factory: () => InputDate,
70-
}),
71-
InputPhone: createComponentSpec({
72-
id: 'InputPhone',
73-
type: 'field',
74-
factory: () => InputText,
75-
defaultProps: { type: 'tel' },
76-
}),
77-
};
15+
import { components } from '../ComponentRegistry';
7816

7917
interface FormWithRHFProps {
8018
schema: FormSchema;
@@ -83,12 +21,28 @@ interface FormWithRHFProps {
8321
/**
8422
* FormWithRHF Component
8523
*
86-
* Renders a form using react-hook-form for state management.
24+
* Renders a form using react-hook-form for state management with AJV validation.
8725
* Demonstrates how to integrate external form libraries with Schepta.
8826
*/
8927
export const FormWithRHF: React.FC<FormWithRHFProps> = ({ schema }) => {
9028
const [submittedValues, setSubmittedValues] = useState<Record<string, any> | null>(null);
9129

30+
// Create RHF components with validation config
31+
const rhfComponents = useMemo(() => ({
32+
// Register RHF FieldWrapper - this makes all fields use RHF's Controller
33+
FieldWrapper: createComponentSpec({
34+
id: 'FieldWrapper',
35+
type: 'field-wrapper',
36+
factory: () => RHFFieldWrapper,
37+
}),
38+
// Register RHF FormContainer with validation - this provides the FormProvider context
39+
FormContainer: createComponentSpec({
40+
id: 'FormContainer',
41+
type: 'FormContainer',
42+
factory: () => RHFFormContainer,
43+
}),
44+
}), []);
45+
9246
const handleSubmit = (values: Record<string, any>) => {
9347
console.log('Form submitted (RHF):', values);
9448
setSubmittedValues(values);
@@ -110,12 +64,12 @@ export const FormWithRHF: React.FC<FormWithRHFProps> = ({ schema }) => {
11064
borderRadius: '4px',
11165
fontSize: '14px',
11266
}}>
113-
This form uses <strong>react-hook-form</strong> for state management.
67+
This form uses <strong>react-hook-form</strong> with <strong>AJV validation</strong>.
11468
The FieldWrapper and FormContainer are custom RHF implementations.
11569
</div>
11670
<FormFactory
11771
schema={schema}
118-
components={rhfComponents}
72+
components={{...components, ...rhfComponents}}
11973
onSubmit={handleSubmit}
12074
debug={true}
12175
/>

examples/react/src/basic-ui/components/Forms/NativeForm.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useMemo } from "react";
22
import { FormFactory } from '@schepta/factory-react';
3-
import { FormSchema } from "@schepta/core";
3+
import { FormSchema, generateValidationSchema } from "@schepta/core";
44

55
interface FormProps {
66
schema: FormSchema;
7+
initialValues?: Record<string, any>;
78
}
89

9-
export const NativeForm = ({ schema }: FormProps) => {
10+
export const NativeForm = ({ schema, initialValues: externalInitialValues }: FormProps) => {
1011
const [submittedValues, setSubmittedValues] = useState<Record<string, any> | null>(null);
1112

13+
const initialValues = useMemo(() => {
14+
const { initialValues: schemaInitialValues } = generateValidationSchema(schema);
15+
return { ...schemaInitialValues, ...externalInitialValues };
16+
}, [schema, externalInitialValues]);
17+
1218
const handleSubmit = (values: Record<string, any>) => {
1319
console.log('Form submitted:', values);
1420
setSubmittedValues(values);
@@ -25,6 +31,7 @@ export const NativeForm = ({ schema }: FormProps) => {
2531
>
2632
<FormFactory
2733
schema={schema}
34+
initialValues={initialValues}
2835
onSubmit={handleSubmit}
2936
debug={true}
3037
/>

0 commit comments

Comments
 (0)