Files
klz-cables.com/components/forms/README.md
2025-12-29 18:18:48 +01:00

632 lines
14 KiB
Markdown

# KLZ Forms System
A comprehensive, reusable form system for Next.js applications with full TypeScript support, accessibility features, and consistent styling.
## Features
- **Complete Form Components**: All essential form inputs with consistent styling
- **Validation System**: Built-in validation with custom rules
- **Type Safety**: Full TypeScript support
- **Accessibility**: ARIA attributes and keyboard navigation
- **Internationalization**: Ready for i18n
- **Customizable**: Flexible props for different use cases
- **Animation**: Smooth transitions and animations
- **Error Handling**: Multiple error display modes
- **Auto-resize**: Smart textarea resizing
- **Character Count**: Built-in character counting
## Installation
The form system is already included in the project. All components use the existing design system tokens.
## Components
### FormField
Wrapper component that provides consistent form field experience.
```tsx
<FormField
type="text"
name="email"
label="Email Address"
required
placeholder="your@email.com"
value={value}
error={error}
onChange={(e) => setValue(e.target.value)}
/>
```
**Supported Types**: `text`, `email`, `tel`, `textarea`, `select`, `checkbox`, `radio`, `number`, `password`, `date`, `time`, `url`
### FormInput
Base input component with icon support and clear button.
```tsx
<FormInput
type="email"
name="email"
label="Email"
prefix={<EmailIcon />}
showClear
value={value}
onChange={handleChange}
/>
```
### FormTextarea
Textarea with auto-resize and character counting.
```tsx
<FormTextarea
name="message"
label="Message"
rows={5}
showCharCount
maxLength={500}
autoResize
value={value}
onChange={handleChange}
/>
```
### FormSelect
Select dropdown with search and multi-select support.
```tsx
<FormSelect
name="country"
label="Country"
options={[
{ value: 'de', label: 'Germany' },
{ value: 'us', label: 'United States' }
]}
value={value}
onChange={handleChange}
/>
```
### FormCheckbox
Single checkbox or checkbox group with indeterminate state.
```tsx
// Single checkbox
<FormCheckbox
name="agree"
label="I agree to terms"
checked={checked}
onChange={(values) => setChecked(values.length > 0)}
/>
// Checkbox group
<FormCheckbox
name="services"
label="Services"
options={[
{ value: 'consulting', label: 'Consulting' },
{ value: 'support', label: 'Support' }
]}
value={selectedValues}
onChange={(values) => setSelectedValues(values)}
/>
```
### FormRadio
Radio button group with custom styling.
```tsx
<FormRadio
name="payment"
label="Payment Method"
options={[
{ value: 'credit-card', label: 'Credit Card' },
{ value: 'paypal', label: 'PayPal' }
]}
value={value}
onChange={(value) => setValue(value)}
/>
```
### FormError
Error message display with multiple variants.
```tsx
<FormError
errors={errors}
variant="block"
showIcon
/>
```
### FormSuccess
Success message with auto-dismiss option.
```tsx
<FormSuccess
message="Form submitted successfully!"
autoDismiss
onClose={() => setShowSuccess(false)}
/>
```
## Hooks
### useForm
Main form state management hook with validation and submission handling.
```tsx
const form = useForm({
initialValues: {
name: '',
email: '',
},
validationRules: {
name: { required: true, minLength: { value: 2, message: 'Too short' } },
email: { required: true, email: true },
},
onSubmit: async (values) => {
// Handle submission
await api.submit(values);
},
});
// In your component
<form {...form.getFormProps()}>
<input
value={form.values.name}
onChange={(e) => form.setFieldValue('name', e.target.value)}
/>
{form.errors.name && <FormError errors={form.errors.name} />}
<button type="submit" disabled={!form.isValid || form.isSubmitting}>
Submit
</button>
</form>
```
### useFormField
Hook for managing individual field state.
```tsx
const field = useFormField({
initialValue: '',
validate: (value) => value.length < 2 ? 'Too short' : null,
});
// field.value, field.error, field.touched, field.handleChange, etc.
```
### useFormValidation
Validation logic and utilities.
```tsx
const { validateField, validateForm } = useFormValidation();
const errors = validateField(value, {
required: true,
email: true,
}, 'email');
```
## Validation Rules
Available validation rules:
```typescript
{
required: boolean | string; // Required field
minLength: { value: number, message: string };
maxLength: { value: number, message: string };
pattern: { value: RegExp, message: string };
min: { value: number, message: string };
max: { value: number, message: string };
email: boolean | string;
url: boolean | string;
number: boolean | string;
custom: (value) => string | null; // Custom validation
}
```
## Examples
### Simple Contact Form
```tsx
import { useForm, FormField, Button } from '@/components/forms';
export function ContactForm() {
const form = useForm({
initialValues: { name: '', email: '', message: '' },
validationRules: {
name: { required: true, minLength: { value: 2 } },
email: { required: true, email: true },
message: { required: true },
},
onSubmit: async (values) => {
await sendEmail(values);
alert('Sent!');
form.reset();
},
});
return (
<form {...form.getFormProps()}>
<FormField
name="name"
label="Name"
required
value={form.values.name}
error={form.errors.name?.[0]}
onChange={(e) => form.setFieldValue('name', e.target.value)}
/>
<FormField
type="email"
name="email"
label="Email"
required
value={form.values.email}
error={form.errors.email?.[0]}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<FormField
type="textarea"
name="message"
label="Message"
required
rows={5}
value={form.values.message}
error={form.errors.message?.[0]}
onChange={(e) => form.setFieldValue('message', e.target.value)}
/>
<Button
type="submit"
variant="primary"
disabled={!form.isValid || form.isSubmitting}
loading={form.isSubmitting}
>
Send
</Button>
</form>
);
}
```
### Registration Form
```tsx
import { useForm, FormField, Button } from '@/components/forms';
export function RegistrationForm() {
const form = useForm({
initialValues: {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
terms: false,
},
validationRules: {
firstName: { required: true, minLength: { value: 2 } },
lastName: { required: true, minLength: { value: 2 } },
email: { required: true, email: true },
password: {
required: true,
minLength: { value: 8 },
pattern: { value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/ }
},
confirmPassword: {
required: true,
custom: (value) => value === form.values.password ? null : 'Passwords do not match'
},
terms: {
required: 'You must accept terms',
custom: (value) => value ? null : 'Required'
},
},
onSubmit: async (values) => {
await registerUser(values);
alert('Registered!');
},
});
return (
<form {...form.getFormProps()}>
<div className="grid grid-cols-2 gap-4">
<FormField
name="firstName"
label="First Name"
required
value={form.values.firstName}
error={form.errors.firstName?.[0]}
onChange={(e) => form.setFieldValue('firstName', e.target.value)}
/>
<FormField
name="lastName"
label="Last Name"
required
value={form.values.lastName}
error={form.errors.lastName?.[0]}
onChange={(e) => form.setFieldValue('lastName', e.target.value)}
/>
</div>
<FormField
type="email"
name="email"
label="Email"
required
value={form.values.email}
error={form.errors.email?.[0]}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<div className="grid grid-cols-2 gap-4">
<FormField
type="password"
name="password"
label="Password"
required
helpText="Min 8 chars with uppercase, lowercase, and number"
value={form.values.password}
error={form.errors.password?.[0]}
onChange={(e) => form.setFieldValue('password', e.target.value)}
/>
<FormField
type="password"
name="confirmPassword"
label="Confirm Password"
required
value={form.values.confirmPassword}
error={form.errors.confirmPassword?.[0]}
onChange={(e) => form.setFieldValue('confirmPassword', e.target.value)}
/>
</div>
<FormField
type="checkbox"
name="terms"
label="I accept the terms and conditions"
required
checked={form.values.terms}
error={form.errors.terms?.[0]}
onChange={(values) => form.setFieldValue('terms', values.length > 0)}
/>
<Button
type="submit"
variant="primary"
disabled={!form.isValid || form.isSubmitting}
loading={form.isSubmitting}
>
Register
</Button>
</form>
);
}
```
### Search and Filter Form
```tsx
import { useForm, FormField, Button } from '@/components/forms';
export function SearchForm() {
const form = useForm({
initialValues: {
search: '',
category: '',
status: '',
},
validationRules: {},
onSubmit: async (values) => {
await performSearch(values);
},
});
const categoryOptions = [
{ value: '', label: 'All' },
{ value: 'cables', label: 'Cables' },
{ value: 'connectors', label: 'Connectors' },
];
return (
<form {...form.getFormProps()} className="space-y-4">
<div className="grid grid-cols-3 gap-4">
<FormField
type="text"
name="search"
label="Search"
placeholder="Search..."
prefix={<SearchIcon />}
showClear
value={form.values.search}
onChange={(e) => form.setFieldValue('search', e.target.value)}
/>
<FormField
type="select"
name="category"
label="Category"
options={categoryOptions}
value={form.values.category}
onChange={(e) => form.setFieldValue('category', e.target.value)}
/>
<div className="flex gap-2 items-end">
<Button type="submit" variant="primary" size="sm">
Search
</Button>
<Button type="button" variant="outline" size="sm" onClick={form.reset}>
Reset
</Button>
</div>
</div>
</form>
);
}
```
## Best Practices
### 1. Always Use FormField for Consistency
```tsx
// ✅ Good
<FormField name="email" type="email" label="Email" ... />
// ❌ Avoid
<div>
<label>Email</label>
<input type="email" ... />
</div>
```
### 2. Validate Before Submit
```tsx
const form = useForm({
validationRules: {
email: { required: true, email: true },
},
onSubmit: async (values) => {
// Validation happens automatically
// Only called if isValid is true
},
});
```
### 3. Show Errors Only After Touch
```tsx
{form.touched.email && form.errors.email && (
<FormError errors={form.errors.email} />
)}
```
### 4. Disable Submit When Invalid
```tsx
<Button
type="submit"
disabled={!form.isValid || form.isSubmitting}
loading={form.isSubmitting}
>
Submit
</Button>
```
### 5. Reset After Success
```tsx
onSubmit: async (values) => {
await submit(values);
form.reset();
}
```
## Accessibility
All components include:
- Proper ARIA attributes
- Keyboard navigation support
- Focus management
- Screen reader support
- Required field indicators
## Styling
Components use the design system:
- Colors: `--color-primary`, `--color-danger`, `--color-success`
- Spacing: `--spacing-sm`, `--spacing-md`, etc.
- Typography: `--font-size-sm`, `--font-size-base`
- Borders: `--radius-md`
- Transitions: `--transition-fast`
## TypeScript Support
Full TypeScript support with proper interfaces:
```typescript
import type {
FormFieldProps,
FormInputProps,
ValidationRules,
FormErrors
} from '@/components/forms';
```
## Testing
Example test setup:
```tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { useForm } from '@/components/forms';
test('form validation', () => {
const TestComponent = () => {
const form = useForm({
initialValues: { email: '' },
validationRules: { email: { required: true, email: true } },
onSubmit: jest.fn(),
});
return (
<form {...form.getFormProps()}>
<input
value={form.values.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
};
render(<TestComponent />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'invalid' } });
// Validation should trigger
});
```
## Performance Tips
1. **Memoize validation rules** if they depend on external values
2. **Use useCallback** for event handlers
3. **Avoid unnecessary re-renders** by splitting large forms
4. **Lazy load** form examples for better initial load
## Migration from Legacy Forms
If migrating from old form components:
```tsx
// Old
<input
value={email}
onChange={e => setEmail(e.target.value)}
className={error ? 'error' : ''}
/>
// New
<FormField
type="email"
name="email"
value={form.values.email}
error={form.errors.email?.[0]}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
```
## Troubleshooting
### Common Issues
1. **Validation not working**: Ensure `validationRules` match `initialValues` keys
2. **Form not submitting**: Check `isValid` state and `required` fields
3. **Type errors**: Import proper types from the forms module
4. **Styling issues**: Ensure design system CSS is imported
### Getting Help
Check the examples in `FormExamples.tsx` for complete implementations.
## License
Internal KLZ Cables component system