forked from UKSOURCE/hailearning.edu.vn
style(header): Implement responsive mobile menu and improve header layout
This commit is contained in:
112
app/components/layout/Header/MobileMenu.tsx
Normal file
112
app/components/layout/Header/MobileMenu.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
"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;
|
||||
Reference in New Issue
Block a user