/** * FormField Component * * A reusable form field component that handles the common pattern of: * - Label * - Controller (react-hook-form) * - Input/Select/Textarea * - Error handling * * This component reduces code duplication across all form components. */ import React from 'react'; import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; // ============================================================================= // TYPES // ============================================================================= export interface FormFieldProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > { // Form control control: Control; name: TName; // Field configuration label?: string; placeholder?: string; type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url'; required?: boolean; // Field type fieldType?: 'input' | 'textarea' | 'select'; // Styling className?: string; labelClassName?: string; inputClassName?: string; errorClassName?: string; // Validation validation?: { required?: boolean | string; minLength?: { value: number; message: string }; maxLength?: { value: number; message: string }; pattern?: { value: RegExp; message: string }; }; // Additional props disabled?: boolean; readOnly?: boolean; autoComplete?: string; size?: 'sm' | 'md' | 'lg'; // Custom render function render?: (props: { field: any; fieldState: { error?: FieldError }; }) => React.ReactElement; } // ============================================================================= // COMPONENT // ============================================================================= export function FormField< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath >({ control, name, label, placeholder, type = 'text', required = false, fieldType = 'input', className = '', labelClassName = '', inputClassName = '', errorClassName = '', validation, disabled = false, readOnly = false, autoComplete, size = 'md', render, }: FormFieldProps) { // ============================================================================= // HELPER FUNCTIONS // ============================================================================= const getInputSize = (size: 'sm' | 'md' | 'lg') => { switch (size) { case 'sm': return 'h-8 text-sm'; case 'lg': return 'h-12 text-base'; default: return 'h-10 text-sm'; } }; const getTextareaSize = (size: 'sm' | 'md' | 'lg') => { switch (size) { case 'sm': return 'min-h-[80px] text-sm'; case 'lg': return 'min-h-[120px] text-base'; default: return 'min-h-[100px] text-sm'; } }; // ============================================================================= // RENDER FUNCTIONS // ============================================================================= const renderInput = (field: any, fieldState: { error?: FieldError }) => { const inputProps = { ...field, type, placeholder, disabled, readOnly, autoComplete, className: cn( 'w-full transition-colors', getInputSize(size), fieldState.error && 'border-destructive focus:border-destructive', inputClassName ), }; return ; }; const renderTextarea = (field: any, fieldState: { error?: FieldError }) => { const textareaProps = { ...field, placeholder, disabled, readOnly, autoComplete, className: cn( 'w-full resize-none transition-colors', getTextareaSize(size), fieldState.error && 'border-destructive focus:border-destructive', inputClassName ), }; return