🐛 fix menu not closing when menu item is clicked (#4716)

This commit is contained in:
Matiss Janis Aboltins
2025-03-31 18:15:17 +01:00
committed by GitHub
parent e04ca554e2
commit aef38f1679
7 changed files with 232 additions and 179 deletions

View File

@@ -3,11 +3,13 @@ import {
useEffect,
useRef,
useState,
type ComponentProps,
type ComponentType,
type SVGProps,
type CSSProperties,
} from 'react';
import { Button } from './Button';
import { Text } from './Text';
import { theme } from './theme';
import { Toggle } from './Toggle';
@@ -61,6 +63,7 @@ type MenuProps<NameType> = {
style?: CSSProperties;
className?: string;
getItemStyle?: (item: MenuItemObject<NameType>) => CSSProperties;
slot?: ComponentProps<typeof Button>['slot'];
};
export function Menu<const NameType = string>({
@@ -71,6 +74,7 @@ export function Menu<const NameType = string>({
style,
className,
getItemStyle,
slot,
}: MenuProps<NameType>) {
const elRef = useRef<HTMLDivElement>(null);
const items = allItems.filter(x => x);
@@ -161,9 +165,10 @@ export function Menu<const NameType = string>({
const Icon = item.icon;
return (
<View
role="button"
<Button
key={String(item.name)}
variant="bare"
slot={slot}
style={{
cursor: 'default',
padding: 10,
@@ -179,11 +184,9 @@ export function Menu<const NameType = string>({
}),
...(!isLabel(item) && getItemStyle?.(item)),
}}
onPointerEnter={() => setHoveredIndex(idx)}
onPointerLeave={() => setHoveredIndex(null)}
onPointerUp={e => {
e.stopPropagation();
onHoverStart={() => setHoveredIndex(idx)}
onHoverEnd={() => setHoveredIndex(null)}
onPress={() => {
if (
!item.disabled &&
item.toggle === undefined &&
@@ -232,7 +235,7 @@ export function Menu<const NameType = string>({
</View>
)}
{item.key && <Keybinding keyName={item.key} />}
</View>
</Button>
);
})}
{footer}

View File

@@ -180,7 +180,11 @@ export class MobileBudgetPage {
const categoryButton = await this.#getButtonForCategory(categoryName);
await categoryButton.click();
return new CategoryMenuModal(this.page.getByRole('dialog'));
return new CategoryMenuModal(
this.page.getByRole('dialog', {
name: 'Modal dialog',
}),
);
}
async #getButtonForCell(
@@ -222,7 +226,11 @@ export class MobileBudgetPage {
const budgetedButton = await this.getButtonForBudgeted(categoryName);
await budgetedButton.click();
return new BudgetMenuModal(this.page.getByRole('dialog'));
return new BudgetMenuModal(
this.page.getByRole('dialog', {
name: 'Modal dialog',
}),
);
}
async openSpentPage(categoryName: string) {
@@ -239,7 +247,11 @@ export class MobileBudgetPage {
if (await balanceButton.isVisible()) {
await balanceButton.click();
return new BalanceMenuModal(this.page.getByRole('dialog'));
return new BalanceMenuModal(
this.page.getByRole('dialog', {
name: 'Modal dialog',
}),
);
} else {
throw new Error(
`Balance button for category ${categoryName} not found or not visible.`,
@@ -324,7 +336,11 @@ export class MobileBudgetPage {
}
await budgetSummaryButton.click();
return new EnvelopeBudgetSummaryModal(this.page.getByRole('dialog'));
return new EnvelopeBudgetSummaryModal(
this.page.getByRole('dialog', {
name: 'Modal dialog',
}),
);
}
async #getButtonForTrackingBudgetSummary({
@@ -358,6 +374,10 @@ export class MobileBudgetPage {
}
await budgetSummaryButton.click();
return new TrackingBudgetSummaryModal(this.page.getByRole('dialog'));
return new TrackingBudgetSummaryModal(
this.page.getByRole('dialog', {
name: 'Modal dialog',
}),
);
}
}

View File

@@ -25,6 +25,10 @@ export class CategoryMenuModal {
async editNotes() {
await this.editNotesButton.click();
return new EditNotesModal(this.page.getByRole('dialog'));
return new EditNotesModal(
this.page.getByRole('dialog', {
name: 'Modal dialog',
}),
);
}
}

View File

@@ -5,7 +5,7 @@ import React, {
type ReactNode,
type ComponentProps,
} from 'react';
import { DialogTrigger } from 'react-aria-components';
import { Dialog, DialogTrigger } from 'react-aria-components';
import { useHotkeys } from 'react-hotkeys-hook';
import { Trans, useTranslation } from 'react-i18next';
@@ -463,18 +463,20 @@ export function AccountHeader({
</Button>
<Popover style={{ width: 275 }}>
<AccountMenu
account={account}
canSync={canSync}
canShowBalances={
canCalculateBalance ? canCalculateBalance() : false
}
isSorted={isSorted}
showBalances={showBalances}
showCleared={showCleared}
showReconciled={showReconciled}
onMenuSelect={onMenuSelect}
/>
<Dialog>
<AccountMenu
account={account}
canSync={canSync}
canShowBalances={
canCalculateBalance ? canCalculateBalance() : false
}
isSorted={isSorted}
showBalances={showBalances}
showCleared={showCleared}
showReconciled={showReconciled}
onMenuSelect={onMenuSelect}
/>
</Dialog>
</Popover>
</DialogTrigger>
</View>
@@ -490,20 +492,23 @@ export function AccountHeader({
</Button>
<Popover>
<Menu
onMenuSelect={onMenuSelect}
items={[
...(isSorted
? [
{
name: 'remove-sorting',
text: t('Remove all sorting'),
} as const,
]
: []),
{ name: 'export', text: t('Export') },
]}
/>
<Dialog>
<Menu
slot="close"
onMenuSelect={onMenuSelect}
items={[
...(isSorted
? [
{
name: 'remove-sorting',
text: t('Remove all sorting'),
} as const,
]
: []),
{ name: 'export', text: t('Export') },
]}
/>
</Dialog>
</Popover>
</DialogTrigger>
</View>
@@ -722,6 +727,7 @@ function AccountMenu({
return (
<Menu
slot="close"
onMenuSelect={item => {
onMenuSelect(item);
}}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { DialogTrigger } from 'react-aria-components';
import { Dialog, DialogTrigger } from 'react-aria-components';
import { Trans, useTranslation } from 'react-i18next';
import { Button, ButtonWithLoading } from '@actual-app/components/button';
@@ -416,19 +416,21 @@ export function CreateAccountModal({
</Button>
<Popover>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onGoCardlessReset();
}
}}
items={[
{
name: 'reconfigure',
text: t('Reset GoCardless credentials'),
},
]}
/>
<Dialog>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onGoCardlessReset();
}
}}
items={[
{
name: 'reconfigure',
text: t('Reset GoCardless credentials'),
},
]}
/>
</Dialog>
</Popover>
</DialogTrigger>
)}
@@ -479,19 +481,21 @@ export function CreateAccountModal({
/>
</Button>
<Popover>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onSimpleFinReset();
}
}}
items={[
{
name: 'reconfigure',
text: t('Reset SimpleFIN credentials'),
},
]}
/>
<Dialog>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onSimpleFinReset();
}
}}
items={[
{
name: 'reconfigure',
text: t('Reset SimpleFIN credentials'),
},
]}
/>
</Dialog>
</Popover>
</DialogTrigger>
)}
@@ -543,19 +547,23 @@ export function CreateAccountModal({
</Button>
<Popover>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onPluggyAiReset();
}
}}
items={[
{
name: 'reconfigure',
text: t('Reset Pluggy.ai credentials'),
},
]}
/>
<Dialog>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onPluggyAiReset();
}
}}
items={[
{
name: 'reconfigure',
text: t(
'Reset Pluggy.ai credentials',
),
},
]}
/>
</Dialog>
</Popover>
</DialogTrigger>
)}

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import { DialogTrigger } from 'react-aria-components';
import { Dialog, DialogTrigger } from 'react-aria-components';
import { Responsive, WidthProvider, type Layout } from 'react-grid-layout';
import { useHotkeys } from 'react-hotkeys-hook';
import { Trans, useTranslation } from 'react-i18next';
@@ -339,73 +339,76 @@ export function Overview() {
</Button>
<Popover>
<Menu
onMenuSelect={item => {
if (item === 'custom-report') {
navigate('/reports/custom');
return;
}
<Dialog>
<Menu
slot="close"
onMenuSelect={item => {
if (item === 'custom-report') {
navigate('/reports/custom');
return;
}
function isExistingCustomReport(
name: string,
): name is `custom-report-${string}` {
return name.startsWith('custom-report-');
}
if (isExistingCustomReport(item)) {
const [, reportId] = item.split('custom-report-');
onAddWidget<CustomReportWidget>('custom-report', {
id: reportId,
});
return;
}
function isExistingCustomReport(
name: string,
): name is `custom-report-${string}` {
return name.startsWith('custom-report-');
}
if (isExistingCustomReport(item)) {
const [, reportId] = item.split('custom-report-');
onAddWidget<CustomReportWidget>('custom-report', {
id: reportId,
});
return;
}
if (item === 'markdown-card') {
onAddWidget<MarkdownWidget>(item, {
content: `### ${t('Text Widget')}\n\n${t('Edit this widget to change the **markdown** content.')}`,
});
return;
}
if (item === 'markdown-card') {
onAddWidget<MarkdownWidget>(item, {
content: `### ${t('Text Widget')}\n\n${t('Edit this widget to change the **markdown** content.')}`,
});
return;
}
onAddWidget(item);
}}
items={[
{
name: 'cash-flow-card' as const,
text: t('Cash flow graph'),
},
{
name: 'net-worth-card' as const,
text: t('Net worth graph'),
},
{
name: 'spending-card' as const,
text: t('Spending analysis'),
},
{
name: 'markdown-card' as const,
text: t('Text widget'),
},
{
name: 'summary-card' as const,
text: t('Summary card'),
},
{
name: 'calendar-card' as const,
text: t('Calendar card'),
},
{
name: 'custom-report' as const,
text: t('New custom report'),
},
...(customReports.length
? ([Menu.line] satisfies Array<typeof Menu.line>)
: []),
...customReports.map(report => ({
name: `custom-report-${report.id}` as const,
text: report.name,
})),
]}
/>
onAddWidget(item);
}}
items={[
{
name: 'cash-flow-card' as const,
text: t('Cash flow graph'),
},
{
name: 'net-worth-card' as const,
text: t('Net worth graph'),
},
{
name: 'spending-card' as const,
text: t('Spending analysis'),
},
{
name: 'markdown-card' as const,
text: t('Text widget'),
},
{
name: 'summary-card' as const,
text: t('Summary card'),
},
{
name: 'calendar-card' as const,
text: t('Calendar card'),
},
{
name: 'custom-report' as const,
text: t('New custom report'),
},
...(customReports.length
? ([Menu.line] satisfies Array<typeof Menu.line>)
: []),
...customReports.map(report => ({
name: `custom-report-${report.id}` as const,
text: report.name,
})),
]}
/>
</Dialog>
</Popover>
</DialogTrigger>
@@ -434,39 +437,42 @@ export function Overview() {
/>
</Button>
<Popover>
<Menu
onMenuSelect={item => {
switch (item) {
case 'reset':
onResetDashboard();
break;
case 'export':
onExport();
break;
case 'import':
onImport();
break;
}
}}
items={[
{
name: 'reset',
text: t('Reset to default'),
disabled: isImporting,
},
Menu.line,
{
name: 'import',
text: t('Import'),
disabled: isImporting,
},
{
name: 'export',
text: t('Export'),
disabled: isImporting,
},
]}
/>
<Dialog>
<Menu
slot="close"
onMenuSelect={item => {
switch (item) {
case 'reset':
onResetDashboard();
break;
case 'export':
onExport();
break;
case 'import':
onImport();
break;
}
}}
items={[
{
name: 'reset',
text: t('Reset to default'),
disabled: isImporting,
},
Menu.line,
{
name: 'import',
text: t('Import'),
disabled: isImporting,
},
{
name: 'export',
text: t('Export'),
disabled: isImporting,
},
]}
/>
</Dialog>
</Popover>
</DialogTrigger>
</>

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [MatissJanis]
---
Fix menu not closing when menu item is clicked