ClosableTextInput.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import React, {
  2. FC, memo, useEffect, useRef, useState,
  3. } from 'react';
  4. import { useTranslation } from 'react-i18next';
  5. export const AlertType = {
  6. WARNING: 'warning',
  7. ERROR: 'error',
  8. } as const;
  9. export type AlertType = typeof AlertType[keyof typeof AlertType];
  10. export type AlertInfo = {
  11. type?: AlertType
  12. message?: string
  13. }
  14. type ClosableTextInputProps = {
  15. isShown: boolean
  16. placeholder?: string
  17. inputValidator?(text: string): AlertInfo | Promise<AlertInfo> | null
  18. onPressEnter?(): void
  19. onClickOutside?(): void
  20. }
  21. const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextInputProps) => {
  22. const { t } = useTranslation();
  23. const inputRef = useRef<HTMLInputElement>(null);
  24. const [currentAlertInfo, setAlertInfo] = useState<AlertInfo | null>(null);
  25. const onChangeHandler = async(e) => {
  26. if (props.inputValidator == null) { return }
  27. const alertInfo = await props.inputValidator(e.target.value);
  28. setAlertInfo(alertInfo);
  29. };
  30. const onPressEnter = () => {
  31. if (props.onPressEnter == null) {
  32. return;
  33. }
  34. props.onPressEnter();
  35. };
  36. const onKeyDownHandler = (e) => {
  37. switch (e.key) {
  38. case 'Enter':
  39. onPressEnter();
  40. break;
  41. default:
  42. break;
  43. }
  44. };
  45. /*
  46. * Hide when click outside the ref
  47. */
  48. const onBlurHandler = () => {
  49. if (props.onClickOutside == null) {
  50. return;
  51. }
  52. props.onClickOutside();
  53. };
  54. // didMount
  55. useEffect(() => {
  56. // autoFocus
  57. if (inputRef?.current == null) {
  58. return;
  59. }
  60. inputRef.current.focus();
  61. });
  62. const AlertInfo = () => {
  63. if (currentAlertInfo == null) {
  64. return <></>;
  65. }
  66. const alertType = currentAlertInfo.type != null ? currentAlertInfo.type : AlertType.ERROR;
  67. const alertMessage = currentAlertInfo.message != null ? currentAlertInfo.message : 'Invalid value';
  68. const alertTextStyle = alertType === AlertType.ERROR ? 'text-danger' : 'text-warning';
  69. const translation = alertType === AlertType.ERROR ? 'Error' : 'Warning';
  70. return (
  71. <p className={`${alertTextStyle} text-center mt-1`}>{t(translation)}: {alertMessage}</p>
  72. );
  73. };
  74. return (
  75. <div className={props.isShown ? 'd-block' : 'd-none'}>
  76. <input
  77. ref={inputRef}
  78. type="text"
  79. className="form-control"
  80. placeholder={props.placeholder}
  81. name="input"
  82. onChange={onChangeHandler}
  83. onKeyDown={onKeyDownHandler}
  84. onBlur={onBlurHandler}
  85. autoFocus={false}
  86. />
  87. <AlertInfo />
  88. </div>
  89. );
  90. });
  91. export default ClosableTextInput;