diff --git a/.eslintrc.js b/.eslintrc.js index 9653190ca8f28677b6bcc41aa57bdd1c1100f659..95078706ffa1d764424a75e46d9ac1da33bb259b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -281,8 +281,8 @@ module.exports = { './packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx', './packages/desktop-client/src/components/budget/index.tsx', './packages/desktop-client/src/components/budget/MobileBudget.tsx', - './packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx', - './packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx', + './packages/desktop-client/src/components/budget/rollover/HoldMenu.tsx', + './packages/desktop-client/src/components/budget/rollover/TransferMenu.tsx', './packages/desktop-client/src/components/common/Menu.tsx', './packages/desktop-client/src/components/FinancesApp.tsx', './packages/desktop-client/src/components/GlobalKeys.ts', diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png index 859d3bc11ea5dad29e10ac05b762216c12c1670b..965136b385cd1fdef63ad8e8013978c6732e8427 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png index b75ff17d7272fcbd762fdf639f3a008e5914ecbd..0cad57c4c389d5903690580382edc679a972c818 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png index 2911c4932381d33653345d60d2428cfbf38b68f3..d75b2db3e9e9e0f876fbfa97b11a3ff672307935 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/budget/BudgetTotals.tsx b/packages/desktop-client/src/components/budget/BudgetTotals.tsx index 005e2afa939f1365135a987dc2bae76843c06cff..7126ff4d6add7223175da6c553410d3d4ef1e1ff 100644 --- a/packages/desktop-client/src/components/budget/BudgetTotals.tsx +++ b/packages/desktop-client/src/components/budget/BudgetTotals.tsx @@ -1,11 +1,11 @@ -import React, { type ComponentProps, memo, useState } from 'react'; +import React, { type ComponentProps, memo, useRef, useState } from 'react'; import { SvgDotsHorizontalTriple } from '../../icons/v1'; import { theme, styles } from '../../style'; import { Button } from '../common/Button'; import { Menu } from '../common/Menu'; +import { Popover } from '../common/Popover'; import { View } from '../common/View'; -import { Tooltip } from '../tooltips'; import { RenderMonths } from './RenderMonths'; import { getScrollbarWidth } from './util'; @@ -24,6 +24,8 @@ export const BudgetTotals = memo(function BudgetTotals({ collapseAllCategories, }: BudgetTotalsProps) { const [menuOpen, setMenuOpen] = useState(false); + const triggerRef = useRef(null); + return ( <View data-testid="budget-totals" @@ -54,6 +56,7 @@ export const BudgetTotals = memo(function BudgetTotals({ > <View style={{ flexGrow: '1' }}>Category</View> <Button + ref={triggerRef} type="bare" aria-label="Menu" onClick={() => { @@ -66,44 +69,41 @@ export const BudgetTotals = memo(function BudgetTotals({ height={15} style={{ color: theme.pageTextLight }} /> - {menuOpen && ( - <Tooltip - position="bottom-right" - width={200} - style={{ padding: 0 }} - onClose={() => { - setMenuOpen(false); - }} - > - <Menu - onMenuSelect={type => { - if (type === 'toggle-visibility') { - toggleHiddenCategories(); - } else if (type === 'expandAllCategories') { - expandAllCategories(); - } else if (type === 'collapseAllCategories') { - collapseAllCategories(); - } - setMenuOpen(false); - }} - items={[ - { - name: 'toggle-visibility', - text: 'Toggle hidden categories', - }, - { - name: 'expandAllCategories', - text: 'Expand all', - }, - { - name: 'collapseAllCategories', - text: 'Collapse all', - }, - ]} - /> - </Tooltip> - )} </Button> + + <Popover + triggerRef={triggerRef} + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} + style={{ width: 200 }} + > + <Menu + onMenuSelect={type => { + if (type === 'toggle-visibility') { + toggleHiddenCategories(); + } else if (type === 'expandAllCategories') { + expandAllCategories(); + } else if (type === 'collapseAllCategories') { + collapseAllCategories(); + } + setMenuOpen(false); + }} + items={[ + { + name: 'toggle-visibility', + text: 'Toggle hidden categories', + }, + { + name: 'expandAllCategories', + text: 'Expand all', + }, + { + name: 'collapseAllCategories', + text: 'Collapse all', + }, + ]} + /> + </Popover> </View> <RenderMonths component={MonthComponent} /> </View> diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index f62255cf91107c1d0b68135145ae92ca67bcb2d2..c8986725fe543079b45de359058c261c76787314 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React, { type CSSProperties, type Ref, useState } from 'react'; +import React, { type CSSProperties, type Ref, useRef, useState } from 'react'; import { type CategoryGroupEntity, @@ -10,10 +10,10 @@ import { SvgCheveronDown } from '../../icons/v1'; import { theme } from '../../style'; import { Button } from '../common/Button'; import { Menu } from '../common/Menu'; +import { Popover } from '../common/Popover'; import { View } from '../common/View'; import { NotesButton } from '../NotesButton'; import { InputCell } from '../table'; -import { Tooltip } from '../tooltips'; type SidebarCategoryProps = { innerRef: Ref<HTMLDivElement>; @@ -26,7 +26,7 @@ type SidebarCategoryProps = { borderColor?: string; isLast?: boolean; onEditName: (id: string) => void; - onSave: (group) => void; + onSave: (category: CategoryEntity) => void; onDelete: (id: string) => Promise<void>; onHideNewCategory?: () => void; }; @@ -47,6 +47,7 @@ export function SidebarCategory({ }: SidebarCategoryProps) { const temporary = category.id === 'new'; const [menuOpen, setMenuOpen] = useState(false); + const triggerRef = useRef(null); const displayed = ( <View @@ -69,7 +70,7 @@ export function SidebarCategory({ > {category.name} </div> - <View style={{ flexShrink: 0, marginLeft: 5 }}> + <View style={{ flexShrink: 0, marginLeft: 5 }} ref={triggerRef}> <Button type="bare" className="hover-visible" @@ -85,35 +86,35 @@ export function SidebarCategory({ style={{ color: 'currentColor' }} /> </Button> - {menuOpen && ( - <Tooltip - position="bottom-left" - width={200} - style={{ padding: 0 }} - onClose={() => setMenuOpen(false)} - > - <Menu - onMenuSelect={type => { - if (type === 'rename') { - onEditName(category.id); - } else if (type === 'delete') { - onDelete(category.id); - } else if (type === 'toggle-visibility') { - onSave({ ...category, hidden: !category.hidden }); - } - setMenuOpen(false); - }} - items={[ - !categoryGroup?.hidden && { - name: 'toggle-visibility', - text: category.hidden ? 'Show' : 'Hide', - }, - { name: 'rename', text: 'Rename' }, - { name: 'delete', text: 'Delete' }, - ]} - /> - </Tooltip> - )} + + <Popover + triggerRef={triggerRef} + placement="bottom start" + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} + style={{ width: 200 }} + > + <Menu + onMenuSelect={type => { + if (type === 'rename') { + onEditName(category.id); + } else if (type === 'delete') { + onDelete(category.id); + } else if (type === 'toggle-visibility') { + onSave({ ...category, hidden: !category.hidden }); + } + setMenuOpen(false); + }} + items={[ + !categoryGroup?.hidden && { + name: 'toggle-visibility', + text: category.hidden ? 'Show' : 'Hide', + }, + { name: 'rename', text: 'Rename' }, + { name: 'delete', text: 'Delete' }, + ]} + /> + </Popover> </View> <View style={{ flex: 1 }} /> <NotesButton diff --git a/packages/desktop-client/src/components/budget/SidebarGroup.tsx b/packages/desktop-client/src/components/budget/SidebarGroup.tsx index c762540579be6723437795435822c86acaae671e..0c9e3b4ec6329d3b4f80449120691c92dc89debf 100644 --- a/packages/desktop-client/src/components/budget/SidebarGroup.tsx +++ b/packages/desktop-client/src/components/budget/SidebarGroup.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React, { type CSSProperties, useState } from 'react'; +import React, { type CSSProperties, useRef, useState } from 'react'; import { type ConnectDragSource } from 'react-dnd'; import { SvgExpandArrow } from '../../icons/v0'; @@ -7,11 +7,11 @@ import { SvgCheveronDown } from '../../icons/v1'; import { theme } from '../../style'; import { Button } from '../common/Button'; import { Menu } from '../common/Menu'; +import { Popover } from '../common/Popover'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { NotesButton } from '../NotesButton'; import { InputCell } from '../table'; -import { Tooltip } from '../tooltips'; type SidebarGroupProps = { group: { @@ -52,6 +52,7 @@ export function SidebarGroup({ }: SidebarGroupProps) { const temporary = group.id === 'new'; const [menuOpen, setMenuOpen] = useState(false); + const triggerRef = useRef(null); const displayed = ( <View @@ -91,7 +92,7 @@ export function SidebarGroup({ </div> {!dragPreview && ( <> - <View style={{ marginLeft: 5, flexShrink: 0 }}> + <View style={{ marginLeft: 5, flexShrink: 0 }} ref={triggerRef}> <Button type="bare" className="hover-visible" @@ -103,38 +104,38 @@ export function SidebarGroup({ > <SvgCheveronDown width={14} height={14} /> </Button> - {menuOpen && ( - <Tooltip - position="bottom-left" - width={200} - style={{ padding: 0 }} - onClose={() => setMenuOpen(false)} - > - <Menu - onMenuSelect={type => { - if (type === 'rename') { - onEdit(group.id); - } else if (type === 'add-category') { - onShowNewCategory(group.id); - } else if (type === 'delete') { - onDelete(group.id); - } else if (type === 'toggle-visibility') { - onSave({ ...group, hidden: !group.hidden }); - } - setMenuOpen(false); - }} - items={[ - { name: 'add-category', text: 'Add category' }, - !group.is_income && { - name: 'toggle-visibility', - text: group.hidden ? 'Show' : 'Hide', - }, - { name: 'rename', text: 'Rename' }, - onDelete && { name: 'delete', text: 'Delete' }, - ]} - /> - </Tooltip> - )} + + <Popover + triggerRef={triggerRef} + placement="bottom start" + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} + style={{ width: 200 }} + > + <Menu + onMenuSelect={type => { + if (type === 'rename') { + onEdit(group.id); + } else if (type === 'add-category') { + onShowNewCategory(group.id); + } else if (type === 'delete') { + onDelete(group.id); + } else if (type === 'toggle-visibility') { + onSave({ ...group, hidden: !group.hidden }); + } + setMenuOpen(false); + }} + items={[ + { name: 'add-category', text: 'Add category' }, + !group.is_income && { + name: 'toggle-visibility', + text: group.hidden ? 'Show' : 'Hide', + }, + { name: 'rename', text: 'Rename' }, + onDelete && { name: 'delete', text: 'Delete' }, + ]} + /> + </Popover> </View> <View style={{ flex: 1 }} /> <NotesButton diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx similarity index 52% rename from packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx rename to packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx index 1432556c096ce5f58dbc5ed931cb397345e2bf6e..0957b7bc827088147d488fa580fa37254fb796bb 100644 --- a/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx @@ -3,66 +3,49 @@ import React, { useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; -import { Tooltip } from '../../tooltips'; import { BalanceMenu } from './BalanceMenu'; -import { CoverTooltip } from './CoverTooltip'; -import { TransferTooltip } from './TransferTooltip'; +import { CoverMenu } from './CoverMenu'; +import { TransferMenu } from './TransferMenu'; -type BalanceTooltipProps = { +type BalanceMovementMenuProps = { categoryId: string; - tooltip: { close: () => void }; month: string; onBudgetAction: (month: string, action: string, arg?: unknown) => void; onClose?: () => void; }; -export function BalanceTooltip({ +export function BalanceMovementMenu({ categoryId, - tooltip, month, onBudgetAction, - onClose, - ...tooltipProps -}: BalanceTooltipProps) { + onClose = () => {}, +}: BalanceMovementMenuProps) { const catBalance = useSheetValue(rolloverBudget.catBalance(categoryId)); const [menu, setMenu] = useState('menu'); - const _onClose = () => { - tooltip.close(); - onClose?.(); - }; - return ( <> {menu === 'menu' && ( - <Tooltip - position="bottom-right" - width={200} - style={{ padding: 0 }} - onClose={_onClose} - {...tooltipProps} - > - <BalanceMenu - categoryId={categoryId} - onCarryover={carryover => { - onBudgetAction(month, 'carryover', { - category: categoryId, - flag: carryover, - }); - _onClose(); - }} - onTransfer={() => setMenu('transfer')} - onCover={() => setMenu('cover')} - /> - </Tooltip> + <BalanceMenu + categoryId={categoryId} + onCarryover={carryover => { + onBudgetAction(month, 'carryover', { + category: categoryId, + flag: carryover, + }); + onClose(); + }} + onTransfer={() => setMenu('transfer')} + onCover={() => setMenu('cover')} + /> )} {menu === 'transfer' && ( - <TransferTooltip + <TransferMenu initialAmount={catBalance} showToBeBudgeted={true} - onClose={_onClose} + onClose={onClose} onSubmit={(amount, toCategoryId) => { onBudgetAction(month, 'transfer-category', { amount, @@ -74,8 +57,8 @@ export function BalanceTooltip({ )} {menu === 'cover' && ( - <CoverTooltip - onClose={_onClose} + <CoverMenu + onClose={onClose} onSubmit={fromCategoryId => { onBudgetAction(month, 'cover', { to: categoryId, diff --git a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx similarity index 71% rename from packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx rename to packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx index 60458e71e8ecfb033bab8f6b0bf8d60b0ca4b798..d321357adfdbbc95d16f6f5cf64f0a2322a2ca5c 100644 --- a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx @@ -1,46 +1,18 @@ -import React, { type ComponentProps, useState } from 'react'; +import React, { useState } from 'react'; import { useCategories } from '../../../hooks/useCategories'; import { CategoryAutocomplete } from '../../autocomplete/CategoryAutocomplete'; import { Button } from '../../common/Button'; import { InitialFocus } from '../../common/InitialFocus'; import { View } from '../../common/View'; -import { Tooltip } from '../../tooltips'; import { addToBeBudgetedGroup } from '../util'; -type CoverTooltipProps = { - tooltipProps?: ComponentProps<typeof Tooltip>; +type CoverMenuProps = { onSubmit: (categoryId: string) => void; onClose: () => void; }; -export function CoverTooltip({ - tooltipProps, - onSubmit, - onClose, -}: CoverTooltipProps) { - const _onSubmit = (categoryId: string) => { - onSubmit?.(categoryId); - onClose?.(); - }; - return ( - <Tooltip - position="bottom-right" - width={200} - style={{ padding: 10 }} - {...tooltipProps} - onClose={onClose} - > - <Cover onSubmit={_onSubmit} /> - </Tooltip> - ); -} - -type CoverProps = { - onSubmit: (categoryId: string) => void; -}; - -function Cover({ onSubmit }: CoverProps) { +export function CoverMenu({ onSubmit, onClose }: CoverMenuProps) { const { grouped: originalCategoryGroups } = useCategories(); const categoryGroups = addToBeBudgetedGroup( originalCategoryGroups.filter(g => !g.is_income), @@ -51,9 +23,10 @@ function Cover({ onSubmit }: CoverProps) { if (categoryId) { onSubmit(categoryId); } + onClose(); } return ( - <> + <View style={{ padding: 10 }}> <View style={{ marginBottom: 5 }}>Cover from category:</View> <InitialFocus> @@ -94,6 +67,6 @@ function Cover({ onSubmit }: CoverProps) { Transfer </Button> </View> - </> + </View> ); } diff --git a/packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/HoldMenu.tsx similarity index 80% rename from packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx rename to packages/desktop-client/src/components/budget/rollover/HoldMenu.tsx index a2a4e69025e310ff3ab67abae933cbe46c99a1bb..c935e7f10ba1755e0f52c4296e84c98bfbe64518 100644 --- a/packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/HoldMenu.tsx @@ -3,7 +3,6 @@ import React, { useContext, useEffect, type ChangeEvent, - type ComponentPropsWithoutRef, } from 'react'; import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider'; @@ -15,17 +14,12 @@ import { InitialFocus } from '../../common/InitialFocus'; import { Input } from '../../common/Input'; import { View } from '../../common/View'; import { NamespaceContext } from '../../spreadsheet/NamespaceContext'; -import { Tooltip } from '../../tooltips'; -type HoldTooltipProps = ComponentPropsWithoutRef<typeof Tooltip> & { +type HoldMenuProps = { onSubmit: (amount: number) => void; + onClose: () => void; }; -export function HoldTooltip({ - onSubmit, - onClose, - position = 'bottom-right', - ...props -}: HoldTooltipProps) { +export function HoldMenu({ onSubmit, onClose }: HoldMenuProps) { const spreadsheet = useSpreadsheet(); const sheetName = useContext(NamespaceContext); @@ -47,18 +41,12 @@ export function HoldTooltip({ } if (amount === null) { - // See `TransferTooltip` for more info about this + // See `TransferMenu` for more info about this return null; } return ( - <Tooltip - position={position} - width={200} - style={{ padding: 10 }} - onClose={onClose} - {...props} - > + <View style={{ padding: 10 }}> <View style={{ marginBottom: 5 }}>Hold this amount:</View> <View> <InitialFocus> @@ -89,6 +77,6 @@ export function HoldTooltip({ Hold </Button> </View> - </Tooltip> + </View> ); } diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index b1ba0ff719eb6072fcb3657e83a5787ff435ca79..492fb5b1c5a0880aa31a3703a1edb791174fde11 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -1,5 +1,5 @@ import type React from 'react'; -import { memo, useState } from 'react'; +import { memo, useRef, useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; @@ -8,16 +8,16 @@ import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; import { SvgCheveronDown } from '../../../icons/v1'; import { styles, theme, type CSSProperties } from '../../../style'; import { Button } from '../../common/Button'; +import { Popover } from '../../common/Popover'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { CellValue } from '../../spreadsheet/CellValue'; import { useFormat } from '../../spreadsheet/useFormat'; import { Row, Field, SheetCell } from '../../table'; -import { Tooltip, useTooltip } from '../../tooltips'; import { BalanceWithCarryover } from '../BalanceWithCarryover'; import { makeAmountGrey } from '../util'; -import { BalanceTooltip } from './BalanceTooltip'; +import { BalanceMovementMenu } from './BalanceMovementMenu'; import { BudgetMenu } from './BudgetMenu'; const headerLabelStyle: CSSProperties = { @@ -152,8 +152,10 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ onBudgetAction, onShowActivity, }: ExpenseCategoryMonthProps) { - const balanceTooltip = useTooltip(); - const [menuOpen, setMenuOpen] = useState(false); + const budgetMenuTriggerRef = useRef(null); + const balanceMenuTriggerRef = useRef(null); + const [budgetMenuOpen, setBudgetMenuOpen] = useState(false); + const [balanceMenuOpen, setBalanceMenuOpen] = useState(false); const [hover, setHover] = useState(false); return ( @@ -180,7 +182,7 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ setHover(false); }} > - {!editing && (hover || menuOpen) ? ( + {!editing && (hover || budgetMenuOpen) ? ( <View style={{ flexShrink: 1, @@ -192,10 +194,11 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ }} > <Button + ref={budgetMenuTriggerRef} type="bare" onClick={e => { e.stopPropagation(); - setMenuOpen(true); + setBudgetMenuOpen(true); }} style={{ padding: 3, @@ -205,47 +208,43 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ width={14} height={14} className="hover-visible" - style={menuOpen ? { opacity: 1 } : {}} + style={budgetMenuOpen ? { opacity: 1 } : {}} /> </Button> - {menuOpen && ( - <Tooltip - position="bottom-left" - width={200} - style={{ padding: 0 }} - onClose={() => setMenuOpen(false)} - > - <BudgetMenu - onCopyLastMonthAverage={() => { - onBudgetAction?.(month, 'copy-single-last', { - category: category.id, - }); - }} - onSetMonthsAverage={numberOfMonths => { - if ( - numberOfMonths !== 3 && - numberOfMonths !== 6 && - numberOfMonths !== 12 - ) { - return; - } - onBudgetAction?.( - month, - `set-single-${numberOfMonths}-avg`, - { - category: category.id, - }, - ); - }} - onApplyBudgetTemplate={() => { - onBudgetAction?.(month, 'apply-single-category-template', { - category: category.id, - }); - }} - /> - </Tooltip> - )} + <Popover + triggerRef={budgetMenuTriggerRef} + placement="bottom start" + isOpen={budgetMenuOpen} + onOpenChange={() => setBudgetMenuOpen(false)} + style={{ width: 200 }} + > + <BudgetMenu + onCopyLastMonthAverage={() => { + onBudgetAction?.(month, 'copy-single-last', { + category: category.id, + }); + }} + onSetMonthsAverage={numberOfMonths => { + if ( + numberOfMonths !== 3 && + numberOfMonths !== 6 && + numberOfMonths !== 12 + ) { + return; + } + + onBudgetAction?.(month, `set-single-${numberOfMonths}-avg`, { + category: category.id, + }); + }} + onApplyBudgetTemplate={() => { + onBudgetAction?.(month, 'apply-single-category-template', { + category: category.id, + }); + }} + /> + </Popover> </View> ) : null} <SheetCell @@ -315,7 +314,10 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ width="flex" style={{ paddingRight: styles.monthRightPadding, textAlign: 'right' }} > - <span {...balanceTooltip.getOpenEvents()}> + <span + ref={balanceMenuTriggerRef} + onClick={() => setBalanceMenuOpen(true)} + > <BalanceWithCarryover carryover={rolloverBudget.catCarryover(category.id)} balance={rolloverBudget.catBalance(category.id)} @@ -323,14 +325,21 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ budgeted={rolloverBudget.catBudgeted(category.id)} /> </span> - {balanceTooltip.isOpen && ( - <BalanceTooltip + + <Popover + triggerRef={balanceMenuTriggerRef} + placement="bottom end" + isOpen={balanceMenuOpen} + onOpenChange={() => setBalanceMenuOpen(false)} + style={{ width: 200 }} + > + <BalanceMovementMenu categoryId={category.id} - tooltip={balanceTooltip} month={month} onBudgetAction={onBudgetAction} + onClose={() => setBalanceMenuOpen(false)} /> - )} + </Popover> </Field> </View> ); diff --git a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/TransferMenu.tsx similarity index 71% rename from packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx rename to packages/desktop-client/src/components/budget/rollover/TransferMenu.tsx index f23e53ca5f1eaf82bb0cee77d9396f80dcaffaf2..544504ab480b4366fa1d38468a13e4a41009be4a 100644 --- a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/TransferMenu.tsx @@ -1,5 +1,4 @@ -import type React from 'react'; -import { useState, type ComponentPropsWithoutRef } from 'react'; +import React, { useState } from 'react'; import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; @@ -10,52 +9,21 @@ import { Button } from '../../common/Button'; import { InitialFocus } from '../../common/InitialFocus'; import { Input } from '../../common/Input'; import { View } from '../../common/View'; -import { Tooltip } from '../../tooltips'; import { addToBeBudgetedGroup } from '../util'; -type TransferTooltipProps = ComponentPropsWithoutRef<typeof Tooltip> & { +type TransferMenuProps = { initialAmount?: number; showToBeBudgeted?: boolean; onSubmit: (amount: number, categoryId: string) => void; + onClose: () => void; }; -export function TransferTooltip({ +export function TransferMenu({ initialAmount = 0, showToBeBudgeted, onSubmit, onClose, - position = 'bottom-right', - ...props -}: TransferTooltipProps) { - const _onSubmit = (amount: number, categoryId: string) => { - onSubmit?.(amount, categoryId); - onClose?.(); - }; - - return ( - <Tooltip - position={position} - width={200} - style={{ padding: 10 }} - onClose={onClose} - {...props} - > - <Transfer amount={initialAmount} showToBeBudgeted onSubmit={_onSubmit} /> - </Tooltip> - ); -} - -type TransferProps = { - amount: number; - showToBeBudgeted: boolean; - onSubmit: (amount: number, categoryId: string) => void; -}; - -function Transfer({ - amount: initialAmount, - showToBeBudgeted, - onSubmit, -}: TransferProps) { +}: TransferMenuProps) { const { grouped: originalCategoryGroups } = useCategories(); let categoryGroups = originalCategoryGroups.filter(g => !g.is_income); if (showToBeBudgeted) { @@ -71,10 +39,12 @@ function Transfer({ if (parsedAmount && categoryId) { onSubmit?.(amountToInteger(parsedAmount), categoryId); } + + onClose(); }; return ( - <> + <View style={{ padding: 10 }}> <View style={{ marginBottom: 5 }}>Transfer this amount:</View> <View> <InitialFocus> @@ -93,7 +63,8 @@ function Transfer({ openOnFocus={true} onSelect={(id: string | undefined) => setCategoryId(id || null)} inputProps={{ - onEnter: () => _onSubmit(amount, categoryId), + onEnter: event => + !event.defaultPrevented && _onSubmit(amount, categoryId), placeholder: '(none)', }} showHiddenCategories={true} @@ -117,6 +88,6 @@ function Transfer({ Transfer </Button> </View> - </> + </View> ); } diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx index a63e573fbde8e8e91cfc88a028c97fc629234f63..b1f65c7f9c8d32481a19978c60bc80d080ecc4c8 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx @@ -192,7 +192,6 @@ export function BudgetSummary({ month }: BudgetSummaryProps) { }} > <ToBudget - showTotalsTooltipOnHover={true} prevMonthName={prevMonthName} month={month} onBudgetAction={onBudgetAction} diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx index 5630ad92bfec8c4b9f7778793b43a4427858402d..8d5234b1c5f602b5c032ab893af695bffc1b449c 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx @@ -1,12 +1,13 @@ -import React, { useState, type ComponentPropsWithoutRef } from 'react'; +import React, { useRef, useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; import { type CSSProperties } from '../../../../style'; +import { Popover } from '../../../common/Popover'; +import { View } from '../../../common/View'; import { useSheetValue } from '../../../spreadsheet/useSheetValue'; -import { Tooltip } from '../../../tooltips'; -import { HoldTooltip } from '../HoldTooltip'; -import { TransferTooltip } from '../TransferTooltip'; +import { HoldMenu } from '../HoldMenu'; +import { TransferMenu } from '../TransferMenu'; import { ToBudgetAmount } from './ToBudgetAmount'; import { ToBudgetMenu } from './ToBudgetMenu'; @@ -15,27 +16,18 @@ type ToBudgetProps = { month: string; onBudgetAction: (month: string, action: string, arg?: unknown) => void; prevMonthName: string; - showTotalsTooltipOnHover?: boolean; style?: CSSProperties; amountStyle?: CSSProperties; - menuTooltipProps?: ComponentPropsWithoutRef<typeof Tooltip>; - totalsTooltipProps?: ComponentPropsWithoutRef<typeof Tooltip>; - holdTooltipProps?: ComponentPropsWithoutRef<typeof HoldTooltip>; - transferTooltipProps?: ComponentPropsWithoutRef<typeof TransferTooltip>; }; export function ToBudget({ month, prevMonthName, - showTotalsTooltipOnHover, onBudgetAction, style, amountStyle, - menuTooltipProps, - totalsTooltipProps, - holdTooltipProps, - transferTooltipProps, }: ToBudgetProps) { const [menuOpen, setMenuOpen] = useState<string | null>(null); + const triggerRef = useRef(null); const sheetValue = useSheetValue({ name: rolloverBudget.toBudget, value: 0, @@ -44,22 +36,23 @@ export function ToBudget({ return ( <> - <ToBudgetAmount - onClick={() => setMenuOpen('actions')} - prevMonthName={prevMonthName} - showTotalsTooltipOnHover={showTotalsTooltipOnHover} - totalsTooltipProps={totalsTooltipProps} - style={style} - amountStyle={amountStyle} - /> - {menuOpen === 'actions' && ( - <Tooltip - position="bottom-center" - width={200} - style={{ padding: 0 }} - onClose={() => setMenuOpen(null)} - {...menuTooltipProps} - > + <View ref={triggerRef}> + <ToBudgetAmount + onClick={() => setMenuOpen('actions')} + prevMonthName={prevMonthName} + style={style} + amountStyle={amountStyle} + /> + </View> + + <Popover + triggerRef={triggerRef} + placement="bottom" + isOpen={!!menuOpen} + onOpenChange={() => setMenuOpen(null)} + style={{ width: 200 }} + > + {menuOpen === 'actions' && ( <ToBudgetMenu onTransfer={() => setMenuOpen('transfer')} onHoldBuffer={() => setMenuOpen('buffer')} @@ -68,30 +61,28 @@ export function ToBudget({ setMenuOpen(null); }} /> - </Tooltip> - )} - {menuOpen === 'buffer' && ( - <HoldTooltip - onClose={() => setMenuOpen(null)} - onSubmit={amount => { - onBudgetAction(month, 'hold', { amount }); - }} - {...holdTooltipProps} - /> - )} - {menuOpen === 'transfer' && ( - <TransferTooltip - initialAmount={availableValue} - onClose={() => setMenuOpen(null)} - onSubmit={(amount, category) => { - onBudgetAction(month, 'transfer-available', { - amount, - category, - }); - }} - {...transferTooltipProps} - /> - )} + )} + {menuOpen === 'buffer' && ( + <HoldMenu + onClose={() => setMenuOpen(null)} + onSubmit={amount => { + onBudgetAction(month, 'hold', { amount }); + }} + /> + )} + {menuOpen === 'transfer' && ( + <TransferMenu + initialAmount={availableValue} + onClose={() => setMenuOpen(null)} + onSubmit={(amount, category) => { + onBudgetAction(month, 'transfer-available', { + amount, + category, + }); + }} + /> + )} + </Popover> </> ); } diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx index 4764903c74b7f230f4211ee94bc4177800e6a980..693fb3a57f5e3d6fc59b52ca2d65d51b20b50894 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx @@ -1,4 +1,4 @@ -import React, { type ComponentPropsWithoutRef } from 'react'; +import React from 'react'; import { css } from 'glamor'; @@ -6,20 +6,17 @@ import { rolloverBudget } from 'loot-core/src/client/queries'; import { theme, styles, type CSSProperties } from '../../../../style'; import { Block } from '../../../common/Block'; -import { HoverTarget } from '../../../common/HoverTarget'; +import { Tooltip } from '../../../common/Tooltip'; import { View } from '../../../common/View'; import { PrivacyFilter } from '../../../PrivacyFilter'; import { useFormat } from '../../../spreadsheet/useFormat'; import { useSheetName } from '../../../spreadsheet/useSheetName'; import { useSheetValue } from '../../../spreadsheet/useSheetValue'; -import { Tooltip } from '../../../tooltips'; import { TotalsList } from './TotalsList'; type ToBudgetAmountProps = { prevMonthName: string; - showTotalsTooltipOnHover?: boolean; - totalsTooltipProps?: ComponentPropsWithoutRef<typeof Tooltip>; style?: CSSProperties; amountStyle?: CSSProperties; onClick: () => void; @@ -27,8 +24,6 @@ type ToBudgetAmountProps = { export function ToBudgetAmount({ prevMonthName, - showTotalsTooltipOnHover, - totalsTooltipProps, style, amountStyle, onClick, @@ -47,18 +42,17 @@ export function ToBudgetAmount({ <View style={{ alignItems: 'center', ...style }}> <Block>{isNegative ? 'Overbudgeted:' : 'To Budget:'}</Block> <View> - <HoverTarget - disabled={!showTotalsTooltipOnHover} - renderContent={() => ( - <Tooltip position="bottom-center" {...totalsTooltipProps}> - <TotalsList - prevMonthName={prevMonthName} - style={{ - padding: 7, - }} - /> - </Tooltip> - )} + <Tooltip + content={ + <TotalsList + prevMonthName={prevMonthName} + style={{ + padding: 7, + }} + /> + } + placement="bottom" + triggerProps={{ delay: 0 }} > <PrivacyFilter blurIntensity={7}> <Block @@ -85,7 +79,7 @@ export function ToBudgetAmount({ {format(num, 'financial')} </Block> </PrivacyFilter> - </HoverTarget> + </Tooltip> </View> </View> ); diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx index 105f703aefdb59c162e95f900ff5a7b2120d12c9..709c7295bdd13c010182af4f8fe238dc03ae1c7e 100644 --- a/packages/desktop-client/src/components/common/Menu.tsx +++ b/packages/desktop-client/src/components/common/Menu.tsx @@ -167,11 +167,13 @@ export function Menu<T extends MenuItem>({ }} onPointerEnter={() => setHoveredIndex(idx)} onPointerLeave={() => setHoveredIndex(null)} - onClick={() => - !item.disabled && - item.toggle === undefined && - onMenuSelect?.(item.name) - } + onClick={e => { + e.stopPropagation(); + + if (!item.disabled && item.toggle === undefined) { + onMenuSelect?.(item.name); + } + }} > {/* Force it to line up evenly */} {item.toggle === undefined ? ( diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx index 8df4a8c76df7f6c66467fd4d1d892eb24b220c8e..1a2f3b06c1d4eb9cef37f7776a4693e8c0bbf0c2 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx @@ -506,7 +506,7 @@ describe('Transactions', () => { // The category field should still be editing expectToBeEditingField(container, 'category', 2); // No dropdown should be open - expect(container.querySelector('[data-testid="autocomplete"]')).toBe(null); + expect(screen.queryByTestId('autocomplete')).toBe(null); // Pressing enter should now move down await userEvent.type(input, '[Enter]'); diff --git a/upcoming-release-notes/2724.md b/upcoming-release-notes/2724.md new file mode 100644 index 0000000000000000000000000000000000000000..8c6ed88381b89c15c217bd2e245d7f329eccec7b --- /dev/null +++ b/upcoming-release-notes/2724.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Migrating native `Tooltip` component to react-aria Tooltip/Popover (vol.4)