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 +388 -62
- package/dist/CustomForm.js +75 -17
- package/dist/types.d.ts +10 -2
- package/package.json +7 -7
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` |
|
|
43
|
-
| `formControl` | Control object from `react-hook-form`. | Yes
|
|
44
|
-
| `submitButton` | Boolean to toggle
|
|
45
|
-
| `resetButton` | Boolean to toggle
|
|
46
|
-
| `actionButtonsPlacement` | CSS property for button placement.
|
|
47
|
-
| `otherProps` | Any additional props to pass down to the form component. | No
|
|
48
|
-
|
|
49
|
-
#### CustomField
|
|
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.
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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 = (
|
|
95
|
-
console.log({ error:
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
package/dist/CustomForm.js
CHANGED
|
@@ -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,
|
|
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(
|
|
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.
|
|
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?:
|
|
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
|
|
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": "
|
|
30
|
-
"@mui/x-date-pickers": "^
|
|
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": "^
|
|
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": "^
|
|
40
|
-
"@types/react": "^18.
|
|
41
|
-
"typescript": "^5.
|
|
39
|
+
"@types/node": "^22.7.0",
|
|
40
|
+
"@types/react": "^18.3.9",
|
|
41
|
+
"typescript": "^5.6.2"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {}
|
|
44
44
|
}
|