Skip to content

Commit e68e06c

Browse files
committed
fix(layout): portal version popover so sidebar overflow doesn't clip it
The brand block uses overflow-hidden to drive the sidebar collapse animation, which was clipping the absolutely-positioned version popover. Render it via createPortal anchored to the button rect with position: fixed so it floats above the sidebar.
1 parent f5f5f92 commit e68e06c

1 file changed

Lines changed: 24 additions & 3 deletions

File tree

frontend/src/components/Layout.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type CSSProperties, type PropsWithChildren, type ReactNode, useEffect, useRef, useState } from 'react'
2+
import { createPortal } from 'react-dom'
23
import { NavLink, useLocation } from 'react-router-dom'
34
import { LayoutDashboard, Users, Activity, Settings, Server, Languages, Globe, BookOpen, KeyRound, Image as ImageIcon, ShieldAlert, ExternalLink, ChevronLeft, Palette, Sun, Moon, LogOut, Radar } from 'lucide-react'
45
import { useTranslation } from 'react-i18next'
@@ -62,16 +63,26 @@ export default function Layout({ children }: PropsWithChildren) {
6263
})
6364
}
6465
const versionPopoverRef = useRef<HTMLDivElement | null>(null)
66+
const versionButtonRef = useRef<HTMLButtonElement | null>(null)
67+
const [versionPopoverPos, setVersionPopoverPos] = useState<{ top: number; left: number } | null>(null)
6568
const releaseURL = latestVersion
6669
? `https://github.com/james-6-23/codex2api/releases/tag/${encodeURIComponent(latestVersion)}`
6770
: undefined
6871

6972
useEffect(() => {
7073
if (!showVersionPopover) return
7174

75+
const updatePosition = () => {
76+
const rect = versionButtonRef.current?.getBoundingClientRect()
77+
if (!rect) return
78+
setVersionPopoverPos({ top: rect.bottom + 8, left: rect.left })
79+
}
80+
updatePosition()
81+
7282
const handlePointerDown = (event: PointerEvent) => {
7383
const target = event.target instanceof Node ? event.target : null
7484
if (target && versionPopoverRef.current?.contains(target)) return
85+
if (target && versionButtonRef.current?.contains(target)) return
7586
setShowVersionPopover(false)
7687
}
7788
const handleKeyDown = (event: KeyboardEvent) => {
@@ -80,9 +91,13 @@ export default function Layout({ children }: PropsWithChildren) {
8091

8192
document.addEventListener('pointerdown', handlePointerDown)
8293
document.addEventListener('keydown', handleKeyDown)
94+
window.addEventListener('resize', updatePosition)
95+
window.addEventListener('scroll', updatePosition, true)
8396
return () => {
8497
document.removeEventListener('pointerdown', handlePointerDown)
8598
document.removeEventListener('keydown', handleKeyDown)
99+
window.removeEventListener('resize', updatePosition)
100+
window.removeEventListener('scroll', updatePosition, true)
86101
}
87102
}, [showVersionPopover])
88103

@@ -232,6 +247,7 @@ export default function Layout({ children }: PropsWithChildren) {
232247
</h1>
233248
<div ref={versionPopoverRef} className="relative w-fit">
234249
<button
250+
ref={versionButtonRef}
235251
type="button"
236252
className="relative inline-flex cursor-pointer items-center rounded-md bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold text-primary ring-1 ring-primary/10 transition-colors hover:bg-primary/15"
237253
title={hasUpdate && latestVersion ? t('common.newVersionAvailable', { version: latestVersion }) : undefined}
@@ -243,8 +259,12 @@ export default function Layout({ children }: PropsWithChildren) {
243259
<span className="absolute -top-1.5 left-1/2 size-2.5 -translate-x-1/2 rounded-full bg-red-500 shadow-sm ring-2 ring-[hsl(var(--sidebar-background))] animate-pulse" />
244260
)}
245261
</button>
246-
{showVersionPopover && (
247-
<div className="absolute left-0 top-[calc(100%+8px)] z-50 w-[240px] rounded-lg border border-border bg-popover p-3 text-left shadow-xl">
262+
{showVersionPopover && versionPopoverPos && createPortal(
263+
<div
264+
ref={versionPopoverRef}
265+
style={{ position: 'fixed', top: versionPopoverPos.top, left: versionPopoverPos.left }}
266+
className="z-[100] w-[240px] rounded-lg border border-border bg-popover p-3 text-left shadow-xl"
267+
>
248268
<div className="text-[13px] font-semibold text-foreground">
249269
{latestVersion
250270
? hasUpdate
@@ -272,7 +292,8 @@ export default function Layout({ children }: PropsWithChildren) {
272292
<ExternalLink className="size-3.5" />
273293
</a>
274294
)}
275-
</div>
295+
</div>,
296+
document.body,
276297
)}
277298
</div>
278299
</div>

0 commit comments

Comments
 (0)