react-smart-fields 1.0.2 → 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/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pratikpanchal25.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,23 +1,86 @@
1
- # 🧩 react-dynamic-fields
1
+ # 🧩 DynamicFields React Component
2
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.
3
+ A flexible, fully-styled dynamic form generator built with **React** and **Tailwind CSS**, supporting custom input types, validation, and class overrides. Ideal for building forms quickly with full control over design and behavior.
4
4
 
5
5
  ---
6
6
 
7
- ## 🚀 Features
7
+ ## Features
8
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
9
+ - 📦 Supports `text`, `number`, `checkbox`, `radio`, and `select` fields
10
+ - 💅 Fully customizable via Tailwind-compatible className props
11
+ - 🧠 Built-in required field validation
12
+ - 🧱 Extensible for advanced usage
13
+ - 🌓 Full **dark mode** support
14
+ - 🔄 Real-time `onChange` callback with live `formData` and `errors`
15
15
 
16
16
  ---
17
17
 
18
18
  ## 📦 Installation
19
19
 
20
- ```bash
21
- npm install react-dynamic-fields
22
- # or
23
- yarn add react-dynamic-fields
20
+ Copy the `DynamicFields.tsx` file into your React project.
21
+
22
+ Make sure you have Tailwind CSS and React configured in your app.
23
+
24
+ ---
25
+
26
+ ## 🚀 Usage
27
+
28
+ ### Basic Example
29
+
30
+ ```tsx
31
+ import React from "react";
32
+ import DynamicFields from "./DynamicFields";
33
+
34
+ const fields = [
35
+ {
36
+ name: "username",
37
+ label: "Username",
38
+ type: "text",
39
+ required: true,
40
+ },
41
+ {
42
+ name: "email",
43
+ label: "Email",
44
+ type: "text",
45
+ required: true,
46
+ },
47
+ {
48
+ name: "gender",
49
+ label: "Gender",
50
+ type: "radio",
51
+ required: true,
52
+ options: [
53
+ { label: "Male", value: "male" },
54
+ { label: "Female", value: "female" },
55
+ ],
56
+ },
57
+ {
58
+ name: "country",
59
+ label: "Country",
60
+ type: "select",
61
+ options: [
62
+ { label: "USA", value: "us" },
63
+ { label: "India", value: "in" },
64
+ ],
65
+ },
66
+ {
67
+ name: "subscribe",
68
+ label: "Subscribe",
69
+ type: "checkbox",
70
+ }
71
+ ];
72
+
73
+ function App() {
74
+ const handleChange = (data) => {
75
+ console.log("Form Data:", data);
76
+ };
77
+
78
+ return (
79
+ <DynamicFields
80
+ fields={fields}
81
+ title="Sign Up"
82
+ description="Please fill all fields"
83
+ onChange={handleChange}
84
+ />
85
+ );
86
+ }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ export interface FieldOption {
3
+ label: string;
4
+ value: string | number | boolean;
5
+ }
6
+ export interface FieldConfig {
7
+ name: string;
8
+ label?: string;
9
+ type: "text" | "number" | "select" | "radio" | "checkbox";
10
+ options?: FieldOption[];
11
+ required?: boolean;
12
+ placeholder?: string;
13
+ description?: string;
14
+ }
15
+ export interface DynamicFieldsProps {
16
+ fields: FieldConfig[];
17
+ onChange: (data: Record<string, any>) => void;
18
+ className?: string;
19
+ inputClassName?: string;
20
+ labelClassName?: string;
21
+ mainFieldClassName?: string;
22
+ fieldClassName?: string;
23
+ errorClassName?: string;
24
+ selectClassName?: string;
25
+ optionClassName?: string;
26
+ checkboxClassName?: string;
27
+ radioClassName?: string;
28
+ title?: string;
29
+ description?: string;
30
+ }
31
+ export declare const DynamicFields: React.FC<DynamicFieldsProps>;
@@ -0,0 +1,97 @@
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
+ };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { DynamicFields } from "./components/DynamicField";
2
- export type { FieldConfig, FieldOption } from "./components/DynamicField";
1
+ export { DynamicFields } from "./components/DynamicFields";
2
+ export type { FieldConfig, FieldOption } from "./components/DynamicFields";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { DynamicFields } from "./components/DynamicField";
1
+ export { DynamicFields } from "./components/DynamicFields";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-smart-fields",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
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",
@@ -1,27 +0,0 @@
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 {};
@@ -1,44 +0,0 @@
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
- };