mui-custom-form 1.0.1 → 1.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # 📝 CustomForm
2
2
 
3
- `CustomForm` is a versatile React form component designed to simplify your form-building journey. Powered by MUI components and the flexibility of `react-hook-form`, this package offers an intuitive way to create dynamic and responsive forms.
3
+ `CustomForm` is a versatile React form component designed to simplify your form-building journey. Powered by MUI components and the flexibility of `react-hook-form`, this package offers an intuitive way to create dynamic and responsive forms with a wide range of field types and customization options.
4
4
 
5
5
  ## 📚 Table of Contents
6
6
 
@@ -8,7 +8,13 @@
8
8
  - [✨ Features](#features)
9
9
  - [🚀 Usage](#usage)
10
10
  - [🔧 Props](#props)
11
+ - [CustomForm Component](#customform-component)
12
+ - [CustomField Configuration](#customfield-configuration)
13
+ - [🧩 Custom Components](#custom-components)
11
14
  - [📖 Examples](#examples)
15
+ - [Basic Form](#basic-form)
16
+ - [Form with Zod Validation](#form-with-zod-validation)
17
+ - [Comprehensive Example with All Field Types](#comprehensive-example-with-all-field-types)
12
18
  - [🤝 Contribution](#contribution)
13
19
  - [📜 License](#license)
14
20
 
@@ -18,17 +24,15 @@
18
24
  npm install mui-custom-form
19
25
  ```
20
26
 
21
- 📦 **Dependencies**:
22
-
23
- - `@mui/material`
24
- - `@mui/x-date-pickers`
25
- - `react-hook-form`
26
-
27
27
  ## ✨ Features
28
28
 
29
- - 🎛 **Dynamic Field Types**
30
- - 🎨 **MUI Integration**
31
- - 📱 **Responsive Design**
29
+ - 🎛 **Dynamic Field Types**: Supports a wide range of field types including `text`, `number`, `password`, `textarea`, `date`, `file`, `switch`, `checkbox-group`, `radio-group`, and `custom` components.
30
+ - 🎨 **MUI Integration**: Seamlessly integrates with Material-UI components for a consistent and polished UI.
31
+ - 📱 **Responsive Design**: Utilizes MUI's grid system to ensure forms are responsive and well-organized across different screen sizes.
32
+ - 🛠 **Customizable Input Props**: Allows passing additional properties to input fields for enhanced customization.
33
+ - 🧩 **Custom Components**: Enables integration of custom components that adhere to the `value` and `onChange` interface, providing maximum flexibility.
34
+ - 🔄 **Form Control Integration**: Leverages `react-hook-form` for efficient form state management and validation.
35
+ - 🧰 **Validation Support**: Supports schema-based validation with Zod.
32
36
 
33
37
  ## 🚀 Usage
34
38
 
@@ -38,37 +42,82 @@ npm install mui-custom-form
38
42
 
39
43
  | Name | Description | Is Required? |
40
44
  | ------------------------ | ------------------------------------------------------------------ | ------------ |
41
- | `fieldsGroups` | 2D array representing groups of fields in the form. | Yes |
42
- | `onSubmit` | Array containing functions for form submission and error handling. | Yes |
43
- | `formControl` | Control object from `react-hook-form`. | Yes |
44
- | `submitButton` | Boolean to toggle the visibility of the submit button. | No |
45
- | `resetButton` | Boolean to toggle the visibility of the reset button. | No |
46
- | `actionButtonsPlacement` | CSS property for button placement. | No |
47
- | `otherProps` | Any additional props to pass down to the form component. | No |
48
-
49
- #### CustomField 2D Array
50
-
51
- | Name | Description | Is Required? |
52
- | ------------ | ------------------------------------------- | ------------ |
53
- | `label` | The display label of the field. | Yes |
54
- | `name` | The name attribute of the field. | Yes |
55
- | `type` | The type of the field. | Yes |
56
- | `list` | An array of options for select type fields. | Conditional |
57
- | `required` | Is the field mandatory? | No |
58
- | `otherProps` | Any additional props. | No |
59
- | `span` | Grid span for the field. | No |
60
-
61
- #### \* `list` prop is required when the type is `single-select` or `multi-select`.
45
+ | `fieldsGroups` | 2D array representing groups of fields in the form. | **Yes** |
46
+ | `onSubmit` | Tuple containing functions for form submission and error handling. | **Yes** |
47
+ | `formControl` | Control object from `react-hook-form`. | **Yes** |
48
+ | `submitButton` | Boolean or `ButtonProps` to toggle and customize the submit button.| **No** |
49
+ | `resetButton` | Boolean or `ButtonProps` to toggle and customize the reset button. | **No** |
50
+ | `actionButtonsPlacement` | CSS property for button placement (e.g., `flex-end`, `center`). | **No** |
51
+ | `otherProps` | Any additional props to pass down to the form component. | **No** |
52
+
53
+ #### CustomField Configuration
54
+
55
+ | Name | Description | Is Required? |
56
+ | ------------ | ------------------------------------------- | ---------------- |
57
+ | `label` | The display label of the field. | **Yes** |
58
+ | `name` | The name attribute of the field. | **Yes** |
59
+ | `type` | The type of the field. | **Yes** |
60
+ | `list` | An array of options for select type fields. | *Conditional* |
61
+ | `required` | Is the field mandatory? | **No** |
62
+ | `otherProps` | Any additional props. | **No** |
63
+ | `span` | Grid span for the field (1-12). | **No** |
64
+ | `component` | Custom React component for the field. | *Conditional* |
65
+
66
+ > **Notes**:
67
+ > - The `list` prop is **required** when the `type` is `single-select`, `multi-select`, `checkbox-group`, or `radio-group`.
68
+ > - The `component` prop is **required** when the `type` is `custom`.
69
+
70
+ ### 🧩 Custom Components
71
+
72
+ To integrate custom components into your forms, ensure they adhere to the `CustomComponentProps` interface, which requires `value` and `onChange` props.
73
+
74
+ ```typescript
75
+ export interface CustomComponentProps {
76
+ value: any;
77
+ onChange: (value: any) => void;
78
+ [key: string]: any; // Allow additional props
79
+ }
80
+ ```
62
81
 
63
- ---
82
+ **Example of a Custom Component:**
83
+
84
+ ```tsx
85
+ // CustomComponents.tsx
86
+ import React from "react";
87
+ import { TextField } from "@mui/material";
88
+ import { CustomComponentProps } from "./types";
89
+
90
+ export const CustomTextComponent: React.FC<CustomComponentProps> = ({
91
+ value,
92
+ onChange,
93
+ placeholder,
94
+ }) => {
95
+ return (
96
+ <TextField
97
+ value={value || ""}
98
+ onChange={(e) => onChange(e.target.value)}
99
+ placeholder={placeholder}
100
+ fullWidth
101
+ />
102
+ );
103
+ };
104
+ ```
64
105
 
65
106
  ## 📖 Examples
66
107
 
67
108
  ### Basic Form
68
109
 
69
- ```ts
110
+ A simple form demonstrating basic field types like `text` and `date`.
111
+
112
+ ```tsx
113
+ // BasicForm.tsx
114
+ import React from "react";
115
+ import { useForm } from "react-hook-form";
116
+ import { CustomForm } from "mui-custom-form";
117
+ import { IFieldGroup } from "mui-custom-form/types";
118
+
70
119
  const BasicForm = () => {
71
- const formControl = useForm<{ username: string; birthdate: string }>()
120
+ const formControl = useForm<{ username: string; birthdate: string }>();
72
121
 
73
122
  const fieldsGroups: IFieldGroup = [
74
123
  [
@@ -77,23 +126,26 @@ const BasicForm = () => {
77
126
  name: "username",
78
127
  type: "text",
79
128
  required: true,
129
+ otherProps: { placeholder: "Enter your username" },
130
+ span: 6,
80
131
  },
81
132
  {
82
133
  label: "Birthdate",
83
134
  name: "birthdate",
84
135
  type: "date",
85
136
  required: true,
137
+ span: 6,
86
138
  },
87
139
  ],
88
- ]
140
+ ];
89
141
 
90
142
  const handleSubmit = (data: { username: string; birthdate: string }) => {
91
- console.log({ success: data })
92
- }
143
+ console.log({ success: data });
144
+ };
93
145
 
94
- const submitError = (data: unknown) => {
95
- console.log({ error: data })
96
- }
146
+ const submitError = (errors: any) => {
147
+ console.log({ error: errors });
148
+ };
97
149
 
98
150
  return (
99
151
  <CustomForm
@@ -101,31 +153,54 @@ const BasicForm = () => {
101
153
  onSubmit={[handleSubmit, submitError]}
102
154
  formControl={formControl}
103
155
  />
104
- )
105
- }
156
+ );
157
+ };
158
+
159
+ export default BasicForm;
106
160
  ```
107
161
 
108
162
  ### Form with Zod Validation
109
163
 
110
- ```ts
111
- const GENDERS = ["Male", "Female"] as const
112
- const HOBBIES = ["Coding", "Collections", "Hiking"] as const
164
+ A form demonstrating advanced field types and schema-based validation using Zod.
165
+
166
+ ```tsx
167
+ // FormWithZod.tsx
168
+ import React from "react";
169
+ import { useForm } from "react-hook-form";
170
+ import { zodResolver } from "@hookform/resolvers/zod";
171
+ import { z } from "zod";
172
+ import { CustomForm } from "mui-custom-form";
173
+ import { IFieldGroup } from "mui-custom-form/types";
174
+
175
+ const GENDERS = ["Male", "Female", "Other"] as const;
176
+ const HOBBIES = ["Coding", "Collections", "Hiking"] as const;
113
177
 
114
178
  const Fields = z.object({
115
- username: z.string(),
116
- age: z.number().min(16).max(80),
117
- gender: z.enum(GENDERS),
118
- hobbies: z.array(z.enum(HOBBIES)).nonempty("Please choose one"),
119
- birthDate: z.date(),
179
+ username: z.string().min(1, "Username is required"),
180
+ age: z
181
+ .number({
182
+ required_error: "Age is required",
183
+ invalid_type_error: "Age must be a number",
184
+ })
185
+ .min(16, "Minimum age is 16")
186
+ .max(80, "Maximum age is 80"),
187
+ gender: z.enum(GENDERS, { required_error: "Gender is required" }),
188
+ hobbies: z
189
+ .array(z.enum(HOBBIES), { required_error: "Hobbies are required" })
190
+ .nonempty("Please choose at least one hobby"),
191
+ birthDate: z.date({ required_error: "Birthdate is required" }),
120
192
  file: z.instanceof(File).optional(),
121
- })
193
+ });
122
194
 
123
- type FieldTypes = z.infer<typeof Fields>
195
+ type FieldTypes = z.infer<typeof Fields>;
124
196
 
125
197
  const FormWithZod = () => {
126
198
  const formControl = useForm<FieldTypes>({
127
199
  resolver: zodResolver(Fields),
128
- })
200
+ defaultValues: {
201
+ hobbies: [],
202
+ },
203
+ });
129
204
 
130
205
  const fieldsGroups: IFieldGroup<FieldTypes> = [
131
206
  [
@@ -134,57 +209,308 @@ const FormWithZod = () => {
134
209
  name: "username",
135
210
  type: "text",
136
211
  required: true,
212
+ otherProps: { placeholder: "Enter your username" },
213
+ span: 6,
137
214
  },
138
215
  {
139
216
  label: "Age",
140
217
  name: "age",
141
218
  type: "number",
142
219
  required: true,
220
+ otherProps: { min: 16, max: 80 },
221
+ span: 6,
143
222
  },
144
223
  ],
145
224
  [
146
225
  {
147
226
  label: "Gender",
148
227
  name: "gender",
149
- type: "single-select",
228
+ type: "radio-group",
150
229
  list: GENDERS.map((gender) => ({ label: gender, value: gender })),
230
+ required: true,
231
+ otherProps: { row: true },
232
+ span: 6,
151
233
  },
152
234
  {
153
235
  label: "Hobbies",
154
236
  name: "hobbies",
155
- type: "multi-select",
237
+ type: "checkbox-group",
156
238
  list: HOBBIES.map((hobby) => ({ label: hobby, value: hobby })),
157
239
  required: true,
240
+ span: 6,
158
241
  },
159
242
  ],
160
243
  [
161
244
  {
162
- label: "Date of birth",
245
+ label: "Birthdate",
163
246
  name: "birthDate",
164
247
  type: "date",
248
+ required: true,
249
+ span: 6,
165
250
  },
166
251
  {
167
252
  label: "Upload Image",
168
253
  name: "file",
169
254
  type: "file",
255
+ required: false,
256
+ span: 6,
170
257
  },
171
258
  ],
172
- ]
259
+ ];
173
260
 
174
261
  const onSubmit = (data: FieldTypes) => {
175
- console.log({ success: data })
176
- }
262
+ console.log({ success: data });
263
+ };
264
+
265
+ const submitError = (errors: any) => {
266
+ console.log({ errors });
267
+ };
177
268
 
178
269
  return (
179
270
  <CustomForm
180
271
  fieldsGroups={fieldsGroups}
181
- onSubmit={[onSubmit]}
272
+ onSubmit={[onSubmit, submitError]}
182
273
  formControl={formControl}
183
274
  />
184
- )
185
- }
275
+ );
276
+ };
277
+
278
+ export default FormWithZod;
279
+ ```
280
+
281
+ ### Comprehensive Example with All Field Types
282
+
283
+ An extensive example showcasing all supported field types, customizable input props, and custom components.
284
+
285
+ ```tsx
286
+ // ComprehensiveForm.tsx
287
+ import React from "react";
288
+ import { useForm } from "react-hook-form";
289
+ import { zodResolver } from "@hookform/resolvers/zod";
290
+ import { z } from "zod";
291
+ import { Container, Typography, ButtonProps } from "@mui/material";
292
+ import { CustomForm } from "mui-custom-form";
293
+ import { IFieldGroup } from "mui-custom-form/types";
294
+ import {
295
+ CustomTextComponent,
296
+ DateRangePickerComponent,
297
+ } from "./CustomComponents"; // Ensure correct import path
298
+
299
+ const GENDERS = ["Male", "Female", "Other"] as const;
300
+ const HOBBIES = ["Coding", "Collections", "Hiking"] as const;
301
+
302
+ const Fields = z.object({
303
+ username: z.string().min(1, "Username is required"),
304
+ password: z.string().min(6, "Password must be at least 6 characters"),
305
+ bio: z.string().optional(),
306
+ subscribe: z.boolean().optional(),
307
+ age: z
308
+ .number({
309
+ required_error: "Age is required",
310
+ invalid_type_error: "Age must be a number",
311
+ })
312
+ .min(16, "Minimum age is 16")
313
+ .max(80, "Maximum age is 80"),
314
+ gender: z.enum(GENDERS, { required_error: "Gender is required" }),
315
+ hobbies: z
316
+ .array(z.enum(HOBBIES), { required_error: "Hobbies are required" })
317
+ .nonempty("Please choose at least one hobby"),
318
+ birthDate: z.date({ required_error: "Birthdate is required" }),
319
+ file: z.instanceof(File).optional(),
320
+ customField: z.string().optional(),
321
+ eventDates: z
322
+ .tuple([z.date().nullable(), z.date().nullable()])
323
+ .optional(),
324
+ });
325
+
326
+ type FieldTypes = z.infer<typeof Fields>;
327
+
328
+ const ComprehensiveForm = () => {
329
+ const formControl = useForm<FieldTypes>({
330
+ resolver: zodResolver(Fields),
331
+ defaultValues: {
332
+ subscribe: false,
333
+ hobbies: [],
334
+ eventDates: [null, null],
335
+ },
336
+ });
337
+
338
+ const fieldsGroups: IFieldGroup<FieldTypes> = [
339
+ [
340
+ {
341
+ label: "Username",
342
+ name: "username",
343
+ type: "text",
344
+ required: true,
345
+ otherProps: { placeholder: "Enter your username" },
346
+ span: 6,
347
+ },
348
+ {
349
+ label: "Password",
350
+ name: "password",
351
+ type: "password",
352
+ required: true,
353
+ otherProps: { placeholder: "Enter your password" },
354
+ span: 6,
355
+ },
356
+ ],
357
+ [
358
+ {
359
+ label: "Bio",
360
+ name: "bio",
361
+ type: "textarea",
362
+ required: false,
363
+ otherProps: { rows: 5, placeholder: "Tell us about yourself" },
364
+ span: 12,
365
+ },
366
+ ],
367
+ [
368
+ {
369
+ label: "Subscribe to Newsletter",
370
+ name: "subscribe",
371
+ type: "switch",
372
+ required: false,
373
+ otherProps: { color: "primary" },
374
+ span: 6,
375
+ },
376
+ ],
377
+ [
378
+ {
379
+ label: "Age",
380
+ name: "age",
381
+ type: "number",
382
+ required: true,
383
+ otherProps: { min: 16, max: 80 },
384
+ span: 6,
385
+ },
386
+ {
387
+ label: "Gender",
388
+ name: "gender",
389
+ type: "radio-group",
390
+ list: GENDERS.map((gender) => ({
391
+ label: gender,
392
+ value: gender,
393
+ })),
394
+ required: true,
395
+ otherProps: { row: true },
396
+ span: 6,
397
+ },
398
+ ],
399
+ [
400
+ {
401
+ label: "Hobbies",
402
+ name: "hobbies",
403
+ type: "checkbox-group",
404
+ list: HOBBIES.map((hobby) => ({ label: hobby, value: hobby })),
405
+ required: true,
406
+ span: 6,
407
+ },
408
+ {
409
+ label: "Birthdate",
410
+ name: "birthDate",
411
+ type: "date",
412
+ required: true,
413
+ span: 6,
414
+ },
415
+ ],
416
+ [
417
+ {
418
+ label: "Event Dates",
419
+ name: "eventDates",
420
+ type: "custom",
421
+ component: DateRangePickerComponent,
422
+ required: false,
423
+ span: 6,
424
+ },
425
+ {
426
+ label: "Upload Image",
427
+ name: "file",
428
+ type: "file",
429
+ required: false,
430
+ span: 6,
431
+ },
432
+ ],
433
+ [
434
+ {
435
+ label: "Custom Text Field",
436
+ name: "customField",
437
+ type: "custom",
438
+ component: CustomTextComponent,
439
+ required: false,
440
+ otherProps: { placeholder: "Custom input here" },
441
+ span: 12,
442
+ },
443
+ ],
444
+ ];
445
+
446
+ const onSubmit = (data: FieldTypes) => {
447
+ console.log({ success: data });
448
+ };
449
+
450
+ const submitError = (errors: any) => {
451
+ console.log({ errors });
452
+ };
453
+
454
+ const submitButtonProps: ButtonProps = {
455
+ variant: "contained",
456
+ color: "primary",
457
+ };
458
+
459
+ const resetButtonProps: ButtonProps = {
460
+ variant: "outlined",
461
+ color: "secondary",
462
+ };
463
+
464
+ return (
465
+ <Container maxWidth="md" sx={{ mt: 4, mb: 4 }}>
466
+ <Typography variant="h4" gutterBottom>
467
+ Comprehensive Form
468
+ </Typography>
469
+ <CustomForm
470
+ fieldsGroups={fieldsGroups}
471
+ onSubmit={[onSubmit, submitError]}
472
+ formControl={formControl}
473
+ submitButton={submitButtonProps}
474
+ resetButton={resetButtonProps}
475
+ actionButtonsPlacement="flex-end"
476
+ otherProps={{ spacing: 2 }}
477
+ />
478
+ </Container>
479
+ );
480
+ };
481
+
482
+ export default ComprehensiveForm;
186
483
  ```
187
484
 
485
+ ## 🤝 Contribution
486
+
487
+ Your contributions are always welcome! Whether it's reporting bugs, suggesting features, or submitting pull requests, we appreciate your support in making `CustomForm` better for everyone.
488
+
489
+ 1. **Fork the Repository**
490
+ 2. **Create a Feature Branch**
491
+ ```bash
492
+ git checkout -b feature/YourFeature
493
+ ```
494
+ 3. **Commit Your Changes**
495
+ ```bash
496
+ git commit -m "Add YourFeature"
497
+ ```
498
+ 4. **Push to the Branch**
499
+ ```bash
500
+ git push origin feature/YourFeature
501
+ ```
502
+ 5. **Open a Pull Request**
503
+
504
+ Please ensure your code follows the project's coding standards and includes relevant tests where applicable.
505
+
506
+ ## 📜 License
507
+
508
+ This project is licensed under the [MIT license](https://github.com/DevOsamaIslam/mui-custom-form/blob/master/LICENSE).
509
+
510
+ ---
511
+
512
+ If you have any questions or need further assistance, feel free to reach out or open an issue in the repository!
513
+
188
514
  ---
189
515
 
190
516
  ## 🤝 Contribution
@@ -10,11 +10,21 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
14
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
15
+ if (ar || !(i in from)) {
16
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
17
+ ar[i] = from[i];
18
+ }
19
+ }
20
+ return to.concat(ar || Array.prototype.slice.call(from));
21
+ };
13
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
24
  };
16
25
  Object.defineProperty(exports, "__esModule", { value: true });
17
26
  exports.CustomForm = void 0;
27
+ // CustomForm.tsx
18
28
  var material_1 = require("@mui/material");
19
29
  var x_date_pickers_1 = require("@mui/x-date-pickers");
20
30
  var react_1 = __importDefault(require("react"));
@@ -26,6 +36,7 @@ var CustomForm = function (_a) {
26
36
  switch (field.type) {
27
37
  case "text":
28
38
  case "number":
39
+ case "password":
29
40
  return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
30
41
  var controlField = _a.field, error = _a.fieldState.error;
31
42
  return (react_1.default.createElement(material_1.TextField, __assign({}, controlField, { value: controlField.value || "" }, field.otherProps, { label: field.label, type: field.type, fullWidth: true, required: field.required, error: !!error, helperText: error === null || error === void 0 ? void 0 : error.message, onChange: function (e) {
@@ -36,9 +47,15 @@ var CustomForm = function (_a) {
36
47
  : e.target.value);
37
48
  } })));
38
49
  } }));
50
+ case "textarea":
51
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
52
+ var _b;
53
+ var controlField = _a.field, error = _a.fieldState.error;
54
+ return (react_1.default.createElement(material_1.TextField, __assign({}, controlField, { value: controlField.value || "" }, field.otherProps, { label: field.label, type: "text", fullWidth: true, required: field.required, multiline: true, rows: ((_b = field.otherProps) === null || _b === void 0 ? void 0 : _b.rows) || 4, error: !!error, helperText: error === null || error === void 0 ? void 0 : error.message, onChange: controlField.onChange })));
55
+ } }));
39
56
  case "single-select":
40
57
  case "multi-select":
41
- return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, render: function (_a) {
58
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
42
59
  var _b;
43
60
  var controlField = _a.field, error = _a.fieldState.error;
44
61
  return (react_1.default.createElement(material_1.FormControl, { fullWidth: true, error: !!error },
@@ -48,29 +65,70 @@ var CustomForm = function (_a) {
48
65
  error && react_1.default.createElement(material_1.FormHelperText, null, error.message)));
49
66
  } }));
50
67
  case "date":
51
- return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, render: function (_a) {
68
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
52
69
  var controlField = _a.field, error = _a.fieldState.error;
53
- return (react_1.default.createElement(x_date_pickers_1.DatePicker, __assign({}, controlField, { value: controlField.value || null }, field.otherProps, { label: field.label, onChange: controlField.onChange, slotProps: {
54
- textField: {
55
- required: field.required,
56
- error: !!error,
57
- helperText: error === null || error === void 0 ? void 0 : error.message,
58
- fullWidth: true,
59
- },
60
- } })));
70
+ return (react_1.default.createElement(x_date_pickers_1.DatePicker, __assign({}, controlField, { value: controlField.value || null }, field.otherProps, { label: field.label, onChange: function (date) { return controlField.onChange(date); }, renderInput: function (params) { return (react_1.default.createElement(material_1.TextField, __assign({}, params, { required: field.required, error: !!error, helperText: error === null || error === void 0 ? void 0 : error.message, fullWidth: true }))); } })));
61
71
  } }));
62
72
  case "file":
63
- return (react_1.default.createElement(react_1.default.Fragment, null,
64
- react_1.default.createElement(material_1.FormLabel, { component: "legend" }, field.label),
73
+ return (react_1.default.createElement(material_1.FormControl, { fullWidth: true, error: !!field.required },
74
+ react_1.default.createElement(material_1.FormLabel, { component: "legend", required: field.required }, field.label),
65
75
  react_1.default.createElement(material_1.Button, { variant: "contained", component: "label" },
66
76
  "Upload File",
67
- react_1.default.createElement("input", { type: "file", hidden: true, onChange: function (e) {
68
- // Set the value to either the File instance or undefined
77
+ react_1.default.createElement("input", __assign({ type: "file", hidden: true, onChange: function (e) {
69
78
  var fileValue = e.target.files && e.target.files.length > 0
70
79
  ? e.target.files[0]
71
80
  : undefined;
72
81
  setValue(field.name, fileValue);
73
- } }))));
82
+ } }, field.otherProps)))));
83
+ case "switch":
84
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
85
+ var controlField = _a.field, error = _a.fieldState.error;
86
+ return (react_1.default.createElement(material_1.FormControlLabel, { control: react_1.default.createElement(material_1.Switch, __assign({}, controlField, { checked: !!controlField.value, onChange: function (e) { return controlField.onChange(e.target.checked); } }, field.otherProps)), label: field.label }));
87
+ } }));
88
+ case "checkbox-group":
89
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
90
+ var _b;
91
+ var controlField = _a.field, error = _a.fieldState.error;
92
+ return (react_1.default.createElement(material_1.FormControl, { component: "fieldset", error: !!error },
93
+ react_1.default.createElement(material_1.FormLabel, { component: "legend", required: field.required }, field.label),
94
+ react_1.default.createElement(material_1.Stack, { direction: "row", spacing: 2 }, (_b = field.list) === null || _b === void 0 ? void 0 : _b.map(function (option) {
95
+ var _a;
96
+ return (react_1.default.createElement(material_1.FormControlLabel, { key: option.value, control: react_1.default.createElement(material_1.Checkbox, __assign({ checked: ((_a = controlField.value) === null || _a === void 0 ? void 0 : _a.includes(option.value)) || false, onChange: function (e) {
97
+ var checked = e.target.checked;
98
+ var value = option.value;
99
+ if (checked) {
100
+ controlField.onChange(__spreadArray(__spreadArray([], (controlField.value || []), true), [
101
+ value,
102
+ ], false));
103
+ }
104
+ else {
105
+ controlField.onChange((controlField.value || []).filter(function (v) { return v !== value; }));
106
+ }
107
+ } }, field.otherProps)), label: option.label }));
108
+ })),
109
+ error && react_1.default.createElement(material_1.FormHelperText, null, error.message)));
110
+ } }));
111
+ case "radio-group":
112
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
113
+ var _b, _c;
114
+ var controlField = _a.field, error = _a.fieldState.error;
115
+ return (react_1.default.createElement(material_1.FormControl, { component: "fieldset", error: !!error },
116
+ react_1.default.createElement(material_1.FormLabel, { component: "legend", required: field.required }, field.label),
117
+ react_1.default.createElement(material_1.RadioGroup, __assign({}, controlField, { value: controlField.value || "", onChange: function (e) { return controlField.onChange(e.target.value); }, row: ((_b = field.otherProps) === null || _b === void 0 ? void 0 : _b.row) || false }), (_c = field.list) === null || _c === void 0 ? void 0 : _c.map(function (option) { return (react_1.default.createElement(material_1.FormControlLabel, { key: option.value, value: option.value, control: react_1.default.createElement(material_1.Radio, __assign({}, field.otherProps)), label: option.label })); })),
118
+ error && react_1.default.createElement(material_1.FormHelperText, null, error.message)));
119
+ } }));
120
+ case "custom":
121
+ if (field.component) {
122
+ var CustomComponent_1 = field.component;
123
+ return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, rules: { required: field.required }, render: function (_a) {
124
+ var controlField = _a.field, error = _a.fieldState.error;
125
+ return (react_1.default.createElement(material_1.FormControl, { fullWidth: true, error: !!error },
126
+ react_1.default.createElement(material_1.FormLabel, { component: "legend", required: field.required }, field.label),
127
+ react_1.default.createElement(CustomComponent_1, __assign({ value: controlField.value, onChange: controlField.onChange }, field.otherProps)),
128
+ error && react_1.default.createElement(material_1.FormHelperText, null, error.message)));
129
+ } }));
130
+ }
131
+ return null;
74
132
  default:
75
133
  return null;
76
134
  }
@@ -78,8 +136,8 @@ var CustomForm = function (_a) {
78
136
  var submitButtonProps = submitButton && submitButton !== true ? submitButton : {};
79
137
  var resetButtonProps = resetButton && resetButton !== true ? resetButton : {};
80
138
  return (react_1.default.createElement(material_1.Stack, __assign({ component: "form", onSubmit: formControl.handleSubmit(onSubmit[0], onSubmit[1]), noValidate: true }, otherProps, { spacing: 3 }),
81
- react_1.default.createElement(material_1.Grid, { container: true, spacing: 1 }, fieldsGroups.map(function (fields, rowIndex) { return (react_1.default.createElement(material_1.Grid, { container: true, item: true, key: rowIndex, spacing: 2 }, fields.map(function (field, fieldIndex) { return (react_1.default.createElement(material_1.Grid, { item: true, key: fieldIndex, xs: field.span || true }, renderField(field))); }))); })),
82
- react_1.default.createElement(material_1.Stack, { direction: "row", justifyContent: actionButtonsPlacement },
139
+ react_1.default.createElement(material_1.Grid2, { container: true, spacing: 1 }, fieldsGroups.map(function (fields, rowIndex) { return (react_1.default.createElement(material_1.Grid2, { container: true, key: rowIndex, spacing: 2 }, fields.map(function (field, fieldIndex) { return (react_1.default.createElement(material_1.Grid2, { key: fieldIndex, size: field.span || 12 }, renderField(field))); }))); })),
140
+ react_1.default.createElement(material_1.Stack, { direction: "row", justifyContent: actionButtonsPlacement || "flex-end", spacing: 2 },
83
141
  resetButton && (react_1.default.createElement(material_1.Button, __assign({ type: "reset", variant: "contained", onClick: function () { return reset(); } }, resetButtonProps), "Reset")),
84
142
  submitButton && (react_1.default.createElement(material_1.Button, __assign({ type: "submit", variant: "contained" }, submitButtonProps), "Submit")))));
85
143
  };
package/dist/types.d.ts CHANGED
@@ -3,7 +3,7 @@ import { CSSProperties } from "react";
3
3
  import { FieldValues, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
4
4
  type name<T> = T extends string ? string : keyof T;
5
5
  type SelectTypes = "single-select" | "multi-select";
6
- type BaseTypes = "text" | "number" | "date" | "file";
6
+ type BaseTypes = "text" | "number" | "date" | "file" | "password" | "textarea" | "switch" | "checkbox-group" | "radio-group" | "custom";
7
7
  export interface ISelectField {
8
8
  type: SelectTypes;
9
9
  list: $option[];
@@ -11,11 +11,12 @@ export interface ISelectField {
11
11
  export interface IOtherField {
12
12
  type: BaseTypes;
13
13
  list?: $option[];
14
+ component?: React.ComponentType<CustomComponentProps>;
14
15
  }
15
16
  export type ICustomField<T = string> = {
16
17
  label: string;
17
18
  name: name<T> | name<T>[];
18
- required?: true;
19
+ required?: boolean;
19
20
  otherProps?: any;
20
21
  span?: number;
21
22
  } & ({
@@ -23,6 +24,8 @@ export type ICustomField<T = string> = {
23
24
  list: $option[];
24
25
  } | {
25
26
  type: BaseTypes;
27
+ list?: $option[];
28
+ component?: React.ComponentType<CustomComponentProps>;
26
29
  });
27
30
  export type $option<T = any> = {
28
31
  icon?: React.ReactNode;
@@ -39,4 +42,9 @@ export interface ICustomForm<T extends FieldValues> {
39
42
  resetButton?: ButtonProps | boolean;
40
43
  otherProps?: any;
41
44
  }
45
+ export interface CustomComponentProps {
46
+ value: any;
47
+ onChange: (value: any) => void;
48
+ [key: string]: any;
49
+ }
42
50
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mui-custom-form",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A versatile React form component utilizing MUI components and react-hook-form.",
5
5
  "main": "dist/CustomForm.js",
6
6
  "types": "dist/CustomForm.d.ts",
@@ -26,19 +26,19 @@
26
26
  },
27
27
  "homepage": "https://github.com/DevOsamaIslam/mui-custom-form#readme",
28
28
  "peerDependencies": {
29
- "@mui/material": "5.14.2",
30
- "@mui/x-date-pickers": "^6.10.2",
29
+ "@mui/material": "6.0.1",
30
+ "@mui/x-date-pickers": "^7.15.0",
31
31
  "react-hook-form": "^7.45.2",
32
- "date-fns": "^2.30.0",
32
+ "date-fns": "^4.1.0",
33
33
  "dayjs": "^1.11.9",
34
34
  "moment": "^2.29.4"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@emotion/react": "^11.11.1",
38
38
  "@emotion/styled": "^11.11.0",
39
- "@types/node": "^20.5.1",
40
- "@types/react": "^18.2.20",
41
- "typescript": "^5.1.6"
39
+ "@types/node": "^22.7.0",
40
+ "@types/react": "^18.3.9",
41
+ "typescript": "^5.6.2"
42
42
  },
43
43
  "dependencies": {}
44
44
  }