127 lines
6.5 KiB
TypeScript
127 lines
6.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
|
|
interface NavigationItem {
|
|
key: string;
|
|
label: string;
|
|
href: string;
|
|
current: boolean;
|
|
dropdown?: Array<{ label: string; href: string }>;
|
|
}
|
|
|
|
interface MobileMenuProps {
|
|
isOpen: boolean;
|
|
items: NavigationItem[];
|
|
onClose: () => void;
|
|
currentLang: string;
|
|
}
|
|
|
|
export function MobileMenu({ isOpen, items, onClose, currentLang }: MobileMenuProps) {
|
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
|
|
|
const toggleExpanded = (key: string) => {
|
|
const newExpanded = new Set(expandedItems);
|
|
if (newExpanded.has(key)) {
|
|
newExpanded.delete(key);
|
|
} else {
|
|
newExpanded.add(key);
|
|
}
|
|
setExpandedItems(newExpanded);
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="lg:hidden fixed inset-0 z-50 bg-black bg-opacity-50" data-mobile-menu>
|
|
<div className="fixed inset-y-0 right-0 max-w-xs w-full bg-gradient-to-b from-amber-900 to-yellow-800 shadow-xl transform transition-transform duration-300 ease-in-out">
|
|
<div className="flex flex-col h-full">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-4 border-b border-amber-700">
|
|
<h2 className="text-lg font-semibold text-yellow-300">
|
|
{currentLang === 'zh' ? '菜单' : 'Menu'}
|
|
</h2>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 rounded-md text-yellow-100 hover:text-yellow-300 hover:bg-amber-800 focus:outline-none focus:ring-2 focus:ring-yellow-400 transition-colors duration-200"
|
|
aria-label="Close menu"
|
|
>
|
|
<ChevronRight className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Navigation Items */}
|
|
<div className="flex-1 overflow-y-auto py-4">
|
|
<nav className="space-y-1 px-4" role="navigation">
|
|
{items.map((item) => (
|
|
<div key={item.key}>
|
|
{item.dropdown ? (
|
|
<div>
|
|
<button
|
|
onClick={() => toggleExpanded(item.key)}
|
|
className={`w-full flex items-center justify-between px-4 py-3 text-left text-sm font-medium rounded-md transition-colors duration-200 min-h-[44px] ${
|
|
item.current
|
|
? 'bg-amber-800 text-yellow-300'
|
|
: 'text-yellow-100 hover:text-yellow-300 hover:bg-amber-800/50'
|
|
}`}
|
|
aria-expanded={expandedItems.has(item.key)}
|
|
aria-controls={`mobile-submenu-${item.key}`}
|
|
>
|
|
<span>{item.label}</span>
|
|
<ChevronDown
|
|
className={`w-4 h-4 transition-transform duration-200 ${
|
|
expandedItems.has(item.key)
|
|
? 'rotate-180'
|
|
: ''
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
{expandedItems.has(item.key) && (
|
|
<div
|
|
id={`mobile-submenu-${item.key}`}
|
|
className="mt-1 space-y-1 pl-4"
|
|
>
|
|
{item.dropdown.map((subItem, index) => (
|
|
<a
|
|
key={index}
|
|
href={subItem.href}
|
|
className="block px-4 py-3 text-sm text-yellow-200 hover:text-yellow-300 hover:bg-amber-800/30 rounded-md transition-colors duration-200 min-h-[44px] flex items-center"
|
|
onClick={onClose}
|
|
>
|
|
{subItem.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<a
|
|
href={item.href}
|
|
className={`block px-4 py-3 text-sm font-medium rounded-md transition-colors duration-200 min-h-[44px] flex items-center ${
|
|
item.current
|
|
? 'bg-amber-800 text-yellow-300'
|
|
: 'text-yellow-100 hover:text-yellow-300 hover:bg-amber-800/50'
|
|
}`}
|
|
aria-current={item.current ? 'page' : undefined}
|
|
onClick={onClose}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
)}
|
|
</div>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="p-4 border-t border-amber-700">
|
|
<p className="text-xs text-yellow-200 text-center">© 2024 CloudPro</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|