diff --git a/packages/studio/src/components/ui/Menu.test.tsx b/packages/studio/src/components/ui/Menu.test.tsx
index ef75f8b558..7c042264d7 100644
--- a/packages/studio/src/components/ui/Menu.test.tsx
+++ b/packages/studio/src/components/ui/Menu.test.tsx
@@ -298,6 +298,27 @@ describe("Popover", () => {
expect(document.activeElement).toBe(input);
expect(document.querySelector('[role="menu"]')).toBeNull();
});
+
+ it("points at its trigger only when asked", async () => {
+ const renderOpen = (arrow: boolean) => {
+ render(
+ Speed} arrow={arrow} defaultOpen>
+ 1x
+ ,
+ );
+ };
+ renderOpen(false);
+ await settle();
+ expect(one("[role=dialog]", "popup").querySelector("svg")).toBeNull();
+ act(() => mounted?.root.unmount());
+ mounted?.host.remove();
+ mounted = null;
+
+ renderOpen(true);
+ await settle();
+ const arrow = one("[role=dialog] svg", "arrow").parentElement!;
+ expect(arrow.getAttribute("data-side")).toBe("bottom");
+ });
});
describe("hotkey classification", () => {
diff --git a/packages/studio/src/components/ui/Popover.tsx b/packages/studio/src/components/ui/Popover.tsx
index 43409e4e26..3a070883ca 100644
--- a/packages/studio/src/components/ui/Popover.tsx
+++ b/packages/studio/src/components/ui/Popover.tsx
@@ -14,6 +14,15 @@ type PortalContainer = ComponentPropsWithoutRef["cont
const SIDE_OFFSET = 6;
const VIEWPORT_MARGIN = 8;
+/** The arrow's 14x8 box overlaps the popup's border by 1px, so its fill hides the border where it meets the popup. */
+const ARROW_PX = 7;
+const ARROW_CORNER_CLEARANCE = 10;
+const arrowPlace = cn(
+ "data-[side=bottom]:-top-[7px] data-[side=top]:-bottom-[7px] data-[side=top]:rotate-180",
+ "data-[side=left]:-right-[10px] data-[side=left]:rotate-90",
+ "data-[side=right]:-left-[10px] data-[side=right]:-rotate-90",
+);
+
interface PopoverProps extends Omit, "children"> {
/** A single element. It becomes the trigger; no wrapper is added around it. */
trigger: ReactElement;
@@ -22,6 +31,8 @@ interface PopoverProps extends Omit
+ {arrow && (
+
+
+
+ )}
{children}