add shortcuts in tray for quit and settings item

This commit is contained in:
Eduard Gert
2026-05-27 15:37:05 +02:00
parent a241112a1d
commit a8ad73d2d9
3 changed files with 39 additions and 8 deletions
@@ -208,7 +208,7 @@ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => ( const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
<span <span
className={cn("ml-auto text-xs tracking-widest text-nb-gray-400 opacity-60", className)} className={cn("ml-auto text-xs tracking-widest text-nb-gray-100 opacity-60", className)}
{...props} {...props}
/> />
); );
+18 -5
View File
@@ -1,4 +1,4 @@
import { useState } from "react"; import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
ArrowUpCircleIcon, ArrowUpCircleIcon,
@@ -15,24 +15,34 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/DropdownMenu"; } from "@/components/DropdownMenu";
import { IconButton } from "@/components/IconButton"; import { IconButton } from "@/components/IconButton";
import { ProfileDropdown } from "@/components/ProfileDropdown"; import { ProfileDropdown } from "@/components/ProfileDropdown";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext"; import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
import { cn } from "@/lib/cn"; import { cn } from "@/lib/cn";
import { formatShortcut, useKeyboardShortcut } from "@/lib/useKeyboardShortcut";
import { useViewMode, type ViewMode } from "@/lib/viewMode"; import { useViewMode, type ViewMode } from "@/lib/viewMode";
const SETTINGS_SHORTCUT = { key: ",", cmd: true } as const;
export const Header = () => { export const Header = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const { viewMode, setViewMode } = useViewMode(); const { viewMode, setViewMode } = useViewMode();
const { updateAvailable } = useClientVersion(); const { updateAvailable } = useClientVersion();
const openSettings = () => { const openSettings = useCallback(() => {
setMenuOpen(false); setMenuOpen(false);
void WindowManager.OpenSettings("").catch(() => {}); void WindowManager.OpenSettings("").catch(() => {});
}; }, []);
// Mirror the tray's Settings accelerator so the keystroke works while
// the main window has focus too. The tray's SetAccelerator paints the
// glyph on macOS/Linux but only fires the menu item — it can't reach the
// webview's input loop, hence the parallel React-side listener.
useKeyboardShortcut(SETTINGS_SHORTCUT, openSettings);
const openAbout = () => { const openAbout = () => {
setMenuOpen(false); setMenuOpen(false);
@@ -79,9 +89,12 @@ export const Header = () => {
</> </>
)} )}
<DropdownMenuItem onClick={openSettings}> <DropdownMenuItem onClick={openSettings}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 w-full">
<Settings size={14} /> <Settings size={14} />
{t("header.menu.settings")} <span className="flex-1">{t("header.menu.settings")}</span>
<DropdownMenuShortcut>
{formatShortcut(SETTINGS_SHORTCUT)}
</DropdownMenuShortcut>
</div> </div>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
+20 -2
View File
@@ -372,6 +372,20 @@ func (t *Tray) buildMenu() *application.Menu {
// The tray icon's left-click handler is intentionally unbound (see // The tray icon's left-click handler is intentionally unbound (see
// NewTray for the rationale), so expose the window through an explicit // NewTray for the rationale), so expose the window through an explicit
// menu entry on every platform. // menu entry on every platform.
//
// Accelerators are wired on the Settings and Quit entries below.
// Cross-platform behaviour in Wails v3 alpha.95:
// - macOS: SetAccelerator calls NSMenuItem.setKeyEquivalent — the
// glyph row paints to the right of the label and the combo fires
// when the menu is open OR while the app is the frontmost app.
// - Linux (GTK): SetAccelerator binds the GTK accel — the combo
// fires while the menu is open and the label paints the row. On
// XEmbed/AppIndicator hosts the visual hint may not render but
// activation through the keyboard still resolves.
// - Windows: SetAccelerator is a no-op in alpha.95 (the impl is
// commented out in menuitem_windows.go), so the row is plain
// text. We still call it for forward compatibility — a future
// Wails release picks the labels up without churn here.
menu.Add(t.loc.T("tray.menu.open")).OnClick(func(*application.Context) { t.ShowWindow() }) menu.Add(t.loc.T("tray.menu.open")).OnClick(func(*application.Context) { t.ShowWindow() })
menu.AddSeparator() menu.AddSeparator()
@@ -396,7 +410,9 @@ func (t *Tray) buildMenu() *application.Menu {
// surfaces the day-to-day actions. The trailing ellipsis on the label // surfaces the day-to-day actions. The trailing ellipsis on the label
// (i18n string) follows the macOS HIG convention for menu items that // (i18n string) follows the macOS HIG convention for menu items that
// open a dialog/window rather than performing an inline action. // open a dialog/window rather than performing an inline action.
t.settingsItem = menu.Add(t.loc.T("tray.menu.settings")).OnClick(func(*application.Context) { t.svc.WindowManager.OpenSettings("") }) t.settingsItem = menu.Add(t.loc.T("tray.menu.settings")).
SetAccelerator("CmdOrCtrl+,").
OnClick(func(*application.Context) { t.svc.WindowManager.OpenSettings("") })
aboutLabel := t.loc.T("tray.menu.about") aboutLabel := t.loc.T("tray.menu.about")
about := menu.AddSubmenu(aboutLabel) about := menu.AddSubmenu(aboutLabel)
@@ -427,7 +443,9 @@ func (t *Tray) buildMenu() *application.Menu {
t.updater.attach(updateItem) t.updater.attach(updateItem)
menu.AddSeparator() menu.AddSeparator()
menu.Add(t.loc.T("tray.menu.quit")).OnClick(func(*application.Context) { t.app.Quit() }) menu.Add(t.loc.T("tray.menu.quit")).
SetAccelerator("CmdOrCtrl+Q").
OnClick(func(*application.Context) { t.app.Quit() })
return menu return menu
} }