fix: expose desktop menu in window chrome
This commit is contained in:
@@ -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 />)
|
||||
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user