AutosizeInput.tsx 1.49 KiB
Newer Older
Andrei Eres's avatar
Andrei Eres committed
import React from 'react'

Andrei Eres's avatar
Andrei Eres committed
type Props = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
  onChange: (value: string) => void
}
Andrei Eres's avatar
Andrei Eres committed

Andrei Eres's avatar
Andrei Eres committed
export const AutosizeInput: React.FC<Props> = ({
Andrei Eres's avatar
Andrei Eres committed
  value,
  placeholder,
  onChange,
  ...rest
}) => {
  const [content, setContent] = React.useState<string>(value?.toString() || '')
  const [width, setWidth] = React.useState(0)
  const span = React.useRef<HTMLSpanElement>(null)
  const isContentChangedRef = React.useRef(false)
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!isContentChangedRef.current) isContentChangedRef.current = true
Andrei Eres's avatar
Andrei Eres committed
    setContent(e.currentTarget.value)
  }

  React.useEffect(() => {
    const newContent = value?.toString()
    if (newContent && newContent !== content) setContent(newContent)
  }, [value])
Andrei Eres's avatar
Andrei Eres committed

  React.useEffect(() => {
    setWidth(span.current?.offsetWidth || 0)
    if (isContentChangedRef.current) onChange(content)
Andrei Eres's avatar
Andrei Eres committed
  }, [content])

  return (
Andrei Eres's avatar
Andrei Eres committed
    <div className='inline-block relative'>
      <span
        className='opacity-0 absolute whitespace-nowrap pointer-events-none'
        ref={span}
      >
Andrei Eres's avatar
Andrei Eres committed
        {content || placeholder}
      </span>
      <input
Andrei Eres's avatar
Andrei Eres committed
        className='relative max-w-xs transition-colors bg-_bg-300 text-_crypto-400 outline-none border-b border-_border-400 hover:border-_border-500 focus:border-_border-500'
Andrei Eres's avatar
Andrei Eres committed
        value={content}
        placeholder={placeholder}
        style={{ width }}
        onChange={handleChange}
        {...rest}
      />
    </div>
  )
}