This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at master 6.2 kB View raw
1import React, { useContext, useState, useRef, useEffect } from 'react'; 2import { Link, useNavigate } from 'react-router-dom'; 3import { ThemeContext } from '../../contexts/ThemeContext'; 4import './Navbar.css'; 5 6// Dropdown Menu Component 7const DropdownMenu = ({ title, path, items }) => { 8 const [isOpen, setIsOpen] = useState(false); 9 const dropdownRef = useRef(null); 10 11 // Handle click outside to close the dropdown 12 useEffect(() => { 13 const handleClickOutside = (event) => { 14 if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 15 setIsOpen(false); 16 } 17 }; 18 19 // Add event listener only when dropdown is open 20 if (isOpen) { 21 document.addEventListener('mousedown', handleClickOutside); 22 } 23 24 return () => { 25 document.removeEventListener('mousedown', handleClickOutside); 26 }; 27 }, [isOpen]); 28 29 // Add event listener to close dropdown on scroll 30 useEffect(() => { 31 const handleScroll = () => { 32 if (isOpen) { 33 setIsOpen(false); 34 } 35 }; 36 37 window.addEventListener('scroll', handleScroll); 38 return () => { 39 window.removeEventListener('scroll', handleScroll); 40 }; 41 }, [isOpen]); 42 43 // Detect if we're on mobile based on window width 44 const [windowWidth, setWindowWidth] = useState(window.innerWidth); 45 const isMobile = windowWidth <= 940; 46 47 // Update window width on resize 48 useEffect(() => { 49 const handleResize = () => { 50 setWindowWidth(window.innerWidth); 51 // Close dropdown on resize to prevent positioning issues 52 if (isOpen) { 53 setIsOpen(false); 54 } 55 }; 56 57 window.addEventListener('resize', handleResize); 58 return () => { 59 window.removeEventListener('resize', handleResize); 60 }; 61 }, [isOpen]); 62 63 return ( 64 <li 65 className={`dropdown-container ${isOpen ? 'active' : ''}`} 66 ref={dropdownRef} 67 onMouseEnter={() => !isMobile && setIsOpen(true)} 68 onMouseLeave={() => !isMobile && setIsOpen(false)} 69 > 70 <Link 71 to={path} 72 className="dropdown-trigger" 73 onClick={(e) => { 74 if (isMobile) { 75 e.preventDefault(); 76 setIsOpen(!isOpen); 77 } 78 }} 79 > 80 {title} 81 </Link> 82 {isOpen && ( 83 <ul className="dropdown-menu"> 84 {items.map((item, index) => ( 85 <li key={index}> 86 <Link 87 to={item.path} 88 onClick={() => setIsOpen(false)} 89 > 90 {item.title} 91 </Link> 92 </li> 93 ))} 94 </ul> 95 )} 96 </li> 97 ); 98}; 99 100// Regular menu item component to ensure consistent styling 101const MenuItem = ({ title, path }) => { 102 return ( 103 <li> 104 <Link to={path}>{title}</Link> 105 </li> 106 ); 107}; 108 109const Navbar = () => { 110 const { isDarkMode, toggleDarkMode } = useContext(ThemeContext); 111 const navigate = useNavigate(); 112 113 // Define dropdown menus structure 114 const scoreDropdown = { 115 title: "score", 116 path: "/", 117 items: [ 118 { title: "score", path: "/" }, 119 { title: "compare", path: "/compare" }, 120 { title: "leaderboard", path: "/leaderboard" }, 121 { title: "alt text rating", path: "/alt-text" }, 122 { title: "shortcut", path: "/shortcut" } 123 ] 124 }; 125 126 const aboutDropdown = { 127 title: "about", 128 path: "/about", 129 items: [ 130 { title: "about", path: "/about" }, 131 { title: "methodology", path: "/methodology" }, 132 { title: "definitions", path: "/definitions" } 133 ] 134 }; 135 136 return ( 137 <header className="navbar"> 138 <div className="navbar-container"> 139 <div className="navbar-left"> 140 <div className="navbar-logo"> 141 <Link to="/"> 142 <span className="cred">cred</span> 143 <span className="period">.</span> 144 <span className="blue">blue</span> 145 </Link> 146 <div className="beta-badge"> 147 beta 148 </div> 149 </div> 150 <nav className="navbar-links"> 151 <ul> 152 <DropdownMenu {...scoreDropdown} /> 153 <DropdownMenu {...aboutDropdown} /> 154 <MenuItem title="resources" path="/resources" /> 155 </ul> 156 </nav> 157 </div> 158 <div className="navbar-actions"> 159 {/* Social Links */} 160 <a 161 href="https://discord.gg/95ypHb2qPE" 162 target="_blank" 163 rel="noopener noreferrer" 164 className="nav-icon-discord" 165 aria-label="Discord Profile" 166 > 167 <svg className="icon" fill="currentColor"> 168 <use href="/icons/icons-sprite.svg#icon-discord" /> 169 </svg> 170 </a> 171 <a 172 href="https://bsky.app/profile/cred.blue" 173 target="_blank" 174 rel="noopener noreferrer" 175 className="nav-icon" 176 aria-label="Bluesky Profile" 177 > 178 <svg className="icon" fill="currentColor"> 179 <use href="/icons/icons-sprite.svg#icon-bluesky" /> 180 </svg> 181 </a> 182 <a 183 href="https://github.com/dame-is/cred.blue" 184 target="_blank" 185 rel="noopener noreferrer" 186 className="nav-icon" 187 aria-label="GitHub Profile" 188 > 189 <svg className="icon" fill="currentColor"> 190 <use href="/icons/icons-sprite.svg#icon-github" /> 191 </svg> 192 </a> 193 {/* Theme Toggle */} 194 <button 195 className="theme-toggle-button" 196 onClick={toggleDarkMode} 197 aria-label="Toggle dark mode" 198 > 199 <svg className="icon" fill="currentColor"> 200 <use href={`/icons/icons-sprite.svg#icon-${isDarkMode ? 'sun' : 'moon'}`} /> 201 </svg> 202 </button> 203 <div className="navbar-support-button-container"> 204 <button 205 className="navbar-support-button" 206 type="button" 207 onClick={() => navigate(`/supporter`)} 208 > 209 become a supporter 210 </button> 211 </div> 212 </div> 213 </div> 214 </header> 215 ); 216}; 217 218export default Navbar;