diff --git a/web/src/components/SessionActionMenu.tsx b/web/src/components/SessionActionMenu.tsx index 5d225602..9f86e5b1 100644 --- a/web/src/components/SessionActionMenu.tsx +++ b/web/src/components/SessionActionMenu.tsx @@ -5,8 +5,7 @@ import { useLayoutEffect, useRef, useState, - type CSSProperties, - type RefObject + type CSSProperties } from 'react' import { useTranslation } from '@/lib/use-translation' @@ -17,8 +16,7 @@ type SessionActionMenuProps = { onRename: () => void onArchive: () => void onDelete: () => void - anchorRef?: RefObject - align?: 'start' | 'end' + anchorPoint: { x: number; y: number } menuId?: string } @@ -101,8 +99,7 @@ export function SessionActionMenu(props: SessionActionMenuProps) { onRename, onArchive, onDelete, - anchorRef, - align = 'end', + anchorPoint, menuId } = props const menuRef = useRef(null) @@ -136,32 +133,19 @@ export function SessionActionMenu(props: SessionActionMenuProps) { const padding = 8 const gap = 8 - let top = (viewportHeight - menuRect.height) / 2 - let left = (viewportWidth - menuRect.width) / 2 - let transformOrigin = 'top center' + const spaceBelow = viewportHeight - anchorPoint.y + const spaceAbove = anchorPoint.y + const openAbove = spaceBelow < menuRect.height + gap && spaceAbove > spaceBelow - const anchorEl = anchorRef?.current - if (anchorEl) { - const anchorRect = anchorEl.getBoundingClientRect() - const spaceBelow = viewportHeight - anchorRect.bottom - const spaceAbove = anchorRect.top - const openAbove = spaceBelow < menuRect.height + gap && spaceAbove > spaceBelow - - top = openAbove ? anchorRect.top - menuRect.height - gap : anchorRect.bottom + gap - if (align === 'start') { - left = anchorRect.left - transformOrigin = openAbove ? 'bottom left' : 'top left' - } else { - left = anchorRect.right - menuRect.width - transformOrigin = openAbove ? 'bottom right' : 'top right' - } - } + let top = openAbove ? anchorPoint.y - menuRect.height - gap : anchorPoint.y + gap + let left = anchorPoint.x - menuRect.width / 2 + const transformOrigin = openAbove ? 'bottom center' : 'top center' top = Math.min(Math.max(top, padding), viewportHeight - menuRect.height - padding) left = Math.min(Math.max(left, padding), viewportWidth - menuRect.width - padding) setMenuPosition({ top, left, transformOrigin }) - }, [align, anchorRef]) + }, [anchorPoint]) useLayoutEffect(() => { if (!isOpen) return @@ -177,7 +161,6 @@ export function SessionActionMenu(props: SessionActionMenuProps) { const handlePointerDown = (event: PointerEvent) => { const target = event.target as Node if (menuRef.current?.contains(target)) return - if (anchorRef?.current?.contains(target)) return onClose() } @@ -202,7 +185,7 @@ export function SessionActionMenu(props: SessionActionMenuProps) { window.removeEventListener('resize', handleReflow) window.removeEventListener('scroll', handleReflow, true) } - }, [anchorRef, isOpen, onClose, updatePosition]) + }, [isOpen, onClose, updatePosition]) useEffect(() => { if (!isOpen) return diff --git a/web/src/components/SessionHeader.tsx b/web/src/components/SessionHeader.tsx index b5d58e9e..3728b267 100644 --- a/web/src/components/SessionHeader.tsx +++ b/web/src/components/SessionHeader.tsx @@ -72,6 +72,7 @@ export function SessionHeader(props: { const worktreeBranch = session.metadata?.worktree?.branch const [menuOpen, setMenuOpen] = useState(false) + const [menuAnchorPoint, setMenuAnchorPoint] = useState<{ x: number; y: number }>({ x: 0, y: 0 }) const menuId = useId() const menuAnchorRef = useRef(null) const [renameOpen, setRenameOpen] = useState(false) @@ -89,6 +90,14 @@ export function SessionHeader(props: { onSessionDeleted?.() } + const handleMenuToggle = () => { + if (!menuOpen && menuAnchorRef.current) { + const rect = menuAnchorRef.current.getBoundingClientRect() + setMenuAnchorPoint({ x: rect.right, y: rect.bottom }) + } + setMenuOpen((open) => !open) + } + // In Telegram, don't render header (Telegram provides its own) if (isTelegramApp()) { return null @@ -151,7 +160,8 @@ export function SessionHeader(props: {