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

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.

<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.

<FormInput
  type="email"
  name="email"
  label="Email"
  prefix={<EmailIcon />}
  showClear
  value={value}
  onChange={handleChange}
/>

FormTextarea

Textarea with auto-resize and character counting.

<FormTextarea
  name="message"
  label="Message"
  rows={5}
  showCharCount
  maxLength={500}
  autoResize
  value={value}
  onChange={handleChange}
/>

FormSelect

Select dropdown with search and multi-select support.

<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.

// 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.

<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.

<FormError 
  errors={errors} 
  variant="block" 
  showIcon 
/>

FormSuccess

Success message with auto-dismiss option.

<FormSuccess 
  message="Form submitted successfully!" 
  autoDismiss 
  onClose={() => setShowSuccess(false)}
/>

Hooks

useForm

Main form state management hook with validation and submission handling.

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.

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.

const { validateField, validateForm } = useFormValidation();

const errors = validateField(value, {
  required: true,
  email: true,
}, 'email');

Validation Rules

Available validation rules:

{
  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

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

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

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

// ✅ Good
<FormField name="email" type="email" label="Email" ... />

// ❌ Avoid
<div>
  <label>Email</label>
  <input type="email" ... />
</div>

2. Validate Before Submit

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

{form.touched.email && form.errors.email && (
  <FormError errors={form.errors.email} />
)}

4. Disable Submit When Invalid

<Button 
  type="submit" 
  disabled={!form.isValid || form.isSubmitting}
  loading={form.isSubmitting}
>
  Submit
</Button>

5. Reset After Success

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:

import type { 
  FormFieldProps,
  FormInputProps,
  ValidationRules,
  FormErrors 
} from '@/components/forms';

Testing

Example test setup:

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:

// 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