Skip to content

Commit 4993929

Browse files
committed
fix(webapp): make the admin debug panel a hover popover so copy buttons work
Radix tooltips enforce one-open-at-a-time globally (opening any tooltip dispatches a document event that closes the others), so hovering a copy button — which opens its own "Copy" tooltip — dismissed the debug panel. Render the panel as a hover-triggered Popover instead. A popover holds interactive content without closing, so copy-on-hover now works and the panel stays open while you interact with it.
1 parent 8322189 commit 4993929

1 file changed

Lines changed: 45 additions & 16 deletions

File tree

apps/webapp/app/components/admin/debugTooltip.tsx

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
2+
import { useEffect, useRef, useState } from "react";
23
import { CopyableText } from "~/components/primitives/CopyableText";
4+
import { Popover, PopoverContent, PopoverTrigger } from "~/components/primitives/Popover";
35
import * as Property from "~/components/primitives/PropertyTable";
4-
import {
5-
Tooltip,
6-
TooltipContent,
7-
TooltipProvider,
8-
TooltipTrigger,
9-
} from "~/components/primitives/Tooltip";
106
import { useOptionalEnvironment } from "~/hooks/useEnvironment";
117
import { useIsImpersonating, useOptionalOrganization } from "~/hooks/useOrganizations";
128
import { useOptionalProject } from "~/hooks/useProject";
@@ -15,22 +11,55 @@ import { useHasAdminAccess, useUser } from "~/hooks/useUser";
1511
export function AdminDebugTooltip({ children }: { children?: React.ReactNode }) {
1612
const hasAdminAccess = useHasAdminAccess();
1713
const isImpersonating = useIsImpersonating();
14+
const [open, setOpen] = useState(false);
15+
const closeTimeout = useRef<ReturnType<typeof setTimeout>>();
16+
17+
// Clean up any pending close timer on unmount.
18+
useEffect(() => () => clearTimeout(closeTimeout.current), []);
1819

1920
if (!hasAdminAccess && !isImpersonating) {
2021
return null;
2122
}
2223

24+
const openNow = () => {
25+
clearTimeout(closeTimeout.current);
26+
setOpen(true);
27+
};
28+
29+
// Delay closing so moving the pointer across the gap into the popover — or onto a
30+
// copy button that opens its own tooltip — doesn't dismiss it. This is a Popover
31+
// rather than a Tooltip on purpose: Radix tooltips only allow one open at a time, so
32+
// a copy button's tooltip would otherwise close this panel.
33+
const closeSoon = () => {
34+
clearTimeout(closeTimeout.current);
35+
closeTimeout.current = setTimeout(() => setOpen(false), 150);
36+
};
37+
2338
return (
24-
<TooltipProvider>
25-
<Tooltip>
26-
<TooltipTrigger>
27-
<ShieldCheckIcon className="size-5" />
28-
</TooltipTrigger>
29-
<TooltipContent className="max-h-[90vh] overflow-y-auto pr-8">
30-
<Content>{children}</Content>
31-
</TooltipContent>
32-
</Tooltip>
33-
</TooltipProvider>
39+
<Popover open={open} onOpenChange={setOpen}>
40+
<PopoverTrigger
41+
type="button"
42+
aria-label="Admin debug info"
43+
className="flex items-center outline-hidden focus-custom"
44+
onClick={(e) => e.preventDefault()}
45+
onMouseEnter={openNow}
46+
onMouseLeave={closeSoon}
47+
onFocus={openNow}
48+
onBlur={closeSoon}
49+
>
50+
<ShieldCheckIcon className="size-5" />
51+
</PopoverTrigger>
52+
<PopoverContent
53+
align="end"
54+
className="max-h-[90vh] overflow-y-auto px-3 py-2 pr-8 text-sm"
55+
onOpenAutoFocus={(e) => e.preventDefault()}
56+
onCloseAutoFocus={(e) => e.preventDefault()}
57+
onMouseEnter={openNow}
58+
onMouseLeave={closeSoon}
59+
>
60+
<Content>{children}</Content>
61+
</PopoverContent>
62+
</Popover>
3463
);
3564
}
3665

0 commit comments

Comments
 (0)