react-smart-fields 1.1.6 → 2.2.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 +218 -129
- package/dist/index.cjs +595 -0
- package/dist/index.d.cts +129 -0
- package/dist/index.d.ts +129 -2
- package/dist/index.js +558 -1
- package/package.json +25 -14
- package/.gitattributes +0 -2
- package/dist/components/DynamicFields.d.ts +0 -31
- package/dist/components/DynamicFields.js +0 -97
- package/src/components/DynamicFields.tsx +0 -259
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -13
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
-
export const DynamicFields = ({ fields, onChange, className = "", inputClassName = "", labelClassName = "", mainFieldClassName = "", fieldClassName = "", errorClassName = "", selectClassName = "", optionClassName = "", checkboxClassName = "", radioClassName = "", title, description }) => {
|
|
3
|
-
const [formData, setFormData] = useState({});
|
|
4
|
-
const [formErrors, setFormErrors] = useState({});
|
|
5
|
-
const [openDropdowns, setOpenDropdowns] = useState({});
|
|
6
|
-
const dropdownRefs = useRef({});
|
|
7
|
-
// Consistent styling for all components with dark mode support
|
|
8
|
-
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";
|
|
9
|
-
const defaultLabelClassName = "flex text-sm font-medium text-gray-800 dark:text-gray-200 leading-none mb-2";
|
|
10
|
-
const defaultFieldClassName = "w-full";
|
|
11
|
-
const defaultErrorClassName = "text-sm font-medium text-red-500 dark:text-red-400";
|
|
12
|
-
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";
|
|
13
|
-
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";
|
|
14
|
-
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";
|
|
15
|
-
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";
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const initialData = {};
|
|
18
|
-
fields.forEach((f) => {
|
|
19
|
-
initialData[f.name] = f.type === "checkbox" ? false : "";
|
|
20
|
-
});
|
|
21
|
-
setFormData(initialData);
|
|
22
|
-
}, [fields]);
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const handleClickOutside = (event) => {
|
|
25
|
-
Object.keys(dropdownRefs.current).forEach((fieldName) => {
|
|
26
|
-
const ref = dropdownRefs.current[fieldName];
|
|
27
|
-
if (ref && !ref.contains(event.target)) {
|
|
28
|
-
setOpenDropdowns(prev => ({ ...prev, [fieldName]: false }));
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
};
|
|
32
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
33
|
-
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
34
|
-
}, []);
|
|
35
|
-
const toggleDropdown = (fieldName) => {
|
|
36
|
-
setOpenDropdowns(prev => ({ ...prev, [fieldName]: !prev[fieldName] }));
|
|
37
|
-
};
|
|
38
|
-
const selectOption = (fieldName, value) => {
|
|
39
|
-
handleChange(fieldName, value);
|
|
40
|
-
setOpenDropdowns(prev => ({ ...prev, [fieldName]: false }));
|
|
41
|
-
};
|
|
42
|
-
const handleChange = (name, value) => {
|
|
43
|
-
const updatedData = { ...formData, [name]: value };
|
|
44
|
-
setFormData(updatedData);
|
|
45
|
-
onChange(updatedData);
|
|
46
|
-
const field = fields.find((f) => f.name === name);
|
|
47
|
-
if (field?.required && (value === "" || value === null || value === undefined)) {
|
|
48
|
-
setFormErrors((prev) => ({ ...prev, [name]: "This field is required" }));
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
setFormErrors((prev) => {
|
|
52
|
-
const newErrors = { ...prev };
|
|
53
|
-
delete newErrors[name];
|
|
54
|
-
return newErrors;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
const renderField = (field) => {
|
|
59
|
-
const error = formErrors[field.name];
|
|
60
|
-
const finalInputClassName = `${defaultInputClassName} ${inputClassName || ""}`.trim();
|
|
61
|
-
const finalLabelClassName = `${defaultLabelClassName} ${labelClassName || ""}`.trim();
|
|
62
|
-
const finalFieldClassName = `${defaultFieldClassName} ${fieldClassName || ""}`.trim();
|
|
63
|
-
const finalErrorClassName = `${defaultErrorClassName} ${errorClassName || ""}`.trim();
|
|
64
|
-
const finalSelectClassName = `${defaultSelectClassName} ${selectClassName || ""}`.trim();
|
|
65
|
-
const finalCheckboxClassName = `${defaultCheckboxClassName} ${checkboxClassName || ""}`.trim();
|
|
66
|
-
const finalRadioClassName = `${defaultRadioClassName} ${radioClassName || ""}`.trim();
|
|
67
|
-
const finalOptionClassName = `${defaultOptionClassName} ${optionClassName || ""}`.trim();
|
|
68
|
-
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" : ""}`;
|
|
69
|
-
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" : ""}`;
|
|
70
|
-
return (React.createElement("div", { key: field.name, className: finalFieldClassName },
|
|
71
|
-
field.label && field.type !== "checkbox" && (React.createElement("label", { htmlFor: field.name, className: finalLabelClassName },
|
|
72
|
-
field.label,
|
|
73
|
-
field.required && React.createElement("span", { className: "text-red-500 dark:text-red-400 ml-1" }, "*"))),
|
|
74
|
-
field.description && field.type !== "checkbox" && (React.createElement("p", { className: "text-sm text-gray-500 dark:text-gray-400" }, field.description)),
|
|
75
|
-
field.type === "select" && field.options ? (React.createElement("div", { ref: el => { dropdownRefs.current[field.name] = el; }, className: "relative" },
|
|
76
|
-
React.createElement("div", { className: `${selectClassWithError} flex items-center justify-between`, onClick: () => toggleDropdown(field.name) },
|
|
77
|
-
React.createElement("span", { className: formData[field.name] ? "text-gray-800 dark:text-gray-200" : "text-gray-500 dark:text-gray-400" }, formData[field.name]
|
|
78
|
-
? field.options.find(opt => String(opt.value) === String(formData[field.name]))?.label
|
|
79
|
-
: field.placeholder || "Select an option..."),
|
|
80
|
-
React.createElement("svg", { className: `w-4 h-4 text-gray-400 dark:text-gray-500 transform transition-transform ${openDropdowns[field.name] ? 'rotate-180' : ''}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
|
|
81
|
-
React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }))),
|
|
82
|
-
openDropdowns[field.name] && (React.createElement("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" }, field.options.map((opt, index) => (React.createElement("div", { key: String(opt.value), className: `${finalOptionClassName} ${index === 0 ? 'rounded-t-lg' : ''} ${index === field.options.length - 1 ? 'rounded-b-lg' : ''}`, onClick: () => selectOption(field.name, opt.value) }, opt.label))))))) : field.type === "radio" && field.options ? (React.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3" }, field.options.map((opt) => (React.createElement("div", { key: String(opt.value), className: "flex items-center space-x-2" },
|
|
83
|
-
React.createElement("input", { type: "radio", id: `${field.name}-${opt.value}`, name: field.name, value: String(opt.value), checked: formData[field.name] === opt.value, onChange: () => handleChange(field.name, opt.value), className: finalRadioClassName }),
|
|
84
|
-
React.createElement("label", { htmlFor: `${field.name}-${opt.value}`, className: "select-none text-sm font-medium text-gray-800 dark:text-gray-200 leading-none cursor-pointer" }, opt.label)))))) : field.type === "checkbox" ? (React.createElement("div", { className: "flex items-center space-x-2" },
|
|
85
|
-
React.createElement("input", { type: "checkbox", id: field.name, checked: formData[field.name] || false, onChange: (e) => handleChange(field.name, e.target.checked), className: finalCheckboxClassName }),
|
|
86
|
-
React.createElement("label", { htmlFor: field.name, className: "text-sm font-medium text-gray-800 dark:text-gray-200 leading-none cursor-pointer" },
|
|
87
|
-
field.label,
|
|
88
|
-
field.required && React.createElement("span", { className: "text-red-500 dark:text-red-400 ml-1" }, "*")),
|
|
89
|
-
field.description && (React.createElement("p", { className: "text-sm text-gray-500 dark:text-gray-400 ml-6" }, field.description)))) : (React.createElement("input", { type: field.type, id: field.name, name: field.name, placeholder: field.placeholder, className: inputClassWithError, value: formData[field.name] ?? "", onChange: (e) => handleChange(field.name, field.type === "number" ? Number(e.target.value) || "" : e.target.value) })),
|
|
90
|
-
error && React.createElement("p", { className: finalErrorClassName }, error)));
|
|
91
|
-
};
|
|
92
|
-
return (React.createElement("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}` },
|
|
93
|
-
title && (React.createElement("div", null,
|
|
94
|
-
React.createElement("h2", { className: "text-2xl font-semibold text-gray-800 dark:text-gray-100 tracking-tight" }, title),
|
|
95
|
-
description && (React.createElement("p", { className: "text-sm text-gray-500 dark:text-gray-400" }, description)))),
|
|
96
|
-
React.createElement("div", { className: `space-y-2 ${mainFieldClassName}` }, fields.map(renderField))));
|
|
97
|
-
};
|
|
@@ -1,259 +0,0 @@
|
|
|
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
DELETED
package/tsconfig.json
DELETED