♻️ (tooltip) refactoring to react-aria (vol.6) (#2771)

* ♻️ (tooltip) refactoring to react-aria (vol.6)

* Release notes
This commit is contained in:
Matiss Janis Aboltins
2024-06-03 11:56:39 +01:00
committed by GitHub
parent c92266fd7f
commit 0e86dea544
10 changed files with 252 additions and 251 deletions

View File

@@ -28,9 +28,9 @@ import { styles, theme } from '../../style';
import { tokens } from '../../tokens';
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 { Tooltip } from '../tooltips';
function getFileDescription(file) {
if (file.state === 'unknown') {
@@ -84,11 +84,13 @@ function FileMenu({ onDelete, onClose }) {
}
function FileMenuButton({ state, onDelete }) {
const triggerRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);
return (
<View>
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={e => {
@@ -98,19 +100,18 @@ function FileMenuButton({ state, onDelete }) {
>
<SvgDotsHorizontalTriple style={{ width: 16, height: 16 }} />
</Button>
{menuOpen && (
<Tooltip
position="bottom-right"
style={{ padding: 0 }}
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<FileMenu
state={state}
onDelete={onDelete}
onClose={() => setMenuOpen(false)}
>
<FileMenu
state={state}
onDelete={onDelete}
onClose={() => setMenuOpen(false)}
/>
</Tooltip>
)}
/>
</Popover>
</View>
);
}

View File

@@ -1,4 +1,4 @@
import React, { type ComponentProps, useState } from 'react';
import React, { type ComponentProps, useRef, useState } from 'react';
import { type AccountEntity } from 'loot-core/types/models';
@@ -10,10 +10,10 @@ import { type CSSProperties, styles, theme } from '../../style';
import { Button } from '../common/Button';
import { Menu } from '../common/Menu';
import { Modal, ModalTitle } from '../common/Modal';
import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { type CommonModalProps } from '../Modals';
import { Notes } from '../Notes';
import { Tooltip } from '../tooltips';
type AccountMenuModalProps = {
modalProps: CommonModalProps;
@@ -155,6 +155,7 @@ function AdditionalAccountMenu({
onClose,
onReopen,
}: AdditionalAccountMenuProps) {
const triggerRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);
const itemStyle: CSSProperties = {
...styles.mediumText,
@@ -169,6 +170,7 @@ function AdditionalAccountMenu({
return (
<View>
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={() => {
@@ -180,47 +182,44 @@ function AdditionalAccountMenu({
height={17}
style={{ color: 'currentColor' }}
/>
{menuOpen && (
<Tooltip
position="bottom-left"
style={{ padding: 0 }}
onClose={() => {
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
placement="bottom start"
onOpenChange={() => setMenuOpen(false)}
>
<Menu
getItemStyle={getItemStyle}
items={[
account.closed
? {
name: 'reopen',
text: 'Reopen account',
icon: SvgLockOpen,
iconSize: 15,
}
: {
name: 'close',
text: 'Close account',
icon: SvgClose,
iconSize: 15,
},
]}
onMenuSelect={name => {
setMenuOpen(false);
switch (name) {
case 'close':
onClose?.(account.id);
break;
case 'reopen':
onReopen?.(account.id);
break;
default:
throw new Error(`Unrecognized menu option: ${name}`);
}
}}
>
<Menu
getItemStyle={getItemStyle}
items={[
account.closed
? {
name: 'reopen',
text: 'Reopen account',
icon: SvgLockOpen,
iconSize: 15,
}
: {
name: 'close',
text: 'Close account',
icon: SvgClose,
iconSize: 15,
},
]}
onMenuSelect={name => {
setMenuOpen(false);
switch (name) {
case 'close':
onClose?.(account.id);
break;
case 'reopen':
onReopen?.(account.id);
break;
default:
throw new Error(`Unrecognized menu option: ${name}`);
}
}}
/>
</Tooltip>
)}
/>
</Popover>
</Button>
</View>
);

View File

@@ -1,5 +1,5 @@
// @ts-strict-ignore
import React, { type ComponentProps, useState } from 'react';
import React, { type ComponentProps, useRef, useState } from 'react';
import { type CategoryGroupEntity } from 'loot-core/src/types/models';
@@ -11,10 +11,10 @@ import { type CSSProperties, styles, theme } from '../../style';
import { Button } from '../common/Button';
import { Menu } from '../common/Menu';
import { Modal, ModalTitle } from '../common/Modal';
import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { type CommonModalProps } from '../Modals';
import { Notes } from '../Notes';
import { Tooltip } from '../tooltips';
type CategoryGroupMenuModalProps = {
modalProps: CommonModalProps;
@@ -155,6 +155,7 @@ export function CategoryGroupMenuModal({
}
function AdditionalCategoryGroupMenu({ group, onDelete, onToggleVisibility }) {
const triggerRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);
const itemStyle: CSSProperties = {
...styles.mediumText,
@@ -170,6 +171,7 @@ function AdditionalCategoryGroupMenu({ group, onDelete, onToggleVisibility }) {
<View>
{!group.is_income && (
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={() => {
@@ -181,52 +183,47 @@ function AdditionalCategoryGroupMenu({ group, onDelete, onToggleVisibility }) {
height={17}
style={{ color: 'currentColor' }}
/>
{menuOpen && (
<Tooltip
position="bottom-left"
style={{ padding: 0 }}
onClose={() => {
setMenuOpen(false);
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
placement="bottom start"
onOpenChange={() => setMenuOpen(false)}
>
<Menu
style={{
...styles.mediumText,
color: theme.formLabelText,
}}
>
<Menu
style={{
...styles.mediumText,
color: theme.formLabelText,
}}
getItemStyle={getItemStyle}
items={
[
getItemStyle={getItemStyle}
items={
[
{
name: 'toggleVisibility',
text: group.hidden ? 'Show' : 'Hide',
icon: group.hidden ? SvgViewShow : SvgViewHide,
iconSize: 16,
},
...(!group.is_income && [
Menu.line,
{
name: 'toggleVisibility',
text: group.hidden ? 'Show' : 'Hide',
icon: group.hidden ? SvgViewShow : SvgViewHide,
iconSize: 16,
name: 'delete',
text: 'Delete',
icon: SvgTrash,
iconSize: 15,
},
...(!group.is_income && [
Menu.line,
{
name: 'delete',
text: 'Delete',
icon: SvgTrash,
iconSize: 15,
},
]),
].filter(i => i != null) as ComponentProps<
typeof Menu
>['items']
]),
].filter(i => i != null) as ComponentProps<typeof Menu>['items']
}
onMenuSelect={itemName => {
setMenuOpen(false);
if (itemName === 'delete') {
onDelete();
} else if (itemName === 'toggleVisibility') {
onToggleVisibility();
}
onMenuSelect={itemName => {
setMenuOpen(false);
if (itemName === 'delete') {
onDelete();
} else if (itemName === 'toggleVisibility') {
onToggleVisibility();
}
}}
/>
</Tooltip>
)}
}}
/>
</Popover>
</Button>
)}
</View>

View File

@@ -1,5 +1,5 @@
// @ts-strict-ignore
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { type CategoryEntity } from 'loot-core/src/types/models';
@@ -12,10 +12,10 @@ import { type CSSProperties, styles, theme } from '../../style';
import { Button } from '../common/Button';
import { Menu } from '../common/Menu';
import { Modal, ModalTitle } from '../common/Modal';
import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { type CommonModalProps } from '../Modals';
import { Notes } from '../Notes';
import { Tooltip } from '../tooltips';
type CategoryMenuModalProps = {
modalProps: CommonModalProps;
@@ -147,6 +147,7 @@ function AdditionalCategoryMenu({
onDelete,
onToggleVisibility,
}) {
const triggerRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);
const itemStyle: CSSProperties = {
...styles.mediumText,
@@ -161,6 +162,7 @@ function AdditionalCategoryMenu({
return (
<View>
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={() => {
@@ -172,42 +174,39 @@ function AdditionalCategoryMenu({
height={17}
style={{ color: 'currentColor' }}
/>
{menuOpen && (
<Tooltip
position="bottom-left"
style={{ padding: 0 }}
onClose={() => {
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
placement="bottom start"
onOpenChange={() => setMenuOpen(false)}
>
<Menu
getItemStyle={getItemStyle}
items={[
!categoryGroup?.hidden && {
name: 'toggleVisibility',
text: category.hidden ? 'Show' : 'Hide',
icon: category.hidden ? SvgViewShow : SvgViewHide,
iconSize: 16,
},
!categoryGroup?.hidden && Menu.line,
{
name: 'delete',
text: 'Delete',
icon: SvgTrash,
iconSize: 15,
},
]}
onMenuSelect={itemName => {
setMenuOpen(false);
if (itemName === 'delete') {
onDelete();
} else if (itemName === 'toggleVisibility') {
onToggleVisibility();
}
}}
>
<Menu
getItemStyle={getItemStyle}
items={[
!categoryGroup?.hidden && {
name: 'toggleVisibility',
text: category.hidden ? 'Show' : 'Hide',
icon: category.hidden ? SvgViewShow : SvgViewHide,
iconSize: 16,
},
!categoryGroup?.hidden && Menu.line,
{
name: 'delete',
text: 'Delete',
icon: SvgTrash,
iconSize: 15,
},
]}
onMenuSelect={itemName => {
setMenuOpen(false);
if (itemName === 'delete') {
onDelete();
} else if (itemName === 'toggleVisibility') {
onToggleVisibility();
}
}}
/>
</Tooltip>
)}
/>
</Popover>
</Button>
</View>
);

View File

@@ -40,9 +40,9 @@ import { Modal } from '../common/Modal';
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 { StatusBadge } from '../schedules/StatusBadge';
import { Tooltip } from '../tooltips';
import { SimpleTransactionsTable } from '../transactions/SimpleTransactionsTable';
import { BetweenAmountInput } from '../util/AmountInput';
import { DisplayId } from '../util/DisplayId';
@@ -415,33 +415,29 @@ function ActionEditor({ action, editorStyle, onChange, onDelete, onAdd }) {
}
function StageInfo() {
const [open, setOpen] = useState();
return (
<View style={{ position: 'relative', marginLeft: 5 }}>
<View
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
<Tooltip
content={
<>
The stage of a rule allows you to force a specific order. Pre rules
always run first, and post rules always run last. Within each stage
rules are automatically ordered from least to most specific.
</>
}
placement="bottom start"
style={{
...styles.tooltip,
padding: 10,
color: theme.pageTextLight,
maxWidth: 450,
lineHeight: 1.5,
}}
>
<SvgInformationOutline
style={{ width: 11, height: 11, color: theme.pageTextLight }}
/>
</View>
{open && (
<Tooltip
position="bottom-left"
style={{
padding: 10,
color: theme.pageTextLight,
maxWidth: 450,
lineHeight: 1.5,
}}
>
The stage of a rule allows you to force a specific order. Pre rules
always run first, and post rules always run last. Within each stage
rules are automatically ordered from least to most specific.
</Tooltip>
)}
</Tooltip>
</View>
);
}

View File

@@ -20,10 +20,10 @@ import { Link } from '../common/Link';
import { Menu } from '../common/Menu';
import { Modal } from '../common/Modal';
import { Paragraph } from '../common/Paragraph';
import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { FormField, FormLabel } from '../forms';
import { type CommonModalProps } from '../Modals';
import { Tooltip } from '../tooltips';
import { COUNTRY_OPTIONS } from './countries';
@@ -103,6 +103,7 @@ export function GoCardlessExternalMsg({
>(null);
const [menuOpen, setMenuOpen] = useState<boolean>(false);
const data = useRef<GoCardlessToken | null>(null);
const triggerRef = useRef(null);
const {
data: bankOptions,
@@ -230,6 +231,7 @@ export function GoCardlessExternalMsg({
Link bank in browser &rarr;
</Button>
<Button
ref={triggerRef}
type="bare"
onClick={() => setMenuOpen(true)}
aria-label="Menu"
@@ -239,28 +241,27 @@ export function GoCardlessExternalMsg({
height={15}
style={{ transform: 'rotateZ(90deg)' }}
/>
{menuOpen && (
<Tooltip
position="bottom-right"
width={200}
style={{ padding: 0 }}
onClose={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onGoCardlessInit();
}
}}
items={[
{
name: 'reconfigure',
text: 'Set new API secrets',
},
]}
/>
</Tooltip>
)}
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
style={{ width: 200 }}
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
if (item === 'reconfigure') {
onGoCardlessInit();
}
}}
items={[
{
name: 'reconfigure',
text: 'Set new API secrets',
},
]}
/>
</Popover>
</Button>
</View>
</View>

View File

@@ -23,6 +23,7 @@ import { useStableCallback } from '../../hooks/useStableCallback';
import { SvgExpandArrow } from '../../icons/v0';
import { theme } from '../../style';
import { Button } from '../common/Button';
import { Popover } from '../common/Popover';
import { Search } from '../common/Search';
import { View } from '../common/View';
import { TableHeader, Cell, SelectCell, useTableNavigator } from '../table';
@@ -102,6 +103,7 @@ export const ManagePayees = forwardRef(
const [filter, setFilter] = useState('');
const table = useRef(null);
const scrollTo = useRef(null);
const triggerRef = useRef(null);
const resetAnimation = useRef(false);
const [orphanedOnly, setOrphanedOnly] = useState(false);
@@ -233,6 +235,7 @@ export const ManagePayees = forwardRef(
>
<View style={{ flexShrink: 0 }}>
<Button
ref={triggerRef}
type="bare"
style={{ marginRight: 10 }}
disabled={buttonsDisabled}
@@ -245,7 +248,14 @@ export const ManagePayees = forwardRef(
plural(selected.items.size, 'payee', 'payees')}
<SvgExpandArrow width={8} height={8} style={{ marginLeft: 5 }} />
</Button>
{menuOpen && (
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
placement="bottom start"
style={{ width: 250 }}
onOpenChange={() => setMenuOpen(false)}
>
<PayeeMenu
payeesById={payeesById}
selectedPayees={selected.items}
@@ -253,7 +263,7 @@ export const ManagePayees = forwardRef(
onDelete={onDelete}
onMerge={onMerge}
/>
)}
</Popover>
</View>
<View
style={{

View File

@@ -4,7 +4,6 @@ import { SvgDelete, SvgMerge } from '../../icons/v0';
import { theme } from '../../style';
import { Menu } from '../common/Menu';
import { View } from '../common/View';
import { Tooltip } from '../tooltips';
type PayeeMenuProps = {
payeesById: Record<PayeeEntity['id'], PayeeEntity>;
@@ -27,57 +26,50 @@ export function PayeeMenu({
);
return (
<Tooltip
position="bottom"
width={250}
style={{ padding: 0 }}
onClose={onClose}
>
<Menu
onMenuSelect={type => {
onClose();
switch (type) {
case 'delete':
onDelete();
break;
case 'merge':
onMerge();
break;
default:
}
}}
footer={
<View
style={{
padding: 3,
fontSize: 11,
fontStyle: 'italic',
color: theme.pageTextSubdued,
}}
>
{[...selectedPayees]
.slice(0, 4)
.map(id => payeesById[id].name)
.join(', ') + (selectedPayees.size > 4 ? ', and more' : '')}
</View>
<Menu
onMenuSelect={type => {
onClose();
switch (type) {
case 'delete':
onDelete();
break;
case 'merge':
onMerge();
break;
default:
}
items={[
{
icon: SvgDelete,
name: 'delete',
text: 'Delete',
disabled: isDisabled,
},
{
icon: SvgMerge,
iconSize: 9,
name: 'merge',
text: 'Merge',
disabled: isDisabled || selectedPayees.size < 2,
},
Menu.line,
]}
/>
</Tooltip>
}}
footer={
<View
style={{
padding: 3,
fontSize: 11,
fontStyle: 'italic',
color: theme.pageTextSubdued,
}}
>
{[...selectedPayees]
.slice(0, 4)
.map(id => payeesById[id].name)
.join(', ') + (selectedPayees.size > 4 ? ', and more' : '')}
</View>
}
items={[
{
icon: SvgDelete,
name: 'delete',
text: 'Delete',
disabled: isDisabled,
},
{
icon: SvgMerge,
iconSize: 9,
name: 'merge',
text: 'Merge',
disabled: isDisabled || selectedPayees.size < 2,
},
Menu.line,
]}
/>
);
}

View File

@@ -1,5 +1,5 @@
// @ts-strict-ignore
import React, { useState, useMemo, type CSSProperties } from 'react';
import React, { useRef, useState, useMemo, type CSSProperties } from 'react';
import {
type ScheduleStatusType,
@@ -18,11 +18,11 @@ import { SvgCheck } from '../../icons/v2';
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 { PrivacyFilter } from '../PrivacyFilter';
import { Table, TableHeader, Row, Field, Cell } from '../table';
import { Tooltip } from '../tooltips';
import { DisplayId } from '../util/DisplayId';
import { StatusBadge } from './StatusBadge';
@@ -60,6 +60,7 @@ function OverflowMenu({
status: ScheduleStatusType;
onAction: SchedulesTableProps['onAction'];
}) {
const triggerRef = useRef(null);
const [open, setOpen] = useState(false);
const getMenuItems = () => {
@@ -96,6 +97,7 @@ function OverflowMenu({
return (
<View>
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={e => {
@@ -109,22 +111,20 @@ function OverflowMenu({
style={{ transform: 'rotateZ(90deg)' }}
/>
</Button>
{open && (
<Tooltip
position="bottom-right"
width={150}
style={{ padding: 0 }}
onClose={() => setOpen(false)}
>
<Menu
onMenuSelect={name => {
onAction(name, schedule.id);
setOpen(false);
}}
items={getMenuItems()}
/>
</Tooltip>
)}
<Popover
triggerRef={triggerRef}
isOpen={open}
onOpenChange={() => setOpen(false)}
>
<Menu
onMenuSelect={name => {
onAction(name, schedule.id);
setOpen(false);
}}
items={getMenuItems()}
/>
</Popover>
</View>
);
}

View File

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