import { cn } from "@/lib/utils"; import { AnimatePresence, motion } from "motion/react"; import { Cross as HamburgerCross } from "hamburger-react"; import { useEffect, useState, useRef } from "react"; declare module "react" { interface CSSProperties { [key: `--${string}`]: string | number; } } export const HoverNavigation = ({ items, className, }: { items: { label: string; link: string; }[]; className?: string; }) => { const [hoveredIndex, setHoveredIndex] = useState(null); const [activeIndex, setActiveIndex] = useState(null); const [hamburgerOpen, setHamburgerOpen] = useState(false); const [theme, setTheme] = useState<"light" | "dark">("dark"); const hamburgerRef = useRef(null); const menuRef = useRef(null); useEffect(() => { setTheme( document.documentElement.dataset.theme === "light" ? "light" : "dark", ); }, []); const toggleTheme = () => { const nextTheme = theme === "dark" ? "light" : "dark"; document.documentElement.dataset.theme = nextTheme; localStorage.setItem("theme", nextTheme); document .querySelector('meta[name="theme-color"]') ?.setAttribute( "content", nextTheme === "dark" ? "#101010" : "#fafafa", ); setTheme(nextTheme); }; // Track which section is in view useEffect(() => { const sectionIds = items.map((item) => { const url = new URL(item.link, window.location.origin); return url.hash.replace("#", ""); }); const sectionElements: (Element | null)[] = sectionIds.map((id) => { if (!id) return null; return document.getElementById(id); }); const visibilityMap = new Map(); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { visibilityMap.set(entry.target, entry.intersectionRatio); }); let maxRatio = 0; let maxIdx = -1; sectionElements.forEach((el, idx) => { if (!el) return; const ratio = visibilityMap.get(el) ?? 0; if (ratio > maxRatio) { maxRatio = ratio; maxIdx = idx; } }); if (maxIdx !== -1 && maxRatio > 0) { setActiveIndex(maxIdx); } }, { rootMargin: "-20% 0px -50% 0px", threshold: [0, 0.25, 0.5, 0.75, 1], }, ); sectionElements.forEach((el) => { if (el) observer.observe(el); }); // set hover index on projects when on "/projects" const splittedURL = window.location.href.split("/"); if (splittedURL[splittedURL.length - 1]) { setActiveIndex(2); } return () => observer.disconnect(); }, [items]); useEffect(() => { // hamburger stuff const handleClickOutside = (event: MouseEvent) => { if ( hamburgerOpen && hamburgerRef.current && menuRef.current && !hamburgerRef.current.contains(event.target as Node) && !menuRef.current.contains(event.target as Node) ) { setHamburgerOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [hamburgerOpen]); // Use hoveredIndex when hovering, otherwise use activeIndex const displayIndex = hoveredIndex !== null ? hoveredIndex : activeIndex; return ( <>
{/* Mobile Navigation Menu */} {hamburgerOpen && ( )} ); }; const ThemeToggle = ({ theme, onToggle, }: { theme: "light" | "dark"; onToggle: () => void; }) => ( ); export const NavBarLink = ({ className, children, }: { className?: string; children: React.ReactNode; }) => { return (
{children}
); };