mui-custom-form 0.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/dist/CustomForm.d.ts +25 -0
- package/dist/CustomForm.js +75 -0
- package/package.json +40 -0
- package/readme.md +94 -0
- package/src/CustomForm.tsx +171 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useForm } from "react-hook-form";
|
|
2
|
+
import React from "react";
|
|
3
|
+
export type $option<T = any> = {
|
|
4
|
+
icon?: React.ReactNode;
|
|
5
|
+
label: string;
|
|
6
|
+
value: T;
|
|
7
|
+
};
|
|
8
|
+
export interface ICustomField<T = string> {
|
|
9
|
+
label: string;
|
|
10
|
+
name: T extends string ? string : keyof T;
|
|
11
|
+
type: "text" | "number" | "single-select" | "multi-select" | "date" | "file";
|
|
12
|
+
list?: $option[];
|
|
13
|
+
required?: boolean;
|
|
14
|
+
otherProps?: any;
|
|
15
|
+
span?: number;
|
|
16
|
+
}
|
|
17
|
+
interface ICustomForm {
|
|
18
|
+
fieldsGroups: ICustomField<any>[][];
|
|
19
|
+
onSubmit: ReturnType<this["formControl"]["handleSubmit"]>;
|
|
20
|
+
formControl: ReturnType<typeof useForm>;
|
|
21
|
+
submitButton?: boolean;
|
|
22
|
+
otherProps?: any;
|
|
23
|
+
}
|
|
24
|
+
export declare const CustomForm: React.FC<ICustomForm>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.CustomForm = void 0;
|
|
18
|
+
var material_1 = require("@mui/material");
|
|
19
|
+
var x_date_pickers_1 = require("@mui/x-date-pickers");
|
|
20
|
+
var react_hook_form_1 = require("react-hook-form");
|
|
21
|
+
var react_1 = __importDefault(require("react"));
|
|
22
|
+
var CustomForm = function (_a) {
|
|
23
|
+
var fieldsGroups = _a.fieldsGroups, onSubmit = _a.onSubmit, formControl = _a.formControl, _b = _a.submitButton, submitButton = _b === void 0 ? true : _b, otherProps = _a.otherProps;
|
|
24
|
+
var control = formControl.control, setValue = formControl.setValue;
|
|
25
|
+
var renderField = function (field) {
|
|
26
|
+
switch (field.type) {
|
|
27
|
+
case "text":
|
|
28
|
+
case "number":
|
|
29
|
+
return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, render: function (_a) {
|
|
30
|
+
var controlField = _a.field, error = _a.fieldState.error;
|
|
31
|
+
return (react_1.default.createElement(material_1.TextField, __assign({}, controlField, 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 })));
|
|
32
|
+
} }));
|
|
33
|
+
case "single-select":
|
|
34
|
+
case "multi-select":
|
|
35
|
+
return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, render: function (_a) {
|
|
36
|
+
var _b;
|
|
37
|
+
var controlField = _a.field, error = _a.fieldState.error;
|
|
38
|
+
return (react_1.default.createElement(material_1.FormControl, { fullWidth: true, error: !!error },
|
|
39
|
+
react_1.default.createElement(material_1.InputLabel, { required: field.required }, field.label),
|
|
40
|
+
react_1.default.createElement(material_1.Select, __assign({}, controlField, field.otherProps, { multiple: field.type === "multi-select" }), (_b = field.list) === null || _b === void 0 ? void 0 : _b.map(function (item) { return (react_1.default.createElement(material_1.MenuItem, { key: item.value, value: item.value }, item.label)); })),
|
|
41
|
+
error && react_1.default.createElement(material_1.FormHelperText, null, error.message)));
|
|
42
|
+
} }));
|
|
43
|
+
case "date":
|
|
44
|
+
return (react_1.default.createElement(react_hook_form_1.Controller, { name: field.name, control: control, render: function (_a) {
|
|
45
|
+
var controlField = _a.field, error = _a.fieldState.error;
|
|
46
|
+
return (react_1.default.createElement(x_date_pickers_1.DatePicker, __assign({}, controlField, field.otherProps, { label: field.label, onChange: controlField.onChange, slotProps: {
|
|
47
|
+
textField: {
|
|
48
|
+
required: field.required,
|
|
49
|
+
error: !!error,
|
|
50
|
+
helperText: error === null || error === void 0 ? void 0 : error.message,
|
|
51
|
+
fullWidth: true,
|
|
52
|
+
},
|
|
53
|
+
} })));
|
|
54
|
+
} }));
|
|
55
|
+
case "file":
|
|
56
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
57
|
+
react_1.default.createElement(material_1.FormLabel, { component: "legend" }, field.label),
|
|
58
|
+
react_1.default.createElement(material_1.Button, { variant: "contained", component: "label" },
|
|
59
|
+
"Upload File",
|
|
60
|
+
react_1.default.createElement("input", { type: "file", hidden: true, onChange: function (e) {
|
|
61
|
+
// Set the value to either the File instance or undefined
|
|
62
|
+
var fileValue = e.target.files && e.target.files.length > 0
|
|
63
|
+
? e.target.files[0]
|
|
64
|
+
: undefined;
|
|
65
|
+
setValue(field.name, fileValue);
|
|
66
|
+
} }))));
|
|
67
|
+
default:
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return (react_1.default.createElement(material_1.Stack, __assign({ component: "form", onSubmit: onSubmit, noValidate: true }, otherProps, { spacing: 3 }),
|
|
72
|
+
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))); }))); })),
|
|
73
|
+
submitButton && (react_1.default.createElement(material_1.Button, { type: "submit", variant: "contained" }, "Submit"))));
|
|
74
|
+
};
|
|
75
|
+
exports.CustomForm = CustomForm;
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mui-custom-form",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A versatile React form component utilizing MUI components and react-hook-form.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/DevOsamaIslam/muicustom-form.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"react",
|
|
17
|
+
"form",
|
|
18
|
+
"MUI",
|
|
19
|
+
"react-hook-form",
|
|
20
|
+
"customizable"
|
|
21
|
+
],
|
|
22
|
+
"author": "Your Name",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/your-github-username/custom-form/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/your-github-username/custom-form#readme",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@mui/material": "5.14.2",
|
|
30
|
+
"@mui/x-date-pickers": "^6.10.2",
|
|
31
|
+
"react-hook-form": "^7.45.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@emotion/react": "^11.11.1",
|
|
35
|
+
"@emotion/styled": "^11.11.0",
|
|
36
|
+
"@types/node": "^20.5.1",
|
|
37
|
+
"@types/react": "^18.2.20",
|
|
38
|
+
"typescript": "^5.1.6"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
## CustomForm
|
|
2
|
+
|
|
3
|
+
`CustomForm` is a versatile React form component designed to streamline the form-building process. Utilizing the power of MUI components and the flexibility of `react-hook-form`, this package offers an intuitive way to create dynamic and responsive forms.
|
|
4
|
+
|
|
5
|
+
### Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Features](#features)
|
|
9
|
+
- [Usage](#usage)
|
|
10
|
+
- [Props](#props)
|
|
11
|
+
- [Example](#example)
|
|
12
|
+
- [Contribution](#contribution)
|
|
13
|
+
- [License](#license)
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
|
|
17
|
+
To add `CustomForm` to your project, use the following command:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install mui-custom-form
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Ensure that you have the required peer dependencies installed:
|
|
24
|
+
|
|
25
|
+
- `@mui/material`
|
|
26
|
+
- `@mui/x-date-pickers`
|
|
27
|
+
- `react-hook-form`
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
- **Dynamic Field Types**: Supports text, number, single-select, multi-select, date, and file input types.
|
|
32
|
+
- **MUI Integration**: Seamless integration with MUI components for a consistent and professional look.
|
|
33
|
+
- **Responsive Design**: Adaptable grid system, ensuring your form looks great on all devices.
|
|
34
|
+
|
|
35
|
+
### Usage
|
|
36
|
+
|
|
37
|
+
#### Props
|
|
38
|
+
|
|
39
|
+
| Name | Description |
|
|
40
|
+
| -------------- | -------------------------------------------------------- |
|
|
41
|
+
| `fieldsGroups` | 2D array representing groups of fields in the form. |
|
|
42
|
+
| `onSubmit` | Function called upon form submission. |
|
|
43
|
+
| `formControl` | Control object from `react-hook-form`. |
|
|
44
|
+
| `submitButton` | Boolean to toggle the visibility of the submit button. |
|
|
45
|
+
| `otherProps` | Any additional props to pass down to the form component. |
|
|
46
|
+
|
|
47
|
+
### Example
|
|
48
|
+
|
|
49
|
+
Here's a simple example showcasing how to implement a `CustomForm`:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import React from "react"
|
|
53
|
+
import { CustomForm } from "your-package-name"
|
|
54
|
+
|
|
55
|
+
const MyComponent = () => {
|
|
56
|
+
const formControl = useForm()
|
|
57
|
+
|
|
58
|
+
const fieldsGroups = [
|
|
59
|
+
[
|
|
60
|
+
{
|
|
61
|
+
label: "Username",
|
|
62
|
+
name: "username",
|
|
63
|
+
type: "text",
|
|
64
|
+
required: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: "Birthdate",
|
|
68
|
+
name: "birthdate",
|
|
69
|
+
type: "date",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const handleSubmit = (data) => {
|
|
75
|
+
console.log(data)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<CustomForm
|
|
80
|
+
fieldsGroups={fieldsGroups}
|
|
81
|
+
onSubmit={handleSubmit}
|
|
82
|
+
formControl={formControl}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default MyComponent
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Contribution
|
|
91
|
+
|
|
92
|
+
Your contributions are always welcome! Please create a pull request and we'll review your suggestion.
|
|
93
|
+
|
|
94
|
+
---
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
FormControl,
|
|
4
|
+
FormHelperText,
|
|
5
|
+
FormLabel,
|
|
6
|
+
Grid,
|
|
7
|
+
InputLabel,
|
|
8
|
+
MenuItem,
|
|
9
|
+
Select,
|
|
10
|
+
Stack,
|
|
11
|
+
TextField,
|
|
12
|
+
} from "@mui/material"
|
|
13
|
+
import { DatePicker } from "@mui/x-date-pickers"
|
|
14
|
+
import { Controller } from "react-hook-form"
|
|
15
|
+
import { useForm } from "react-hook-form"
|
|
16
|
+
import React from "react"
|
|
17
|
+
|
|
18
|
+
export type $option<T = any> = {
|
|
19
|
+
icon?: React.ReactNode
|
|
20
|
+
label: string
|
|
21
|
+
value: T
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ICustomField<T = string> {
|
|
25
|
+
label: string
|
|
26
|
+
name: T extends string ? string : keyof T
|
|
27
|
+
type: "text" | "number" | "single-select" | "multi-select" | "date" | "file"
|
|
28
|
+
list?: $option[]
|
|
29
|
+
required?: boolean // This can be further refined to your validation rules
|
|
30
|
+
otherProps?: any
|
|
31
|
+
span?: number // A number between 1 and 12, representing MUI's grid system
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ICustomForm {
|
|
35
|
+
fieldsGroups: ICustomField<any>[][]
|
|
36
|
+
onSubmit: ReturnType<this["formControl"]["handleSubmit"]>
|
|
37
|
+
formControl: ReturnType<typeof useForm>
|
|
38
|
+
submitButton?: boolean
|
|
39
|
+
otherProps?: any
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const CustomForm: React.FC<ICustomForm> = ({
|
|
43
|
+
fieldsGroups,
|
|
44
|
+
onSubmit,
|
|
45
|
+
formControl,
|
|
46
|
+
submitButton = true,
|
|
47
|
+
otherProps,
|
|
48
|
+
}) => {
|
|
49
|
+
const { control, setValue } = formControl
|
|
50
|
+
|
|
51
|
+
const renderField = (field: ICustomField<string>) => {
|
|
52
|
+
switch (field.type) {
|
|
53
|
+
case "text":
|
|
54
|
+
case "number":
|
|
55
|
+
return (
|
|
56
|
+
<Controller
|
|
57
|
+
name={field.name}
|
|
58
|
+
control={control}
|
|
59
|
+
render={({ field: controlField, fieldState: { error } }) => (
|
|
60
|
+
<TextField
|
|
61
|
+
{...controlField}
|
|
62
|
+
{...field.otherProps}
|
|
63
|
+
label={field.label}
|
|
64
|
+
type={field.type}
|
|
65
|
+
fullWidth
|
|
66
|
+
required={field.required}
|
|
67
|
+
error={!!error}
|
|
68
|
+
helperText={error?.message}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
case "single-select":
|
|
74
|
+
case "multi-select":
|
|
75
|
+
return (
|
|
76
|
+
<Controller
|
|
77
|
+
name={field.name}
|
|
78
|
+
control={control}
|
|
79
|
+
render={({ field: controlField, fieldState: { error } }) => (
|
|
80
|
+
<FormControl fullWidth error={!!error}>
|
|
81
|
+
<InputLabel required={field.required}>{field.label}</InputLabel>
|
|
82
|
+
<Select
|
|
83
|
+
{...controlField}
|
|
84
|
+
{...field.otherProps}
|
|
85
|
+
multiple={field.type === "multi-select"}>
|
|
86
|
+
{field.list?.map((item) => (
|
|
87
|
+
<MenuItem key={item.value} value={item.value}>
|
|
88
|
+
{item.label}
|
|
89
|
+
</MenuItem>
|
|
90
|
+
))}
|
|
91
|
+
</Select>
|
|
92
|
+
{error && <FormHelperText>{error.message}</FormHelperText>}
|
|
93
|
+
</FormControl>
|
|
94
|
+
)}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
case "date":
|
|
98
|
+
return (
|
|
99
|
+
<Controller
|
|
100
|
+
name={field.name}
|
|
101
|
+
control={control}
|
|
102
|
+
render={({ field: controlField, fieldState: { error } }) => (
|
|
103
|
+
<DatePicker
|
|
104
|
+
{...controlField}
|
|
105
|
+
{...field.otherProps}
|
|
106
|
+
label={field.label}
|
|
107
|
+
onChange={controlField.onChange}
|
|
108
|
+
slotProps={{
|
|
109
|
+
textField: {
|
|
110
|
+
required: field.required,
|
|
111
|
+
error: !!error,
|
|
112
|
+
helperText: error?.message,
|
|
113
|
+
fullWidth: true,
|
|
114
|
+
},
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
case "file":
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<FormLabel component="legend">{field.label}</FormLabel>
|
|
124
|
+
<Button variant="contained" component="label">
|
|
125
|
+
Upload File
|
|
126
|
+
<input
|
|
127
|
+
type="file"
|
|
128
|
+
hidden
|
|
129
|
+
onChange={(e) => {
|
|
130
|
+
// Set the value to either the File instance or undefined
|
|
131
|
+
const fileValue =
|
|
132
|
+
e.target.files && e.target.files.length > 0
|
|
133
|
+
? e.target.files[0]
|
|
134
|
+
: undefined
|
|
135
|
+
setValue(field.name, fileValue)
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
</Button>
|
|
139
|
+
</>
|
|
140
|
+
)
|
|
141
|
+
default:
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<Stack
|
|
148
|
+
component="form"
|
|
149
|
+
onSubmit={onSubmit}
|
|
150
|
+
noValidate
|
|
151
|
+
{...otherProps}
|
|
152
|
+
spacing={3}>
|
|
153
|
+
<Grid container spacing={1}>
|
|
154
|
+
{fieldsGroups.map((fields, rowIndex) => (
|
|
155
|
+
<Grid container item key={rowIndex} spacing={2}>
|
|
156
|
+
{fields.map((field, fieldIndex) => (
|
|
157
|
+
<Grid item key={fieldIndex} xs={field.span || true}>
|
|
158
|
+
{renderField(field as unknown as ICustomField<string>)}
|
|
159
|
+
</Grid>
|
|
160
|
+
))}
|
|
161
|
+
</Grid>
|
|
162
|
+
))}
|
|
163
|
+
</Grid>
|
|
164
|
+
{submitButton && (
|
|
165
|
+
<Button type="submit" variant="contained">
|
|
166
|
+
Submit
|
|
167
|
+
</Button>
|
|
168
|
+
)}
|
|
169
|
+
</Stack>
|
|
170
|
+
)
|
|
171
|
+
}
|
package/tsconfig.json
ADDED