react-native-mantine 0.7.0 → 0.9.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.
Files changed (81) hide show
  1. package/README.md +37 -5
  2. package/lib/commonjs/components/Group/index.js.map +1 -1
  3. package/lib/commonjs/components/Radio/index.js +62 -6
  4. package/lib/commonjs/components/Radio/index.js.map +1 -1
  5. package/lib/commonjs/components/Switch/index.js +6 -2
  6. package/lib/commonjs/components/Switch/index.js.map +1 -1
  7. package/lib/commonjs/hooks/index.js +28 -0
  8. package/lib/commonjs/hooks/index.js.map +1 -0
  9. package/lib/commonjs/hooks/use-form/index.js +40 -0
  10. package/lib/commonjs/hooks/use-form/index.js.map +1 -0
  11. package/lib/commonjs/hooks/use-form/types.js +2 -0
  12. package/lib/commonjs/hooks/use-form/types.js.map +1 -0
  13. package/lib/commonjs/hooks/use-form/useForm.js +418 -0
  14. package/lib/commonjs/hooks/use-form/useForm.js.map +1 -0
  15. package/lib/commonjs/hooks/use-form/validators.js +135 -0
  16. package/lib/commonjs/hooks/use-form/validators.js.map +1 -0
  17. package/lib/commonjs/index.js +11 -0
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/module/components/Group/index.js.map +1 -1
  20. package/lib/module/components/Radio/index.js +64 -8
  21. package/lib/module/components/Radio/index.js.map +1 -1
  22. package/lib/module/components/Switch/index.js +6 -2
  23. package/lib/module/components/Switch/index.js.map +1 -1
  24. package/lib/module/hooks/index.js +5 -0
  25. package/lib/module/hooks/index.js.map +1 -0
  26. package/lib/module/hooks/use-form/index.js +6 -0
  27. package/lib/module/hooks/use-form/index.js.map +1 -0
  28. package/lib/module/hooks/use-form/types.js +2 -0
  29. package/lib/module/hooks/use-form/types.js.map +1 -0
  30. package/lib/module/hooks/use-form/useForm.js +414 -0
  31. package/lib/module/hooks/use-form/useForm.js.map +1 -0
  32. package/lib/module/hooks/use-form/validators.js +122 -0
  33. package/lib/module/hooks/use-form/validators.js.map +1 -0
  34. package/lib/module/index.js +1 -0
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/typescript/commonjs/src/components/Group/index.d.ts +1 -0
  37. package/lib/typescript/commonjs/src/components/Group/index.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/src/components/Radio/index.d.ts +22 -1
  39. package/lib/typescript/commonjs/src/components/Radio/index.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/src/components/Switch/index.d.ts +3 -3
  41. package/lib/typescript/commonjs/src/components/Switch/index.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/src/hooks/index.d.ts +3 -0
  43. package/lib/typescript/commonjs/src/hooks/index.d.ts.map +1 -0
  44. package/lib/typescript/commonjs/src/hooks/use-form/index.d.ts +4 -0
  45. package/lib/typescript/commonjs/src/hooks/use-form/index.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/src/hooks/use-form/types.d.ts +119 -0
  47. package/lib/typescript/commonjs/src/hooks/use-form/types.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/src/hooks/use-form/useForm.d.ts +30 -0
  49. package/lib/typescript/commonjs/src/hooks/use-form/useForm.d.ts.map +1 -0
  50. package/lib/typescript/commonjs/src/hooks/use-form/validators.d.ts +41 -0
  51. package/lib/typescript/commonjs/src/hooks/use-form/validators.d.ts.map +1 -0
  52. package/lib/typescript/commonjs/src/index.d.ts +1 -0
  53. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  54. package/lib/typescript/module/src/components/Group/index.d.ts +1 -0
  55. package/lib/typescript/module/src/components/Group/index.d.ts.map +1 -1
  56. package/lib/typescript/module/src/components/Radio/index.d.ts +22 -1
  57. package/lib/typescript/module/src/components/Radio/index.d.ts.map +1 -1
  58. package/lib/typescript/module/src/components/Switch/index.d.ts +3 -3
  59. package/lib/typescript/module/src/components/Switch/index.d.ts.map +1 -1
  60. package/lib/typescript/module/src/hooks/index.d.ts +3 -0
  61. package/lib/typescript/module/src/hooks/index.d.ts.map +1 -0
  62. package/lib/typescript/module/src/hooks/use-form/index.d.ts +4 -0
  63. package/lib/typescript/module/src/hooks/use-form/index.d.ts.map +1 -0
  64. package/lib/typescript/module/src/hooks/use-form/types.d.ts +119 -0
  65. package/lib/typescript/module/src/hooks/use-form/types.d.ts.map +1 -0
  66. package/lib/typescript/module/src/hooks/use-form/useForm.d.ts +30 -0
  67. package/lib/typescript/module/src/hooks/use-form/useForm.d.ts.map +1 -0
  68. package/lib/typescript/module/src/hooks/use-form/validators.d.ts +41 -0
  69. package/lib/typescript/module/src/hooks/use-form/validators.d.ts.map +1 -0
  70. package/lib/typescript/module/src/index.d.ts +1 -0
  71. package/lib/typescript/module/src/index.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/components/Group/index.tsx +1 -0
  74. package/src/components/Radio/index.tsx +99 -8
  75. package/src/components/Switch/index.tsx +10 -6
  76. package/src/hooks/index.ts +2 -0
  77. package/src/hooks/use-form/index.ts +3 -0
  78. package/src/hooks/use-form/types.ts +169 -0
  79. package/src/hooks/use-form/useForm.ts +436 -0
  80. package/src/hooks/use-form/validators.ts +143 -0
  81. package/src/index.tsx +1 -0
@@ -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
+ }
@@ -0,0 +1,143 @@
1
+ import type { FormValidator } from './types';
2
+
3
+ /**
4
+ * Built-in validators
5
+ */
6
+
7
+ /**
8
+ * Validates that a value is not empty
9
+ */
10
+ export const isNotEmpty = (message = 'This field is required'): FormValidator<any> => {
11
+ return (value: any) => {
12
+ if (value === null || value === undefined || value === '') {
13
+ return message;
14
+ }
15
+ if (Array.isArray(value) && value.length === 0) {
16
+ return message;
17
+ }
18
+ if (typeof value === 'string' && value.trim().length === 0) {
19
+ return message;
20
+ }
21
+ return null;
22
+ };
23
+ };
24
+
25
+ /**
26
+ * Validates minimum length
27
+ */
28
+ export const minLength = (
29
+ min: number,
30
+ message?: string
31
+ ): FormValidator<string> => {
32
+ return (value: string) => {
33
+ if (!value) return null;
34
+ if (value.length < min) {
35
+ return message || `Must be at least ${min} characters`;
36
+ }
37
+ return null;
38
+ };
39
+ };
40
+
41
+ /**
42
+ * Validates maximum length
43
+ */
44
+ export const maxLength = (
45
+ max: number,
46
+ message?: string
47
+ ): FormValidator<string> => {
48
+ return (value: string) => {
49
+ if (!value) return null;
50
+ if (value.length > max) {
51
+ return message || `Must be at most ${max} characters`;
52
+ }
53
+ return null;
54
+ };
55
+ };
56
+
57
+ /**
58
+ * Validates that value matches a pattern
59
+ */
60
+ export const matches = (
61
+ pattern: RegExp,
62
+ message = 'Invalid format'
63
+ ): FormValidator<string> => {
64
+ return (value: string) => {
65
+ if (!value) return null;
66
+ if (!pattern.test(value)) {
67
+ return message;
68
+ }
69
+ return null;
70
+ };
71
+ };
72
+
73
+ /**
74
+ * Validates email format
75
+ */
76
+ export const isEmail = (message = 'Invalid email'): FormValidator<string> => {
77
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
78
+ return matches(emailRegex, message);
79
+ };
80
+
81
+ /**
82
+ * Validates minimum value
83
+ */
84
+ export const min = (
85
+ minValue: number,
86
+ message?: string
87
+ ): FormValidator<number> => {
88
+ return (value: number) => {
89
+ if (value === null || value === undefined) return null;
90
+ if (value < minValue) {
91
+ return message || `Must be at least ${minValue}`;
92
+ }
93
+ return null;
94
+ };
95
+ };
96
+
97
+ /**
98
+ * Validates maximum value
99
+ */
100
+ export const max = (
101
+ maxValue: number,
102
+ message?: string
103
+ ): FormValidator<number> => {
104
+ return (value: number) => {
105
+ if (value === null || value === undefined) return null;
106
+ if (value > maxValue) {
107
+ return message || `Must be at most ${maxValue}`;
108
+ }
109
+ return null;
110
+ };
111
+ };
112
+
113
+ /**
114
+ * Validates that value is in a range
115
+ */
116
+ export const inRange = (
117
+ minValue: number,
118
+ maxValue: number,
119
+ message?: string
120
+ ): FormValidator<number> => {
121
+ return (value: number) => {
122
+ if (value === null || value === undefined) return null;
123
+ if (value < minValue || value > maxValue) {
124
+ return message || `Must be between ${minValue} and ${maxValue}`;
125
+ }
126
+ return null;
127
+ };
128
+ };
129
+
130
+ /**
131
+ * Validates that value is in a list
132
+ */
133
+ export const isInArray = <T>(
134
+ array: T[],
135
+ message = 'Invalid value'
136
+ ): FormValidator<T> => {
137
+ return (value: T) => {
138
+ if (!array.includes(value)) {
139
+ return message;
140
+ }
141
+ return null;
142
+ };
143
+ };
package/src/index.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './theme';
2
2
  export * from './components';
3
+ export * from './hooks';
3
4
  export type * from './theme/theme';
4
5
  export type * from './theme/types';