Refactor useAuth hook, settings components, and docs for formatting and readability improvements

- Consolidated multi-line arguments into single lines where appropriate in `useAuth`.
- Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`).
- Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`).
- Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
This commit is contained in:
2025-11-10 11:03:45 +01:00
parent 464a6140c4
commit 96df7edf88
208 changed files with 4056 additions and 4556 deletions

View File

@@ -27,6 +27,7 @@
- **shadcn/ui components** - Input, Label, Button, etc.
**Why this stack?**
- ✅ Type-safe validation (TypeScript + Zod)
- ✅ Minimal re-renders (react-hook-form)
- ✅ Accessible by default (shadcn/ui)
@@ -80,11 +81,7 @@ export function SimpleForm() {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
{...form.register('email')}
/>
<Input id="email" type="email" {...form.register('email')} />
</div>
<Button type="submit">Submit</Button>
@@ -180,6 +177,7 @@ export function LoginForm() {
```
**Key points:**
1. Define Zod schema first
2. Infer TypeScript type with `z.infer`
3. Use `zodResolver` in `useForm`
@@ -217,15 +215,9 @@ export function LoginForm() {
```tsx
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
rows={4}
{...form.register('description')}
/>
<Textarea id="description" rows={4} {...form.register('description')} />
{form.formState.errors.description && (
<p className="text-sm text-destructive">
{form.formState.errors.description.message}
</p>
<p className="text-sm text-destructive">{form.formState.errors.description.message}</p>
)}
</div>
```
@@ -237,10 +229,7 @@ export function LoginForm() {
```tsx
<div className="space-y-2">
<Label htmlFor="role">Role</Label>
<Select
value={form.watch('role')}
onValueChange={(value) => form.setValue('role', value)}
>
<Select value={form.watch('role')} onValueChange={(value) => form.setValue('role', value)}>
<SelectTrigger id="role">
<SelectValue placeholder="Select a role" />
</SelectTrigger>
@@ -251,9 +240,7 @@ export function LoginForm() {
</SelectContent>
</Select>
{form.formState.errors.role && (
<p className="text-sm text-destructive">
{form.formState.errors.role.message}
</p>
<p className="text-sm text-destructive">{form.formState.errors.role.message}</p>
)}
</div>
```
@@ -272,12 +259,12 @@ export function LoginForm() {
<Label htmlFor="terms" className="text-sm font-normal">
I accept the terms and conditions
</Label>
</div>
{form.formState.errors.acceptTerms && (
<p className="text-sm text-destructive">
{form.formState.errors.acceptTerms.message}
</p>
)}
</div>;
{
form.formState.errors.acceptTerms && (
<p className="text-sm text-destructive">{form.formState.errors.acceptTerms.message}</p>
);
}
```
---
@@ -289,22 +276,16 @@ export function LoginForm() {
<Label>Notification Method</Label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<input
type="radio"
id="email"
value="email"
{...form.register('notificationMethod')}
/>
<Label htmlFor="email" className="font-normal">Email</Label>
<input type="radio" id="email" value="email" {...form.register('notificationMethod')} />
<Label htmlFor="email" className="font-normal">
Email
</Label>
</div>
<div className="flex items-center space-x-2">
<input
type="radio"
id="sms"
value="sms"
{...form.register('notificationMethod')}
/>
<Label htmlFor="sms" className="font-normal">SMS</Label>
<input type="radio" id="sms" value="sms" {...form.register('notificationMethod')} />
<Label htmlFor="sms" className="font-normal">
SMS
</Label>
</div>
</div>
</div>
@@ -320,65 +301,68 @@ export function LoginForm() {
import { z } from 'zod';
// Email
z.string().email('Invalid email address')
z.string().email('Invalid email address');
// Min/max length
z.string().min(8, 'Minimum 8 characters').max(100, 'Maximum 100 characters')
z.string().min(8, 'Minimum 8 characters').max(100, 'Maximum 100 characters');
// Required field
z.string().min(1, 'This field is required')
z.string().min(1, 'This field is required');
// Optional field
z.string().optional()
z.string().optional();
// Number with range
z.number().min(0).max(100)
z.number().min(0).max(100);
// Number from string input
z.coerce.number().min(0)
z.coerce.number().min(0);
// Enum
z.enum(['admin', 'user', 'guest'], {
errorMap: () => ({ message: 'Invalid role' })
})
errorMap: () => ({ message: 'Invalid role' }),
});
// URL
z.string().url('Invalid URL')
z.string().url('Invalid URL');
// Password with requirements
z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[0-9]/, 'Password must contain at least one number');
// Confirm password
z.object({
password: z.string().min(8),
confirmPassword: z.string()
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
})
path: ['confirmPassword'],
});
// Custom validation
z.string().refine((val) => !val.includes('badword'), {
message: 'Invalid input',
})
});
// Conditional fields
z.object({
role: z.enum(['admin', 'user']),
adminKey: z.string().optional(),
}).refine((data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}).refine(
(data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}
return true;
},
{
message: 'Admin key required for admin role',
path: ['adminKey'],
}
return true;
}, {
message: 'Admin key required for admin role',
path: ['adminKey'],
})
);
```
---
@@ -448,6 +432,7 @@ type UserFormData = z.infer<typeof userFormSchema>;
```
**Accessibility notes:**
- Use `aria-invalid` to indicate error state
- Use `aria-describedby` to link error message
- Error ID format: `{fieldName}-error`
@@ -470,14 +455,14 @@ const onSubmit = async (data: FormData) => {
};
// Display form-level error
{form.formState.errors.root && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
{form.formState.errors.root.message}
</AlertDescription>
</Alert>
)}
{
form.formState.errors.root && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{form.formState.errors.root.message}</AlertDescription>
</Alert>
);
}
```
---
@@ -620,32 +605,26 @@ const onSubmit = async (data: FormData) => {
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold">Personal Information</h3>
<p className="text-sm text-muted-foreground">
Basic details about you
</p>
<p className="text-sm text-muted-foreground">Basic details about you</p>
</div>
<Separator />
<div className="space-y-4">
{/* Fields */}
</div>
<div className="space-y-4">{/* Fields */}</div>
</div>
{/* Section 2 */}
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold">Account Settings</h3>
<p className="text-sm text-muted-foreground">
Configure your account preferences
</p>
<p className="text-sm text-muted-foreground">Configure your account preferences</p>
</div>
<Separator />
<div className="space-y-4">
{/* Fields */}
</div>
<div className="space-y-4">{/* Fields */}</div>
</div>
<div className="flex justify-end gap-4">
<Button type="button" variant="outline">Cancel</Button>
<Button type="button" variant="outline">
Cancel
</Button>
<Button type="submit">Save Changes</Button>
</div>
</form>
@@ -661,10 +640,14 @@ const onSubmit = async (data: FormData) => {
import { useFieldArray } from 'react-hook-form';
const schema = z.object({
items: z.array(z.object({
name: z.string().min(1),
quantity: z.coerce.number().min(1),
})).min(1, 'At least one item required'),
items: z
.array(
z.object({
name: z.string().min(1),
quantity: z.coerce.number().min(1),
})
)
.min(1, 'At least one item required'),
});
function DynamicForm() {
@@ -684,30 +667,19 @@ function DynamicForm() {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex gap-4">
<Input
{...form.register(`items.${index}.name`)}
placeholder="Item name"
/>
<Input {...form.register(`items.${index}.name`)} placeholder="Item name" />
<Input
type="number"
{...form.register(`items.${index}.quantity`)}
placeholder="Quantity"
/>
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
>
<Button type="button" variant="destructive" onClick={() => remove(index)}>
Remove
</Button>
</div>
))}
<Button
type="button"
variant="outline"
onClick={() => append({ name: '', quantity: 1 })}
>
<Button type="button" variant="outline" onClick={() => append({ name: '', quantity: 1 })}>
Add Item
</Button>
@@ -722,18 +694,23 @@ function DynamicForm() {
### Conditional Fields
```tsx
const schema = z.object({
role: z.enum(['user', 'admin']),
adminKey: z.string().optional(),
}).refine((data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}
return true;
}, {
message: 'Admin key required',
path: ['adminKey'],
});
const schema = z
.object({
role: z.enum(['user', 'admin']),
adminKey: z.string().optional(),
})
.refine(
(data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}
return true;
},
{
message: 'Admin key required',
path: ['adminKey'],
}
);
function ConditionalForm() {
const form = useForm({ resolver: zodResolver(schema) });
@@ -741,23 +718,17 @@ function ConditionalForm() {
return (
<form className="space-y-4">
<Select
value={role}
onValueChange={(val) => form.setValue('role', val as any)}
>
<SelectTrigger><SelectValue /></SelectTrigger>
<Select value={role} onValueChange={(val) => form.setValue('role', val as any)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="user">User</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
</SelectContent>
</Select>
{role === 'admin' && (
<Input
{...form.register('adminKey')}
placeholder="Admin Key"
/>
)}
{role === 'admin' && <Input {...form.register('adminKey')} placeholder="Admin Key" />}
</form>
);
}
@@ -774,14 +745,10 @@ const schema = z.object({
}),
});
<input
type="file"
{...form.register('file')}
accept="image/*"
/>
<input type="file" {...form.register('file')} accept="image/*" />;
const onSubmit = (data: FormData) => {
const file = data.file[0]; // FileList -> File
const file = data.file[0]; // FileList -> File
const formData = new FormData();
formData.append('file', file);
// Upload formData
@@ -795,6 +762,7 @@ const onSubmit = (data: FormData) => {
Before shipping a form, verify:
### Functionality
- [ ] All fields register correctly
- [ ] Validation works (test invalid inputs)
- [ ] Submit handler fires
@@ -803,6 +771,7 @@ Before shipping a form, verify:
- [ ] Success case redirects/shows success
### Accessibility
- [ ] Labels associated with inputs (`htmlFor` + `id`)
- [ ] Error messages use `aria-describedby`
- [ ] Invalid inputs have `aria-invalid`
@@ -810,6 +779,7 @@ Before shipping a form, verify:
- [ ] Submit button disabled during submission
### UX
- [ ] Field errors appear on blur or submit
- [ ] Loading state prevents double-submit
- [ ] Success message or redirect on success
@@ -827,11 +797,13 @@ Before shipping a form, verify:
---
**Related Documentation:**
- [Components](./02-components.md) - Input, Label, Button, Select
- [Layouts](./03-layouts.md) - Form layout patterns
- [Accessibility](./07-accessibility.md) - ARIA attributes for forms
**External Resources:**
- [react-hook-form Documentation](https://react-hook-form.com)
- [Zod Documentation](https://zod.dev)