CloudPro/components/Navigation/DropdownMenu.tsx
2025-09-16 16:15:57 +08:00

80 lines
2.7 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
interface DropdownItem {
label: string;
href: string;
}
interface DropdownMenuProps {
items: DropdownItem[];
onClose: () => void;
}
export function DropdownMenu({ items, onClose }: DropdownMenuProps) {
const menuRef = useRef<HTMLDivElement>(null);
// Handle keyboard navigation within dropdown
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (!menuRef.current) return;
const focusableElements = menuRef.current.querySelectorAll('a');
const currentIndex = Array.from(focusableElements).findIndex(
(el) => el === document.activeElement,
);
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
const nextIndex =
currentIndex < focusableElements.length - 1 ? currentIndex + 1 : 0;
focusableElements[nextIndex]?.focus();
break;
case 'ArrowUp':
event.preventDefault();
const prevIndex =
currentIndex > 0 ? currentIndex - 1 : focusableElements.length - 1;
focusableElements[prevIndex]?.focus();
break;
case 'Escape':
event.preventDefault();
onClose();
break;
case 'Tab':
if (event.shiftKey && currentIndex === 0) {
onClose();
} else if (!event.shiftKey && currentIndex === focusableElements.length - 1) {
onClose();
}
break;
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
return (
<div
ref={menuRef}
className="absolute top-full left-0 mt-1 w-64 bg-white rounded-lg shadow-xl border border-amber-200 py-2 z-50 animate-in fade-in-0 zoom-in-95 duration-200"
role="menu"
aria-orientation="vertical"
>
{items.map((item, index) => (
<a
key={index}
href={item.href}
className="block px-4 py-3 text-sm text-amber-900 hover:bg-amber-50 hover:text-amber-800 transition-colors duration-150 focus:outline-none focus:bg-amber-100 focus:text-amber-800 min-h-[44px] flex items-center"
role="menuitem"
tabIndex={-1}
>
{item.label}
</a>
))}
</div>
);
}