react-native-mantine 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -6
- package/lib/commonjs/components/Anchor/index.js +6 -1
- package/lib/commonjs/components/Anchor/index.js.map +1 -1
- package/lib/commonjs/components/Badge/index.js +12 -13
- package/lib/commonjs/components/Badge/index.js.map +1 -1
- package/lib/commonjs/components/Blockquote/index.js +8 -12
- package/lib/commonjs/components/Blockquote/index.js.map +1 -1
- package/lib/commonjs/components/Button/index.js +5 -6
- package/lib/commonjs/components/Button/index.js.map +1 -1
- package/lib/commonjs/components/Chip/index.js +8 -9
- package/lib/commonjs/components/Chip/index.js.map +1 -1
- package/lib/commonjs/components/Code/index.js +13 -8
- package/lib/commonjs/components/Code/index.js.map +1 -1
- package/lib/commonjs/components/Group/index.js.map +1 -1
- package/lib/commonjs/components/Kbd/index.js +7 -8
- package/lib/commonjs/components/Kbd/index.js.map +1 -1
- package/lib/commonjs/components/List/index.js +6 -5
- package/lib/commonjs/components/List/index.js.map +1 -1
- package/lib/commonjs/components/Mark/index.js +4 -0
- package/lib/commonjs/components/Mark/index.js.map +1 -1
- package/lib/commonjs/components/Menu/index.js +12 -15
- package/lib/commonjs/components/Menu/index.js.map +1 -1
- package/lib/commonjs/components/NavLink/index.js +7 -11
- package/lib/commonjs/components/NavLink/index.js.map +1 -1
- package/lib/commonjs/components/Notification/index.js +5 -15
- package/lib/commonjs/components/Notification/index.js.map +1 -1
- package/lib/commonjs/components/Radio/index.js +62 -6
- package/lib/commonjs/components/Radio/index.js.map +1 -1
- package/lib/commonjs/components/Table/Table.example.js +131 -0
- package/lib/commonjs/components/Table/Table.example.js.map +1 -0
- package/lib/commonjs/components/Table/index.js +69 -15
- package/lib/commonjs/components/Table/index.js.map +1 -1
- package/lib/commonjs/components/Title/index.js +4 -0
- package/lib/commonjs/components/Title/index.js.map +1 -1
- package/lib/commonjs/hooks/index.js +28 -0
- package/lib/commonjs/hooks/index.js.map +1 -0
- package/lib/commonjs/hooks/use-form/index.js +40 -0
- package/lib/commonjs/hooks/use-form/index.js.map +1 -0
- package/lib/commonjs/hooks/use-form/types.js +2 -0
- package/lib/commonjs/hooks/use-form/types.js.map +1 -0
- package/lib/commonjs/hooks/use-form/useForm.js +418 -0
- package/lib/commonjs/hooks/use-form/useForm.js.map +1 -0
- package/lib/commonjs/hooks/use-form/validators.js +135 -0
- package/lib/commonjs/hooks/use-form/validators.js.map +1 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/theme/utils/index.js +26 -0
- package/lib/commonjs/theme/utils/index.js.map +1 -0
- package/lib/commonjs/theme/utils/withTextWrapper.js +49 -0
- package/lib/commonjs/theme/utils/withTextWrapper.js.map +1 -0
- package/lib/module/components/Anchor/index.js +6 -1
- package/lib/module/components/Anchor/index.js.map +1 -1
- package/lib/module/components/Badge/index.js +5 -6
- package/lib/module/components/Badge/index.js.map +1 -1
- package/lib/module/components/Blockquote/index.js +6 -10
- package/lib/module/components/Blockquote/index.js.map +1 -1
- package/lib/module/components/Button/index.js +5 -6
- package/lib/module/components/Button/index.js.map +1 -1
- package/lib/module/components/Chip/index.js +5 -6
- package/lib/module/components/Chip/index.js.map +1 -1
- package/lib/module/components/Code/index.js +13 -8
- package/lib/module/components/Code/index.js.map +1 -1
- package/lib/module/components/Group/index.js.map +1 -1
- package/lib/module/components/Kbd/index.js +5 -6
- package/lib/module/components/Kbd/index.js.map +1 -1
- package/lib/module/components/List/index.js +6 -5
- package/lib/module/components/List/index.js.map +1 -1
- package/lib/module/components/Mark/index.js +4 -0
- package/lib/module/components/Mark/index.js.map +1 -1
- package/lib/module/components/Menu/index.js +7 -10
- package/lib/module/components/Menu/index.js.map +1 -1
- package/lib/module/components/NavLink/index.js +5 -9
- package/lib/module/components/NavLink/index.js.map +1 -1
- package/lib/module/components/Notification/index.js +5 -15
- package/lib/module/components/Notification/index.js.map +1 -1
- package/lib/module/components/Radio/index.js +64 -8
- package/lib/module/components/Radio/index.js.map +1 -1
- package/lib/module/components/Table/Table.example.js +128 -0
- package/lib/module/components/Table/Table.example.js.map +1 -0
- package/lib/module/components/Table/index.js +70 -16
- package/lib/module/components/Table/index.js.map +1 -1
- package/lib/module/components/Title/index.js +4 -0
- package/lib/module/components/Title/index.js.map +1 -1
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/use-form/index.js +6 -0
- package/lib/module/hooks/use-form/index.js.map +1 -0
- package/lib/module/hooks/use-form/types.js +2 -0
- package/lib/module/hooks/use-form/types.js.map +1 -0
- package/lib/module/hooks/use-form/useForm.js +414 -0
- package/lib/module/hooks/use-form/useForm.js.map +1 -0
- package/lib/module/hooks/use-form/validators.js +122 -0
- package/lib/module/hooks/use-form/validators.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/theme/utils/index.js +5 -0
- package/lib/module/theme/utils/index.js.map +1 -0
- package/lib/module/theme/utils/withTextWrapper.js +45 -0
- package/lib/module/theme/utils/withTextWrapper.js.map +1 -0
- package/lib/typescript/commonjs/src/components/Anchor/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Anchor/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Badge/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Badge/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Blockquote/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Blockquote/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Button/index.d.ts +2 -2
- package/lib/typescript/commonjs/src/components/Button/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Chip/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Chip/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Code/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Code/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Group/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/components/Group/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Kbd/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Kbd/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/List/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/List/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Mark/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Mark/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Menu/index.d.ts +3 -2
- package/lib/typescript/commonjs/src/components/Menu/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/NavLink/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/NavLink/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Notification/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Notification/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Radio/index.d.ts +22 -1
- package/lib/typescript/commonjs/src/components/Radio/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Table/Table.example.d.ts +3 -0
- package/lib/typescript/commonjs/src/components/Table/Table.example.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/Table/index.d.ts +7 -2
- package/lib/typescript/commonjs/src/components/Table/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Title/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/Title/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/hooks/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/index.d.ts +4 -0
- package/lib/typescript/commonjs/src/hooks/use-form/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/types.d.ts +119 -0
- package/lib/typescript/commonjs/src/hooks/use-form/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/useForm.d.ts +30 -0
- package/lib/typescript/commonjs/src/hooks/use-form/useForm.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/validators.d.ts +41 -0
- package/lib/typescript/commonjs/src/hooks/use-form/validators.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/theme/utils/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/theme/utils/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/theme/utils/withTextWrapper.d.ts +41 -0
- package/lib/typescript/commonjs/src/theme/utils/withTextWrapper.d.ts.map +1 -0
- package/lib/typescript/module/src/components/Anchor/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Anchor/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Badge/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Badge/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Blockquote/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Blockquote/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Button/index.d.ts +2 -2
- package/lib/typescript/module/src/components/Button/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Chip/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Chip/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Code/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Code/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Group/index.d.ts +1 -0
- package/lib/typescript/module/src/components/Group/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Kbd/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Kbd/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/List/index.d.ts +2 -1
- package/lib/typescript/module/src/components/List/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Mark/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Mark/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Menu/index.d.ts +3 -2
- package/lib/typescript/module/src/components/Menu/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/NavLink/index.d.ts +2 -1
- package/lib/typescript/module/src/components/NavLink/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Notification/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Notification/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Radio/index.d.ts +22 -1
- package/lib/typescript/module/src/components/Radio/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Table/Table.example.d.ts +3 -0
- package/lib/typescript/module/src/components/Table/Table.example.d.ts.map +1 -0
- package/lib/typescript/module/src/components/Table/index.d.ts +7 -2
- package/lib/typescript/module/src/components/Table/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Title/index.d.ts +2 -1
- package/lib/typescript/module/src/components/Title/index.d.ts.map +1 -1
- package/lib/typescript/module/src/hooks/index.d.ts +3 -0
- package/lib/typescript/module/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/index.d.ts +4 -0
- package/lib/typescript/module/src/hooks/use-form/index.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/types.d.ts +119 -0
- package/lib/typescript/module/src/hooks/use-form/types.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/useForm.d.ts +30 -0
- package/lib/typescript/module/src/hooks/use-form/useForm.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/validators.d.ts +41 -0
- package/lib/typescript/module/src/hooks/use-form/validators.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +1 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/theme/utils/index.d.ts +3 -0
- package/lib/typescript/module/src/theme/utils/index.d.ts.map +1 -0
- package/lib/typescript/module/src/theme/utils/withTextWrapper.d.ts +41 -0
- package/lib/typescript/module/src/theme/utils/withTextWrapper.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Anchor/index.tsx +8 -2
- package/src/components/Badge/index.tsx +5 -3
- package/src/components/Blockquote/index.tsx +6 -5
- package/src/components/Button/index.tsx +5 -6
- package/src/components/Chip/index.tsx +5 -3
- package/src/components/Code/index.tsx +19 -14
- package/src/components/Group/index.tsx +1 -0
- package/src/components/Kbd/index.tsx +5 -4
- package/src/components/List/index.tsx +7 -8
- package/src/components/Mark/index.tsx +7 -2
- package/src/components/Menu/index.tsx +8 -7
- package/src/components/NavLink/index.tsx +6 -4
- package/src/components/Notification/index.tsx +6 -15
- package/src/components/Radio/index.tsx +99 -8
- package/src/components/Table/Table.example.tsx +85 -0
- package/src/components/Table/index.tsx +93 -11
- package/src/components/Title/index.tsx +7 -2
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-form/index.ts +3 -0
- package/src/hooks/use-form/types.ts +169 -0
- package/src/hooks/use-form/useForm.ts +436 -0
- package/src/hooks/use-form/validators.ts +143 -0
- package/src/index.tsx +1 -0
- package/src/theme/utils/index.ts +2 -0
- package/src/theme/utils/withTextWrapper.tsx +58 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form field validation function
|
|
3
|
+
*/
|
|
4
|
+
export type FormValidator<T> = (value: T) => string | null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Form validation rules
|
|
8
|
+
*/
|
|
9
|
+
export type FormValidationRules<Values> = {
|
|
10
|
+
[K in keyof Values]?: FormValidator<Values[K]> | FormValidator<Values[K]>[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Form errors
|
|
15
|
+
*/
|
|
16
|
+
export type FormErrors<Values = Record<string, any>> = Partial<
|
|
17
|
+
Record<keyof Values, string>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Form touched state
|
|
22
|
+
*/
|
|
23
|
+
export type FormTouched<Values = Record<string, any>> = Partial<
|
|
24
|
+
Record<keyof Values, boolean>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Form dirty state
|
|
29
|
+
*/
|
|
30
|
+
export type FormDirty<Values = Record<string, any>> = Partial<
|
|
31
|
+
Record<keyof Values, boolean>
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Form field status
|
|
36
|
+
*/
|
|
37
|
+
export interface FormFieldStatus {
|
|
38
|
+
hasError: boolean;
|
|
39
|
+
isTouched: boolean;
|
|
40
|
+
isDirty: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Props to be spread on input components
|
|
45
|
+
*/
|
|
46
|
+
export interface FormInputProps<T = any> {
|
|
47
|
+
value: T;
|
|
48
|
+
onChange?: (value: T) => void;
|
|
49
|
+
onChangeText?: (text: string) => void;
|
|
50
|
+
error?: string;
|
|
51
|
+
onBlur?: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Form submit handler
|
|
56
|
+
*/
|
|
57
|
+
export type FormSubmitHandler<Values> = (
|
|
58
|
+
values: Values,
|
|
59
|
+
event?: any
|
|
60
|
+
) => void | Promise<void>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* useForm hook input parameters
|
|
64
|
+
*/
|
|
65
|
+
export interface UseFormInput<Values = Record<string, any>> {
|
|
66
|
+
/** Initial form values */
|
|
67
|
+
initialValues?: Values;
|
|
68
|
+
|
|
69
|
+
/** Initial form errors */
|
|
70
|
+
initialErrors?: FormErrors<Values>;
|
|
71
|
+
|
|
72
|
+
/** Initial touched state */
|
|
73
|
+
initialTouched?: FormTouched<Values>;
|
|
74
|
+
|
|
75
|
+
/** Initial dirty state */
|
|
76
|
+
initialDirty?: FormDirty<Values>;
|
|
77
|
+
|
|
78
|
+
/** Form validation rules */
|
|
79
|
+
validate?: FormValidationRules<Values>;
|
|
80
|
+
|
|
81
|
+
/** Clear input error on change */
|
|
82
|
+
clearInputErrorOnChange?: boolean;
|
|
83
|
+
|
|
84
|
+
/** Validate input on change */
|
|
85
|
+
validateInputOnChange?: boolean;
|
|
86
|
+
|
|
87
|
+
/** Validate input on blur */
|
|
88
|
+
validateInputOnBlur?: boolean;
|
|
89
|
+
|
|
90
|
+
/** Transform values before submit */
|
|
91
|
+
transformValues?: (values: Values) => any;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* useForm hook return type
|
|
96
|
+
*/
|
|
97
|
+
export interface UseFormReturnType<Values = Record<string, any>> {
|
|
98
|
+
/** Current form values */
|
|
99
|
+
values: Values;
|
|
100
|
+
|
|
101
|
+
/** Current form errors */
|
|
102
|
+
errors: FormErrors<Values>;
|
|
103
|
+
|
|
104
|
+
/** Current touched state */
|
|
105
|
+
touched: FormTouched<Values>;
|
|
106
|
+
|
|
107
|
+
/** Current dirty state */
|
|
108
|
+
dirty: FormDirty<Values>;
|
|
109
|
+
|
|
110
|
+
/** Set a single field value */
|
|
111
|
+
setFieldValue: <K extends keyof Values>(field: K, value: Values[K]) => void;
|
|
112
|
+
|
|
113
|
+
/** Set multiple values */
|
|
114
|
+
setValues: (values: Partial<Values>) => void;
|
|
115
|
+
|
|
116
|
+
/** Set a single field error */
|
|
117
|
+
setFieldError: <K extends keyof Values>(field: K, error: string | null) => void;
|
|
118
|
+
|
|
119
|
+
/** Set multiple errors */
|
|
120
|
+
setErrors: (errors: FormErrors<Values>) => void;
|
|
121
|
+
|
|
122
|
+
/** Clear a specific field error */
|
|
123
|
+
clearFieldError: <K extends keyof Values>(field: K) => void;
|
|
124
|
+
|
|
125
|
+
/** Clear all errors */
|
|
126
|
+
clearErrors: () => void;
|
|
127
|
+
|
|
128
|
+
/** Reset form to initial values */
|
|
129
|
+
reset: () => void;
|
|
130
|
+
|
|
131
|
+
/** Validate a specific field */
|
|
132
|
+
validateField: <K extends keyof Values>(field: K) => boolean;
|
|
133
|
+
|
|
134
|
+
/** Validate entire form */
|
|
135
|
+
validate: () => boolean;
|
|
136
|
+
|
|
137
|
+
/** Check if form is valid */
|
|
138
|
+
isValid: () => boolean;
|
|
139
|
+
|
|
140
|
+
/** Check if form is dirty */
|
|
141
|
+
isDirty: () => boolean;
|
|
142
|
+
|
|
143
|
+
/** Get field status */
|
|
144
|
+
getFieldStatus: <K extends keyof Values>(field: K) => FormFieldStatus;
|
|
145
|
+
|
|
146
|
+
/** Get props to spread on input component */
|
|
147
|
+
getInputProps: <K extends keyof Values>(
|
|
148
|
+
field: K,
|
|
149
|
+
options?: {
|
|
150
|
+
type?: 'input' | 'checkbox' | 'radio' | 'select';
|
|
151
|
+
withError?: boolean;
|
|
152
|
+
withFocus?: boolean;
|
|
153
|
+
}
|
|
154
|
+
) => FormInputProps<Values[K]>;
|
|
155
|
+
|
|
156
|
+
/** Handle form submit */
|
|
157
|
+
onSubmit: (
|
|
158
|
+
handleSubmit: FormSubmitHandler<Values>
|
|
159
|
+
) => (event?: any) => void | Promise<void>;
|
|
160
|
+
|
|
161
|
+
/** Reset touched state */
|
|
162
|
+
resetTouched: () => void;
|
|
163
|
+
|
|
164
|
+
/** Reset dirty state */
|
|
165
|
+
resetDirty: () => void;
|
|
166
|
+
|
|
167
|
+
/** Mark field as touched */
|
|
168
|
+
setFieldTouched: <K extends keyof Values>(field: K, touched?: boolean) => void;
|
|
169
|
+
}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
FormErrors,
|
|
4
|
+
FormFieldStatus,
|
|
5
|
+
FormInputProps,
|
|
6
|
+
FormSubmitHandler,
|
|
7
|
+
FormTouched,
|
|
8
|
+
FormDirty,
|
|
9
|
+
FormValidationRules,
|
|
10
|
+
UseFormInput,
|
|
11
|
+
UseFormReturnType,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validates a single field value against validation rules
|
|
16
|
+
*/
|
|
17
|
+
function validateFieldValue<Values>(
|
|
18
|
+
field: keyof Values,
|
|
19
|
+
value: any,
|
|
20
|
+
rules?: FormValidationRules<Values>
|
|
21
|
+
): string | null {
|
|
22
|
+
if (!rules || !rules[field]) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const validators = rules[field];
|
|
27
|
+
if (!validators) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle single validator
|
|
32
|
+
if (typeof validators === 'function') {
|
|
33
|
+
return validators(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handle array of validators
|
|
37
|
+
if (Array.isArray(validators)) {
|
|
38
|
+
for (const validator of validators) {
|
|
39
|
+
const error = validator(value);
|
|
40
|
+
if (error) {
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validates all form values
|
|
51
|
+
*/
|
|
52
|
+
function validateAllValues<Values>(
|
|
53
|
+
values: Values,
|
|
54
|
+
rules?: FormValidationRules<Values>
|
|
55
|
+
): FormErrors<Values> {
|
|
56
|
+
if (!rules) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const errors: FormErrors<Values> = {};
|
|
61
|
+
|
|
62
|
+
for (const field in rules) {
|
|
63
|
+
const error = validateFieldValue(field, values[field], rules);
|
|
64
|
+
if (error) {
|
|
65
|
+
errors[field] = error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return errors;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Form state management hook for React Native
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* const form = useForm({
|
|
78
|
+
* initialValues: {
|
|
79
|
+
* email: '',
|
|
80
|
+
* password: '',
|
|
81
|
+
* },
|
|
82
|
+
* validate: {
|
|
83
|
+
* email: isEmail('Invalid email'),
|
|
84
|
+
* password: minLength(6, 'Password must be at least 6 characters'),
|
|
85
|
+
* },
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* return (
|
|
89
|
+
* <View>
|
|
90
|
+
* <TextInput {...form.getInputProps('email')} />
|
|
91
|
+
* <TextInput {...form.getInputProps('password')} />
|
|
92
|
+
* <Button onPress={form.onSubmit((values) => console.log(values))}>
|
|
93
|
+
* Submit
|
|
94
|
+
* </Button>
|
|
95
|
+
* </View>
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function useForm<Values extends Record<string, any> = Record<string, any>>({
|
|
100
|
+
initialValues = {} as Values,
|
|
101
|
+
initialErrors = {},
|
|
102
|
+
initialTouched = {},
|
|
103
|
+
initialDirty = {},
|
|
104
|
+
validate: validationRules,
|
|
105
|
+
clearInputErrorOnChange = true,
|
|
106
|
+
validateInputOnChange = false,
|
|
107
|
+
validateInputOnBlur = false,
|
|
108
|
+
transformValues = (values: Values) => values,
|
|
109
|
+
}: UseFormInput<Values> = {}): UseFormReturnType<Values> {
|
|
110
|
+
// Form state
|
|
111
|
+
const [values, setValuesState] = useState<Values>(initialValues);
|
|
112
|
+
const [errors, setErrorsState] = useState<FormErrors<Values>>(initialErrors);
|
|
113
|
+
const [touched, setTouchedState] = useState<FormTouched<Values>>(initialTouched);
|
|
114
|
+
const [dirty, setDirtyState] = useState<FormDirty<Values>>(initialDirty);
|
|
115
|
+
|
|
116
|
+
// Keep track of initial values for reset
|
|
117
|
+
const initialValuesRef = useRef<Values>(initialValues);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Set a single field value
|
|
121
|
+
*/
|
|
122
|
+
const setFieldValue = useCallback(
|
|
123
|
+
<K extends keyof Values>(field: K, value: Values[K]) => {
|
|
124
|
+
setValuesState((prev) => ({ ...prev, [field]: value }));
|
|
125
|
+
setDirtyState((prev) => ({ ...prev, [field]: true }));
|
|
126
|
+
|
|
127
|
+
// Validate on change if enabled
|
|
128
|
+
if (validateInputOnChange && validationRules) {
|
|
129
|
+
const error = validateFieldValue(field, value, validationRules);
|
|
130
|
+
if (error) {
|
|
131
|
+
setErrorsState((prev) => ({ ...prev, [field]: error }));
|
|
132
|
+
} else {
|
|
133
|
+
setErrorsState((prev) => {
|
|
134
|
+
const newErrors = { ...prev };
|
|
135
|
+
delete newErrors[field];
|
|
136
|
+
return newErrors;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
} else if (clearInputErrorOnChange) {
|
|
140
|
+
// Clear error on change if enabled
|
|
141
|
+
setErrorsState((prev) => {
|
|
142
|
+
const newErrors = { ...prev };
|
|
143
|
+
delete newErrors[field];
|
|
144
|
+
return newErrors;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
[validateInputOnChange, clearInputErrorOnChange, validationRules]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Set multiple field values
|
|
153
|
+
*/
|
|
154
|
+
const setValues = useCallback((newValues: Partial<Values>) => {
|
|
155
|
+
setValuesState((prev) => ({ ...prev, ...newValues }));
|
|
156
|
+
|
|
157
|
+
// Mark all updated fields as dirty
|
|
158
|
+
const dirtyFields: FormDirty<Values> = {};
|
|
159
|
+
for (const key in newValues) {
|
|
160
|
+
dirtyFields[key] = true;
|
|
161
|
+
}
|
|
162
|
+
setDirtyState((prev) => ({ ...prev, ...dirtyFields }));
|
|
163
|
+
|
|
164
|
+
if (clearInputErrorOnChange) {
|
|
165
|
+
setErrorsState((prev) => {
|
|
166
|
+
const newErrors = { ...prev };
|
|
167
|
+
for (const key in newValues) {
|
|
168
|
+
delete newErrors[key];
|
|
169
|
+
}
|
|
170
|
+
return newErrors;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}, [clearInputErrorOnChange]);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Set a single field error
|
|
177
|
+
*/
|
|
178
|
+
const setFieldError = useCallback(
|
|
179
|
+
<K extends keyof Values>(field: K, error: string | null) => {
|
|
180
|
+
if (error === null) {
|
|
181
|
+
setErrorsState((prev) => {
|
|
182
|
+
const newErrors = { ...prev };
|
|
183
|
+
delete newErrors[field];
|
|
184
|
+
return newErrors;
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
setErrorsState((prev) => ({ ...prev, [field]: error }));
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Set multiple errors
|
|
195
|
+
*/
|
|
196
|
+
const setErrors = useCallback((newErrors: FormErrors<Values>) => {
|
|
197
|
+
setErrorsState(newErrors);
|
|
198
|
+
}, []);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Clear a specific field error
|
|
202
|
+
*/
|
|
203
|
+
const clearFieldError = useCallback(<K extends keyof Values>(field: K) => {
|
|
204
|
+
setErrorsState((prev) => {
|
|
205
|
+
const newErrors = { ...prev };
|
|
206
|
+
delete newErrors[field];
|
|
207
|
+
return newErrors;
|
|
208
|
+
});
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Clear all errors
|
|
213
|
+
*/
|
|
214
|
+
const clearErrors = useCallback(() => {
|
|
215
|
+
setErrorsState({});
|
|
216
|
+
}, []);
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Reset form to initial values
|
|
220
|
+
*/
|
|
221
|
+
const reset = useCallback(() => {
|
|
222
|
+
setValuesState(initialValuesRef.current);
|
|
223
|
+
setErrorsState({});
|
|
224
|
+
setTouchedState({});
|
|
225
|
+
setDirtyState({});
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Validate a specific field
|
|
230
|
+
*/
|
|
231
|
+
const validateField = useCallback(
|
|
232
|
+
<K extends keyof Values>(field: K): boolean => {
|
|
233
|
+
if (!validationRules) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const error = validateFieldValue(field, values[field], validationRules);
|
|
238
|
+
if (error) {
|
|
239
|
+
setFieldError(field, error);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
clearFieldError(field);
|
|
244
|
+
return true;
|
|
245
|
+
},
|
|
246
|
+
[values, validationRules, setFieldError, clearFieldError]
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate entire form
|
|
251
|
+
*/
|
|
252
|
+
const validate = useCallback((): boolean => {
|
|
253
|
+
if (!validationRules) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const newErrors = validateAllValues(values, validationRules);
|
|
258
|
+
setErrorsState(newErrors);
|
|
259
|
+
return Object.keys(newErrors).length === 0;
|
|
260
|
+
}, [values, validationRules]);
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if form is valid
|
|
264
|
+
*/
|
|
265
|
+
const isValid = useCallback((): boolean => {
|
|
266
|
+
if (!validationRules) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const validationErrors = validateAllValues(values, validationRules);
|
|
271
|
+
return Object.keys(validationErrors).length === 0;
|
|
272
|
+
}, [values, validationRules]);
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if form is dirty
|
|
276
|
+
*/
|
|
277
|
+
const isDirty = useCallback((): boolean => {
|
|
278
|
+
return Object.values(dirty).some((isDirty) => isDirty === true);
|
|
279
|
+
}, [dirty]);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get field status
|
|
283
|
+
*/
|
|
284
|
+
const getFieldStatus = useCallback(
|
|
285
|
+
<K extends keyof Values>(field: K): FormFieldStatus => {
|
|
286
|
+
return {
|
|
287
|
+
hasError: !!errors[field],
|
|
288
|
+
isTouched: !!touched[field],
|
|
289
|
+
isDirty: !!dirty[field],
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
[errors, touched, dirty]
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Mark field as touched
|
|
297
|
+
*/
|
|
298
|
+
const setFieldTouched = useCallback(
|
|
299
|
+
<K extends keyof Values>(field: K, isTouched = true) => {
|
|
300
|
+
setTouchedState((prev) => ({ ...prev, [field]: isTouched }));
|
|
301
|
+
|
|
302
|
+
// Validate on blur if enabled
|
|
303
|
+
if (isTouched && validateInputOnBlur && validationRules) {
|
|
304
|
+
const error = validateFieldValue(field, values[field], validationRules);
|
|
305
|
+
if (error) {
|
|
306
|
+
setFieldError(field, error);
|
|
307
|
+
} else {
|
|
308
|
+
clearFieldError(field);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
[validateInputOnBlur, validationRules, values, setFieldError, clearFieldError]
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Reset touched state
|
|
317
|
+
*/
|
|
318
|
+
const resetTouched = useCallback(() => {
|
|
319
|
+
setTouchedState({});
|
|
320
|
+
}, []);
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Reset dirty state
|
|
324
|
+
*/
|
|
325
|
+
const resetDirty = useCallback(() => {
|
|
326
|
+
setDirtyState({});
|
|
327
|
+
}, []);
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get props to spread on input component
|
|
331
|
+
*/
|
|
332
|
+
const getInputProps = useCallback(
|
|
333
|
+
<K extends keyof Values>(
|
|
334
|
+
field: K,
|
|
335
|
+
options: {
|
|
336
|
+
type?: 'input' | 'checkbox' | 'radio' | 'select';
|
|
337
|
+
withError?: boolean;
|
|
338
|
+
withFocus?: boolean;
|
|
339
|
+
} = {}
|
|
340
|
+
): FormInputProps<Values[K]> => {
|
|
341
|
+
const { type = 'input', withError = true, withFocus = true } = options;
|
|
342
|
+
|
|
343
|
+
const baseProps: FormInputProps<Values[K]> = {
|
|
344
|
+
value: values[field],
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Add error if needed
|
|
348
|
+
if (withError && errors[field]) {
|
|
349
|
+
baseProps.error = errors[field];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Add blur handler if focus tracking is enabled
|
|
353
|
+
if (withFocus) {
|
|
354
|
+
baseProps.onBlur = () => {
|
|
355
|
+
setFieldTouched(field, true);
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Handle different input types
|
|
360
|
+
if (type === 'checkbox') {
|
|
361
|
+
return {
|
|
362
|
+
...baseProps,
|
|
363
|
+
onChange: (value: Values[K]) => {
|
|
364
|
+
setFieldValue(field, value);
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (type === 'radio' || type === 'select') {
|
|
370
|
+
return {
|
|
371
|
+
...baseProps,
|
|
372
|
+
onChange: (value: Values[K]) => {
|
|
373
|
+
setFieldValue(field, value);
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Default text input
|
|
379
|
+
return {
|
|
380
|
+
...baseProps,
|
|
381
|
+
onChangeText: (text: string) => {
|
|
382
|
+
setFieldValue(field, text as any);
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
[values, errors, setFieldValue, setFieldTouched]
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Handle form submit
|
|
391
|
+
*/
|
|
392
|
+
const onSubmit = useCallback(
|
|
393
|
+
(handleSubmit: FormSubmitHandler<Values>) => {
|
|
394
|
+
return async (event?: any) => {
|
|
395
|
+
event?.preventDefault?.();
|
|
396
|
+
|
|
397
|
+
// Validate form
|
|
398
|
+
const isFormValid = validate();
|
|
399
|
+
if (!isFormValid) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Transform values if needed
|
|
404
|
+
const transformedValues = transformValues(values);
|
|
405
|
+
|
|
406
|
+
// Call submit handler
|
|
407
|
+
await handleSubmit(transformedValues, event);
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
[validate, values, transformValues]
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
values,
|
|
415
|
+
errors,
|
|
416
|
+
touched,
|
|
417
|
+
dirty,
|
|
418
|
+
setFieldValue,
|
|
419
|
+
setValues,
|
|
420
|
+
setFieldError,
|
|
421
|
+
setErrors,
|
|
422
|
+
clearFieldError,
|
|
423
|
+
clearErrors,
|
|
424
|
+
reset,
|
|
425
|
+
validateField,
|
|
426
|
+
validate,
|
|
427
|
+
isValid,
|
|
428
|
+
isDirty,
|
|
429
|
+
getFieldStatus,
|
|
430
|
+
getInputProps,
|
|
431
|
+
onSubmit,
|
|
432
|
+
resetTouched,
|
|
433
|
+
resetDirty,
|
|
434
|
+
setFieldTouched,
|
|
435
|
+
};
|
|
436
|
+
}
|