mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
♻️ (tooltip) migration to react-aria (vol 2) (#2593)
This commit is contained in:
committed by
GitHub
parent
d7075ae551
commit
16df116d1d
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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. Let’s 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>
|
||||
);
|
||||
}
|
||||
|
||||
17
packages/desktop-client/src/components/common/Popover.tsx
Normal file
17
packages/desktop-client/src/components/common/Popover.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
6
upcoming-release-notes/2593.md
Normal file
6
upcoming-release-notes/2593.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Migrating native `Tooltip` component to react-aria Tooltip/Popover (vol.2)
|
||||
Reference in New Issue
Block a user