225 lines
7.8 KiB
TypeScript
225 lines
7.8 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import {
|
|
Search,
|
|
Bell,
|
|
Menu,
|
|
X,
|
|
Home,
|
|
Film,
|
|
Tv,
|
|
Music,
|
|
Calendar,
|
|
Heart,
|
|
Clock,
|
|
Download,
|
|
Settings,
|
|
} from 'lucide-react';
|
|
import { clsx } from 'clsx';
|
|
|
|
const navLinks = [
|
|
{ path: '/', label: 'Home', icon: Home },
|
|
{ path: '/browse', label: 'Movies', icon: Film },
|
|
{ path: '/tv', label: 'TV Shows', icon: Tv },
|
|
{ path: '/music', label: 'Music', icon: Music },
|
|
{ path: '/calendar', label: 'Calendar', icon: Calendar },
|
|
{ path: '/watchlist', label: 'My List', icon: Heart },
|
|
{ path: '/downloads', label: 'Downloads', icon: Download },
|
|
];
|
|
|
|
export default function Navbar() {
|
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setIsScrolled(window.scrollY > 50);
|
|
};
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
// Close mobile menu on route change
|
|
useEffect(() => {
|
|
setIsMobileMenuOpen(false);
|
|
}, [location.pathname]);
|
|
|
|
const handleSearch = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (searchQuery.trim()) {
|
|
navigate(`/search?q=${encodeURIComponent(searchQuery.trim())}`);
|
|
setIsSearchOpen(false);
|
|
setSearchQuery('');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<nav
|
|
className={clsx(
|
|
'fixed top-0 left-0 right-0 z-50 transition-all duration-300 safe-top',
|
|
isScrolled || isMobileMenuOpen
|
|
? 'bg-netflix-black/95 backdrop-blur-md shadow-lg'
|
|
: 'bg-gradient-to-b from-black/80 to-transparent'
|
|
)}
|
|
>
|
|
<div className="max-w-[1920px] mx-auto px-4 md:px-8">
|
|
<div className="flex items-center justify-between h-16 md:h-20">
|
|
{/* Logo */}
|
|
<Link to="/" className="flex items-center gap-2">
|
|
<motion.div
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
className="text-2xl md:text-3xl font-bold text-netflix-red"
|
|
>
|
|
beStream
|
|
</motion.div>
|
|
</Link>
|
|
|
|
{/* Desktop Navigation */}
|
|
<div className="hidden lg:flex items-center gap-6">
|
|
{navLinks.map((link) => (
|
|
<Link
|
|
key={link.path}
|
|
to={link.path}
|
|
className={clsx(
|
|
'text-sm font-medium transition-colors hover:text-white',
|
|
location.pathname === link.path
|
|
? 'text-white'
|
|
: 'text-gray-300'
|
|
)}
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{/* Right Section */}
|
|
<div className="flex items-center gap-2 md:gap-4">
|
|
{/* Search */}
|
|
<AnimatePresence>
|
|
{isSearchOpen ? (
|
|
<motion.form
|
|
initial={{ width: 0, opacity: 0 }}
|
|
animate={{ width: 'auto', opacity: 1 }}
|
|
exit={{ width: 0, opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
onSubmit={handleSearch}
|
|
className="flex items-center"
|
|
>
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search movies..."
|
|
className="w-40 md:w-64 bg-netflix-dark-gray/80 border border-white/20 rounded-l-md py-2 px-4 text-white placeholder-gray-400 focus:outline-none focus:border-white/40"
|
|
autoFocus
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsSearchOpen(false)}
|
|
className="bg-netflix-dark-gray/80 border border-l-0 border-white/20 rounded-r-md py-2 px-3 hover:bg-netflix-medium-gray transition-colors"
|
|
>
|
|
<X size={20} />
|
|
</button>
|
|
</motion.form>
|
|
) : (
|
|
<motion.button
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
onClick={() => setIsSearchOpen(true)}
|
|
className="p-2 hover:bg-white/10 rounded-full transition-colors"
|
|
>
|
|
<Search size={20} />
|
|
</motion.button>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* Notifications */}
|
|
<motion.button
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
className="hidden md:flex p-2 hover:bg-white/10 rounded-full transition-colors relative"
|
|
>
|
|
<Bell size={20} />
|
|
<span className="absolute top-1 right-1 w-2 h-2 bg-netflix-red rounded-full" />
|
|
</motion.button>
|
|
|
|
{/* Settings */}
|
|
<Link to="/settings">
|
|
<motion.div
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
className="hidden md:flex p-2 hover:bg-white/10 rounded-full transition-colors"
|
|
>
|
|
<Settings size={20} />
|
|
</motion.div>
|
|
</Link>
|
|
|
|
{/* Mobile Menu Button */}
|
|
<button
|
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
className="lg:hidden p-2 hover:bg-white/10 rounded-full transition-colors"
|
|
>
|
|
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Mobile Menu */}
|
|
<AnimatePresence>
|
|
{isMobileMenuOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="fixed inset-0 top-16 z-40 bg-netflix-black/98 backdrop-blur-lg lg:hidden safe-top"
|
|
>
|
|
<div className="flex flex-col p-6 gap-2">
|
|
{navLinks.map((link) => {
|
|
const Icon = link.icon;
|
|
return (
|
|
<Link
|
|
key={link.path}
|
|
to={link.path}
|
|
className={clsx(
|
|
'flex items-center gap-4 p-4 rounded-lg transition-colors',
|
|
location.pathname === link.path
|
|
? 'bg-netflix-red text-white'
|
|
: 'text-gray-300 hover:bg-white/10'
|
|
)}
|
|
>
|
|
<Icon size={24} />
|
|
<span className="text-lg font-medium">{link.label}</span>
|
|
</Link>
|
|
);
|
|
})}
|
|
<Link
|
|
to="/settings"
|
|
className={clsx(
|
|
'flex items-center gap-4 p-4 rounded-lg transition-colors',
|
|
location.pathname === '/settings'
|
|
? 'bg-netflix-red text-white'
|
|
: 'text-gray-300 hover:bg-white/10'
|
|
)}
|
|
>
|
|
<Settings size={24} />
|
|
<span className="text-lg font-medium">Settings</span>
|
|
</Link>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
}
|
|
|