Home

Fluid Input

Updated Jan, 2025

Search

fluid-input.tsx

import { MagnifyingGlass, X } from '@phosphor-icons/react'
import { AnimatePresence, motion, MotionConfig } from 'motion/react'
import React from 'react'
import { useOnClickOutside } from 'usehooks-ts'
import { cn } from '@/src/utils'
export function FluidInput() {
const [input, setInput] = React.useState('')
const [isActive, setIsActive] = React.useState(false)
const ref = React.useRef<HTMLDivElement>(null)
useOnClickOutside(ref as React.RefObject<HTMLDivElement>, () => {
setInput('')
setIsActive(false)
})
React.useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
setInput('')
setIsActive(false)
}
}
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
}, [])
return (
<MotionConfig transition={{ duration: 0.5, type: 'spring', bounce: 0.2 }}>
<motion.div
ref={ref}
layout
tabIndex={0}
style={{ borderRadius: 16 }}
onClick={() => setIsActive(true)}
onFocus={() => setIsActive(true)}
animate={{ width: isActive ? 300 : 116 }}
className={cn('relative flex h-10 w-fit max-w-sm cursor-pointer! items-center overflow-hidden bg-neutral-800 outline-none', {
'bg-neutral-800 hover:bg-neutral-700/50': !isActive,
})}
>
<div className={'pointer-events-none absolute left-4 flex cursor-default items-center'}>
<MagnifyingGlass
size={18}
className={'text-white'}
/>
</div>
<AnimatePresence
initial={false}
mode={'popLayout'}
>
{isActive ? (
<motion.input
type={'text'}
spellCheck={false}
autoComplete={'off'}
animate={{ filter: 'blur(0px)' }}
exit={{ filter: 'blur(4px)' }}
style={{ borderRadius: 16 }}
placeholder={'Username or tag'}
value={input}
autoFocus
onChange={e => setInput(e.target.value)}
className={'h-full w-full px-12 text-white outline-none placeholder:text-white/60'}
/>
) : (
<motion.p
animate={{ filter: 'blur(0px)' }}
exit={{ filter: 'blur(4px)' }}
className={'ml-11 pr-4 text-white/60'}
>
{'Search'}
</motion.p>
)}
</AnimatePresence>
<AnimatePresence
initial={false}
mode={'popLayout'}
>
{isActive && (
<motion.div
initial={{ opacity: 0, scale: 0.25, filter: 'blur(4px)' }}
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
exit={{ opacity: 0, scale: 0.25, filter: 'blur(4px)' }}
style={{ borderRadius: 9999 }}
className={'absolute right-4 flex items-center justify-end overflow-hidden'}
>
<motion.button
animate={{ width: input.length > 0 ? 20 : 56 }}
transition={{ duration: 0.25, type: 'spring', bounce: 0.2 }}
onClick={() => {
if (input.length > 0) {
setInput('')
}
}}
className={cn('flex size-5 cursor-pointer items-center justify-center', {
'bg-neutral-200 px-2 text-xs font-semibold text-neutral-800 uppercase': input.length === 0,
'bg-neutral-600 p-1 text-neutral-50': input.length > 0,
})}
>
{input.length > 0 ? <X weight={'bold'} /> : 'Paste'}
</motion.button>
</motion.div>
)}
</AnimatePresence>
</motion.div>
</MotionConfig>
)
}