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/README.md CHANGED
@@ -1,49 +1,59 @@
1
- # SuprForm Monorepo
1
+ # SuprForm πŸš€
2
2
 
3
- This repo is now a monorepo with two packages:
3
+ <div align="center">
4
4
 
5
- - packages/suprform β€” the publishable headless React form library
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
- Use npm workspaces to develop both packages together.
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/)
9
10
 
10
- ## Quick commands
11
+ [Features](#-key-features) β€’ [Installation](#-installation) β€’ [Quick Start](#-quick-start) β€’ [Documentation](#-api-reference) β€’ [Examples](#-usage-examples)
11
12
 
12
- - Install dependencies (from repo root):
13
+ </div>
13
14
 
14
- ```bash
15
- npm install
16
- ```
17
-
18
- - Build all workspaces:
19
-
20
- ```bash
21
- npm run build
22
- ```
15
+ ---
23
16
 
24
- - Develop the site:
17
+ ## 🎯 What is SuprForm?
25
18
 
26
- ```bash
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
- ## Publishing the library
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
- The library lives in packages/suprform and remains publish-ready. From repo root:
29
+ ---
33
30
 
34
- ```bash
35
- cd packages/suprform
36
- npm version [patch|minor|major]
37
- npm publish --access public
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
- Ensure you are logged in to npm and have 2FA OTP ready if enabled.
50
+ ---
41
51
 
42
- ## Core Features
52
+ ## ✨ Key Features
43
53
 
44
- ### 🎨 **Design System Agnostic**
54
+ ### 🎨 Design System Agnostic
45
55
 
46
- Use with any UI frameworkβ€”Material-UI, Ant Design, Tailwind, shadcn/ui, or plain HTML. SuprForm provides the logic, you control the design.
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
- ### 🎯 **Composable Field Control**
75
+ ### 🎯 Composable Field Control
61
76
 
62
- 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:
63
78
 
64
- - Automatic label and error rendering
65
- - Field state tracking (touched, dirty, valid)
66
- - Seamless integration with any input component
67
- - 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
68
84
 
69
- ### πŸ”’ **TypeScript-First Architecture**
85
+ ### πŸ”’ TypeScript-First Architecture
70
86
 
71
- 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:
72
88
 
73
89
  ```tsx
74
- interface FormData {
90
+ interface UserForm {
75
91
  email: string;
76
92
  age: number;
93
+ isSubscribed: boolean;
77
94
  }
78
95
 
79
- <SuprForm<FormData>
96
+ <SuprForm<UserForm>
80
97
  onSubmit={(values) => {
81
- // values is typed as FormData
82
- console.log(values.email); // βœ“ Type-safe
98
+ // βœ… values is fully typed as UserForm
99
+ console.log(values.email.toLowerCase()); // Type-safe!
83
100
  }}
84
101
  >
85
- <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! */}
86
105
  </SuprForm>;
87
106
  ```
88
107
 
89
- ### πŸ‘οΈ **Conditional Field Visibility**
108
+ ### πŸ‘οΈ Conditional Field Visibility
90
109
 
91
- 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:
92
111
 
93
112
  ```tsx
94
113
  <SuprForm.Control
95
- name='creditCard'
114
+ name='promoCode'
96
115
  visibility={{
97
116
  operator: 'AND',
98
117
  conditions: [
99
- { name: 'paymentMethod', operator: 'EQUALS', value: 'card' },
100
- { name: 'amount', operator: 'GREATER_THAN', value: 0 },
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, 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`
109
128
 
110
- ### βœ… **Declarative Validation**
129
+ ### βœ… Powerful Validation
111
130
 
112
- Powered by react-hook-form's validation system with support for sync and async validators.
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 checkAvailability(value);
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
- ### πŸŽ›οΈ **Imperative Form Control**
149
+ ### πŸŽ›οΈ Imperative Form Control
131
150
 
132
- Access react-hook-form methods via ref for programmatic form manipulation.
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.setValue('email', 'test@example.com');
143
- formRef.current.trigger('email'); // Manually validate
144
- 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
145
164
  ```
146
165
 
147
- **Available methods:** `setValue`, `setError`, `clearErrors`, `getValues`, `reset`, `setFocus`, `resetField`, `trigger`, `unregister`, `watch`
166
+ ### πŸ”„ Dynamic Field Arrays
167
+
168
+ Manage repeating field groups with `SuprForm.ControlArray`:
148
169
 
149
- ### πŸ“¦ **Zero UI Dependencies**
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
- Only React as a peer dependency. No CSS framework lock-in, no component library coupling. Bring your own design system.
180
+ // Add/remove items:
181
+ arrayRef.current?.append({ name: '', years: 0 });
182
+ arrayRef.current?.remove(0);
183
+ ```
152
184
 
153
185
  ---
154
186
 
155
- ## Quick Start
156
-
157
- ### Installation
187
+ ## πŸ“¦ Installation
158
188
 
159
189
  ```bash
160
190
  npm install suprform
161
191
  ```
162
192
 
163
- ### 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
164
202
 
165
- The composable `SuprForm.Control` component handles everything for you:
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: 'Password must be at least 8 characters' },
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.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>
209
310
 
210
- - Label rendering with proper `htmlFor` linking
211
- - Validation on blur and change
212
- - Error message display
213
- - Field state management (value, touched, dirty, error)
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
- ## Advanced Usage
318
+ ## 🎨 Usage Examples
219
319
 
220
- ### With UI Component Libraries
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
- function SignupForm() {
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('User signed up:', values);
335
+ console.log('Submitting:', values);
231
336
  }}
232
337
  >
233
- <SuprForm.Control
234
- name='username'
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: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
348
+ value: /^\S+@\S+$/,
255
349
  message: 'Invalid email',
256
350
  },
257
351
  }}
258
352
  >
259
- <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' />
260
389
  </SuprForm.Control>
261
390
 
262
391
  <Button variant='contained' type='submit'>
263
- Sign Up
392
+ Submit
264
393
  </Button>
265
394
  </SuprForm>
266
395
  );
267
396
  }
268
397
  ```
269
398
 
270
- ### 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
271
420
 
272
421
  Show/hide fields based on other field values:
273
422
 
274
423
  ```tsx
275
- <SuprForm onSubmit={handleSubmit}>
276
- <SuprForm.Control name='hasDiscount' label='Apply Discount?'>
277
- <input type='checkbox' />
278
- </SuprForm.Control>
424
+ interface PaymentForm {
425
+ paymentMethod: 'card' | 'paypal' | 'cash';
426
+ cardNumber?: string;
427
+ paypalEmail?: string;
428
+ }
279
429
 
280
- {/* Only show when hasDiscount is checked */}
281
- <SuprForm.Control
282
- name='discountCode'
283
- label='Discount Code'
284
- visibility={{
285
- operator: 'AND',
286
- conditions: [{ name: 'hasDiscount', operator: 'EQUALS', value: true }],
287
- }}
288
- rules={{ required: 'Discount code required' }}
289
- >
290
- <input type='text' />
291
- </SuprForm.Control>
292
- </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
+ }
293
471
  ```
294
472
 
295
- ### Async Validation
473
+ **Complex conditions with OR:**
296
474
 
297
475
  ```tsx
298
476
  <SuprForm.Control
299
- name='username'
300
- rules={{
301
- required: 'Username is required',
302
- validate: async (value) => {
303
- const response = await fetch(`/api/check-username/${value}`);
304
- const data = await response.json();
305
- return data.available || 'Username already taken';
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
- ### Programmatic Form Control
490
+ ### 4. Dynamic Field Arrays
491
+
492
+ Manage repeating field groups:
314
493
 
315
494
  ```tsx
316
- function MyForm() {
317
- 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>>();
318
644
 
319
645
  const prefillForm = () => {
320
- formRef.current.setValue('email', 'user@example.com');
321
- 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);
322
662
  };
323
663
 
324
664
  return (
325
- <>
326
- <button onClick={prefillForm}>Prefill</button>
327
- <SuprForm ref={formRef} onSubmit={handleSubmit}>
328
- <SuprForm.Control name='email'>
329
- <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' />
330
676
  </SuprForm.Control>
331
- <SuprForm.Control name='password'>
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 that wraps your form fields.
701
+ Root form component.
347
702
 
348
- **Props:**
703
+ #### Props
349
704
 
350
- | Prop | Type | Description |
351
- | -------------- | --------------------- | -------------------------------------------- |
352
- | `children` | `ReactNode` | Form fields and elements |
353
- | `onSubmit` | `(values: T) => void` | Called with form values when valid |
354
- | `onError` | `(errors) => void` | Called when form submission fails validation |
355
- | `formOptions` | `UseFormProps` | Options for react-hook-form's `useForm()` |
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` | 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 |
360
716
 
361
- **Example:**
717
+ #### Example
362
718
 
363
719
  ```tsx
364
- <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
+ >
365
730
  {/* fields */}
366
731
  </SuprForm>
367
732
  ```
368
733
 
369
734
  ### `<SuprForm.Control>`
370
735
 
371
- Composable field wrapper that handles labels, errors, and validation.
736
+ Field wrapper component.
372
737
 
373
- **Props:**
738
+ #### Props
374
739
 
375
- | Prop | Type | Description |
376
- | ------------------ | ----------------------- | ----------------------------------------------------------- |
377
- | `name` | `string` | **Required.** Field name (type-checked against form values) |
378
- | `children` | `ReactElement` | **Required.** Your input component |
379
- | `rules` | `RegisterOptions` | Validation rules (react-hook-form format) |
380
- | `label` | `string` | Label text (rendered above input) |
381
- | `className` | `string` | CSS class for wrapper div |
382
- | `id` | `string` | HTML id (auto-generated if not provided) |
383
- | `disabled` | `boolean` | Disable the field |
384
- | `visibility` | `Visibility \| boolean` | Conditional visibility rules |
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
- **Example:**
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 [react-hook-form validation rules](https://react-hook-form.com/docs/useform/register#options):
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
- ```tsx
852
+ ```typescript
422
853
  {
423
854
  operator: 'AND' | 'OR',
424
855
  conditions: [
425
856
  {
426
- name: 'fieldName',
427
- operator: 'EQUALS' | 'NOT_EQUALS' | 'GREATER_THAN' | 'LESS_THAN' |
428
- 'GREATER_THAN_OR_EQUAL' | 'LESS_THAN_OR_EQUAL' |
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
- **Example:**
865
+ #### Operators
437
866
 
438
- ```tsx
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
- ### Form Ref Methods
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
- When you pass a `ref` to `<SuprForm>`, you get access to:
873
+ #### Examples
456
874
 
457
875
  ```tsx
458
- formRef.current.setValue(name, value) // Set field value
459
- formRef.current.setError(name, error) // Set field error
460
- formRef.current.clearErrors(name?) // Clear errors
461
- formRef.current.getValues(name?) // Get field value(s)
462
- formRef.current.reset(values?) // Reset form
463
- formRef.current.setFocus(name) // Focus field
464
- formRef.current.resetField(name) // Reset specific field
465
- formRef.current.trigger(name?) // Trigger validation
466
- formRef.current.unregister(name) // Unregister field
467
- formRef.current.watch(name?) // Watch field value(s)
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
- SuprForm is **design system agnostic**. Style using CSS classes:
1095
+ ### CSS Classes
1096
+
1097
+ SuprForm adds these classes for customization:
477
1098
 
478
1099
  ```css
479
1100
  .controlled-field {
480
- /* Field wrapper */
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 (defaults: red, 14px, margin-top 4px) */
1109
+ /* Error message */
1110
+ /* Default: color: red; font-size: 13px; */
487
1111
  }
488
1112
  ```
489
1113
 
490
- **Tailwind Example:**
1114
+ ### Tailwind CSS
491
1115
 
492
1116
  ```tsx
493
- <SuprForm.Control name='email' className='mb-4'>
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
- **styled-components Example:**
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: #d32f2f;
1139
+ color: #dc2626;
1140
+ font-size: 0.875rem;
1141
+ margin-top: 0.25rem;
507
1142
  }
508
1143
  `;
509
1144
 
510
- <SuprForm.Control className={StyledField} name='email'>
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
- ## Publishing
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 # or minor/major
523
- npm publish --access public --otp=XXXXXX
1570
+ npm version patch # or minor/major
1571
+ npm publish --access public
524
1572
  ```
525
1573
 
526
- 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
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
- ## 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
+ ---
535
1612
 
536
- Contributions welcome! Submit a Pull Request on [GitHub](https://github.com/Albinbritto/suprform).
1613
+ **Made with ❀️ by [Albin Britto](https://github.com/Albinbritto)**