| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- import type { FC } from 'react';
- import React, {
- memo, useEffect, useRef, useState,
- } from 'react';
- import { useTranslation } from 'next-i18next';
- import AutosizeInput from 'react-input-autosize';
- import type { AlertInfo } from '~/client/util/input-validator';
- import { AlertType, inputValidator } from '~/client/util/input-validator';
- type ClosableTextInputProps = {
- value?: string
- placeholder?: string
- validationTarget?: string,
- useAutosizeInput?: boolean
- inputClassName?: string,
- onPressEnter?(inputText: string | null): void
- onPressEscape?: () => void
- onClickOutside?(): void
- onChange?(inputText: string): void
- }
- const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextInputProps) => {
- const { t } = useTranslation();
- const { validationTarget } = props;
- const inputRef = useRef<HTMLInputElement>(null);
- const [inputText, setInputText] = useState(props.value);
- const [currentAlertInfo, setAlertInfo] = useState<AlertInfo | null>(null);
- const [isAbleToShowAlert, setIsAbleToShowAlert] = useState(false);
- const [isComposing, setComposing] = useState(false);
- const createValidation = async(inputText: string) => {
- const alertInfo = await inputValidator(inputText, validationTarget);
- if (alertInfo && alertInfo.message != null && alertInfo.target != null) {
- alertInfo.message = t(alertInfo.message, { target: t(alertInfo.target) });
- }
- setAlertInfo(alertInfo);
- };
- const onChangeHandler = async(e: React.ChangeEvent<HTMLInputElement>) => {
- const inputText = e.target.value;
- createValidation(inputText);
- setInputText(inputText);
- setIsAbleToShowAlert(true);
- props.onChange?.(inputText);
- };
- const onFocusHandler = async(e: React.ChangeEvent<HTMLInputElement>) => {
- const inputText = e.target.value;
- await createValidation(inputText);
- };
- const onPressEnter = () => {
- if (props.onPressEnter != null) {
- const text = inputText != null ? inputText.trim() : null;
- if (currentAlertInfo == null) {
- props.onPressEnter(text);
- }
- }
- };
- const onKeyDownHandler = (e) => {
- switch (e.key) {
- case 'Enter':
- // Do nothing when composing
- if (isComposing) {
- return;
- }
- onPressEnter();
- break;
- case 'Escape':
- if (isComposing) {
- return;
- }
- props.onPressEscape?.();
- break;
- default:
- break;
- }
- };
- /*
- * Hide when click outside the ref
- */
- const onBlurHandler = () => {
- if (props.onClickOutside == null) {
- return;
- }
- props.onClickOutside();
- };
- // didMount
- useEffect(() => {
- // autoFocus
- if (inputRef?.current == null) {
- return;
- }
- inputRef.current.focus();
- });
- const AlertInfo = () => {
- if (currentAlertInfo == null) {
- return <></>;
- }
- const alertType = currentAlertInfo.type != null ? currentAlertInfo.type : AlertType.ERROR;
- const alertMessage = currentAlertInfo.message != null ? currentAlertInfo.message : 'Invalid value';
- const alertTextStyle = alertType === AlertType.ERROR ? 'text-danger' : 'text-warning';
- const translation = alertType === AlertType.ERROR ? 'Error' : 'Warning';
- return (
- <p className={`${alertTextStyle} text-center mt-1`}>{t(translation)}: {alertMessage}</p>
- );
- };
- const inputProps = {
- 'data-testid': 'closable-text-input',
- value: inputText || '',
- ref: inputRef,
- type: 'text',
- placeholder: props.placeholder,
- name: 'input',
- onFocus: onFocusHandler,
- onChange: onChangeHandler,
- onKeyDown: onKeyDownHandler,
- onCompositionStart: () => setComposing(true),
- onCompositionEnd: () => setComposing(false),
- onBlur: onBlurHandler,
- };
- const inputClassName = `form-control ${props.inputClassName ?? ''}`;
- return (
- <div>
- { props.useAutosizeInput
- ? <AutosizeInput inputClassName={inputClassName} {...inputProps} />
- : <input className={inputClassName} {...inputProps} />
- }
- {isAbleToShowAlert && <AlertInfo />}
- </div>
- );
- });
- ClosableTextInput.displayName = 'ClosableTextInput';
- export default ClosableTextInput;
|