141 lines
3.6 KiB
TypeScript
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>
|
|
);
|
|
};
|