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 { 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}
|
||||||
|
|||||||
Reference in New Issue
Block a user