fix: portal sidebar dropdowns to escape backdrop-filter clipping

Bump @ciphera-net/ui to 0.2.14 which portals AppLauncher and UserMenu
dropdowns via createPortal when anchor="right". Apply the same fix to
NotificationCenter. This escapes the sidebar's backdrop-filter
containing block that was clipping all fixed-positioned dropdowns.
This commit is contained in:
Usman Baig
2026-03-19 00:35:51 +01:00
parent 3e67af5646
commit 1755dcb9dc
3 changed files with 139 additions and 126 deletions

View File

@@ -5,6 +5,7 @@
*/ */
import { useEffect, useState, useRef, useCallback } from 'react' import { useEffect, useState, useRef, useCallback } from 'react'
import { createPortal } from 'react-dom'
import Link from 'next/link' import Link from 'next/link'
import { listNotifications, markNotificationRead, markAllNotificationsRead, type Notification } from '@/lib/api/notifications' import { listNotifications, markNotificationRead, markAllNotificationsRead, type Notification } from '@/lib/api/notifications'
import { getAuthErrorMessage } from '@ciphera-net/ui' import { getAuthErrorMessage } from '@ciphera-net/ui'
@@ -53,6 +54,7 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const dropdownRef = useRef<HTMLDivElement>(null) const dropdownRef = useRef<HTMLDivElement>(null)
const panelRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
const [fixedPos, setFixedPos] = useState<{ left: number; top: number } | null>(null) const [fixedPos, setFixedPos] = useState<{ left: number; top: number } | null>(null)
@@ -107,7 +109,11 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau
useEffect(() => { useEffect(() => {
if (!open) return if (!open) return
function handleClickOutside(e: MouseEvent) { function handleClickOutside(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { const target = e.target as Node
if (
dropdownRef.current && !dropdownRef.current.contains(target) &&
(!panelRef.current || !panelRef.current.contains(target))
) {
setOpen(false) setOpen(false)
} }
} }
@@ -186,8 +192,10 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau
)} )}
</button> </button>
{open && ( {(() => {
const panel = open ? (
<div <div
ref={panelRef}
id="notification-dropdown" id="notification-dropdown"
role="dialog" role="dialog"
aria-label="Notifications" aria-label="Notifications"
@@ -309,7 +317,12 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau
</Link> </Link>
</div> </div>
</div> </div>
)} ) : null
return anchor === 'right' && panel && typeof document !== 'undefined'
? createPortal(panel, document.body)
: panel
})()}
</div> </div>
) )
} }

8
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "pulse-frontend", "name": "pulse-frontend",
"version": "0.15.0-alpha", "version": "0.15.0-alpha",
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.2.13", "@ciphera-net/ui": "^0.2.14",
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"@phosphor-icons/react": "^2.1.10", "@phosphor-icons/react": "^2.1.10",
"@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/browser": "^13.2.2",
@@ -1670,9 +1670,9 @@
} }
}, },
"node_modules/@ciphera-net/ui": { "node_modules/@ciphera-net/ui": {
"version": "0.2.13", "version": "0.2.14",
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.2.13/710acb1221e5906b8fd622ff6c77b469781c449d", "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.2.14/6502f8587e7e841c507c670c212f801e047a85a9",
"integrity": "sha512-pHo0DkkjAr7qlnPy1GBtHm3XFQlUC8b/FQM5PCu9JvW3pHg6znufyCP/8Wqbu6jOIt4C+c6CSKvMKzeYPKi42Q==", "integrity": "sha512-Jb2tG4DNXEwYMLdyQg+BwBpUKcMtmsAnkrtbenhQEiIngRpwgS7YyjtJVa0aDMPkFD4uq6nVMLoSRTMNgxekMg==",
"dependencies": { "dependencies": {
"@phosphor-icons/react": "^2.1.10", "@phosphor-icons/react": "^2.1.10",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",

View File

@@ -12,7 +12,7 @@
"test:watch": "vitest" "test:watch": "vitest"
}, },
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.2.13", "@ciphera-net/ui": "^0.2.14",
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"@phosphor-icons/react": "^2.1.10", "@phosphor-icons/react": "^2.1.10",
"@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/browser": "^13.2.2",