♻️ (tooltip) migration to react-aria (vol 2) (#2593)

This commit is contained in:
Matiss Janis Aboltins
2024-04-14 11:30:57 +02:00
committed by GitHub
parent d7075ae551
commit 16df116d1d
8 changed files with 173 additions and 142 deletions

View File

@@ -1,5 +1,5 @@
// @ts-strict-ignore
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
@@ -9,10 +9,10 @@ import { theme, styles, type CSSProperties } 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 { useServerURL } from './ServerContext';
import { Tooltip } from './tooltips';
type LoggedInUserProps = {
hideIfNoServer?: boolean;
@@ -29,6 +29,7 @@ export function LoggedInUser({
const [loading, setLoading] = useState(true);
const [menuOpen, setMenuOpen] = useState(false);
const serverUrl = useServerURL();
const triggerRef = useRef(null);
useEffect(() => {
getUserData().then(() => setLoading(false));
@@ -95,6 +96,7 @@ export function LoggedInUser({
return (
<View style={{ flexDirection: 'row', alignItems: 'center', ...style }}>
<Button
ref={triggerRef}
type="bare"
onClick={() => setMenuOpen(true)}
style={color && { color }}
@@ -102,29 +104,27 @@ export function LoggedInUser({
{serverMessage()}
</Button>
{menuOpen && (
<Tooltip
position="bottom-right"
style={{ padding: 0 }}
onClose={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={onMenuSelect}
items={[
serverUrl &&
!userData?.offline && {
name: 'change-password',
text: 'Change password',
},
serverUrl && { name: 'sign-out', text: 'Sign out' },
{
name: 'config-server',
text: serverUrl ? 'Change server URL' : 'Start using a server',
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={onMenuSelect}
items={[
serverUrl &&
!userData?.offline && {
name: 'change-password',
text: 'Change password',
},
]}
/>
</Tooltip>
)}
serverUrl && { name: 'sign-out', text: 'Sign out' },
{
name: 'config-server',
text: serverUrl ? 'Change server URL' : 'Start using a server',
},
]}
/>
</Popover>
</View>
);
}

View File

@@ -1,5 +1,4 @@
import React, { useEffect, useRef, useState, type ComponentProps } from 'react';
import { Popover } from 'react-aria-components';
import { useLiveQuery } from 'loot-core/src/client/query-hooks';
import { send } from 'loot-core/src/platform/client/fetch';
@@ -7,9 +6,10 @@ import { q } from 'loot-core/src/shared/query';
import { type NoteEntity } from 'loot-core/types/models';
import { SvgCustomNotesPaper } from '../icons/v2';
import { type CSSProperties, styles, theme } from '../style';
import { type CSSProperties, theme } from '../style';
import { Button } from './common/Button';
import { Popover } from './common/Popover';
import { Tooltip } from './common/Tooltip';
import { View } from './common/View';
import { Notes } from './Notes';
@@ -81,7 +81,7 @@ export function NotesButton({
isOpen={isOpen}
onOpenChange={onClose}
placement={tooltipPosition}
style={{ ...styles.tooltip, marginTop: -8 }}
style={{ padding: 4 }}
>
<Notes notes={tempNotes} editable focused onChange={setTempNotes} />
</Popover>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import type { Theme } from 'loot-core/src/types/prefs';
@@ -8,7 +8,7 @@ import { type CSSProperties, themeOptions, useTheme } from '../style';
import { Button } from './common/Button';
import { Menu } from './common/Menu';
import { Tooltip } from './tooltips';
import { Popover } from './common/Popover';
type ThemeSelectorProps = {
style?: CSSProperties;
@@ -17,6 +17,7 @@ type ThemeSelectorProps = {
export function ThemeSelector({ style }: ThemeSelectorProps) {
const [theme, switchTheme] = useTheme();
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
const { isNarrowWidth } = useResponsive();
@@ -34,26 +35,32 @@ export function ThemeSelector({ style }: ThemeSelectorProps) {
const Icon = themeIcons[theme] || SvgSun;
return isNarrowWidth ? null : (
<Button
type="bare"
aria-label="Switch theme"
onClick={() => setMenuOpen(true)}
style={style}
>
<Icon style={{ width: 13, height: 13, color: 'inherit' }} />
{menuOpen && (
<Tooltip
position="bottom-right"
style={{ padding: 0 }}
onClose={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={onMenuSelect}
items={themeOptions.map(([name, text]) => ({ name, text }))}
/>
</Tooltip>
)}
</Button>
if (isNarrowWidth) {
return null;
}
return (
<>
<Button
ref={triggerRef}
type="bare"
aria-label="Switch theme"
onClick={() => setMenuOpen(true)}
style={style}
>
<Icon style={{ width: 13, height: 13, color: 'inherit' }} />
</Button>
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={onMenuSelect}
items={themeOptions.map(([name, text]) => ({ name, text }))}
/>
</Popover>
</>
);
}

View File

@@ -35,6 +35,7 @@ import { MonthCountSelector } from './budget/MonthCountSelector';
import { Button, ButtonWithLoading } from './common/Button';
import { Link } from './common/Link';
import { Paragraph } from './common/Paragraph';
import { Popover } from './common/Popover';
import { Text } from './common/Text';
import { View } from './common/View';
import { LoggedInUser } from './LoggedInUser';
@@ -42,7 +43,6 @@ import { useServerURL } from './ServerContext';
import { useSidebar } from './sidebar/SidebarProvider';
import { useSheetValue } from './spreadsheet/useSheetValue';
import { ThemeSelector } from './ThemeSelector';
import { Tooltip } from './tooltips';
export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type';
@@ -290,7 +290,8 @@ function BudgetTitlebar() {
const { sendEvent } = useContext(TitlebarContext);
const [loading, setLoading] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
const [showPopover, setShowPopover] = useState(false);
const triggerRef = useRef(null);
const reportBudgetEnabled = useFeatureFlag('reportBudget');
@@ -320,6 +321,7 @@ function BudgetTitlebar() {
{reportBudgetEnabled && (
<View style={{ marginLeft: -5 }}>
<ButtonWithLoading
ref={triggerRef}
type="bare"
loading={loading}
style={{
@@ -327,52 +329,48 @@ function BudgetTitlebar() {
padding: '4px 7px',
}}
title="Learn more about budgeting"
onClick={() => setShowTooltip(true)}
onClick={() => setShowPopover(true)}
>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}
</ButtonWithLoading>
{showTooltip && (
<Tooltip
position="bottom-left"
onClose={() => setShowTooltip(false)}
style={{
padding: 10,
maxWidth: 400,
}}
>
<Paragraph>
You are currently using a{' '}
<Text style={{ fontWeight: 600 }}>
{budgetType === 'report'
? 'Report budget'
: 'Rollover budget'}
.
</Text>{' '}
Switching will not lose any data and you can always switch back.
</Paragraph>
<Paragraph>
<ButtonWithLoading
type="primary"
loading={loading}
onClick={onSwitchType}
>
Switch to a{' '}
{budgetType === 'report'
? 'Rollover budget'
: 'Report budget'}
</ButtonWithLoading>
</Paragraph>
<Paragraph isLast={true}>
<Link
variant="external"
to="https://actualbudget.org/docs/experimental/report-budget"
linkColor="muted"
>
How do these types of budgeting work?
</Link>
</Paragraph>
</Tooltip>
)}
<Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={showPopover}
onOpenChange={() => setShowPopover(false)}
style={{
padding: 10,
maxWidth: 400,
}}
>
<Paragraph>
You are currently using a{' '}
<Text style={{ fontWeight: 600 }}>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
</Text>{' '}
Switching will not lose any data and you can always switch back.
</Paragraph>
<Paragraph>
<ButtonWithLoading
type="primary"
loading={loading}
onClick={onSwitchType}
>
Switch to a{' '}
{budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
</ButtonWithLoading>
</Paragraph>
<Paragraph isLast={true}>
<Link
variant="external"
to="https://actualbudget.org/docs/experimental/report-budget"
linkColor="muted"
>
How do these types of budgeting work?
</Link>
</Paragraph>
</Popover>
</View>
)}
</View>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
@@ -9,8 +9,8 @@ import { SvgExclamationOutline } from '../../icons/v1';
import { theme } from '../../style';
import { Button } from '../common/Button';
import { Link } from '../common/Link';
import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { Tooltip } from '../tooltips';
function getErrorMessage(type, code) {
switch (type.toUpperCase()) {
@@ -56,6 +56,8 @@ export function AccountSyncCheck() {
const { id } = useParams();
const [open, setOpen] = useState(false);
const triggerRef = useRef(null);
if (!failedAccounts) {
return null;
}
@@ -85,6 +87,7 @@ export function AccountSyncCheck() {
return (
<View>
<Button
ref={triggerRef}
type="bare"
style={{
flexDirection: 'row',
@@ -102,38 +105,34 @@ export function AccountSyncCheck() {
This account is experiencing connection problems. Lets fix it.
</Button>
{open && (
<Tooltip
position="bottom-left"
onClose={() => setOpen(false)}
style={{ fontSize: 14, padding: 15, maxWidth: 400 }}
>
<div style={{ marginBottom: '1.15em' }}>
The server returned the following error:
</div>
<Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={open}
onOpenChange={() => setOpen(false)}
style={{ fontSize: 14, padding: 15, maxWidth: 400 }}
>
<div style={{ marginBottom: '1.15em' }}>
The server returned the following error:
</div>
<div style={{ marginBottom: '1.25em', color: theme.errorText }}>
{getErrorMessage(error.type, error.code)}
</div>
<div style={{ marginBottom: '1.25em', color: theme.errorText }}>
{getErrorMessage(error.type, error.code)}
</div>
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
{showAuth ? (
<>
<Button onClick={unlink}>Unlink</Button>
<Button
type="primary"
onClick={reauth}
style={{ marginLeft: 5 }}
>
Reauthorize
</Button>
</>
) : (
<Button onClick={unlink}>Unlink account</Button>
)}
</View>
</Tooltip>
)}
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
{showAuth ? (
<>
<Button onClick={unlink}>Unlink</Button>
<Button type="primary" onClick={reauth} style={{ marginLeft: 5 }}>
Reauthorize
</Button>
</>
) : (
<Button onClick={unlink}>Unlink account</Button>
)}
</View>
</Popover>
</View>
);
}

View File

@@ -0,0 +1,17 @@
import { type ComponentProps } from 'react';
import { Popover as ReactAriaPopover } from 'react-aria-components';
import { styles } from '../../style';
type PopoverProps = ComponentProps<typeof ReactAriaPopover>;
export const Popover = ({ style = {}, ...props }: PopoverProps) => {
return (
<ReactAriaPopover
placement="bottom end"
offset={0}
style={{ ...styles.tooltip, padding: 0, ...style }}
{...props}
/>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import {
@@ -20,9 +20,9 @@ import { Button } from '../common/Button';
import { InitialFocus } from '../common/InitialFocus';
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 { Tooltip } from '../tooltips';
import { Accounts } from './Accounts';
import { Item } from './Item';
@@ -145,6 +145,7 @@ function EditableBudgetName() {
const [budgetName, setBudgetNamePref] = useLocalPref('budgetName');
const [editing, setEditing] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
function onMenuSelect(type: string) {
setMenuOpen(false);
@@ -195,9 +196,12 @@ function EditableBudgetName() {
/>
</InitialFocus>
);
} else {
return (
}
return (
<>
<Button
ref={triggerRef}
type="bare"
color={theme.buttonNormalBorder}
style={{
@@ -212,16 +216,16 @@ function EditableBudgetName() {
{budgetName || 'A budget has no name'}
</Text>
<SvgExpandArrow width={7} height={7} style={{ marginLeft: 5 }} />
{menuOpen && (
<Tooltip
position="bottom-left"
style={{ padding: 0 }}
onClose={() => setMenuOpen(false)}
>
<Menu onMenuSelect={onMenuSelect} items={items} />
</Tooltip>
)}
</Button>
);
}
<Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu onMenuSelect={onMenuSelect} items={items} />
</Popover>
</>
);
}

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Migrating native `Tooltip` component to react-aria Tooltip/Popover (vol.2)