import React, { useState, useCallback } from 'react'; import { cn } from '@/lib/utils'; import FormLabel from './FormLabel'; import FormError from './FormError'; /** * FormInput Component * Base input component with all HTML5 input types, validation states, icons, and clear button */ export interface FormInputProps extends Omit, 'prefix' | 'suffix'> { label?: string; error?: string | string[]; helpText?: string; required?: boolean; prefix?: React.ReactNode; suffix?: React.ReactNode; showClear?: boolean; iconPosition?: 'prefix' | 'suffix'; containerClassName?: string; inputClassName?: string; onClear?: () => void; } export const FormInput: React.FC = ({ label, error, helpText, required = false, prefix, suffix, showClear = false, iconPosition = 'prefix', containerClassName, inputClassName, onClear, disabled = false, value = '', onChange, ...props }) => { const [isFocused, setIsFocused] = useState(false); const hasError = !!error; const showError = hasError; const handleClear = useCallback(() => { if (onChange) { const syntheticEvent = { target: { value: '', name: props.name, type: props.type }, currentTarget: { value: '', name: props.name, type: props.type }, } as React.ChangeEvent; onChange(syntheticEvent); } if (onClear) { onClear(); } }, [onChange, onClear, props.name, props.type]); const handleFocus = () => setIsFocused(true); const handleBlur = () => setIsFocused(false); const inputId = props.id || (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined); const baseInputClasses = cn( 'w-full px-3 py-2 border rounded-md transition-all duration-200', 'bg-neutral-light text-text-primary', 'placeholder:text-text-light', 'focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary', 'disabled:opacity-60 disabled:cursor-not-allowed', { 'border-neutral-dark hover:border-neutral-dark': !hasError && !isFocused, 'border-primary ring-2 ring-primary': isFocused && !hasError, 'border-danger ring-2 ring-danger/20': hasError, 'pl-10': prefix && iconPosition === 'prefix', 'pr-10': (suffix && iconPosition === 'suffix') || (showClear && value), }, inputClassName ); const containerClasses = cn( 'flex flex-col gap-1.5', containerClassName ); const iconWrapperClasses = cn( 'absolute top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-text-secondary', { 'left-3': iconPosition === 'prefix', 'right-3': iconPosition === 'suffix', } ); const clearButtonClasses = cn( 'absolute top-1/2 -translate-y-1/2 right-2', 'p-1 rounded-md hover:bg-neutral-dark transition-colors', 'text-text-secondary hover:text-text-primary', 'focus:outline-none focus:ring-2 focus:ring-primary' ); const showPrefix = prefix && iconPosition === 'prefix'; const showSuffix = suffix && iconPosition === 'suffix'; const showClearButton = showClear && value && !disabled; return (
{label && ( {label} )}
{showPrefix && (
{prefix}
)} {showSuffix && (
{suffix}
)} {showClearButton && ( )}
{helpText && (

{helpText}

)} {showError && ( )}
); }; FormInput.displayName = 'FormInput'; export default FormInput;