fix: show alert focus rings only for keyboard input
Radix focus transfers can preserve :focus-visible during mouse hover and when returning focus to the alert trigger. Track actual input modality so pointer interactions keep the thin hover border and keyboard navigation retains its focus rings. Cover desktop and narrow layouts, pointer selection, keyboard return focus, and switching input modes with browser regressions.
This commit is contained in:
@@ -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<void>;
|
||||
}) {
|
||||
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 (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={onOpenChange}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -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({
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" sideOffset={8} className={cn("w-[min(392px,calc(100vw-24px))]", menuSurfaceClassName)}
|
||||
onKeyDownCapture={() => { keyboardInteraction.current = true; }}
|
||||
onPointerDownCapture={() => { keyboardInteraction.current = false; }}
|
||||
>
|
||||
<DropdownMenuContent align="end" sideOffset={8} className={cn("w-[min(392px,calc(100vw-24px))]", menuSurfaceClassName)}>
|
||||
<AlertQueueSummary
|
||||
count={alerts.length}
|
||||
latestAlertTime={latestAlertTime}
|
||||
@@ -235,7 +248,10 @@ export function AlertMenu({
|
||||
>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="absolute inset-0 z-0 h-full w-full cursor-pointer rounded-[inherit] border-0 bg-transparent p-0 hover:bg-transparent focus:bg-transparent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--action-focus-ring)]"
|
||||
className={cn(
|
||||
"absolute inset-0 z-0 h-full w-full cursor-pointer rounded-[inherit] border-0 bg-transparent p-0 hover:bg-transparent focus:bg-transparent focus-visible:ring-inset focus-visible:ring-[var(--action-focus-ring)]",
|
||||
keyboardInteraction && "focus-visible:ring-2"
|
||||
)}
|
||||
onSelect={() => onSelectAlert(alert)}
|
||||
>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user