fix: mobil menü drawer'ı body'ye portal et — header transform'u fixed konumu bozuyordu
Header scroll efekti #floating-header-wrap'e transform uyguladığı için içindeki position:fixed overlay viewport yerine header'a göre konumlanıyordu. Drawer + overlay artık createPortal ile document.body'ye render ediliyor: sağ drawer, beyaz panel, tam ekran koyu overlay (z-100).
This commit is contained in:
+119
-110
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { Menu, X, ChevronDown, Phone, ArrowRight } from "lucide-react";
|
import { Menu, X, ChevronDown, Phone, ArrowRight } from "lucide-react";
|
||||||
@@ -8,7 +9,6 @@ import { Menu, X, ChevronDown, Phone, ArrowRight } from "lucide-react";
|
|||||||
type NavService = { slug: string; title: string };
|
type NavService = { slug: string; title: string };
|
||||||
|
|
||||||
const LINKS = [
|
const LINKS = [
|
||||||
{ href: "/", label: "Anasayfa" },
|
|
||||||
{ href: "/cozumler", label: "Çözümler" },
|
{ href: "/cozumler", label: "Çözümler" },
|
||||||
{ href: "/projeler", label: "Projeler" },
|
{ href: "/projeler", label: "Projeler" },
|
||||||
{ href: "/blog", label: "Blog" },
|
{ href: "/blog", label: "Blog" },
|
||||||
@@ -25,10 +25,13 @@ export function MobileMenu({
|
|||||||
phone: string;
|
phone: string;
|
||||||
phoneRaw: string;
|
phoneRaw: string;
|
||||||
}) {
|
}) {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [servicesOpen, setServicesOpen] = useState(false);
|
const [servicesOpen, setServicesOpen] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
useEffect(() => setMounted(true), []);
|
||||||
|
|
||||||
// Rota değişince menüyü kapat
|
// Rota değişince menüyü kapat
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -42,6 +45,120 @@ export function MobileMenu({
|
|||||||
};
|
};
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
// Drawer + overlay — header transform bağlamından kaçmak için body'ye portal
|
||||||
|
const overlay = (
|
||||||
|
<div
|
||||||
|
className={`fixed inset-0 z-[100] lg:hidden ${
|
||||||
|
open ? "" : "pointer-events-none"
|
||||||
|
}`}
|
||||||
|
aria-hidden={!open}
|
||||||
|
>
|
||||||
|
{/* Koyu overlay */}
|
||||||
|
<div
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className={`absolute inset-0 bg-black/50 transition-opacity duration-300 ${
|
||||||
|
open ? "opacity-100" : "opacity-0"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Sağ drawer — beyaz panel */}
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
className={`absolute right-0 top-0 flex h-full w-[82%] max-w-[340px] flex-col bg-white shadow-2xl transition-transform duration-300 ease-out ${
|
||||||
|
open ? "translate-x-0" : "translate-x-full"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* Üst bar */}
|
||||||
|
<div className="flex h-14 items-center justify-between border-b border-gray-100 px-5">
|
||||||
|
<span className="text-sm font-semibold tracking-tight text-[var(--navy)]">
|
||||||
|
Menü
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
aria-label="Menüyü kapat"
|
||||||
|
className="-mr-2 inline-flex size-9 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 hover:text-[var(--navy)]"
|
||||||
|
>
|
||||||
|
<X className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Linkler */}
|
||||||
|
<nav className="flex-1 overflow-y-auto px-3 py-4">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||||
|
>
|
||||||
|
Anasayfa
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Hizmetler — açılır */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setServicesOpen((v) => !v)}
|
||||||
|
aria-expanded={servicesOpen}
|
||||||
|
className="flex w-full items-center justify-between rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||||
|
>
|
||||||
|
Hizmetler
|
||||||
|
<ChevronDown
|
||||||
|
className={`size-4 transition-transform duration-200 ${
|
||||||
|
servicesOpen ? "rotate-180" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{servicesOpen && (
|
||||||
|
<div className="mb-1 ml-3 border-l border-gray-100 pl-3">
|
||||||
|
{services.map((s) => (
|
||||||
|
<Link
|
||||||
|
key={s.slug}
|
||||||
|
href={`/hizmetler/${s.slug}`}
|
||||||
|
className="block rounded-lg px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||||
|
>
|
||||||
|
{s.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link
|
||||||
|
href="/hizmetler"
|
||||||
|
className="block rounded-lg px-3 py-2 text-sm font-semibold text-[var(--sky-600)] hover:text-[var(--navy)]"
|
||||||
|
>
|
||||||
|
Tüm hizmetleri gör →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{LINKS.map((l) => (
|
||||||
|
<Link
|
||||||
|
key={l.href}
|
||||||
|
href={l.href}
|
||||||
|
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||||
|
>
|
||||||
|
{l.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Alt CTA */}
|
||||||
|
<div className="space-y-2 border-t border-gray-100 p-4">
|
||||||
|
<a
|
||||||
|
href={`tel:${phoneRaw}`}
|
||||||
|
className="flex items-center justify-center gap-2 rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-[var(--navy)] transition-colors hover:border-[var(--navy)]"
|
||||||
|
>
|
||||||
|
<Phone className="size-4" />
|
||||||
|
{phone}
|
||||||
|
</a>
|
||||||
|
<Link
|
||||||
|
href="/iletisim"
|
||||||
|
className="flex items-center justify-center gap-2 rounded-xl bg-[var(--navy)] px-4 py-3 text-sm font-semibold text-white transition-colors hover:bg-[var(--navy-700)]"
|
||||||
|
>
|
||||||
|
Ücretsiz Teklif
|
||||||
|
<ArrowRight className="size-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:hidden">
|
<div className="lg:hidden">
|
||||||
<button
|
<button
|
||||||
@@ -54,115 +171,7 @@ export function MobileMenu({
|
|||||||
<Menu className="size-5" />
|
<Menu className="size-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Overlay + drawer */}
|
{mounted && createPortal(overlay, document.body)}
|
||||||
<div
|
|
||||||
className={`fixed inset-0 z-[60] lg:hidden ${
|
|
||||||
open ? "" : "pointer-events-none"
|
|
||||||
}`}
|
|
||||||
aria-hidden={!open}
|
|
||||||
>
|
|
||||||
{/* Backdrop */}
|
|
||||||
<div
|
|
||||||
onClick={() => setOpen(false)}
|
|
||||||
className={`absolute inset-0 bg-black/40 backdrop-blur-sm transition-opacity duration-300 ${
|
|
||||||
open ? "opacity-100" : "opacity-0"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Panel */}
|
|
||||||
<div
|
|
||||||
className={`absolute right-0 top-0 flex h-full w-[86%] max-w-sm flex-col bg-white shadow-2xl transition-transform duration-300 ease-out ${
|
|
||||||
open ? "translate-x-0" : "translate-x-full"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Üst bar */}
|
|
||||||
<div className="flex h-14 items-center justify-between border-b border-gray-100 px-5">
|
|
||||||
<span className="text-sm font-semibold tracking-tight text-[var(--navy)]">
|
|
||||||
Menü
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setOpen(false)}
|
|
||||||
aria-label="Menüyü kapat"
|
|
||||||
className="inline-flex size-9 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 hover:text-[var(--navy)]"
|
|
||||||
>
|
|
||||||
<X className="size-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Linkler */}
|
|
||||||
<nav className="flex-1 overflow-y-auto px-3 py-4">
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
|
||||||
>
|
|
||||||
Anasayfa
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* Hizmetler — açılır */}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setServicesOpen((v) => !v)}
|
|
||||||
aria-expanded={servicesOpen}
|
|
||||||
className="flex w-full items-center justify-between rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
|
||||||
>
|
|
||||||
Hizmetler
|
|
||||||
<ChevronDown
|
|
||||||
className={`size-4 transition-transform duration-200 ${
|
|
||||||
servicesOpen ? "rotate-180" : ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{servicesOpen && (
|
|
||||||
<div className="mb-1 ml-3 border-l border-gray-100 pl-3">
|
|
||||||
{services.map((s) => (
|
|
||||||
<Link
|
|
||||||
key={s.slug}
|
|
||||||
href={`/hizmetler/${s.slug}`}
|
|
||||||
className="block rounded-lg px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
|
||||||
>
|
|
||||||
{s.title}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
<Link
|
|
||||||
href="/hizmetler"
|
|
||||||
className="block rounded-lg px-3 py-2 text-sm font-semibold text-[var(--sky-600)] hover:text-[var(--navy)]"
|
|
||||||
>
|
|
||||||
Tüm hizmetleri gör →
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{LINKS.filter((l) => l.href !== "/").map((l) => (
|
|
||||||
<Link
|
|
||||||
key={l.href}
|
|
||||||
href={l.href}
|
|
||||||
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
|
||||||
>
|
|
||||||
{l.label}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Alt CTA */}
|
|
||||||
<div className="space-y-2 border-t border-gray-100 p-4">
|
|
||||||
<a
|
|
||||||
href={`tel:${phoneRaw}`}
|
|
||||||
className="flex items-center justify-center gap-2 rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-[var(--navy)] transition-colors hover:border-[var(--navy)]"
|
|
||||||
>
|
|
||||||
<Phone className="size-4" />
|
|
||||||
{phone}
|
|
||||||
</a>
|
|
||||||
<Link
|
|
||||||
href="/iletisim"
|
|
||||||
className="flex items-center justify-center gap-2 rounded-xl bg-[var(--navy)] px-4 py-3 text-sm font-semibold text-white transition-colors hover:bg-[var(--navy-700)]"
|
|
||||||
>
|
|
||||||
Ücretsiz Teklif
|
|
||||||
<ArrowRight className="size-4" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user