Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 105 additions & 64 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,73 @@
import { NavLink, Link } from "react-router-dom";
import { useState, useContext } from "react";
import { useState, useContext, useRef, useEffect } from "react";
import { ThemeContext } from "../context/ThemeContext";
import { Moon, Sun, Menu, X, Github } from "lucide-react";
import { Moon, Sun, Menu, X } from "lucide-react";

const navItems = [
{ to: "/", label: "Home" },
{ to: "/track", label: "Tracker" },
{ to: "/contributors", label: "Contributors" },
{ to: "/login", label: "Login" },
];

const Navbar: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);

const [pillStyle, setPillStyle] = useState<{
left: number;
width: number;
opacity: number;
}>({ left: 0, width: 0, opacity: 0 });

const [scrolled,setScrolled]= useState(false);
useEffect( () => {
const handleScroll = () => {
setScrolled(window.scrollY>20);
};

window.addEventListener("scroll",handleScroll);
return () => window.removeEventListener("scroll",handleScroll);
},[]);
Comment on lines +22 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Initialize scroll-derived state on mount.

scrolled is only set after the first scroll event, so loading a page at a non-zero scroll position renders the wrong navbar style initially. Call the handler once in the effect (and make the listener passive).

Suggested fix
 useEffect( () => {
   const handleScroll = () => {
     setScrolled(window.scrollY>20);
   };

-  window.addEventListener("scroll",handleScroll);
-  return () => window.removeEventListener("scroll",handleScroll);
+  handleScroll();
+  window.addEventListener("scroll", handleScroll, { passive: true });
+  return () => window.removeEventListener("scroll", handleScroll);
 },[]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect( () => {
const handleScroll = () => {
setScrolled(window.scrollY>20);
};
window.addEventListener("scroll",handleScroll);
return () => window.removeEventListener("scroll",handleScroll);
},[]);
useEffect( () => {
const handleScroll = () => {
setScrolled(window.scrollY>20);
};
handleScroll();
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
},[]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Navbar.tsx` around lines 22 - 29, The useEffect adds a scroll
listener but never initializes scrolled, so call the handler once on mount and
add the listener as passive; specifically, inside the useEffect where
handleScroll(), setScrolled, and window.addEventListener("scroll", handleScroll)
are used, invoke handleScroll() immediately after defining it and change the
addEventListener call to use { passive: true } so the navbar state is correct on
initial render and the scroll listener is passive.


const navRef = useRef<HTMLDivElement>(null);
const themeContext = useContext(ThemeContext);

if (!themeContext) return null;

const { toggleTheme, mode } = themeContext;

const navLinkStyles = ({ isActive }: { isActive: boolean }) =>
`px-4 py-2 rounded-xl text-sm lg:text-base font-semibold transition-all duration-300 ${
const closeMenu = () => setIsOpen(false);

const handleMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
const nav = navRef.current;
const item = e.currentTarget;
if (!nav) return;
const navRect = nav.getBoundingClientRect();
const itemRect = item.getBoundingClientRect();
setPillStyle({
left: itemRect.left - navRect.left,
width: itemRect.width,
opacity: 1,
});
};

const handleMouseLeave = () => {
setPillStyle((prev) => ({ ...prev, opacity: 0 }));
};

const navLinkClass = ({ isActive }: { isActive: boolean }) =>
`relative z-10 px-4 py-2 rounded-xl text-sm lg:text-base font-semibold transition-colors duration-200 ${
isActive
? "text-blue-600 bg-blue-100 dark:bg-blue-900/40 shadow-sm"
: "text-slate-700 dark:text-gray-300 hover:text-blue-500"
? "text-blue-600 dark:text-blue-400"
: "text-slate-700 dark:text-gray-300"
}`;

const closeMenu = () => setIsOpen(false);

return (
<nav className="sticky top-0 z-50 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 transition-colors duration-300 backdrop-blur">
<nav className={`sticky top-0 z-50 border-b transition-all duration-300 ${
scrolled
? "bg-white/70 dark:bg-gray-900/70 backdrop-blur-2xl border-gray-200 dark:border-gray-800 shadow-sm"
: "bg-white dark:bg-gray-900 border-transparent"
}`}
>
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">

{/* Logo */}
Expand All @@ -35,27 +80,42 @@ const Navbar: React.FC = () => {
alt="CRL Icon"
className="h-8 w-8 object-contain"
/>

<span>GitHub Tracker</span>
</Link>

{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-3">
<NavLink to="/" className={navLinkStyles}>
Home
</NavLink>

<NavLink to="/track" className={navLinkStyles}>
Tracker
</NavLink>

<NavLink to="/contributors" className={navLinkStyles}>
Contributors
</NavLink>
<div
ref={navRef}
className="hidden md:flex items-center gap-1 relative"
onMouseLeave={handleMouseLeave}
>
{/* Sliding pill */}
<span
className="absolute top-0 h-full rounded-xl bg-gray-100 dark:bg-gray-800 pointer-events-none"
style={{
left: pillStyle.left,
width: pillStyle.width,
opacity: pillStyle.opacity,
transition: "left 0.2s ease, width 0.2s ease, opacity 0.15s ease",
backdropFilter: "blur(20px)",
WebkitBackdropFilter: "blur(20px)",
background:
mode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.35)",
boxShadow:
mode === "dark" ? "0 4px 20px rgba(0,0,0,0.25)" : "0 4px 20px rgba(0,0,0,0.08)",
}}
/>

<NavLink to="/login" className={navLinkStyles}>
Login
</NavLink>
{navItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
onMouseEnter={handleMouseEnter}
className={navLinkClass}
>
{item.label}
</NavLink>
))}

{/* Theme Toggle */}
<button
Expand All @@ -73,8 +133,6 @@ const Navbar: React.FC = () => {

{/* Mobile Controls */}
<div className="md:hidden flex items-center gap-2">

{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
Expand All @@ -83,11 +141,10 @@ const Navbar: React.FC = () => {
{mode === "dark" ? (
<Sun className="h-5 w-5 text-yellow-400" />
) : (
<Moon className="h-5 w-5 text-white" />
<Moon className="h-5 w-5 text-black" />
)}
</button>

{/* Menu Toggle */}
<button
onClick={() => setIsOpen(!isOpen)}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
Expand All @@ -104,45 +161,29 @@ const Navbar: React.FC = () => {

{/* Mobile Menu */}
{isOpen && (
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 ani-fade-in">
<div className="px-6 py-5 flex flex-col gap-3">

<NavLink
to="/"
className={navLinkStyles}
onClick={closeMenu}
>
Home
</NavLink>

<NavLink
to="/track"
className={navLinkStyles}
onClick={closeMenu}
>
Tracker
</NavLink>

<NavLink
to="/contributors"
className={navLinkStyles}
onClick={closeMenu}
>
Contributors
</NavLink>

<NavLink
to="/login"
className={navLinkStyles}
onClick={closeMenu}
>
Login
</NavLink>
{navItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
onClick={closeMenu}
className={({ isActive }) =>
`px-4 py-2 rounded-xl text-sm font-semibold transition-all duration-300 ${
isActive
? "text-blue-600 bg-blue-100 dark:bg-blue-900/40 shadow-sm"
: "text-slate-700 dark:text-gray-300 hover:text-blue-500"
}`
}
>
{item.label}
</NavLink>
))}
</div>
</div>
)}
</nav>
);
};

export default Navbar;
export default Navbar;
18 changes: 18 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,21 @@
.icon-issue-closed {
color: #cf222e;
}

@layer utilities{
@keyframes fade-in{
from {
opacity:0;
transform: translateY(20px);
filter:blur(45px);
}
to{
opacity:1;
transform: translateY(0);
filter:blur(0);
}
}
.ani-fade-in{
animation: fade-in 0.8s ease-out both;
}
}
Loading