diff --git a/src/app/globals.css b/src/app/globals.css index 4db63a850..b912eb050 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -406,4 +406,24 @@ .vjs-text-track-cue { display: none !important; -} \ No newline at end of file +} + +/* CSS styles for the floating navbar */ +/* Extra shadow for floating effect */ +.floating-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + width: 100%; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); +} + +/* Mobile adjustment */ +@media (max-width: 640px) { + .floating-nav { + left: 0; + right: 0; + } +} + \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 14e7375b5..3155e5b37 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import { useSession } from 'next-auth/react'; import { usePathname, useRouter } from 'next/navigation'; -import React, { useState, useCallback, useMemo } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import Link from 'next/link'; import { ArrowLeft, Menu, Search, X } from 'lucide-react'; import { Button } from './ui/button'; @@ -13,122 +13,241 @@ import ProfileDropdown from './profile-menu/ProfileDropdown'; import { SearchBar } from './search/SearchBar'; export const Navbar = () => { + // Basic state for user session and navigation const { data: session } = useSession(); const router = useRouter(); + const pathname = usePathname(); + + // State for menu and search toggles const [isMenuOpen, setIsMenuOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false); - const pathname = usePathname(); + + // Create a reference to the navbar container element + const navbarRef = useRef(null); + + // State for controlling navbar visibility + const [showNavbar, setShowNavbar] = useState(true); + const [previousScrollPosition, setPreviousScrollPosition] = useState(0); + + // Function to handle opening/closing the menu + function toggleMenu() { + setIsMenuOpen(!isMenuOpen); + } + + // Function to handle opening/closing the search + function toggleSearch() { + setIsSearchOpen(!isSearchOpen); + } + + // This runs when the user scrolls + useEffect(() => { + function handleScroll() { + // Get current scroll position + const currentScrollPosition = window.scrollY; + + // At the top of the page + if (currentScrollPosition === 0) { + setShowNavbar(true); + // Remove floating style if navbar exists + if (navbarRef.current) { + navbarRef.current.classList.remove('floating-nav'); + } + } else if (currentScrollPosition > previousScrollPosition) { + setShowNavbar(false); + // Add floating style if navbar exists + if (navbarRef.current) { + navbarRef.current.classList.add('floating-nav'); + } + } else if (currentScrollPosition < previousScrollPosition) { + setShowNavbar(true); + // Add floating style if navbar exists + if (navbarRef.current) { + navbarRef.current.classList.add('floating-nav'); + } + } + + // Save current position for next comparison + setPreviousScrollPosition(currentScrollPosition); + } - // Memoizing the toggleMenu and toggleSearch functions - const toggleMenu = useCallback(() => setIsMenuOpen((prev) => !prev), []); - const toggleSearch = useCallback(() => setIsSearchOpen((prev) => !prev), []); + // Add scroll listener when component mounts + window.addEventListener('scroll', handleScroll); + + // Remove scroll listener when component unmounts + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [previousScrollPosition]); - // Memoizing the navItemVariants object - const navItemVariants = useMemo( - () => ({ - hidden: { opacity: 0, y: -20 }, - visible: (i: number) => ({ - opacity: 1, - y: 0, - transition: { - delay: i * 0.1, - duration: 0.5, - ease: [0.43, 0.13, 0.23, 0.96], - }, - }), + // Show or hide navbar based on scroll direction + useEffect(() => { + if (navbarRef.current) { + if (showNavbar) { + // Show navbar + navbarRef.current.style.transform = 'translateY(0)'; + navbarRef.current.style.opacity = '1'; + } else { + // Hide navbar + navbarRef.current.style.transform = 'translateY(-100%)'; + navbarRef.current.style.opacity = '0'; + } + } + }, [showNavbar]); + + // Animation settings for navbar items + const navItemAnimations = { + hidden: { opacity: 0, y: -20 }, + visible: (i: number) => ({ + opacity: 1, + y: 0, + transition: { + delay: i * 0.1, + duration: 0.5, + ease: [0.43, 0.13, 0.23, 0.96], + }, }), - [], - ); + }; return ( <> - -
- - {session?.user && pathname !== '/home' && ( - - )} - - 100xDevs Logo -

- 100xDevs -

- -
+ {/* Navbar with animations */} + + {/* Navbar content */} +
+ {/* Left side - Logo and back button */} + + {/* Back button - only shows for logged in users not on home page */} + {session?.user && pathname !== '/home' && ( + + )} + + {/* Logo and site name */} + + 100xDevs Logo +

+ 100xDevs +

+ +
- - {/* Search Bar */} - {session?.user && ( - <> -
- -
-
+ {/* Right side - Search, theme, profile */} + + {/* Search Bar - only for logged in users */} + {session?.user && ( + <> + {/* Desktop search */} +
+ +
+ {/* Mobile search button */} +
+ +
+ + )} + + {/* Theme toggle button */} + + + {/* User profile dropdown - only for logged in users */} + {session?.user && } + + {/* Login/signup for logged out users */} + {!session?.user && ( + <> + {/* Mobile menu button */} -
- - )} - - - {session?.user && } + + {/* Desktop auth buttons */} +
+ + +
+ + )} +
+
- {!session?.user && ( - <> - -
- -
- - )} - -
- - {/* Mobile menu */} - - {!session?.user && isMenuOpen && ( - - - - + - - )} - -
+ )} + + + - {/* Mobile search overlay */} + {/* Mobile search overlay - shows when search is opened on mobile */} {isSearchOpen && ( { ); -}; +}; \ No newline at end of file