RenameInput.tsx 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import type { FC, InputHTMLAttributes } from 'react';
  2. import { useState } from 'react';
  3. import { debounce } from 'throttle-debounce';
  4. import type { InputValidationResult } from '~/client/util/use-input-validator';
  5. import styles from './RenameInput.module.scss';
  6. const moduleClass = styles['rename-input'] ?? '';
  7. type RenameInputProps = {
  8. inputProps: InputHTMLAttributes<HTMLInputElement> & { ref?: (r: HTMLInputElement | null) => void };
  9. validateName?: (name: string) => InputValidationResult | null;
  10. className?: string;
  11. };
  12. export const RenameInput: FC<RenameInputProps> = ({
  13. inputProps,
  14. validateName,
  15. className,
  16. }) => {
  17. const [validationResult, setValidationResult] =
  18. useState<InputValidationResult | null>(null);
  19. const validate = debounce(300, (value: string) => {
  20. setValidationResult(validateName?.(value) ?? null);
  21. });
  22. const isInvalid = validationResult != null;
  23. return (
  24. <div className={`${moduleClass} ${className ?? ''} flex-fill`}>
  25. <input
  26. {...inputProps}
  27. onChange={(e) => {
  28. inputProps.onChange?.(e);
  29. validate(e.target.value);
  30. }}
  31. onBlur={(e) => {
  32. setValidationResult(null);
  33. inputProps.onBlur?.(e);
  34. }}
  35. type="text"
  36. className={`form-control form-control-sm ${isInvalid ? 'is-invalid' : ''}`}
  37. />
  38. {isInvalid && (
  39. <div className="invalid-feedback d-block my-1">
  40. {validationResult.message}
  41. </div>
  42. )}
  43. </div>
  44. );
  45. };