fixed burger menu mobile
This commit is contained in:
parent
009ea87198
commit
486451381c
26
src/App.tsx
26
src/App.tsx
@ -2,7 +2,7 @@ import { Toaster } from "@/components/ui/toaster";
|
|||||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import Index from "./pages/Index";
|
import Index from "./pages/Index";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
import ProjectPage from "./pages/ProjectPage";
|
import ProjectPage from "./pages/ProjectPage";
|
||||||
@ -13,6 +13,21 @@ const queryClient = new QueryClient();
|
|||||||
|
|
||||||
const IndexWrapper = () => <Index />;
|
const IndexWrapper = () => <Index />;
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <IndexWrapper />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/project/:projectId",
|
||||||
|
element: <ProjectPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "*",
|
||||||
|
element: <NotFound />,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
@ -20,14 +35,7 @@ const App = () => (
|
|||||||
<ParticlesBackground />
|
<ParticlesBackground />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Sonner />
|
<Sonner />
|
||||||
<BrowserRouter>
|
<RouterProvider router={router} />
|
||||||
<Routes>
|
|
||||||
<Route path="/" element={<IndexWrapper />} />
|
|
||||||
<Route path="/project/:projectId" element={<ProjectPage />} />
|
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
|
||||||
<Route path="*" element={<NotFound />} />
|
|
||||||
</Routes>
|
|
||||||
</BrowserRouter>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
@ -1,26 +1,53 @@
|
|||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun, Menu, X } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
import { motion } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
const { language, setLanguage, t } = useLanguage();
|
const { language, setLanguage, t } = useLanguage();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Check if we're on a project page
|
// Check if we're on a project page
|
||||||
const isProjectPage = location.pathname.startsWith('/project/');
|
const isProjectPage = location.pathname.startsWith('/project/');
|
||||||
|
|
||||||
|
// Close mobile menu when clicking outside
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) {
|
||||||
|
setMobileMenuOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mobileMenuOpen) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [mobileMenuOpen]);
|
||||||
|
|
||||||
const handleNavigation = (section: string) => {
|
const handleNavigation = (section: string) => {
|
||||||
|
// Close mobile menu immediately
|
||||||
|
setMobileMenuOpen(false);
|
||||||
|
|
||||||
|
// Then scroll to section (happens independently)
|
||||||
if (isProjectPage) {
|
if (isProjectPage) {
|
||||||
// If on project page, navigate to home with section anchor
|
// If on project page, navigate to home with section anchor
|
||||||
navigate(`/#${section}`);
|
navigate(`/#${section}`);
|
||||||
} else {
|
} else {
|
||||||
// If on home page, scroll to section
|
// If on home page, scroll to section
|
||||||
scrollToSection(section);
|
// Use setTimeout to ensure scroll happens after menu animation starts
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToSection(section);
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,6 +77,7 @@ export const Header = () => {
|
|||||||
initial={{ y: -100 }}
|
initial={{ y: -100 }}
|
||||||
animate={{ y: 0 }}
|
animate={{ y: 0 }}
|
||||||
className="fixed top-0 left-0 right-0 z-50 border-b border-border/40 bg-background/80 backdrop-blur-lg"
|
className="fixed top-0 left-0 right-0 z-50 border-b border-border/40 bg-background/80 backdrop-blur-lg"
|
||||||
|
ref={mobileMenuRef}
|
||||||
>
|
>
|
||||||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||||
<motion.button
|
<motion.button
|
||||||
@ -63,6 +91,7 @@ export const Header = () => {
|
|||||||
AP
|
AP
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|
||||||
|
{/* Desktop Navigation */}
|
||||||
<nav className="hidden md:flex items-center gap-8">
|
<nav className="hidden md:flex items-center gap-8">
|
||||||
{["home", "projects", "skills", "contact"].map((item, i) => (
|
{["home", "projects", "skills", "contact"].map((item, i) => (
|
||||||
<motion.button
|
<motion.button
|
||||||
@ -107,8 +136,58 @@ export const Header = () => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Button */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ delay: 0.5 }}
|
||||||
|
className="md:hidden"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
{mobileMenuOpen ? (
|
||||||
|
<X className="h-5 w-5" />
|
||||||
|
) : (
|
||||||
|
<Menu className="h-5 w-5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Navigation */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<motion.nav
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: "auto" }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="md:hidden border-t border-border/40 bg-background/95 backdrop-blur-lg overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="container mx-auto px-4 py-4 flex flex-col gap-4">
|
||||||
|
{["home", "projects", "skills", "contact"].map((item, i) => (
|
||||||
|
<motion.button
|
||||||
|
key={item}
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -20 }}
|
||||||
|
transition={{ delay: 0.1 * i }}
|
||||||
|
onClick={() => handleNavigation(item)}
|
||||||
|
className="text-left text-base font-medium text-muted-foreground hover:text-foreground transition-colors py-2"
|
||||||
|
>
|
||||||
|
{t(`nav.${item}`)}
|
||||||
|
</motion.button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.nav>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</motion.header>
|
</motion.header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const ParticlesBackground = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const particlesLoaded = async (container) => {
|
const particlesLoaded = async (container) => {
|
||||||
console.log(container);
|
// console.log(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user