Skip to content

Commit 55e2901

Browse files
committed
feat(form): add custom validation message component and improve validation message system
1 parent 8befe77 commit 55e2901

File tree

5 files changed

+396
-196
lines changed

5 files changed

+396
-196
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* eslint-disable no-template-curly-in-string */
2+
'use client';
3+
4+
import { Button, Card, Form, FormField, Input, useForm } from 'soybean-react-ui';
5+
6+
// Form field types
7+
type Inputs = {
8+
age: number;
9+
email: string;
10+
username: string;
11+
};
12+
13+
const initialValues: Inputs = {
14+
age: 0,
15+
email: '',
16+
username: ''
17+
};
18+
19+
const ValidateMessagesDemo = () => {
20+
const [form] = useForm<Inputs>();
21+
22+
return (
23+
<Card title="Form with custom validateMessages">
24+
<Form
25+
className="w-[480px] max-sm:w-full space-y-4"
26+
form={form}
27+
initialValues={initialValues}
28+
validateMessages={{
29+
number: {
30+
min: 'This field must be greater than ${min}'
31+
},
32+
required: 'This field must be required'
33+
}}
34+
>
35+
<FormField
36+
label="Username"
37+
name="username"
38+
rules={[{ required: true }]}
39+
>
40+
<Input placeholder="Enter username" />
41+
</FormField>
42+
43+
<FormField
44+
label="Age"
45+
name="age"
46+
rules={[{ min: 18, required: true, type: 'number' }]}
47+
>
48+
<Input
49+
placeholder="Enter age"
50+
type="number"
51+
/>
52+
</FormField>
53+
54+
<FormField
55+
label="Email"
56+
name="email"
57+
rules={[{ required: true, type: 'email' }]}
58+
>
59+
<Input placeholder="Enter email" />
60+
</FormField>
61+
62+
<div className="flex gap-2">
63+
<Button type="submit">Submit</Button>
64+
</div>
65+
</Form>
66+
</Card>
67+
);
68+
};
69+
70+
export default ValidateMessagesDemo;

playground/src/app/(demo)/form/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import AsyncValidatorResolver from './modules/AsyncValidatorResolver';
22
import ClearDestroy from './modules/ClearDestroy';
33
import ComputedDemo from './modules/ComputedDemo';
4+
import CustomValidateMessages from './modules/CustomValidateMessages';
45
import Default from './modules/Default';
56
import FieldChange from './modules/FieldChange';
67
import List from './modules/List';
@@ -48,6 +49,8 @@ const FormPage = () => {
4849

4950
<UndoRedo />
5051

52+
<CustomValidateMessages />
53+
5154
<ClearDestroy />
5255
</div>
5356
);

primitives/filed-form /src/form-core/createStore.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { type ChangeMask, ChangeTag } from './event';
1515
import type { Action, ArrayOpArgs, Middleware, ValidateFieldsOptions } from './middleware';
1616
import { compose } from './middleware';
1717
import type { Callbacks, FieldEntity, Meta, Store, StoreValue } from './types';
18-
import type { ValidateMessages } from './validate';
18+
import { type ValidateMessages, defaultValidateMessages } from './validate';
1919
import { runRulesWithMode } from './validation';
2020
import type { Rule, ValidateOptions } from './validation';
2121

@@ -95,7 +95,7 @@ class FormStore {
9595
/** Form lifecycle callbacks */
9696
private _callbacks: Callbacks = {};
9797
/** Validation message templates */
98-
private _validateMessages: ValidateMessages = {};
98+
private _validateMessages: ValidateMessages = defaultValidateMessages;
9999

100100
// Status tracking sets
101101
/** Set of fields that have been touched by user interaction */
@@ -161,9 +161,6 @@ class FormStore {
161161
/** Set of hidden field keys */
162162
private _hiddenKeys = new Set<string>();
163163

164-
/** Transform functions to run before form submission */
165-
private _preSubmit: Array<(values: Store) => Store> = [];
166-
167164
/** Flag to preserve field values after unmount */
168165
private _preserve = false;
169166

@@ -197,7 +194,7 @@ class FormStore {
197194
* Sets validation message templates
198195
*/
199196
private setValidateMessages = (m: ValidateMessages) => {
200-
this._validateMessages = m || {};
197+
this._validateMessages = assign(defaultValidateMessages, m);
201198
};
202199

203200
/**
@@ -887,7 +884,16 @@ class FormStore {
887884
let warns: string[] = [];
888885

889886
try {
890-
({ errors, warns } = await runRulesWithMode(value, rules, 'parallelAll', this._store));
887+
const { errors: validErrors, warns: validWarns } = await runRulesWithMode(
888+
value,
889+
rules,
890+
'parallelAll',
891+
this._store,
892+
this._validateMessages
893+
);
894+
895+
errors = validErrors;
896+
warns = validWarns;
891897
} catch (e: any) {
892898
// 4) Fallback: convert thrown errors into messages (avoid silent pass/hang)
893899
errors = [String(e?.message ?? e)];
Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,109 @@
1+
/* eslint-disable no-template-curly-in-string */
2+
/**
3+
* Validation message system for form field validation
4+
* Provides customizable error messages for different validation rule types
5+
*/
6+
7+
/** Type alias for validation message strings */
18
type ValidateMessage = string;
29

10+
/**
11+
* Configuration object for validation error messages
12+
* Supports nested message structures for different validation types
13+
*/
314
export interface ValidateMessages {
4-
array?: {
5-
len?: ValidateMessage;
6-
max?: ValidateMessage;
7-
min?: ValidateMessage;
8-
range?: ValidateMessage;
9-
};
15+
/** Error message for boolean type validation */
16+
boolean?: ValidateMessage;
17+
18+
/** Error messages for date type validation */
1019
date?: {
11-
format?: ValidateMessage;
1220
invalid?: ValidateMessage;
13-
parse?: ValidateMessage;
21+
max?: ValidateMessage;
22+
min?: ValidateMessage;
1423
};
24+
25+
/** Default error message when no specific message is found */
1526
default?: ValidateMessage;
27+
28+
/** Error message for email format validation */
29+
email?: ValidateMessage;
30+
31+
/** Error message for enum validation failure */
1632
enum?: ValidateMessage;
33+
34+
/** Error message for float type validation */
35+
float?: ValidateMessage;
36+
37+
/** Error message for hex color validation */
38+
hex?: ValidateMessage;
39+
40+
/** Error messages for integer type validation */
41+
integer?: {
42+
max?: ValidateMessage;
43+
min?: ValidateMessage;
44+
};
45+
46+
/** Error messages for number type validation */
1747
number?: {
1848
len?: ValidateMessage;
1949
max?: ValidateMessage;
2050
min?: ValidateMessage;
21-
range?: ValidateMessage;
22-
};
23-
pattern?: {
24-
mismatch?: ValidateMessage;
2551
};
52+
53+
/** Error message for regular expression validation */
54+
regexp?: ValidateMessage;
55+
56+
/** Error message for required field validation */
2657
required?: ValidateMessage;
58+
59+
/** Error messages for string type validation */
2760
string?: {
2861
len?: ValidateMessage;
29-
max?: ValidateMessage;
30-
min?: ValidateMessage;
31-
range?: ValidateMessage;
32-
};
33-
types?: {
34-
array?: ValidateMessage;
35-
boolean?: ValidateMessage;
36-
date?: ValidateMessage;
37-
email?: ValidateMessage;
38-
float?: ValidateMessage;
39-
hex?: ValidateMessage;
40-
integer?: ValidateMessage;
41-
method?: ValidateMessage;
42-
number?: ValidateMessage;
43-
object?: ValidateMessage;
44-
regexp?: ValidateMessage;
45-
string?: ValidateMessage;
46-
url?: ValidateMessage;
62+
maxLength?: ValidateMessage;
63+
minLength?: ValidateMessage;
64+
pattern?: ValidateMessage;
4765
};
66+
67+
/** Error message for URL format validation */
68+
url?: ValidateMessage;
69+
70+
/** Error message for whitespace-only string validation */
4871
whitespace?: ValidateMessage;
4972
}
73+
74+
/**
75+
* Default validation message templates with placeholder support
76+
* These messages can be customized by providing a ValidateMessages object
77+
* Placeholders like ${min}, ${max}, ${len} are replaced with actual rule values
78+
*/
79+
export const defaultValidateMessages: ValidateMessages = {
80+
boolean: 'Must be a boolean',
81+
date: {
82+
invalid: 'Must be a valid Date',
83+
max: 'Date is later than maximum',
84+
min: 'Date is earlier than minimum'
85+
},
86+
email: 'Must be a valid email',
87+
enum: 'Value is not in enum',
88+
float: 'Must be a float',
89+
hex: 'Must be a valid hex color',
90+
integer: {
91+
max: 'Max is ${max}',
92+
min: 'Min is ${min}'
93+
},
94+
number: {
95+
len: 'Must equal ${len}',
96+
max: 'Max is ${max}',
97+
min: 'Min is ${min}'
98+
},
99+
regexp: 'Must be a valid regular expression',
100+
required: 'This field is required',
101+
string: {
102+
len: 'Length must be ${len}',
103+
maxLength: 'Max length is ${maxLength}',
104+
minLength: 'Min length is ${minLength}',
105+
pattern: 'Pattern not match'
106+
},
107+
url: 'Must be a valid URL',
108+
whitespace: 'Only whitespace is not allowed'
109+
};

0 commit comments

Comments
 (0)