a321ac5c9b
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).
178 lines
5.8 KiB
TypeScript
178 lines
5.8 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { createPortal } from "react-dom";
|
||
import Link from "next/link";
|
||
import { usePathname } from "next/navigation";
|
||
import { Menu, X, ChevronDown, Phone, ArrowRight } from "lucide-react";
|
||
|
||
type NavService = { slug: string; title: string };
|
||
|
||
const LINKS = [
|
||
{ href: "/cozumler", label: "Çözümler" },
|
||
{ href: "/projeler", label: "Projeler" },
|
||
{ href: "/blog", label: "Blog" },
|
||
{ href: "/hakkimizda", label: "Hakkımızda" },
|
||
{ href: "/iletisim", label: "İletişim" },
|
||
];
|
||
|
||
export function MobileMenu({
|
||
services,
|
||
phone,
|
||
phoneRaw,
|
||
}: {
|
||
services: NavService[];
|
||
phone: string;
|
||
phoneRaw: string;
|
||
}) {
|
||
const [mounted, setMounted] = useState(false);
|
||
const [open, setOpen] = useState(false);
|
||
const [servicesOpen, setServicesOpen] = useState(false);
|
||
const pathname = usePathname();
|
||
|
||
useEffect(() => setMounted(true), []);
|
||
|
||
// Rota değişince menüyü kapat
|
||
useEffect(() => {
|
||
setOpen(false);
|
||
}, [pathname]);
|
||
|
||
// Açıkken arka plan kaydırmasını kilitle
|
||
useEffect(() => {
|
||
document.body.style.overflow = open ? "hidden" : "";
|
||
return () => {
|
||
document.body.style.overflow = "";
|
||
};
|
||
}, [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 (
|
||
<div className="lg:hidden">
|
||
<button
|
||
type="button"
|
||
onClick={() => setOpen(true)}
|
||
aria-label="Menüyü aç"
|
||
aria-expanded={open}
|
||
className="inline-flex size-9 items-center justify-center rounded-lg text-gray-700 transition-colors hover:bg-gray-100 hover:text-[var(--navy)]"
|
||
>
|
||
<Menu className="size-5" />
|
||
</button>
|
||
|
||
{mounted && createPortal(overlay, document.body)}
|
||
</div>
|
||
);
|
||
}
|