react-smart-fields 1.1.5 โ 2.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/README.md +218 -130
- package/dist/index.cjs +594 -0
- package/dist/index.d.cts +129 -0
- package/dist/index.d.ts +129 -2
- package/dist/index.js +557 -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
package/README.md
CHANGED
|
@@ -1,191 +1,279 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React Smart Fields
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|

|
|
6
|
-

|
|
7
6
|
|
|
7
|
+
`react-smart-fields` is a powerful dynamic form renderer for React with:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- configurable field schemas
|
|
10
|
+
- built-in + custom validation
|
|
11
|
+
- conditional visibility/disable/required rules
|
|
12
|
+
- async options for selects
|
|
13
|
+
- nested field names (`user.addresses[0].city`)
|
|
14
|
+
- theme presets + headless mode + full render slots
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
## Installation
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* ๐
Fully customizable via Tailwind-compatible className props
|
|
17
|
-
* ๐ง Built-in required field validation
|
|
18
|
-
* ๐งฑ Extensible for advanced usage
|
|
19
|
-
* ๐ Full **dark mode** support
|
|
20
|
-
* ๐ Real-time `onChange` callback with live `formData` and `errors`
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## ๐ฆ Installation
|
|
25
|
-
|
|
26
|
-
Copy the `DynamicFields.tsx` file into your React project.
|
|
27
|
-
|
|
28
|
-
Make sure you have Tailwind CSS and React configured in your app.
|
|
18
|
+
```bash
|
|
19
|
+
npm install react-smart-fields
|
|
20
|
+
```
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
or
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
```bash
|
|
25
|
+
yarn add react-smart-fields
|
|
26
|
+
```
|
|
33
27
|
|
|
34
|
-
|
|
28
|
+
## Quick Start
|
|
35
29
|
|
|
36
30
|
```tsx
|
|
37
|
-
import
|
|
38
|
-
import DynamicFields from "./DynamicFields";
|
|
31
|
+
import { DynamicFields, FieldConfig } from "react-smart-fields";
|
|
39
32
|
|
|
40
|
-
const fields = [
|
|
41
|
-
{
|
|
42
|
-
name: "username",
|
|
43
|
-
label: "Username",
|
|
44
|
-
type: "text",
|
|
45
|
-
required: true,
|
|
46
|
-
},
|
|
33
|
+
const fields: FieldConfig[] = [
|
|
34
|
+
{ name: "name", label: "Name", type: "text", required: true },
|
|
47
35
|
{
|
|
48
36
|
name: "email",
|
|
49
37
|
label: "Email",
|
|
50
|
-
type: "
|
|
51
|
-
required: true,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: "gender",
|
|
55
|
-
label: "Gender",
|
|
56
|
-
type: "radio",
|
|
38
|
+
type: "email",
|
|
57
39
|
required: true,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{ label: "Female", value: "female" },
|
|
61
|
-
],
|
|
40
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
41
|
+
patternMessage: "Please enter a valid email",
|
|
62
42
|
},
|
|
63
43
|
{
|
|
64
44
|
name: "country",
|
|
65
45
|
label: "Country",
|
|
66
46
|
type: "select",
|
|
47
|
+
placeholder: "Select country",
|
|
67
48
|
options: [
|
|
68
|
-
{ label: "USA", value: "us" },
|
|
69
49
|
{ label: "India", value: "in" },
|
|
50
|
+
{ label: "United States", value: "us" },
|
|
70
51
|
],
|
|
71
52
|
},
|
|
72
53
|
{
|
|
73
|
-
name: "
|
|
74
|
-
label: "Subscribe",
|
|
54
|
+
name: "newsletter",
|
|
55
|
+
label: "Subscribe to newsletter",
|
|
75
56
|
type: "checkbox",
|
|
76
|
-
|
|
57
|
+
defaultValue: true,
|
|
58
|
+
},
|
|
77
59
|
];
|
|
78
60
|
|
|
79
|
-
function App() {
|
|
80
|
-
const handleChange = (data) => {
|
|
81
|
-
console.log("Form Data:", data);
|
|
82
|
-
};
|
|
83
|
-
|
|
61
|
+
export default function App() {
|
|
84
62
|
return (
|
|
85
63
|
<DynamicFields
|
|
86
64
|
fields={fields}
|
|
87
|
-
title="
|
|
88
|
-
description="
|
|
89
|
-
|
|
65
|
+
title="Profile"
|
|
66
|
+
description="Update your profile details"
|
|
67
|
+
theme="default"
|
|
68
|
+
size="md"
|
|
69
|
+
onChange={(values, meta) => {
|
|
70
|
+
console.log(values, meta?.errors);
|
|
71
|
+
}}
|
|
90
72
|
/>
|
|
91
73
|
);
|
|
92
74
|
}
|
|
93
75
|
```
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
## Major Capabilities
|
|
78
|
+
|
|
79
|
+
- **Validation engine**: `required`, `min/max`, `minLength/maxLength`, `pattern`, and `validate`
|
|
80
|
+
- **Conditional behavior**: `showWhen`, `disableWhen`, `requiredWhen`
|
|
81
|
+
- **Nested paths**: dot/bracket field names are supported
|
|
82
|
+
- **Async select options**: `loadOptions` with `loadOptionsOn: "mount" | "change"`
|
|
83
|
+
- **Theme presets**: `default`, `minimal`, `filled`, `underline`
|
|
84
|
+
- **Headless mode**: set `headless` to fully control UI
|
|
85
|
+
- **Render slots**: `renderField`, `renderControl`, `renderLabel`, `renderDescription`, `renderError`
|
|
86
|
+
- **State styling hooks**: `stateClassNames` for `invalid`, `disabled`, `readonly`, `dirty`, `touched`
|
|
87
|
+
|
|
88
|
+
## Component Props
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Required | Description |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `fields` | `FieldConfig[]` | โ
| Field schema array |
|
|
93
|
+
| `onChange` | `(values, meta?) => void` | โ
| Fires on every value/meta update |
|
|
94
|
+
| `value` | `Record<string, any>` | โ | Controlled mode values |
|
|
95
|
+
| `defaultValues` | `Record<string, any>` | โ | Initial values (uncontrolled mode) |
|
|
96
|
+
| `resolver` | `(values) => errors \| Promise<errors>` | โ | External validation resolver |
|
|
97
|
+
| `validateMode` | `"change" \| "blur"` | โ | Validation trigger mode |
|
|
98
|
+
| `showErrorWhen` | `"always" \| "touched" \| "dirty"` | โ | Error visibility strategy |
|
|
99
|
+
| `headless` | `boolean` | โ | Disables built-in styling |
|
|
100
|
+
| `theme` | `"default" \| "minimal" \| "filled" \| "underline"` | โ | Built-in visual preset |
|
|
101
|
+
| `size` | `"sm" \| "md" \| "lg"` | โ | Input/label sizing |
|
|
102
|
+
| `ui` | `UiClassMap` | โ | Global slot class overrides |
|
|
103
|
+
| `stateClassNames` | `StateClassNames` | โ | State class hooks |
|
|
104
|
+
| `renderField` | `(ctx) => ReactNode` | โ | Full custom field renderer |
|
|
105
|
+
| `renderControl` | `(ctx) => ReactNode` | โ | Custom control renderer |
|
|
106
|
+
| `renderLabel` | `(ctx) => ReactNode` | โ | Custom label renderer |
|
|
107
|
+
| `renderDescription` | `(ctx) => ReactNode` | โ | Custom helper text renderer |
|
|
108
|
+
| `renderError` | `(ctx) => ReactNode` | โ | Custom error renderer |
|
|
109
|
+
| `title` | `string` | โ | Form heading |
|
|
110
|
+
| `description` | `string` | โ | Form subtitle |
|
|
111
|
+
|
|
112
|
+
Legacy class props are still supported for backward compatibility:
|
|
113
|
+
`className`, `inputClassName`, `labelClassName`, `mainFieldClassName`, `fieldClassName`, `errorClassName`, `selectClassName`, `optionClassName`, `checkboxClassName`, `radioClassName`.
|
|
114
|
+
|
|
115
|
+
## FieldConfig API
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
```ts
|
|
118
|
+
type FieldConfig = {
|
|
119
|
+
name: string;
|
|
120
|
+
label?: string;
|
|
121
|
+
type:
|
|
122
|
+
| "text"
|
|
123
|
+
| "number"
|
|
124
|
+
| "select"
|
|
125
|
+
| "radio"
|
|
126
|
+
| "checkbox"
|
|
127
|
+
| "textarea"
|
|
128
|
+
| "email"
|
|
129
|
+
| "password"
|
|
130
|
+
| "tel"
|
|
131
|
+
| "url"
|
|
132
|
+
| "date";
|
|
133
|
+
|
|
134
|
+
// values/options
|
|
135
|
+
defaultValue?: any;
|
|
136
|
+
placeholder?: string;
|
|
137
|
+
options?: { label: string; value: string | number | boolean | null | undefined; disabled?: boolean }[];
|
|
138
|
+
loadOptions?: (ctx: { values: Record<string, any>; field: FieldConfig }) => Promise<FieldOption[]>;
|
|
139
|
+
loadOptionsOn?: "mount" | "change";
|
|
140
|
+
|
|
141
|
+
// behavior
|
|
142
|
+
disabled?: boolean;
|
|
143
|
+
readOnly?: boolean;
|
|
144
|
+
showWhen?: (values: Record<string, any>) => boolean;
|
|
145
|
+
disableWhen?: (values: Record<string, any>) => boolean;
|
|
146
|
+
required?: boolean;
|
|
147
|
+
requiredWhen?: (values: Record<string, any>) => boolean;
|
|
148
|
+
requiredMessage?: string;
|
|
149
|
+
|
|
150
|
+
// validation
|
|
151
|
+
min?: number;
|
|
152
|
+
max?: number;
|
|
153
|
+
minLength?: number;
|
|
154
|
+
maxLength?: number;
|
|
155
|
+
pattern?: RegExp;
|
|
156
|
+
patternMessage?: string;
|
|
157
|
+
validate?: (value: any, values: Record<string, any>) => string | undefined | null;
|
|
158
|
+
|
|
159
|
+
// value transforms
|
|
160
|
+
transformIn?: (storedValue: any, values: Record<string, any>) => any;
|
|
161
|
+
transformOut?: (rawValue: any, values: Record<string, any>) => any;
|
|
162
|
+
|
|
163
|
+
// styling/rendering
|
|
164
|
+
className?: string;
|
|
165
|
+
inputClassName?: string;
|
|
166
|
+
labelClassName?: string;
|
|
167
|
+
errorClassName?: string;
|
|
168
|
+
ui?: UiClassMap;
|
|
169
|
+
renderControl?: (ctx: RenderControlContext) => ReactNode;
|
|
170
|
+
};
|
|
171
|
+
```
|
|
119
172
|
|
|
120
|
-
|
|
173
|
+
## Customization Examples
|
|
121
174
|
|
|
122
|
-
|
|
123
|
-
| ------------- | --------------------------------------------------------- | --------------------------------- | -------------------------------------- |
|
|
124
|
-
| `name` | `string` | โ
Yes | Unique key |
|
|
125
|
-
| `label` | `string` | โ No | Display label |
|
|
126
|
-
| `type` | `"text"`, `"number"`, `"select"`, `"radio"`, `"checkbox"` | โ
Yes | Field type |
|
|
127
|
-
| `required` | `boolean` | โ No | Show red asterisk and basic validation |
|
|
128
|
-
| `placeholder` | `string` | โ No | Placeholder (for inputs/selects) |
|
|
129
|
-
| `description` | `string` | โ No | Optional helper text |
|
|
130
|
-
| `options` | `{ label: string; value: any; }[]` | Required for `select` and `radio` | Dropdown/Radio values |
|
|
175
|
+
### 1) Conditional + Nested Fields
|
|
131
176
|
|
|
132
|
-
|
|
177
|
+
```tsx
|
|
178
|
+
const fields: FieldConfig[] = [
|
|
179
|
+
{ name: "accountType", label: "Account Type", type: "select", options: [
|
|
180
|
+
{ label: "Individual", value: "individual" },
|
|
181
|
+
{ label: "Business", value: "business" },
|
|
182
|
+
]},
|
|
183
|
+
{
|
|
184
|
+
name: "company.name",
|
|
185
|
+
label: "Company Name",
|
|
186
|
+
type: "text",
|
|
187
|
+
showWhen: (values) => values.accountType === "business",
|
|
188
|
+
requiredWhen: (values) => values.accountType === "business",
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "addresses[0].city",
|
|
192
|
+
label: "Primary City",
|
|
193
|
+
type: "text",
|
|
194
|
+
required: true,
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
```
|
|
133
198
|
|
|
134
|
-
|
|
199
|
+
### 2) Async Select Options
|
|
135
200
|
|
|
136
|
-
|
|
201
|
+
```tsx
|
|
202
|
+
{
|
|
203
|
+
name: "state",
|
|
204
|
+
label: "State",
|
|
205
|
+
type: "select",
|
|
206
|
+
loadOptionsOn: "change",
|
|
207
|
+
loadOptions: async ({ values }) => {
|
|
208
|
+
if (!values.country) return [];
|
|
209
|
+
const response = await fetch(`/api/states?country=${values.country}`);
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
return data.map((item: any) => ({ label: item.name, value: item.code }));
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
```
|
|
137
215
|
|
|
138
|
-
|
|
139
|
-
| ------------------- | ------------------- | ---------------------------------- |
|
|
140
|
-
| Wrapper div | `className` | `bg-white dark:bg-gray-900` |
|
|
141
|
-
| Label text | `labelClassName` | `text-gray-800 dark:text-gray-200` |
|
|
142
|
-
| Input fields | `inputClassName` | `bg-white dark:bg-gray-800` |
|
|
143
|
-
| Select dropdown | `selectClassName` | `rounded-lg` |
|
|
144
|
-
| Radio buttons | `radioClassName` | `rounded-full` |
|
|
145
|
-
| Checkbox | `checkboxClassName` | `rounded` |
|
|
146
|
-
| Field container div | `fieldClassName` | `w-full` |
|
|
147
|
-
| Dropdown options | `optionClassName` | `hover:bg-gray-100` |
|
|
148
|
-
| Error messages | `errorClassName` | `text-red-500` |
|
|
216
|
+
### 3) Headless + Slots
|
|
149
217
|
|
|
150
|
-
|
|
218
|
+
```tsx
|
|
219
|
+
<DynamicFields
|
|
220
|
+
fields={fields}
|
|
221
|
+
headless
|
|
222
|
+
renderField={({ field, labelNode, controlNode, errorNode, helpNode }) => (
|
|
223
|
+
<div key={field.name} className="mb-6">
|
|
224
|
+
{labelNode}
|
|
225
|
+
<div className="mt-2">{controlNode}</div>
|
|
226
|
+
<div className="mt-1">{helpNode}</div>
|
|
227
|
+
<div className="mt-1">{errorNode}</div>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
onChange={(values) => console.log(values)}
|
|
231
|
+
/>
|
|
232
|
+
```
|
|
151
233
|
|
|
152
|
-
|
|
234
|
+
### 4) Resolver Validation
|
|
153
235
|
|
|
154
|
-
|
|
236
|
+
```tsx
|
|
237
|
+
<DynamicFields
|
|
238
|
+
fields={fields}
|
|
239
|
+
resolver={(values) => {
|
|
240
|
+
const errors: Record<string, string> = {};
|
|
241
|
+
if (values.password !== values.confirmPassword) {
|
|
242
|
+
errors.confirmPassword = "Passwords do not match";
|
|
243
|
+
}
|
|
244
|
+
return errors;
|
|
245
|
+
}}
|
|
246
|
+
onChange={(values, meta) => {
|
|
247
|
+
console.log(meta?.errors);
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
155
251
|
|
|
156
|
-
|
|
252
|
+
## Type Exports
|
|
157
253
|
|
|
158
254
|
```ts
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
255
|
+
import type {
|
|
256
|
+
FieldConfig,
|
|
257
|
+
FieldOption,
|
|
258
|
+
FieldType,
|
|
259
|
+
DynamicFieldsProps,
|
|
260
|
+
UiClassMap,
|
|
261
|
+
StateClassNames,
|
|
262
|
+
ChangeMeta,
|
|
263
|
+
RenderControlContext,
|
|
264
|
+
RenderFieldContext,
|
|
265
|
+
} from "react-smart-fields";
|
|
167
266
|
```
|
|
168
267
|
|
|
169
|
-
|
|
268
|
+
## Author
|
|
170
269
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
**Name:** Pratik Panchal
|
|
270
|
+
**Name:** Pratik Panchal
|
|
174
271
|
**GitHub:** [@Pratikpanchal25](https://github.com/Pratikpanchal25)
|
|
175
272
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
## ๐ License
|
|
273
|
+
## License
|
|
180
274
|
|
|
181
275
|
MIT โ Free to use, modify and distribute.
|
|
182
276
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
## ๐ Contributing
|
|
186
|
-
|
|
187
|
-
Pull requests are welcome! If you find bugs, feel free to [open an issue](https://github.com/ShubhamNakum/DynamicFields/issues).
|
|
188
|
-
|
|
189
|
-
---
|
|
277
|
+
## Contributing
|
|
190
278
|
|
|
191
|
-
|
|
279
|
+
Pull requests are welcome! If you find bugs, feel free to open an issue.
|