11import { type CSSProperties , type PropsWithChildren , type ReactNode , useEffect , useRef , useState } from 'react'
2+ import { createPortal } from 'react-dom'
23import { NavLink , useLocation } from 'react-router-dom'
34import { LayoutDashboard , Users , Activity , Settings , Server , Languages , Globe , BookOpen , KeyRound , Image as ImageIcon , ShieldAlert , ExternalLink , ChevronLeft , Palette , Sun , Moon , LogOut , Radar } from 'lucide-react'
45import { 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