From 994b1cf3b776e5f4cca803bc41bf16d6fe201204 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 5 Jun 2026 08:57:00 +0000 Subject: [PATCH] feat: make notification center mobile friendly - Use useMobileViewport to detect mobile and position dropdown centered with left/right margins instead of right-aligned, which caused overflow on small screens. - Add a semi-transparent backdrop overlay on mobile so tapping outside the dropdown naturally closes it. - Update mobile CSS: notification-dropdown fills screen width with 0.75rem margins, max-height capped at 70vh for reachability. - Remove the 360px max-width cap on mobile so the dropdown uses available screen space properly. Quality gates: tsc --noEmit (pass), build (pass) --- .../notification/notification-center.tsx | 125 ++++++++++-------- apps/web/src/styles.css | 15 ++- 2 files changed, 85 insertions(+), 55 deletions(-) diff --git a/apps/web/src/components/features/notification/notification-center.tsx b/apps/web/src/components/features/notification/notification-center.tsx index a4ffedb..123eb8c 100644 --- a/apps/web/src/components/features/notification/notification-center.tsx +++ b/apps/web/src/components/features/notification/notification-center.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState, useCallback } from "react"; import { createPortal } from "react-dom"; import { useNotifications } from "../../../hooks/use-notifications"; +import { useMobileViewport } from "../../../hooks/use-mobile-viewport"; import { NotificationItem } from "./notification-item"; import { Icon } from "../../icon"; @@ -23,6 +24,7 @@ export function NotificationCenter({ setIsDropdownOpen, } = useNotifications(); + const isMobile = useMobileViewport(); const bellRef = useRef(null); const dropdownRef = useRef(null); const [dropdownStyle, setDropdownStyle] = useState({}); @@ -30,11 +32,19 @@ export function NotificationCenter({ const updatePosition = useCallback(() => { if (!bellRef.current) return; const rect = bellRef.current.getBoundingClientRect(); - setDropdownStyle({ - top: rect.bottom + 6, - right: window.innerWidth - rect.right, - }); - }, []); + if (isMobile) { + setDropdownStyle({ + top: rect.bottom + 6, + left: "1rem", + right: "1rem", + }); + } else { + setDropdownStyle({ + top: rect.bottom + 6, + right: window.innerWidth - rect.right, + }); + } + }, [isMobile]); useEffect(() => { if (!isDropdownOpen) return; @@ -104,55 +114,64 @@ export function NotificationCenter({ {isDropdownOpen && createPortal( -
-
- Notifications -
- -
    - {notifications.length === 0 ? ( -
  • No notifications
  • - ) : ( - notifications.map((n) => ( - - )) - )} -
- - {notifications.length > 0 && ( -
- - -
+ <> + {isMobile && ( +
setIsDropdownOpen(false)} + aria-hidden="true" + /> )} -
, +
+
+ Notifications +
+ +
    + {notifications.length === 0 ? ( +
  • No notifications
  • + ) : ( + notifications.map((n) => ( + + )) + )} +
+ + {notifications.length > 0 && ( +
+ + +
+ )} +
+ , document.body, )}
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 7a0dc25..288b5a9 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -4838,9 +4838,20 @@ a:active, } @media (max-width: 767px) { + .notification-dropdown-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.35); + z-index: 9998; + } + .notification-dropdown { - width: calc(100vw - 2rem); - max-width: 360px; + width: auto; + left: 0.75rem; + right: 0.75rem; + max-width: none; + border-radius: 12px; + max-height: 70vh; } }