@reactfast/forms 0.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/LICENSE.md +21 -0
- package/README.md +439 -0
- package/dist/ctrlform.cjs.js +53 -0
- package/dist/ctrlform.es.js +15486 -0
- package/dist/ctrlform.umd.js +53 -0
- package/index.d.ts +780 -0
- package/package.json +54 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jonathon McClendon
|
|
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
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# 🎛️ @reactfast/forms
|
|
2
|
+
|
|
3
|
+
**Dynamic React forms powered by JSON schemas, modifiers, and subforms.**
|
|
4
|
+
Create complex, adaptive form systems without boilerplate — designed for scale, simplicity, and composability.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/@reactfast/forms)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://github.com/jonathonmcclendon/NovaForms/actions)
|
|
9
|
+
[](https://github.com/jonathonmcclendon/NovaForms/issues)
|
|
10
|
+
|
|
11
|
+
### 📚 [**View Live Documentation**](https://nova-forms-next.vercel.app/) 📚
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⚙️ Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @reactfast/forms
|
|
19
|
+
# or
|
|
20
|
+
yarn add @reactfast/forms
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
CtrlForm requires **React 18+** and **React DOM 18+** as peer dependencies.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Quick Start
|
|
28
|
+
|
|
29
|
+
The simplest way to get started:
|
|
30
|
+
|
|
31
|
+
```jsx
|
|
32
|
+
import { useState } from "react";
|
|
33
|
+
import { Form, createFormHandler } from "@reactfast/forms";
|
|
34
|
+
|
|
35
|
+
const fields = [
|
|
36
|
+
{ name: "firstName", title: "First Name", type: "string", width: 50 },
|
|
37
|
+
{ name: "lastName", title: "Last Name", type: "string", width: 50 },
|
|
38
|
+
{ name: "email", title: "Email", type: "email", width: 100 },
|
|
39
|
+
{ name: "subscribe", title: "Subscribe?", type: "boolean", width: 100 },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export default function App() {
|
|
43
|
+
const [formData, setFormData] = useState({});
|
|
44
|
+
|
|
45
|
+
const handleChange = createFormHandler({
|
|
46
|
+
fields,
|
|
47
|
+
setState: setFormData,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Form
|
|
52
|
+
fields={fields}
|
|
53
|
+
onChange={handleChange}
|
|
54
|
+
formData={formData}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## ✨ Features
|
|
63
|
+
|
|
64
|
+
- ⚡ **Controlled forms** — simple `value`/`onChange` pattern like React inputs
|
|
65
|
+
- 🧩 **Composable** — each field is a reusable React component
|
|
66
|
+
- 🔄 **Advanced conditional logic** — dynamic show/hide, disable, and field dependencies
|
|
67
|
+
- 📱 **Responsive layout** — automatic width handling with Tailwind classes
|
|
68
|
+
- 🧱 **Subforms & arrays** — nested or repeated field groups are first-class citizens
|
|
69
|
+
- 🎨 **Theming-ready** — customize UI with Tailwind or your own design system
|
|
70
|
+
- 🔌 **Extensible** — register your own field components via `registerField()`
|
|
71
|
+
- 🧠 **Smart rules system** — powerful top-level rules with field-level triggers
|
|
72
|
+
- 🔢 **Math operations** — automatic calculations with add, subtract, multiply, divide
|
|
73
|
+
- 📝 **String operations** — concatenation and text manipulation
|
|
74
|
+
- ✅ **Pattern validation** — client-side regex validation with custom messages
|
|
75
|
+
- 🎯 **Multiple field types** — 20+ built-in field types from text to file uploads
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🎯 Built-in Field Types
|
|
80
|
+
|
|
81
|
+
Nova Forms comes with 20+ field types ready to use:
|
|
82
|
+
|
|
83
|
+
| Type | Description | Example |
|
|
84
|
+
|------|-------------|---------|
|
|
85
|
+
| `string` | Text input | `{ type: "string", title: "Name" }` |
|
|
86
|
+
| `text` | Textarea | `{ type: "text", title: "Description" }` |
|
|
87
|
+
| `email` | Email input with validation | `{ type: "email", title: "Email" }` |
|
|
88
|
+
| `tel` | Phone number input | `{ type: "tel", title: "Phone" }` |
|
|
89
|
+
| `url` | URL input | `{ type: "url", title: "Website" }` |
|
|
90
|
+
| `number` | Number input | `{ type: "number", title: "Age" }` |
|
|
91
|
+
| `boolean` | Checkbox | `{ type: "boolean", title: "Subscribe" }` |
|
|
92
|
+
| `toggle` | Toggle switch | `{ type: "toggle", title: "Enable" }` |
|
|
93
|
+
| `date` | Date picker | `{ type: "date", title: "Birth Date" }` |
|
|
94
|
+
| `datetime` | Date and time picker | `{ type: "datetime", title: "Event Time" }` |
|
|
95
|
+
| `time` | Time picker | `{ type: "time", title: "Start Time" }` |
|
|
96
|
+
| `color` | Color picker | `{ type: "color", title: "Theme Color" }` |
|
|
97
|
+
| `select` | Single select dropdown | `{ type: "select", options: [...] }` |
|
|
98
|
+
| `multiselect` | Multi-select dropdown | `{ type: "multiselect", options: [...] }` |
|
|
99
|
+
| `radio` | Radio button group | `{ type: "radio", options: [...] }` |
|
|
100
|
+
| `file` | File upload | `{ type: "file", title: "Upload" }` |
|
|
101
|
+
| `fileV2` | Enhanced file upload | `{ type: "fileV2", title: "Photo" }` |
|
|
102
|
+
| `uploadToBase` | Base64 image upload | `{ type: "uploadToBase", title: "Avatar" }` |
|
|
103
|
+
| `array` | Dynamic subform/array | `{ type: "array", fields: [...] }` |
|
|
104
|
+
| `subForm` | Nested form group | `{ type: "subForm", fields: [...] }` |
|
|
105
|
+
| `signature` | Signature pad | `{ type: "signature", title: "Signature" }` |
|
|
106
|
+
| `rating` | Star rating | `{ type: "rating", title: "Rating" }` |
|
|
107
|
+
| `scale` | Likert scale | `{ type: "scale", title: "Satisfaction" }` |
|
|
108
|
+
| `captcha` | reCAPTCHA | `{ type: "captcha" }` |
|
|
109
|
+
| `header` | Section header | `{ type: "header", title: "Section" }` |
|
|
110
|
+
| `paragraph` | Static text | `{ type: "paragraph", content: "Text" }` |
|
|
111
|
+
| `image` | Static image | `{ type: "image", image: { src: "..." } }` |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🧩 Example: Registering Custom Fields
|
|
116
|
+
|
|
117
|
+
You can extend Nova Forms with your own field types:
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
import { registerField } from "@reactfast/forms";
|
|
121
|
+
|
|
122
|
+
function QRCodeScannerField({ field, value, onChange }) {
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
<p>Scan QR Code for {field.title}</p>
|
|
126
|
+
{/* Your scanner logic */}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
registerField("qrScanner", QRCodeScannerField);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Now use it in your fields array:
|
|
135
|
+
|
|
136
|
+
```jsx
|
|
137
|
+
const fields = [
|
|
138
|
+
{
|
|
139
|
+
name: "eventCheckIn",
|
|
140
|
+
title: "Check In",
|
|
141
|
+
type: "qrScanner",
|
|
142
|
+
width: 100
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 🧠 API Overview
|
|
150
|
+
|
|
151
|
+
### `Form`
|
|
152
|
+
|
|
153
|
+
Renders a form based on your field array with integrated modifiers and conditions.
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Description |
|
|
156
|
+
| -------------- | --------------------- | ----------------------------------------------- |
|
|
157
|
+
| `fields` | `array` | Array of field definitions |
|
|
158
|
+
| `onChange` | `function` | Change handler (from createFormHandler) |
|
|
159
|
+
| `formData` | `object` | Form data object from parent state |
|
|
160
|
+
| `theme` | `object` _(optional)_ | Custom theme overrides |
|
|
161
|
+
| `isMobileView` | `boolean` _(optional)_ | Force mobile layout (full width) |
|
|
162
|
+
|
|
163
|
+
### `createFormHandler`
|
|
164
|
+
|
|
165
|
+
Creates a change handler that manages state and applies modifiers.
|
|
166
|
+
|
|
167
|
+
| Prop | Type | Description |
|
|
168
|
+
| -------------- | --------------------- | ----------------------------------------------- |
|
|
169
|
+
| `fields` | `array` | Array of field definitions |
|
|
170
|
+
| `setState` | `function` | React setState function |
|
|
171
|
+
| `rules` | `array` _(optional)_ | Top-level rules referenced by field triggers |
|
|
172
|
+
|
|
173
|
+
### Field Schema
|
|
174
|
+
|
|
175
|
+
Each field object supports:
|
|
176
|
+
|
|
177
|
+
| Property | Type | Description |
|
|
178
|
+
|----------|------|-------------|
|
|
179
|
+
| `name` | `string` | Field name (required) |
|
|
180
|
+
| `type` | `string` | Field type (string, email, boolean, etc.) |
|
|
181
|
+
| `title` | `string` | Display label (preferred over `label`) |
|
|
182
|
+
| `label` | `string` | Display label (legacy, use `title`) |
|
|
183
|
+
| `width` | `number` | Width percentage (25, 50, 75, 100) |
|
|
184
|
+
| `default` | `any` | Default value |
|
|
185
|
+
| `readOnly` | `boolean` | Make field read-only |
|
|
186
|
+
| `required` | `boolean` | Mark field as required |
|
|
187
|
+
| `placeholder` | `string` | Placeholder text |
|
|
188
|
+
| `description` | `string` | Help text below field |
|
|
189
|
+
| `helper` | `string` | Additional help text |
|
|
190
|
+
| `error` | `string` | Error message to display |
|
|
191
|
+
| `leadingIcon` | `Component` | Icon component before input |
|
|
192
|
+
| `trailingIcon` | `Component` | Icon component after input |
|
|
193
|
+
| `modifiers` | `array` | (Legacy) field-local modifiers for values |
|
|
194
|
+
| `triggers` | `array` | Triggers that reference top-level rules |
|
|
195
|
+
| `conditions.hiddenWhen` | `array or object` | Conditions to hide (rendered with `hidden` class) |
|
|
196
|
+
| `conditions.hiddenMode` | `any or all` | Mode for hidden conditions (default any) |
|
|
197
|
+
| `conditions.readOnlyWhen` | `array or object` | Conditions to set readOnly |
|
|
198
|
+
| `conditions.readOnlyMode` | `any or all` | Mode for readOnly conditions (default any) |
|
|
199
|
+
| `pattern` | `RegExp \| string \| Array<{ regex, message } \| string>` | Client-side pattern checks with messages |
|
|
200
|
+
| `options` | `array` | Options for select, radio, multiselect fields |
|
|
201
|
+
| `fields` | `array` | Sub-fields for array/subForm types |
|
|
202
|
+
|
|
203
|
+
### Modifiers (legacy)
|
|
204
|
+
|
|
205
|
+
Field-local modifiers automatically update dependent field values. These are still supported for backward compatibility, but the preferred approach is to use top-level rules and field-level triggers.
|
|
206
|
+
|
|
207
|
+
```jsx
|
|
208
|
+
{
|
|
209
|
+
name: "firstName",
|
|
210
|
+
type: "string",
|
|
211
|
+
modifiers: [
|
|
212
|
+
{
|
|
213
|
+
target: "fullName",
|
|
214
|
+
type: "concat",
|
|
215
|
+
when: "true",
|
|
216
|
+
value: " " // adds space
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Conditions
|
|
223
|
+
|
|
224
|
+
Control field visibility and state:
|
|
225
|
+
|
|
226
|
+
```jsx
|
|
227
|
+
{
|
|
228
|
+
name: "subscribe",
|
|
229
|
+
type: "boolean",
|
|
230
|
+
conditions: {
|
|
231
|
+
hiddenWhen: {
|
|
232
|
+
field: "age",
|
|
233
|
+
when: "less than",
|
|
234
|
+
value: 18
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### Rules & Triggers
|
|
243
|
+
|
|
244
|
+
- Rules live at the top level and have unique names. A rule contains one or more effects that target a field and either change its value or attributes.
|
|
245
|
+
- Triggers live on fields and reference a rule by name. When the trigger's conditions match, the rule's effects are applied.
|
|
246
|
+
- Value effects are applied inside `createFormHandler`. Attribute effects (e.g., `hidden`, `readOnly`, `title`) are applied in `NovaForm`.
|
|
247
|
+
|
|
248
|
+
Rules shape:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
const rules = [
|
|
252
|
+
{
|
|
253
|
+
name: "fullNameRule",
|
|
254
|
+
effects: [
|
|
255
|
+
{ targetField: "displayName", prop: "value", type: "concat", kind: "string", value: " " },
|
|
256
|
+
{ targetField: "age", prop: "readOnly", value: true },
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
];
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Trigger shape on a field:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
{
|
|
266
|
+
name: "firstName",
|
|
267
|
+
type: "string",
|
|
268
|
+
triggers: [
|
|
269
|
+
{
|
|
270
|
+
rule: "fullNameRule",
|
|
271
|
+
when: [
|
|
272
|
+
{ field: "firstName", when: "not empty" },
|
|
273
|
+
{ field: "lastName", when: "not empty" },
|
|
274
|
+
],
|
|
275
|
+
mode: "all", // all = AND, any = OR (default)
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Pass `rules` to both `createFormHandler` and `Form`:
|
|
282
|
+
|
|
283
|
+
```jsx
|
|
284
|
+
const handleChange = createFormHandler({ fields, rules, setState: setFormData });
|
|
285
|
+
|
|
286
|
+
<Form fields={fields} rules={rules} onChange={handleChange} formData={formData} />
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Hidden fields remain mounted and use Tailwind's `hidden` class so values still update.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 🧱 Architecture Overview
|
|
294
|
+
|
|
295
|
+
Nova Forms is organized for **extensibility** and **maintainability**:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
src/
|
|
299
|
+
├── core/ → field registry and evaluation
|
|
300
|
+
├── formFields/ → built-in field components
|
|
301
|
+
├── handlers/ → form handlers and modifiers
|
|
302
|
+
├── utils/ → shared utilities
|
|
303
|
+
├── NovaForm.jsx → main form component
|
|
304
|
+
└── returnFields.jsx → field renderer
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 🔄 Migration from Manual Field Mapping
|
|
310
|
+
|
|
311
|
+
If you're currently mapping fields manually:
|
|
312
|
+
|
|
313
|
+
**Before:**
|
|
314
|
+
```jsx
|
|
315
|
+
import { ReturnFieldsV2, createFormHandler, initializeFormData } from "@reactfast/forms";
|
|
316
|
+
|
|
317
|
+
const [formData, setFormData] = useState(() => initializeFormData(fields));
|
|
318
|
+
const handleChange = createFormHandler({ fields, setState: setFormData });
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<div className="-mx-2 flex flex-wrap">
|
|
322
|
+
{fields.map((field) => (
|
|
323
|
+
<div key={field.name} className={`${getWidthClass(field.width)} mb-4 px-2`}>
|
|
324
|
+
<ReturnFieldsV2
|
|
325
|
+
field={field}
|
|
326
|
+
value={formData[field.name]}
|
|
327
|
+
onChange={handleChange}
|
|
328
|
+
/>
|
|
329
|
+
</div>
|
|
330
|
+
))}
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**After:**
|
|
336
|
+
```jsx
|
|
337
|
+
import { Form, createFormHandler } from "@reactfast/forms";
|
|
338
|
+
|
|
339
|
+
const [formData, setFormData] = useState({});
|
|
340
|
+
const handleChange = createFormHandler({ fields, setState: setFormData });
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<Form
|
|
344
|
+
fields={fields}
|
|
345
|
+
onChange={handleChange}
|
|
346
|
+
formData={formData}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 📚 Documentation
|
|
354
|
+
|
|
355
|
+
For comprehensive guides and examples, see our documentation:
|
|
356
|
+
|
|
357
|
+
- **Live Documentation**: [nova-forms-next.vercel.app](https://nova-forms-next.vercel.app/)
|
|
358
|
+
- **Docs Repository**: [jonathonmcclen/NovaForms-Next](https://github.com/jonathonmcclen/NovaForms-Next)
|
|
359
|
+
|
|
360
|
+
- **[Introduction](documentation/intro.md)** - Complete overview of Nova Forms
|
|
361
|
+
- **[Quick Start](documentation/quickstart.md)** - Get up and running quickly
|
|
362
|
+
- **[createFormHandler](documentation/createFormHandler.md)** - Understanding the form handler system
|
|
363
|
+
- **[Fields & Schemas](documentation/fields-schemas.md)** - Complete field reference and schema guide
|
|
364
|
+
- **[Rules System](documentation/rules.md)** - Advanced rules and effects
|
|
365
|
+
- **[Triggers & Conditions](documentation/triggers.md)** - Conditional logic and triggers
|
|
366
|
+
- **[Dynamic Hide](documentation/dynamic-hide.md)** - Show/hide fields dynamically
|
|
367
|
+
- **[Dynamic Disable](documentation/dynamic-disable.md)** - Enable/disable fields dynamically
|
|
368
|
+
- **[Custom Fields](documentation/custom-fields.md)** - Creating and registering custom field types
|
|
369
|
+
- **[Styling with Tailwind](documentation/styling-tailwind.md)** - Tailwind CSS integration
|
|
370
|
+
- **[Theme Styling](documentation/styling-theme.md)** - Custom theming system
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## 🤝 Contributing
|
|
375
|
+
|
|
376
|
+
We welcome pull requests and feature suggestions!
|
|
377
|
+
|
|
378
|
+
1. Fork the repo
|
|
379
|
+
2. Create a feature branch
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
git checkout -b feature/your-feature
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
3. Commit your changes
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
git commit -m "Add new feature"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
4. Push to your branch
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
git push origin feature/your-feature
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
5. Open a pull request
|
|
398
|
+
|
|
399
|
+
> 🔒 Only approved code owners can merge to main.
|
|
400
|
+
> See `.github/CODEOWNERS` for details.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 🪪 License
|
|
405
|
+
|
|
406
|
+
Licensed under the [MIT License](LICENSE).
|
|
407
|
+
Copyright © 2025 [Jonathon McClendon](https://github.com/jonathonmcclendon)
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 💡 Maintained by
|
|
412
|
+
|
|
413
|
+
**Jonathon McClendon**
|
|
414
|
+
Creator of Nova Forms — building high-performance tools for scalable React ecosystems.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
### 🌟 Support the Project
|
|
419
|
+
|
|
420
|
+
If Nova Forms helps you ship faster or cleaner React code:
|
|
421
|
+
|
|
422
|
+
- ⭐ Star the repo
|
|
423
|
+
- 🐛 Open an issue for bugs or feature ideas
|
|
424
|
+
- 💬 Share it with other developers
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
> _"A form library that feels invisible — flexible, composable, and future-proof."_
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### 🧭 Next Steps (Roadmap Ideas)
|
|
433
|
+
|
|
434
|
+
- [ ] Advanced validation layer (Yup / Zod integration)
|
|
435
|
+
- [ ] Enhanced theming system (context-aware)
|
|
436
|
+
- [ ] Multi-Step Forms / Wizard support
|
|
437
|
+
- [ ] TypeScript definitions (optional)
|
|
438
|
+
|
|
439
|
+
---
|