Skip to content

Commit b418b39

Browse files
authored
Merge pull request #19 from workfloworchestrator/1694-basic-fields
1694 basic fields: Integer field
2 parents 07b9181 + c54dd0b commit b418b39

File tree

10 files changed

+165
-54
lines changed

10 files changed

+165
-54
lines changed

frontend/apps/example/src/fields/TextArea.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
/**
2-
* Pydantic Forms
3-
*
4-
* Text component
5-
*/
61
import React from 'react';
72

83
import type {
94
PydanticFormFieldElement,
105
PydanticFormFieldElementProps,
116
} from 'pydantic-forms';
127

8+
/**
9+
* Pydantic Forms
10+
*
11+
* Text component
12+
*/
1313
export const TextArea: PydanticFormFieldElement = ({
1414
value,
1515
onChange,

frontend/packages/pydantic-forms/src/components/defaultComponentMatchers.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33
*
44
* We will search for the first field that returns a positive match
55
*/
6-
import { PydanticComponentMatcher } from '@/types';
6+
import { PydanticComponentMatcher, PydanticFormFieldType } from '@/types';
77

8+
import { IntegerField } from './fields/IntegerField';
9+
import { zodValidationPresets } from './zodValidations';
10+
11+
// no matchers, it defaults to Text field in the mapToComponent function
812
const defaultComponentMatchers: PydanticComponentMatcher[] = [
9-
// no matchers, it defaults to Text field in the mapToComponent function
13+
{
14+
id: 'integerfield',
15+
Element: IntegerField,
16+
matcher(field) {
17+
return field.type === PydanticFormFieldType.INTEGER;
18+
},
19+
validator: zodValidationPresets.integer,
20+
},
1021
];
1122

1223
export default defaultComponentMatchers;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
3+
import type { PydanticFormFieldElementProps } from '@/types';
4+
5+
export const IntegerField = ({
6+
value,
7+
onChange,
8+
onBlur,
9+
disabled,
10+
}: PydanticFormFieldElementProps) => {
11+
return (
12+
<input
13+
onBlur={onBlur}
14+
onChange={(t) => {
15+
const value = parseInt(t.currentTarget.value);
16+
onChange(value);
17+
}}
18+
disabled={disabled}
19+
value={value}
20+
type="number"
21+
style={{
22+
width: '400px',
23+
height: '30px',
24+
padding: '4px',
25+
marginTop: '4px',
26+
}}
27+
/>
28+
);
29+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './FieldWrap';
22
export * from './FormRow';
33
export * from './TextField';
4+
export * from './IntegerField';

frontend/packages/pydantic-forms/src/components/render/Fields.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,21 @@ export function RenderFields({ fields }: RenderFieldsProps) {
4444
if (!element) {
4545
return <></>;
4646
}
47-
const FormElement = wrapFieldElement(element, field, rhf);
47+
const formElement = wrapFieldElement(element, field, rhf);
4848

4949
if (formLayout === PydanticFormLayout.ONE_COL) {
5050
return (
5151
<Row key={field.id}>
5252
<Col md={field.columns} sm={12} e2e-id={field.id}>
53-
{FormElement}
53+
{formElement}
5454
</Col>
5555
</Row>
5656
);
5757
}
5858

5959
return (
6060
<Col key={field.id} md={field.columns} sm={12} e2e-id={field.id}>
61-
{FormElement}
61+
{formElement}
6262
</Col>
6363
);
6464
});

frontend/packages/pydantic-forms/src/components/zodValidations.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,59 @@ export const zodValidationPresets: PydanticFormZodValidationPresets = {
5555
) as unknown as z.ZodString;
5656
}
5757

58+
return validationRule;
59+
},
60+
integer: (field) => {
61+
const {
62+
minimum,
63+
maximum,
64+
exclusiveMaximum,
65+
exclusiveMinimum,
66+
multipleOf,
67+
} = field?.validation ?? {};
68+
69+
let validationRule = z
70+
.number({
71+
required_error: 'Dit veld is verplicht',
72+
invalid_type_error: 'Dit veld moet een integer zijn',
73+
})
74+
.int();
75+
76+
if (minimum) {
77+
validationRule = validationRule.gte(
78+
minimum,
79+
`Dit veld heeft een minimum waarde van ${minimum}`,
80+
);
81+
}
82+
83+
if (exclusiveMinimum) {
84+
validationRule = validationRule.gt(
85+
exclusiveMinimum,
86+
`Dit veld heeft een minimum waarde van meer dan ${exclusiveMinimum}`,
87+
);
88+
}
89+
90+
if (maximum) {
91+
validationRule = validationRule.lte(
92+
maximum,
93+
`Dit veld heeft een maximum waarde van${maximum}`,
94+
);
95+
}
96+
97+
if (exclusiveMaximum) {
98+
validationRule = validationRule.lt(
99+
exclusiveMaximum,
100+
`Dit veld heeft een maximum waarde van minder dan ${exclusiveMaximum}`,
101+
);
102+
}
103+
104+
if (multipleOf) {
105+
validationRule = validationRule.multipleOf(
106+
multipleOf,
107+
`De waarde van dit veld moet een veelvoud zijn van ${multipleOf}`,
108+
);
109+
}
110+
58111
return validationRule;
59112
},
60113
};

frontend/packages/pydantic-forms/src/core/helper.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,26 +168,41 @@ export const getFieldLabelById = (
168168
/**
169169
* Field to validation object
170170
*
171-
* @param field A field from the 'properties' key of the JSON Schema
171+
* @param fieldProperties A field from the 'properties' key of the JSON Schema
172172
* @returns returns a validation object
173173
*/
174174
export const getFieldValidation = (
175-
field: PydanticFormApiResponsePropertyResolved,
175+
fieldProperties: PydanticFormApiResponsePropertyResolved,
176176
) => {
177177
const validation: PydanticFormFieldValidation = {};
178-
179-
const optionDef = getFieldAllOfAnyOfEntry(field);
180-
181-
const isNullable = optionDef?.filter((option) => option.type === 'null');
178+
const propertyDef = getFieldAllOfAnyOfEntry(fieldProperties);
179+
const isNullable = propertyDef?.filter((option) => option.type === 'null');
182180

183181
if (isNullable) {
184182
validation.isNullable = true;
185183
}
186184

187-
for (const option of [field, ...(optionDef ?? [])]) {
188-
if (option.maxLength) validation.maxLength = option.maxLength;
189-
if (option.minLength) validation.minLength = option.minLength;
190-
if (option.pattern) validation.pattern = option.pattern;
185+
for (const properties of [fieldProperties, ...(propertyDef ?? [])]) {
186+
if (fieldProperties.type === PydanticFormFieldType.STRING) {
187+
if (properties.maxLength)
188+
validation.maxLength = properties.maxLength;
189+
if (properties.minLength)
190+
validation.minLength = properties.minLength;
191+
if (properties.pattern) validation.pattern = properties.pattern;
192+
}
193+
if (
194+
fieldProperties.type === PydanticFormFieldType.NUMBER ||
195+
fieldProperties.type === PydanticFormFieldType.INTEGER
196+
) {
197+
if (properties.minimum) validation.minimum = properties.minimum;
198+
if (properties.maximum) validation.maximum = properties.maximum;
199+
if (properties.exclusiveMinimum)
200+
validation.exclusiveMinimum = properties.exclusiveMinimum;
201+
if (properties.exclusiveMaximum)
202+
validation.exclusiveMaximum = properties.exclusiveMaximum;
203+
if (properties.multipleOf)
204+
validation.multipleOf = properties.multipleOf;
205+
}
191206
}
192207

193208
return validation;

frontend/packages/pydantic-forms/src/core/hooks/useRefParser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { PydanticFormApiRefResolved } from '@/types';
1515
export function useRefParser(
1616
id: string,
1717
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18-
source: any = {},
18+
source: object = {},
1919
swrConfig?: SWRConfiguration,
2020
) {
2121
return useSWR<PydanticFormApiRefResolved | undefined>(

frontend/packages/pydantic-forms/src/core/mapFieldToComponent.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,27 @@ export const mapFieldToComponent = (
4343
): PydanticFormField => {
4444
const matcher = getComponentMatcher(componentMatcher);
4545

46-
const schemaField = schema.properties[fieldId];
47-
const options = getFieldOptions(schemaField);
46+
const fieldProperties = schema.properties[fieldId];
47+
const options = getFieldOptions(fieldProperties);
4848

49-
const fieldOptionsEntry = getFieldAllOfAnyOfEntry(schemaField);
49+
const fieldOptionsEntry = getFieldAllOfAnyOfEntry(fieldProperties);
5050

5151
const field: PydanticFormField = {
5252
id: fieldId,
53-
title: formLabels[fieldId]?.toString() ?? schemaField.title,
53+
title: formLabels[fieldId]?.toString() ?? fieldProperties.title,
5454
description: formLabels[fieldId + '_info']?.toString() ?? '',
55-
format: schemaField.format ?? fieldOptionsEntry?.[0]?.format,
55+
format: fieldProperties.format ?? fieldOptionsEntry?.[0]?.format,
5656
type:
57-
schemaField.type ??
57+
fieldProperties.type ??
5858
fieldOptionsEntry?.[0]?.type ??
5959
fieldOptionsEntry?.[0]?.items?.type,
6060
options: options.options,
6161
isEnumField: options.isOptionsField,
62-
default: schemaField.default,
63-
validation: getFieldValidation(schemaField),
62+
default: fieldProperties.default,
63+
validation: getFieldValidation(fieldProperties),
6464
required: !!schema.required?.includes(fieldId),
65-
attributes: getFieldAttributes(schemaField),
66-
schemaField: schemaField,
65+
attributes: getFieldAttributes(fieldProperties),
66+
schemaField: fieldProperties,
6767
columns: 6,
6868
...fieldDetailProvider?.[fieldId],
6969
};

frontend/packages/pydantic-forms/src/types.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -115,22 +115,27 @@ export interface PydanticFormFieldSection {
115115
}
116116

117117
export enum PydanticFormFieldType {
118+
// Primitive types https://json-schema.org/understanding-json-schema/reference/type
119+
STRING = 'string',
120+
INTEGER = 'integer',
121+
NUMBER = 'number',
122+
ENUM = 'enum',
123+
CONST = 'const',
124+
BOOLEAN = 'boolean',
125+
ARRAY = 'array',
126+
OBJECT = 'object',
127+
NULL = 'null',
128+
129+
// Complex types
118130
OPTGROUP = 'optGroup',
119131
SKIP = 'skip',
120132
LONG = 'long',
121133
HIDDEN = 'hidden',
122134
LABEL = 'label',
123135
SUMMARY = 'summary',
124136
ACCEPT = 'accept',
125-
NULL = 'null',
126-
127137
DATE = 'date',
128138
DATETIME = 'date-time',
129-
OBJECT = 'object',
130-
STRING = 'string',
131-
ARRAY = 'array',
132-
BOOLEAN = 'boolean',
133-
NUMBER = 'number',
134139
TIMESTAMP = 'timestamp',
135140
LIST = 'list',
136141
DEFINED_LIST = 'defined-list',
@@ -167,7 +172,8 @@ export interface PydanticFormFieldOption {
167172
label: string;
168173
}
169174

170-
export interface PydanticFormApiResponsePropertyResolved {
175+
export interface PydanticFormApiResponsePropertyResolved
176+
extends PydanticFormFieldValidation {
171177
type: PydanticFormFieldType;
172178

173179
anyOf?: PydanticFormFieldAnyOfResolved[];
@@ -189,21 +195,22 @@ export interface PydanticFormApiResponsePropertyResolved {
189195
sensitive: boolean;
190196
password: boolean;
191197
};
192-
193-
// validation props
194-
maxLength?: number;
195-
minLength?: number;
196-
pattern?: string;
197198
}
198199

199200
export interface PydanticFormFieldValidation {
200201
maxLength?: number;
201202
minLength?: number;
202203
pattern?: string;
203204
isNullable?: boolean;
205+
minimum?: number;
206+
maximum?: number;
207+
exclusiveMinimum?: number;
208+
exclusiveMaximum?: number;
209+
multipleOf?: number;
204210
}
205211

206-
export interface PydanticFormFieldAttributes {
212+
export interface PydanticFormFieldAttributes
213+
extends PydanticFormFieldValidation {
207214
disabled?: boolean;
208215
sensitive?: boolean;
209216
password?: boolean;
@@ -212,29 +219,24 @@ export interface PydanticFormFieldAttributes {
212219
};
213220
}
214221

215-
export interface PydanticFormFieldAnyOfResolved {
222+
export interface PydanticFormFieldAnyOfResolved
223+
extends PydanticFormFieldValidation {
216224
items?: PydanticFormFieldAnyOfResolvedItems;
217225
enum?: string[];
218226
options?: {
219227
[id: string]: string;
220228
};
221229
format?: 'date' | 'date-time';
222230
type: PydanticFormListFieldType;
223-
maxLength?: number;
224-
minLength?: number;
225-
pattern?: string;
226231
}
227-
228-
export interface PydanticFormFieldAnyOfResolvedItems {
232+
export interface PydanticFormFieldAnyOfResolvedItems
233+
extends PydanticFormFieldValidation {
229234
enum: string[];
230235
options?: {
231236
[id: string]: string;
232237
};
233238
title: string;
234239
type: PydanticFormListFieldType;
235-
maxLength?: number;
236-
minLength?: number;
237-
pattern?: string;
238240
}
239241

240242
export type PydanticFormListFieldType =

0 commit comments

Comments
 (0)