From bc4ed3b89f6954e18a63a724f35686b318d05a10 Mon Sep 17 00:00:00 2001 From: zipg Date: Thu, 10 Sep 2026 00:42:36 +0800 Subject: [PATCH] fix(desktop): restore tab bar after layout changes --- .../src/components/layout/EditorGroup.vue | 5 ++++- .../layout/__tests__/AppTabBar.portal.spec.ts | 22 +++++++++++++++++++ .../layout/__tests__/AppTabBar.spec.ts | 1 + .../EditorGroupTabBar.groups.spec.ts | 3 ++- .../src/components/layout/appTabBar.css | 8 ++++++- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/components/layout/EditorGroup.vue b/apps/desktop/src/components/layout/EditorGroup.vue index 60d187cba8..cc39ff6e7b 100644 --- a/apps/desktop/src/components/layout/EditorGroup.vue +++ b/apps/desktop/src/components/layout/EditorGroup.vue @@ -92,7 +92,10 @@ const queryStore = useQueryStore(); const settingsStore = useSettingsStore(); const toolbar = inject(EDITOR_TOOLBAR_ACTIONS, createNoopEditorToolbarActions()); const tabBarPortal = inject(GROUP_TAB_BAR_PORTAL, null); -const tabBarTarget = computed(() => tabBarPortal?.targets.get(props.groupId)); +const tabBarTarget = computed(() => { + if (!tabBarPortal?.active.value) return undefined; + return tabBarPortal.targets.get(props.groupId); +}); const groupTabs = computed(() => { const byId = new Map(queryStore.tabs.map((tab) => [tab.id, tab])); return props.tabIds.map((id) => byId.get(id)).filter((tab): tab is QueryTab => !!tab); diff --git a/apps/desktop/src/components/layout/__tests__/AppTabBar.portal.spec.ts b/apps/desktop/src/components/layout/__tests__/AppTabBar.portal.spec.ts index f9985c27f2..59aec69174 100644 --- a/apps/desktop/src/components/layout/__tests__/AppTabBar.portal.spec.ts +++ b/apps/desktop/src/components/layout/__tests__/AppTabBar.portal.spec.ts @@ -328,6 +328,28 @@ describe("AppTabBar group navigation portal", () => { expect(warnings).toEqual([]); }); + it("moves a horizontal bar to the workspace rail after settings releases its portal", async () => { + const { host, settings, actions, warnings } = mountNavigation("top"); + await settle(); + const bar = element(host, ".editor-group[data-group-id='main'] [data-main-tab-bar]"); + + actions.activateSettingsPage(); + await settle(); + expect(element(host, "[data-special-page-tab-target='main']").contains(bar)).toBe(true); + + actions.closeSettingsPage(); + await settle(); + expect(element(host, ".editor-group[data-group-id='main']").contains(bar)).toBe(true); + + settings.editorSettings.tabGroupMode = "database-type"; + settings.editorSettings.tabPlacement = "left"; + await settle(); + + expect(element(host, "[data-workspace-tab-target='main']").contains(bar)).toBe(true); + expect(element(host, "[data-special-page-workspace]").style.display).toBe("none"); + expect(warnings).toEqual([]); + }); + it.each(["left", "right"] as const)("synchronizes %s rail width and collapse with the persistent bar", async (placement) => { const { host, navigation, warnings } = mountNavigation(placement); await settle(); diff --git a/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts b/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts index 6a043a83b3..9546a1cfef 100644 --- a/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts +++ b/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts @@ -14,6 +14,7 @@ describe("AppTabBar shared group navigation", () => { expect(tabBarSource).not.toContain("data-return-tab"); expect(tabBarSource).not.toContain("createRenameDuplicateTabItems"); expect(groupSource).toContain(" { expect(source).toContain(':data-group-mode="settingsStore.editorSettings.tabGroupMode"'); expect(sharedStyles).toContain(".app-tab-bar:not(.vertical-tab-layout) .tab-group-header"); expect(sharedStyles).toMatch(/\.app-tab-bar\.classic-tab-layout:not\(\.vertical-tab-layout\) \.tab-group-header-content\s*\{[^}]*height:\s*100%;[^}]*border-radius:\s*0;/s); + expect(sharedStyles).toMatch(/\.app-tab-bar\.classic-tab-layout:not\(\.vertical-tab-layout\):not\(:has\(\.wrap-mode\)\)\[data-group-mode="none"\] \.app-tab-pill\s*\{[^}]*border-right-width:\s*0\.5px;/s); expect(sharedStyles).toMatch(/\.tab-overflow-control\s*\{[^}]*width:\s*34px;[^}]*flex:\s*0 0 34px;/s); expect(sharedStyles).toContain("padding-inline-end: 2.125rem;"); expect(sharedStyles).toContain("inset-inline-end: 2.125rem;"); - expect(sharedStyles).toMatch(/\.app-tab-bar:not\(\.vertical-tab-layout\)\[data-placement="top"\] \.tab-overflow-control::before\s*\{[^}]*inset-inline:\s*-0\.25rem 0;/s); + expect(sharedStyles).toMatch(/\.app-tab-bar:not\(\.vertical-tab-layout\)\[data-placement="top"\] \.tab-overflow-control::before\s*\{[^}]*inset-inline:\s*-0\.25rem 0;[^}]*background:\s*transparent;/s); expect(sharedStyles).toMatch(/\.app-tab-scroll\.wrap-mode\.classic-wrap \.tab-group-header,[\s\S]*?\.tab-group-header-content\s*\{[^}]*height:\s*2rem !important;/s); expect(sharedStyles).toMatch(/\.app-tab-scroll\.wrap-mode\.classic-wrap \.tab-group-header:not\(\.tab-group-header--collapsed\)::after\s*\{[^}]*right:\s*-1px;/s); expect(sharedStyles).toContain(".app-tab-bar:not(.vertical-tab-layout) .tab-group-header::after"); diff --git a/apps/desktop/src/components/layout/appTabBar.css b/apps/desktop/src/components/layout/appTabBar.css index bae702d027..8af65eeb3e 100644 --- a/apps/desktop/src/components/layout/appTabBar.css +++ b/apps/desktop/src/components/layout/appTabBar.css @@ -213,7 +213,7 @@ z-index: -1; inset-block: 0; inset-inline: -0.25rem 0; - background: color-mix(in srgb, var(--background) 48%, transparent); + background: transparent; pointer-events: none; } @@ -587,6 +587,12 @@ border-radius: 0; } +/* A one-pixel CSS border becomes visually heavy on scaled displays. Keep the + * classic ungrouped single-row separators as the thinnest available rule. */ +.app-tab-bar.classic-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode))[data-group-mode="none"] .app-tab-pill { + border-right-width: 0.5px; +} + .app-tab-bar:not(.vertical-tab-layout) .tab-group-header:hover .tab-group-header-content, .app-tab-bar:not(.vertical-tab-layout) .tab-group-header--active .tab-group-header-content { background: color-mix(in srgb, var(--tab-group-color) 18%, transparent);