suprform 1.1.4 β 1.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/LICENSE +1 -1
- package/README.md +1316 -202
- package/dist/components/FormControl.d.ts +1 -1
- package/dist/components/FormControl.d.ts.map +1 -1
- package/dist/suprform.cjs.js +1 -1
- package/dist/suprform.cjs.js.map +1 -1
- package/dist/suprform.es.js +695 -669
- package/dist/suprform.es.js.map +1 -1
- package/dist/type.d.ts +4 -0
- package/dist/type.d.ts.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,12 +1,59 @@
|
|
|
1
|
-
# SuprForm
|
|
1
|
+
# SuprForm π
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**A headless, TypeScript-first React form library for managing complex state, validation, and conditional logic**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/suprform)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
[Features](#-key-features) β’ [Installation](#-installation) β’ [Quick Start](#-quick-start) β’ [Documentation](#-api-reference) β’ [Examples](#-usage-examples)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## π― What is SuprForm?
|
|
18
|
+
|
|
19
|
+
SuprForm is a **headless form library** that gives you complete control over your form's appearance while handling all the complex logic under the hood. Built on top of `react-hook-form`, it provides:
|
|
20
|
+
|
|
21
|
+
- β¨ **Zero UI dependencies** - Works with any design system (Material-UI, Ant Design, shadcn/ui, plain HTML)
|
|
22
|
+
- π― **Composable components** - Intuitive API with `SuprForm.Control` and `SuprForm.ControlArray`
|
|
23
|
+
- π **TypeScript-first** - Full type inference for field names, values, and validation
|
|
24
|
+
- ποΈ **Conditional logic** - Declarative field visibility and disability based on other fields
|
|
25
|
+
- β
**Powerful validation** - Sync and async validation with helpful error messages
|
|
26
|
+
- ποΈ **Imperative control** - Access form methods via ref for programmatic manipulation
|
|
27
|
+
- π¦ **Lightweight** - Only React as a peer dependency
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## π Table of Contents
|
|
32
|
+
|
|
33
|
+
- [Key Features](#-key-features)
|
|
34
|
+
- [Installation](#-installation)
|
|
35
|
+
- [Quick Start](#-quick-start)
|
|
36
|
+
- [Core Concepts](#-core-concepts)
|
|
37
|
+
- [Usage Examples](#-usage-examples)
|
|
38
|
+
- [Basic Form](#1-basic-form)
|
|
39
|
+
- [With UI Libraries](#2-with-ui-libraries-material-ui-antd-shadcnui)
|
|
40
|
+
- [Conditional Fields](#3-conditional-field-visibility)
|
|
41
|
+
- [Field Arrays](#4-dynamic-field-arrays)
|
|
42
|
+
- [Async Validation](#5-async-validation)
|
|
43
|
+
- [Form Control via Ref](#6-programmatic-form-control)
|
|
44
|
+
- [API Reference](#-api-reference)
|
|
45
|
+
- [TypeScript Guide](#-typescript-guide)
|
|
46
|
+
- [Styling](#-styling)
|
|
47
|
+
- [FAQs](#-frequently-asked-questions)
|
|
48
|
+
- [Contributing](#-contributing)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## β¨ Key Features
|
|
53
|
+
|
|
54
|
+
### π¨ Design System Agnostic
|
|
55
|
+
|
|
56
|
+
Use with **any** UI framework. SuprForm provides the logic, you control the design.
|
|
10
57
|
|
|
11
58
|
```tsx
|
|
12
59
|
// Works with Material-UI
|
|
@@ -16,63 +63,72 @@ Use with any UI frameworkβMaterial-UI, Ant Design, Tailwind, shadcn/ui, or pla
|
|
|
16
63
|
|
|
17
64
|
// Works with plain HTML
|
|
18
65
|
<SuprForm.Control name="email" rules={{ required: true }}>
|
|
19
|
-
<input type="email" />
|
|
66
|
+
<input type="email" className="my-input" />
|
|
67
|
+
</SuprForm.Control>
|
|
68
|
+
|
|
69
|
+
// Works with shadcn/ui
|
|
70
|
+
<SuprForm.Control name="email">
|
|
71
|
+
<Input type="email" />
|
|
20
72
|
</SuprForm.Control>
|
|
21
73
|
```
|
|
22
74
|
|
|
23
|
-
### π―
|
|
75
|
+
### π― Composable Field Control
|
|
24
76
|
|
|
25
|
-
The `SuprForm.Control` component
|
|
77
|
+
The `SuprForm.Control` component handles everything automatically:
|
|
26
78
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
79
|
+
- β
Label rendering with proper `htmlFor` linking
|
|
80
|
+
- β
Error message display (auto-styled but customizable)
|
|
81
|
+
- β
Validation on blur and change
|
|
82
|
+
- β
Field state management (value, touched, dirty)
|
|
83
|
+
- β
Works with **any** input component
|
|
31
84
|
|
|
32
|
-
### π
|
|
85
|
+
### π TypeScript-First Architecture
|
|
33
86
|
|
|
34
|
-
|
|
87
|
+
Full type inference for field names, values, and validation rules:
|
|
35
88
|
|
|
36
89
|
```tsx
|
|
37
|
-
interface
|
|
90
|
+
interface UserForm {
|
|
38
91
|
email: string;
|
|
39
92
|
age: number;
|
|
93
|
+
isSubscribed: boolean;
|
|
40
94
|
}
|
|
41
95
|
|
|
42
|
-
<SuprForm<
|
|
96
|
+
<SuprForm<UserForm>
|
|
43
97
|
onSubmit={(values) => {
|
|
44
|
-
// values is typed as
|
|
45
|
-
console.log(values.email); //
|
|
98
|
+
// β
values is fully typed as UserForm
|
|
99
|
+
console.log(values.email.toLowerCase()); // Type-safe!
|
|
46
100
|
}}
|
|
47
101
|
>
|
|
48
|
-
<SuprForm.Control name='email' /> {/*
|
|
102
|
+
<SuprForm.Control name='email' /> {/* β
Autocomplete works */}
|
|
103
|
+
<SuprForm.Control name='age' /> {/* β
Type-checked */}
|
|
104
|
+
<SuprForm.Control name='invalid' /> {/* β TypeScript error! */}
|
|
49
105
|
</SuprForm>;
|
|
50
106
|
```
|
|
51
107
|
|
|
52
|
-
### ποΈ
|
|
108
|
+
### ποΈ Conditional Field Visibility
|
|
53
109
|
|
|
54
|
-
|
|
110
|
+
Declaratively show/hide or enable/disable fields based on other field values:
|
|
55
111
|
|
|
56
112
|
```tsx
|
|
57
113
|
<SuprForm.Control
|
|
58
|
-
name='
|
|
114
|
+
name='promoCode'
|
|
59
115
|
visibility={{
|
|
60
116
|
operator: 'AND',
|
|
61
117
|
conditions: [
|
|
62
|
-
{ name: '
|
|
63
|
-
{ name: '
|
|
118
|
+
{ name: 'hasDiscount', operator: 'EQUALS', value: true },
|
|
119
|
+
{ name: 'orderTotal', operator: 'GREATER_THAN', value: 50 },
|
|
64
120
|
],
|
|
65
121
|
}}
|
|
66
122
|
>
|
|
67
|
-
<input />
|
|
123
|
+
<input type='text' />
|
|
68
124
|
</SuprForm.Control>
|
|
69
125
|
```
|
|
70
126
|
|
|
71
|
-
**Supported operators:** EQUALS
|
|
127
|
+
**Supported operators:** `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `STARTS_WITH`, `ENDS_WITH`, `INCLUDES`, `NOT_INCLUDES`
|
|
72
128
|
|
|
73
|
-
### β
|
|
129
|
+
### β
Powerful Validation
|
|
74
130
|
|
|
75
|
-
|
|
131
|
+
Sync and async validation with helpful error messages:
|
|
76
132
|
|
|
77
133
|
```tsx
|
|
78
134
|
<SuprForm.Control
|
|
@@ -81,8 +137,8 @@ Powered by react-hook-form's validation system with support for sync and async v
|
|
|
81
137
|
required: 'Username is required',
|
|
82
138
|
minLength: { value: 3, message: 'Min 3 characters' },
|
|
83
139
|
validate: async (value) => {
|
|
84
|
-
const available = await
|
|
85
|
-
return available || 'Username taken';
|
|
140
|
+
const available = await checkUsername(value);
|
|
141
|
+
return available || 'Username already taken';
|
|
86
142
|
},
|
|
87
143
|
}}
|
|
88
144
|
>
|
|
@@ -90,53 +146,73 @@ Powered by react-hook-form's validation system with support for sync and async v
|
|
|
90
146
|
</SuprForm.Control>
|
|
91
147
|
```
|
|
92
148
|
|
|
93
|
-
### ποΈ
|
|
149
|
+
### ποΈ Imperative Form Control
|
|
94
150
|
|
|
95
|
-
Access
|
|
151
|
+
Access form methods via ref for programmatic manipulation:
|
|
96
152
|
|
|
97
153
|
```tsx
|
|
98
|
-
const formRef = useRef();
|
|
154
|
+
const formRef = useRef<SuprFormRef<FormData>>();
|
|
99
155
|
|
|
100
156
|
<SuprForm ref={formRef} onSubmit={handleSubmit}>
|
|
101
157
|
{/* ... */}
|
|
102
158
|
</SuprForm>;
|
|
103
159
|
|
|
104
160
|
// Later:
|
|
105
|
-
formRef.current
|
|
106
|
-
formRef.current
|
|
107
|
-
formRef.current
|
|
161
|
+
formRef.current?.setValue('email', 'user@example.com');
|
|
162
|
+
formRef.current?.trigger('email'); // Validate
|
|
163
|
+
formRef.current?.reset(); // Reset form
|
|
108
164
|
```
|
|
109
165
|
|
|
110
|
-
|
|
166
|
+
### π Dynamic Field Arrays
|
|
111
167
|
|
|
112
|
-
|
|
168
|
+
Manage repeating field groups with `SuprForm.ControlArray`:
|
|
113
169
|
|
|
114
|
-
|
|
170
|
+
```tsx
|
|
171
|
+
<SuprForm.ControlArray name='hobbies' ref={arrayRef}>
|
|
172
|
+
<SuprForm.Control name='name' label='Hobby Name'>
|
|
173
|
+
<input />
|
|
174
|
+
</SuprForm.Control>
|
|
175
|
+
<SuprForm.Control name='years' label='Years'>
|
|
176
|
+
<input type='number' />
|
|
177
|
+
</SuprForm.Control>
|
|
178
|
+
</SuprForm.ControlArray>;
|
|
115
179
|
|
|
116
|
-
|
|
180
|
+
// Add/remove items:
|
|
181
|
+
arrayRef.current?.append({ name: '', years: 0 });
|
|
182
|
+
arrayRef.current?.remove(0);
|
|
183
|
+
```
|
|
117
184
|
|
|
118
|
-
|
|
185
|
+
---
|
|
119
186
|
|
|
120
|
-
|
|
187
|
+
## π¦ Installation
|
|
121
188
|
|
|
122
189
|
```bash
|
|
123
190
|
npm install suprform
|
|
124
191
|
```
|
|
125
192
|
|
|
126
|
-
|
|
193
|
+
**Peer dependencies:** React 18 or 19
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm install react react-dom
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## β‘ Quick Start
|
|
127
202
|
|
|
128
|
-
|
|
203
|
+
Here's a complete login form in under 30 lines:
|
|
129
204
|
|
|
130
205
|
```tsx
|
|
131
206
|
import SuprForm from 'suprform';
|
|
132
207
|
|
|
133
208
|
function LoginForm() {
|
|
209
|
+
const handleSubmit = (values: { email: string; password: string }) => {
|
|
210
|
+
console.log('Form submitted:', values);
|
|
211
|
+
// Call your API here
|
|
212
|
+
};
|
|
213
|
+
|
|
134
214
|
return (
|
|
135
|
-
<SuprForm
|
|
136
|
-
onSubmit={(values) => {
|
|
137
|
-
console.log('Form submitted:', values);
|
|
138
|
-
}}
|
|
139
|
-
>
|
|
215
|
+
<SuprForm onSubmit={handleSubmit}>
|
|
140
216
|
<SuprForm.Control
|
|
141
217
|
name='email'
|
|
142
218
|
label='Email Address'
|
|
@@ -156,7 +232,7 @@ function LoginForm() {
|
|
|
156
232
|
label='Password'
|
|
157
233
|
rules={{
|
|
158
234
|
required: 'Password is required',
|
|
159
|
-
minLength: { value: 8, message: '
|
|
235
|
+
minLength: { value: 8, message: 'Min 8 characters' },
|
|
160
236
|
}}
|
|
161
237
|
>
|
|
162
238
|
<input type='password' placeholder='β’β’β’β’β’β’β’β’' />
|
|
@@ -168,44 +244,99 @@ function LoginForm() {
|
|
|
168
244
|
}
|
|
169
245
|
```
|
|
170
246
|
|
|
171
|
-
SuprForm
|
|
247
|
+
**That's it!** SuprForm automatically handles:
|
|
248
|
+
|
|
249
|
+
- β
Form submission
|
|
250
|
+
- β
Validation on blur and submit
|
|
251
|
+
- β
Error message display
|
|
252
|
+
- β
Field state management
|
|
253
|
+
- β
Label rendering
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## π‘ Core Concepts
|
|
258
|
+
|
|
259
|
+
### 1. The `<SuprForm>` Component
|
|
260
|
+
|
|
261
|
+
The root component that wraps your form:
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
<SuprForm
|
|
265
|
+
onSubmit={(values) => console.log(values)}
|
|
266
|
+
onError={(errors) => console.error(errors)}
|
|
267
|
+
formOptions={{ mode: 'onBlur' }} // react-hook-form options
|
|
268
|
+
showAsterisk={true} // Show * on required fields
|
|
269
|
+
>
|
|
270
|
+
{/* Your form fields */}
|
|
271
|
+
</SuprForm>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 2. The `<SuprForm.Control>` Component
|
|
275
|
+
|
|
276
|
+
Wraps individual input fields:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
<SuprForm.Control
|
|
280
|
+
name="fieldName" // Required: field identifier
|
|
281
|
+
label="Field Label" // Optional: displays above input
|
|
282
|
+
rules={{ // Optional: validation rules
|
|
283
|
+
required: 'Required!',
|
|
284
|
+
minLength: { value: 3, message: 'Too short' }
|
|
285
|
+
}}
|
|
286
|
+
visibility={{ // Optional: conditional rendering
|
|
287
|
+
operator: 'AND',
|
|
288
|
+
conditions: [...]
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<input type="text" /> {/* Your input component */}
|
|
292
|
+
</SuprForm.Control>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 3. The `<SuprForm.ControlArray>` Component
|
|
296
|
+
|
|
297
|
+
Manages dynamic lists of fields:
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
const arrayRef = useRef<FormControlArrayRef>();
|
|
301
|
+
|
|
302
|
+
<SuprForm.ControlArray name="items" ref={arrayRef}>
|
|
303
|
+
<SuprForm.Control name="title">
|
|
304
|
+
<input />
|
|
305
|
+
</SuprForm.Control>
|
|
306
|
+
<SuprForm.Control name="quantity">
|
|
307
|
+
<input type="number" />
|
|
308
|
+
</SuprForm.Control>
|
|
309
|
+
</SuprForm.ControlArray>
|
|
172
310
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- Works with any input component (HTML, Material-UI, custom, etc.)
|
|
311
|
+
<button onClick={() => arrayRef.current?.append({ title: '', quantity: 0 })}>
|
|
312
|
+
Add Item
|
|
313
|
+
</button>
|
|
314
|
+
```
|
|
178
315
|
|
|
179
316
|
---
|
|
180
317
|
|
|
181
|
-
##
|
|
318
|
+
## π¨ Usage Examples
|
|
182
319
|
|
|
183
|
-
###
|
|
320
|
+
### 1. Basic Form
|
|
184
321
|
|
|
185
322
|
```tsx
|
|
186
323
|
import SuprForm from 'suprform';
|
|
187
|
-
import { TextField, Button } from '@mui/material'; // or your own
|
|
188
324
|
|
|
189
|
-
|
|
325
|
+
interface ContactForm {
|
|
326
|
+
name: string;
|
|
327
|
+
email: string;
|
|
328
|
+
message: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function ContactForm() {
|
|
190
332
|
return (
|
|
191
|
-
<SuprForm
|
|
333
|
+
<SuprForm<ContactForm>
|
|
192
334
|
onSubmit={(values) => {
|
|
193
|
-
console.log('
|
|
335
|
+
console.log('Submitting:', values);
|
|
194
336
|
}}
|
|
195
337
|
>
|
|
196
|
-
<SuprForm.Control
|
|
197
|
-
|
|
198
|
-
label='Username'
|
|
199
|
-
rules={{
|
|
200
|
-
required: 'Username is required',
|
|
201
|
-
minLength: { value: 3, message: 'Min 3 characters' },
|
|
202
|
-
pattern: {
|
|
203
|
-
value: /^[a-zA-Z0-9_]+$/,
|
|
204
|
-
message: 'Only letters, numbers, and underscores',
|
|
205
|
-
},
|
|
206
|
-
}}
|
|
207
|
-
>
|
|
208
|
-
<TextField variant='outlined' fullWidth />
|
|
338
|
+
<SuprForm.Control name='name' label='Your Name' rules={{ required: 'Name is required' }}>
|
|
339
|
+
<input type='text' />
|
|
209
340
|
</SuprForm.Control>
|
|
210
341
|
|
|
211
342
|
<SuprForm.Control
|
|
@@ -214,140 +345,411 @@ function SignupForm() {
|
|
|
214
345
|
rules={{
|
|
215
346
|
required: 'Email is required',
|
|
216
347
|
pattern: {
|
|
217
|
-
value:
|
|
348
|
+
value: /^\S+@\S+$/,
|
|
218
349
|
message: 'Invalid email',
|
|
219
350
|
},
|
|
220
351
|
}}
|
|
221
352
|
>
|
|
222
|
-
<
|
|
353
|
+
<input type='email' />
|
|
354
|
+
</SuprForm.Control>
|
|
355
|
+
|
|
356
|
+
<SuprForm.Control
|
|
357
|
+
name='message'
|
|
358
|
+
label='Message'
|
|
359
|
+
rules={{ minLength: { value: 10, message: 'Min 10 characters' } }}
|
|
360
|
+
>
|
|
361
|
+
<textarea rows={4} />
|
|
362
|
+
</SuprForm.Control>
|
|
363
|
+
|
|
364
|
+
<button type='submit'>Send Message</button>
|
|
365
|
+
</SuprForm>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 2. With UI Libraries (Material-UI, AntD, shadcn/ui)
|
|
371
|
+
|
|
372
|
+
SuprForm works seamlessly with any component library:
|
|
373
|
+
|
|
374
|
+
**Material-UI:**
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
import { TextField, Button, Checkbox, FormControlLabel } from '@mui/material';
|
|
378
|
+
import SuprForm from 'suprform';
|
|
379
|
+
|
|
380
|
+
function MUIForm() {
|
|
381
|
+
return (
|
|
382
|
+
<SuprForm onSubmit={(values) => console.log(values)}>
|
|
383
|
+
<SuprForm.Control name='username' label='Username' rules={{ required: 'Required' }}>
|
|
384
|
+
<TextField variant='outlined' fullWidth />
|
|
385
|
+
</SuprForm.Control>
|
|
386
|
+
|
|
387
|
+
<SuprForm.Control name='subscribe'>
|
|
388
|
+
<FormControlLabel control={<Checkbox />} label='Subscribe to newsletter' />
|
|
223
389
|
</SuprForm.Control>
|
|
224
390
|
|
|
225
391
|
<Button variant='contained' type='submit'>
|
|
226
|
-
|
|
392
|
+
Submit
|
|
227
393
|
</Button>
|
|
228
394
|
</SuprForm>
|
|
229
395
|
);
|
|
230
396
|
}
|
|
231
397
|
```
|
|
232
398
|
|
|
233
|
-
|
|
399
|
+
**shadcn/ui:**
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
import { Input } from '@/components/ui/input';
|
|
403
|
+
import { Button } from '@/components/ui/button';
|
|
404
|
+
import SuprForm from 'suprform';
|
|
405
|
+
|
|
406
|
+
function ShadcnForm() {
|
|
407
|
+
return (
|
|
408
|
+
<SuprForm onSubmit={(values) => console.log(values)}>
|
|
409
|
+
<SuprForm.Control name='email' label='Email' rules={{ required: true }}>
|
|
410
|
+
<Input type='email' placeholder='Email' />
|
|
411
|
+
</SuprForm.Control>
|
|
412
|
+
|
|
413
|
+
<Button type='submit'>Submit</Button>
|
|
414
|
+
</SuprForm>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 3. Conditional Field Visibility
|
|
234
420
|
|
|
235
421
|
Show/hide fields based on other field values:
|
|
236
422
|
|
|
237
423
|
```tsx
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
424
|
+
interface PaymentForm {
|
|
425
|
+
paymentMethod: 'card' | 'paypal' | 'cash';
|
|
426
|
+
cardNumber?: string;
|
|
427
|
+
paypalEmail?: string;
|
|
428
|
+
}
|
|
242
429
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
430
|
+
function PaymentForm() {
|
|
431
|
+
return (
|
|
432
|
+
<SuprForm<PaymentForm> onSubmit={(values) => console.log(values)}>
|
|
433
|
+
<SuprForm.Control name='paymentMethod' label='Payment Method'>
|
|
434
|
+
<select>
|
|
435
|
+
<option value='card'>Credit Card</option>
|
|
436
|
+
<option value='paypal'>PayPal</option>
|
|
437
|
+
<option value='cash'>Cash</option>
|
|
438
|
+
</select>
|
|
439
|
+
</SuprForm.Control>
|
|
440
|
+
|
|
441
|
+
{/* Only show when card is selected */}
|
|
442
|
+
<SuprForm.Control
|
|
443
|
+
name='cardNumber'
|
|
444
|
+
label='Card Number'
|
|
445
|
+
visibility={{
|
|
446
|
+
operator: 'AND',
|
|
447
|
+
conditions: [{ name: 'paymentMethod', operator: 'EQUALS', value: 'card' }],
|
|
448
|
+
}}
|
|
449
|
+
rules={{ required: 'Card number required' }}
|
|
450
|
+
>
|
|
451
|
+
<input type='text' placeholder='1234 5678 9012 3456' />
|
|
452
|
+
</SuprForm.Control>
|
|
453
|
+
|
|
454
|
+
{/* Only show when PayPal is selected */}
|
|
455
|
+
<SuprForm.Control
|
|
456
|
+
name='paypalEmail'
|
|
457
|
+
label='PayPal Email'
|
|
458
|
+
visibility={{
|
|
459
|
+
operator: 'AND',
|
|
460
|
+
conditions: [{ name: 'paymentMethod', operator: 'EQUALS', value: 'paypal' }],
|
|
461
|
+
}}
|
|
462
|
+
rules={{ required: 'PayPal email required' }}
|
|
463
|
+
>
|
|
464
|
+
<input type='email' />
|
|
465
|
+
</SuprForm.Control>
|
|
466
|
+
|
|
467
|
+
<button type='submit'>Submit Payment</button>
|
|
468
|
+
</SuprForm>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
256
471
|
```
|
|
257
472
|
|
|
258
|
-
|
|
473
|
+
**Complex conditions with OR:**
|
|
259
474
|
|
|
260
475
|
```tsx
|
|
261
476
|
<SuprForm.Control
|
|
262
|
-
name='
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
},
|
|
477
|
+
name='discountCode'
|
|
478
|
+
visibility={{
|
|
479
|
+
operator: 'OR', // Show if ANY condition is true
|
|
480
|
+
conditions: [
|
|
481
|
+
{ name: 'isVip', operator: 'EQUALS', value: true },
|
|
482
|
+
{ name: 'orderTotal', operator: 'GREATER_THAN', value: 100 },
|
|
483
|
+
],
|
|
270
484
|
}}
|
|
271
485
|
>
|
|
272
|
-
<input />
|
|
486
|
+
<input placeholder='Enter discount code' />
|
|
273
487
|
</SuprForm.Control>
|
|
274
488
|
```
|
|
275
489
|
|
|
276
|
-
###
|
|
490
|
+
### 4. Dynamic Field Arrays
|
|
491
|
+
|
|
492
|
+
Manage repeating field groups:
|
|
277
493
|
|
|
278
494
|
```tsx
|
|
279
|
-
|
|
280
|
-
|
|
495
|
+
import { useRef } from 'react';
|
|
496
|
+
import SuprForm, { FormControlArrayRef } from 'suprform';
|
|
497
|
+
|
|
498
|
+
interface Education {
|
|
499
|
+
school: string;
|
|
500
|
+
degree: string;
|
|
501
|
+
year: number;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
interface ResumeForm {
|
|
505
|
+
name: string;
|
|
506
|
+
education: Education[];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function ResumeForm() {
|
|
510
|
+
const educationRef = useRef<FormControlArrayRef<ResumeForm, 'education'>>();
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<SuprForm<ResumeForm>
|
|
514
|
+
onSubmit={(values) => console.log(values)}
|
|
515
|
+
formOptions={{
|
|
516
|
+
defaultValues: {
|
|
517
|
+
name: '',
|
|
518
|
+
education: [{ school: '', degree: '', year: 2024 }]
|
|
519
|
+
}
|
|
520
|
+
}}
|
|
521
|
+
>
|
|
522
|
+
<SuprForm.Control name="name" label="Full Name">
|
|
523
|
+
<input type="text" />
|
|
524
|
+
</SuprForm.Control>
|
|
525
|
+
|
|
526
|
+
<h3>Education</h3>
|
|
527
|
+
<SuprForm.ControlArray name="education" ref={educationRef}>
|
|
528
|
+
<div className="education-item">
|
|
529
|
+
<SuprForm.Control name="school" label="School">
|
|
530
|
+
<input type="text" />
|
|
531
|
+
</SuprForm.Control>
|
|
532
|
+
|
|
533
|
+
<SuprForm.Control name="degree" label="Degree">
|
|
534
|
+
<input type="text" />
|
|
535
|
+
</SuprForm.Control>
|
|
536
|
+
|
|
537
|
+
<SuprForm.Control name="year" label="Graduation Year">
|
|
538
|
+
<input type="number" />
|
|
539
|
+
</SuprForm.Control>
|
|
540
|
+
|
|
541
|
+
<button
|
|
542
|
+
type="button"
|
|
543
|
+
onClick={() => {
|
|
544
|
+
const index = /* get current index */;
|
|
545
|
+
educationRef.current?.remove(index);
|
|
546
|
+
}}
|
|
547
|
+
>
|
|
548
|
+
Remove
|
|
549
|
+
</button>
|
|
550
|
+
</div>
|
|
551
|
+
</SuprForm.ControlArray>
|
|
552
|
+
|
|
553
|
+
<button
|
|
554
|
+
type="button"
|
|
555
|
+
onClick={() => {
|
|
556
|
+
educationRef.current?.append({ school: '', degree: '', year: 2024 });
|
|
557
|
+
}}
|
|
558
|
+
>
|
|
559
|
+
+ Add Education
|
|
560
|
+
</button>
|
|
561
|
+
|
|
562
|
+
<button type="submit">Save Resume</button>
|
|
563
|
+
</SuprForm>
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Field Array Methods (via ref):**
|
|
569
|
+
|
|
570
|
+
- `append(value)` - Add item to end
|
|
571
|
+
- `prepend(value)` - Add item to beginning
|
|
572
|
+
- `insert(index, value)` - Insert at position
|
|
573
|
+
- `remove(index)` - Remove item
|
|
574
|
+
- `move(from, to)` - Reorder items
|
|
575
|
+
- `update(index, value)` - Update item
|
|
576
|
+
- `replace(values)` - Replace all items
|
|
577
|
+
|
|
578
|
+
### 5. Async Validation
|
|
579
|
+
|
|
580
|
+
Validate against your backend:
|
|
581
|
+
|
|
582
|
+
```tsx
|
|
583
|
+
function SignupForm() {
|
|
584
|
+
const checkUsernameAvailability = async (username: string) => {
|
|
585
|
+
const response = await fetch(`/api/check-username?name=${username}`);
|
|
586
|
+
const data = await response.json();
|
|
587
|
+
return data.available;
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
return (
|
|
591
|
+
<SuprForm onSubmit={(values) => console.log(values)}>
|
|
592
|
+
<SuprForm.Control
|
|
593
|
+
name='username'
|
|
594
|
+
label='Username'
|
|
595
|
+
rules={{
|
|
596
|
+
required: 'Username is required',
|
|
597
|
+
minLength: { value: 3, message: 'Min 3 characters' },
|
|
598
|
+
validate: async (value) => {
|
|
599
|
+
const available = await checkUsernameAvailability(value);
|
|
600
|
+
return available || 'Username already taken';
|
|
601
|
+
},
|
|
602
|
+
}}
|
|
603
|
+
>
|
|
604
|
+
<input type='text' />
|
|
605
|
+
</SuprForm.Control>
|
|
606
|
+
|
|
607
|
+
<SuprForm.Control
|
|
608
|
+
name='email'
|
|
609
|
+
label='Email'
|
|
610
|
+
rules={{
|
|
611
|
+
required: true,
|
|
612
|
+
validate: async (value) => {
|
|
613
|
+
const response = await fetch(`/api/check-email?email=${value}`);
|
|
614
|
+
const data = await response.json();
|
|
615
|
+
return data.available || 'Email already registered';
|
|
616
|
+
},
|
|
617
|
+
}}
|
|
618
|
+
>
|
|
619
|
+
<input type='email' />
|
|
620
|
+
</SuprForm.Control>
|
|
621
|
+
|
|
622
|
+
<button type='submit'>Sign Up</button>
|
|
623
|
+
</SuprForm>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### 6. Programmatic Form Control
|
|
629
|
+
|
|
630
|
+
Access form methods via ref:
|
|
631
|
+
|
|
632
|
+
```tsx
|
|
633
|
+
import { useRef } from 'react';
|
|
634
|
+
import SuprForm, { SuprFormRef } from 'suprform';
|
|
635
|
+
|
|
636
|
+
interface UserForm {
|
|
637
|
+
email: string;
|
|
638
|
+
password: string;
|
|
639
|
+
rememberMe: boolean;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function LoginFormWithRef() {
|
|
643
|
+
const formRef = useRef<SuprFormRef<UserForm>>();
|
|
281
644
|
|
|
282
645
|
const prefillForm = () => {
|
|
283
|
-
formRef.current
|
|
284
|
-
formRef.current
|
|
646
|
+
formRef.current?.setValue('email', 'demo@example.com');
|
|
647
|
+
formRef.current?.setValue('password', 'password123');
|
|
648
|
+
formRef.current?.setValue('rememberMe', true);
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const clearForm = () => {
|
|
652
|
+
formRef.current?.reset();
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const validateEmail = () => {
|
|
656
|
+
formRef.current?.trigger('email');
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const watchEmail = () => {
|
|
660
|
+
const email = formRef.current?.getValues('email');
|
|
661
|
+
console.log('Current email:', email);
|
|
285
662
|
};
|
|
286
663
|
|
|
287
664
|
return (
|
|
288
|
-
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
<
|
|
292
|
-
|
|
665
|
+
<div>
|
|
666
|
+
<div className='actions'>
|
|
667
|
+
<button onClick={prefillForm}>Prefill Demo</button>
|
|
668
|
+
<button onClick={clearForm}>Clear</button>
|
|
669
|
+
<button onClick={validateEmail}>Validate Email</button>
|
|
670
|
+
<button onClick={watchEmail}>Get Email Value</button>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
<SuprForm<UserForm> ref={formRef} onSubmit={(values) => console.log(values)}>
|
|
674
|
+
<SuprForm.Control name='email' label='Email'>
|
|
675
|
+
<input type='email' />
|
|
293
676
|
</SuprForm.Control>
|
|
294
|
-
|
|
677
|
+
|
|
678
|
+
<SuprForm.Control name='password' label='Password'>
|
|
295
679
|
<input type='password' />
|
|
296
680
|
</SuprForm.Control>
|
|
681
|
+
|
|
682
|
+
<SuprForm.Control name='rememberMe'>
|
|
683
|
+
<label>
|
|
684
|
+
<input type='checkbox' /> Remember me
|
|
685
|
+
</label>
|
|
686
|
+
</SuprForm.Control>
|
|
687
|
+
|
|
688
|
+
<button type='submit'>Login</button>
|
|
297
689
|
</SuprForm>
|
|
298
|
-
|
|
690
|
+
</div>
|
|
299
691
|
);
|
|
300
692
|
}
|
|
301
693
|
```
|
|
302
694
|
|
|
303
695
|
---
|
|
304
696
|
|
|
305
|
-
## API Reference
|
|
697
|
+
## π API Reference
|
|
306
698
|
|
|
307
699
|
### `<SuprForm>`
|
|
308
700
|
|
|
309
|
-
Root form component
|
|
701
|
+
Root form component.
|
|
310
702
|
|
|
311
|
-
|
|
703
|
+
#### Props
|
|
312
704
|
|
|
313
|
-
| Prop | Type | Description
|
|
314
|
-
| -------------- | --------------------- |
|
|
315
|
-
| `children` | `ReactNode` | Form fields and elements
|
|
316
|
-
| `onSubmit` | `(values: T) => void` | Called
|
|
317
|
-
| `onError` | `(errors) => void` | Called when form submission fails validation
|
|
318
|
-
| `formOptions` | `UseFormProps` | Options
|
|
319
|
-
| `className` | `string` | CSS class for `<form>` element
|
|
320
|
-
| `style` | `CSSProperties` | Inline styles for `<form>`
|
|
321
|
-
| `showAsterisk` | `boolean` | Show asterisk on required field labels
|
|
322
|
-
| `ref` | `Ref
|
|
705
|
+
| Prop | Type | Description |
|
|
706
|
+
| -------------- | --------------------- | ------------------------------------------------- |
|
|
707
|
+
| `children` | `ReactNode` | Form fields and elements |
|
|
708
|
+
| `onSubmit` | `(values: T) => void` | Called when form is valid and submitted |
|
|
709
|
+
| `onError` | `(errors) => void` | Called when form submission fails validation |
|
|
710
|
+
| `formOptions` | `UseFormProps` | Options passed to `react-hook-form`'s `useForm()` |
|
|
711
|
+
| `className` | `string` | CSS class for `<form>` element |
|
|
712
|
+
| `style` | `CSSProperties` | Inline styles for `<form>` |
|
|
713
|
+
| `showAsterisk` | `boolean` | Show red asterisk on required field labels |
|
|
714
|
+
| `ref` | `Ref<SuprFormRef>` | Access form methods imperatively |
|
|
715
|
+
| `onChange` | `(values: T) => void` | Called whenever any field value changes |
|
|
323
716
|
|
|
324
|
-
|
|
717
|
+
#### Example
|
|
325
718
|
|
|
326
719
|
```tsx
|
|
327
|
-
<SuprForm
|
|
720
|
+
<SuprForm
|
|
721
|
+
onSubmit={(values) => console.log('Submit:', values)}
|
|
722
|
+
onError={(errors) => console.error('Errors:', errors)}
|
|
723
|
+
formOptions={{
|
|
724
|
+
mode: 'onBlur', // When to validate
|
|
725
|
+
defaultValues: {...}, // Initial values
|
|
726
|
+
}}
|
|
727
|
+
showAsterisk={true}
|
|
728
|
+
onChange={(values) => console.log('Changed:', values)}
|
|
729
|
+
>
|
|
328
730
|
{/* fields */}
|
|
329
731
|
</SuprForm>
|
|
330
732
|
```
|
|
331
733
|
|
|
332
734
|
### `<SuprForm.Control>`
|
|
333
735
|
|
|
334
|
-
|
|
736
|
+
Field wrapper component.
|
|
335
737
|
|
|
336
|
-
|
|
738
|
+
#### Props
|
|
337
739
|
|
|
338
|
-
| Prop | Type | Description
|
|
339
|
-
| ------------------ | ----------------------- |
|
|
340
|
-
| `name`
|
|
341
|
-
| `children`
|
|
342
|
-
| `rules` | `RegisterOptions` | Validation rules (
|
|
343
|
-
| `label` | `string` | Label text
|
|
344
|
-
| `className` | `string` | CSS class for wrapper div
|
|
345
|
-
| `id` | `string` | HTML id (auto-generated if
|
|
346
|
-
| `disabled` | `boolean`
|
|
347
|
-
| `visibility` | `
|
|
348
|
-
| `shouldUnregister` | `boolean` | Unregister field when unmounted
|
|
740
|
+
| Prop | Type | Description |
|
|
741
|
+
| ------------------ | ----------------------- | --------------------------------------- |
|
|
742
|
+
| `name` β οΈ | `string` | **Required.** Field name (type-checked) |
|
|
743
|
+
| `children` β οΈ | `ReactElement` | **Required.** Your input component |
|
|
744
|
+
| `rules` | `RegisterOptions` | Validation rules (see below) |
|
|
745
|
+
| `label` | `string` | Label text rendered above input |
|
|
746
|
+
| `className` | `string` | CSS class for wrapper `<div>` |
|
|
747
|
+
| `id` | `string` | HTML id (auto-generated if omitted) |
|
|
748
|
+
| `disabled` | `boolean \| Visibility` | Disable field (can be conditional) |
|
|
749
|
+
| `visibility` | `boolean \| Visibility` | Show/hide field (can be conditional) |
|
|
750
|
+
| `shouldUnregister` | `boolean` | Unregister field when unmounted |
|
|
349
751
|
|
|
350
|
-
|
|
752
|
+
#### Example
|
|
351
753
|
|
|
352
754
|
```tsx
|
|
353
755
|
<SuprForm.Control
|
|
@@ -363,9 +765,38 @@ Composable field wrapper that handles labels, errors, and validation.
|
|
|
363
765
|
</SuprForm.Control>
|
|
364
766
|
```
|
|
365
767
|
|
|
768
|
+
### `<SuprForm.ControlArray>`
|
|
769
|
+
|
|
770
|
+
Dynamic field array component.
|
|
771
|
+
|
|
772
|
+
#### Props
|
|
773
|
+
|
|
774
|
+
| Prop | Type | Description |
|
|
775
|
+
| ------------- | ---------------------------- | -------------------------------- |
|
|
776
|
+
| `name` β οΈ | `string` | **Required.** Array field name |
|
|
777
|
+
| `children` β οΈ | `ReactNode` | **Required.** Field template |
|
|
778
|
+
| `ref` | `Ref<FormControlArrayRef>` | Access array methods |
|
|
779
|
+
| `rules` | `RegisterOptions` | Validation rules for array |
|
|
780
|
+
| `className` | `string` | CSS class for wrapper |
|
|
781
|
+
| `visibility` | `Record<string, Visibility>` | Conditional visibility per field |
|
|
782
|
+
| `disabled` | `Record<string, Visibility>` | Conditional disability per field |
|
|
783
|
+
|
|
784
|
+
#### Example
|
|
785
|
+
|
|
786
|
+
```tsx
|
|
787
|
+
<SuprForm.ControlArray name='hobbies' ref={arrayRef}>
|
|
788
|
+
<SuprForm.Control name='name'>
|
|
789
|
+
<input />
|
|
790
|
+
</SuprForm.Control>
|
|
791
|
+
<SuprForm.Control name='years'>
|
|
792
|
+
<input type='number' />
|
|
793
|
+
</SuprForm.Control>
|
|
794
|
+
</SuprForm.ControlArray>
|
|
795
|
+
```
|
|
796
|
+
|
|
366
797
|
### Validation Rules
|
|
367
798
|
|
|
368
|
-
SuprForm uses
|
|
799
|
+
SuprForm uses `react-hook-form` validation rules:
|
|
369
800
|
|
|
370
801
|
| Rule | Type | Description |
|
|
371
802
|
| ----------- | ----------------------------------------- | -------------------------- |
|
|
@@ -377,123 +808,806 @@ SuprForm uses [react-hook-form validation rules](https://react-hook-form.com/doc
|
|
|
377
808
|
| `pattern` | `{ value: RegExp, message: string }` | Regex pattern match |
|
|
378
809
|
| `validate` | `(value) => boolean \| string \| Promise` | Custom validation function |
|
|
379
810
|
|
|
811
|
+
#### Examples
|
|
812
|
+
|
|
813
|
+
```tsx
|
|
814
|
+
// Simple required
|
|
815
|
+
rules={{ required: true }}
|
|
816
|
+
|
|
817
|
+
// Required with message
|
|
818
|
+
rules={{ required: 'This field is required' }}
|
|
819
|
+
|
|
820
|
+
// Multiple rules
|
|
821
|
+
rules={{
|
|
822
|
+
required: 'Email is required',
|
|
823
|
+
pattern: {
|
|
824
|
+
value: /^\S+@\S+$/,
|
|
825
|
+
message: 'Invalid email format'
|
|
826
|
+
}
|
|
827
|
+
}}
|
|
828
|
+
|
|
829
|
+
// Custom validation
|
|
830
|
+
rules={{
|
|
831
|
+
validate: (value) => {
|
|
832
|
+
if (value.length < 8) return 'Too short';
|
|
833
|
+
if (!/[A-Z]/.test(value)) return 'Need uppercase';
|
|
834
|
+
return true; // Valid
|
|
835
|
+
}
|
|
836
|
+
}}
|
|
837
|
+
|
|
838
|
+
// Async validation
|
|
839
|
+
rules={{
|
|
840
|
+
validate: async (value) => {
|
|
841
|
+
const response = await fetch(`/api/check/${value}`);
|
|
842
|
+
const data = await response.json();
|
|
843
|
+
return data.valid || 'Invalid value';
|
|
844
|
+
}
|
|
845
|
+
}}
|
|
846
|
+
```
|
|
847
|
+
|
|
380
848
|
### Conditional Visibility
|
|
381
849
|
|
|
382
850
|
The `visibility` prop accepts:
|
|
383
851
|
|
|
384
|
-
```
|
|
852
|
+
```typescript
|
|
385
853
|
{
|
|
386
854
|
operator: 'AND' | 'OR',
|
|
387
855
|
conditions: [
|
|
388
856
|
{
|
|
389
|
-
name: 'fieldName',
|
|
390
|
-
operator: '
|
|
391
|
-
|
|
392
|
-
'STARTS_WITH' | 'ENDS_WITH' | 'INCLUDES' | 'NOT_INCLUDES',
|
|
393
|
-
value: any
|
|
857
|
+
name: 'fieldName', // Field to check
|
|
858
|
+
operator: '...', // Comparison operator
|
|
859
|
+
value: any // Value to compare
|
|
394
860
|
}
|
|
395
861
|
]
|
|
396
862
|
}
|
|
397
863
|
```
|
|
398
864
|
|
|
399
|
-
|
|
865
|
+
#### Operators
|
|
400
866
|
|
|
401
|
-
|
|
402
|
-
<SuprForm.Control
|
|
403
|
-
name='promoCode'
|
|
404
|
-
visibility={{
|
|
405
|
-
operator: 'OR',
|
|
406
|
-
conditions: [
|
|
407
|
-
{ name: 'isVip', operator: 'EQUALS', value: true },
|
|
408
|
-
{ name: 'orderTotal', operator: 'GREATER_THAN', value: 100 },
|
|
409
|
-
],
|
|
410
|
-
}}
|
|
411
|
-
>
|
|
412
|
-
<input />
|
|
413
|
-
</SuprForm.Control>
|
|
414
|
-
```
|
|
867
|
+
**String operators:** `EQUALS`, `NOT_EQUALS`, `STARTS_WITH`, `ENDS_WITH`, `INCLUDES`, `NOT_INCLUDES`
|
|
415
868
|
|
|
416
|
-
|
|
869
|
+
**Number operators:** `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`
|
|
417
870
|
|
|
418
|
-
|
|
871
|
+
**Boolean operators:** `EQUALS`, `NOT_EQUALS`
|
|
419
872
|
|
|
420
|
-
|
|
421
|
-
formRef.current.setValue(name, value) // Set field value
|
|
422
|
-
formRef.current.setError(name, error) // Set field error
|
|
423
|
-
formRef.current.clearErrors(name?) // Clear errors
|
|
424
|
-
formRef.current.getValues(name?) // Get field value(s)
|
|
425
|
-
formRef.current.reset(values?) // Reset form
|
|
426
|
-
formRef.current.setFocus(name) // Focus field
|
|
427
|
-
formRef.current.resetField(name) // Reset specific field
|
|
428
|
-
formRef.current.trigger(name?) // Trigger validation
|
|
429
|
-
formRef.current.unregister(name) // Unregister field
|
|
430
|
-
formRef.current.watch(name?) // Watch field value(s)
|
|
431
|
-
```
|
|
873
|
+
#### Examples
|
|
432
874
|
|
|
433
|
-
|
|
875
|
+
```tsx
|
|
876
|
+
// Show when checkbox is checked
|
|
877
|
+
visibility={{
|
|
878
|
+
operator: 'AND',
|
|
879
|
+
conditions: [
|
|
880
|
+
{ name: 'agreeToTerms', operator: 'EQUALS', value: true }
|
|
881
|
+
]
|
|
882
|
+
}}
|
|
883
|
+
|
|
884
|
+
// Show when amount > 100 OR user is VIP
|
|
885
|
+
visibility={{
|
|
886
|
+
operator: 'OR',
|
|
887
|
+
conditions: [
|
|
888
|
+
{ name: 'amount', operator: 'GREATER_THAN', value: 100 },
|
|
889
|
+
{ name: 'isVip', operator: 'EQUALS', value: true }
|
|
890
|
+
]
|
|
891
|
+
}}
|
|
892
|
+
|
|
893
|
+
// Show when username starts with 'admin'
|
|
894
|
+
visibility={{
|
|
895
|
+
operator: 'AND',
|
|
896
|
+
conditions: [
|
|
897
|
+
{ name: 'username', operator: 'STARTS_WITH', value: 'admin' }
|
|
898
|
+
]
|
|
899
|
+
}}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### Form Ref Methods
|
|
903
|
+
|
|
904
|
+
When you pass a `ref` to `<SuprForm>`, you get:
|
|
905
|
+
|
|
906
|
+
```typescript
|
|
907
|
+
interface SuprFormRef<T> {
|
|
908
|
+
setValue: (name: keyof T, value: any) => void;
|
|
909
|
+
setError: (name: keyof T, error: ErrorOption) => void;
|
|
910
|
+
clearErrors: (name?: keyof T | keyof T[]) => void;
|
|
911
|
+
getValues: (name?: keyof T) => any;
|
|
912
|
+
reset: (values?: Partial<T>) => void;
|
|
913
|
+
setFocus: (name: keyof T) => void;
|
|
914
|
+
resetField: (name: keyof T) => void;
|
|
915
|
+
trigger: (name?: keyof T | keyof T[]) => Promise<boolean>;
|
|
916
|
+
unregister: (name: keyof T) => void;
|
|
917
|
+
watch: (name?: keyof T) => any;
|
|
918
|
+
handleSubmit: (
|
|
919
|
+
onValid: (data: T) => void,
|
|
920
|
+
onInvalid?: (errors: any) => void
|
|
921
|
+
) => (e?: Event) => void;
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
#### Examples
|
|
926
|
+
|
|
927
|
+
```tsx
|
|
928
|
+
const formRef = useRef<SuprFormRef<FormData>>();
|
|
929
|
+
|
|
930
|
+
// Set field value
|
|
931
|
+
formRef.current?.setValue('email', 'user@example.com');
|
|
932
|
+
|
|
933
|
+
// Set field error
|
|
934
|
+
formRef.current?.setError('email', {
|
|
935
|
+
type: 'manual',
|
|
936
|
+
message: 'This email is already taken',
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// Clear all errors
|
|
940
|
+
formRef.current?.clearErrors();
|
|
941
|
+
|
|
942
|
+
// Get current values
|
|
943
|
+
const values = formRef.current?.getValues();
|
|
944
|
+
const email = formRef.current?.getValues('email');
|
|
945
|
+
|
|
946
|
+
// Reset form
|
|
947
|
+
formRef.current?.reset();
|
|
948
|
+
formRef.current?.reset({ email: 'default@example.com' });
|
|
949
|
+
|
|
950
|
+
// Focus field
|
|
951
|
+
formRef.current?.setFocus('email');
|
|
952
|
+
|
|
953
|
+
// Trigger validation
|
|
954
|
+
const isValid = await formRef.current?.trigger();
|
|
955
|
+
await formRef.current?.trigger('email'); // Validate single field
|
|
956
|
+
|
|
957
|
+
// Watch field value
|
|
958
|
+
const watchedEmail = formRef.current?.watch('email');
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
### Field Array Ref Methods
|
|
962
|
+
|
|
963
|
+
When you pass a `ref` to `<SuprForm.ControlArray>`:
|
|
964
|
+
|
|
965
|
+
```typescript
|
|
966
|
+
interface FormControlArrayRef<T, TArrayName> {
|
|
967
|
+
fields: Array<Record<'id', string> & FieldArrayItem>;
|
|
968
|
+
append: (value: FieldArrayItem) => void;
|
|
969
|
+
prepend: (value: FieldArrayItem) => void;
|
|
970
|
+
insert: (index: number, value: FieldArrayItem) => void;
|
|
971
|
+
remove: (index?: number | number[]) => void;
|
|
972
|
+
move: (from: number, to: number) => void;
|
|
973
|
+
update: (index: number, value: FieldArrayItem) => void;
|
|
974
|
+
replace: (values: FieldArrayItem[]) => void;
|
|
975
|
+
}
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
#### Examples
|
|
979
|
+
|
|
980
|
+
```tsx
|
|
981
|
+
const arrayRef = useRef<FormControlArrayRef>();
|
|
982
|
+
|
|
983
|
+
// Add item to end
|
|
984
|
+
arrayRef.current?.append({ name: '', value: '' });
|
|
985
|
+
|
|
986
|
+
// Add item to beginning
|
|
987
|
+
arrayRef.current?.prepend({ name: '', value: '' });
|
|
988
|
+
|
|
989
|
+
// Insert at position
|
|
990
|
+
arrayRef.current?.insert(1, { name: 'New', value: 'Item' });
|
|
991
|
+
|
|
992
|
+
// Remove item(s)
|
|
993
|
+
arrayRef.current?.remove(0);
|
|
994
|
+
arrayRef.current?.remove([0, 2, 4]);
|
|
995
|
+
|
|
996
|
+
// Move item
|
|
997
|
+
arrayRef.current?.move(0, 3);
|
|
998
|
+
|
|
999
|
+
// Update item
|
|
1000
|
+
arrayRef.current?.update(1, { name: 'Updated', value: 'Value' });
|
|
1001
|
+
|
|
1002
|
+
// Replace all items
|
|
1003
|
+
arrayRef.current?.replace([
|
|
1004
|
+
{ name: 'Item 1', value: '1' },
|
|
1005
|
+
{ name: 'Item 2', value: '2' },
|
|
1006
|
+
]);
|
|
1007
|
+
|
|
1008
|
+
// Access current fields
|
|
1009
|
+
console.log(arrayRef.current?.fields);
|
|
1010
|
+
```
|
|
434
1011
|
|
|
435
1012
|
---
|
|
436
1013
|
|
|
437
|
-
##
|
|
1014
|
+
## π· TypeScript Guide
|
|
1015
|
+
|
|
1016
|
+
SuprForm is built with TypeScript-first design.
|
|
1017
|
+
|
|
1018
|
+
### Type Your Form Data
|
|
1019
|
+
|
|
1020
|
+
```tsx
|
|
1021
|
+
interface SignupForm {
|
|
1022
|
+
username: string;
|
|
1023
|
+
email: string;
|
|
1024
|
+
age: number;
|
|
1025
|
+
agreeToTerms: boolean;
|
|
1026
|
+
hobbies: Array<{ name: string; years: number }>;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
<SuprForm<SignupForm>
|
|
1030
|
+
onSubmit={(values) => {
|
|
1031
|
+
// values is typed as SignupForm
|
|
1032
|
+
console.log(values.username); // β
Type-safe
|
|
1033
|
+
}}
|
|
1034
|
+
>
|
|
1035
|
+
{/* Field names are type-checked */}
|
|
1036
|
+
<SuprForm.Control name='username'>
|
|
1037
|
+
{' '}
|
|
1038
|
+
{/* β
Valid */}
|
|
1039
|
+
<input />
|
|
1040
|
+
</SuprForm.Control>
|
|
1041
|
+
|
|
1042
|
+
<SuprForm.Control name='invalidField'>
|
|
1043
|
+
{' '}
|
|
1044
|
+
{/* β TypeScript error */}
|
|
1045
|
+
<input />
|
|
1046
|
+
</SuprForm.Control>
|
|
1047
|
+
</SuprForm>;
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
### Type Form Refs
|
|
1051
|
+
|
|
1052
|
+
```tsx
|
|
1053
|
+
const formRef = useRef<SuprFormRef<SignupForm>>();
|
|
1054
|
+
|
|
1055
|
+
// All methods are type-safe
|
|
1056
|
+
formRef.current?.setValue('username', 'john'); // β
|
|
1057
|
+
formRef.current?.setValue('invalid', 'x'); // β Error
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
### Type Field Array Refs
|
|
1061
|
+
|
|
1062
|
+
```tsx
|
|
1063
|
+
const arrayRef = useRef<FormControlArrayRef<SignupForm, 'hobbies'>>();
|
|
1064
|
+
|
|
1065
|
+
// Methods know the shape of array items
|
|
1066
|
+
arrayRef.current?.append({ name: '', years: 0 }); // β
|
|
1067
|
+
arrayRef.current?.append({ invalid: 'x' }); // β Error
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
### Infer Types from Default Values
|
|
1071
|
+
|
|
1072
|
+
```tsx
|
|
1073
|
+
const defaultValues = {
|
|
1074
|
+
name: '',
|
|
1075
|
+
age: 0,
|
|
1076
|
+
email: '',
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
<SuprForm
|
|
1080
|
+
formOptions={{ defaultValues }}
|
|
1081
|
+
onSubmit={(values) => {
|
|
1082
|
+
// values is inferred as { name: string; age: number; email: string }
|
|
1083
|
+
}}
|
|
1084
|
+
>
|
|
1085
|
+
{/* ... */}
|
|
1086
|
+
</SuprForm>;
|
|
1087
|
+
```
|
|
438
1088
|
|
|
439
|
-
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
## π¨ Styling
|
|
1092
|
+
|
|
1093
|
+
SuprForm is **design system agnostic**. Style it however you want:
|
|
1094
|
+
|
|
1095
|
+
### CSS Classes
|
|
1096
|
+
|
|
1097
|
+
SuprForm adds these classes for customization:
|
|
440
1098
|
|
|
441
1099
|
```css
|
|
442
1100
|
.controlled-field {
|
|
443
|
-
/*
|
|
1101
|
+
/* Wrapper for each field */
|
|
444
1102
|
}
|
|
1103
|
+
|
|
445
1104
|
.controlled-field-label {
|
|
446
1105
|
/* Label element */
|
|
447
1106
|
}
|
|
1107
|
+
|
|
448
1108
|
.controlled-field-error {
|
|
449
|
-
/* Error message
|
|
1109
|
+
/* Error message */
|
|
1110
|
+
/* Default: color: red; font-size: 13px; */
|
|
450
1111
|
}
|
|
451
1112
|
```
|
|
452
1113
|
|
|
453
|
-
|
|
1114
|
+
### Tailwind CSS
|
|
454
1115
|
|
|
455
1116
|
```tsx
|
|
456
|
-
<SuprForm.Control name='email' className='mb-
|
|
457
|
-
<input className='w-full px-4 py-2 border rounded-lg focus:ring-2' />
|
|
1117
|
+
<SuprForm.Control name='email' className='mb-6'>
|
|
1118
|
+
<input className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500' />
|
|
458
1119
|
</SuprForm.Control>
|
|
459
1120
|
```
|
|
460
1121
|
|
|
461
|
-
|
|
1122
|
+
### CSS-in-JS (styled-components, emotion)
|
|
462
1123
|
|
|
463
1124
|
```tsx
|
|
1125
|
+
import styled from 'styled-components';
|
|
1126
|
+
|
|
464
1127
|
const StyledField = styled.div`
|
|
1128
|
+
.controlled-field {
|
|
1129
|
+
margin-bottom: 1rem;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
465
1132
|
.controlled-field-label {
|
|
466
1133
|
font-weight: 600;
|
|
1134
|
+
color: #333;
|
|
1135
|
+
margin-bottom: 0.5rem;
|
|
467
1136
|
}
|
|
1137
|
+
|
|
468
1138
|
.controlled-field-error {
|
|
469
|
-
color: #
|
|
1139
|
+
color: #dc2626;
|
|
1140
|
+
font-size: 0.875rem;
|
|
1141
|
+
margin-top: 0.25rem;
|
|
470
1142
|
}
|
|
471
1143
|
`;
|
|
472
1144
|
|
|
473
|
-
<SuprForm.Control className={StyledField}
|
|
1145
|
+
<SuprForm.Control name='email' className={StyledField}>
|
|
474
1146
|
<input />
|
|
475
1147
|
</SuprForm.Control>;
|
|
476
1148
|
```
|
|
477
1149
|
|
|
1150
|
+
### Global Styles
|
|
1151
|
+
|
|
1152
|
+
```css
|
|
1153
|
+
/* styles.css */
|
|
1154
|
+
.controlled-field {
|
|
1155
|
+
display: flex;
|
|
1156
|
+
flex-direction: column;
|
|
1157
|
+
gap: 8px;
|
|
1158
|
+
margin-bottom: 16px;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.controlled-field-label {
|
|
1162
|
+
font-weight: 500;
|
|
1163
|
+
color: #1a1a1a;
|
|
1164
|
+
font-size: 14px;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.controlled-field-error {
|
|
1168
|
+
color: #ef4444;
|
|
1169
|
+
font-size: 13px;
|
|
1170
|
+
margin-top: 4px;
|
|
1171
|
+
}
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
### Override Component Styles
|
|
1175
|
+
|
|
1176
|
+
```tsx
|
|
1177
|
+
<SuprForm.Control name='email' className='custom-field'>
|
|
1178
|
+
<input
|
|
1179
|
+
style={{
|
|
1180
|
+
padding: '12px',
|
|
1181
|
+
border: '2px solid #e5e7eb',
|
|
1182
|
+
borderRadius: '8px',
|
|
1183
|
+
}}
|
|
1184
|
+
/>
|
|
1185
|
+
</SuprForm.Control>
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
---
|
|
1189
|
+
|
|
1190
|
+
## β Frequently Asked Questions
|
|
1191
|
+
|
|
1192
|
+
### Can I use SuprForm with Material-UI / Ant Design / shadcn/ui?
|
|
1193
|
+
|
|
1194
|
+
**Yes!** SuprForm is design system agnostic. Just pass your component library's input components as children:
|
|
1195
|
+
|
|
1196
|
+
```tsx
|
|
1197
|
+
// Material-UI
|
|
1198
|
+
<SuprForm.Control name="email">
|
|
1199
|
+
<TextField variant="outlined" />
|
|
1200
|
+
</SuprForm.Control>
|
|
1201
|
+
|
|
1202
|
+
// Ant Design
|
|
1203
|
+
<SuprForm.Control name="username">
|
|
1204
|
+
<Input placeholder="Username" />
|
|
1205
|
+
</SuprForm.Control>
|
|
1206
|
+
|
|
1207
|
+
// shadcn/ui
|
|
1208
|
+
<SuprForm.Control name="password">
|
|
1209
|
+
<Input type="password" />
|
|
1210
|
+
</SuprForm.Control>
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
### How do I set default values?
|
|
1214
|
+
|
|
1215
|
+
Use the `formOptions.defaultValues` prop:
|
|
1216
|
+
|
|
1217
|
+
```tsx
|
|
1218
|
+
<SuprForm
|
|
1219
|
+
formOptions={{
|
|
1220
|
+
defaultValues: {
|
|
1221
|
+
email: 'user@example.com',
|
|
1222
|
+
age: 25,
|
|
1223
|
+
agreeToTerms: false,
|
|
1224
|
+
},
|
|
1225
|
+
}}
|
|
1226
|
+
onSubmit={handleSubmit}
|
|
1227
|
+
>
|
|
1228
|
+
{/* fields */}
|
|
1229
|
+
</SuprForm>
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
### How do I reset the form after submission?
|
|
1233
|
+
|
|
1234
|
+
Use a ref and call `reset()`:
|
|
1235
|
+
|
|
1236
|
+
```tsx
|
|
1237
|
+
const formRef = useRef<SuprFormRef>();
|
|
1238
|
+
|
|
1239
|
+
<SuprForm
|
|
1240
|
+
ref={formRef}
|
|
1241
|
+
onSubmit={(values) => {
|
|
1242
|
+
console.log(values);
|
|
1243
|
+
formRef.current?.reset(); // Reset after submit
|
|
1244
|
+
}}
|
|
1245
|
+
>
|
|
1246
|
+
{/* fields */}
|
|
1247
|
+
</SuprForm>;
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
### How do I validate on change instead of on blur?
|
|
1251
|
+
|
|
1252
|
+
Pass `mode` in `formOptions`:
|
|
1253
|
+
|
|
1254
|
+
```tsx
|
|
1255
|
+
<SuprForm formOptions={{ mode: 'onChange' }} onSubmit={handleSubmit}>
|
|
1256
|
+
{/* fields */}
|
|
1257
|
+
</SuprForm>
|
|
1258
|
+
```
|
|
1259
|
+
|
|
1260
|
+
Options: `onBlur` (default), `onChange`, `onSubmit`, `onTouched`, `all`
|
|
1261
|
+
|
|
1262
|
+
### Can I use custom validation functions?
|
|
1263
|
+
|
|
1264
|
+
Yes! Use the `validate` rule:
|
|
1265
|
+
|
|
1266
|
+
```tsx
|
|
1267
|
+
<SuprForm.Control
|
|
1268
|
+
name='password'
|
|
1269
|
+
rules={{
|
|
1270
|
+
validate: (value) => {
|
|
1271
|
+
if (value.length < 8) return 'Min 8 characters';
|
|
1272
|
+
if (!/[A-Z]/.test(value)) return 'Need uppercase letter';
|
|
1273
|
+
if (!/[0-9]/.test(value)) return 'Need number';
|
|
1274
|
+
return true;
|
|
1275
|
+
},
|
|
1276
|
+
}}
|
|
1277
|
+
>
|
|
1278
|
+
<input type='password' />
|
|
1279
|
+
</SuprForm.Control>
|
|
1280
|
+
```
|
|
1281
|
+
|
|
1282
|
+
### How do I access form values outside the form?
|
|
1283
|
+
|
|
1284
|
+
Use a ref and `getValues()`:
|
|
1285
|
+
|
|
1286
|
+
```tsx
|
|
1287
|
+
const formRef = useRef<SuprFormRef>();
|
|
1288
|
+
|
|
1289
|
+
<SuprForm ref={formRef} onSubmit={handleSubmit}>
|
|
1290
|
+
{/* fields */}
|
|
1291
|
+
</SuprForm>
|
|
1292
|
+
|
|
1293
|
+
<button onClick={() => {
|
|
1294
|
+
const values = formRef.current?.getValues();
|
|
1295
|
+
console.log(values);
|
|
1296
|
+
}}>
|
|
1297
|
+
Log Values
|
|
1298
|
+
</button>
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
### Can I watch field values in real-time?
|
|
1302
|
+
|
|
1303
|
+
Yes! Use the `onChange` prop:
|
|
1304
|
+
|
|
1305
|
+
```tsx
|
|
1306
|
+
<SuprForm
|
|
1307
|
+
onChange={(values) => {
|
|
1308
|
+
console.log('Form changed:', values);
|
|
1309
|
+
}}
|
|
1310
|
+
onSubmit={handleSubmit}
|
|
1311
|
+
>
|
|
1312
|
+
{/* fields */}
|
|
1313
|
+
</SuprForm>
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
Or use the ref's `watch()` method:
|
|
1317
|
+
|
|
1318
|
+
```tsx
|
|
1319
|
+
const formRef = useRef<SuprFormRef>();
|
|
1320
|
+
|
|
1321
|
+
useEffect(() => {
|
|
1322
|
+
const email = formRef.current?.watch('email');
|
|
1323
|
+
console.log('Email:', email);
|
|
1324
|
+
}, []);
|
|
1325
|
+
```
|
|
1326
|
+
|
|
1327
|
+
### How do I handle form submission errors?
|
|
1328
|
+
|
|
1329
|
+
Use the `onError` prop:
|
|
1330
|
+
|
|
1331
|
+
```tsx
|
|
1332
|
+
<SuprForm
|
|
1333
|
+
onSubmit={(values) => console.log('Success:', values)}
|
|
1334
|
+
onError={(errors) => {
|
|
1335
|
+
console.error('Validation errors:', errors);
|
|
1336
|
+
// Show notification, etc.
|
|
1337
|
+
}}
|
|
1338
|
+
>
|
|
1339
|
+
{/* fields */}
|
|
1340
|
+
</SuprForm>
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
### Can I show a loading state during async validation?
|
|
1344
|
+
|
|
1345
|
+
Yes! Track the form's `isValidating` state:
|
|
1346
|
+
|
|
1347
|
+
```tsx
|
|
1348
|
+
function MyForm() {
|
|
1349
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
1350
|
+
|
|
1351
|
+
return (
|
|
1352
|
+
<SuprForm onSubmit={handleSubmit}>
|
|
1353
|
+
<SuprForm.Control
|
|
1354
|
+
name='username'
|
|
1355
|
+
rules={{
|
|
1356
|
+
validate: async (value) => {
|
|
1357
|
+
setIsValidating(true);
|
|
1358
|
+
const available = await checkUsername(value);
|
|
1359
|
+
setIsValidating(false);
|
|
1360
|
+
return available || 'Username taken';
|
|
1361
|
+
},
|
|
1362
|
+
}}
|
|
1363
|
+
>
|
|
1364
|
+
<input disabled={isValidating} />
|
|
1365
|
+
</SuprForm.Control>
|
|
1366
|
+
|
|
1367
|
+
{isValidating && <span>Checking...</span>}
|
|
1368
|
+
</SuprForm>
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
### How do I conditionally disable fields?
|
|
1374
|
+
|
|
1375
|
+
Use the `disabled` prop with a visibility object:
|
|
1376
|
+
|
|
1377
|
+
```tsx
|
|
1378
|
+
<SuprForm.Control
|
|
1379
|
+
name='creditCard'
|
|
1380
|
+
disabled={{
|
|
1381
|
+
operator: 'AND',
|
|
1382
|
+
conditions: [{ name: 'paymentMethod', operator: 'NOT_EQUALS', value: 'card' }],
|
|
1383
|
+
}}
|
|
1384
|
+
>
|
|
1385
|
+
<input />
|
|
1386
|
+
</SuprForm.Control>
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
Or use a simple boolean:
|
|
1390
|
+
|
|
1391
|
+
```tsx
|
|
1392
|
+
<SuprForm.Control name='email' disabled={true}>
|
|
1393
|
+
<input />
|
|
1394
|
+
</SuprForm.Control>
|
|
1395
|
+
```
|
|
1396
|
+
|
|
478
1397
|
---
|
|
479
1398
|
|
|
480
|
-
##
|
|
1399
|
+
## π Advanced Topics
|
|
1400
|
+
|
|
1401
|
+
### Nested Objects
|
|
1402
|
+
|
|
1403
|
+
SuprForm supports nested field names using dot notation:
|
|
1404
|
+
|
|
1405
|
+
```tsx
|
|
1406
|
+
interface UserForm {
|
|
1407
|
+
name: string;
|
|
1408
|
+
address: {
|
|
1409
|
+
street: string;
|
|
1410
|
+
city: string;
|
|
1411
|
+
zip: string;
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
<SuprForm<UserForm> onSubmit={handleSubmit}>
|
|
1416
|
+
<SuprForm.Control name='name'>
|
|
1417
|
+
<input />
|
|
1418
|
+
</SuprForm.Control>
|
|
1419
|
+
|
|
1420
|
+
<SuprForm.Control name='address.street'>
|
|
1421
|
+
<input />
|
|
1422
|
+
</SuprForm.Control>
|
|
1423
|
+
|
|
1424
|
+
<SuprForm.Control name='address.city'>
|
|
1425
|
+
<input />
|
|
1426
|
+
</SuprForm.Control>
|
|
1427
|
+
|
|
1428
|
+
<SuprForm.Control name='address.zip'>
|
|
1429
|
+
<input />
|
|
1430
|
+
</SuprForm.Control>
|
|
1431
|
+
</SuprForm>;
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
### Custom Error Messages
|
|
1435
|
+
|
|
1436
|
+
Customize error display:
|
|
1437
|
+
|
|
1438
|
+
```tsx
|
|
1439
|
+
import { useFormContext } from 'react-hook-form';
|
|
1440
|
+
|
|
1441
|
+
function CustomFormControl({ name, children, label, rules }) {
|
|
1442
|
+
const {
|
|
1443
|
+
control,
|
|
1444
|
+
formState: { errors },
|
|
1445
|
+
} = useFormContext();
|
|
1446
|
+
const error = errors[name];
|
|
1447
|
+
|
|
1448
|
+
return (
|
|
1449
|
+
<div>
|
|
1450
|
+
<label>{label}</label>
|
|
1451
|
+
{children}
|
|
1452
|
+
{error && <div className='custom-error'>β οΈ {error.message}</div>}
|
|
1453
|
+
</div>
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
### Server-Side Validation
|
|
1459
|
+
|
|
1460
|
+
Handle server errors:
|
|
1461
|
+
|
|
1462
|
+
```tsx
|
|
1463
|
+
const formRef = useRef<SuprFormRef>();
|
|
1464
|
+
|
|
1465
|
+
const handleSubmit = async (values) => {
|
|
1466
|
+
try {
|
|
1467
|
+
await api.submitForm(values);
|
|
1468
|
+
} catch (error) {
|
|
1469
|
+
// Set server errors on fields
|
|
1470
|
+
if (error.field === 'email') {
|
|
1471
|
+
formRef.current?.setError('email', {
|
|
1472
|
+
type: 'server',
|
|
1473
|
+
message: error.message,
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
<SuprForm ref={formRef} onSubmit={handleSubmit}>
|
|
1480
|
+
{/* fields */}
|
|
1481
|
+
</SuprForm>;
|
|
1482
|
+
```
|
|
1483
|
+
|
|
1484
|
+
### Dependent Field Validation
|
|
1485
|
+
|
|
1486
|
+
Validate one field based on another:
|
|
1487
|
+
|
|
1488
|
+
```tsx
|
|
1489
|
+
<SuprForm.Control
|
|
1490
|
+
name="password"
|
|
1491
|
+
rules={{ required: 'Password required' }}
|
|
1492
|
+
>
|
|
1493
|
+
<input type="password" />
|
|
1494
|
+
</SuprForm.Control>
|
|
1495
|
+
|
|
1496
|
+
<SuprForm.Control
|
|
1497
|
+
name="confirmPassword"
|
|
1498
|
+
rules={{
|
|
1499
|
+
required: 'Confirm password',
|
|
1500
|
+
validate: (value, formValues) => {
|
|
1501
|
+
return value === formValues.password || 'Passwords must match';
|
|
1502
|
+
}
|
|
1503
|
+
}}
|
|
1504
|
+
>
|
|
1505
|
+
<input type="password" />
|
|
1506
|
+
</SuprForm.Control>
|
|
1507
|
+
```
|
|
1508
|
+
|
|
1509
|
+
---
|
|
1510
|
+
|
|
1511
|
+
## π€ Contributing
|
|
1512
|
+
|
|
1513
|
+
Contributions are welcome! Here's how to get started:
|
|
1514
|
+
|
|
1515
|
+
### Development Setup
|
|
481
1516
|
|
|
482
1517
|
```bash
|
|
1518
|
+
# Clone the repository
|
|
1519
|
+
git clone https://github.com/Albinbritto/suprform.git
|
|
1520
|
+
cd suprform
|
|
1521
|
+
|
|
1522
|
+
# Install dependencies
|
|
1523
|
+
npm install
|
|
1524
|
+
|
|
1525
|
+
# Run tests
|
|
1526
|
+
npm test
|
|
1527
|
+
|
|
1528
|
+
# Run tests in watch mode
|
|
1529
|
+
npm run test:watch
|
|
1530
|
+
|
|
1531
|
+
# Build the library
|
|
1532
|
+
npm run build
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
### Project Structure
|
|
1536
|
+
|
|
1537
|
+
```
|
|
1538
|
+
suprform/
|
|
1539
|
+
βββ src/
|
|
1540
|
+
β βββ components/
|
|
1541
|
+
β β βββ SuprForm.tsx # Root form component
|
|
1542
|
+
β β βββ FormControl.tsx # Field wrapper
|
|
1543
|
+
β β βββ FormControlArray.tsx # Dynamic arrays
|
|
1544
|
+
β β βββ ConditionChecker.tsx # Visibility logic
|
|
1545
|
+
β β βββ DisabilityChecker.tsx # Disability logic
|
|
1546
|
+
β βββ context/
|
|
1547
|
+
β β βββ SuprFormContext.tsx # Form context
|
|
1548
|
+
β βββ type.ts # TypeScript types
|
|
1549
|
+
β βββ util.ts # Helper functions
|
|
1550
|
+
β βββ index.ts # Public exports
|
|
1551
|
+
βββ dist/ # Build output
|
|
1552
|
+
βββ package.json
|
|
1553
|
+
βββ README.md
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
### Running Storybook
|
|
1557
|
+
|
|
1558
|
+
```bash
|
|
1559
|
+
npm run storybook
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
### Publishing
|
|
1563
|
+
|
|
1564
|
+
```bash
|
|
1565
|
+
# Login to npm
|
|
483
1566
|
npm login
|
|
1567
|
+
|
|
1568
|
+
# Build and publish
|
|
484
1569
|
npm run build
|
|
485
|
-
npm version patch
|
|
486
|
-
npm publish --access public
|
|
1570
|
+
npm version patch # or minor/major
|
|
1571
|
+
npm publish --access public
|
|
487
1572
|
```
|
|
488
1573
|
|
|
489
|
-
|
|
1574
|
+
### Submitting Pull Requests
|
|
1575
|
+
|
|
1576
|
+
1. Fork the repository
|
|
1577
|
+
2. Create a feature branch: `git checkout -b feature/your-feature`
|
|
1578
|
+
3. Make your changes
|
|
1579
|
+
4. Write/update tests
|
|
1580
|
+
5. Run tests: `npm test`
|
|
1581
|
+
6. Commit: `git commit -m "Add your feature"`
|
|
1582
|
+
7. Push: `git push origin feature/your-feature`
|
|
1583
|
+
8. Open a Pull Request
|
|
490
1584
|
|
|
491
1585
|
---
|
|
492
1586
|
|
|
493
|
-
## License
|
|
1587
|
+
## π License
|
|
494
1588
|
|
|
495
|
-
MIT
|
|
1589
|
+
MIT Β© [Albin Britto](https://github.com/Albinbritto)
|
|
496
1590
|
|
|
497
|
-
|
|
1591
|
+
---
|
|
1592
|
+
|
|
1593
|
+
## π Links
|
|
1594
|
+
|
|
1595
|
+
- **GitHub:** [https://github.com/Albinbritto/suprform](https://github.com/Albinbritto/suprform)
|
|
1596
|
+
- **npm:** [https://www.npmjs.com/package/suprform](https://www.npmjs.com/package/suprform)
|
|
1597
|
+
- **Issues:** [https://github.com/Albinbritto/suprform/issues](https://github.com/Albinbritto/suprform/issues)
|
|
1598
|
+
|
|
1599
|
+
---
|
|
1600
|
+
|
|
1601
|
+
## π Support
|
|
1602
|
+
|
|
1603
|
+
If you find SuprForm helpful, please:
|
|
1604
|
+
|
|
1605
|
+
- β Star the repository
|
|
1606
|
+
- π Report bugs
|
|
1607
|
+
- π‘ Suggest features
|
|
1608
|
+
- π Improve documentation
|
|
1609
|
+
- π€ Contribute code
|
|
1610
|
+
|
|
1611
|
+
---
|
|
498
1612
|
|
|
499
|
-
|
|
1613
|
+
**Made with β€οΈ by [Albin Britto](https://github.com/Albinbritto)**
|