+ const button = (
+
+ );
+ const titles = (
+
{menus.map((m, i) => (
+ );
+ if (!merged) return showTitles ? titles : button;
+ return (
+
+
+ {menus.map((m) => (
+
+ {label(m.id, m.label)}
+
+ ))}
+
+
{showTitles ? titles : button}
+ {!showTitles && expanded && (
+
+
)}
diff --git a/src/workbench/TitleBar.module.css b/src/workbench/TitleBar.module.css
index da2d54ded..f4dde6046 100644
--- a/src/workbench/TitleBar.module.css
+++ b/src/workbench/TitleBar.module.css
@@ -125,7 +125,32 @@
outline: 2px solid var(--focus-ring);
outline-offset: -2px;
}
-/* The expanded main menu: File Edit View … in place of the project widgets. */
+/* Preserve the menu's natural width while allowing its slot to yield to toolbar controls. */
+.mainMenuSlot {
+ position: relative;
+ flex: 0 1 auto;
+ min-width: 34px;
+ height: 30px;
+ overflow: hidden;
+}
+.mainMenuSlot ~ .item,
+.mainMenuSlot ~ .tools {
+ flex-shrink: 0;
+}
+.menuMeasure {
+ width: max-content;
+ visibility: hidden;
+ pointer-events: none;
+}
+.menuPresentation {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ display: flex;
+ align-items: center;
+}
+/* The main menu titles. */
.menuBar {
flex: none;
display: flex;
diff --git a/src/workbench/TitleBar.test.tsx b/src/workbench/TitleBar.test.tsx
index eb99647a4..580e43e06 100644
--- a/src/workbench/TitleBar.test.tsx
+++ b/src/workbench/TitleBar.test.tsx
@@ -1,8 +1,10 @@
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("./WindowControls", () => ({ WindowControls: () => null }));
-import { fireEvent, render, screen } from "@testing-library/react";
+import { act, fireEvent, render, screen } from "@testing-library/react";
import { registerActions } from "@/platform/actions";
+import { commands, type Settings } from "@/platform/ipc";
+import { useProject } from "@/platform/project";
import { usePlatform } from "@/platform/platformInfo";
import { useThemeOverride } from "./theme";
import { customizeToolbarItems, gearMenuItems, TitleBar } from "./TitleBar";
@@ -103,3 +105,32 @@ describe("title bar in Distraction Free Mode (bare)", () => {
}
});
});
+
+describe("merged title bar menu", () => {
+ it.each(["windows", "linux", "macos"])(
+ "%s keeps toolbar controls available and uses the menu only when frameless",
+ async (os) => {
+ const savedPlatform = usePlatform.getState();
+ const savedProject = useProject.getState();
+ const model = vi
+ .spyOn(commands, "menuModel")
+ .mockResolvedValue([{ kind: "submenu", id: "file", label: "File", children: [] }]);
+ usePlatform.setState({ os, framelessTitleBar: os !== "macos", overlayTitleBar: os === "macos" });
+ useProject.setState({ settings: { appearance: { mainMenuStyle: "merged" } } as Settings });
+ try {
+ render(
);
+ await act(async () => {});
+ expect(!!screen.queryByRole("menubar")).toBe(os !== "macos");
+ expect(screen.queryByRole("button", { name: "Main Menu" })).toBeNull();
+ if (os !== "macos") fireEvent.click(screen.getByRole("menuitem", { name: "File" }));
+ expect(screen.getByRole("button", { name: "OpenIDEA" })).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: "IDE and Project Settings" }));
+ expect(await screen.findByRole("menu", { name: "IDE and Project Settings" })).toBeInTheDocument();
+ } finally {
+ usePlatform.setState(savedPlatform);
+ useProject.setState(savedProject);
+ model.mockRestore();
+ }
+ },
+ );
+});