All files / app/src/shared/ui/textarea textarea.tsx

82.6% Statements 76/92
44.44% Branches 4/9
20% Functions 1/5
82.6% Lines 76/92

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 1161x                                 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x         1x 1x       1x 1x 1x 1x 1x 1x 1x   1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x 1x 1x 2x 2x 2x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x       1x       1x       1x 1x 1x   1x 1x  
import { forwardRef, InputHTMLAttributes, useEffect, useRef, useState } from 'react'
import cn from 'classnames'
import { Nullable } from '@/shared/@types'
 
export interface TextareaProps extends InputHTMLAttributes<HTMLTextAreaElement> {
  name: string
  label?: string
  defaultValue?: string
  error?: boolean
  errorMessage?: string
  className?: string
  labelClassName?: string
  textareaClassName?: string
  resizable?: boolean
  autosize?: boolean
}
 
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
  (
    {
      name,
      label,
      className,
      labelClassName,
      textareaClassName,
      resizable,
      autosize = false,
      error,
      errorMessage,
      ...rest
    },
    ref
  ) => {
    const innerRef = useRef<Nullable<HTMLTextAreaElement>>(null)
    const [isFocused, setIsFocused] = useState(false)
    const [value, setValue] = useState(rest.defaultValue)
 
    useEffect(() => {
      const textarea = innerRef.current
      const onChange = (e: Event | HTMLTextAreaElement) => {
        const element = ((e as Event).target as HTMLTextAreaElement) || e
        element.style.cssText = 'height:auto'
        element.style.cssText = 'height:' + element.scrollHeight + 'px'
      }
      const resizeObserver = new ResizeObserver(entries => onChange(entries[0].target as HTMLTextAreaElement))
      if (textarea && autosize) {
        textarea.addEventListener('input', onChange)
        resizeObserver.observe(textarea)
      }
      return () => {
        if (textarea) {
          textarea.removeEventListener('input', onChange)
          resizeObserver.unobserve(textarea)
        }
      }
    }, [autosize])
 
    const isFilled = value || rest.value
    const isActive = isFocused || isFilled
 
    return (
      <div className={cn('relative flex flex-col w-full transition-[margin,colors] mt-medium', className)}>
        <label
          htmlFor={name}
          className={cn(
            'absolute left-0 top-0 pointer-events-none transition-transform',
            {
              'text-text-secondary cursor-text': !rest.disabled,
              'text-background-primary cursor-not-allowed': rest.disabled,
              '-translate-y-full -top-1': isActive,
              'translate-x-5 translate-y-3': !isActive,
            },
            labelClassName
          )}
        >
          <h3 className='font-normal'>{label}</h3>
        </label>
        <textarea
          {...rest}
          ref={instance => {
            innerRef.current = instance
            typeof ref === 'function' && ref(instance)
          }}
          id={name}
          name={name}
          className={cn(
            `w-full bg-white disabled:bg-background-secondary text-black disabled:text-background-primary
             border-2 hover:border-main focus:border-main disabled:border-border disabled:cursor-not-allowed
             rounded-base px-[18px] py-2 outline-none transition-colors text-h4`,
            {
              'border-border': !(isFilled && !isFocused),
              'border-background-primary bg-background-secondary': isFilled && !isFocused,
              'border-red': error,
              'resize-none': !resizable,
            },
            textareaClassName
          )}
          onFocus={e => {
            setIsFocused(true)
            rest.onFocus?.(e)
          }}
          onBlur={e => {
            setIsFocused(false)
            rest?.onBlur?.(e)
          }}
          onChange={e => {
            rest?.onChange?.(e)
            setValue(e.target.value)
          }}
        />
        {error && <h4 className='mt-1 text-red'>{errorMessage}</h4>}
      </div>
    )
  }
)