Skip to content
Snippets Groups Projects
Unverified Commit 16df116d authored by Matiss Janis Aboltins's avatar Matiss Janis Aboltins Committed by GitHub
Browse files

:recycle: (tooltip) migration to react-aria (vol 2) (#2593)

parent d7075ae5
No related branches found
No related tags found
No related merge requests found
// @ts-strict-ignore // @ts-strict-ignore
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types'; import { type State } from 'loot-core/src/client/state-types';
...@@ -9,10 +9,10 @@ import { theme, styles, type CSSProperties } from '../style'; ...@@ -9,10 +9,10 @@ import { theme, styles, type CSSProperties } from '../style';
import { Button } from './common/Button'; import { Button } from './common/Button';
import { Menu } from './common/Menu'; import { Menu } from './common/Menu';
import { Popover } from './common/Popover';
import { Text } from './common/Text'; import { Text } from './common/Text';
import { View } from './common/View'; import { View } from './common/View';
import { useServerURL } from './ServerContext'; import { useServerURL } from './ServerContext';
import { Tooltip } from './tooltips';
type LoggedInUserProps = { type LoggedInUserProps = {
hideIfNoServer?: boolean; hideIfNoServer?: boolean;
...@@ -29,6 +29,7 @@ export function LoggedInUser({ ...@@ -29,6 +29,7 @@ export function LoggedInUser({
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const serverUrl = useServerURL(); const serverUrl = useServerURL();
const triggerRef = useRef(null);
useEffect(() => { useEffect(() => {
getUserData().then(() => setLoading(false)); getUserData().then(() => setLoading(false));
...@@ -95,6 +96,7 @@ export function LoggedInUser({ ...@@ -95,6 +96,7 @@ export function LoggedInUser({
return ( return (
<View style={{ flexDirection: 'row', alignItems: 'center', ...style }}> <View style={{ flexDirection: 'row', alignItems: 'center', ...style }}>
<Button <Button
ref={triggerRef}
type="bare" type="bare"
onClick={() => setMenuOpen(true)} onClick={() => setMenuOpen(true)}
style={color && { color }} style={color && { color }}
...@@ -102,29 +104,27 @@ export function LoggedInUser({ ...@@ -102,29 +104,27 @@ export function LoggedInUser({
{serverMessage()} {serverMessage()}
</Button> </Button>
{menuOpen && ( <Popover
<Tooltip triggerRef={triggerRef}
position="bottom-right" isOpen={menuOpen}
style={{ padding: 0 }} onOpenChange={() => setMenuOpen(false)}
onClose={() => setMenuOpen(false)} >
> <Menu
<Menu onMenuSelect={onMenuSelect}
onMenuSelect={onMenuSelect} items={[
items={[ serverUrl &&
serverUrl && !userData?.offline && {
!userData?.offline && { name: 'change-password',
name: 'change-password', text: 'Change password',
text: 'Change password',
},
serverUrl && { name: 'sign-out', text: 'Sign out' },
{
name: 'config-server',
text: serverUrl ? 'Change server URL' : 'Start using a server',
}, },
]} serverUrl && { name: 'sign-out', text: 'Sign out' },
/> {
</Tooltip> name: 'config-server',
)} text: serverUrl ? 'Change server URL' : 'Start using a server',
},
]}
/>
</Popover>
</View> </View>
); );
} }
import React, { useEffect, useRef, useState, type ComponentProps } from 'react'; 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 { useLiveQuery } from 'loot-core/src/client/query-hooks';
import { send } from 'loot-core/src/platform/client/fetch'; import { send } from 'loot-core/src/platform/client/fetch';
...@@ -7,9 +6,10 @@ import { q } from 'loot-core/src/shared/query'; ...@@ -7,9 +6,10 @@ import { q } from 'loot-core/src/shared/query';
import { type NoteEntity } from 'loot-core/types/models'; import { type NoteEntity } from 'loot-core/types/models';
import { SvgCustomNotesPaper } from '../icons/v2'; import { SvgCustomNotesPaper } from '../icons/v2';
import { type CSSProperties, styles, theme } from '../style'; import { type CSSProperties, theme } from '../style';
import { Button } from './common/Button'; import { Button } from './common/Button';
import { Popover } from './common/Popover';
import { Tooltip } from './common/Tooltip'; import { Tooltip } from './common/Tooltip';
import { View } from './common/View'; import { View } from './common/View';
import { Notes } from './Notes'; import { Notes } from './Notes';
...@@ -81,7 +81,7 @@ export function NotesButton({ ...@@ -81,7 +81,7 @@ export function NotesButton({
isOpen={isOpen} isOpen={isOpen}
onOpenChange={onClose} onOpenChange={onClose}
placement={tooltipPosition} placement={tooltipPosition}
style={{ ...styles.tooltip, marginTop: -8 }} style={{ padding: 4 }}
> >
<Notes notes={tempNotes} editable focused onChange={setTempNotes} /> <Notes notes={tempNotes} editable focused onChange={setTempNotes} />
</Popover> </Popover>
......
import React, { useState } from 'react'; import React, { useRef, useState } from 'react';
import type { Theme } from 'loot-core/src/types/prefs'; import type { Theme } from 'loot-core/src/types/prefs';
...@@ -8,7 +8,7 @@ import { type CSSProperties, themeOptions, useTheme } from '../style'; ...@@ -8,7 +8,7 @@ import { type CSSProperties, themeOptions, useTheme } from '../style';
import { Button } from './common/Button'; import { Button } from './common/Button';
import { Menu } from './common/Menu'; import { Menu } from './common/Menu';
import { Tooltip } from './tooltips'; import { Popover } from './common/Popover';
type ThemeSelectorProps = { type ThemeSelectorProps = {
style?: CSSProperties; style?: CSSProperties;
...@@ -17,6 +17,7 @@ type ThemeSelectorProps = { ...@@ -17,6 +17,7 @@ type ThemeSelectorProps = {
export function ThemeSelector({ style }: ThemeSelectorProps) { export function ThemeSelector({ style }: ThemeSelectorProps) {
const [theme, switchTheme] = useTheme(); const [theme, switchTheme] = useTheme();
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
const { isNarrowWidth } = useResponsive(); const { isNarrowWidth } = useResponsive();
...@@ -34,26 +35,32 @@ export function ThemeSelector({ style }: ThemeSelectorProps) { ...@@ -34,26 +35,32 @@ export function ThemeSelector({ style }: ThemeSelectorProps) {
const Icon = themeIcons[theme] || SvgSun; const Icon = themeIcons[theme] || SvgSun;
return isNarrowWidth ? null : ( if (isNarrowWidth) {
<Button return null;
type="bare" }
aria-label="Switch theme"
onClick={() => setMenuOpen(true)} return (
style={style} <>
> <Button
<Icon style={{ width: 13, height: 13, color: 'inherit' }} /> ref={triggerRef}
{menuOpen && ( type="bare"
<Tooltip aria-label="Switch theme"
position="bottom-right" onClick={() => setMenuOpen(true)}
style={{ padding: 0 }} style={style}
onClose={() => setMenuOpen(false)} >
> <Icon style={{ width: 13, height: 13, color: 'inherit' }} />
<Menu </Button>
onMenuSelect={onMenuSelect}
items={themeOptions.map(([name, text]) => ({ name, text }))} <Popover
/> triggerRef={triggerRef}
</Tooltip> isOpen={menuOpen}
)} onOpenChange={() => setMenuOpen(false)}
</Button> >
<Menu
onMenuSelect={onMenuSelect}
items={themeOptions.map(([name, text]) => ({ name, text }))}
/>
</Popover>
</>
); );
} }
...@@ -35,6 +35,7 @@ import { MonthCountSelector } from './budget/MonthCountSelector'; ...@@ -35,6 +35,7 @@ import { MonthCountSelector } from './budget/MonthCountSelector';
import { Button, ButtonWithLoading } from './common/Button'; import { Button, ButtonWithLoading } from './common/Button';
import { Link } from './common/Link'; import { Link } from './common/Link';
import { Paragraph } from './common/Paragraph'; import { Paragraph } from './common/Paragraph';
import { Popover } from './common/Popover';
import { Text } from './common/Text'; import { Text } from './common/Text';
import { View } from './common/View'; import { View } from './common/View';
import { LoggedInUser } from './LoggedInUser'; import { LoggedInUser } from './LoggedInUser';
...@@ -42,7 +43,6 @@ import { useServerURL } from './ServerContext'; ...@@ -42,7 +43,6 @@ import { useServerURL } from './ServerContext';
import { useSidebar } from './sidebar/SidebarProvider'; import { useSidebar } from './sidebar/SidebarProvider';
import { useSheetValue } from './spreadsheet/useSheetValue'; import { useSheetValue } from './spreadsheet/useSheetValue';
import { ThemeSelector } from './ThemeSelector'; import { ThemeSelector } from './ThemeSelector';
import { Tooltip } from './tooltips';
export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type'; export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type';
...@@ -290,7 +290,8 @@ function BudgetTitlebar() { ...@@ -290,7 +290,8 @@ function BudgetTitlebar() {
const { sendEvent } = useContext(TitlebarContext); const { sendEvent } = useContext(TitlebarContext);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showTooltip, setShowTooltip] = useState(false); const [showPopover, setShowPopover] = useState(false);
const triggerRef = useRef(null);
const reportBudgetEnabled = useFeatureFlag('reportBudget'); const reportBudgetEnabled = useFeatureFlag('reportBudget');
...@@ -320,6 +321,7 @@ function BudgetTitlebar() { ...@@ -320,6 +321,7 @@ function BudgetTitlebar() {
{reportBudgetEnabled && ( {reportBudgetEnabled && (
<View style={{ marginLeft: -5 }}> <View style={{ marginLeft: -5 }}>
<ButtonWithLoading <ButtonWithLoading
ref={triggerRef}
type="bare" type="bare"
loading={loading} loading={loading}
style={{ style={{
...@@ -327,52 +329,48 @@ function BudgetTitlebar() { ...@@ -327,52 +329,48 @@ function BudgetTitlebar() {
padding: '4px 7px', padding: '4px 7px',
}} }}
title="Learn more about budgeting" title="Learn more about budgeting"
onClick={() => setShowTooltip(true)} onClick={() => setShowPopover(true)}
> >
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'} {budgetType === 'report' ? 'Report budget' : 'Rollover budget'}
</ButtonWithLoading> </ButtonWithLoading>
{showTooltip && (
<Tooltip <Popover
position="bottom-left" triggerRef={triggerRef}
onClose={() => setShowTooltip(false)} placement="bottom start"
style={{ isOpen={showPopover}
padding: 10, onOpenChange={() => setShowPopover(false)}
maxWidth: 400, style={{
}} padding: 10,
> maxWidth: 400,
<Paragraph> }}
You are currently using a{' '} >
<Text style={{ fontWeight: 600 }}> <Paragraph>
{budgetType === 'report' You are currently using a{' '}
? 'Report budget' <Text style={{ fontWeight: 600 }}>
: 'Rollover budget'} {budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
. </Text>{' '}
</Text>{' '} Switching will not lose any data and you can always switch back.
Switching will not lose any data and you can always switch back. </Paragraph>
</Paragraph> <Paragraph>
<Paragraph> <ButtonWithLoading
<ButtonWithLoading type="primary"
type="primary" loading={loading}
loading={loading} onClick={onSwitchType}
onClick={onSwitchType} >
> Switch to a{' '}
Switch to a{' '} {budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
{budgetType === 'report' </ButtonWithLoading>
? 'Rollover budget' </Paragraph>
: 'Report budget'} <Paragraph isLast={true}>
</ButtonWithLoading> <Link
</Paragraph> variant="external"
<Paragraph isLast={true}> to="https://actualbudget.org/docs/experimental/report-budget"
<Link linkColor="muted"
variant="external" >
to="https://actualbudget.org/docs/experimental/report-budget" How do these types of budgeting work?
linkColor="muted" </Link>
> </Paragraph>
How do these types of budgeting work? </Popover>
</Link>
</Paragraph>
</Tooltip>
)}
</View> </View>
)} )}
</View> </View>
......
import React, { useState } from 'react'; import React, { useRef, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
...@@ -9,8 +9,8 @@ import { SvgExclamationOutline } from '../../icons/v1'; ...@@ -9,8 +9,8 @@ import { SvgExclamationOutline } from '../../icons/v1';
import { theme } from '../../style'; import { theme } from '../../style';
import { Button } from '../common/Button'; import { Button } from '../common/Button';
import { Link } from '../common/Link'; import { Link } from '../common/Link';
import { Popover } from '../common/Popover';
import { View } from '../common/View'; import { View } from '../common/View';
import { Tooltip } from '../tooltips';
function getErrorMessage(type, code) { function getErrorMessage(type, code) {
switch (type.toUpperCase()) { switch (type.toUpperCase()) {
...@@ -56,6 +56,8 @@ export function AccountSyncCheck() { ...@@ -56,6 +56,8 @@ export function AccountSyncCheck() {
const { id } = useParams(); const { id } = useParams();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const triggerRef = useRef(null);
if (!failedAccounts) { if (!failedAccounts) {
return null; return null;
} }
...@@ -85,6 +87,7 @@ export function AccountSyncCheck() { ...@@ -85,6 +87,7 @@ export function AccountSyncCheck() {
return ( return (
<View> <View>
<Button <Button
ref={triggerRef}
type="bare" type="bare"
style={{ style={{
flexDirection: 'row', flexDirection: 'row',
...@@ -102,38 +105,34 @@ export function AccountSyncCheck() { ...@@ -102,38 +105,34 @@ export function AccountSyncCheck() {
This account is experiencing connection problems. Let’s fix it. This account is experiencing connection problems. Let’s fix it.
</Button> </Button>
{open && ( <Popover
<Tooltip triggerRef={triggerRef}
position="bottom-left" placement="bottom start"
onClose={() => setOpen(false)} isOpen={open}
style={{ fontSize: 14, padding: 15, maxWidth: 400 }} onOpenChange={() => setOpen(false)}
> style={{ fontSize: 14, padding: 15, maxWidth: 400 }}
<div style={{ marginBottom: '1.15em' }}> >
The server returned the following error: <div style={{ marginBottom: '1.15em' }}>
</div> The server returned the following error:
</div>
<div style={{ marginBottom: '1.25em', color: theme.errorText }}>
{getErrorMessage(error.type, error.code)} <div style={{ marginBottom: '1.25em', color: theme.errorText }}>
</div> {getErrorMessage(error.type, error.code)}
</div>
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
{showAuth ? ( <View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
<> {showAuth ? (
<Button onClick={unlink}>Unlink</Button> <>
<Button <Button onClick={unlink}>Unlink</Button>
type="primary" <Button type="primary" onClick={reauth} style={{ marginLeft: 5 }}>
onClick={reauth} Reauthorize
style={{ marginLeft: 5 }} </Button>
> </>
Reauthorize ) : (
</Button> <Button onClick={unlink}>Unlink account</Button>
</> )}
) : ( </View>
<Button onClick={unlink}>Unlink account</Button> </Popover>
)}
</View>
</Tooltip>
)}
</View> </View>
); );
} }
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}
/>
);
};
import React, { useState } from 'react'; import React, { useRef, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { import {
...@@ -20,9 +20,9 @@ import { Button } from '../common/Button'; ...@@ -20,9 +20,9 @@ import { Button } from '../common/Button';
import { InitialFocus } from '../common/InitialFocus'; import { InitialFocus } from '../common/InitialFocus';
import { Input } from '../common/Input'; import { Input } from '../common/Input';
import { Menu } from '../common/Menu'; import { Menu } from '../common/Menu';
import { Popover } from '../common/Popover';
import { Text } from '../common/Text'; import { Text } from '../common/Text';
import { View } from '../common/View'; import { View } from '../common/View';
import { Tooltip } from '../tooltips';
import { Accounts } from './Accounts'; import { Accounts } from './Accounts';
import { Item } from './Item'; import { Item } from './Item';
...@@ -145,6 +145,7 @@ function EditableBudgetName() { ...@@ -145,6 +145,7 @@ function EditableBudgetName() {
const [budgetName, setBudgetNamePref] = useLocalPref('budgetName'); const [budgetName, setBudgetNamePref] = useLocalPref('budgetName');
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
function onMenuSelect(type: string) { function onMenuSelect(type: string) {
setMenuOpen(false); setMenuOpen(false);
...@@ -195,9 +196,12 @@ function EditableBudgetName() { ...@@ -195,9 +196,12 @@ function EditableBudgetName() {
/> />
</InitialFocus> </InitialFocus>
); );
} else { }
return (
return (
<>
<Button <Button
ref={triggerRef}
type="bare" type="bare"
color={theme.buttonNormalBorder} color={theme.buttonNormalBorder}
style={{ style={{
...@@ -212,16 +216,16 @@ function EditableBudgetName() { ...@@ -212,16 +216,16 @@ function EditableBudgetName() {
{budgetName || 'A budget has no name'} {budgetName || 'A budget has no name'}
</Text> </Text>
<SvgExpandArrow width={7} height={7} style={{ marginLeft: 5 }} /> <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> </Button>
);
} <Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu onMenuSelect={onMenuSelect} items={items} />
</Popover>
</>
);
} }
---
category: Maintenance
authors: [MatissJanis]
---
Migrating native `Tooltip` component to react-aria Tooltip/Popover (vol.2)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment