suprform 1.1.5 β 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 +1302 -225
- 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 +470 -449
- package/dist/suprform.es.js.map +1 -1
- package/dist/type.d.ts +4 -0
- package/dist/type.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,49 +1,59 @@
|
|
|
1
|
-
# SuprForm
|
|
1
|
+
# SuprForm π
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
- packages/site β a Next.js site to market the library with examples
|
|
5
|
+
**A headless, TypeScript-first React form library for managing complex state, validation, and conditional logic**
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/suprform)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
[Features](#-key-features) β’ [Installation](#-installation) β’ [Quick Start](#-quick-start) β’ [Documentation](#-api-reference) β’ [Examples](#-usage-examples)
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
</div>
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
npm install
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
- Build all workspaces:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm run build
|
|
22
|
-
```
|
|
15
|
+
---
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
## π― What is SuprForm?
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
npm run dev:site
|
|
28
|
-
```
|
|
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:
|
|
29
20
|
|
|
30
|
-
|
|
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
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
---
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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)
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
---
|
|
41
51
|
|
|
42
|
-
##
|
|
52
|
+
## β¨ Key Features
|
|
43
53
|
|
|
44
|
-
### π¨
|
|
54
|
+
### π¨ Design System Agnostic
|
|
45
55
|
|
|
46
|
-
Use with any UI framework
|
|
56
|
+
Use with **any** UI framework. SuprForm provides the logic, you control the design.
|
|
47
57
|
|
|
48
58
|
```tsx
|
|
49
59
|
// Works with Material-UI
|
|
@@ -53,63 +63,72 @@ Use with any UI frameworkβMaterial-UI, Ant Design, Tailwind, shadcn/ui, or pla
|
|
|
53
63
|
|
|
54
64
|
// Works with plain HTML
|
|
55
65
|
<SuprForm.Control name="email" rules={{ required: true }}>
|
|
56
|
-
<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" />
|
|
57
72
|
</SuprForm.Control>
|
|
58
73
|
```
|
|
59
74
|
|
|
60
|
-
### π―
|
|
75
|
+
### π― Composable Field Control
|
|
61
76
|
|
|
62
|
-
The `SuprForm.Control` component
|
|
77
|
+
The `SuprForm.Control` component handles everything automatically:
|
|
63
78
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
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
|
|
68
84
|
|
|
69
|
-
### π
|
|
85
|
+
### π TypeScript-First Architecture
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
Full type inference for field names, values, and validation rules:
|
|
72
88
|
|
|
73
89
|
```tsx
|
|
74
|
-
interface
|
|
90
|
+
interface UserForm {
|
|
75
91
|
email: string;
|
|
76
92
|
age: number;
|
|
93
|
+
isSubscribed: boolean;
|
|
77
94
|
}
|
|
78
95
|
|
|
79
|
-
<SuprForm<
|
|
96
|
+
<SuprForm<UserForm>
|
|
80
97
|
onSubmit={(values) => {
|
|
81
|
-
// values is typed as
|
|
82
|
-
console.log(values.email); //
|
|
98
|
+
// β
values is fully typed as UserForm
|
|
99
|
+
console.log(values.email.toLowerCase()); // Type-safe!
|
|
83
100
|
}}
|
|
84
101
|
>
|
|
85
|
-
<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! */}
|
|
86
105
|
</SuprForm>;
|
|
87
106
|
```
|
|
88
107
|
|
|
89
|
-
### ποΈ
|
|
108
|
+
### ποΈ Conditional Field Visibility
|
|
90
109
|
|
|
91
|
-
|
|
110
|
+
Declaratively show/hide or enable/disable fields based on other field values:
|
|
92
111
|
|
|
93
112
|
```tsx
|
|
94
113
|
<SuprForm.Control
|
|
95
|
-
name='
|
|
114
|
+
name='promoCode'
|
|
96
115
|
visibility={{
|
|
97
116
|
operator: 'AND',
|
|
98
117
|
conditions: [
|
|
99
|
-
{ name: '
|
|
100
|
-
{ name: '
|
|
118
|
+
{ name: 'hasDiscount', operator: 'EQUALS', value: true },
|
|
119
|
+
{ name: 'orderTotal', operator: 'GREATER_THAN', value: 50 },
|
|
101
120
|
],
|
|
102
121
|
}}
|
|
103
122
|
>
|
|
104
|
-
<input />
|
|
123
|
+
<input type='text' />
|
|
105
124
|
</SuprForm.Control>
|
|
106
125
|
```
|
|
107
126
|
|
|
108
|
-
**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`
|
|
109
128
|
|
|
110
|
-
### β
|
|
129
|
+
### β
Powerful Validation
|
|
111
130
|
|
|
112
|
-
|
|
131
|
+
Sync and async validation with helpful error messages:
|
|
113
132
|
|
|
114
133
|
```tsx
|
|
115
134
|
<SuprForm.Control
|
|
@@ -118,8 +137,8 @@ Powered by react-hook-form's validation system with support for sync and async v
|
|
|
118
137
|
required: 'Username is required',
|
|
119
138
|
minLength: { value: 3, message: 'Min 3 characters' },
|
|
120
139
|
validate: async (value) => {
|
|
121
|
-
const available = await
|
|
122
|
-
return available || 'Username taken';
|
|
140
|
+
const available = await checkUsername(value);
|
|
141
|
+
return available || 'Username already taken';
|
|
123
142
|
},
|
|
124
143
|
}}
|
|
125
144
|
>
|
|
@@ -127,53 +146,73 @@ Powered by react-hook-form's validation system with support for sync and async v
|
|
|
127
146
|
</SuprForm.Control>
|
|
128
147
|
```
|
|
129
148
|
|
|
130
|
-
### ποΈ
|
|
149
|
+
### ποΈ Imperative Form Control
|
|
131
150
|
|
|
132
|
-
Access
|
|
151
|
+
Access form methods via ref for programmatic manipulation:
|
|
133
152
|
|
|
134
153
|
```tsx
|
|
135
|
-
const formRef = useRef();
|
|
154
|
+
const formRef = useRef<SuprFormRef<FormData>>();
|
|
136
155
|
|
|
137
156
|
<SuprForm ref={formRef} onSubmit={handleSubmit}>
|
|
138
157
|
{/* ... */}
|
|
139
158
|
</SuprForm>;
|
|
140
159
|
|
|
141
160
|
// Later:
|
|
142
|
-
formRef.current
|
|
143
|
-
formRef.current
|
|
144
|
-
formRef.current
|
|
161
|
+
formRef.current?.setValue('email', 'user@example.com');
|
|
162
|
+
formRef.current?.trigger('email'); // Validate
|
|
163
|
+
formRef.current?.reset(); // Reset form
|
|
145
164
|
```
|
|
146
165
|
|
|
147
|
-
|
|
166
|
+
### π Dynamic Field Arrays
|
|
167
|
+
|
|
168
|
+
Manage repeating field groups with `SuprForm.ControlArray`:
|
|
148
169
|
|
|
149
|
-
|
|
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>;
|
|
150
179
|
|
|
151
|
-
|
|
180
|
+
// Add/remove items:
|
|
181
|
+
arrayRef.current?.append({ name: '', years: 0 });
|
|
182
|
+
arrayRef.current?.remove(0);
|
|
183
|
+
```
|
|
152
184
|
|
|
153
185
|
---
|
|
154
186
|
|
|
155
|
-
##
|
|
156
|
-
|
|
157
|
-
### Installation
|
|
187
|
+
## π¦ Installation
|
|
158
188
|
|
|
159
189
|
```bash
|
|
160
190
|
npm install suprform
|
|
161
191
|
```
|
|
162
192
|
|
|
163
|
-
|
|
193
|
+
**Peer dependencies:** React 18 or 19
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm install react react-dom
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## β‘ Quick Start
|
|
164
202
|
|
|
165
|
-
|
|
203
|
+
Here's a complete login form in under 30 lines:
|
|
166
204
|
|
|
167
205
|
```tsx
|
|
168
206
|
import SuprForm from 'suprform';
|
|
169
207
|
|
|
170
208
|
function LoginForm() {
|
|
209
|
+
const handleSubmit = (values: { email: string; password: string }) => {
|
|
210
|
+
console.log('Form submitted:', values);
|
|
211
|
+
// Call your API here
|
|
212
|
+
};
|
|
213
|
+
|
|
171
214
|
return (
|
|
172
|
-
<SuprForm
|
|
173
|
-
onSubmit={(values) => {
|
|
174
|
-
console.log('Form submitted:', values);
|
|
175
|
-
}}
|
|
176
|
-
>
|
|
215
|
+
<SuprForm onSubmit={handleSubmit}>
|
|
177
216
|
<SuprForm.Control
|
|
178
217
|
name='email'
|
|
179
218
|
label='Email Address'
|
|
@@ -193,7 +232,7 @@ function LoginForm() {
|
|
|
193
232
|
label='Password'
|
|
194
233
|
rules={{
|
|
195
234
|
required: 'Password is required',
|
|
196
|
-
minLength: { value: 8, message: '
|
|
235
|
+
minLength: { value: 8, message: 'Min 8 characters' },
|
|
197
236
|
}}
|
|
198
237
|
>
|
|
199
238
|
<input type='password' placeholder='β’β’β’β’β’β’β’β’' />
|
|
@@ -205,44 +244,99 @@ function LoginForm() {
|
|
|
205
244
|
}
|
|
206
245
|
```
|
|
207
246
|
|
|
208
|
-
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>
|
|
209
310
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- 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
|
+
```
|
|
215
315
|
|
|
216
316
|
---
|
|
217
317
|
|
|
218
|
-
##
|
|
318
|
+
## π¨ Usage Examples
|
|
219
319
|
|
|
220
|
-
###
|
|
320
|
+
### 1. Basic Form
|
|
221
321
|
|
|
222
322
|
```tsx
|
|
223
323
|
import SuprForm from 'suprform';
|
|
224
|
-
import { TextField, Button } from '@mui/material'; // or your own
|
|
225
324
|
|
|
226
|
-
|
|
325
|
+
interface ContactForm {
|
|
326
|
+
name: string;
|
|
327
|
+
email: string;
|
|
328
|
+
message: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function ContactForm() {
|
|
227
332
|
return (
|
|
228
|
-
<SuprForm
|
|
333
|
+
<SuprForm<ContactForm>
|
|
229
334
|
onSubmit={(values) => {
|
|
230
|
-
console.log('
|
|
335
|
+
console.log('Submitting:', values);
|
|
231
336
|
}}
|
|
232
337
|
>
|
|
233
|
-
<SuprForm.Control
|
|
234
|
-
|
|
235
|
-
label='Username'
|
|
236
|
-
rules={{
|
|
237
|
-
required: 'Username is required',
|
|
238
|
-
minLength: { value: 3, message: 'Min 3 characters' },
|
|
239
|
-
pattern: {
|
|
240
|
-
value: /^[a-zA-Z0-9_]+$/,
|
|
241
|
-
message: 'Only letters, numbers, and underscores',
|
|
242
|
-
},
|
|
243
|
-
}}
|
|
244
|
-
>
|
|
245
|
-
<TextField variant='outlined' fullWidth />
|
|
338
|
+
<SuprForm.Control name='name' label='Your Name' rules={{ required: 'Name is required' }}>
|
|
339
|
+
<input type='text' />
|
|
246
340
|
</SuprForm.Control>
|
|
247
341
|
|
|
248
342
|
<SuprForm.Control
|
|
@@ -251,140 +345,411 @@ function SignupForm() {
|
|
|
251
345
|
rules={{
|
|
252
346
|
required: 'Email is required',
|
|
253
347
|
pattern: {
|
|
254
|
-
value:
|
|
348
|
+
value: /^\S+@\S+$/,
|
|
255
349
|
message: 'Invalid email',
|
|
256
350
|
},
|
|
257
351
|
}}
|
|
258
352
|
>
|
|
259
|
-
<
|
|
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' />
|
|
260
389
|
</SuprForm.Control>
|
|
261
390
|
|
|
262
391
|
<Button variant='contained' type='submit'>
|
|
263
|
-
|
|
392
|
+
Submit
|
|
264
393
|
</Button>
|
|
265
394
|
</SuprForm>
|
|
266
395
|
);
|
|
267
396
|
}
|
|
268
397
|
```
|
|
269
398
|
|
|
270
|
-
|
|
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
|
|
271
420
|
|
|
272
421
|
Show/hide fields based on other field values:
|
|
273
422
|
|
|
274
423
|
```tsx
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
424
|
+
interface PaymentForm {
|
|
425
|
+
paymentMethod: 'card' | 'paypal' | 'cash';
|
|
426
|
+
cardNumber?: string;
|
|
427
|
+
paypalEmail?: string;
|
|
428
|
+
}
|
|
279
429
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
+
}
|
|
293
471
|
```
|
|
294
472
|
|
|
295
|
-
|
|
473
|
+
**Complex conditions with OR:**
|
|
296
474
|
|
|
297
475
|
```tsx
|
|
298
476
|
<SuprForm.Control
|
|
299
|
-
name='
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
},
|
|
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
|
+
],
|
|
307
484
|
}}
|
|
308
485
|
>
|
|
309
|
-
<input />
|
|
486
|
+
<input placeholder='Enter discount code' />
|
|
310
487
|
</SuprForm.Control>
|
|
311
488
|
```
|
|
312
489
|
|
|
313
|
-
###
|
|
490
|
+
### 4. Dynamic Field Arrays
|
|
491
|
+
|
|
492
|
+
Manage repeating field groups:
|
|
314
493
|
|
|
315
494
|
```tsx
|
|
316
|
-
|
|
317
|
-
|
|
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>>();
|
|
318
644
|
|
|
319
645
|
const prefillForm = () => {
|
|
320
|
-
formRef.current
|
|
321
|
-
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);
|
|
322
662
|
};
|
|
323
663
|
|
|
324
664
|
return (
|
|
325
|
-
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
<
|
|
329
|
-
|
|
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' />
|
|
330
676
|
</SuprForm.Control>
|
|
331
|
-
|
|
677
|
+
|
|
678
|
+
<SuprForm.Control name='password' label='Password'>
|
|
332
679
|
<input type='password' />
|
|
333
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>
|
|
334
689
|
</SuprForm>
|
|
335
|
-
|
|
690
|
+
</div>
|
|
336
691
|
);
|
|
337
692
|
}
|
|
338
693
|
```
|
|
339
694
|
|
|
340
695
|
---
|
|
341
696
|
|
|
342
|
-
## API Reference
|
|
697
|
+
## π API Reference
|
|
343
698
|
|
|
344
699
|
### `<SuprForm>`
|
|
345
700
|
|
|
346
|
-
Root form component
|
|
701
|
+
Root form component.
|
|
347
702
|
|
|
348
|
-
|
|
703
|
+
#### Props
|
|
349
704
|
|
|
350
|
-
| Prop | Type | Description
|
|
351
|
-
| -------------- | --------------------- |
|
|
352
|
-
| `children` | `ReactNode` | Form fields and elements
|
|
353
|
-
| `onSubmit` | `(values: T) => void` | Called
|
|
354
|
-
| `onError` | `(errors) => void` | Called when form submission fails validation
|
|
355
|
-
| `formOptions` | `UseFormProps` | Options
|
|
356
|
-
| `className` | `string` | CSS class for `<form>` element
|
|
357
|
-
| `style` | `CSSProperties` | Inline styles for `<form>`
|
|
358
|
-
| `showAsterisk` | `boolean` | Show asterisk on required field labels
|
|
359
|
-
| `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 |
|
|
360
716
|
|
|
361
|
-
|
|
717
|
+
#### Example
|
|
362
718
|
|
|
363
719
|
```tsx
|
|
364
|
-
<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
|
+
>
|
|
365
730
|
{/* fields */}
|
|
366
731
|
</SuprForm>
|
|
367
732
|
```
|
|
368
733
|
|
|
369
734
|
### `<SuprForm.Control>`
|
|
370
735
|
|
|
371
|
-
|
|
736
|
+
Field wrapper component.
|
|
372
737
|
|
|
373
|
-
|
|
738
|
+
#### Props
|
|
374
739
|
|
|
375
|
-
| Prop | Type | Description
|
|
376
|
-
| ------------------ | ----------------------- |
|
|
377
|
-
| `name`
|
|
378
|
-
| `children`
|
|
379
|
-
| `rules` | `RegisterOptions` | Validation rules (
|
|
380
|
-
| `label` | `string` | Label text
|
|
381
|
-
| `className` | `string` | CSS class for wrapper div
|
|
382
|
-
| `id` | `string` | HTML id (auto-generated if
|
|
383
|
-
| `disabled` | `boolean`
|
|
384
|
-
| `visibility` | `
|
|
385
|
-
| `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 |
|
|
386
751
|
|
|
387
|
-
|
|
752
|
+
#### Example
|
|
388
753
|
|
|
389
754
|
```tsx
|
|
390
755
|
<SuprForm.Control
|
|
@@ -400,9 +765,38 @@ Composable field wrapper that handles labels, errors, and validation.
|
|
|
400
765
|
</SuprForm.Control>
|
|
401
766
|
```
|
|
402
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
|
+
|
|
403
797
|
### Validation Rules
|
|
404
798
|
|
|
405
|
-
SuprForm uses
|
|
799
|
+
SuprForm uses `react-hook-form` validation rules:
|
|
406
800
|
|
|
407
801
|
| Rule | Type | Description |
|
|
408
802
|
| ----------- | ----------------------------------------- | -------------------------- |
|
|
@@ -414,123 +808,806 @@ SuprForm uses [react-hook-form validation rules](https://react-hook-form.com/doc
|
|
|
414
808
|
| `pattern` | `{ value: RegExp, message: string }` | Regex pattern match |
|
|
415
809
|
| `validate` | `(value) => boolean \| string \| Promise` | Custom validation function |
|
|
416
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
|
+
|
|
417
848
|
### Conditional Visibility
|
|
418
849
|
|
|
419
850
|
The `visibility` prop accepts:
|
|
420
851
|
|
|
421
|
-
```
|
|
852
|
+
```typescript
|
|
422
853
|
{
|
|
423
854
|
operator: 'AND' | 'OR',
|
|
424
855
|
conditions: [
|
|
425
856
|
{
|
|
426
|
-
name: 'fieldName',
|
|
427
|
-
operator: '
|
|
428
|
-
|
|
429
|
-
'STARTS_WITH' | 'ENDS_WITH' | 'INCLUDES' | 'NOT_INCLUDES',
|
|
430
|
-
value: any
|
|
857
|
+
name: 'fieldName', // Field to check
|
|
858
|
+
operator: '...', // Comparison operator
|
|
859
|
+
value: any // Value to compare
|
|
431
860
|
}
|
|
432
861
|
]
|
|
433
862
|
}
|
|
434
863
|
```
|
|
435
864
|
|
|
436
|
-
|
|
865
|
+
#### Operators
|
|
437
866
|
|
|
438
|
-
|
|
439
|
-
<SuprForm.Control
|
|
440
|
-
name='promoCode'
|
|
441
|
-
visibility={{
|
|
442
|
-
operator: 'OR',
|
|
443
|
-
conditions: [
|
|
444
|
-
{ name: 'isVip', operator: 'EQUALS', value: true },
|
|
445
|
-
{ name: 'orderTotal', operator: 'GREATER_THAN', value: 100 },
|
|
446
|
-
],
|
|
447
|
-
}}
|
|
448
|
-
>
|
|
449
|
-
<input />
|
|
450
|
-
</SuprForm.Control>
|
|
451
|
-
```
|
|
867
|
+
**String operators:** `EQUALS`, `NOT_EQUALS`, `STARTS_WITH`, `ENDS_WITH`, `INCLUDES`, `NOT_INCLUDES`
|
|
452
868
|
|
|
453
|
-
|
|
869
|
+
**Number operators:** `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`
|
|
870
|
+
|
|
871
|
+
**Boolean operators:** `EQUALS`, `NOT_EQUALS`
|
|
454
872
|
|
|
455
|
-
|
|
873
|
+
#### Examples
|
|
456
874
|
|
|
457
875
|
```tsx
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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);
|
|
468
1010
|
```
|
|
469
1011
|
|
|
470
1012
|
---
|
|
471
1013
|
|
|
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
|
+
```
|
|
1088
|
+
|
|
472
1089
|
---
|
|
473
1090
|
|
|
474
|
-
## Styling
|
|
1091
|
+
## π¨ Styling
|
|
1092
|
+
|
|
1093
|
+
SuprForm is **design system agnostic**. Style it however you want:
|
|
475
1094
|
|
|
476
|
-
|
|
1095
|
+
### CSS Classes
|
|
1096
|
+
|
|
1097
|
+
SuprForm adds these classes for customization:
|
|
477
1098
|
|
|
478
1099
|
```css
|
|
479
1100
|
.controlled-field {
|
|
480
|
-
/*
|
|
1101
|
+
/* Wrapper for each field */
|
|
481
1102
|
}
|
|
1103
|
+
|
|
482
1104
|
.controlled-field-label {
|
|
483
1105
|
/* Label element */
|
|
484
1106
|
}
|
|
1107
|
+
|
|
485
1108
|
.controlled-field-error {
|
|
486
|
-
/* Error message
|
|
1109
|
+
/* Error message */
|
|
1110
|
+
/* Default: color: red; font-size: 13px; */
|
|
487
1111
|
}
|
|
488
1112
|
```
|
|
489
1113
|
|
|
490
|
-
|
|
1114
|
+
### Tailwind CSS
|
|
491
1115
|
|
|
492
1116
|
```tsx
|
|
493
|
-
<SuprForm.Control name='email' className='mb-
|
|
494
|
-
<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' />
|
|
495
1119
|
</SuprForm.Control>
|
|
496
1120
|
```
|
|
497
1121
|
|
|
498
|
-
|
|
1122
|
+
### CSS-in-JS (styled-components, emotion)
|
|
499
1123
|
|
|
500
1124
|
```tsx
|
|
1125
|
+
import styled from 'styled-components';
|
|
1126
|
+
|
|
501
1127
|
const StyledField = styled.div`
|
|
1128
|
+
.controlled-field {
|
|
1129
|
+
margin-bottom: 1rem;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
502
1132
|
.controlled-field-label {
|
|
503
1133
|
font-weight: 600;
|
|
1134
|
+
color: #333;
|
|
1135
|
+
margin-bottom: 0.5rem;
|
|
504
1136
|
}
|
|
1137
|
+
|
|
505
1138
|
.controlled-field-error {
|
|
506
|
-
color: #
|
|
1139
|
+
color: #dc2626;
|
|
1140
|
+
font-size: 0.875rem;
|
|
1141
|
+
margin-top: 0.25rem;
|
|
507
1142
|
}
|
|
508
1143
|
`;
|
|
509
1144
|
|
|
510
|
-
<SuprForm.Control className={StyledField}
|
|
1145
|
+
<SuprForm.Control name='email' className={StyledField}>
|
|
511
1146
|
<input />
|
|
512
1147
|
</SuprForm.Control>;
|
|
513
1148
|
```
|
|
514
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
|
+
|
|
1397
|
+
---
|
|
1398
|
+
|
|
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
|
+
|
|
515
1509
|
---
|
|
516
1510
|
|
|
517
|
-
##
|
|
1511
|
+
## π€ Contributing
|
|
1512
|
+
|
|
1513
|
+
Contributions are welcome! Here's how to get started:
|
|
1514
|
+
|
|
1515
|
+
### Development Setup
|
|
518
1516
|
|
|
519
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
|
|
520
1566
|
npm login
|
|
1567
|
+
|
|
1568
|
+
# Build and publish
|
|
521
1569
|
npm run build
|
|
522
|
-
npm version patch
|
|
523
|
-
npm publish --access public
|
|
1570
|
+
npm version patch # or minor/major
|
|
1571
|
+
npm publish --access public
|
|
524
1572
|
```
|
|
525
1573
|
|
|
526
|
-
|
|
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
|
|
527
1584
|
|
|
528
1585
|
---
|
|
529
1586
|
|
|
530
|
-
## License
|
|
1587
|
+
## π License
|
|
531
1588
|
|
|
532
|
-
MIT
|
|
1589
|
+
MIT Β© [Albin Britto](https://github.com/Albinbritto)
|
|
533
1590
|
|
|
534
|
-
|
|
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
|
+
---
|
|
535
1612
|
|
|
536
|
-
|
|
1613
|
+
**Made with β€οΈ by [Albin Britto](https://github.com/Albinbritto)**
|