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}