Mobile Payees - move to react-aria GridList to improve performance (#5802)

This commit is contained in:
Matiss Janis Aboltins
2025-09-27 21:54:16 +01:00
committed by GitHub
parent 6365a8f4bb
commit 3559b2df3a
5 changed files with 108 additions and 86 deletions

View File

@@ -1,5 +1,5 @@
---
description:
description:
globs: *.ts,*.tsx
alwaysApply: false
---
@@ -21,7 +21,7 @@ Naming Conventions
TypeScript Usage
- Use TypeScript for all code; prefer interfaces over types.
- Use TypeScript for all code; prefer types over interfaces.
- Avoid enums; use objects or maps instead.
- Avoid using `any` or `unknown` unless absolutely necessary. Look for type definitions in the codebase instead.
- Avoid type assertions with `as` or `!`; prefer using `satisfies`.

View File

@@ -154,4 +154,10 @@ export const styles: Record<string, any> = {
borderRadius: 4,
padding: '3px 5px',
},
mobileListItem: {
borderBottom: `1px solid ${theme.tableBorder}`,
backgroundColor: theme.tableBackground,
padding: 16,
cursor: 'pointer',
},
};

View File

@@ -1,4 +1,5 @@
import { Trans } from 'react-i18next';
import { GridList } from 'react-aria-components';
import { Trans, useTranslation } from 'react-i18next';
import { AnimatedLoading } from '@actual-app/components/icons/AnimatedLoading';
import { Text } from '@actual-app/components/text';
@@ -24,6 +25,8 @@ export function PayeesList({
isLoading = false,
onPayeePress,
}: PayeesListProps) {
const { t } = useTranslation();
if (isLoading && payees.length === 0) {
return (
<View
@@ -63,22 +66,30 @@ export function PayeesList({
}
return (
<View
style={{ flex: 1, paddingBottom: MOBILE_NAV_HEIGHT, overflow: 'auto' }}
>
{payees.map(payee => (
<PayeesListItem
key={payee.id}
payee={payee}
ruleCount={ruleCounts.get(payee.id) ?? 0}
onPress={() => onPayeePress(payee)}
/>
))}
<View>
<GridList
aria-label={t('Payees')}
aria-busy={isLoading || undefined}
items={payees}
style={{
flex: 1,
paddingBottom: MOBILE_NAV_HEIGHT,
overflow: 'auto',
}}
>
{payee => (
<PayeesListItem
value={payee}
ruleCount={ruleCounts.get(payee.id) ?? 0}
onAction={() => onPayeePress(payee)}
/>
)}
</GridList>
{isLoading && (
<View
style={{
alignItems: 'center',
paddingVertical: 20,
paddingTop: 20,
}}
>
<AnimatedLoading style={{ width: 20, height: 20 }} />

View File

@@ -1,9 +1,10 @@
import React from 'react';
import React, { memo } from 'react';
import { GridListItem, type GridListItemProps } from 'react-aria-components';
import { useTranslation } from 'react-i18next';
import { Button } from '@actual-app/components/button';
import { SvgBookmark } from '@actual-app/components/icons/v1';
import { SpaceBetween } from '@actual-app/components/space-between';
import { styles } from '@actual-app/components/styles';
import { theme } from '@actual-app/components/theme';
import { type PayeeEntity } from 'loot-core/types/models';
@@ -11,84 +12,82 @@ import { type PayeeEntity } from 'loot-core/types/models';
import { PayeeRuleCountLabel } from '@desktop-client/components/payees/PayeeRuleCountLabel';
type PayeesListItemProps = {
payee: PayeeEntity;
value: PayeeEntity;
ruleCount: number;
onPress: () => void;
};
} & Omit<GridListItemProps<PayeeEntity>, 'value'>;
export function PayeesListItem({
payee,
export const PayeesListItem = memo(function PayeeListItem({
value: payee,
ruleCount,
onPress,
...props
}: PayeesListItemProps) {
const { t } = useTranslation();
return (
<Button
variant="bare"
style={{
minHeight: 56,
width: '100%',
borderRadius: 0,
borderWidth: '0 0 1px 0',
borderColor: theme.tableBorder,
borderStyle: 'solid',
backgroundColor: theme.tableBackground,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
padding: '12px 16px',
gap: 5,
}}
onPress={onPress}
>
{payee.favorite && (
<SvgBookmark
width={15}
height={15}
style={{
color: theme.pageText,
flexShrink: 0,
}}
/>
)}
<SpaceBetween
style={{
justifyContent: 'space-between',
flex: 1,
alignItems: 'flex-start',
}}
>
<span
style={{
fontSize: 15,
fontWeight: 500,
color: payee.transfer_acct ? theme.pageTextSubdued : theme.pageText,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,
textAlign: 'left',
}}
title={payee.name}
>
{(payee.transfer_acct ? t('Transfer: ') : '') + payee.name}
</span>
const label = payee.transfer_acct
? t('Transfer: {{name}}', { name: payee.name })
: payee.name;
<span
return (
<GridListItem
id={payee.id}
value={payee}
textValue={label}
style={styles.mobileListItem}
{...props}
>
<SpaceBetween gap={5}>
{payee.favorite && (
<SvgBookmark
aria-hidden
focusable={false}
width={15}
height={15}
style={{
color: theme.pageText,
flexShrink: 0,
}}
/>
)}
<SpaceBetween
style={{
borderRadius: 4,
padding: '3px 6px',
backgroundColor: theme.noticeBackground,
border: '1px solid ' + theme.noticeBackground,
color: theme.noticeTextDark,
fontSize: 12,
flexShrink: 0,
justifyContent: 'space-between',
flex: 1,
alignItems: 'flex-start',
}}
>
<PayeeRuleCountLabel count={ruleCount} style={{ fontSize: 12 }} />
</span>
<span
style={{
fontSize: 15,
fontWeight: 500,
color: payee.transfer_acct
? theme.pageTextSubdued
: theme.pageText,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,
textAlign: 'left',
}}
title={label}
>
{label}
</span>
<span
style={{
borderRadius: 4,
padding: '3px 6px',
backgroundColor: theme.noticeBackground,
border: '1px solid ' + theme.noticeBackground,
color: theme.noticeTextDark,
fontSize: 12,
flexShrink: 0,
}}
>
<PayeeRuleCountLabel count={ruleCount} style={{ fontSize: 12 }} />
</span>
</SpaceBetween>
</SpaceBetween>
</Button>
</GridListItem>
);
}
});

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Mobile payee list - moved to react-aria GridList to improve performance