react-smart-fields 1.0.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 +23 -0
- package/dist/components/DynamicField.d.ts +27 -0
- package/dist/components/DynamicField.js +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +26 -0
- package/src/components/DynamicField.tsx +163 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 🧩 react-dynamic-fields
|
|
2
|
+
|
|
3
|
+
> A flexible, customizable, and developer-friendly component to generate dynamic form fields in React. Supports all HTML inputs, validation, and styling out of the box.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Features
|
|
8
|
+
|
|
9
|
+
✅ Supports all HTML field types (text, number, select, radio, checkbox, etc.)
|
|
10
|
+
✅ Real-time error validation (required & custom)
|
|
11
|
+
✅ Style customization with `style` or `className`
|
|
12
|
+
✅ Minimal setup – just pass a `fields` config
|
|
13
|
+
✅ React 18+ support
|
|
14
|
+
✅ TypeScript ready
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react-dynamic-fields
|
|
22
|
+
# or
|
|
23
|
+
yarn add react-dynamic-fields
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type FieldType = "text" | "number" | "email" | "password" | "date" | "checkbox" | "radio" | "select";
|
|
3
|
+
export interface FieldOption {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number | boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface FieldConfig {
|
|
8
|
+
name: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
type: FieldType;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
defaultValue?: any;
|
|
13
|
+
customValidation?: (value: any) => string | null;
|
|
14
|
+
options?: FieldOption[];
|
|
15
|
+
style?: React.CSSProperties;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
interface DynamicFieldsProps {
|
|
19
|
+
fields: FieldConfig[];
|
|
20
|
+
onChange?: (formData: {
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
}, errors: {
|
|
23
|
+
[key: string]: string | null;
|
|
24
|
+
}) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare const DynamicFields: React.FC<DynamicFieldsProps>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
export const DynamicFields = ({ fields, onChange, }) => {
|
|
3
|
+
const [formData, setFormData] = useState(() => {
|
|
4
|
+
const initial = {};
|
|
5
|
+
fields.forEach((f) => {
|
|
6
|
+
initial[f.name] =
|
|
7
|
+
f.type === "checkbox"
|
|
8
|
+
? f.defaultValue ?? false
|
|
9
|
+
: f.defaultValue ?? "";
|
|
10
|
+
});
|
|
11
|
+
return initial;
|
|
12
|
+
});
|
|
13
|
+
const [errors, setErrors] = useState({});
|
|
14
|
+
const validateField = (field, value) => {
|
|
15
|
+
if (field.required && (value === "" || value === null || value === undefined || value === false)) {
|
|
16
|
+
return "This field is required";
|
|
17
|
+
}
|
|
18
|
+
if (field.customValidation)
|
|
19
|
+
return field.customValidation(value);
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
const handleChange = (name, value, field) => {
|
|
23
|
+
const newFormData = { ...formData, [name]: value };
|
|
24
|
+
const error = validateField(field, value);
|
|
25
|
+
const newErrors = { ...errors, [name]: error };
|
|
26
|
+
setFormData(newFormData);
|
|
27
|
+
setErrors(newErrors);
|
|
28
|
+
if (onChange)
|
|
29
|
+
onChange(newFormData, newErrors);
|
|
30
|
+
};
|
|
31
|
+
return (React.createElement(React.Fragment, null, fields.map((field) => (React.createElement("div", { key: field.name, style: { marginBottom: "1rem" } },
|
|
32
|
+
field.label && (React.createElement("label", { htmlFor: field.name, style: { display: "block" } }, field.label)),
|
|
33
|
+
field.type === "select" && (React.createElement("select", { name: field.name, className: field.className, style: field.style, value: formData[field.name], onChange: (e) => handleChange(field.name, e.target.value, field) },
|
|
34
|
+
React.createElement("option", { value: "" }, "Select an option"),
|
|
35
|
+
field.options?.map((opt) => (React.createElement("option", { key: String(opt.value), value: String(opt.value) }, opt.label))))),
|
|
36
|
+
field.type === "radio" &&
|
|
37
|
+
field.options?.map((opt) => (React.createElement("label", { key: String(opt.value), style: { marginRight: "1rem" } },
|
|
38
|
+
React.createElement("input", { type: "radio", name: field.name, value: String(opt.value), checked: formData[field.name] === opt.value, onChange: () => handleChange(field.name, opt.value, field) }),
|
|
39
|
+
" ",
|
|
40
|
+
opt.label))),
|
|
41
|
+
field.type === "checkbox" && (React.createElement("input", { type: "checkbox", name: field.name, checked: formData[field.name], className: field.className, style: field.style, onChange: (e) => handleChange(field.name, e.target.checked, field) })),
|
|
42
|
+
!["select", "radio", "checkbox"].includes(field.type) && (React.createElement("input", { type: field.type, name: field.name, value: formData[field.name], className: field.className, style: field.style, onChange: (e) => handleChange(field.name, e.target.value, field) })),
|
|
43
|
+
errors[field.name] && (React.createElement("p", { style: { color: "red", marginTop: "0.25rem" } }, errors[field.name])))))));
|
|
44
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DynamicFields } from "./components/DynamicField";
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-smart-fields",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "> A flexible, customizable, and developer-friendly component to generate dynamic form fields in React. Supports all HTML inputs, validation, and styling out of the box.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Pratik Panchal",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"csstype": "^3.1.3",
|
|
15
|
+
"js-tokens": "^4.0.0",
|
|
16
|
+
"loose-envify": "^1.4.0",
|
|
17
|
+
"react": "^18.3.1"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.0.0",
|
|
21
|
+
"@types/react": "^18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": "^18.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
type FieldType =
|
|
4
|
+
| "text"
|
|
5
|
+
| "number"
|
|
6
|
+
| "email"
|
|
7
|
+
| "password"
|
|
8
|
+
| "date"
|
|
9
|
+
| "checkbox"
|
|
10
|
+
| "radio"
|
|
11
|
+
| "select";
|
|
12
|
+
|
|
13
|
+
export interface FieldOption {
|
|
14
|
+
label: string;
|
|
15
|
+
value: string | number | boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface FieldConfig {
|
|
19
|
+
name: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
type: FieldType;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
defaultValue?: any;
|
|
24
|
+
customValidation?: (value: any) => string | null;
|
|
25
|
+
options?: FieldOption[]; // for select & radio
|
|
26
|
+
style?: React.CSSProperties;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DynamicFieldsProps {
|
|
31
|
+
fields: FieldConfig[];
|
|
32
|
+
onChange?: (
|
|
33
|
+
formData: { [key: string]: any },
|
|
34
|
+
errors: { [key: string]: string | null }
|
|
35
|
+
) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const DynamicFields: React.FC<DynamicFieldsProps> = ({
|
|
39
|
+
fields,
|
|
40
|
+
onChange,
|
|
41
|
+
}) => {
|
|
42
|
+
const [formData, setFormData] = useState<{ [key: string]: any }>(() => {
|
|
43
|
+
const initial: any = {};
|
|
44
|
+
fields.forEach((f) => {
|
|
45
|
+
initial[f.name] =
|
|
46
|
+
f.type === "checkbox"
|
|
47
|
+
? f.defaultValue ?? false
|
|
48
|
+
: f.defaultValue ?? "";
|
|
49
|
+
});
|
|
50
|
+
return initial;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const [errors, setErrors] = useState<{ [key: string]: string | null }>({});
|
|
54
|
+
|
|
55
|
+
const validateField = (field: FieldConfig, value: any) => {
|
|
56
|
+
if (field.required && (value === "" || value === null || value === undefined || value === false)) {
|
|
57
|
+
return "This field is required";
|
|
58
|
+
}
|
|
59
|
+
if (field.customValidation) return field.customValidation(value);
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleChange = (
|
|
64
|
+
name: string,
|
|
65
|
+
value: any,
|
|
66
|
+
field: FieldConfig
|
|
67
|
+
) => {
|
|
68
|
+
const newFormData = { ...formData, [name]: value };
|
|
69
|
+
const error = validateField(field, value);
|
|
70
|
+
const newErrors = { ...errors, [name]: error };
|
|
71
|
+
|
|
72
|
+
setFormData(newFormData);
|
|
73
|
+
setErrors(newErrors);
|
|
74
|
+
|
|
75
|
+
if (onChange) onChange(newFormData, newErrors);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
{fields.map((field) => (
|
|
81
|
+
<div key={field.name} style={{ marginBottom: "1rem" }}>
|
|
82
|
+
{field.label && (
|
|
83
|
+
<label htmlFor={field.name} style={{ display: "block" }}>
|
|
84
|
+
{field.label}
|
|
85
|
+
</label>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{/* Select Dropdown */}
|
|
89
|
+
{field.type === "select" && (
|
|
90
|
+
<select
|
|
91
|
+
name={field.name}
|
|
92
|
+
className={field.className}
|
|
93
|
+
style={field.style}
|
|
94
|
+
value={formData[field.name]}
|
|
95
|
+
onChange={(e) =>
|
|
96
|
+
handleChange(field.name, e.target.value, field)
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
<option value="">Select an option</option>
|
|
100
|
+
{field.options?.map((opt) => (
|
|
101
|
+
<option key={String(opt.value)} value={String(opt.value)}>
|
|
102
|
+
{opt.label}
|
|
103
|
+
</option>
|
|
104
|
+
))}
|
|
105
|
+
</select>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Radio Buttons */}
|
|
109
|
+
{field.type === "radio" &&
|
|
110
|
+
field.options?.map((opt) => (
|
|
111
|
+
<label key={String(opt.value)} style={{ marginRight: "1rem" }}>
|
|
112
|
+
<input
|
|
113
|
+
type="radio"
|
|
114
|
+
name={field.name}
|
|
115
|
+
value={String(opt.value)}
|
|
116
|
+
checked={formData[field.name] === opt.value}
|
|
117
|
+
onChange={() =>
|
|
118
|
+
handleChange(field.name, opt.value, field)
|
|
119
|
+
}
|
|
120
|
+
/>{" "}
|
|
121
|
+
{opt.label}
|
|
122
|
+
</label>
|
|
123
|
+
))}
|
|
124
|
+
|
|
125
|
+
{/* Checkbox */}
|
|
126
|
+
{field.type === "checkbox" && (
|
|
127
|
+
<input
|
|
128
|
+
type="checkbox"
|
|
129
|
+
name={field.name}
|
|
130
|
+
checked={formData[field.name]}
|
|
131
|
+
className={field.className}
|
|
132
|
+
style={field.style}
|
|
133
|
+
onChange={(e) =>
|
|
134
|
+
handleChange(field.name, e.target.checked, field)
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{/* Other Inputs (text, email, number, etc.) */}
|
|
140
|
+
{!["select", "radio", "checkbox"].includes(field.type) && (
|
|
141
|
+
<input
|
|
142
|
+
type={field.type}
|
|
143
|
+
name={field.name}
|
|
144
|
+
value={formData[field.name]}
|
|
145
|
+
className={field.className}
|
|
146
|
+
style={field.style}
|
|
147
|
+
onChange={(e) =>
|
|
148
|
+
handleChange(field.name, e.target.value, field)
|
|
149
|
+
}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Error Display */}
|
|
154
|
+
{errors[field.name] && (
|
|
155
|
+
<p style={{ color: "red", marginTop: "0.25rem" }}>
|
|
156
|
+
{errors[field.name]}
|
|
157
|
+
</p>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
))}
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
};
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED