From 64cd6ee3c9812bc6a467931129eb915064c59998 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins <matiss@mja.lv> Date: Sun, 16 Jun 2024 14:31:10 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(tooltip)=20refactoring=20?= =?UTF-8?q?to=20react-aria=20(vol.9)=20(#2826)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/accounts/Header.jsx | 145 +++++++++--------- .../src/components/accounts/Reconcile.jsx | 39 +++-- .../src/components/common/HoverTarget.tsx | 54 ------- .../src/components/common/MenuTooltip.tsx | 22 --- .../src/components/filters/FiltersMenu.jsx | 35 ++--- .../desktop-client/src/components/table.tsx | 69 ++++----- upcoming-release-notes/2826.md | 6 + 7 files changed, 137 insertions(+), 233 deletions(-) delete mode 100644 packages/desktop-client/src/components/common/HoverTarget.tsx delete mode 100644 packages/desktop-client/src/components/common/MenuTooltip.tsx create mode 100644 upcoming-release-notes/2826.md diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index a3daf4b75..0ce452e8e 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -19,7 +19,7 @@ import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Menu } from '../common/Menu'; import { MenuButton } from '../common/MenuButton'; -import { MenuTooltip } from '../common/MenuTooltip'; +import { Popover } from '../common/Popover'; import { Search } from '../common/Search'; import { Stack } from '../common/Stack'; import { View } from '../common/View'; @@ -29,7 +29,7 @@ import { NotesButton } from '../NotesButton'; import { SelectedTransactionsButton } from '../transactions/SelectedTransactions'; import { Balances } from './Balance'; -import { ReconcilingMessage, ReconcileTooltip } from './Reconcile'; +import { ReconcilingMessage, ReconcileMenu } from './Reconcile'; export function AccountHeader({ filteredAmount, @@ -86,6 +86,7 @@ export function AccountHeader({ }) { const [menuOpen, setMenuOpen] = useState(false); const searchInput = useRef(null); + const triggerRef = useRef(null); const splitsExpanded = useSplitsExpanded(); const syncServerStatus = useSyncServerStatus(); const isUsingServer = syncServerStatus !== 'no-server'; @@ -338,9 +339,14 @@ export function AccountHeader({ </Button> {account ? ( <View> - <MenuButton onClick={() => setMenuOpen(true)} /> + <MenuButton ref={triggerRef} onClick={() => setMenuOpen(true)} /> - {menuOpen && ( + <Popover + triggerRef={triggerRef} + style={{ width: 275 }} + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} + > <AccountMenu account={account} canSync={canSync} @@ -356,22 +362,31 @@ export function AccountHeader({ onReconcile={onReconcile} onClose={() => setMenuOpen(false)} /> - )} + </Popover> </View> ) : ( <View> - <MenuButton onClick={() => setMenuOpen(true)} /> + <MenuButton ref={triggerRef} onClick={() => setMenuOpen(true)} /> - {menuOpen && ( - <CategoryMenu + <Popover + triggerRef={triggerRef} + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} + > + <Menu onMenuSelect={item => { setMenuOpen(false); onMenuSelect(item); }} - onClose={() => setMenuOpen(false)} - isSorted={isSorted} + items={[ + isSorted && { + name: 'remove-sorting', + text: 'Remove all sorting', + }, + { name: 'export', text: 'Export' }, + ]} /> - )} + </Popover> </View> )} </Stack> @@ -418,76 +433,54 @@ function AccountMenu({ const syncServerStatus = useSyncServerStatus(); return tooltip === 'reconcile' ? ( - <ReconcileTooltip + <ReconcileMenu account={account} onClose={onClose} onReconcile={onReconcile} /> ) : ( - <MenuTooltip width={200} onClose={onClose}> - <Menu - onMenuSelect={item => { - if (item === 'reconcile') { - setTooltip('reconcile'); - } else { - onMenuSelect(item); - } - }} - items={[ - isSorted && { - name: 'remove-sorting', - text: 'Remove all sorting', - }, - canShowBalances && { - name: 'toggle-balance', - text: (showBalances ? 'Hide' : 'Show') + ' running balance', - }, - { - name: 'toggle-cleared', - text: (showCleared ? 'Hide' : 'Show') + ' “cleared†checkboxes', - }, - { - name: 'toggle-reconciled', - text: - (showReconciled ? 'Hide' : 'Show') + ' reconciled transactions', - }, - { name: 'export', text: 'Export' }, - { name: 'reconcile', text: 'Reconcile' }, - account && - !account.closed && - (canSync - ? { - name: 'unlink', - text: 'Unlink account', - } - : syncServerStatus === 'online' && { - name: 'link', - text: 'Link account', - }), - account.closed - ? { name: 'reopen', text: 'Reopen account' } - : { name: 'close', text: 'Close account' }, - ].filter(x => x)} - /> - </MenuTooltip> - ); -} - -function CategoryMenu({ onClose, onMenuSelect, isSorted }) { - return ( - <MenuTooltip width={200} onClose={onClose}> - <Menu - onMenuSelect={item => { + <Menu + onMenuSelect={item => { + if (item === 'reconcile') { + setTooltip('reconcile'); + } else { onMenuSelect(item); - }} - items={[ - isSorted && { - name: 'remove-sorting', - text: 'Remove all sorting', - }, - { name: 'export', text: 'Export' }, - ]} - /> - </MenuTooltip> + } + }} + items={[ + isSorted && { + name: 'remove-sorting', + text: 'Remove all sorting', + }, + canShowBalances && { + name: 'toggle-balance', + text: (showBalances ? 'Hide' : 'Show') + ' running balance', + }, + { + name: 'toggle-cleared', + text: (showCleared ? 'Hide' : 'Show') + ' “cleared†checkboxes', + }, + { + name: 'toggle-reconciled', + text: (showReconciled ? 'Hide' : 'Show') + ' reconciled transactions', + }, + { name: 'export', text: 'Export' }, + { name: 'reconcile', text: 'Reconcile' }, + account && + !account.closed && + (canSync + ? { + name: 'unlink', + text: 'Unlink account', + } + : syncServerStatus === 'online' && { + name: 'link', + text: 'Link account', + }), + account.closed + ? { name: 'reopen', text: 'Reopen account' } + : { name: 'close', text: 'Close account' }, + ].filter(x => x)} + /> ); } diff --git a/packages/desktop-client/src/components/accounts/Reconcile.jsx b/packages/desktop-client/src/components/accounts/Reconcile.jsx index 76fc01da3..d80668b8e 100644 --- a/packages/desktop-client/src/components/accounts/Reconcile.jsx +++ b/packages/desktop-client/src/components/accounts/Reconcile.jsx @@ -12,7 +12,6 @@ import { Text } from '../common/Text'; import { View } from '../common/View'; import { useFormat } from '../spreadsheet/useFormat'; import { useSheetValue } from '../spreadsheet/useSheetValue'; -import { Tooltip } from '../tooltips'; export function ReconcilingMessage({ balanceQuery, @@ -95,7 +94,7 @@ export function ReconcilingMessage({ ); } -export function ReconcileTooltip({ account, onReconcile, onClose }) { +export function ReconcileMenu({ account, onReconcile, onClose }) { const balanceQuery = queries.accountBalance(account); const clearedBalance = useSheetValue({ name: balanceQuery.name + '-cleared', @@ -117,24 +116,22 @@ export function ReconcileTooltip({ account, onReconcile, onClose }) { } return ( - <Tooltip position="bottom-right" width={275} onClose={onClose}> - <View style={{ padding: '5px 8px' }}> - <Text> - Enter the current balance of your bank account that you want to - reconcile with: - </Text> - <form onSubmit={onSubmit}> - {clearedBalance != null && ( - <InitialFocus> - <Input - defaultValue={format(clearedBalance, 'financial')} - style={{ margin: '7px 0' }} - /> - </InitialFocus> - )} - <Button type="primary">Reconcile</Button> - </form> - </View> - </Tooltip> + <View style={{ padding: '5px 8px' }}> + <Text> + Enter the current balance of your bank account that you want to + reconcile with: + </Text> + <form onSubmit={onSubmit}> + {clearedBalance != null && ( + <InitialFocus> + <Input + defaultValue={format(clearedBalance, 'financial')} + style={{ margin: '7px 0' }} + /> + </InitialFocus> + )} + <Button type="primary">Reconcile</Button> + </form> + </View> ); } diff --git a/packages/desktop-client/src/components/common/HoverTarget.tsx b/packages/desktop-client/src/components/common/HoverTarget.tsx deleted file mode 100644 index ec3d4e015..000000000 --- a/packages/desktop-client/src/components/common/HoverTarget.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useCallback, useEffect, useState, type ReactNode } from 'react'; - -import { type CSSProperties } from '../../style'; - -import { View } from './View'; - -type HoverTargetProps = { - style?: CSSProperties; - contentStyle?: CSSProperties; - children: ReactNode; - renderContent: () => ReactNode; - disabled?: boolean; -}; - -export function HoverTarget({ - style, - contentStyle, - children, - renderContent, - disabled, -}: HoverTargetProps) { - const [hovered, setHovered] = useState(false); - - const onPointerEnter = useCallback(() => { - if (!disabled) { - setHovered(true); - } - }, [disabled]); - - const onPointerLeave = useCallback(() => { - if (!disabled) { - setHovered(false); - } - }, [disabled]); - - useEffect(() => { - if (disabled && hovered) { - setHovered(false); - } - }, [disabled, hovered]); - - return ( - <View style={style}> - <View - onPointerEnter={onPointerEnter} - onPointerLeave={onPointerLeave} - style={contentStyle} - > - {children} - </View> - {hovered && renderContent()} - </View> - ); -} diff --git a/packages/desktop-client/src/components/common/MenuTooltip.tsx b/packages/desktop-client/src/components/common/MenuTooltip.tsx deleted file mode 100644 index 2b255d77f..000000000 --- a/packages/desktop-client/src/components/common/MenuTooltip.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { type ReactNode } from 'react'; - -import { Tooltip } from '../tooltips'; - -type MenuTooltipProps = { - width: number; - onClose: () => void; - children: ReactNode; -}; - -export function MenuTooltip({ width, onClose, children }: MenuTooltipProps) { - return ( - <Tooltip - position="bottom-right" - width={width} - style={{ padding: 0 }} - onClose={onClose} - > - {children} - </Tooltip> - ); -} diff --git a/packages/desktop-client/src/components/filters/FiltersMenu.jsx b/packages/desktop-client/src/components/filters/FiltersMenu.jsx index b8b79edae..ac1f48d6a 100644 --- a/packages/desktop-client/src/components/filters/FiltersMenu.jsx +++ b/packages/desktop-client/src/components/filters/FiltersMenu.jsx @@ -21,16 +21,15 @@ import { import { titleFirst } from 'loot-core/src/shared/util'; import { useDateFormat } from '../../hooks/useDateFormat'; -import { theme } from '../../style'; +import { styles, theme } from '../../style'; import { Button } from '../common/Button'; -import { HoverTarget } from '../common/HoverTarget'; import { Menu } from '../common/Menu'; import { Popover } from '../common/Popover'; import { Select } from '../common/Select'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; +import { Tooltip } from '../common/Tooltip'; import { View } from '../common/View'; -import { Tooltip } from '../tooltips'; import { GenericInput } from '../util/GenericInput'; import { CompactFiltersButton } from './CompactFiltersButton'; @@ -321,23 +320,17 @@ export function FilterButton({ onApply, compact, hover, exclude }) { return ( <View> <View ref={triggerRef}> - <HoverTarget - style={{ flexShrink: 0 }} - renderContent={() => - hover && ( - <Tooltip - position="bottom-left" - style={{ - lineHeight: 1.5, - padding: '6px 10px', - backgroundColor: theme.menuBackground, - color: theme.menuItemText, - }} - > - <Text>Filters</Text> - </Tooltip> - ) - } + <Tooltip + style={{ + ...styles.tooltip, + lineHeight: 1.5, + padding: '6px 10px', + }} + content={<Text>Filters</Text>} + placement="bottom start" + triggerProps={{ + isDisabled: !hover, + }} > {compact ? ( <CompactFiltersButton @@ -346,7 +339,7 @@ export function FilterButton({ onApply, compact, hover, exclude }) { ) : ( <FiltersButton onClick={() => dispatch({ type: 'select-field' })} /> )} - </HoverTarget> + </Tooltip> </View> <Popover diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx index cfd2ad90e..d321e6052 100644 --- a/packages/desktop-client/src/components/table.tsx +++ b/packages/desktop-client/src/components/table.tsx @@ -31,6 +31,7 @@ import { type CSSProperties, styles, theme } from '../style'; import { Button } from './common/Button'; import { Input } from './common/Input'; import { Menu } from './common/Menu'; +import { Popover } from './common/Popover'; import { Text } from './common/Text'; import { View } from './common/View'; import { FixedSizeList } from './FixedSizeList'; @@ -41,7 +42,7 @@ import { import { type Binding } from './spreadsheet'; import { type FormatType, useFormat } from './spreadsheet/useFormat'; import { useSheetValue } from './spreadsheet/useSheetValue'; -import { Tooltip, IntersectionBoundary } from './tooltips'; +import { IntersectionBoundary } from './tooltips'; export const ROW_HEIGHT = 32; @@ -383,38 +384,24 @@ type InputCellProps = ComponentProps<typeof Cell> & { onUpdate?: ComponentProps<typeof InputValue>['onUpdate']; onBlur?: ComponentProps<typeof InputValue>['onBlur']; textAlign?: CSSProperties['textAlign']; - error?: ReactNode; }; export function InputCell({ inputProps, onUpdate, onBlur, textAlign, - error, ...props }: InputCellProps) { return ( <Cell textAlign={textAlign} {...props}> {() => ( - <> - <InputValue - value={props.value} - onUpdate={onUpdate} - onBlur={onBlur} - style={{ textAlign, ...(inputProps && inputProps.style) }} - {...inputProps} - /> - {error && ( - <Tooltip - key="error" - targetHeight={ROW_HEIGHT} - width={180} - position="bottom-left" - > - {error} - </Tooltip> - )} - </> + <InputValue + value={props.value} + onUpdate={onUpdate} + onBlur={onBlur} + style={{ textAlign, ...(inputProps && inputProps.style) }} + {...inputProps} + /> )} </Cell> ); @@ -809,6 +796,7 @@ export function TableHeader({ export function SelectedItemsButton({ name, items, onSelect }) { const selectedItems = useSelectedItems(); const [menuOpen, setMenuOpen] = useState(null); + const triggerRef = useRef(null); if (selectedItems.size === 0) { return null; @@ -817,6 +805,7 @@ export function SelectedItemsButton({ name, items, onSelect }) { return ( <View style={{ marginLeft: 10, flexShrink: 0 }}> <Button + ref={triggerRef} type="bare" style={{ color: theme.pageTextPositive }} onClick={() => setMenuOpen(true)} @@ -830,23 +819,25 @@ export function SelectedItemsButton({ name, items, onSelect }) { {selectedItems.size} {name} </Button> - {menuOpen && ( - <Tooltip - position="bottom-right" - width={200} - style={{ padding: 0, backgroundColor: theme.menuBackground }} - onClose={() => setMenuOpen(false)} - data-testid={name + '-select-tooltip'} - > - <Menu - onMenuSelect={name => { - onSelect(name, [...selectedItems]); - setMenuOpen(false); - }} - items={items} - /> - </Tooltip> - )} + <Popover + triggerRef={triggerRef} + style={{ + width: 200, + padding: 0, + backgroundColor: theme.menuBackground, + }} + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} + data-testid={name + '-select-tooltip'} + > + <Menu + onMenuSelect={name => { + onSelect(name, [...selectedItems]); + setMenuOpen(false); + }} + items={items} + /> + </Popover> </View> ); } diff --git a/upcoming-release-notes/2826.md b/upcoming-release-notes/2826.md new file mode 100644 index 000000000..4ca032999 --- /dev/null +++ b/upcoming-release-notes/2826.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Migrating native `Tooltip` component to react-aria Tooltip/Popover (vol.9) -- GitLab