ClosableTextInput.tsx 2.4 KB

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