2023-04-14 16:38:58 +02:00
|
|
|
import React, {
|
|
|
|
|
forwardRef,
|
|
|
|
|
InputHTMLAttributes,
|
|
|
|
|
ReactNode,
|
|
|
|
|
useEffect,
|
|
|
|
|
useRef,
|
|
|
|
|
useState,
|
|
|
|
|
} from "react";
|
|
|
|
|
import classNames from "classnames";
|
2023-05-04 15:04:47 +02:00
|
|
|
import { randomString } from ":/utils";
|
|
|
|
|
import { Field, FieldProps } from ":/components/Forms/Field";
|
2023-05-05 16:03:49 +02:00
|
|
|
import { LabelledBox } from ":/components/Forms/LabelledBox";
|
2023-02-16 14:42:42 +01:00
|
|
|
|
2023-08-29 16:21:19 +02:00
|
|
|
export type InputProps = InputHTMLAttributes<HTMLInputElement> &
|
2023-04-14 16:38:58 +02:00
|
|
|
FieldProps & {
|
|
|
|
|
label?: string;
|
|
|
|
|
icon?: ReactNode;
|
|
|
|
|
rightIcon?: ReactNode;
|
|
|
|
|
charCounter?: boolean;
|
|
|
|
|
charCounterMax?: number;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-29 16:21:19 +02:00
|
|
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
2023-04-14 16:38:58 +02:00
|
|
|
(
|
|
|
|
|
{
|
|
|
|
|
className,
|
|
|
|
|
defaultValue,
|
|
|
|
|
label,
|
|
|
|
|
id,
|
|
|
|
|
icon,
|
|
|
|
|
rightIcon,
|
|
|
|
|
charCounter,
|
|
|
|
|
charCounterMax,
|
|
|
|
|
...props
|
2023-08-29 16:21:19 +02:00
|
|
|
}: InputProps,
|
2023-07-18 15:43:56 +02:00
|
|
|
ref,
|
2023-04-14 16:38:58 +02:00
|
|
|
) => {
|
|
|
|
|
const classes = ["c__input"];
|
|
|
|
|
if (className) {
|
|
|
|
|
classes.push(className);
|
|
|
|
|
}
|
2023-07-26 16:52:22 +02:00
|
|
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
2023-04-14 16:38:58 +02:00
|
|
|
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}`
|
2023-08-30 11:40:35 +02:00
|
|
|
: props.rightText;
|
2023-04-14 16:38:58 +02:00
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
|
|
|
|
return (
|
2023-08-30 11:40:35 +02:00
|
|
|
<Field {...props} rightText={rightTextToUse}>
|
2023-04-14 16:38:58 +02:00
|
|
|
{/* 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",
|
2023-08-30 11:40:35 +02:00
|
|
|
"c__input__wrapper--" + props.state,
|
2023-04-14 16:38:58 +02:00
|
|
|
{
|
|
|
|
|
"c__input__wrapper--disabled": props.disabled,
|
2023-07-18 15:43:56 +02:00
|
|
|
},
|
2023-04-14 16:38:58 +02:00
|
|
|
)}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
inputRef.current?.focus();
|
|
|
|
|
}}
|
|
|
|
|
>
|
2023-06-12 14:26:32 +02:00
|
|
|
{!!icon && <div className="c__input__icon-left">{icon}</div>}
|
2023-05-05 16:03:49 +02:00
|
|
|
<LabelledBox
|
|
|
|
|
label={label}
|
|
|
|
|
htmlFor={idToUse.current}
|
|
|
|
|
labelAsPlaceholder={labelAsPlaceholder}
|
|
|
|
|
>
|
2023-04-14 16:38:58 +02:00
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
className={classes.join(" ")}
|
|
|
|
|
{...props}
|
|
|
|
|
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);
|
|
|
|
|
}}
|
2023-07-26 16:52:22 +02:00
|
|
|
ref={(inputTextRef) => {
|
|
|
|
|
if (ref) {
|
|
|
|
|
if (typeof ref === "function") {
|
|
|
|
|
ref(inputTextRef);
|
|
|
|
|
} else {
|
|
|
|
|
ref.current = inputTextRef;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inputRef.current = inputTextRef;
|
|
|
|
|
}}
|
2023-04-14 16:38:58 +02:00
|
|
|
/>
|
2023-05-05 16:03:49 +02:00
|
|
|
</LabelledBox>
|
2023-06-12 14:26:32 +02:00
|
|
|
{!!rightIcon && (
|
|
|
|
|
<div className="c__input__icon-right">{rightIcon}</div>
|
|
|
|
|
)}
|
2023-04-14 16:38:58 +02:00
|
|
|
</div>
|
|
|
|
|
</Field>
|
|
|
|
|
);
|
2023-07-18 15:43:56 +02:00
|
|
|
},
|
2023-04-14 16:38:58 +02:00
|
|
|
);
|