"use client"; import { useState, useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { Menu, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { LayoutDashboard, Users, Tag, Package, Banknote, Truck, Fuel, Send, BarChart3, ScrollText, Settings, } from "lucide-react"; import { cn } from "@/lib/utils"; type NavItem = { href: string; label: string; icon: React.ComponentType<{ className?: string }>; }; const groups: { label: string; items: NavItem[] }[] = [ { label: "Overview", items: [{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard }], }, { label: "Operations", items: [ { href: "/customers", label: "Customers", icon: Users }, { href: "/delivery-notes", label: "Delivery Notes", icon: Package }, { href: "/cash-customers", label: "Cash Customers", icon: Banknote }, { href: "/suppliers", label: "Suppliers", icon: Truck }, { href: "/fuel-purchases", label: "Fuel Purchases", icon: Fuel }, ], }, { label: "Pricing & reports", items: [ { href: "/pricing", label: "Pricing", icon: Tag }, { href: "/send-statement", label: "Send Statement", icon: Send }, { href: "/custom-reports", label: "Custom Reports", icon: BarChart3 }, { href: "/statements", label: "Statement Log", icon: ScrollText }, ], }, { label: "Admin", items: [{ href: "/settings", label: "Settings", icon: Settings }], }, ]; export function MobileNav() { const [open, setOpen] = useState(false); const [rendered, setRendered] = useState(false); const [mounted, setMounted] = useState(false); const pathname = usePathname(); const triggerRef = useRef(null); // Track mount so we only portal on the client (avoids SSR mismatch) useEffect(() => { setMounted(true); }, []); // Close the menu whenever the route changes useEffect(() => { setOpen(false); }, [pathname]); // Keep the sheet mounted briefly after closing so the exit animation plays useEffect(() => { if (open) { setRendered(true); return; } const t = setTimeout(() => setRendered(false), 250); return () => clearTimeout(t); }, [open]); // Lock body scroll while open useEffect(() => { if (!open) return; const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, [open]); // Close on Escape, restore focus to the trigger useEffect(() => { if (!open) return; function onKey(e: KeyboardEvent) { if (e.key === "Escape") { setOpen(false); triggerRef.current?.focus(); } } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open]); const trigger = ( ); if (!rendered || !mounted) { return trigger; } const sheet = ( <> {/* Backdrop — fades in/out */}
setOpen(false)} aria-hidden /> {/* Sheet — slides in from the left. Rendered via portal at document.body so it escapes any containing block created by parent transforms/filters (e.g. backdrop-blur). */} ); return ( <> {trigger} {createPortal(sheet, document.body)} ); }