index.tsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { 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 = (
  18. {
  19. ref: outerRef,
  20. value,
  21. onChange,
  22. placeholder,
  23. className,
  24. wrapperClassName,
  25. minHeight = 36,
  26. maxHeight = 96,
  27. autoFocus,
  28. controlFocus,
  29. onKeyDown,
  30. onKeyUp,
  31. }: IProps & {
  32. ref: React.RefObject<unknown>;
  33. },
  34. ) => {
  35. // eslint-disable-next-line react-hooks/rules-of-hooks
  36. const ref = outerRef || useRef<HTMLTextAreaElement>(null)
  37. const doFocus = () => {
  38. if (ref.current) {
  39. ref.current.setSelectionRange(value.length, value.length)
  40. ref.current.focus()
  41. return true
  42. }
  43. return false
  44. }
  45. const focus = async () => {
  46. if (!doFocus()) {
  47. let hasFocus = false
  48. await sleep(100)
  49. hasFocus = doFocus()
  50. if (!hasFocus)
  51. focus()
  52. }
  53. }
  54. useEffect(() => {
  55. if (autoFocus)
  56. focus()
  57. }, [])
  58. useEffect(() => {
  59. if (controlFocus)
  60. focus()
  61. }, [controlFocus])
  62. return (
  63. (<div className={`relative ${wrapperClassName}`}>
  64. <div className={cn(className, 'invisible overflow-y-auto whitespace-pre-wrap break-all')} style={{
  65. minHeight,
  66. maxHeight,
  67. paddingRight: (value && value.trim().length > 10000) ? 140 : 130,
  68. }}>
  69. {!value ? placeholder : value.replace(/\n$/, '\n ')}
  70. </div>
  71. <textarea
  72. ref={ref}
  73. autoFocus={autoFocus}
  74. className={cn(className, 'absolute inset-0 resize-none overflow-auto')}
  75. style={{
  76. paddingRight: (value && value.trim().length > 10000) ? 140 : 130,
  77. }}
  78. placeholder={placeholder}
  79. onChange={onChange}
  80. onKeyDown={onKeyDown}
  81. onKeyUp={onKeyUp}
  82. value={value}
  83. />
  84. </div>)
  85. )
  86. }
  87. AutoHeightTextarea.displayName = 'AutoHeightTextarea'
  88. export default AutoHeightTextarea