Files
beStream/src/components/layout/Navbar.tsx
2025-12-14 12:57:37 +01:00

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>
</>
);
}