/** * Input - Reusable text input with label, validation, and error display. * * Supports all standard HTML input types, optional leading/trailing * icons, helper text, and inline error messages. */ import { forwardRef } from "react"; import PropTypes from "prop-types"; const Input = forwardRef(function Input( { id, name, type = "text", label, placeholder, value, defaultValue, onChange, onBlur, disabled = false, readOnly = false, required = false, error, helperText, leadingIcon, trailingIcon, className = "", inputClassName = "", ...rest }, ref, ) { const inputId = id || name; const hasError = Boolean(error); const describedBy = []; if (hasError) { describedBy.push(`${inputId}-error`); } if (helperText && !hasError) { describedBy.push(`${inputId}-helper`); } const ringColor = hasError ? "border-danger-500 focus:ring-danger-500 focus:border-danger-500" : "border-neutral-300 focus:ring-primary-500 focus:border-primary-500"; const inputClasses = [ "block w-full rounded-lg border px-3 py-2 text-sm text-neutral-900", "placeholder:text-neutral-400", "transition-colors duration-150", "focus:outline-none focus:ring-2 focus:ring-offset-0", "disabled:bg-neutral-100 disabled:cursor-not-allowed", "read-only:bg-neutral-50", ringColor, leadingIcon ? "pl-10" : "", trailingIcon ? "pr-10" : "", inputClassName, ] .filter(Boolean) .join(" "); return (
{label && ( )}
{leadingIcon && (
{leadingIcon}
)} 0 ? describedBy.join(" ") : undefined } className={inputClasses} {...rest} /> {trailingIcon && (
{trailingIcon}
)}
{hasError && ( )} {helperText && !hasError && (

{helperText}

)}
); }); Input.propTypes = { id: PropTypes.string, name: PropTypes.string, type: PropTypes.string, label: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), onChange: PropTypes.func, onBlur: PropTypes.func, disabled: PropTypes.bool, readOnly: PropTypes.bool, required: PropTypes.bool, error: PropTypes.string, helperText: PropTypes.string, leadingIcon: PropTypes.node, trailingIcon: PropTypes.node, className: PropTypes.string, inputClassName: PropTypes.string, }; export default Input;