forked from UKSOURCE/hailearning.edu.vn
113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
|
|
interface MenuItem {
|
|
label: string;
|
|
href: string;
|
|
submenu?: MenuItem[];
|
|
}
|
|
|
|
interface MobileMenuProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
menuItems: MenuItem[];
|
|
}
|
|
|
|
const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, menuItems }) => {
|
|
const [expandedItems, setExpandedItems] = useState<{ [key: string]: boolean }>({});
|
|
|
|
// Disable body scroll when menu is open
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
document.body.style.overflow = "hidden";
|
|
} else {
|
|
document.body.style.overflow = "";
|
|
}
|
|
|
|
return () => {
|
|
document.body.style.overflow = "";
|
|
};
|
|
}, [isOpen]);
|
|
|
|
const toggleSubmenu = (path: string) => {
|
|
setExpandedItems((prev) => ({
|
|
...prev,
|
|
[path]: !prev[path],
|
|
}));
|
|
};
|
|
|
|
const handleLinkClick = () => {
|
|
onClose();
|
|
};
|
|
|
|
// Recursive component for rendering menu items with nested submenus
|
|
const renderMenuItem = (item: MenuItem, index: number, parentPath: string = "", level: number = 0) => {
|
|
const currentPath = parentPath ? `${parentPath}-${index}` : `${index}`;
|
|
const hasSubmenu = item.submenu && item.submenu.length > 0;
|
|
const isExpanded = expandedItems[currentPath];
|
|
|
|
return (
|
|
<li key={currentPath} className={`mobile-menu-item mobile-menu-level-${level}`}>
|
|
<div className="mobile-menu-item-wrapper">
|
|
{hasSubmenu ? (
|
|
<>
|
|
<span className="mobile-menu-link">{item.label}</span>
|
|
<button
|
|
className="mobile-menu-toggle"
|
|
onClick={() => toggleSubmenu(currentPath)}
|
|
aria-label={`Toggle ${item.label} submenu`}
|
|
>
|
|
<i className={`fas ${isExpanded ? "fa-minus" : "fa-plus"}`}></i>
|
|
</button>
|
|
</>
|
|
) : (
|
|
<Link href={item.href} className="mobile-menu-link" onClick={handleLinkClick}>
|
|
{item.label}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
|
|
{/* Submenu with accordion animation - supports nested submenus */}
|
|
{hasSubmenu && (
|
|
<ul
|
|
className={`mobile-submenu mobile-submenu-level-${level + 1} ${isExpanded ? "mobile-submenu-open" : ""}`}
|
|
>
|
|
{item.submenu!.map((subItem, subIndex) =>
|
|
renderMenuItem(subItem, subIndex, currentPath, level + 1),
|
|
)}
|
|
</ul>
|
|
)}
|
|
</li>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Mobile Menu Slide Panel */}
|
|
<div className={`mobile-slide-menu ${isOpen ? "mobile-menu-open" : ""}`}>
|
|
<div className="mobile-menu-header">
|
|
<div className="mobile-menu-logo">
|
|
<Link href="/" onClick={handleLinkClick}>
|
|
<img src="/assets/img/logo/black-logo.svg" alt="logo" />
|
|
</Link>
|
|
</div>
|
|
<button className="mobile-menu-close" onClick={onClose} aria-label="Close menu">
|
|
<i className="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<nav className="mobile-menu-nav">
|
|
<ul className="mobile-menu-list">{menuItems.map((item, index) => renderMenuItem(item, index))}</ul>
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Overlay */}
|
|
<div className={`mobile-menu-overlay ${isOpen ? "mobile-overlay-open" : ""}`} onClick={onClose}></div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default MobileMenu;
|