From c301e292d11ad16296c48d45a781a33d48a809a9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sat, 1 Aug 2026 13:36:04 -0500 Subject: [PATCH] fix(menubar): add Cycle Theme action under Help menu (closes #453) Theme is cycleable from TitleBar (Windows/Linux) and Toolbar (macOS inline). The native and inline MenuBar had Check-for-Updates but no theme action, leaving macOS users without a single-click shortcut and inconsistent with VS Code / DataGrip / IntelliJ conventions. Adds: - 'Cycle Theme' under Help menu in MenuBar - Dispatches the 'cycle-theme' menu-action event - AppLayout handleAction routes that id to a Dark \u2192 Light \u2192 System cycle via the existing useThemeStore (mirrors Toolbar.cycleTheme) 2 new MenuBar tests verify the entry exists and dispatches the correct event id. Closes #453 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/components/layout/AppLayout.tsx | 13 +++++++++++++ src/components/layout/MenuBar.tsx | 2 ++ .../layout/__tests__/MenuBar.test.tsx | 17 +++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index f936d53..991cbd1 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -10,6 +10,7 @@ import { useConnectionStore } from "../../stores/connectionStore"; import { useEditorStore } from "../../stores/editorStore"; import { useResultStore } from "../../stores/resultStore"; import { useSettingsStore } from "../../stores/settingsStore"; +import { useThemeStore } from "../../stores/themeStore"; import { AIChatPanel } from "../ai/AIChatPanel"; import { BackupDialog } from "../backup/BackupDialog"; import { RestoreDialog } from "../backup/RestoreDialog"; @@ -161,6 +162,18 @@ export function AppLayout() { case "check-for-updates": void useSettingsStore.getState().checkForUpdates(); break; + case "cycle-theme": + // Cycle Dark → Light → System, mirroring Toolbar.cycleTheme so + // macOS users get the same one-click action as everyone else. + // (refs #453) + { + const themeStore = useThemeStore.getState(); + const themeOrder = ["dark", "light", "system"] as const; + const idx = themeOrder.indexOf(themeStore.theme); + const next = themeOrder[(idx + 1) % themeOrder.length]; + themeStore.setTheme(next); + } + break; case "about": setHelpTab("about"); setShowShortcuts(true); diff --git a/src/components/layout/MenuBar.tsx b/src/components/layout/MenuBar.tsx index 9ee5e39..44d4ac1 100644 --- a/src/components/layout/MenuBar.tsx +++ b/src/components/layout/MenuBar.tsx @@ -57,6 +57,8 @@ const MENUS: MenuDef[] = [ items: [ { type: "item", id: "check-for-updates", label: "Check for Updates…" }, { type: "separator" }, + { type: "item", id: "cycle-theme", label: "Cycle Theme (Dark / Light / System)" }, + { type: "separator" }, { type: "item", id: "keyboard-shortcuts", label: "Keyboard Shortcuts", shortcut: "F1" }, { type: "separator" }, { type: "item", id: "about", label: "About SQLPilot" }, diff --git a/src/components/layout/__tests__/MenuBar.test.tsx b/src/components/layout/__tests__/MenuBar.test.tsx index affc822..483a482 100644 --- a/src/components/layout/__tests__/MenuBar.test.tsx +++ b/src/components/layout/__tests__/MenuBar.test.tsx @@ -160,4 +160,21 @@ describe("MenuBar", () => { expect.objectContaining({ detail: "check-for-updates" }), ); }); + + it("includes a 'Cycle Theme' entry under the Help menu (refs #453)", () => { + render(); + + fireEvent.click(screen.getByText("Help")); + expect(screen.getByText(/Cycle Theme/)).toBeInTheDocument(); + }); + + it("dispatches 'cycle-theme' menu-action when the entry is clicked", () => { + render(); + + fireEvent.click(screen.getByText("Help")); + fireEvent.click(screen.getByText(/Cycle Theme/)); + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ detail: "cycle-theme" }), + ); + }); });