@delightui/components 0.1.162-alpha.4 → 0.1.162

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 +2 -28
  18. package/dist/cjs/components/molecules/FormField/FormField.presenter.d.ts +8 -17
  19. package/dist/cjs/components/molecules/FormField/FormField.types.d.ts +52 -115
  20. package/dist/cjs/components/molecules/FormField/FormField.utils.d.ts +11 -8
  21. package/dist/cjs/components/molecules/FormField/index.d.ts +2 -1
  22. package/dist/cjs/components/molecules/Search/Search.d.ts +1 -1
  23. package/dist/cjs/components/molecules/Search/Search.presenter.d.ts +2 -2
  24. package/dist/cjs/components/molecules/Select/Select.presenter.d.ts +4 -4
  25. package/dist/cjs/components/organisms/Form/DropzoneFormExample.d.ts +6 -0
  26. package/dist/cjs/components/organisms/Form/Form.d.ts +1 -22
  27. package/dist/cjs/components/organisms/Form/Form.types.d.ts +93 -64
  28. package/dist/cjs/components/organisms/Form/Form.utils.d.ts +2 -0
  29. package/dist/cjs/components/organisms/Form/FormContext.d.ts +5 -0
  30. package/dist/cjs/components/organisms/Form/UpdatedFormExample.d.ts +23 -0
  31. package/dist/cjs/components/organisms/Form/{examples/UseFormExample.d.ts → UseFormExample.d.ts} +3 -0
  32. package/dist/cjs/components/organisms/Form/index.d.ts +4 -7
  33. package/dist/cjs/components/organisms/Form/useForm.d.ts +50 -0
  34. package/dist/cjs/library.js +3 -3
  35. package/dist/cjs/library.js.map +1 -1
  36. package/dist/esm/components/atoms/Checkbox/Checkbox.d.ts +1 -1
  37. package/dist/esm/components/atoms/Checkbox/Checkbox.presenter.d.ts +1 -1
  38. package/dist/esm/components/atoms/CheckboxItem/CheckboxItem.d.ts +1 -1
  39. package/dist/esm/components/atoms/CheckboxItem/CheckboxItem.presenter.d.ts +2 -2
  40. package/dist/esm/components/atoms/CustomToggle/CustomToggle.d.ts +1 -1
  41. package/dist/esm/components/atoms/Input/Input.d.ts +1 -1
  42. package/dist/esm/components/atoms/Input/Input.presenter.d.ts +2 -2
  43. package/dist/esm/components/atoms/Password/Password.presenter.d.ts +2 -2
  44. package/dist/esm/components/atoms/RadioButton/RadioButton.d.ts +1 -1
  45. package/dist/esm/components/atoms/RadioButtonItem/RadioButtonItem.d.ts +1 -1
  46. package/dist/esm/components/atoms/RadioButtonItem/RadioButtonItem.presenter.d.ts +2 -2
  47. package/dist/esm/components/atoms/TextArea/TextArea.d.ts +1 -1
  48. package/dist/esm/components/atoms/TextArea/TextArea.presenter.d.ts +1 -1
  49. package/dist/esm/components/atoms/Toggle/Toggle.d.ts +1 -1
  50. package/dist/esm/components/atoms/Toggle/Toggle.presenter.d.ts +2 -2
  51. package/dist/esm/components/atoms/ToggleButton/ToggleButton.d.ts +1 -1
  52. package/dist/esm/components/molecules/FormField/FormField.d.ts +2 -28
  53. package/dist/esm/components/molecules/FormField/FormField.presenter.d.ts +8 -17
  54. package/dist/esm/components/molecules/FormField/FormField.types.d.ts +52 -115
  55. package/dist/esm/components/molecules/FormField/FormField.utils.d.ts +11 -8
  56. package/dist/esm/components/molecules/FormField/index.d.ts +2 -1
  57. package/dist/esm/components/molecules/Search/Search.d.ts +1 -1
  58. package/dist/esm/components/molecules/Search/Search.presenter.d.ts +2 -2
  59. package/dist/esm/components/molecules/Select/Select.presenter.d.ts +4 -4
  60. package/dist/esm/components/organisms/Form/DropzoneFormExample.d.ts +6 -0
  61. package/dist/esm/components/organisms/Form/Form.d.ts +1 -22
  62. package/dist/esm/components/organisms/Form/Form.types.d.ts +93 -64
  63. package/dist/esm/components/organisms/Form/Form.utils.d.ts +2 -0
  64. package/dist/esm/components/organisms/Form/FormContext.d.ts +5 -0
  65. package/dist/esm/components/organisms/Form/UpdatedFormExample.d.ts +23 -0
  66. package/dist/esm/components/organisms/Form/{examples/UseFormExample.d.ts → UseFormExample.d.ts} +3 -0
  67. package/dist/esm/components/organisms/Form/index.d.ts +4 -7
  68. package/dist/esm/components/organisms/Form/useForm.d.ts +50 -0
  69. package/dist/esm/library.js +3 -3
  70. package/dist/esm/library.js.map +1 -1
  71. package/dist/index.d.ts +232 -270
  72. package/docs/components/molecules/FormField.md +34 -129
  73. package/docs/components/organisms/Form.md +162 -858
  74. package/package.json +1 -4
  75. package/dist/cjs/components/molecules/FormField/useSafeController.d.ts +0 -14
  76. package/dist/cjs/components/molecules/FormField/useSafeFormContext.d.ts +0 -8
  77. package/dist/cjs/components/organisms/Form/Form.presenter.d.ts +0 -290
  78. package/dist/cjs/components/organisms/Form/examples/DropzoneFormExample.d.ts +0 -6
  79. package/dist/cjs/components/organisms/Form/examples/UpdatedFormExample.d.ts +0 -2
  80. package/dist/cjs/components/organisms/Form/useAutosave.d.ts +0 -10
  81. package/dist/esm/components/molecules/FormField/useSafeController.d.ts +0 -14
  82. package/dist/esm/components/molecules/FormField/useSafeFormContext.d.ts +0 -8
  83. package/dist/esm/components/organisms/Form/Form.presenter.d.ts +0 -290
  84. package/dist/esm/components/organisms/Form/examples/DropzoneFormExample.d.ts +0 -6
  85. package/dist/esm/components/organisms/Form/examples/UpdatedFormExample.d.ts +0 -2
  86. package/dist/esm/components/organisms/Form/useAutosave.d.ts +0 -10
  87. package/docs/FORM_MIGRATION_GUIDE.md +0 -631
  88. /package/dist/cjs/components/organisms/Form/{examples/AutosaveFormExample.d.ts → AutosaveFormExample.d.ts} +0 -0
  89. /package/dist/esm/components/organisms/Form/{examples/AutosaveFormExample.d.ts → AutosaveFormExample.d.ts} +0 -0
@@ -1,84 +1,68 @@
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
-
5
3
  ## Description
6
4
 
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.
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.
8
6
 
9
7
  ## Aliases
10
8
 
11
9
  - Form
10
+ - FormProvider
11
+ - FormContainer
12
+ - FormWrapper
12
13
 
13
14
  ## Props Breakdown
14
15
 
16
+ **Extends:** `FormHTMLAttributes<HTMLFormElement>` (excluding `onSubmit`)
17
+
15
18
  | Prop | Type | Default | Required | Description |
16
19
  |------|------|---------|----------|-------------|
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) |
20
- | `autosave` | `boolean` | `false` | No | Enable automatic form submission on value changes |
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
20
  | `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
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 |
27
+ | `autosave` | `boolean` | `false` | No | Enable automatic form submission on value changes |
28
+ | `autosaveDelayMs` | `number` | - | No | Delay in milliseconds before autosave triggers |
38
29
 
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
30
+ Plus all standard HTML form attributes (method, action, encType, target, etc.) except `onSubmit`.
45
31
 
46
32
  ## Examples
47
33
 
48
34
  ### Basic Form
49
35
  ```tsx
50
- import { Form, FormField, Input, Button, TextArea } from '@delightui/components';
36
+ import { Form, FormField, Input, Button } from '@delightui/components';
51
37
 
52
38
  function BasicFormExample() {
53
- const handleSubmit = (values: { name: string; email: string; message: string }) => {
39
+ const handleSubmit = (values, setError) => {
54
40
  console.log('Form submitted:', values);
55
- alert('Form submitted successfully!');
56
- };
57
-
58
- const validateEmail = (value: string) => {
59
- if (!value.includes('@')) {
60
- return 'Please enter a valid email address';
41
+
42
+ // Example validation
43
+ if (!values.email || !values.email.includes('@')) {
44
+ setError('email', 'Please enter a valid email address');
45
+ return;
61
46
  }
62
- return undefined;
47
+
48
+ // Process form submission
49
+ alert('Form submitted successfully!');
63
50
  };
64
51
 
65
52
  return (
66
- <Form
67
- defaultValues={{ name: '', email: '', message: '' }}
68
- onSubmit={handleSubmit}
69
- >
53
+ <Form onSubmit={handleSubmit}>
70
54
  <FormField name="name" label="Full Name" required>
71
55
  <Input placeholder="Enter your name" />
72
56
  </FormField>
73
-
74
- <FormField name="email" label="Email Address" required validate={validateEmail}>
57
+
58
+ <FormField name="email" label="Email Address" required>
75
59
  <Input inputType="Email" placeholder="Enter your email" />
76
60
  </FormField>
77
-
61
+
78
62
  <FormField name="message" label="Message">
79
63
  <TextArea placeholder="Your message..." rows={4} />
80
64
  </FormField>
81
-
65
+
82
66
  <Button actionType="submit">Submit Form</Button>
83
67
  </Form>
84
68
  );
@@ -87,127 +71,115 @@ function BasicFormExample() {
87
71
 
88
72
  ### Form with Validation
89
73
  ```tsx
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';
98
- }
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
74
  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';
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;
122
82
  }
123
- return undefined;
124
- };
125
-
126
- const validateEmail = (value: string) => {
127
- if (!/\S+@\S+\.\S+/.test(value)) {
128
- return 'Please enter a valid email';
83
+
84
+ if (values.password !== values.confirmPassword) {
85
+ setError('confirmPassword', 'Passwords do not match');
86
+ hasErrors = true;
87
+ }
88
+
89
+ if (!hasErrors) {
90
+ console.log('Creating account:', values);
129
91
  }
130
- return undefined;
131
92
  };
132
93
 
133
- const validatePassword = (value: string) => {
134
- if (value.length < 8) {
135
- return 'Password must be at least 8 characters';
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;
101
+ }
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;
136
107
  }
137
- return undefined;
108
+
109
+ return isValid;
138
110
  };
139
111
 
140
112
  return (
141
- <Form
142
- defaultValues={{ username: '', email: '', password: '', confirmPassword: '' }}
113
+ <Form
143
114
  onSubmit={handleSubmit}
144
- mode="onChange" // Validate on every change
115
+ formValidator={formValidator}
116
+ validateOnChange={true}
145
117
  >
146
- <FormField name="username" label="Username" required validate={validateUsername}>
118
+ <FormField name="username" label="Username" required>
147
119
  <Input placeholder="Choose a username" />
148
120
  </FormField>
149
-
150
- <FormField name="email" label="Email" required validate={validateEmail}>
121
+
122
+ <FormField name="email" label="Email" required>
151
123
  <Input inputType="Email" placeholder="your@email.com" />
152
124
  </FormField>
153
-
154
- <FormField name="password" label="Password" required validate={validatePassword}>
125
+
126
+ <FormField name="password" label="Password" required>
155
127
  <Input inputType="Password" placeholder="Create password" />
156
128
  </FormField>
157
-
158
- <ConfirmPasswordField />
159
-
129
+
130
+ <FormField name="confirmPassword" label="Confirm Password" required>
131
+ <Input inputType="Password" placeholder="Confirm password" />
132
+ </FormField>
133
+
160
134
  <div className="form-actions">
161
135
  <Button actionType="submit">Create Account</Button>
136
+ <Button type="Outlined" actionType="reset">Clear Form</Button>
162
137
  </div>
163
138
  </Form>
164
139
  );
165
140
  }
166
141
  ```
167
142
 
168
- ### Form with Initial Values
143
+ ### Form with Initial State
169
144
  ```tsx
170
145
  function InitialStateFormExample() {
171
- const handleSubmit = (values: {
172
- firstName: string;
173
- lastName: string;
174
- email: string;
175
- role: string;
176
- notifications: boolean;
177
- }) => {
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) => {
178
155
  console.log('Updated profile:', values);
179
156
  };
180
157
 
181
- const validateEmail = (value: string) => {
182
- if (!value.includes('@')) {
183
- return 'Invalid email format';
158
+ const handleStateChange = (state, setError) => {
159
+ // Validate on state change
160
+ if (state.email && !state.email.includes('@')) {
161
+ setError('email', 'Invalid email format');
184
162
  }
185
- return undefined;
186
163
  };
187
164
 
188
165
  return (
189
- <Form
190
- defaultValues={{
191
- firstName: 'John',
192
- lastName: 'Doe',
193
- email: 'john.doe@example.com',
194
- role: 'developer',
195
- notifications: true
196
- }}
166
+ <Form
167
+ formState={initialFormState}
197
168
  onSubmit={handleSubmit}
169
+ onFormStateChange={handleStateChange}
198
170
  >
199
171
  <FormField name="firstName" label="First Name" required>
200
172
  <Input />
201
173
  </FormField>
202
-
174
+
203
175
  <FormField name="lastName" label="Last Name" required>
204
176
  <Input />
205
177
  </FormField>
206
-
207
- <FormField name="email" label="Email" required validate={validateEmail}>
178
+
179
+ <FormField name="email" label="Email" required>
208
180
  <Input inputType="Email" />
209
181
  </FormField>
210
-
182
+
211
183
  <FormField name="role" label="Role">
212
184
  <Select>
213
185
  <Option value="developer">Developer</Option>
@@ -215,11 +187,11 @@ function InitialStateFormExample() {
215
187
  <Option value="manager">Manager</Option>
216
188
  </Select>
217
189
  </FormField>
218
-
190
+
219
191
  <FormField name="notifications" label="Email Notifications">
220
192
  <Checkbox>Send me email notifications</Checkbox>
221
193
  </FormField>
222
-
194
+
223
195
  <Button actionType="submit">Update Profile</Button>
224
196
  </Form>
225
197
  );
@@ -564,24 +536,24 @@ function AsyncFormExample() {
564
536
 
565
537
  ### Autosave Form
566
538
  ```tsx
567
- import { useState } from 'react';
568
-
569
539
  function AutosaveFormExample() {
570
540
  const [saveCount, setSaveCount] = useState(0);
571
- const [lastSaved, setLastSaved] = useState<string | null>(null);
541
+ const [lastSaved, setLastSaved] = useState(null);
572
542
 
573
- const handleAutosave = async (values: { title: string; content: string }) => {
543
+ const handleSubmit = async (values, setError) => {
574
544
  try {
575
545
  // Simulate API call to save draft
576
- await fetch('/api/drafts', {
546
+ const response = await fetch('/api/drafts', {
577
547
  method: 'POST',
578
548
  headers: { 'Content-Type': 'application/json' },
579
549
  body: JSON.stringify(values)
580
550
  });
581
-
582
- setSaveCount(prev => prev + 1);
583
- setLastSaved(new Date().toLocaleTimeString());
584
- console.log('Draft auto-saved:', values);
551
+
552
+ if (response.ok) {
553
+ setSaveCount(prev => prev + 1);
554
+ setLastSaved(new Date().toLocaleTimeString());
555
+ console.log('Draft auto-saved:', values);
556
+ }
585
557
  } catch (error) {
586
558
  console.error('Failed to save draft:', error);
587
559
  }
@@ -589,23 +561,29 @@ function AutosaveFormExample() {
589
561
 
590
562
  return (
591
563
  <div>
592
- <Form
593
- defaultValues={{ title: '', content: '' }}
594
- onSubmit={handleAutosave}
564
+ <Form
565
+ onSubmit={handleSubmit}
595
566
  autosave={true}
596
567
  autosaveDelayMs={2000} // Wait 2 seconds after user stops typing
597
568
  >
598
569
  <FormField name="title" label="Title">
599
570
  <Input placeholder="Enter title" />
600
571
  </FormField>
601
-
572
+
602
573
  <FormField name="content" label="Content">
603
- <TextArea
604
- placeholder="Start writing..."
605
- rows={10}
574
+ <TextArea
575
+ placeholder="Start writing..."
576
+ rows={10}
606
577
  />
607
578
  </FormField>
608
-
579
+
580
+ <FormField name="tags" label="Tags">
581
+ <ChipInput
582
+ placeholder="Add tags"
583
+ options={['react', 'typescript', 'design', 'tutorial']}
584
+ />
585
+ </FormField>
586
+
609
587
  {lastSaved && (
610
588
  <Text type="BodySmall">
611
589
  Auto-saved {saveCount} times. Last saved at {lastSaved}
@@ -617,740 +595,66 @@ function AutosaveFormExample() {
617
595
  }
618
596
  ```
619
597
 
620
- ### Async Validation
598
+ ### Autosave with Manual Submit
621
599
  ```tsx
622
- function AsyncValidationExample() {
623
- const validateUsername = async (value: string) => {
624
- if (!value) return 'Username is required';
600
+ function AutosaveWithManualSubmitExample() {
601
+ const [isDraft, setIsDraft] = useState(true);
625
602
 
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';
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));
632
608
  }
633
- return undefined;
634
609
  };
635
610
 
636
- const handleSubmit = (values: { username: string; email: string }) => {
637
- console.log('Account created:', values);
611
+ const handlePublish = () => {
612
+ setIsDraft(false);
613
+ // Form will now submit normally
638
614
  };
639
615
 
640
616
  return (
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
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 || ''
870
624
  }}
871
- onSubmit={handleSubmit}
872
- >
873
- <FormField name="name" label="Name" required>
874
- <Input placeholder="Enter your name" />
875
- </FormField>
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>
951
- </FormField>
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
625
  >
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} />
626
+ <FormField name="title" label="Article Title" required>
627
+ <Input placeholder="Enter title" />
1039
628
  </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>
1087
- <Select>
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>
1092
- </Select>
629
+
630
+ <FormField name="content" label="Article Content" required>
631
+ <TextArea placeholder="Write your article..." rows={15} />
1093
632
  </FormField>
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>
633
+
634
+ <FormField name="category" label="Category">
1118
635
  <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
- )}
636
+ <Option value="tech">Technology</Option>
637
+ <Option value="design">Design</Option>
638
+ <Option value="business">Business</Option>
1134
639
  </Select>
1135
640
  </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}
641
+
642
+ <div className="form-actions">
643
+ <Button type="Outlined">Save as Draft</Button>
644
+ <Button
645
+ actionType="submit"
646
+ onClick={handlePublish}
1249
647
  >
1250
- Check Availability
648
+ Publish Article
1251
649
  </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
- )}
1263
650
  </div>
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>
651
+
652
+ {isDraft && (
653
+ <Text type="BodySmall" style={{ marginTop: '8px' }}>
654
+ Your changes are automatically saved as you type
655
+ </Text>
656
+ )}
1295
657
  </Form>
1296
658
  );
1297
659
  }
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/)
660
+ ```