141 lines
3.6 KiB
TypeScript

import clsx from "clsx";
import {
ChangeEvent,
FC,
FocusEvent,
HTMLAttributes,
HTMLInputTypeAttribute,
useEffect,
useState,
} from "react";
import { v4 as uuidv4 } from "uuid";
import styles from "./styles.module.css";
interface InputProps extends HTMLAttributes<HTMLInputElement> {
min?: number;
max?: number;
autoComplete?: string;
}
export interface InputFieldProps extends HTMLAttributes<HTMLInputElement> {
errorMessage?: string;
initialValue?: string | number | null;
label: string;
maxLength?: number;
placeholder?: string;
type?: HTMLInputTypeAttribute;
inputProps?: InputProps;
}
export const InputField: FC<InputFieldProps> = ({
className,
errorMessage,
initialValue = "",
inputProps,
label,
maxLength,
onBlur,
onChange,
onFocus,
placeholder,
type = "text",
...props
}) => {
// Set default error message if not provided
errorMessage ||= `The maximum length is ${maxLength} characters.`;
// Set up component state and a shared id
const [value, setValue] = useState(initialValue);
const [showError, setShowError] = useState(false);
const id = props.id ?? `input-field-${uuidv4()}`;
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const valueLength = e.target.value.length;
if (maxLength) {
// If the value length exceeds maxLength, show error
if (valueLength > maxLength) {
setShowError(true);
//return; Prevent further processing if maxLength exceeded
return;
}
// If the value length is equal to maxLength, show error
if (valueLength === maxLength) {
setShowError(true);
}
// If the value length is less than maxLength, hide error
if (valueLength < maxLength) {
setShowError(false);
}
}
// Update component state
setValue(e.target.value);
// Call the provided onChange handler if it exists
if (onChange) onChange(e);
return;
};
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
const intValue = parseInt(e.target.value);
// If entered value is below the minimum, reset to minimum
if (
!isNaN(intValue) &&
inputProps?.min !== undefined &&
intValue < inputProps.min
) {
setValue(inputProps.min);
}
// If entered value is above the maximum and no initialValue exists, reset to maximum
if (
!isNaN(intValue) &&
inputProps?.max !== undefined &&
intValue > inputProps.max
) {
setValue(inputProps.max);
}
// Call the provided onBlur handler if it exists
if (onBlur) onBlur(e);
// Reset error state on blur
setShowError(false);
};
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
// Call the provided onFocus handler if it exists
if (onFocus) onFocus(e);
// Check for maxLength error
const valueLength = e.target.value.length;
if (maxLength && valueLength > maxLength - 1) setShowError(true);
};
useEffect(() => {
// Update the value when initialValue changes
setValue(initialValue);
// Reset error state when initial value changes
setShowError(false);
}, [initialValue]);
const inputAttrs = { id, type, placeholder };
return (
<div className={clsx([styles.inputField, className])} {...props}>
<label className={styles.label} htmlFor={id}>
{label}
</label>
<input
className={styles.input}
value={value ?? ""}
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
{...inputAttrs}
{...inputProps}
/>
{showError && <span className={styles.error}>{errorMessage}</span>}
</div>
);
};