index.tsx 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { forwardRef, useEffect, useRef } from 'react'
  2. import cn from '@/utils/classnames'
  3. import { sleep } from '@/utils'
  4. type IProps = {
  5. placeholder?: string
  6. value: string
  7. onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
  8. className?: string
  9. wrapperClassName?: string
  10. minHeight?: number
  11. maxHeight?: number
  12. autoFocus?: boolean
  13. controlFocus?: number
  14. onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void
  15. onKeyUp?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void
  16. }
  17. const AutoHeightTextarea = forwardRef(
  18. (
  19. { value, onChange, placeholder, className, wrapperClassName, minHeight = 36, maxHeight = 96, autoFocus, controlFocus, onKeyDown, onKeyUp }: IProps,
  20. outerRef: any,
  21. ) => {
  22. // eslint-disable-next-line react-hooks/rules-of-hooks
  23. const ref = outerRef || useRef<HTMLTextAreaElement>(null)
  24. const doFocus = () => {
  25. if (ref.current) {
  26. ref.current.setSelectionRange(value.length, value.length)
  27. ref.current.focus()
  28. return true
  29. }
  30. return false
  31. }
  32. const focus = async () => {
  33. if (!doFocus()) {
  34. let hasFocus = false
  35. await sleep(100)
  36. hasFocus = doFocus()
  37. if (!hasFocus)
  38. focus()
  39. }
  40. }
  41. useEffect(() => {
  42. if (autoFocus)
  43. focus()
  44. }, [])
  45. useEffect(() => {
  46. if (controlFocus)
  47. focus()
  48. }, [controlFocus])
  49. return (
  50. <div className={`relative ${wrapperClassName}`}>
  51. <div className={cn(className, 'invisible whitespace-pre-wrap break-all overflow-y-auto')} style={{
  52. minHeight,
  53. maxHeight,
  54. paddingRight: (value && value.trim().length > 10000) ? 140 : 130,
  55. }}>
  56. {!value ? placeholder : value.replace(/\n$/, '\n ')}
  57. </div>
  58. <textarea
  59. ref={ref}
  60. autoFocus={autoFocus}
  61. className={cn(className, 'absolute inset-0 resize-none overflow-auto')}
  62. style={{
  63. paddingRight: (value && value.trim().length > 10000) ? 140 : 130,
  64. }}
  65. placeholder={placeholder}
  66. onChange={onChange}
  67. onKeyDown={onKeyDown}
  68. onKeyUp={onKeyUp}
  69. value={value}
  70. />
  71. </div>
  72. )
  73. },
  74. )
  75. AutoHeightTextarea.displayName = 'AutoHeightTextarea'
  76. export default AutoHeightTextarea