torch-glare 2.4.1 → 2.4.2

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 (79) hide show
  1. package/apps/lib/components/Button.tsx +1 -1
  2. package/apps/lib/components/Card.tsx +47 -20
  3. package/apps/lib/components/ColorPicker.tsx +441 -0
  4. package/apps/lib/components/ConclusionHeader.tsx +148 -0
  5. package/apps/lib/components/DatePicker.tsx +2 -0
  6. package/apps/lib/components/Drawer.tsx +66 -24
  7. package/apps/lib/components/FormBuilder/DisplayField.tsx +40 -0
  8. package/apps/lib/components/FormBuilder/context.ts +71 -0
  9. package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +65 -0
  10. package/apps/lib/components/FormBuilder/fields/ColorField.tsx +76 -0
  11. package/apps/lib/components/FormBuilder/fields/CustomField.tsx +16 -0
  12. package/apps/lib/components/FormBuilder/fields/DateField.tsx +34 -0
  13. package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +79 -0
  14. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +174 -0
  15. package/apps/lib/components/FormBuilder/fields/FileField.tsx +40 -0
  16. package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +139 -0
  17. package/apps/lib/components/FormBuilder/fields/OtpField.tsx +32 -0
  18. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +82 -0
  19. package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +37 -0
  20. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +101 -0
  21. package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +175 -0
  22. package/apps/lib/components/FormBuilder/fields/SliderField.tsx +72 -0
  23. package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +42 -0
  24. package/apps/lib/components/FormBuilder/fields/TableField.tsx +312 -0
  25. package/apps/lib/components/FormBuilder/fields/TextField.tsx +222 -0
  26. package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +49 -0
  27. package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
  28. package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
  29. package/apps/lib/components/FormBuilder/form-builder.tsx +292 -0
  30. package/apps/lib/components/FormBuilder/header.tsx +105 -0
  31. package/apps/lib/components/FormBuilder/index.ts +37 -0
  32. package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
  33. package/apps/lib/components/FormBuilder/stepper.tsx +217 -0
  34. package/apps/lib/components/FormBuilder/submit.tsx +41 -0
  35. package/apps/lib/components/FormBuilder/types.ts +264 -0
  36. package/apps/lib/components/FormBuilder/viewFormat.tsx +137 -0
  37. package/apps/lib/components/FormRenderer/FormDrawer.tsx +121 -0
  38. package/apps/lib/components/FormRenderer/form-renderer.tsx +113 -0
  39. package/apps/lib/components/FormRenderer/index.ts +9 -0
  40. package/apps/lib/components/FormRenderer/types.ts +79 -0
  41. package/apps/lib/components/FormSummary.tsx +282 -0
  42. package/apps/lib/components/ImageAttachment.tsx +36 -61
  43. package/apps/lib/components/Label.tsx +49 -42
  44. package/apps/lib/components/RadioCard.tsx +2 -0
  45. package/apps/lib/components/SearchableSelect.tsx +9 -3
  46. package/apps/lib/components/SectionBlock.tsx +16 -8
  47. package/apps/lib/components/Select.tsx +41 -120
  48. package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
  49. package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +63 -9
  50. package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
  51. package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
  52. package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
  53. package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
  54. package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
  55. package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
  56. package/apps/lib/components/TextEditor/index.ts +7 -0
  57. package/apps/lib/components/Textarea.tsx +1 -1
  58. package/apps/lib/registry.json +51 -58
  59. package/apps/lib/tsconfig.tsbuildinfo +1 -0
  60. package/apps/lib/utils/color.ts +175 -0
  61. package/docs/components/card.md +4 -2
  62. package/docs/components/chart-block-tool.md +5 -4
  63. package/docs/components/color-picker.md +101 -0
  64. package/docs/components/conclusion-header.md +80 -0
  65. package/docs/components/drawer.md +153 -102
  66. package/docs/components/form-builder.md +270 -0
  67. package/docs/components/form-renderer.md +228 -0
  68. package/docs/components/form-summary.md +123 -0
  69. package/docs/components/image-attachment.md +10 -4
  70. package/docs/components/table-dnd-wrapper.md +5 -3
  71. package/docs/components/text-editor.md +18 -0
  72. package/docs/how-to/form-and-list-recipes.md +26 -19
  73. package/docs/how-to/forms-with-form-builder.md +408 -0
  74. package/docs/how-to/guides.md +153 -179
  75. package/docs/tutorials/building-first-form.md +150 -159
  76. package/package.json +1 -1
  77. /package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +0 -0
  78. /package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +0 -0
  79. /package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +0 -0
@@ -16,9 +16,19 @@ related:
16
16
 
17
17
  Learn how to build complete, production-ready forms with TORCH Glare. This tutorial covers everything from basic inputs to validation and error handling.
18
18
 
19
+ > **Build real forms with `FormBuilder`.** In an actual app, do **not** hand-wire fields with
20
+ > `useState` + `LabelField`/`InputField` rows as this tutorial does — that boilerplate is exactly
21
+ > what [`FormBuilder`](../components/form-builder.md) removes. With `FormBuilder` each field is one
22
+ > JSX child (`<FormBuilder.Text name="…" label="…" required />`) and validation comes from a
23
+ > react-hook-form resolver. Add [`FormRenderer`](../components/form-renderer.md) for page/drawer
24
+ > chrome + an `actions` slot for the Save, and [`FormSummary`](../components/form-summary.md) for live totals.
25
+ > See the **[Forms with FormBuilder](../how-to/forms-with-form-builder.md)** guide. The manual
26
+ > approach below is kept only to show what `FormBuilder` does under the hood.
27
+
19
28
  ## What You'll Build
20
29
 
21
30
  A user registration form with:
31
+
22
32
  - Multiple input types (text, email, password)
23
33
  - Form validation
24
34
  - Error handling and display
@@ -40,29 +50,27 @@ Let's start with a basic form structure:
40
50
 
41
51
  ```tsx
42
52
  // components/RegistrationForm.tsx
43
- 'use client';
53
+ "use client";
44
54
 
45
- import { useState } from 'react';
55
+ import { useState } from "react";
46
56
  import { Button } from "@/components/Button";
47
57
 
48
58
  export default function RegistrationForm() {
49
59
  const [formData, setFormData] = useState({
50
- fullName: '',
51
- email: '',
52
- password: '',
53
- confirmPassword: '',
60
+ fullName: "",
61
+ email: "",
62
+ password: "",
63
+ confirmPassword: "",
54
64
  });
55
65
 
56
66
  const handleSubmit = (e: React.FormEvent) => {
57
67
  e.preventDefault();
58
- console.log('Form submitted:', formData);
68
+ console.log("Form submitted:", formData);
59
69
  };
60
70
 
61
71
  return (
62
72
  <form onSubmit={handleSubmit} className="max-w-md mx-auto p-6">
63
- <h2 className="typography-display-medium-bold mb-6">
64
- Create Account
65
- </h2>
73
+ <h2 className="typography-display-medium-bold mb-6">Create Account</h2>
66
74
 
67
75
  {/* We'll add fields here */}
68
76
 
@@ -83,37 +91,35 @@ export default function RegistrationForm() {
83
91
  `LabelField` combines a label and input field with built-in styling:
84
92
 
85
93
  ```tsx
86
- import { useState } from 'react';
94
+ import { useState } from "react";
87
95
  import { Button } from "@/components/Button";
88
96
  import { LabelField } from "@/components/LabelField";
89
97
 
90
98
  export default function RegistrationForm() {
91
99
  const [formData, setFormData] = useState({
92
- fullName: '',
93
- email: '',
94
- password: '',
95
- confirmPassword: '',
100
+ fullName: "",
101
+ email: "",
102
+ password: "",
103
+ confirmPassword: "",
96
104
  });
97
105
 
98
106
  const handleChange = (field: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
99
- setFormData(prev => ({
107
+ setFormData((prev) => ({
100
108
  ...prev,
101
- [field]: e.target.value
109
+ [field]: e.target.value,
102
110
  }));
103
111
  };
104
112
 
105
113
  return (
106
114
  <form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
107
- <h2 className="typography-display-medium-bold mb-6">
108
- Create Account
109
- </h2>
115
+ <h2 className="typography-display-medium-bold mb-6">Create Account</h2>
110
116
 
111
117
  <LabelField
112
118
  theme="light"
113
119
  label="Full Name"
114
120
  requiredLabel="*"
115
121
  value={formData.fullName}
116
- onChange={handleChange('fullName')}
122
+ onChange={handleChange("fullName")}
117
123
  placeholder="John Doe"
118
124
  />
119
125
 
@@ -123,7 +129,7 @@ export default function RegistrationForm() {
123
129
  requiredLabel="*"
124
130
  type="email"
125
131
  value={formData.email}
126
- onChange={handleChange('email')}
132
+ onChange={handleChange("email")}
127
133
  placeholder="you@example.com"
128
134
  />
129
135
 
@@ -133,7 +139,7 @@ export default function RegistrationForm() {
133
139
  requiredLabel="*"
134
140
  type="password"
135
141
  value={formData.password}
136
- onChange={handleChange('password')}
142
+ onChange={handleChange("password")}
137
143
  placeholder="••••••••"
138
144
  />
139
145
 
@@ -143,7 +149,7 @@ export default function RegistrationForm() {
143
149
  requiredLabel="*"
144
150
  type="password"
145
151
  value={formData.confirmPassword}
146
- onChange={handleChange('confirmPassword')}
152
+ onChange={handleChange("confirmPassword")}
147
153
  placeholder="••••••••"
148
154
  />
149
155
 
@@ -162,9 +168,9 @@ export default function RegistrationForm() {
162
168
  Let's add comprehensive validation:
163
169
 
164
170
  ```tsx
165
- 'use client';
171
+ "use client";
166
172
 
167
- import { useState, FormEvent } from 'react';
173
+ import { useState, FormEvent } from "react";
168
174
  import { Button } from "@/components/Button";
169
175
  import { FieldHint } from "@/components/FieldHint";
170
176
  import { LabelField } from "@/components/LabelField";
@@ -178,10 +184,10 @@ interface FormErrors {
178
184
 
179
185
  export default function RegistrationForm() {
180
186
  const [formData, setFormData] = useState({
181
- fullName: '',
182
- email: '',
183
- password: '',
184
- confirmPassword: '',
187
+ fullName: "",
188
+ email: "",
189
+ password: "",
190
+ confirmPassword: "",
185
191
  });
186
192
 
187
193
  const [errors, setErrors] = useState<FormErrors>({});
@@ -189,49 +195,48 @@ export default function RegistrationForm() {
189
195
 
190
196
  // Validation functions
191
197
  const validateFullName = (name: string): string | undefined => {
192
- if (!name.trim()) return 'Full name is required';
193
- if (name.trim().length < 2) return 'Name must be at least 2 characters';
198
+ if (!name.trim()) return "Full name is required";
199
+ if (name.trim().length < 2) return "Name must be at least 2 characters";
194
200
  return undefined;
195
201
  };
196
202
 
197
203
  const validateEmail = (email: string): string | undefined => {
198
- if (!email) return 'Email is required';
204
+ if (!email) return "Email is required";
199
205
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
200
- if (!emailRegex.test(email)) return 'Please enter a valid email';
206
+ if (!emailRegex.test(email)) return "Please enter a valid email";
201
207
  return undefined;
202
208
  };
203
209
 
204
210
  const validatePassword = (password: string): string | undefined => {
205
- if (!password) return 'Password is required';
206
- if (password.length < 8) return 'Password must be at least 8 characters';
207
- if (!/[A-Z]/.test(password)) return 'Password must contain an uppercase letter';
208
- if (!/[a-z]/.test(password)) return 'Password must contain a lowercase letter';
209
- if (!/[0-9]/.test(password)) return 'Password must contain a number';
211
+ if (!password) return "Password is required";
212
+ if (password.length < 8) return "Password must be at least 8 characters";
213
+ if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
214
+ if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
215
+ if (!/[0-9]/.test(password)) return "Password must contain a number";
210
216
  return undefined;
211
217
  };
212
218
 
213
219
  const validateConfirmPassword = (confirmPassword: string): string | undefined => {
214
- if (!confirmPassword) return 'Please confirm your password';
215
- if (confirmPassword !== formData.password) return 'Passwords do not match';
220
+ if (!confirmPassword) return "Please confirm your password";
221
+ if (confirmPassword !== formData.password) return "Passwords do not match";
216
222
  return undefined;
217
223
  };
218
224
 
219
225
  // Handle field changes
220
- const handleChange = (field: keyof typeof formData) => (
221
- e: React.ChangeEvent<HTMLInputElement>
222
- ) => {
223
- const value = e.target.value;
224
- setFormData(prev => ({ ...prev, [field]: value }));
225
-
226
- // Validate on change if field has been touched
227
- if (touched[field]) {
228
- validateField(field, value);
229
- }
230
- };
226
+ const handleChange =
227
+ (field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => {
228
+ const value = e.target.value;
229
+ setFormData((prev) => ({ ...prev, [field]: value }));
230
+
231
+ // Validate on change if field has been touched
232
+ if (touched[field]) {
233
+ validateField(field, value);
234
+ }
235
+ };
231
236
 
232
237
  // Handle blur events
233
238
  const handleBlur = (field: keyof typeof formData) => () => {
234
- setTouched(prev => ({ ...prev, [field]: true }));
239
+ setTouched((prev) => ({ ...prev, [field]: true }));
235
240
  validateField(field, formData[field]);
236
241
  };
237
242
 
@@ -240,21 +245,21 @@ export default function RegistrationForm() {
240
245
  let error: string | undefined;
241
246
 
242
247
  switch (field) {
243
- case 'fullName':
248
+ case "fullName":
244
249
  error = validateFullName(value);
245
250
  break;
246
- case 'email':
251
+ case "email":
247
252
  error = validateEmail(value);
248
253
  break;
249
- case 'password':
254
+ case "password":
250
255
  error = validatePassword(value);
251
256
  break;
252
- case 'confirmPassword':
257
+ case "confirmPassword":
253
258
  error = validateConfirmPassword(value);
254
259
  break;
255
260
  }
256
261
 
257
- setErrors(prev => ({ ...prev, [field]: error }));
262
+ setErrors((prev) => ({ ...prev, [field]: error }));
258
263
  };
259
264
 
260
265
  // Validate all fields
@@ -274,7 +279,7 @@ export default function RegistrationForm() {
274
279
  confirmPassword: true,
275
280
  });
276
281
 
277
- return !Object.values(newErrors).some(error => error !== undefined);
282
+ return !Object.values(newErrors).some((error) => error !== undefined);
278
283
  };
279
284
 
280
285
  // Handle form submission
@@ -282,16 +287,14 @@ export default function RegistrationForm() {
282
287
  e.preventDefault();
283
288
 
284
289
  if (validateForm()) {
285
- console.log('Form is valid:', formData);
290
+ console.log("Form is valid:", formData);
286
291
  // Submit form data
287
292
  }
288
293
  };
289
294
 
290
295
  return (
291
296
  <form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
292
- <h2 className="typography-display-medium-bold mb-6">
293
- Create Account
294
- </h2>
297
+ <h2 className="typography-display-medium-bold mb-6">Create Account</h2>
295
298
 
296
299
  <div>
297
300
  <LabelField
@@ -299,8 +302,8 @@ export default function RegistrationForm() {
299
302
  label="Full Name"
300
303
  requiredLabel="*"
301
304
  value={formData.fullName}
302
- onChange={handleChange('fullName')}
303
- onBlur={handleBlur('fullName')}
305
+ onChange={handleChange("fullName")}
306
+ onBlur={handleBlur("fullName")}
304
307
  placeholder="John Doe"
305
308
  errorMessage={touched.fullName ? errors.fullName : undefined}
306
309
  />
@@ -313,8 +316,8 @@ export default function RegistrationForm() {
313
316
  requiredLabel="*"
314
317
  type="email"
315
318
  value={formData.email}
316
- onChange={handleChange('email')}
317
- onBlur={handleBlur('email')}
319
+ onChange={handleChange("email")}
320
+ onBlur={handleBlur("email")}
318
321
  placeholder="you@example.com"
319
322
  errorMessage={touched.email ? errors.email : undefined}
320
323
  />
@@ -327,8 +330,8 @@ export default function RegistrationForm() {
327
330
  requiredLabel="*"
328
331
  type="password"
329
332
  value={formData.password}
330
- onChange={handleChange('password')}
331
- onBlur={handleBlur('password')}
333
+ onChange={handleChange("password")}
334
+ onBlur={handleBlur("password")}
332
335
  placeholder="••••••••"
333
336
  errorMessage={touched.password ? errors.password : undefined}
334
337
  />
@@ -341,8 +344,8 @@ export default function RegistrationForm() {
341
344
  requiredLabel="*"
342
345
  type="password"
343
346
  value={formData.confirmPassword}
344
- onChange={handleChange('confirmPassword')}
345
- onBlur={handleBlur('confirmPassword')}
347
+ onChange={handleChange("confirmPassword")}
348
+ onBlur={handleBlur("confirmPassword")}
346
349
  placeholder="••••••••"
347
350
  errorMessage={touched.confirmPassword ? errors.confirmPassword : undefined}
348
351
  />
@@ -376,8 +379,8 @@ import { PasswordLevel } from "@/components/PasswordLevel";
376
379
  requiredLabel="*"
377
380
  type="password"
378
381
  value={formData.password}
379
- onChange={handleChange('password')}
380
- onBlur={handleBlur('password')}
382
+ onChange={handleChange("password")}
383
+ onBlur={handleBlur("password")}
381
384
  placeholder="••••••••"
382
385
  errorMessage={touched.password ? errors.password : undefined}
383
386
  />
@@ -388,7 +391,7 @@ import { PasswordLevel } from "@/components/PasswordLevel";
388
391
  <PasswordLevel theme="light" value={formData.password} />
389
392
  </div>
390
393
  )}
391
- </div>
394
+ </div>;
392
395
  ```
393
396
 
394
397
  ---
@@ -409,8 +412,8 @@ import { LabelField } from "@/components/LabelField";
409
412
  requiredLabel="*"
410
413
  type="email"
411
414
  value={formData.email}
412
- onChange={handleChange('email')}
413
- onBlur={handleBlur('email')}
415
+ onChange={handleChange("email")}
416
+ onBlur={handleBlur("email")}
414
417
  placeholder="you@example.com"
415
418
  errorMessage={touched.email ? errors.email : undefined}
416
419
  />
@@ -423,7 +426,7 @@ import { LabelField } from "@/components/LabelField";
423
426
  className="mt-2"
424
427
  />
425
428
  )}
426
- </div>
429
+ </div>;
427
430
  ```
428
431
 
429
432
  ---
@@ -444,12 +447,12 @@ const handleSubmit = async (e: FormEvent) => {
444
447
 
445
448
  try {
446
449
  // Simulate API call
447
- await new Promise(resolve => setTimeout(resolve, 2000));
450
+ await new Promise((resolve) => setTimeout(resolve, 2000));
448
451
 
449
- console.log('Registration successful:', formData);
452
+ console.log("Registration successful:", formData);
450
453
  // Handle success (e.g., redirect, show success message)
451
454
  } catch (error) {
452
- console.error('Registration failed:', error);
455
+ console.error("Registration failed:", error);
453
456
  // Handle error
454
457
  } finally {
455
458
  setIsSubmitting(false);
@@ -457,15 +460,9 @@ const handleSubmit = async (e: FormEvent) => {
457
460
  };
458
461
 
459
462
  // Update button
460
- <Button
461
- theme="light"
462
- variant="PrimeStyle"
463
- type="submit"
464
- className="w-full"
465
- disabled={isSubmitting}
466
- >
467
- {isSubmitting ? 'Registering...' : 'Register'}
468
- </Button>
463
+ <Button theme="light" variant="PrimeStyle" type="submit" className="w-full" disabled={isSubmitting}>
464
+ {isSubmitting ? "Registering..." : "Register"}
465
+ </Button>;
469
466
  ```
470
467
 
471
468
  ---
@@ -483,7 +480,7 @@ const handleSubmit = async (e: FormEvent) => {
483
480
  e.preventDefault();
484
481
 
485
482
  if (!validateForm()) {
486
- toast.error('Please fix the errors in the form');
483
+ toast.error("Please fix the errors in the form");
487
484
  return;
488
485
  }
489
486
 
@@ -491,21 +488,21 @@ const handleSubmit = async (e: FormEvent) => {
491
488
 
492
489
  try {
493
490
  // Simulate API call
494
- await new Promise(resolve => setTimeout(resolve, 2000));
491
+ await new Promise((resolve) => setTimeout(resolve, 2000));
495
492
 
496
- toast.success('Account created successfully!');
493
+ toast.success("Account created successfully!");
497
494
 
498
495
  // Reset form
499
496
  setFormData({
500
- fullName: '',
501
- email: '',
502
- password: '',
503
- confirmPassword: '',
497
+ fullName: "",
498
+ email: "",
499
+ password: "",
500
+ confirmPassword: "",
504
501
  });
505
502
  setErrors({});
506
503
  setTouched({});
507
504
  } catch (error) {
508
- toast.error('Registration failed. Please try again.');
505
+ toast.error("Registration failed. Please try again.");
509
506
  } finally {
510
507
  setIsSubmitting(false);
511
508
  }
@@ -519,9 +516,9 @@ const handleSubmit = async (e: FormEvent) => {
519
516
  Here's the complete, production-ready form:
520
517
 
521
518
  ```tsx
522
- 'use client';
519
+ "use client";
523
520
 
524
- import { useState, FormEvent } from 'react';
521
+ import { useState, FormEvent } from "react";
525
522
  import { Button } from "@/components/Button";
526
523
  import { FieldHint } from "@/components/FieldHint";
527
524
  import { LabelField } from "@/components/LabelField";
@@ -537,10 +534,10 @@ interface FormErrors {
537
534
 
538
535
  export default function RegistrationForm() {
539
536
  const [formData, setFormData] = useState({
540
- fullName: '',
541
- email: '',
542
- password: '',
543
- confirmPassword: '',
537
+ fullName: "",
538
+ email: "",
539
+ password: "",
540
+ confirmPassword: "",
544
541
  });
545
542
 
546
543
  const [errors, setErrors] = useState<FormErrors>({});
@@ -549,46 +546,45 @@ export default function RegistrationForm() {
549
546
 
550
547
  // Validation functions
551
548
  const validateFullName = (name: string): string | undefined => {
552
- if (!name.trim()) return 'Full name is required';
553
- if (name.trim().length < 2) return 'Name must be at least 2 characters';
549
+ if (!name.trim()) return "Full name is required";
550
+ if (name.trim().length < 2) return "Name must be at least 2 characters";
554
551
  return undefined;
555
552
  };
556
553
 
557
554
  const validateEmail = (email: string): string | undefined => {
558
- if (!email) return 'Email is required';
555
+ if (!email) return "Email is required";
559
556
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
560
- if (!emailRegex.test(email)) return 'Please enter a valid email';
557
+ if (!emailRegex.test(email)) return "Please enter a valid email";
561
558
  return undefined;
562
559
  };
563
560
 
564
561
  const validatePassword = (password: string): string | undefined => {
565
- if (!password) return 'Password is required';
566
- if (password.length < 8) return 'Password must be at least 8 characters';
567
- if (!/[A-Z]/.test(password)) return 'Password must contain an uppercase letter';
568
- if (!/[a-z]/.test(password)) return 'Password must contain a lowercase letter';
569
- if (!/[0-9]/.test(password)) return 'Password must contain a number';
562
+ if (!password) return "Password is required";
563
+ if (password.length < 8) return "Password must be at least 8 characters";
564
+ if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
565
+ if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
566
+ if (!/[0-9]/.test(password)) return "Password must contain a number";
570
567
  return undefined;
571
568
  };
572
569
 
573
570
  const validateConfirmPassword = (confirmPassword: string): string | undefined => {
574
- if (!confirmPassword) return 'Please confirm your password';
575
- if (confirmPassword !== formData.password) return 'Passwords do not match';
571
+ if (!confirmPassword) return "Please confirm your password";
572
+ if (confirmPassword !== formData.password) return "Passwords do not match";
576
573
  return undefined;
577
574
  };
578
575
 
579
- const handleChange = (field: keyof typeof formData) => (
580
- e: React.ChangeEvent<HTMLInputElement>
581
- ) => {
582
- const value = e.target.value;
583
- setFormData(prev => ({ ...prev, [field]: value }));
576
+ const handleChange =
577
+ (field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => {
578
+ const value = e.target.value;
579
+ setFormData((prev) => ({ ...prev, [field]: value }));
584
580
 
585
- if (touched[field]) {
586
- validateField(field, value);
587
- }
588
- };
581
+ if (touched[field]) {
582
+ validateField(field, value);
583
+ }
584
+ };
589
585
 
590
586
  const handleBlur = (field: keyof typeof formData) => () => {
591
- setTouched(prev => ({ ...prev, [field]: true }));
587
+ setTouched((prev) => ({ ...prev, [field]: true }));
592
588
  validateField(field, formData[field]);
593
589
  };
594
590
 
@@ -596,21 +592,21 @@ export default function RegistrationForm() {
596
592
  let error: string | undefined;
597
593
 
598
594
  switch (field) {
599
- case 'fullName':
595
+ case "fullName":
600
596
  error = validateFullName(value);
601
597
  break;
602
- case 'email':
598
+ case "email":
603
599
  error = validateEmail(value);
604
600
  break;
605
- case 'password':
601
+ case "password":
606
602
  error = validatePassword(value);
607
603
  break;
608
- case 'confirmPassword':
604
+ case "confirmPassword":
609
605
  error = validateConfirmPassword(value);
610
606
  break;
611
607
  }
612
608
 
613
- setErrors(prev => ({ ...prev, [field]: error }));
609
+ setErrors((prev) => ({ ...prev, [field]: error }));
614
610
  };
615
611
 
616
612
  const validateForm = (): boolean => {
@@ -629,14 +625,14 @@ export default function RegistrationForm() {
629
625
  confirmPassword: true,
630
626
  });
631
627
 
632
- return !Object.values(newErrors).some(error => error !== undefined);
628
+ return !Object.values(newErrors).some((error) => error !== undefined);
633
629
  };
634
630
 
635
631
  const handleSubmit = async (e: FormEvent) => {
636
632
  e.preventDefault();
637
633
 
638
634
  if (!validateForm()) {
639
- toast.error('Please fix the errors in the form');
635
+ toast.error("Please fix the errors in the form");
640
636
  return;
641
637
  }
642
638
 
@@ -644,21 +640,21 @@ export default function RegistrationForm() {
644
640
 
645
641
  try {
646
642
  // Simulate API call
647
- await new Promise(resolve => setTimeout(resolve, 2000));
643
+ await new Promise((resolve) => setTimeout(resolve, 2000));
648
644
 
649
- toast.success('Account created successfully!');
645
+ toast.success("Account created successfully!");
650
646
 
651
647
  // Reset form
652
648
  setFormData({
653
- fullName: '',
654
- email: '',
655
- password: '',
656
- confirmPassword: '',
649
+ fullName: "",
650
+ email: "",
651
+ password: "",
652
+ confirmPassword: "",
657
653
  });
658
654
  setErrors({});
659
655
  setTouched({});
660
656
  } catch (error) {
661
- toast.error('Registration failed. Please try again.');
657
+ toast.error("Registration failed. Please try again.");
662
658
  } finally {
663
659
  setIsSubmitting(false);
664
660
  }
@@ -676,9 +672,7 @@ export default function RegistrationForm() {
676
672
  "
677
673
  >
678
674
  <div className="mb-6">
679
- <h2 className="typography-display-medium-bold mb-2">
680
- Create Account
681
- </h2>
675
+ <h2 className="typography-display-medium-bold mb-2">Create Account</h2>
682
676
  <p className="typography-body-medium-regular text-content-presentation-global-secondary">
683
677
  Join us and get started today
684
678
  </p>
@@ -690,8 +684,8 @@ export default function RegistrationForm() {
690
684
  label="Full Name"
691
685
  requiredLabel="*"
692
686
  value={formData.fullName}
693
- onChange={handleChange('fullName')}
694
- onBlur={handleBlur('fullName')}
687
+ onChange={handleChange("fullName")}
688
+ onBlur={handleBlur("fullName")}
695
689
  placeholder="John Doe"
696
690
  errorMessage={touched.fullName ? errors.fullName : undefined}
697
691
  />
@@ -704,8 +698,8 @@ export default function RegistrationForm() {
704
698
  requiredLabel="*"
705
699
  type="email"
706
700
  value={formData.email}
707
- onChange={handleChange('email')}
708
- onBlur={handleBlur('email')}
701
+ onChange={handleChange("email")}
702
+ onBlur={handleBlur("email")}
709
703
  placeholder="you@example.com"
710
704
  errorMessage={touched.email ? errors.email : undefined}
711
705
  />
@@ -726,8 +720,8 @@ export default function RegistrationForm() {
726
720
  requiredLabel="*"
727
721
  type="password"
728
722
  value={formData.password}
729
- onChange={handleChange('password')}
730
- onBlur={handleBlur('password')}
723
+ onChange={handleChange("password")}
724
+ onBlur={handleBlur("password")}
731
725
  placeholder="••••••••"
732
726
  errorMessage={touched.password ? errors.password : undefined}
733
727
  />
@@ -745,8 +739,8 @@ export default function RegistrationForm() {
745
739
  requiredLabel="*"
746
740
  type="password"
747
741
  value={formData.confirmPassword}
748
- onChange={handleChange('confirmPassword')}
749
- onBlur={handleBlur('confirmPassword')}
742
+ onChange={handleChange("confirmPassword")}
743
+ onBlur={handleBlur("confirmPassword")}
750
744
  placeholder="••••••••"
751
745
  errorMessage={touched.confirmPassword ? errors.confirmPassword : undefined}
752
746
  />
@@ -759,11 +753,11 @@ export default function RegistrationForm() {
759
753
  className="w-full"
760
754
  disabled={isSubmitting}
761
755
  >
762
- {isSubmitting ? 'Registering...' : 'Register'}
756
+ {isSubmitting ? "Registering..." : "Register"}
763
757
  </Button>
764
758
 
765
759
  <p className="typography-body-small-regular text-content-presentation-global-secondary text-center">
766
- Already have an account?{' '}
760
+ Already have an account?{" "}
767
761
  <a href="/login" className="text-content-presentation-action-light-primary">
768
762
  Sign in
769
763
  </a>
@@ -806,11 +800,8 @@ errorMessage={touched.email ? errors.email : undefined}
806
800
  ### 3. Disable Submit During Submission
807
801
 
808
802
  ```tsx
809
- <Button
810
- type="submit"
811
- disabled={isSubmitting || Object.keys(errors).length > 0}
812
- >
813
- {isSubmitting ? 'Submitting...' : 'Submit'}
803
+ <Button type="submit" disabled={isSubmitting || Object.keys(errors).length > 0}>
804
+ {isSubmitting ? "Submitting..." : "Submit"}
814
805
  </Button>
815
806
  ```
816
807
 
@@ -818,10 +809,10 @@ errorMessage={touched.email ? errors.email : undefined}
818
809
 
819
810
  ```tsx
820
811
  // ✓ Good - Specific and actionable
821
- 'Password must contain at least one uppercase letter'
812
+ "Password must contain at least one uppercase letter";
822
813
 
823
814
  // ✗ Bad - Vague
824
- 'Invalid password'
815
+ "Invalid password";
825
816
  ```
826
817
 
827
818
  ### 5. Reset Form After Success
@@ -831,7 +822,7 @@ const handleSuccess = () => {
831
822
  setFormData(initialState);
832
823
  setErrors({});
833
824
  setTouched({});
834
- toast.success('Success!');
825
+ toast.success("Success!");
835
826
  };
836
827
  ```
837
828