diff --git a/frontend/src/components/NotificationDropdown.tsx b/frontend/src/components/NotificationDropdown.tsx index 68178168..e37d3f85 100644 --- a/frontend/src/components/NotificationDropdown.tsx +++ b/frontend/src/components/NotificationDropdown.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useRef } from "react"; import { useRouter } from "next/navigation"; import { useStreamEvents } from "@/hooks/useStreamEvents"; import { formatAmount } from "@/utils/amount"; @@ -24,6 +24,7 @@ export const NotificationDropdown: React.FC = ({ publ const [isOpen, setIsOpen] = useState(false); const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); + const containerRef = useRef(null); const { events: streamEvents, connected } = useStreamEvents({ userPublicKeys: [publicKey], @@ -111,6 +112,53 @@ export const NotificationDropdown: React.FC = ({ publ }); }, [streamEvents, isOpen, formatEventMessage]); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsOpen(false); + return; + } + + if (event.key === "Tab" && containerRef.current && isOpen) { + const focusableElements = containerRef.current.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + if (focusableElements.length === 0) return; + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (event.shiftKey) { + if (document.activeElement === firstElement) { + event.preventDefault(); + lastElement?.focus(); + } + } else { + if (document.activeElement === lastElement) { + event.preventDefault(); + firstElement?.focus(); + } + } + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleKeyDown); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleKeyDown); + }; + }, [isOpen]); + const handleDropdownOpen = useCallback(() => { setIsOpen((prev) => !prev); @@ -121,7 +169,7 @@ export const NotificationDropdown: React.FC = ({ publ }, [isOpen]); return ( -
+