| import React from 'react'; |
| import { Search } from 'lucide-react'; |
| import { cn } from '~/utils'; |
|
|
| const AnimatedSearchInput = ({ |
| value, |
| onChange, |
| isSearching: searching, |
| placeholder, |
| }: { |
| value?: string; |
| onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; |
| isSearching?: boolean; |
| placeholder: string; |
| }) => { |
| const isSearching = searching === true; |
| const hasValue = value != null && value.length > 0; |
|
|
| return ( |
| <div className="relative w-full"> |
| <div className="relative rounded-lg transition-all duration-500 ease-in-out"> |
| <div className="relative"> |
| {/* Icon on the left */} |
| <div className="absolute left-3 top-1/2 z-50 -translate-y-1/2"> |
| <Search |
| className={cn( |
| `h-4 w-4 transition-all duration-500 ease-in-out`, |
| isSearching && hasValue ? 'text-blue-400' : 'text-gray-400', |
| )} |
| /> |
| </div> |
| |
| {/* Input field */} |
| <input |
| type="text" |
| value={value} |
| onChange={onChange} |
| placeholder={placeholder} |
| className={`peer relative z-20 w-full rounded-lg bg-surface-secondary px-10 py-2 outline-none backdrop-blur-sm transition-all duration-500 ease-in-out placeholder:text-gray-400 focus:ring-ring`} |
| /> |
| |
| {/* Gradient overlay */} |
| <div |
| className={`pointer-events-none absolute inset-0 z-20 rounded-lg bg-gradient-to-r from-blue-500/20 via-purple-500/20 to-blue-500/20 transition-all duration-500 ease-in-out ${isSearching && hasValue ? 'opacity-100 blur-sm' : 'opacity-0 blur-none'} `} |
| /> |
| |
| {/* Animated loading indicator */} |
| <div |
| className={`absolute right-3 top-1/2 z-20 -translate-y-1/2 transition-all duration-500 ease-in-out ${isSearching && hasValue ? 'scale-100 opacity-100' : 'scale-0 opacity-0'} `} |
| > |
| <div className="relative h-2 w-2"> |
| <div className="absolute inset-0 animate-ping rounded-full bg-blue-500/60" /> |
| <div className="absolute inset-0 rounded-full bg-blue-500" /> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| {/* Outer glow effect */} |
| <div |
| className={`absolute -inset-8 -z-10 transition-all duration-700 ease-in-out ${isSearching && hasValue ? 'scale-105 opacity-100' : 'scale-100 opacity-0'} `} |
| > |
| <div className="absolute inset-0"> |
| <div |
| className={`bg-gradient-radial absolute inset-0 from-blue-500/10 to-transparent transition-opacity duration-700 ease-in-out ${isSearching && hasValue ? 'animate-pulse-slow opacity-100' : 'opacity-0'} `} |
| /> |
| <div |
| className={`absolute inset-0 bg-gradient-to-r from-purple-500/5 via-blue-500/5 to-purple-500/5 blur-xl transition-all duration-700 ease-in-out ${isSearching && hasValue ? 'animate-gradient-x opacity-100' : 'opacity-0'} `} |
| /> |
| </div> |
| </div> |
| <div |
| className={`absolute inset-0 -z-20 scale-100 bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-blue-500/10 opacity-0 blur-xl transition-all duration-500 ease-in-out peer-focus:scale-105 peer-focus:opacity-100`} |
| /> |
| </div> |
| ); |
| }; |
|
|
| export default AnimatedSearchInput; |
|
|