From 1755dcb9dc66e363b36617bdc5167be1e3f3a813 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Thu, 19 Mar 2026 00:35:51 +0100 Subject: [PATCH] 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. --- .../notifications/NotificationCenter.tsx | 255 +++++++++--------- package-lock.json | 8 +- package.json | 2 +- 3 files changed, 139 insertions(+), 126 deletions(-) diff --git a/components/notifications/NotificationCenter.tsx b/components/notifications/NotificationCenter.tsx index 945bf79..bda781c 100644 --- a/components/notifications/NotificationCenter.tsx +++ b/components/notifications/NotificationCenter.tsx @@ -5,6 +5,7 @@ */ import { useEffect, useState, useRef, useCallback } from 'react' +import { createPortal } from 'react-dom' import Link from 'next/link' import { listNotifications, markNotificationRead, markAllNotificationsRead, type Notification } from '@/lib/api/notifications' import { getAuthErrorMessage } from '@ciphera-net/ui' @@ -53,6 +54,7 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const dropdownRef = useRef(null) + const panelRef = useRef(null) const buttonRef = useRef(null) const [fixedPos, setFixedPos] = useState<{ left: number; top: number } | null>(null) @@ -107,7 +109,11 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau useEffect(() => { if (!open) return 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) } } @@ -186,130 +192,137 @@ export default function NotificationCenter({ anchor = 'bottom', variant = 'defau )} - {open && ( - + ) : null -
- {loading && ( -
- {Array.from({ length: 4 }).map((_, i) => ( -
- -
- - -
-
- ))} -
- )} - {error && ( -
{error}
- )} - {!loading && !error && (notifications?.length ?? 0) === 0 && ( -
- No notifications yet -
- )} - {!loading && !error && (notifications?.length ?? 0) > 0 && ( -
    - {(notifications ?? []).map((n) => ( -
  • - {n.link_url ? ( - handleNotificationClick(n)} - className={`block px-4 py-3 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors ${!n.read ? 'bg-brand-orange/5 dark:bg-brand-orange/10' : ''}`} - > -
    - {getTypeIcon(n.type)} -
    -

    - {n.title} -

    - {n.body && ( -

    - {n.body} -

    - )} -

    - {formatTimeAgo(n.created_at)} -

    -
    -
    - - ) : ( - - )} -
  • - ))} -
- )} -
- -
- setOpen(false)} - className="text-sm text-brand-orange hover:underline" - > - View all - - setOpen(false)} - className="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 hover:text-brand-orange dark:hover:text-brand-orange transition-colors" - > -
- - )} + return anchor === 'right' && panel && typeof document !== 'undefined' + ? createPortal(panel, document.body) + : panel + })()} ) } diff --git a/package-lock.json b/package-lock.json index 30681a7..104a248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "pulse-frontend", "version": "0.15.0-alpha", "dependencies": { - "@ciphera-net/ui": "^0.2.13", + "@ciphera-net/ui": "^0.2.14", "@ducanh2912/next-pwa": "^10.2.9", "@phosphor-icons/react": "^2.1.10", "@simplewebauthn/browser": "^13.2.2", @@ -1670,9 +1670,9 @@ } }, "node_modules/@ciphera-net/ui": { - "version": "0.2.13", - "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.2.13/710acb1221e5906b8fd622ff6c77b469781c449d", - "integrity": "sha512-pHo0DkkjAr7qlnPy1GBtHm3XFQlUC8b/FQM5PCu9JvW3pHg6znufyCP/8Wqbu6jOIt4C+c6CSKvMKzeYPKi42Q==", + "version": "0.2.14", + "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.2.14/6502f8587e7e841c507c670c212f801e047a85a9", + "integrity": "sha512-Jb2tG4DNXEwYMLdyQg+BwBpUKcMtmsAnkrtbenhQEiIngRpwgS7YyjtJVa0aDMPkFD4uq6nVMLoSRTMNgxekMg==", "dependencies": { "@phosphor-icons/react": "^2.1.10", "class-variance-authority": "^0.7.1", diff --git a/package.json b/package.json index 7f9eb1a..5f197de 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test:watch": "vitest" }, "dependencies": { - "@ciphera-net/ui": "^0.2.13", + "@ciphera-net/ui": "^0.2.14", "@ducanh2912/next-pwa": "^10.2.9", "@phosphor-icons/react": "^2.1.10", "@simplewebauthn/browser": "^13.2.2",