@delightui/components 0.1.161 → 0.1.162-alpha.1

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.
Files changed (89) hide show
  1. package/dist/cjs/components/atoms/Checkbox/Checkbox.d.ts +1 -1
  2. package/dist/cjs/components/atoms/Checkbox/Checkbox.presenter.d.ts +1 -1
  3. package/dist/cjs/components/atoms/CheckboxItem/CheckboxItem.d.ts +1 -1
  4. package/dist/cjs/components/atoms/CheckboxItem/CheckboxItem.presenter.d.ts +2 -2
  5. package/dist/cjs/components/atoms/CustomToggle/CustomToggle.d.ts +1 -1
  6. package/dist/cjs/components/atoms/Input/Input.d.ts +1 -1
  7. package/dist/cjs/components/atoms/Input/Input.presenter.d.ts +2 -2
  8. package/dist/cjs/components/atoms/Password/Password.presenter.d.ts +2 -2
  9. package/dist/cjs/components/atoms/RadioButton/RadioButton.d.ts +1 -1
  10. package/dist/cjs/components/atoms/RadioButtonItem/RadioButtonItem.d.ts +1 -1
  11. package/dist/cjs/components/atoms/RadioButtonItem/RadioButtonItem.presenter.d.ts +2 -2
  12. package/dist/cjs/components/atoms/TextArea/TextArea.d.ts +1 -1
  13. package/dist/cjs/components/atoms/TextArea/TextArea.presenter.d.ts +1 -1
  14. package/dist/cjs/components/atoms/Toggle/Toggle.d.ts +1 -1
  15. package/dist/cjs/components/atoms/Toggle/Toggle.presenter.d.ts +2 -2
  16. package/dist/cjs/components/atoms/ToggleButton/ToggleButton.d.ts +1 -1
  17. package/dist/cjs/components/molecules/FormField/FormField.d.ts +28 -2
  18. package/dist/cjs/components/molecules/FormField/FormField.presenter.d.ts +16 -8
  19. package/dist/cjs/components/molecules/FormField/FormField.types.d.ts +85 -52
  20. package/dist/cjs/components/molecules/FormField/FormField.utils.d.ts +8 -11
  21. package/dist/cjs/components/molecules/FormField/index.d.ts +1 -2
  22. package/dist/cjs/components/molecules/FormField/useSafeController.d.ts +14 -0
  23. package/dist/cjs/components/molecules/FormField/useSafeFormContext.d.ts +8 -0
  24. package/dist/cjs/components/molecules/Search/Search.d.ts +1 -1
  25. package/dist/cjs/components/molecules/Search/Search.presenter.d.ts +2 -2
  26. package/dist/cjs/components/molecules/Select/Select.presenter.d.ts +4 -4
  27. package/dist/cjs/components/organisms/Form/Form.d.ts +22 -1
  28. package/dist/cjs/components/organisms/Form/Form.presenter.d.ts +290 -0
  29. package/dist/cjs/components/organisms/Form/Form.types.d.ts +24 -131
  30. package/dist/cjs/components/organisms/Form/examples/DropzoneFormExample.d.ts +6 -0
  31. package/dist/cjs/components/organisms/Form/examples/UpdatedFormExample.d.ts +2 -0
  32. package/dist/cjs/components/organisms/Form/{UseFormExample.d.ts → examples/UseFormExample.d.ts} +0 -3
  33. package/dist/cjs/components/organisms/Form/index.d.ts +4 -4
  34. package/dist/cjs/components/organisms/Form/useAutosave.d.ts +10 -0
  35. package/dist/cjs/library.js +3 -3
  36. package/dist/cjs/library.js.map +1 -1
  37. package/dist/esm/components/atoms/Checkbox/Checkbox.d.ts +1 -1
  38. package/dist/esm/components/atoms/Checkbox/Checkbox.presenter.d.ts +1 -1
  39. package/dist/esm/components/atoms/CheckboxItem/CheckboxItem.d.ts +1 -1
  40. package/dist/esm/components/atoms/CheckboxItem/CheckboxItem.presenter.d.ts +2 -2
  41. package/dist/esm/components/atoms/CustomToggle/CustomToggle.d.ts +1 -1
  42. package/dist/esm/components/atoms/Input/Input.d.ts +1 -1
  43. package/dist/esm/components/atoms/Input/Input.presenter.d.ts +2 -2
  44. package/dist/esm/components/atoms/Password/Password.presenter.d.ts +2 -2
  45. package/dist/esm/components/atoms/RadioButton/RadioButton.d.ts +1 -1
  46. package/dist/esm/components/atoms/RadioButtonItem/RadioButtonItem.d.ts +1 -1
  47. package/dist/esm/components/atoms/RadioButtonItem/RadioButtonItem.presenter.d.ts +2 -2
  48. package/dist/esm/components/atoms/TextArea/TextArea.d.ts +1 -1
  49. package/dist/esm/components/atoms/TextArea/TextArea.presenter.d.ts +1 -1
  50. package/dist/esm/components/atoms/Toggle/Toggle.d.ts +1 -1
  51. package/dist/esm/components/atoms/Toggle/Toggle.presenter.d.ts +2 -2
  52. package/dist/esm/components/atoms/ToggleButton/ToggleButton.d.ts +1 -1
  53. package/dist/esm/components/molecules/FormField/FormField.d.ts +28 -2
  54. package/dist/esm/components/molecules/FormField/FormField.presenter.d.ts +16 -8
  55. package/dist/esm/components/molecules/FormField/FormField.types.d.ts +85 -52
  56. package/dist/esm/components/molecules/FormField/FormField.utils.d.ts +8 -11
  57. package/dist/esm/components/molecules/FormField/index.d.ts +1 -2
  58. package/dist/esm/components/molecules/FormField/useSafeController.d.ts +14 -0
  59. package/dist/esm/components/molecules/FormField/useSafeFormContext.d.ts +8 -0
  60. package/dist/esm/components/molecules/Search/Search.d.ts +1 -1
  61. package/dist/esm/components/molecules/Search/Search.presenter.d.ts +2 -2
  62. package/dist/esm/components/molecules/Select/Select.presenter.d.ts +4 -4
  63. package/dist/esm/components/organisms/Form/Form.d.ts +22 -1
  64. package/dist/esm/components/organisms/Form/Form.presenter.d.ts +290 -0
  65. package/dist/esm/components/organisms/Form/Form.types.d.ts +24 -131
  66. package/dist/esm/components/organisms/Form/examples/DropzoneFormExample.d.ts +6 -0
  67. package/dist/esm/components/organisms/Form/examples/UpdatedFormExample.d.ts +2 -0
  68. package/dist/esm/components/organisms/Form/{UseFormExample.d.ts → examples/UseFormExample.d.ts} +0 -3
  69. package/dist/esm/components/organisms/Form/index.d.ts +4 -4
  70. package/dist/esm/components/organisms/Form/useAutosave.d.ts +10 -0
  71. package/dist/esm/library.js +3 -3
  72. package/dist/esm/library.js.map +1 -1
  73. package/dist/index.d.ts +162 -232
  74. package/docs/FORM_MIGRATION_GUIDE.md +631 -0
  75. package/docs/components/molecules/FormField.md +129 -34
  76. package/docs/components/organisms/Form.md +858 -162
  77. package/package.json +4 -1
  78. package/dist/cjs/components/organisms/Form/DropzoneFormExample.d.ts +0 -6
  79. package/dist/cjs/components/organisms/Form/Form.utils.d.ts +0 -2
  80. package/dist/cjs/components/organisms/Form/FormContext.d.ts +0 -5
  81. package/dist/cjs/components/organisms/Form/UpdatedFormExample.d.ts +0 -23
  82. package/dist/cjs/components/organisms/Form/useForm.d.ts +0 -50
  83. package/dist/esm/components/organisms/Form/DropzoneFormExample.d.ts +0 -6
  84. package/dist/esm/components/organisms/Form/Form.utils.d.ts +0 -2
  85. package/dist/esm/components/organisms/Form/FormContext.d.ts +0 -5
  86. package/dist/esm/components/organisms/Form/UpdatedFormExample.d.ts +0 -23
  87. package/dist/esm/components/organisms/Form/useForm.d.ts +0 -50
  88. /package/dist/cjs/components/organisms/Form/{AutosaveFormExample.d.ts → examples/AutosaveFormExample.d.ts} +0 -0
  89. /package/dist/esm/components/organisms/Form/{AutosaveFormExample.d.ts → examples/AutosaveFormExample.d.ts} +0 -0
@@ -1,68 +1,84 @@
1
1
  # Form
2
2
 
3
+ > **⚠️ BREAKING CHANGES:** The Form component has been rewritten using React Hook Form. See the [Migration Guide](../../../FORM_MIGRATION_GUIDE.md) for detailed migration instructions.
4
+
3
5
  ## Description
4
6
 
5
- A comprehensive form management component that provides state management, validation, error handling, and submission workflows. Built with TypeScript for type safety and supports both controlled and uncontrolled form patterns with extensive validation capabilities.
7
+ A comprehensive form management component built on **React Hook Form** that provides state management, validation, error handling, and submission workflows. Features TypeScript support, autosave functionality, async validation, and seamless integration with the FormField component.
6
8
 
7
9
  ## Aliases
8
10
 
9
11
  - Form
10
- - FormProvider
11
- - FormContainer
12
- - FormWrapper
13
12
 
14
13
  ## Props Breakdown
15
14
 
16
- **Extends:** `FormHTMLAttributes<HTMLFormElement>` (excluding `onSubmit`)
17
-
18
15
  | Prop | Type | Default | Required | Description |
19
16
  |------|------|---------|----------|-------------|
20
- | `formRef` | `Ref<HTMLFormElement>` | - | No | Reference to the form element |
21
- | `children` | `ReactNode` | - | No | Form fields and content |
22
- | `formState` | `FormState` | - | No | Initial form state values |
23
- | `onFormStateChange` | `FormStateChangeHandler` | - | No | Callback when form state changes |
24
- | `formValidator` | `FormValidator` | - | No | Function to validate entire form |
25
- | `onSubmit` | `FormSubmitHandler` | - | No | Form submission handler |
26
- | `validateOnChange` | `boolean` | - | No | Whether to validate on field changes |
17
+ | `children` | `ReactNode` | - | Yes | Form fields and content |
18
+ | `defaultValues` | `object` | - | No | Initial form values (uncontrolled mode) |
19
+ | `onSubmit` | `(values: TFormValues) => void \| Promise<void>` | - | No | Form submission handler (no setError callback) |
27
20
  | `autosave` | `boolean` | `false` | No | Enable automatic form submission on value changes |
28
- | `autosaveDelayMs` | `number` | - | No | Delay in milliseconds before autosave triggers |
21
+ | `autosaveDelayMs` | `number` | `1000` | No | Debounce delay in milliseconds before autosave triggers |
22
+ | `mode` | `'onSubmit' \| 'onChange' \| 'onBlur' \| 'onTouched' \| 'all'` | `'onSubmit'` | No | React Hook Form validation mode |
23
+ | `formOptions` | `UseFormProps` | - | No | Additional React Hook Form options |
24
+ | `formRef` | `Ref<HTMLFormElement>` | - | No | Reference to the form element |
25
+ | `className` | `string` | - | No | CSS class for the form element |
26
+ | `style` | `CSSProperties` | - | No | Inline styles for the form element |
27
+
28
+ ### Removed Props (Breaking Changes)
29
+
30
+ | OLD Prop | Migration Path |
31
+ |----------|----------------|
32
+ | `formState` | Use `defaultValues` instead |
33
+ | `onFormStateChange` | Use RHF's `watch()` or `useWatch()` hooks |
34
+ | `formValidator` | Use field-level validation or RHF resolver |
35
+ | `validateOnChange` | Use `mode="onChange"` instead |
36
+
37
+ ### Key Differences from Old API
29
38
 
30
- Plus all standard HTML form attributes (method, action, encType, target, etc.) except `onSubmit`.
39
+ - **`onSubmit`** now receives only `values` (no `setError` callback)
40
+ - ✅ **`defaultValues`** replaces `formState` for initial values
41
+ - ✅ **`mode`** prop provides flexible validation timing
42
+ - ✅ Full React Hook Form ecosystem access via hooks
43
+ - ✅ Better TypeScript type safety
44
+ - ✅ Improved performance and smaller bundle size
31
45
 
32
46
  ## Examples
33
47
 
34
48
  ### Basic Form
35
49
  ```tsx
36
- import { Form, FormField, Input, Button } from '@delightui/components';
50
+ import { Form, FormField, Input, Button, TextArea } from '@delightui/components';
37
51
 
38
52
  function BasicFormExample() {
39
- const handleSubmit = (values, setError) => {
53
+ const handleSubmit = (values: { name: string; email: string; message: string }) => {
40
54
  console.log('Form submitted:', values);
41
-
42
- // Example validation
43
- if (!values.email || !values.email.includes('@')) {
44
- setError('email', 'Please enter a valid email address');
45
- return;
46
- }
47
-
48
- // Process form submission
49
55
  alert('Form submitted successfully!');
50
56
  };
51
57
 
58
+ const validateEmail = (value: string) => {
59
+ if (!value.includes('@')) {
60
+ return 'Please enter a valid email address';
61
+ }
62
+ return undefined;
63
+ };
64
+
52
65
  return (
53
- <Form onSubmit={handleSubmit}>
66
+ <Form
67
+ defaultValues={{ name: '', email: '', message: '' }}
68
+ onSubmit={handleSubmit}
69
+ >
54
70
  <FormField name="name" label="Full Name" required>
55
71
  <Input placeholder="Enter your name" />
56
72
  </FormField>
57
-
58
- <FormField name="email" label="Email Address" required>
73
+
74
+ <FormField name="email" label="Email Address" required validate={validateEmail}>
59
75
  <Input inputType="Email" placeholder="Enter your email" />
60
76
  </FormField>
61
-
77
+
62
78
  <FormField name="message" label="Message">
63
79
  <TextArea placeholder="Your message..." rows={4} />
64
80
  </FormField>
65
-
81
+
66
82
  <Button actionType="submit">Submit Form</Button>
67
83
  </Form>
68
84
  );
@@ -71,115 +87,127 @@ function BasicFormExample() {
71
87
 
72
88
  ### Form with Validation
73
89
  ```tsx
74
- function ValidationFormExample() {
75
- const handleSubmit = (values, setError) => {
76
- // Custom validation logic
77
- let hasErrors = false;
78
-
79
- if (!values.password || values.password.length < 8) {
80
- setError('password', 'Password must be at least 8 characters');
81
- hasErrors = true;
82
- }
83
-
84
- if (values.password !== values.confirmPassword) {
85
- setError('confirmPassword', 'Passwords do not match');
86
- hasErrors = true;
90
+ import { useWatch } from 'react-hook-form';
91
+
92
+ function ConfirmPasswordField() {
93
+ const password = useWatch({ name: 'password' });
94
+
95
+ const validateConfirm = (value: string) => {
96
+ if (value !== password) {
97
+ return 'Passwords do not match';
87
98
  }
88
-
89
- if (!hasErrors) {
90
- console.log('Creating account:', values);
99
+ return undefined;
100
+ };
101
+
102
+ return (
103
+ <FormField
104
+ name="confirmPassword"
105
+ label="Confirm Password"
106
+ required
107
+ validate={validateConfirm}
108
+ >
109
+ <Input inputType="Password" placeholder="Confirm password" />
110
+ </FormField>
111
+ );
112
+ }
113
+
114
+ function ValidationFormExample() {
115
+ const handleSubmit = (values: { username: string; email: string; password: string; confirmPassword: string }) => {
116
+ console.log('Creating account:', values);
117
+ };
118
+
119
+ const validateUsername = (value: string) => {
120
+ if (value.length < 3) {
121
+ return 'Username must be at least 3 characters';
91
122
  }
123
+ return undefined;
92
124
  };
93
125
 
94
- const formValidator = (values, setError) => {
95
- let isValid = true;
96
-
97
- // Username validation
98
- if (!values.username || values.username.length < 3) {
99
- setError('username', 'Username must be at least 3 characters');
100
- isValid = false;
126
+ const validateEmail = (value: string) => {
127
+ if (!/\S+@\S+\.\S+/.test(value)) {
128
+ return 'Please enter a valid email';
101
129
  }
102
-
103
- // Email validation
104
- if (!values.email || !/\S+@\S+\.\S+/.test(values.email)) {
105
- setError('email', 'Please enter a valid email');
106
- isValid = false;
130
+ return undefined;
131
+ };
132
+
133
+ const validatePassword = (value: string) => {
134
+ if (value.length < 8) {
135
+ return 'Password must be at least 8 characters';
107
136
  }
108
-
109
- return isValid;
137
+ return undefined;
110
138
  };
111
139
 
112
140
  return (
113
- <Form
141
+ <Form
142
+ defaultValues={{ username: '', email: '', password: '', confirmPassword: '' }}
114
143
  onSubmit={handleSubmit}
115
- formValidator={formValidator}
116
- validateOnChange={true}
144
+ mode="onChange" // Validate on every change
117
145
  >
118
- <FormField name="username" label="Username" required>
146
+ <FormField name="username" label="Username" required validate={validateUsername}>
119
147
  <Input placeholder="Choose a username" />
120
148
  </FormField>
121
-
122
- <FormField name="email" label="Email" required>
149
+
150
+ <FormField name="email" label="Email" required validate={validateEmail}>
123
151
  <Input inputType="Email" placeholder="your@email.com" />
124
152
  </FormField>
125
-
126
- <FormField name="password" label="Password" required>
153
+
154
+ <FormField name="password" label="Password" required validate={validatePassword}>
127
155
  <Input inputType="Password" placeholder="Create password" />
128
156
  </FormField>
129
-
130
- <FormField name="confirmPassword" label="Confirm Password" required>
131
- <Input inputType="Password" placeholder="Confirm password" />
132
- </FormField>
133
-
157
+
158
+ <ConfirmPasswordField />
159
+
134
160
  <div className="form-actions">
135
161
  <Button actionType="submit">Create Account</Button>
136
- <Button type="Outlined" actionType="reset">Clear Form</Button>
137
162
  </div>
138
163
  </Form>
139
164
  );
140
165
  }
141
166
  ```
142
167
 
143
- ### Form with Initial State
168
+ ### Form with Initial Values
144
169
  ```tsx
145
170
  function InitialStateFormExample() {
146
- const initialFormState = {
147
- firstName: 'John',
148
- lastName: 'Doe',
149
- email: 'john.doe@example.com',
150
- role: 'developer',
151
- notifications: true
152
- };
153
-
154
- const handleSubmit = (values, setError) => {
171
+ const handleSubmit = (values: {
172
+ firstName: string;
173
+ lastName: string;
174
+ email: string;
175
+ role: string;
176
+ notifications: boolean;
177
+ }) => {
155
178
  console.log('Updated profile:', values);
156
179
  };
157
180
 
158
- const handleStateChange = (state, setError) => {
159
- // Validate on state change
160
- if (state.email && !state.email.includes('@')) {
161
- setError('email', 'Invalid email format');
181
+ const validateEmail = (value: string) => {
182
+ if (!value.includes('@')) {
183
+ return 'Invalid email format';
162
184
  }
185
+ return undefined;
163
186
  };
164
187
 
165
188
  return (
166
- <Form
167
- formState={initialFormState}
189
+ <Form
190
+ defaultValues={{
191
+ firstName: 'John',
192
+ lastName: 'Doe',
193
+ email: 'john.doe@example.com',
194
+ role: 'developer',
195
+ notifications: true
196
+ }}
168
197
  onSubmit={handleSubmit}
169
- onFormStateChange={handleStateChange}
170
198
  >
171
199
  <FormField name="firstName" label="First Name" required>
172
200
  <Input />
173
201
  </FormField>
174
-
202
+
175
203
  <FormField name="lastName" label="Last Name" required>
176
204
  <Input />
177
205
  </FormField>
178
-
179
- <FormField name="email" label="Email" required>
206
+
207
+ <FormField name="email" label="Email" required validate={validateEmail}>
180
208
  <Input inputType="Email" />
181
209
  </FormField>
182
-
210
+
183
211
  <FormField name="role" label="Role">
184
212
  <Select>
185
213
  <Option value="developer">Developer</Option>
@@ -187,11 +215,11 @@ function InitialStateFormExample() {
187
215
  <Option value="manager">Manager</Option>
188
216
  </Select>
189
217
  </FormField>
190
-
218
+
191
219
  <FormField name="notifications" label="Email Notifications">
192
220
  <Checkbox>Send me email notifications</Checkbox>
193
221
  </FormField>
194
-
222
+
195
223
  <Button actionType="submit">Update Profile</Button>
196
224
  </Form>
197
225
  );
@@ -536,24 +564,24 @@ function AsyncFormExample() {
536
564
 
537
565
  ### Autosave Form
538
566
  ```tsx
567
+ import { useState } from 'react';
568
+
539
569
  function AutosaveFormExample() {
540
570
  const [saveCount, setSaveCount] = useState(0);
541
- const [lastSaved, setLastSaved] = useState(null);
571
+ const [lastSaved, setLastSaved] = useState<string | null>(null);
542
572
 
543
- const handleSubmit = async (values, setError) => {
573
+ const handleAutosave = async (values: { title: string; content: string }) => {
544
574
  try {
545
575
  // Simulate API call to save draft
546
- const response = await fetch('/api/drafts', {
576
+ await fetch('/api/drafts', {
547
577
  method: 'POST',
548
578
  headers: { 'Content-Type': 'application/json' },
549
579
  body: JSON.stringify(values)
550
580
  });
551
-
552
- if (response.ok) {
553
- setSaveCount(prev => prev + 1);
554
- setLastSaved(new Date().toLocaleTimeString());
555
- console.log('Draft auto-saved:', values);
556
- }
581
+
582
+ setSaveCount(prev => prev + 1);
583
+ setLastSaved(new Date().toLocaleTimeString());
584
+ console.log('Draft auto-saved:', values);
557
585
  } catch (error) {
558
586
  console.error('Failed to save draft:', error);
559
587
  }
@@ -561,29 +589,23 @@ function AutosaveFormExample() {
561
589
 
562
590
  return (
563
591
  <div>
564
- <Form
565
- onSubmit={handleSubmit}
592
+ <Form
593
+ defaultValues={{ title: '', content: '' }}
594
+ onSubmit={handleAutosave}
566
595
  autosave={true}
567
596
  autosaveDelayMs={2000} // Wait 2 seconds after user stops typing
568
597
  >
569
598
  <FormField name="title" label="Title">
570
599
  <Input placeholder="Enter title" />
571
600
  </FormField>
572
-
601
+
573
602
  <FormField name="content" label="Content">
574
- <TextArea
575
- placeholder="Start writing..."
576
- rows={10}
577
- />
578
- </FormField>
579
-
580
- <FormField name="tags" label="Tags">
581
- <ChipInput
582
- placeholder="Add tags"
583
- options={['react', 'typescript', 'design', 'tutorial']}
603
+ <TextArea
604
+ placeholder="Start writing..."
605
+ rows={10}
584
606
  />
585
607
  </FormField>
586
-
608
+
587
609
  {lastSaved && (
588
610
  <Text type="BodySmall">
589
611
  Auto-saved {saveCount} times. Last saved at {lastSaved}
@@ -595,66 +617,740 @@ function AutosaveFormExample() {
595
617
  }
596
618
  ```
597
619
 
598
- ### Autosave with Manual Submit
620
+ ### Async Validation
599
621
  ```tsx
600
- function AutosaveWithManualSubmitExample() {
601
- const [isDraft, setIsDraft] = useState(true);
622
+ function AsyncValidationExample() {
623
+ const validateUsername = async (value: string) => {
624
+ if (!value) return 'Username is required';
602
625
 
603
- const handleAutosave = (values, setError) => {
604
- if (isDraft) {
605
- console.log('Draft auto-saved:', values);
606
- // Save to local storage or draft API
607
- localStorage.setItem('formDraft', JSON.stringify(values));
626
+ // Simulate API call
627
+ const response = await fetch(`/api/check-username?username=${value}`);
628
+ const { available } = await response.json();
629
+
630
+ if (!available) {
631
+ return 'Username is already taken';
608
632
  }
633
+ return undefined;
609
634
  };
610
635
 
611
- const handlePublish = () => {
612
- setIsDraft(false);
613
- // Form will now submit normally
636
+ const handleSubmit = (values: { username: string; email: string }) => {
637
+ console.log('Account created:', values);
614
638
  };
615
639
 
616
640
  return (
617
- <Form
618
- onSubmit={handleAutosave}
619
- autosave={isDraft}
620
- autosaveDelayMs={1000}
621
- formState={{
622
- title: localStorage.getItem('formDraft')?.title || '',
623
- content: localStorage.getItem('formDraft')?.content || ''
641
+ <Form
642
+ defaultValues={{ username: '', email: '' }}
643
+ onSubmit={handleSubmit}
644
+ >
645
+ <FormField
646
+ name="username"
647
+ label="Username"
648
+ required
649
+ asyncValidate={validateUsername}
650
+ >
651
+ <Input placeholder="Choose a username" />
652
+ </FormField>
653
+
654
+ <FormField name="email" label="Email" required>
655
+ <Input inputType="Email" placeholder="your@email.com" />
656
+ </FormField>
657
+
658
+ <Button actionType="submit">Create Account</Button>
659
+ </Form>
660
+ );
661
+ }
662
+ ```
663
+
664
+ ### Using React Hook Form Hooks
665
+ ```tsx
666
+ import { useFormContext, useWatch } from 'react-hook-form';
667
+
668
+ function FormDebugger() {
669
+ const { formState } = useFormContext();
670
+ const watchedValues = useWatch();
671
+
672
+ return (
673
+ <div style={{ padding: '10px', background: '#f0f0f0', marginTop: '10px' }}>
674
+ <Text type="BodySmall">
675
+ Form is {formState.isDirty ? 'modified' : 'pristine'}
676
+ </Text>
677
+ <Text type="BodySmall">
678
+ Form is {formState.isValid ? 'valid' : 'invalid'}
679
+ </Text>
680
+ <pre>{JSON.stringify(watchedValues, null, 2)}</pre>
681
+ </div>
682
+ );
683
+ }
684
+
685
+ function FormWithHooksExample() {
686
+ const handleSubmit = (values: { email: string; password: string }) => {
687
+ console.log('Submitted:', values);
688
+ };
689
+
690
+ return (
691
+ <Form
692
+ defaultValues={{ email: '', password: '' }}
693
+ onSubmit={handleSubmit}
694
+ mode="onChange"
695
+ >
696
+ <FormField name="email" label="Email" required>
697
+ <Input inputType="Email" />
698
+ </FormField>
699
+
700
+ <FormField name="password" label="Password" required>
701
+ <Input inputType="Password" />
702
+ </FormField>
703
+
704
+ <Button actionType="submit">Sign In</Button>
705
+
706
+ <FormDebugger />
707
+ </Form>
708
+ );
709
+ }
710
+ ```
711
+
712
+ ### Tracking Dirty Fields
713
+ ```tsx
714
+ import { useFormContext } from 'react-hook-form';
715
+
716
+ function DirtyFieldsExample() {
717
+ const handleSubmit = (values: { name: string; email: string; phone: string }) => {
718
+ console.log('Submitted:', values);
719
+ };
720
+
721
+ return (
722
+ <Form
723
+ defaultValues={{ name: 'John Doe', email: 'john@example.com', phone: '' }}
724
+ onSubmit={handleSubmit}
725
+ >
726
+ <FormField name="name" label="Name" required>
727
+ <Input />
728
+ </FormField>
729
+
730
+ <FormField name="email" label="Email" required>
731
+ <Input inputType="Email" />
732
+ </FormField>
733
+
734
+ <FormField name="phone" label="Phone">
735
+ <Input inputType="Tel" />
736
+ </FormField>
737
+
738
+ <DirtyFieldsIndicator />
739
+
740
+ <Button actionType="submit">Save Changes</Button>
741
+ </Form>
742
+ );
743
+ }
744
+
745
+ function DirtyFieldsIndicator() {
746
+ const { formState } = useFormContext();
747
+ const { dirtyFields, isDirty } = formState;
748
+
749
+ if (!isDirty) {
750
+ return (
751
+ <div style={{ padding: '10px', background: '#e8f5e9', marginTop: '10px' }}>
752
+ <Text type="BodySmall">No changes made</Text>
753
+ </div>
754
+ );
755
+ }
756
+
757
+ const changedFields = Object.keys(dirtyFields).filter(key => dirtyFields[key]);
758
+
759
+ return (
760
+ <div style={{ padding: '10px', background: '#fff3e0', marginTop: '10px' }}>
761
+ <Text type="BodySmall" weight="SemiBold">
762
+ Modified fields: {changedFields.join(', ')}
763
+ </Text>
764
+ <Text type="BodySmall">
765
+ You have unsaved changes in {changedFields.length} field(s)
766
+ </Text>
767
+ </div>
768
+ );
769
+ }
770
+ ```
771
+
772
+ ### Server-Side Error Handling
773
+ ```tsx
774
+ import { useFormContext } from 'react-hook-form';
775
+
776
+ function ServerValidationExample() {
777
+ const [isSubmitting, setIsSubmitting] = useState(false);
778
+
779
+ const handleSubmit = async (values: { email: string; username: string }) => {
780
+ setIsSubmitting(true);
781
+
782
+ try {
783
+ const response = await fetch('/api/register', {
784
+ method: 'POST',
785
+ headers: { 'Content-Type': 'application/json' },
786
+ body: JSON.stringify(values)
787
+ });
788
+
789
+ const data = await response.json();
790
+
791
+ if (!response.ok) {
792
+ // Server returned validation errors
793
+ throw data;
794
+ }
795
+
796
+ alert('Registration successful!');
797
+ } catch (error: any) {
798
+ // Handle server-side validation errors
799
+ // Error format: { field: 'email', message: 'Email already exists' }
800
+ console.error('Server validation error:', error);
801
+ } finally {
802
+ setIsSubmitting(false);
803
+ }
804
+ };
805
+
806
+ return (
807
+ <Form
808
+ defaultValues={{ email: '', username: '' }}
809
+ onSubmit={handleSubmit}
810
+ >
811
+ <FormField name="email" label="Email" required>
812
+ <Input inputType="Email" />
813
+ </FormField>
814
+
815
+ <FormField name="username" label="Username" required>
816
+ <Input />
817
+ </FormField>
818
+
819
+ <ServerErrorHandler />
820
+
821
+ <Button actionType="submit" loading={isSubmitting}>
822
+ Register
823
+ </Button>
824
+ </Form>
825
+ );
826
+ }
827
+
828
+ function ServerErrorHandler() {
829
+ const { setError, clearErrors } = useFormContext();
830
+
831
+ // Example: You could expose a method to set errors from parent
832
+ // or handle them via a context/state management solution
833
+
834
+ const handleServerError = (field: string, message: string) => {
835
+ setError(field, {
836
+ type: 'server',
837
+ message: message
838
+ });
839
+ };
840
+
841
+ const handleClearErrors = () => {
842
+ clearErrors(); // Clear all errors
843
+ // or clearErrors('email'); // Clear specific field
844
+ };
845
+
846
+ return null; // This is a utility component
847
+ }
848
+ ```
849
+
850
+ ### Dynamic Form Arrays (useFieldArray)
851
+ ```tsx
852
+ import { useFieldArray, useFormContext } from 'react-hook-form';
853
+
854
+ interface FormValues {
855
+ name: string;
856
+ emails: { value: string }[];
857
+ }
858
+
859
+ function DynamicFieldArrayExample() {
860
+ const handleSubmit = (values: FormValues) => {
861
+ console.log('Submitted:', values);
862
+ alert(`Name: ${values.name}\nEmails: ${values.emails.map(e => e.value).join(', ')}`);
863
+ };
864
+
865
+ return (
866
+ <Form<FormValues>
867
+ defaultValues={{
868
+ name: '',
869
+ emails: [{ value: '' }] // Start with one email field
624
870
  }}
871
+ onSubmit={handleSubmit}
625
872
  >
626
- <FormField name="title" label="Article Title" required>
627
- <Input placeholder="Enter title" />
873
+ <FormField name="name" label="Name" required>
874
+ <Input placeholder="Enter your name" />
628
875
  </FormField>
629
-
630
- <FormField name="content" label="Article Content" required>
631
- <TextArea placeholder="Write your article..." rows={15} />
876
+
877
+ <EmailFieldArray />
878
+
879
+ <Button actionType="submit">Submit</Button>
880
+ </Form>
881
+ );
882
+ }
883
+
884
+ function EmailFieldArray() {
885
+ const { control } = useFormContext();
886
+ const { fields, append, remove } = useFieldArray({
887
+ control,
888
+ name: 'emails'
889
+ });
890
+
891
+ return (
892
+ <div>
893
+ <Text type="BodyMedium" weight="SemiBold">Email Addresses</Text>
894
+
895
+ {fields.map((field, index) => (
896
+ <div key={field.id} style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
897
+ <FormField
898
+ name={`emails.${index}.value` as any}
899
+ label={`Email ${index + 1}`}
900
+ required
901
+ >
902
+ <Input inputType="Email" placeholder="email@example.com" />
903
+ </FormField>
904
+
905
+ {fields.length > 1 && (
906
+ <Button
907
+ type="Ghost"
908
+ style="Destructive"
909
+ size="Small"
910
+ onClick={() => remove(index)}
911
+ >
912
+ Remove
913
+ </Button>
914
+ )}
915
+ </div>
916
+ ))}
917
+
918
+ <Button
919
+ type="Ghost"
920
+ size="Small"
921
+ onClick={() => append({ value: '' })}
922
+ style={{ marginTop: '8px' }}
923
+ >
924
+ Add Email
925
+ </Button>
926
+ </div>
927
+ );
928
+ }
929
+ ```
930
+
931
+ ### Conditional Validation
932
+ ```tsx
933
+ import { useWatch } from 'react-hook-form';
934
+
935
+ function ConditionalValidationExample() {
936
+ const handleSubmit = (values: { contactMethod: string; email?: string; phone?: string }) => {
937
+ console.log('Submitted:', values);
938
+ };
939
+
940
+ return (
941
+ <Form
942
+ defaultValues={{ contactMethod: 'email', email: '', phone: '' }}
943
+ onSubmit={handleSubmit}
944
+ >
945
+ <FormField name="contactMethod" label="Preferred Contact Method">
946
+ <Select>
947
+ <Option value="email">Email</Option>
948
+ <Option value="phone">Phone</Option>
949
+ <Option value="both">Both</Option>
950
+ </Select>
632
951
  </FormField>
633
-
634
- <FormField name="category" label="Category">
952
+
953
+ <ConditionalFields />
954
+
955
+ <Button actionType="submit">Submit</Button>
956
+ </Form>
957
+ );
958
+ }
959
+
960
+ function ConditionalFields() {
961
+ const contactMethod = useWatch({ name: 'contactMethod' });
962
+
963
+ const validateEmail = (value: string) => {
964
+ if ((contactMethod === 'email' || contactMethod === 'both') && !value) {
965
+ return 'Email is required for your selected contact method';
966
+ }
967
+ if (value && !value.includes('@')) {
968
+ return 'Invalid email format';
969
+ }
970
+ return undefined;
971
+ };
972
+
973
+ const validatePhone = (value: string) => {
974
+ if ((contactMethod === 'phone' || contactMethod === 'both') && !value) {
975
+ return 'Phone is required for your selected contact method';
976
+ }
977
+ return undefined;
978
+ };
979
+
980
+ return (
981
+ <>
982
+ {(contactMethod === 'email' || contactMethod === 'both') && (
983
+ <FormField
984
+ name="email"
985
+ label="Email"
986
+ required={contactMethod === 'email' || contactMethod === 'both'}
987
+ validate={validateEmail}
988
+ >
989
+ <Input inputType="Email" placeholder="your@email.com" />
990
+ </FormField>
991
+ )}
992
+
993
+ {(contactMethod === 'phone' || contactMethod === 'both') && (
994
+ <FormField
995
+ name="phone"
996
+ label="Phone"
997
+ required={contactMethod === 'phone' || contactMethod === 'both'}
998
+ validate={validatePhone}
999
+ >
1000
+ <Input inputType="Tel" placeholder="(555) 123-4567" />
1001
+ </FormField>
1002
+ )}
1003
+ </>
1004
+ );
1005
+ }
1006
+ ```
1007
+
1008
+ ### Reset with Specific Values
1009
+ ```tsx
1010
+ import { useFormContext } from 'react-hook-form';
1011
+
1012
+ function ResetWithValuesExample() {
1013
+ const savedData = {
1014
+ name: 'John Doe',
1015
+ email: 'john@example.com',
1016
+ bio: 'Software developer'
1017
+ };
1018
+
1019
+ const handleSubmit = (values: typeof savedData) => {
1020
+ console.log('Saved:', values);
1021
+ alert('Profile updated!');
1022
+ };
1023
+
1024
+ return (
1025
+ <Form
1026
+ defaultValues={savedData}
1027
+ onSubmit={handleSubmit}
1028
+ >
1029
+ <FormField name="name" label="Name" required>
1030
+ <Input />
1031
+ </FormField>
1032
+
1033
+ <FormField name="email" label="Email" required>
1034
+ <Input inputType="Email" />
1035
+ </FormField>
1036
+
1037
+ <FormField name="bio" label="Bio">
1038
+ <TextArea rows={4} />
1039
+ </FormField>
1040
+
1041
+ <ResetButtons savedData={savedData} />
1042
+
1043
+ <Button actionType="submit">Save Changes</Button>
1044
+ </Form>
1045
+ );
1046
+ }
1047
+
1048
+ function ResetButtons({ savedData }: { savedData: any }) {
1049
+ const { reset, formState } = useFormContext();
1050
+
1051
+ return (
1052
+ <div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
1053
+ <Button
1054
+ type="Outlined"
1055
+ onClick={() => reset(savedData)}
1056
+ disabled={!formState.isDirty}
1057
+ >
1058
+ Discard Changes
1059
+ </Button>
1060
+
1061
+ <Button
1062
+ type="Ghost"
1063
+ onClick={() => reset({ name: '', email: '', bio: '' })}
1064
+ >
1065
+ Clear All
1066
+ </Button>
1067
+ </div>
1068
+ );
1069
+ }
1070
+ ```
1071
+
1072
+ ### Watching Specific Fields
1073
+ ```tsx
1074
+ import { useWatch } from 'react-hook-form';
1075
+
1076
+ function WatchSpecificFieldsExample() {
1077
+ const handleSubmit = (values: { country: string; state: string; city: string }) => {
1078
+ console.log('Submitted:', values);
1079
+ };
1080
+
1081
+ return (
1082
+ <Form
1083
+ defaultValues={{ country: '', state: '', city: '' }}
1084
+ onSubmit={handleSubmit}
1085
+ >
1086
+ <FormField name="country" label="Country" required>
635
1087
  <Select>
636
- <Option value="tech">Technology</Option>
637
- <Option value="design">Design</Option>
638
- <Option value="business">Business</Option>
1088
+ <Option value="">Select a country</Option>
1089
+ <Option value="us">United States</Option>
1090
+ <Option value="canada">Canada</Option>
1091
+ <Option value="uk">United Kingdom</Option>
639
1092
  </Select>
640
1093
  </FormField>
641
-
642
- <div className="form-actions">
643
- <Button type="Outlined">Save as Draft</Button>
644
- <Button
645
- actionType="submit"
646
- onClick={handlePublish}
1094
+
1095
+ <DependentFields />
1096
+
1097
+ <Button actionType="submit">Submit</Button>
1098
+ </Form>
1099
+ );
1100
+ }
1101
+
1102
+ function DependentFields() {
1103
+ // Watch only the country field
1104
+ const country = useWatch({ name: 'country' });
1105
+ const { setValue } = useFormContext();
1106
+
1107
+ // Reset dependent fields when country changes
1108
+ useEffect(() => {
1109
+ setValue('state', '');
1110
+ setValue('city', '');
1111
+ }, [country, setValue]);
1112
+
1113
+ if (!country) return null;
1114
+
1115
+ return (
1116
+ <>
1117
+ <FormField name="state" label="State/Province" required>
1118
+ <Select>
1119
+ <Option value="">Select a state</Option>
1120
+ {country === 'us' && (
1121
+ <>
1122
+ <Option value="ca">California</Option>
1123
+ <Option value="ny">New York</Option>
1124
+ <Option value="tx">Texas</Option>
1125
+ </>
1126
+ )}
1127
+ {country === 'canada' && (
1128
+ <>
1129
+ <Option value="on">Ontario</Option>
1130
+ <Option value="qc">Quebec</Option>
1131
+ <Option value="bc">British Columbia</Option>
1132
+ </>
1133
+ )}
1134
+ </Select>
1135
+ </FormField>
1136
+
1137
+ <FormField name="city" label="City" required>
1138
+ <Input placeholder="Enter your city" />
1139
+ </FormField>
1140
+ </>
1141
+ );
1142
+ }
1143
+ ```
1144
+
1145
+ ### Manual Validation Triggering
1146
+ ```tsx
1147
+ import { useFormContext } from 'react-hook-form';
1148
+
1149
+ function ManualValidationExample() {
1150
+ const [isChecking, setIsChecking] = useState(false);
1151
+ const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
1152
+
1153
+ const handleSubmit = (values: { username: string; email: string }) => {
1154
+ console.log('Submitted:', values);
1155
+ };
1156
+
1157
+ return (
1158
+ <Form
1159
+ defaultValues={{ username: '', email: '' }}
1160
+ onSubmit={handleSubmit}
1161
+ >
1162
+ <UsernameFieldWithCheck
1163
+ isChecking={isChecking}
1164
+ setIsChecking={setIsChecking}
1165
+ isAvailable={isAvailable}
1166
+ setIsAvailable={setIsAvailable}
1167
+ />
1168
+
1169
+ <FormField name="email" label="Email" required>
1170
+ <Input inputType="Email" />
1171
+ </FormField>
1172
+
1173
+ <Button actionType="submit">Register</Button>
1174
+ </Form>
1175
+ );
1176
+ }
1177
+
1178
+ function UsernameFieldWithCheck({
1179
+ isChecking,
1180
+ setIsChecking,
1181
+ isAvailable,
1182
+ setIsAvailable
1183
+ }: {
1184
+ isChecking: boolean;
1185
+ setIsChecking: (value: boolean) => void;
1186
+ isAvailable: boolean | null;
1187
+ setIsAvailable: (value: boolean | null) => void;
1188
+ }) {
1189
+ const { trigger, getValues, setError, clearErrors } = useFormContext();
1190
+
1191
+ const checkAvailability = async () => {
1192
+ // First, validate the username field
1193
+ const isValid = await trigger('username');
1194
+
1195
+ if (!isValid) {
1196
+ return; // Don't check if validation fails
1197
+ }
1198
+
1199
+ setIsChecking(true);
1200
+ setIsAvailable(null);
1201
+
1202
+ try {
1203
+ const username = getValues('username');
1204
+ const response = await fetch(`/api/check-username?username=${username}`);
1205
+ const { available } = await response.json();
1206
+
1207
+ setIsAvailable(available);
1208
+
1209
+ if (!available) {
1210
+ setError('username', {
1211
+ type: 'manual',
1212
+ message: 'Username is already taken'
1213
+ });
1214
+ } else {
1215
+ clearErrors('username');
1216
+ }
1217
+ } catch (error) {
1218
+ console.error('Failed to check username:', error);
1219
+ } finally {
1220
+ setIsChecking(false);
1221
+ }
1222
+ };
1223
+
1224
+ return (
1225
+ <div>
1226
+ <FormField
1227
+ name="username"
1228
+ label="Username"
1229
+ required
1230
+ validate={(value) => {
1231
+ if (value.length < 3) {
1232
+ return 'Username must be at least 3 characters';
1233
+ }
1234
+ if (!/^[a-zA-Z0-9_]+$/.test(value)) {
1235
+ return 'Username can only contain letters, numbers, and underscores';
1236
+ }
1237
+ return undefined;
1238
+ }}
1239
+ >
1240
+ <Input placeholder="Choose a username" />
1241
+ </FormField>
1242
+
1243
+ <div style={{ marginTop: '4px', display: 'flex', alignItems: 'center', gap: '8px' }}>
1244
+ <Button
1245
+ type="Ghost"
1246
+ size="Small"
1247
+ onClick={checkAvailability}
1248
+ loading={isChecking}
647
1249
  >
648
- Publish Article
1250
+ Check Availability
649
1251
  </Button>
1252
+
1253
+ {isAvailable === true && (
1254
+ <Text type="BodySmall" style={{ color: 'green' }}>
1255
+ ✓ Username is available
1256
+ </Text>
1257
+ )}
1258
+ {isAvailable === false && (
1259
+ <Text type="BodySmall" style={{ color: 'red' }}>
1260
+ ✗ Username is taken
1261
+ </Text>
1262
+ )}
650
1263
  </div>
651
-
652
- {isDraft && (
653
- <Text type="BodySmall" style={{ marginTop: '8px' }}>
654
- Your changes are automatically saved as you type
655
- </Text>
656
- )}
1264
+ </div>
1265
+ );
1266
+ }
1267
+ ```
1268
+
1269
+ ### FormState Properties Reference
1270
+ ```tsx
1271
+ import { useFormContext } from 'react-hook-form';
1272
+
1273
+ function FormStateReferenceExample() {
1274
+ const handleSubmit = (values: { name: string; email: string }) => {
1275
+ console.log('Submitted:', values);
1276
+ };
1277
+
1278
+ return (
1279
+ <Form
1280
+ defaultValues={{ name: '', email: '' }}
1281
+ onSubmit={handleSubmit}
1282
+ mode="onChange"
1283
+ >
1284
+ <FormField name="name" label="Name" required>
1285
+ <Input />
1286
+ </FormField>
1287
+
1288
+ <FormField name="email" label="Email" required>
1289
+ <Input inputType="Email" />
1290
+ </FormField>
1291
+
1292
+ <FormStateDisplay />
1293
+
1294
+ <Button actionType="submit">Submit</Button>
657
1295
  </Form>
658
1296
  );
659
1297
  }
660
- ```
1298
+
1299
+ function FormStateDisplay() {
1300
+ const { formState } = useFormContext();
1301
+ const {
1302
+ isDirty, // true if any field has been modified
1303
+ isValid, // true if no validation errors
1304
+ isSubmitting, // true during async submission
1305
+ isSubmitted, // true if form has been submitted
1306
+ isSubmitSuccessful, // true if submission was successful
1307
+ submitCount, // number of times form has been submitted
1308
+ touchedFields, // fields that have been focused and blurred
1309
+ dirtyFields, // fields that have been modified
1310
+ errors // current validation errors
1311
+ } = formState;
1312
+
1313
+ return (
1314
+ <div style={{
1315
+ padding: '12px',
1316
+ background: '#f5f5f5',
1317
+ borderRadius: '4px',
1318
+ marginTop: '16px',
1319
+ fontSize: '13px'
1320
+ }}>
1321
+ <Text type="BodySmall" weight="SemiBold">Form State Properties:</Text>
1322
+
1323
+ <div style={{ marginTop: '8px', display: 'grid', gap: '4px' }}>
1324
+ <div><strong>isDirty:</strong> {String(isDirty)}</div>
1325
+ <div><strong>isValid:</strong> {String(isValid)}</div>
1326
+ <div><strong>isSubmitting:</strong> {String(isSubmitting)}</div>
1327
+ <div><strong>isSubmitted:</strong> {String(isSubmitted)}</div>
1328
+ <div><strong>isSubmitSuccessful:</strong> {String(isSubmitSuccessful)}</div>
1329
+ <div><strong>submitCount:</strong> {submitCount}</div>
1330
+ <div><strong>touchedFields:</strong> {JSON.stringify(touchedFields)}</div>
1331
+ <div><strong>dirtyFields:</strong> {JSON.stringify(dirtyFields)}</div>
1332
+ <div><strong>errors:</strong> {JSON.stringify(errors)}</div>
1333
+ </div>
1334
+
1335
+ <div style={{ marginTop: '12px', padding: '8px', background: '#e3f2fd' }}>
1336
+ <Text type="BodySmall" weight="SemiBold">Common Patterns:</Text>
1337
+ <ul style={{ margin: '8px 0', paddingLeft: '20px', fontSize: '12px' }}>
1338
+ <li>Disable submit button: <code>disabled={`{!isDirty || !isValid}`}</code></li>
1339
+ <li>Show loading: <code>loading={`{isSubmitting}`}</code></li>
1340
+ <li>Show success message: <code>{`{isSubmitSuccessful && '✓ Saved!'}`}</code></li>
1341
+ <li>Warn on navigation: <code>{`{isDirty && 'Unsaved changes'}`}</code></li>
1342
+ </ul>
1343
+ </div>
1344
+ </div>
1345
+ );
1346
+ }
1347
+ ```
1348
+
1349
+ ---
1350
+
1351
+ ## Additional Resources
1352
+
1353
+ - [React Hook Form Documentation](https://react-hook-form.com/)
1354
+ - [Migration Guide](../../../FORM_MIGRATION_GUIDE.md)
1355
+ - [FormField Documentation](../../molecules/FormField.md)
1356
+ - [Example Files](../../../src/components/organisms/Form/examples/)