index.tsx 2.4 KB

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