Files
cunningham/packages/react/src/components/Forms/Input/index.tsx
Anthony Le Courric c7000f37d2 🐛(react) fix props not recognized on the input element
The props propagation was adding unknown props to the input element,
which was causing an error warning to be displayed in the console.
2023-09-11 16:15:13 +02:00

146 lines
3.9 KiB
TypeScript

import React, {
forwardRef,
InputHTMLAttributes,
ReactNode,
useEffect,
useRef,
useState,
} from "react";
import classNames from "classnames";
import { randomString } from ":/utils";
import { Field, FieldProps } from ":/components/Forms/Field";
import { LabelledBox } from ":/components/Forms/LabelledBox";
export type InputOnlyProps = {
label?: string;
icon?: ReactNode;
rightIcon?: ReactNode;
charCounter?: boolean;
charCounterMax?: number;
};
export type InputProps = InputHTMLAttributes<HTMLInputElement> &
FieldProps &
InputOnlyProps;
export const Input = forwardRef<HTMLInputElement, InputProps>(
(
{
className,
defaultValue,
label,
id,
icon,
rightIcon,
charCounter,
charCounterMax,
...props
}: InputProps,
ref,
) => {
const classes = ["c__input"];
if (className) {
classes.push(className);
}
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputFocus, setInputFocus] = useState(false);
const [value, setValue] = useState(defaultValue || props.value || "");
const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(!value);
const idToUse = useRef(id || randomString());
const rightTextToUse = charCounter
? `${value.toString().length}/${charCounterMax}`
: props.rightText;
const updateLabel = () => {
if (inputFocus) {
setLabelAsPlaceholder(false);
return;
}
setLabelAsPlaceholder(!value);
};
useEffect(() => {
updateLabel();
}, [inputFocus, value]);
// If the input is used as a controlled component, we need to update the local value.
useEffect(() => {
if (defaultValue !== undefined) {
return;
}
setValue(props.value || "");
}, [props.value]);
const {
compact,
fullWidth,
rightText,
state,
text,
textItems,
...inputProps
} = props;
return (
<Field {...props} rightText={rightTextToUse}>
{/* We disabled linting for this specific line because we consider that the onClick props is only used for */}
{/* mouse users, so this do not engender any issue for accessibility. */}
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
className={classNames(
"c__input__wrapper",
props.state && "c__input__wrapper--" + props.state,
{
"c__input__wrapper--disabled": props.disabled,
},
)}
onClick={() => {
inputRef.current?.focus();
}}
>
{!!icon && <div className="c__input__icon-left">{icon}</div>}
<LabelledBox
label={label}
htmlFor={idToUse.current}
labelAsPlaceholder={labelAsPlaceholder}
disabled={props.disabled}
>
<input
type="text"
className={classes.join(" ")}
{...inputProps}
id={idToUse.current}
value={value}
onFocus={(e) => {
setInputFocus(true);
props.onFocus?.(e);
}}
onBlur={(e) => {
setInputFocus(false);
props.onBlur?.(e);
}}
onChange={(e) => {
setValue(e.target.value);
props.onChange?.(e);
}}
ref={(inputTextRef) => {
if (ref) {
if (typeof ref === "function") {
ref(inputTextRef);
} else {
ref.current = inputTextRef;
}
}
inputRef.current = inputTextRef;
}}
/>
</LabelledBox>
{!!rightIcon && (
<div className="c__input__icon-right">{rightIcon}</div>
)}
</div>
</Field>
);
},
);