Nav menu for mobile
This commit is contained in:
@@ -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)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -1,5 +1,7 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Github, MessageSquare } from "lucide-react";
|
||||
import MobileMenu from "./MobileMenu";
|
||||
import NavMenu from "./NavMenu";
|
||||
|
||||
export default function WebStuff({
|
||||
children,
|
||||
@@ -71,56 +73,7 @@ export default function WebStuff({
|
||||
<img src="/orus2.svg" alt="Logo" className="h-4" />
|
||||
</motion.a>
|
||||
|
||||
{/* Menu */}
|
||||
<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>
|
||||
<NavMenu />
|
||||
</motion.nav>
|
||||
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user