react-smart-fields 1.0.1 → 1.0.2
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/package.json +1 -1
- package/src/components/DynamicFields.tsx +259 -0
- package/src/index.ts +2 -2
- package/src/components/DynamicField.tsx +0 -193
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-smart-fields",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"author": "Pratik Panchal",
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export interface FieldOption {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number | boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface FieldConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
type: "text" | "number" | "select" | "radio" | "checkbox";
|
|
12
|
+
options?: FieldOption[];
|
|
13
|
+
required?: boolean;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DynamicFieldsProps {
|
|
19
|
+
fields: FieldConfig[];
|
|
20
|
+
onChange: (data: Record<string, any>) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
inputClassName?: string;
|
|
23
|
+
labelClassName?: string;
|
|
24
|
+
mainFieldClassName?: string;
|
|
25
|
+
fieldClassName?: string;
|
|
26
|
+
errorClassName?: string;
|
|
27
|
+
selectClassName?: string;
|
|
28
|
+
optionClassName?: string;
|
|
29
|
+
checkboxClassName?: string;
|
|
30
|
+
radioClassName?: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const DynamicFields: React.FC<DynamicFieldsProps> = ({
|
|
36
|
+
fields,
|
|
37
|
+
onChange,
|
|
38
|
+
className = "",
|
|
39
|
+
inputClassName = "",
|
|
40
|
+
labelClassName = "",
|
|
41
|
+
mainFieldClassName = "",
|
|
42
|
+
fieldClassName = "",
|
|
43
|
+
errorClassName = "",
|
|
44
|
+
selectClassName = "",
|
|
45
|
+
optionClassName = "",
|
|
46
|
+
checkboxClassName = "",
|
|
47
|
+
radioClassName = "",
|
|
48
|
+
title,
|
|
49
|
+
description
|
|
50
|
+
}) => {
|
|
51
|
+
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
52
|
+
const [formErrors, setFormErrors] = useState<Record<string, string>>({});
|
|
53
|
+
const [openDropdowns, setOpenDropdowns] = useState<Record<string, boolean>>({});
|
|
54
|
+
const dropdownRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
|
55
|
+
|
|
56
|
+
// Consistent styling for all components with dark mode support
|
|
57
|
+
|
|
58
|
+
const defaultInputClassName = "w-full rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 px-4 py-2 text-sm text-gray-800 dark:text-gray-200 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 shadow-sm transition-colors";
|
|
59
|
+
|
|
60
|
+
const defaultLabelClassName = "flex text-sm font-medium text-gray-800 dark:text-gray-200 leading-none mb-2";
|
|
61
|
+
|
|
62
|
+
const defaultFieldClassName = "w-full";
|
|
63
|
+
|
|
64
|
+
const defaultErrorClassName = "text-sm font-medium text-red-500 dark:text-red-400";
|
|
65
|
+
|
|
66
|
+
const defaultSelectClassName = "w-full rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 cursor-pointer shadow-sm";
|
|
67
|
+
|
|
68
|
+
const defaultCheckboxClassName = "h-4 w-4 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 shadow-sm focus:outline-none transition-colors checked:bg-blue-500 dark:checked:bg-blue-400 checked:border-blue-500 dark:checked:border-blue-400";
|
|
69
|
+
|
|
70
|
+
const defaultRadioClassName = "select-none h-4 w-4 rounded-full border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 shadow-sm focus:outline-none transition-colors checked:bg-blue-500 dark:checked:bg-blue-400 checked:border-blue-500 dark:checked:border-blue-400";
|
|
71
|
+
|
|
72
|
+
const defaultOptionClassName = "py-2 px-4 text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer first:rounded-t-lg last:rounded-b-lg transition-colors";
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const initialData: Record<string, any> = {};
|
|
76
|
+
fields.forEach((f) => {
|
|
77
|
+
initialData[f.name] = f.type === "checkbox" ? false : "";
|
|
78
|
+
});
|
|
79
|
+
setFormData(initialData);
|
|
80
|
+
}, [fields]);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
84
|
+
Object.keys(dropdownRefs.current).forEach((fieldName) => {
|
|
85
|
+
const ref = dropdownRefs.current[fieldName];
|
|
86
|
+
if (ref && !ref.contains(event.target as Node)) {
|
|
87
|
+
setOpenDropdowns(prev => ({ ...prev, [fieldName]: false }));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
93
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const toggleDropdown = (fieldName: string) => {
|
|
97
|
+
setOpenDropdowns(prev => ({ ...prev, [fieldName]: !prev[fieldName] }));
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const selectOption = (fieldName: string, value: any) => {
|
|
101
|
+
handleChange(fieldName, value);
|
|
102
|
+
setOpenDropdowns(prev => ({ ...prev, [fieldName]: false }));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleChange = (name: string, value: any) => {
|
|
106
|
+
const updatedData = { ...formData, [name]: value };
|
|
107
|
+
setFormData(updatedData);
|
|
108
|
+
onChange(updatedData);
|
|
109
|
+
|
|
110
|
+
const field = fields.find((f) => f.name === name);
|
|
111
|
+
if (field?.required && (value === "" || value === null || value === undefined)) {
|
|
112
|
+
setFormErrors((prev) => ({ ...prev, [name]: "This field is required" }));
|
|
113
|
+
} else {
|
|
114
|
+
setFormErrors((prev) => {
|
|
115
|
+
const newErrors = { ...prev };
|
|
116
|
+
delete newErrors[name];
|
|
117
|
+
return newErrors;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const renderField = (field: FieldConfig) => {
|
|
123
|
+
const error = formErrors[field.name];
|
|
124
|
+
const finalInputClassName = `${defaultInputClassName} ${inputClassName || ""}`.trim();
|
|
125
|
+
const finalLabelClassName = `${defaultLabelClassName} ${labelClassName || ""}`.trim();
|
|
126
|
+
const finalFieldClassName = `${defaultFieldClassName} ${fieldClassName || ""}`.trim();
|
|
127
|
+
const finalErrorClassName = `${defaultErrorClassName} ${errorClassName || ""}`.trim();
|
|
128
|
+
const finalSelectClassName = `${defaultSelectClassName} ${selectClassName || ""}`.trim();
|
|
129
|
+
const finalCheckboxClassName = `${defaultCheckboxClassName} ${checkboxClassName || ""}`.trim();
|
|
130
|
+
const finalRadioClassName = `${defaultRadioClassName} ${radioClassName || ""}`.trim();
|
|
131
|
+
const finalOptionClassName = `${defaultOptionClassName} ${optionClassName || ""}`.trim();
|
|
132
|
+
const inputClassWithError = `${finalInputClassName} ${error ? "border-red-500 dark:border-red-400 focus:ring-red-500 dark:focus:ring-red-400 focus:border-red-500 dark:focus:border-red-400" : ""}`;
|
|
133
|
+
const selectClassWithError = `${finalSelectClassName} ${error ? "border-red-500 dark:border-red-400 focus:ring-red-500 dark:focus:ring-red-400 focus:border-red-500 dark:focus:border-red-400" : ""}`;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div key={field.name} className={finalFieldClassName}>
|
|
137
|
+
{field.label && field.type !== "checkbox" && (
|
|
138
|
+
<label htmlFor={field.name} className={finalLabelClassName}>
|
|
139
|
+
{field.label}
|
|
140
|
+
{field.required && <span className="text-red-500 dark:text-red-400 ml-1">*</span>}
|
|
141
|
+
</label>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{field.description && field.type !== "checkbox" && (
|
|
145
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">{field.description}</p>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{field.type === "select" && field.options ? (
|
|
149
|
+
<div
|
|
150
|
+
ref={el => { dropdownRefs.current[field.name] = el; }}
|
|
151
|
+
className="relative"
|
|
152
|
+
>
|
|
153
|
+
<div
|
|
154
|
+
className={`${selectClassWithError} flex items-center justify-between`}
|
|
155
|
+
onClick={() => toggleDropdown(field.name)}
|
|
156
|
+
>
|
|
157
|
+
<span className={formData[field.name] ? "text-gray-800 dark:text-gray-200" : "text-gray-500 dark:text-gray-400"}>
|
|
158
|
+
{formData[field.name]
|
|
159
|
+
? field.options.find(opt => String(opt.value) === String(formData[field.name]))?.label
|
|
160
|
+
: field.placeholder || "Select an option..."
|
|
161
|
+
}
|
|
162
|
+
</span>
|
|
163
|
+
<svg
|
|
164
|
+
className={`w-4 h-4 text-gray-400 dark:text-gray-500 transform transition-transform ${openDropdowns[field.name] ? 'rotate-180' : ''}`}
|
|
165
|
+
fill="none"
|
|
166
|
+
stroke="currentColor"
|
|
167
|
+
viewBox="0 0 24 24"
|
|
168
|
+
>
|
|
169
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
170
|
+
</svg>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
{openDropdowns[field.name] && (
|
|
174
|
+
<div className="absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg dark:shadow-2xl overflow-hidden">
|
|
175
|
+
{field.options.map((opt, index) => (
|
|
176
|
+
<div
|
|
177
|
+
key={String(opt.value)}
|
|
178
|
+
className={`${finalOptionClassName} ${index === 0 ? 'rounded-t-lg' : ''} ${index === field.options!.length - 1 ? 'rounded-b-lg' : ''}`}
|
|
179
|
+
onClick={() => selectOption(field.name, opt.value)}
|
|
180
|
+
>
|
|
181
|
+
{opt.label}
|
|
182
|
+
</div>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
) : field.type === "radio" && field.options ? (
|
|
188
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
189
|
+
{field.options.map((opt) => (
|
|
190
|
+
<div key={String(opt.value)} className="flex items-center space-x-2">
|
|
191
|
+
<input
|
|
192
|
+
type="radio"
|
|
193
|
+
id={`${field.name}-${opt.value}`}
|
|
194
|
+
name={field.name}
|
|
195
|
+
value={String(opt.value)}
|
|
196
|
+
checked={formData[field.name] === opt.value}
|
|
197
|
+
onChange={() => handleChange(field.name, opt.value)}
|
|
198
|
+
className={finalRadioClassName}
|
|
199
|
+
/>
|
|
200
|
+
<label
|
|
201
|
+
htmlFor={`${field.name}-${opt.value}`}
|
|
202
|
+
className="select-none text-sm font-medium text-gray-800 dark:text-gray-200 leading-none cursor-pointer"
|
|
203
|
+
>
|
|
204
|
+
{opt.label}
|
|
205
|
+
</label>
|
|
206
|
+
</div>
|
|
207
|
+
))}
|
|
208
|
+
</div>
|
|
209
|
+
) : field.type === "checkbox" ? (
|
|
210
|
+
<div className="flex items-center space-x-2">
|
|
211
|
+
<input
|
|
212
|
+
type="checkbox"
|
|
213
|
+
id={field.name}
|
|
214
|
+
checked={formData[field.name] || false}
|
|
215
|
+
onChange={(e) => handleChange(field.name, e.target.checked)}
|
|
216
|
+
className={finalCheckboxClassName}
|
|
217
|
+
/>
|
|
218
|
+
<label htmlFor={field.name} className="text-sm font-medium text-gray-800 dark:text-gray-200 leading-none cursor-pointer">
|
|
219
|
+
{field.label}
|
|
220
|
+
{field.required && <span className="text-red-500 dark:text-red-400 ml-1">*</span>}
|
|
221
|
+
</label>
|
|
222
|
+
{field.description && (
|
|
223
|
+
<p className="text-sm text-gray-500 dark:text-gray-400 ml-6">{field.description}</p>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
) : (
|
|
227
|
+
<input
|
|
228
|
+
type={field.type}
|
|
229
|
+
id={field.name}
|
|
230
|
+
name={field.name}
|
|
231
|
+
placeholder={field.placeholder}
|
|
232
|
+
className={inputClassWithError}
|
|
233
|
+
value={formData[field.name] ?? ""}
|
|
234
|
+
onChange={(e) => handleChange(field.name, field.type === "number" ? Number(e.target.value) || "" : e.target.value)}
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{error && <p className={finalErrorClassName}>{error}</p>}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<div className={`w-full max-w-2xl mx-auto p-6 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm dark:shadow-2xl ${className}`}>
|
|
245
|
+
{title && (
|
|
246
|
+
<div>
|
|
247
|
+
<h2 className="text-2xl font-semibold text-gray-800 dark:text-gray-100 tracking-tight">{title}</h2>
|
|
248
|
+
{description && (
|
|
249
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">{description}</p>
|
|
250
|
+
)}
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
<div className={`space-y-2 ${mainFieldClassName}`}>
|
|
255
|
+
{fields.map(renderField)}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DynamicFields } from "./components/
|
|
2
|
-
export type { FieldConfig, FieldOption } from "./components/
|
|
1
|
+
export { DynamicFields } from "./components/DynamicFields";
|
|
2
|
+
export type { FieldConfig, FieldOption } from "./components/DynamicFields";
|
|
@@ -1,193 +0,0 @@
|
|
|
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[];
|
|
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
|
-
const defaultInputClass =
|
|
79
|
-
"padding: 0.6rem 1rem; border: 1px solid #ccc; border-radius: 8px; width: 100%; font-size: 1rem; transition: border-color 0.2s ease-in-out; outline: none;";
|
|
80
|
-
const defaultInputFocus =
|
|
81
|
-
"focus: border-color: #007BFF;";
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<>
|
|
85
|
-
{fields.map((field) => (
|
|
86
|
-
<div key={field.name} style={{ marginBottom: "1.25rem" }}>
|
|
87
|
-
{field.label && (
|
|
88
|
-
<label
|
|
89
|
-
htmlFor={field.name}
|
|
90
|
-
style={{ display: "block", marginBottom: "0.5rem", fontWeight: 600 }}
|
|
91
|
-
>
|
|
92
|
-
{field.label}
|
|
93
|
-
</label>
|
|
94
|
-
)}
|
|
95
|
-
|
|
96
|
-
{/* Select Dropdown */}
|
|
97
|
-
{field.type === "select" && (
|
|
98
|
-
<select
|
|
99
|
-
name={field.name}
|
|
100
|
-
className={field.className}
|
|
101
|
-
style={{
|
|
102
|
-
...field.style,
|
|
103
|
-
...{
|
|
104
|
-
padding: "0.6rem 1rem",
|
|
105
|
-
border: "1px solid #ccc",
|
|
106
|
-
borderRadius: "8px",
|
|
107
|
-
width: "100%",
|
|
108
|
-
fontSize: "1rem",
|
|
109
|
-
},
|
|
110
|
-
}}
|
|
111
|
-
value={formData[field.name]}
|
|
112
|
-
onChange={(e) =>
|
|
113
|
-
handleChange(field.name, e.target.value, field)
|
|
114
|
-
}
|
|
115
|
-
>
|
|
116
|
-
<option value="">Select an option</option>
|
|
117
|
-
{field.options?.map((opt) => (
|
|
118
|
-
<option key={String(opt.value)} value={String(opt.value)}>
|
|
119
|
-
{opt.label}
|
|
120
|
-
</option>
|
|
121
|
-
))}
|
|
122
|
-
</select>
|
|
123
|
-
)}
|
|
124
|
-
|
|
125
|
-
{/* Radio Buttons */}
|
|
126
|
-
{field.type === "radio" && (
|
|
127
|
-
<div style={{ display: "flex", gap: "1rem" }}>
|
|
128
|
-
{field.options?.map((opt) => (
|
|
129
|
-
<label key={String(opt.value)} style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
|
|
130
|
-
<input
|
|
131
|
-
type="radio"
|
|
132
|
-
name={field.name}
|
|
133
|
-
value={String(opt.value)}
|
|
134
|
-
checked={formData[field.name] === opt.value}
|
|
135
|
-
onChange={() => handleChange(field.name, opt.value, field)}
|
|
136
|
-
/>
|
|
137
|
-
{opt.label}
|
|
138
|
-
</label>
|
|
139
|
-
))}
|
|
140
|
-
</div>
|
|
141
|
-
)}
|
|
142
|
-
|
|
143
|
-
{/* Checkbox */}
|
|
144
|
-
{field.type === "checkbox" && (
|
|
145
|
-
<label style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
|
146
|
-
<input
|
|
147
|
-
type="checkbox"
|
|
148
|
-
name={field.name}
|
|
149
|
-
checked={formData[field.name]}
|
|
150
|
-
className={field.className}
|
|
151
|
-
style={field.style}
|
|
152
|
-
onChange={(e) =>
|
|
153
|
-
handleChange(field.name, e.target.checked, field)
|
|
154
|
-
}
|
|
155
|
-
/>
|
|
156
|
-
{field.label}
|
|
157
|
-
</label>
|
|
158
|
-
)}
|
|
159
|
-
|
|
160
|
-
{/* Text, Email, Number, etc. */}
|
|
161
|
-
{!["select", "radio", "checkbox"].includes(field.type) && (
|
|
162
|
-
<input
|
|
163
|
-
type={field.type}
|
|
164
|
-
name={field.name}
|
|
165
|
-
value={formData[field.name]}
|
|
166
|
-
className={field.className}
|
|
167
|
-
style={{
|
|
168
|
-
...{
|
|
169
|
-
padding: "0.6rem 1rem",
|
|
170
|
-
border: "1px solid #ccc",
|
|
171
|
-
borderRadius: "8px",
|
|
172
|
-
width: "100%",
|
|
173
|
-
fontSize: "1rem",
|
|
174
|
-
},
|
|
175
|
-
...field.style,
|
|
176
|
-
}}
|
|
177
|
-
onChange={(e) =>
|
|
178
|
-
handleChange(field.name, e.target.value, field)
|
|
179
|
-
}
|
|
180
|
-
/>
|
|
181
|
-
)}
|
|
182
|
-
|
|
183
|
-
{/* Error Display */}
|
|
184
|
-
{errors[field.name] && (
|
|
185
|
-
<p style={{ color: "red", marginTop: "0.4rem", fontSize: "0.9rem" }}>
|
|
186
|
-
{errors[field.name]}
|
|
187
|
-
</p>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
))}
|
|
191
|
-
</>
|
|
192
|
-
);
|
|
193
|
-
};
|