Source

components/ui/FormField.jsx

import React from 'react';

/**
 * Generic form field component that renders different input types based on props.
 * It handles label, value, change handler, error display, required indicator,
 * and basic styling.
 *
 * @param {object} props - Component props.
 * @param {string} props.label - The label for the form field.
 * @param {string} props.name - The name attribute for the input element.
 * @param {string} [props.type='text'] - The type of input ('text', 'select', 'textarea', 'time', etc.).
 * @param {string|number} props.value - The current value of the input.
 * @param {function} props.onChange - The handler function for input changes.
 * @param {string} [props.error] - An error message to display below the input.
 * @param {boolean} [props.required=false] - Indicates if the field is required.
 * @param {Array<{value: string|number, label: string}>} [props.options=[]] - Options for 'select' type inputs.
 * @param {string} [props.placeholder=''] - The placeholder text for the input.
 * @param {string} [props.className=''] - Additional CSS classes for the input element.
 * @returns {React.Element} The rendered form field component.
 */
const FormField = ({ 
  label, 
  name, 
  type = 'text', 
  value, 
  onChange, 
  error, 
  required = false,
  options = [],
  placeholder = '',
  className = ''
}) => {
  const baseInputClasses = `mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 ${
    error ? 'border-red-500' : ''
  } ${className}`;

  const renderInput = () => {
    switch (type) {
      case 'select':
        return (
          <select
            name={name}
            value={value}
            onChange={onChange}
            className={baseInputClasses}
            required={required}
          >
            <option value="">{placeholder || `Select ${label}...`}</option>
            {options.map((option) => (
              <option key={option.value} value={option.value}>
                {option.label}
              </option>
            ))}
          </select>
        );
      case 'textarea':
        return (
          <textarea
            name={name}
            value={value}
            onChange={onChange}
            placeholder={placeholder}
            className={baseInputClasses}
            required={required}
            rows={4}
          />
        );
      case 'time':
        return (
          <input
            type="time"
            name={name}
            value={value}
            onChange={onChange}
            className={baseInputClasses}
            required={required}
          />
        );
      default:
        return (
          <input
            type={type}
            name={name}
            value={value}
            onChange={onChange}
            placeholder={placeholder}
            className={baseInputClasses}
            required={required}
          />
        );
    }
  };

  return (
    <div className="mb-4">
      <label className="block text-sm font-medium text-gray-700 mb-1">
        {label}
        {required && <span className="text-red-500 ml-1">*</span>}
      </label>
      {renderInput()}
      {error && (
        <p className="mt-1 text-sm text-red-600">{error}</p>
      )}
    </div>
  );
};

export default FormField;