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/README.md CHANGED
@@ -1,12 +1,59 @@
1
- # SuprForm
1
+ # SuprForm πŸš€
2
2
 
3
- A **headless React form library** that wraps [react-hook-form](https://react-hook-form.com) with a composable API for effortless form management. Design system agnostic, TypeScript-first, and built for developer experience.
3
+ <div align="center">
4
4
 
5
- ## Core Features
5
+ **A headless, TypeScript-first React form library for managing complex state, validation, and conditional logic**
6
6
 
7
- ### 🎨 **Design System Agnostic**
7
+ [![npm version](https://img.shields.io/npm/v/suprform.svg)](https://www.npmjs.com/package/suprform)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
10
 
9
- Use with any UI frameworkβ€”Material-UI, Ant Design, Tailwind, shadcn/ui, or plain HTML. SuprForm provides the logic, you control the design.
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
- ### 🎯 **Composable Field Control**
75
+ ### 🎯 Composable Field Control
24
76
 
25
- The `SuprForm.Control` component wraps your inputs with automatic label rendering, error display, and validationβ€”no configuration needed.
77
+ The `SuprForm.Control` component handles everything automatically:
26
78
 
27
- - Automatic label and error rendering
28
- - Field state tracking (touched, dirty, valid)
29
- - Seamless integration with any input component
30
- - Full TypeScript inference for field names and values
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
- ### πŸ”’ **TypeScript-First Architecture**
85
+ ### πŸ”’ TypeScript-First Architecture
33
86
 
34
- Complete type safety with intelligent inference throughout your forms. Field names, validation rules, and form values are all fully typed.
87
+ Full type inference for field names, values, and validation rules:
35
88
 
36
89
  ```tsx
37
- interface FormData {
90
+ interface UserForm {
38
91
  email: string;
39
92
  age: number;
93
+ isSubscribed: boolean;
40
94
  }
41
95
 
42
- <SuprForm<FormData>
96
+ <SuprForm<UserForm>
43
97
  onSubmit={(values) => {
44
- // values is typed as FormData
45
- console.log(values.email); // βœ“ Type-safe
98
+ // βœ… values is fully typed as UserForm
99
+ console.log(values.email.toLowerCase()); // Type-safe!
46
100
  }}
47
101
  >
48
- <SuprForm.Control name='email' /> {/* βœ“ Type-checked */}
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
- ### πŸ‘οΈ **Conditional Field Visibility**
108
+ ### πŸ‘οΈ Conditional Field Visibility
53
109
 
54
- Advanced conditional rendering with declarative visibility rules. Show/hide fields based on other field values with AND/OR logic.
110
+ Declaratively show/hide or enable/disable fields based on other field values:
55
111
 
56
112
  ```tsx
57
113
  <SuprForm.Control
58
- name='creditCard'
114
+ name='promoCode'
59
115
  visibility={{
60
116
  operator: 'AND',
61
117
  conditions: [
62
- { name: 'paymentMethod', operator: 'EQUALS', value: 'card' },
63
- { name: 'amount', operator: 'GREATER_THAN', value: 0 },
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, NOT_EQUALS, GREATER_THAN, LESS_THAN, STARTS_WITH, ENDS_WITH, INCLUDES
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
- ### βœ… **Declarative Validation**
129
+ ### βœ… Powerful Validation
74
130
 
75
- Powered by react-hook-form's validation system with support for sync and async validators.
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 checkAvailability(value);
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
- ### πŸŽ›οΈ **Imperative Form Control**
149
+ ### πŸŽ›οΈ Imperative Form Control
94
150
 
95
- Access react-hook-form methods via ref for programmatic form manipulation.
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.setValue('email', 'test@example.com');
106
- formRef.current.trigger('email'); // Manually validate
107
- formRef.current.reset(); // Reset form
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
- **Available methods:** `setValue`, `setError`, `clearErrors`, `getValues`, `reset`, `setFocus`, `resetField`, `trigger`, `unregister`, `watch`
166
+ ### πŸ”„ Dynamic Field Arrays
111
167
 
112
- ### πŸ“¦ **Zero UI Dependencies**
168
+ Manage repeating field groups with `SuprForm.ControlArray`:
113
169
 
114
- Only React as a peer dependency. No CSS framework lock-in, no component library coupling. Bring your own design system.
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
- ## Quick Start
185
+ ---
119
186
 
120
- ### Installation
187
+ ## πŸ“¦ Installation
121
188
 
122
189
  ```bash
123
190
  npm install suprform
124
191
  ```
125
192
 
126
- ### Basic Example
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
- The composable `SuprForm.Control` component handles everything for you:
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: 'Password must be at least 8 characters' },
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.Control **automatically handles**:
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
- - Label rendering with proper `htmlFor` linking
174
- - Validation on blur and change
175
- - Error message display
176
- - Field state management (value, touched, dirty, error)
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
- ## Advanced Usage
318
+ ## 🎨 Usage Examples
182
319
 
183
- ### With UI Component Libraries
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
- function SignupForm() {
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('User signed up:', values);
335
+ console.log('Submitting:', values);
194
336
  }}
195
337
  >
196
- <SuprForm.Control
197
- name='username'
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: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
348
+ value: /^\S+@\S+$/,
218
349
  message: 'Invalid email',
219
350
  },
220
351
  }}
221
352
  >
222
- <TextField type='email' variant='outlined' fullWidth />
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
- Sign Up
392
+ Submit
227
393
  </Button>
228
394
  </SuprForm>
229
395
  );
230
396
  }
231
397
  ```
232
398
 
233
- ### Conditional Field Visibility
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
- <SuprForm onSubmit={handleSubmit}>
239
- <SuprForm.Control name='hasDiscount' label='Apply Discount?'>
240
- <input type='checkbox' />
241
- </SuprForm.Control>
424
+ interface PaymentForm {
425
+ paymentMethod: 'card' | 'paypal' | 'cash';
426
+ cardNumber?: string;
427
+ paypalEmail?: string;
428
+ }
242
429
 
243
- {/* Only show when hasDiscount is checked */}
244
- <SuprForm.Control
245
- name='discountCode'
246
- label='Discount Code'
247
- visibility={{
248
- operator: 'AND',
249
- conditions: [{ name: 'hasDiscount', operator: 'EQUALS', value: true }],
250
- }}
251
- rules={{ required: 'Discount code required' }}
252
- >
253
- <input type='text' />
254
- </SuprForm.Control>
255
- </SuprForm>
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
- ### Async Validation
473
+ **Complex conditions with OR:**
259
474
 
260
475
  ```tsx
261
476
  <SuprForm.Control
262
- name='username'
263
- rules={{
264
- required: 'Username is required',
265
- validate: async (value) => {
266
- const response = await fetch(`/api/check-username/${value}`);
267
- const data = await response.json();
268
- return data.available || 'Username already taken';
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
- ### Programmatic Form Control
490
+ ### 4. Dynamic Field Arrays
491
+
492
+ Manage repeating field groups:
277
493
 
278
494
  ```tsx
279
- function MyForm() {
280
- const formRef = useRef();
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.setValue('email', 'user@example.com');
284
- formRef.current.setFocus('password');
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
- <button onClick={prefillForm}>Prefill</button>
290
- <SuprForm ref={formRef} onSubmit={handleSubmit}>
291
- <SuprForm.Control name='email'>
292
- <input />
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
- <SuprForm.Control name='password'>
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 that wraps your form fields.
701
+ Root form component.
310
702
 
311
- **Props:**
703
+ #### Props
312
704
 
313
- | Prop | Type | Description |
314
- | -------------- | --------------------- | -------------------------------------------- |
315
- | `children` | `ReactNode` | Form fields and elements |
316
- | `onSubmit` | `(values: T) => void` | Called with form values when valid |
317
- | `onError` | `(errors) => void` | Called when form submission fails validation |
318
- | `formOptions` | `UseFormProps` | Options for react-hook-form's `useForm()` |
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` | Access form methods imperatively |
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
- **Example:**
717
+ #### Example
325
718
 
326
719
  ```tsx
327
- <SuprForm onSubmit={(values) => console.log(values)} formOptions={{ mode: 'onBlur' }} showAsterisk>
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
- Composable field wrapper that handles labels, errors, and validation.
736
+ Field wrapper component.
335
737
 
336
- **Props:**
738
+ #### Props
337
739
 
338
- | Prop | Type | Description |
339
- | ------------------ | ----------------------- | ----------------------------------------------------------- |
340
- | `name` | `string` | **Required.** Field name (type-checked against form values) |
341
- | `children` | `ReactElement` | **Required.** Your input component |
342
- | `rules` | `RegisterOptions` | Validation rules (react-hook-form format) |
343
- | `label` | `string` | Label text (rendered above input) |
344
- | `className` | `string` | CSS class for wrapper div |
345
- | `id` | `string` | HTML id (auto-generated if not provided) |
346
- | `disabled` | `boolean` | Disable the field |
347
- | `visibility` | `Visibility \| boolean` | Conditional visibility rules |
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
- **Example:**
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 [react-hook-form validation rules](https://react-hook-form.com/docs/useform/register#options):
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
- ```tsx
852
+ ```typescript
385
853
  {
386
854
  operator: 'AND' | 'OR',
387
855
  conditions: [
388
856
  {
389
- name: 'fieldName',
390
- operator: 'EQUALS' | 'NOT_EQUALS' | 'GREATER_THAN' | 'LESS_THAN' |
391
- 'GREATER_THAN_OR_EQUAL' | 'LESS_THAN_OR_EQUAL' |
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
- **Example:**
865
+ #### Operators
400
866
 
401
- ```tsx
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
- ### Form Ref Methods
869
+ **Number operators:** `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`
417
870
 
418
- When you pass a `ref` to `<SuprForm>`, you get access to:
871
+ **Boolean operators:** `EQUALS`, `NOT_EQUALS`
419
872
 
420
- ```tsx
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
- ## Styling
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
- SuprForm is **design system agnostic**. Style using CSS classes:
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
- /* Field wrapper */
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 (defaults: red, 14px, margin-top 4px) */
1109
+ /* Error message */
1110
+ /* Default: color: red; font-size: 13px; */
450
1111
  }
451
1112
  ```
452
1113
 
453
- **Tailwind Example:**
1114
+ ### Tailwind CSS
454
1115
 
455
1116
  ```tsx
456
- <SuprForm.Control name='email' className='mb-4'>
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
- **styled-components Example:**
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: #d32f2f;
1139
+ color: #dc2626;
1140
+ font-size: 0.875rem;
1141
+ margin-top: 0.25rem;
470
1142
  }
471
1143
  `;
472
1144
 
473
- <SuprForm.Control className={StyledField} name='email'>
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
- ## Publishing
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 # or minor/major
486
- npm publish --access public --otp=XXXXXX
1570
+ npm version patch # or minor/major
1571
+ npm publish --access public
487
1572
  ```
488
1573
 
489
- The published package contains only compiled `dist/` files (ESM/CJS + TypeScript declarations).
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
- ## Contributing
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
- Contributions welcome! Submit a Pull Request on [GitHub](https://github.com/Albinbritto/suprform).
1613
+ **Made with ❀️ by [Albin Britto](https://github.com/Albinbritto)**