Nav menu for mobile

This commit is contained in:
2025-12-06 22:45:18 +01:00
parent 97761b5df4
commit 73c0c28f8a
3 changed files with 159 additions and 50 deletions
+97
View File
@@ -0,0 +1,97 @@
import { AnimatePresence } from "framer-motion";
import { Menu, MessageSquare, X } from "lucide-react";
import { useState, useEffect, useRef } from "react";
import { motion } from "framer-motion";
import { createPortal } from "react-dom";
export default function MobileMenu() {
const [open, setOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
const [buttonPosition, setButtonPosition] = useState({ top: 0, right: 0 });
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (open && buttonRef.current) {
const rect = buttonRef.current.getBoundingClientRect();
setButtonPosition({
top: rect.bottom + 8,
right: window.innerWidth - rect.right
});
}
}, [open]);
const menuContent = (
<AnimatePresence>
{open && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{ zIndex: 999999 }}
className="fixed inset-0 bg-black/20 md:hidden"
onClick={() => setOpen(false)}
/>
{/* Menu */}
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.25 }}
style={{
position: 'fixed',
top: `${buttonPosition.top}px`,
right: `${buttonPosition.right}px`,
zIndex: 1000000
}}
className="w-44 bg-slate-900 border border-slate-700 rounded-xl shadow-2xl p-4 flex flex-col gap-4 md:hidden"
>
{["Blog", "Projects", "About"].map((item) => (
<a
key={item}
href={`/${item.toLowerCase()}`}
className="text-slate-200 hover:text-white transition-colors"
onClick={() => setOpen(false)}
>
{item}
</a>
))}
<a
href="https://discord.gg/HFRsNxfCqT"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 bg-purple-500 hover:bg-purple-600 rounded-lg font-semibold text-sm text-white"
onClick={() => setOpen(false)}
>
<MessageSquare className="w-4 h-4" />
Join Discord
</a>
</motion.div>
</>
)}
</AnimatePresence>
);
return (
<>
<div className="md:hidden">
<button
ref={buttonRef}
onClick={() => setOpen((prev) => !prev)}
className="p-2 text-slate-200"
>
{open ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
</div>
{mounted && createPortal(menuContent, document.body)}
</>
);
}
+59
View File
@@ -0,0 +1,59 @@
import { motion } from "framer-motion";
import MobileMenu from "./MobileMenu";
import { MessageSquare } from "lucide-react";
export default function NavMenu() {
return (
<motion.div
className="flex gap-6 items-center"
variants={{
hidden: {},
visible: {
transition: {
staggerChildren: 0.08,
delayChildren: 0.15,
},
},
}}
>
{["Blog", "Projects", "About"].map((item) => (
<motion.a
key={item}
href={`/${item.toLowerCase()}`}
className="text-slate-300 hover:text-white transition-colors hidden md:block"
variants={{
hidden: { opacity: 0, y: -12 },
visible: { opacity: 1, y: 0 },
}}
transition={{
duration: 0.8,
}}
whileHover={{ y: -2 }}
>
{item}
</motion.a>
))}
<motion.a
href="https://discord.gg/HFRsNxfCqT"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500 hover:bg-purple-600 rounded-lg font-semibold transition-colors text-sm"
variants={{
hidden: { opacity: 0, y: -12 },
visible: { opacity: 1, y: 0 },
}}
transition={{
duration: 0.45,
ease: [0.25, 0.1, 0.25, 1],
}}
whileTap={{ scale: 0.95 }}
>
<MessageSquare className="w-4 h-4" />
Join Discord
</motion.a>
<MobileMenu />
</motion.div>
);
}
+3 -50
View File
@@ -1,5 +1,7 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Github, MessageSquare } from "lucide-react"; import { Github, MessageSquare } from "lucide-react";
import MobileMenu from "./MobileMenu";
import NavMenu from "./NavMenu";
export default function WebStuff({ export default function WebStuff({
children, children,
@@ -71,56 +73,7 @@ export default function WebStuff({
<img src="/orus2.svg" alt="Logo" className="h-4" /> <img src="/orus2.svg" alt="Logo" className="h-4" />
</motion.a> </motion.a>
{/* Menu */} <NavMenu />
<motion.div
className="flex gap-6 items-center"
variants={{
hidden: {},
visible: {
transition: {
staggerChildren: 0.08,
delayChildren: 0.15,
},
},
}}
>
{["Blog", "Projects", "About"].map((item) => (
<motion.a
key={item}
href={`/${item.toLowerCase()}`}
className="text-slate-300 hover:text-white transition-colors hidden md:block"
variants={{
hidden: { opacity: 0, y: -12 },
visible: { opacity: 1, y: 0 },
}}
transition={{
duration: 0.8,
}}
whileHover={{ y: -2 }}
>
{item}
</motion.a>
))}
<motion.a
href="https://discord.gg/HFRsNxfCqT"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500 hover:bg-purple-600 rounded-lg font-semibold transition-colors text-sm"
variants={{
hidden: { opacity: 0, y: -12 },
visible: { opacity: 1, y: 0 },
}}
transition={{
duration: 0.45,
ease: [0.25, 0.1, 0.25, 1],
}}
whileTap={{ scale: 0.95 }}
>
<MessageSquare className="w-4 h-4" />
Join Discord
</motion.a>
</motion.div>
</motion.nav> </motion.nav>
{children} {children}