diff --git a/src/features/workbench/components/workbench-top-bar-menus.tsx b/src/features/workbench/components/workbench-top-bar-menus.tsx index 85b0b81..128991e 100644 --- a/src/features/workbench/components/workbench-top-bar-menus.tsx +++ b/src/features/workbench/components/workbench-top-bar-menus.tsx @@ -16,7 +16,7 @@ import { UserRound, type LucideIcon } from "lucide-react"; -import { useRef, useState, type ReactNode } from "react"; +import { useEffect, useRef, useState, type ReactNode } from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { ProjectSwitchDialog } from "./project-switch-dialog"; import { @@ -143,13 +143,28 @@ export function AlertMenu({ onAnalyzeAlertsWithAgent: () => void | Promise; }) { const reduceMotion = useReducedMotion(); - const keyboardInteraction = useRef(false); - const skipRemovalMotion = reduceMotion === true || keyboardInteraction.current; + const [keyboardInteraction, setKeyboardInteraction] = useState(false); + const skipRemovalMotion = reduceMotion === true || keyboardInteraction; const hasAlerts = alerts.length > 0; const hasCriticalAlerts = alerts.some((alert) => alert.severity !== "warning"); const alertSummaryTone = hasCriticalAlerts ? "danger" : hasAlerts ? "warning" : "neutral"; const latestAlertTime = alerts[0]?.time; + useEffect(() => { + // Radix can preserve :focus-visible through pointer-driven focus transfers. + // Track actual input, including Tab from controls outside this portaled menu. + const handleKeyboard = () => setKeyboardInteraction(true); + const handlePointer = () => setKeyboardInteraction(false); + document.addEventListener("keydown", handleKeyboard, true); + document.addEventListener("pointerdown", handlePointer, true); + document.addEventListener("pointermove", handlePointer, true); + return () => { + document.removeEventListener("keydown", handleKeyboard, true); + document.removeEventListener("pointerdown", handlePointer, true); + document.removeEventListener("pointermove", handlePointer, true); + }; + }, []); + return ( @@ -158,6 +173,7 @@ export function AlertMenu({ className={cn( "group relative px-2", headerControlButtonClassName, + !keyboardInteraction && "focus-visible:ring-0 focus-visible:ring-offset-0", hasAlerts && !compact && (hasCriticalAlerts ? "status-tone-danger" : "status-tone-warning"), hasAlerts && !compact && @@ -197,10 +213,7 @@ export function AlertMenu({ )} - { keyboardInteraction.current = true; }} - onPointerDownCapture={() => { keyboardInteraction.current = false; }} - > + onSelectAlert(alert)} >