Fix Scroll.

This commit is contained in:
2025-02-10 12:53:25 +01:00
parent 4b74640d87
commit d23c381913
10 changed files with 454 additions and 129 deletions
+123 -72
View File
@@ -1,13 +1,24 @@
// components/Layout.tsx
import { useEffect, useState, ReactNode, useCallback } from "react";
// Layout.tsx
import { useEffect, useState, ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { motion, AnimatePresence } from "framer-motion";
import { Menu, X, Home, User, Briefcase, BookOpen, Phone, LayoutDashboard } from "lucide-react";
import {
Menu,
X,
Home,
User,
Briefcase,
BookOpen,
Phone,
LayoutDashboard,
} from "lucide-react";
import { supabase } from "../utils/supabaseClient";
import { ContactInfo } from "./ContactInfo";
import { NavLink } from "./NavLink";
import LanguageSwitcher from "./LanguageSwitcher";
import { Link, useLocation } from "react-router-dom";
import Footer from "./Footer";
import { useScrollContext } from "./ScrollContext";
interface LayoutProps {
children: ReactNode;
@@ -16,41 +27,27 @@ interface LayoutProps {
const Layout = ({ children }: LayoutProps) => {
const { t } = useTranslation();
const [isAdmin, setIsAdmin] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const [isMounted, setIsMounted] = useState(false);
const location = useLocation();
const { isMenuOpen, setIsMenuOpen } = useScrollContext();
// Memoized toggle function
const toggleMenu = useCallback((force?: boolean) => {
setIsOpen(prev => typeof force === 'boolean' ? force : !prev);
// Initial mount
useEffect(() => {
setIsMounted(true);
}, []);
// Route change handler
// Schließe das Menü nur beim Routenwechsel
useEffect(() => {
toggleMenu(false);
}, [location.pathname, toggleMenu]);
setIsMenuOpen(false);
}, [location.pathname, setIsMenuOpen]);
// Scroll lock effect
useEffect(() => {
const body = document.body;
if (isOpen) {
const scrollY = window.scrollY;
body.style.position = 'fixed';
body.style.top = `-${scrollY}px`;
body.style.width = '100%';
body.style.overflow = 'hidden';
} else {
const scrollY = body.style.top;
body.style.position = '';
body.style.top = '';
body.style.width = '';
body.style.overflow = '';
if (scrollY) {
window.scrollTo(0, parseInt(scrollY || '0') * -1);
}
}
}, [isOpen]);
// Toggle Menu
const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen);
};
// Prüfe Admin-Status und registriere den Scroll-Handler
useEffect(() => {
const checkAdmin = async () => {
const { data, error } = await supabase.auth.getSession();
@@ -82,11 +79,31 @@ const Layout = ({ children }: LayoutProps) => {
}, []);
const navigationLinks = [
{ path: "/", label: t("navigation.main.home"), icon: <Home className="h-5 w-5" /> },
{ path: "/about", label: t("navigation.main.about"), icon: <User className="h-5 w-5" /> },
{ path: "/portfolio", label: t("navigation.main.portfolio"), icon: <Briefcase className="h-5 w-5" /> },
{ path: "/blog", label: t("navigation.main.blog"), icon: <BookOpen className="h-5 w-5" /> },
{ path: "/contact", label: t("navigation.main.contact"), icon: <Phone className="h-5 w-5" /> },
{
path: "/",
label: t("navigation.main.home"),
icon: <Home className="h-5 w-5" />,
},
{
path: "/about",
label: t("navigation.main.about"),
icon: <User className="h-5 w-5" />,
},
{
path: "/portfolio",
label: t("navigation.main.portfolio"),
icon: <Briefcase className="h-5 w-5" />,
},
{
path: "/blog",
label: t("navigation.main.blog"),
icon: <BookOpen className="h-5 w-5" />,
},
{
path: "/contact",
label: t("navigation.main.contact"),
icon: <Phone className="h-5 w-5" />,
},
...(isAdmin
? [
{
@@ -98,11 +115,17 @@ const Layout = ({ children }: LayoutProps) => {
: []),
];
if (!isMounted) {
return null;
}
return (
<div className="min-h-screen bg-zinc-900 flex flex-col">
// Relativer Container, um z-index-Steuerung zu ermöglichen
<div className="min-h-screen bg-zinc-900 flex flex-col relative">
{/* Navigation oberste Ebene */}
<nav
className={`
fixed w-full z-[80] transition-all duration-300
fixed w-full z-40 transition-all duration-300
${scrolled
? "bg-zinc-900/80 backdrop-blur supports-[backdrop-filter]:bg-zinc-900/80"
: "bg-transparent"
@@ -113,6 +136,7 @@ const Layout = ({ children }: LayoutProps) => {
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<Link to="/" className="flex items-center space-x-2 group shrink-0">
<motion.img
src="/logo.png"
@@ -122,47 +146,66 @@ const Layout = ({ children }: LayoutProps) => {
transition={{ type: "spring", stiffness: 400, damping: 10 }}
/>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-2">
{navigationLinks.map(({ path, label, icon }) => (
<NavLink key={path} to={path} icon={icon} label={label} className="text-sm" />
<NavLink
key={path}
to={path}
icon={icon}
label={label}
className="text-sm"
/>
))}
<div className="ml-4 text-zinc-400 pl-2 border-l border-zinc-700">
<LanguageSwitcher />
</div>
</div>
{/* Mobile Menu Button */}
<motion.button
className="md:hidden relative z-[85] p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
onClick={() => toggleMenu()}
aria-expanded={isOpen}
className="md:hidden relative z-50 p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
onClick={toggleMenu}
aria-expanded={isMenuOpen}
aria-controls="mobile-menu"
aria-label={isOpen ? "Close menu" : "Open menu"}
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
whileTap={{ scale: 0.95 }}
>
{isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</motion.button>
</div>
</div>
</nav>
<AnimatePresence>
{isOpen && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/50 md:hidden z-[75]"
onClick={() => toggleMenu(false)}
/>
<motion.div
initial={{ x: "100%" }}
animate={{ x: 0 }}
exit={{ x: "100%" }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden overflow-y-auto border-l border-zinc-800 z-[75]"
>
<div className="flex flex-col h-full pt-16">
<div className="flex-1 overflow-y-auto">
{/* Mobile Menu */}
<AnimatePresence>
{isMenuOpen && (
<>
{/* Backdrop: Sichtbar, aber mit pointer-events-none blockiert er keine Interaktionen */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/50 md:hidden z-20 pointer-events-none"
/>
{/* Sidebar oberhalb des Hauptinhalts */}
<motion.div
initial={{ x: "100%" }}
animate={{ x: 0 }}
exit={{ x: "100%" }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden z-50 border-l border-zinc-800"
>
{/* Header-Spacer für die Navigation */}
<div className="h-16" />
{/* Scrollbarer Sidebar-Inhalt */}
<div className="h-[calc(100vh_-_4rem)] overflow-y-auto">
<div className="flex flex-col h-full">
<div className="flex-1">
<ContactInfo />
<div className="py-4 px-4">
{navigationLinks.map(({ path, label, icon }) => (
@@ -171,7 +214,8 @@ const Layout = ({ children }: LayoutProps) => {
to={path}
icon={icon}
label={label}
onClick={() => toggleMenu(false)}
// Alternativ: Hier könnte ein Klick auch das Menü schließen
onClick={() => setIsMenuOpen(false)}
className="flex items-center w-full mb-1"
/>
))}
@@ -179,19 +223,26 @@ const Layout = ({ children }: LayoutProps) => {
</div>
<div className="border-t border-zinc-800 p-6 mt-auto">
<div className="flex items-center justify-between">
<span className="text-sm text-zinc-500">Sprache ändern</span>
<span className="text-sm text-zinc-500">
Sprache ändern
</span>
<LanguageSwitcher />
</div>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
</nav>
{children}
</div>
</motion.div>
</>
)}
</AnimatePresence>
{/* Hauptinhalt hier kann normal gescrollt werden */}
<main className="flex-1 pt-16 relative z-30">{children}</main>
{/* Footer */}
<Footer />
</div>
);
};
export default Layout;
export default Layout;