fix: expose desktop menu in window chrome

This commit is contained in:
lucaronin
2026-05-24 13:33:14 +02:00
parent b5d462b3cd
commit d204e95460
2 changed files with 89 additions and 28 deletions

View File

@@ -21,11 +21,15 @@ vi.mock('@tauri-apps/api/window', () => ({
async function openSubmenu(label: string) {
fireEvent.pointerDown(screen.getByRole('button', { name: 'Application menu' }), { button: 0 })
const trigger = await screen.findByText(label)
const trigger = await screen.findByRole('menuitem', { name: new RegExp(label) })
fireEvent.pointerMove(trigger)
fireEvent.click(trigger)
}
async function openHorizontalMenu(label: string) {
fireEvent.pointerDown(screen.getByRole('button', { name: label }), { button: 0 })
}
describe('LinuxMenuButton', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -45,6 +49,16 @@ describe('LinuxMenuButton', () => {
expect(invoke).toHaveBeenCalledWith('trigger_menu_command', { id: 'view-toggle-ai-chat' })
}, MENU_TEST_TIMEOUT_MS)
it('dispatches shared menu commands from the horizontal desktop menu', async () => {
render(<LinuxMenuButton />)
expect(screen.getByTestId('desktop-horizontal-menu')).toBeInTheDocument()
await openHorizontalMenu('File')
fireEvent.click(await screen.findByText('New Note'))
expect(invoke).toHaveBeenCalledWith('trigger_menu_command', { id: 'file-new-note' })
}, MENU_TEST_TIMEOUT_MS)
it('invokes direct window actions from the Window submenu', async () => {
render(<LinuxMenuButton />)

View File

@@ -47,6 +47,39 @@ function triggerMenuCommand(menuItemId: string): void {
void invoke('trigger_menu_command', { id: menuItemId }).catch(() => {})
}
function MenuSectionItems({ section }: { section: MenuSection }) {
return (
<>
{section.items.map((item, index) => {
if (item.kind === 'separator') {
return <DropdownMenuSeparator key={`${section.label}-${index}`} />
}
if (item.kind === 'command') {
return (
<DropdownMenuItem
key={item.menuItemId}
onSelect={() => triggerMenuCommand(item.menuItemId)}
>
<span>{item.label}</span>
{item.shortcut && (
<DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>
)}
</DropdownMenuItem>
)
}
return (
<DropdownMenuItem key={`${section.label}-${item.label}`} onSelect={item.action}>
<span>{item.label}</span>
{item.shortcut && <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>}
</DropdownMenuItem>
)
})}
</>
)
}
function HamburgerIcon() {
return (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
@@ -57,7 +90,7 @@ function HamburgerIcon() {
)
}
export function LinuxMenuButton() {
function AppMenuButton() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -77,32 +110,7 @@ export function LinuxMenuButton() {
<DropdownMenuSub key={section.label}>
<DropdownMenuSubTrigger>{section.label}</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="min-w-[220px]">
{section.items.map((item, index) => {
if (item.kind === 'separator') {
return <DropdownMenuSeparator key={`${section.label}-${index}`} />
}
if (item.kind === 'command') {
return (
<DropdownMenuItem
key={item.menuItemId}
onSelect={() => triggerMenuCommand(item.menuItemId)}
>
<span>{item.label}</span>
{item.shortcut && (
<DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>
)}
</DropdownMenuItem>
)
}
return (
<DropdownMenuItem key={`${section.label}-${item.label}`} onSelect={item.action}>
<span>{item.label}</span>
{item.shortcut && <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>}
</DropdownMenuItem>
)
})}
<MenuSectionItems section={section} />
</DropdownMenuSubContent>
</DropdownMenuSub>
))}
@@ -110,3 +118,42 @@ export function LinuxMenuButton() {
</DropdownMenu>
)
}
function HorizontalMenuBar() {
return (
<div
className="hidden h-full min-[760px]:flex"
data-testid="desktop-horizontal-menu"
>
{MENU_SECTIONS.map((section) => (
<DropdownMenu key={section.label}>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-full rounded-none px-3 text-[13px] font-normal text-foreground/75 hover:bg-foreground/10 hover:text-foreground"
data-no-drag
>
{section.label}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" sideOffset={0} className="min-w-[220px]">
<MenuSectionItems section={section} />
</DropdownMenuContent>
</DropdownMenu>
))}
</div>
)
}
export function LinuxMenuButton() {
return (
<>
<div className="min-[760px]:hidden">
<AppMenuButton />
</div>
<HorizontalMenuBar />
</>
)
}