All files / src/components/ui/FormInput index.tsx

100% Statements 139/139
72.72% Branches 8/11
100% Functions 0/0
100% Lines 139/139

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 1401x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 945x 1x 1x 1x 1x 1x  
'use client';
 
/**
 * FormInput Component
 *
 * An input component designed to work with the form validation framework.
 * Wraps the base Input component with touched state support and
 * optimized error display.
 *
 * Supports both legacy props (error, disabled) and standard props
 * (isError, isDisabled, errorMessage) for backward compatibility.
 */
 
import { forwardRef } from 'react';
import { Input } from '../Input';
import { cn } from '@/lib/core';
import type { CommonSize, StateProps, IconSlotProps } from '../types';
 
export interface FormInputProps
  extends StateProps,
    IconSlotProps,
    Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
  /** Input label */
  label: string;
  /**
   * Validation error message (standard prop)
   * Takes precedence over legacy `error` prop
   */
  errorMessage?: string;
  /**
   * @deprecated Use `errorMessage` instead
   * Validation error message (legacy prop)
   */
  error?: string | null;
  /** Whether the field has been touched/blurred */
  touched?: boolean;
  /** Helper text shown below the input */
  helperText?: string;
  /** Input size variant */
  size?: CommonSize;
  /** Additional wrapper class name */
  wrapperClassName?: string;
}
 
/**
 * FormInput Component
 *
 * A text input component that integrates with the form validation framework.
 * Only shows errors when the field has been touched.
 *
 * @example
 * ```tsx
 * // With standard props
 * <FormInput
 *   label="Email"
 *   type="email"
 *   name="email"
 *   value={email}
 *   onChange={handleChange}
 *   isError={!!errors.email}
 *   errorMessage={errors.email}
 *   required
 * />
 *
 * // With form validation framework
 * const { errors, touched, getFieldProps } = useForm({ ... });
 *
 * <FormInput
 *   label="Email"
 *   type="email"
 *   {...getFieldProps('email')}
 *   error={errors.email}
 *   touched={touched.email}
 *   required
 * />
 * ```
 */
export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(
  (
    {
      label,
      // Standard props
      errorMessage,
      isLoading,
      isDisabled,
      isError,
      // Legacy props
      error,
      touched,
      // Common props
      helperText,
      size = 'md',
      leftIcon,
      rightIcon,
      wrapperClassName,
      className,
      id,
      required,
      disabled,
      ...props
    },
    ref
  ) => {
    // Resolve error message (prefer standard prop, convert null to undefined)
    const resolvedErrorMessage = errorMessage ?? error ?? undefined;
 
    // Only show error if the field has been touched (when using touched prop)
    // or if isError is explicitly set
    const hasError = isError ?? (touched ? !!resolvedErrorMessage : false);
    const displayedError = hasError ? resolvedErrorMessage : undefined;
 
    // Resolve disabled state (combine standard and legacy)
    const resolvedDisabled = isDisabled ?? disabled;
 
    return (
      <div className={cn('form-input-wrapper', wrapperClassName)}>
        <Input
          ref={ref as React.Ref<HTMLInputElement>}
          id={id}
          label={label}
          error={displayedError}
          helperText={helperText}
          size={size}
          leftIcon={leftIcon}
          rightIcon={rightIcon}
          required={required}
          disabled={resolvedDisabled || isLoading}
          fullWidth
          className={className}
          {...props}
        />
      </div>
    );
  }
);
 
FormInput.displayName = 'FormInput';
 
export default FormInput;