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 116 | 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 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>
)
}
)
|