[Maintenance] Use Page component for mobile pages (#1993)

* Use Page component for mobile pages

* Release notes

* Use Button instead of Link in MobileBackButton

* Update mobile budget table to use Page component

* Settings page cleanup

* Fix lint error

* Updates + small font size increase in  page headings

* Fix rebase error

* Button height

* Revert payees navtab
This commit is contained in:
Joel Jeremy Marquez
2023-12-05 11:18:35 -08:00
committed by GitHub
parent 7a45d467b7
commit dc872647a9
34 changed files with 684 additions and 892 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -0,0 +1,48 @@
import React from 'react';
import useNavigate from '../hooks/useNavigate';
import CheveronLeft from '../icons/v1/CheveronLeft';
import { type CSSProperties, styles, theme } from '../style';
import Button from './common/Button';
import Text from './common/Text';
type MobileBackButtonProps = {
style?: CSSProperties;
};
export default function MobileBackButton({ style }: MobileBackButtonProps) {
const navigate = useNavigate();
return (
<Button
type="bare"
style={{
color: theme.mobileHeaderText,
justifyContent: 'center',
margin: 10,
paddingLeft: 5,
paddingRight: 3,
...style,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
onPointerUp={() => navigate(-1)}
>
<CheveronLeft
style={{ width: 30, height: 30, margin: -10, marginLeft: -5 }}
/>
<Text
style={{
...styles.text,
fontWeight: 500,
marginLeft: 5,
marginRight: 5,
}}
>
Back
</Text>
</Button>
);
}

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import React, { type ComponentPropsWithoutRef, type ReactNode } from 'react';
import { useResponsive } from '../ResponsiveProvider';
import { theme, styles, type CSSProperties } from '../style';
@@ -7,18 +7,24 @@ import Text from './common/Text';
import View from './common/View';
type PageHeaderProps = {
name: ReactNode;
title: ReactNode;
titleContainerProps?: ComponentPropsWithoutRef<typeof View>;
style?: CSSProperties;
leftContentContainerProps?: ComponentPropsWithoutRef<typeof View>;
leftContent?: ReactNode;
rightContentContainerProps?: ComponentPropsWithoutRef<typeof View>;
rightContent?: ReactNode;
};
const HEADER_HEIGHT = 50;
function PageHeader({
name,
title,
titleContainerProps,
style,
leftContentContainerProps,
leftContent,
rightContentContainerProps,
rightContent,
}: PageHeaderProps) {
const { isNarrowWidth } = useResponsive();
@@ -28,8 +34,8 @@ function PageHeader({
<View
style={{
alignItems: 'center',
backgroundColor: theme.mobilePageBackground,
color: theme.mobileModalText,
backgroundColor: theme.mobileHeaderBackground,
color: theme.mobileHeaderText,
flexDirection: 'row',
flexShrink: 0,
height: HEADER_HEIGHT,
@@ -37,32 +43,40 @@ function PageHeader({
}}
>
<View
{...leftContentContainerProps}
style={{
flexBasis: '25%',
justifyContent: 'flex-start',
flexDirection: 'row',
...leftContentContainerProps?.style,
}}
>
{leftContent}
</View>
<View
role="heading"
{...titleContainerProps}
style={{
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
flexBasis: '50%',
fontSize: 18,
fontSize: 17,
fontWeight: 500,
justifyContent: 'center',
overflowY: 'auto',
...titleContainerProps?.style,
}}
>
{name}
{title}
</View>
<View
{...rightContentContainerProps}
style={{
flexBasis: '25%',
justifyContent: 'flex-end',
flexDirection: 'row',
...rightContentContainerProps?.style,
}}
>
{rightContent}
@@ -80,53 +94,88 @@ function PageHeader({
...style,
}}
>
{name}
{title}
</Text>
);
}
type PageProps = {
title: ReactNode;
titleStyle?: CSSProperties;
headerLeftContent?: ReactNode;
headerRightContent?: ReactNode;
titleContainerProps?: PageHeaderProps['titleContainerProps'];
title: PageHeaderProps['title'];
headerStyle?: CSSProperties;
headerLeftContentContainerProps?: PageHeaderProps['leftContentContainerProps'];
headerLeftContent?: PageHeaderProps['leftContent'];
headerRightContentContainerProps?: PageHeaderProps['rightContentContainerProps'];
headerRightContent?: PageHeaderProps['rightContent'];
style?: CSSProperties;
padding?: number;
childrenContainerProps?: ComponentPropsWithoutRef<typeof View>;
children: ReactNode;
footer?: ReactNode;
};
export function Page({
titleContainerProps,
title,
titleStyle,
headerStyle,
headerLeftContentContainerProps,
headerLeftContent,
headerRightContentContainerProps,
headerRightContent,
style,
padding,
childrenContainerProps,
children,
footer,
}: PageProps) {
let { isNarrowWidth } = useResponsive();
let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20;
const { isNarrowWidth } = useResponsive();
const _padding = padding != null ? padding : isNarrowWidth ? 10 : 20;
return (
<View style={isNarrowWidth ? undefined : styles.page}>
<View
style={{
...(!isNarrowWidth && styles.page),
...style,
}}
>
<PageHeader
name={title}
title={title}
titleContainerProps={titleContainerProps}
leftContentContainerProps={headerLeftContentContainerProps}
leftContent={headerLeftContent}
rightContentContainerProps={headerRightContentContainerProps}
rightContent={headerRightContent}
style={{
...titleStyle,
...(!isNarrowWidth && { paddingInline: HORIZONTAL_PADDING }),
...(!isNarrowWidth && { paddingInline: _padding }),
...headerStyle,
}}
/>
<View
style={
isNarrowWidth
? { overflowY: 'auto', padding: HORIZONTAL_PADDING }
: {
paddingLeft: HORIZONTAL_PADDING,
paddingRight: HORIZONTAL_PADDING,
flex: 1,
}
}
>
{children}
</View>
{isNarrowWidth ? (
<View
{...childrenContainerProps}
style={{
flex: 1,
overflowY: 'auto',
padding: _padding,
...childrenContainerProps?.style,
}}
>
{children}
</View>
) : (
<View
{...childrenContainerProps}
style={{
flex: 1,
paddingLeft: _padding,
paddingRight: _padding,
...childrenContainerProps?.style,
}}
>
{children}
</View>
)}
{footer}
</View>
);
}

View File

@@ -130,7 +130,7 @@ type SyncButtonProps = {
style?: CSSProperties;
isMobile?: boolean;
};
export function SyncButton({ style, isMobile = false }: SyncButtonProps) {
function SyncButton({ style, isMobile = false }: SyncButtonProps) {
let cloudFileId = useSelector(state => state.prefs.local.cloudFileId);
let { sync } = useActions();

View File

@@ -1,17 +1,15 @@
import React, { useState, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { useActions } from '../../hooks/useActions';
import Add from '../../icons/v1/Add';
import CheveronLeft from '../../icons/v1/CheveronLeft';
import SearchAlternate from '../../icons/v2/SearchAlternate';
import { theme, styles } from '../../style';
import Button from '../common/Button';
import { theme } from '../../style';
import ButtonLink from '../common/ButtonLink';
import InputWithContent from '../common/InputWithContent';
import Label from '../common/Label';
import Text from '../common/Text';
import View from '../common/View';
import MobileBackButton from '../MobileBackButton';
import { Page } from '../Page';
import PullToRefresh from '../responsive/PullToRefresh';
import CellValue from '../spreadsheet/CellValue';
import { TransactionList } from '../transactions/MobileTransaction';
@@ -63,9 +61,6 @@ function TransactionSearchInput({ accountName, onSearch }) {
);
}
const LEFT_RIGHT_FLEX_WIDTH = 70;
const BUDGET_HEADER_HEIGHT = 50;
export default function AccountDetails({
account,
prependTransactions,
@@ -90,24 +85,42 @@ export default function AccountDetails({
};
return (
<View
<Page
title={account.name}
headerLeftContent={<MobileBackButton />}
headerRightContent={
<ButtonLink
to="transactions/new"
type="bare"
aria-label="Add Transaction"
style={{
justifyContent: 'center',
color: theme.mobileHeaderText,
margin: 10,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
activeStyle={{ background: 'transparent' }}
>
<Add width={20} height={20} />
</ButtonLink>
}
padding={0}
style={{
flex: 1,
backgroundColor: theme.mobilePageBackground,
overflowY: 'hidden',
flexGrow: 1,
}}
>
<View
style={{
alignItems: 'center',
flexShrink: 0,
overflowY: 'hidden',
top: 0,
marginTop: 10,
}}
>
<AccountDetailsHeader account={account} />
<Label title="BALANCE" style={{ marginTop: 10 }} />
<Label title="BALANCE" />
<CellValue
binding={balance}
type="financial"
@@ -125,7 +138,6 @@ export default function AccountDetails({
onSearch={onSearch}
/>
</View>
<PullToRefresh onRefresh={onRefresh}>
<TransactionList
transactions={allTransactions}
@@ -139,115 +151,6 @@ export default function AccountDetails({
pushModal={pushModal}
/>
</PullToRefresh>
</View>
);
}
function AccountDetailsHeader({ account }) {
return (
<View
style={{
flexDirection: 'row',
flexShrink: 0,
height: BUDGET_HEADER_HEIGHT,
width: '100%',
backgroundColor: theme.mobileHeaderBackground,
}}
>
<View
style={{
width: LEFT_RIGHT_FLEX_WIDTH,
flexDirection: 'row',
}}
>
<Button
type="bare"
style={{
color: theme.mobileHeaderText,
justifyContent: 'center',
margin: 10,
paddingLeft: 5,
paddingRight: 3,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
>
<Link
to={-1}
style={{
...styles.noTapHighlight,
alignItems: 'center',
display: 'flex',
textDecoration: 'none',
}}
>
<CheveronLeft
style={{ width: 30, height: 30, margin: -10, marginLeft: -5 }}
/>
<Text
style={{
...styles.text,
fontWeight: 500,
marginLeft: 5,
marginRight: 5,
}}
>
Back
</Text>
</Link>
</Button>
<View
style={{
flex: 1,
}}
/>
</View>
<View
style={{
flex: 1,
fontSize: 16,
fontWeight: 500,
alignItems: 'center',
justifyContent: 'center',
color: theme.mobileHeaderText,
}}
role="heading"
>
{account.name}
</View>
<View
style={{
width: LEFT_RIGHT_FLEX_WIDTH,
flexDirection: 'row',
}}
>
<View
style={{
flex: 1,
}}
/>
<ButtonLink
to="transactions/new"
type="bare"
aria-label="Add Transaction"
style={{
justifyContent: 'center',
padding: 10,
margin: 10,
color: theme.mobileHeaderText,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
activeStyle={{ background: 'transparent' }}
>
<Add width={20} height={20} style={{ margin: -5 }} />
</ButtonLink>
</View>
</View>
</Page>
);
}

View File

@@ -158,32 +158,28 @@ function AccountList({
};
return (
<View style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}>
<Page
title="Accounts"
titleStyle={{
backgroundColor: theme.mobileHeaderBackground,
color: theme.mobileHeaderText,
fontSize: 16,
}}
headerRightContent={
<Button
type="bare"
style={{
paddingLeft: 12,
paddingRight: 12,
...noBackgroundColorStyle,
}}
activeStyle={noBackgroundColorStyle}
hoveredStyle={noBackgroundColorStyle}
onClick={onAddAccount}
>
<Add width={20} height={20} />
</Button>
}
>
{accounts.length === 0 && <EmptyMessage />}
<PullToRefresh onRefresh={onSync}>
<Page
title="Accounts"
headerRightContent={
<Button
type="bare"
style={{
...noBackgroundColorStyle,
margin: 10,
}}
activeStyle={noBackgroundColorStyle}
hoveredStyle={noBackgroundColorStyle}
onClick={onAddAccount}
>
<Add width={20} height={20} />
</Button>
}
padding={0}
style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}
>
{accounts.length === 0 && <EmptyMessage />}
<PullToRefresh onRefresh={onSync}>
<View style={{ margin: 10 }}>
{budgetedAccounts.length > 0 && (
<AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
)}
@@ -213,9 +209,9 @@ function AccountList({
onSelect={onSelectAccount}
/>
))}
</PullToRefresh>
</Page>
</View>
</View>
</PullToRefresh>
</Page>
);
}

View File

@@ -19,13 +19,12 @@ import Label from '../common/Label';
import Menu from '../common/Menu';
import Text from '../common/Text';
import View from '../common/View';
import { Page } from '../Page';
import PullToRefresh from '../responsive/PullToRefresh';
import { useServerURL } from '../ServerContext';
import CellValue from '../spreadsheet/CellValue';
import NamespaceContext from '../spreadsheet/NamespaceContext';
import useFormat from '../spreadsheet/useFormat';
import useSheetValue from '../spreadsheet/useSheetValue';
import { SyncButton } from '../Titlebar';
import { Tooltip, useTooltip } from '../tooltips';
import { AmountInput } from '../util/AmountInput';
// import {
@@ -1695,7 +1694,6 @@ export function BudgetTable(props) {
const show3Cols = width >= 360;
// let editMode = false; // neuter editMode -- sorry, not rewriting drag-n-drop right now
let currentMonth = monthUtils.currentMonth();
let format = useFormat();
const mobileShowBudgetedColPref = useSelector(state => {
@@ -1724,27 +1722,62 @@ export function BudgetTable(props) {
borderRadius: 'unset',
};
const _onSwitchBudgetType = () => {
pushModal('switch-budget-type', {
onSwitch: onSwitchBudgetType,
});
};
const onToggleHiddenCategories = () => {
savePrefs({
'budget.showHiddenCategories': !showHiddenCategories,
});
};
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month, type)}>
<View style={{ flex: 1, overflowY: 'hidden' }} data-testid="budget-table">
<BudgetHeader
currentMonth={month}
toggleDisplay={toggleDisplay}
monthBounds={monthBounds}
editMode={editMode}
onEditMode={onEditMode}
// onOpenActionSheet={onOpenActionSheet}
onPrevMonth={onPrevMonth}
onNextMonth={onNextMonth}
showHiddenCategories={showHiddenCategories}
savePrefs={savePrefs}
pushModal={pushModal}
onSwitchBudgetType={onSwitchBudgetType}
/>
<Page
padding={0}
title={
<MonthSelector
month={month}
monthBounds={monthBounds}
onPrevMonth={onPrevMonth}
onNextMonth={onNextMonth}
/>
}
headerRightContent={
!editMode ? (
<BudgetMenu
onEditMode={onEditMode}
onToggleHiddenCategories={onToggleHiddenCategories}
onSwitchBudgetType={_onSwitchBudgetType}
/>
) : (
<Button
type="bare"
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
style={{
...styles.noTapHighlight,
...styles.text,
backgroundColor: 'transparent',
color: theme.mobileHeaderText,
}}
onClick={() => onEditMode?.(false)}
>
Done
</Button>
)
}
style={{ flex: 1 }}
>
<View
style={{
flexDirection: 'row',
flex: '0 0 auto',
flexShrink: 0,
padding: 10,
paddingRight: 14,
backgroundColor: theme.tableRowHeaderBackground,
@@ -1754,7 +1787,7 @@ export function BudgetTable(props) {
>
{type === 'report' ? (
<Saved
projected={month >= currentMonth}
projected={month >= monthUtils.currentMonth()}
onClick={onShowBudgetSummary}
/>
) : (
@@ -1873,123 +1906,95 @@ export function BudgetTable(props) {
/>
</View>
</View>
<View style={{ overflowY: 'auto' }}>
<PullToRefresh onRefresh={onRefresh}>
{!editMode ? (
// <ScrollView
// ref={el => (this.list = el)}
// keyboardShouldPersistTaps="always"
// refreshControl={refreshControl}
// style={{ backgroundColor: colors.n10 }}
// automaticallyAdjustContentInsets={false}
// >
<View>
<BudgetGroups
type={type}
categoryGroups={categoryGroups}
showBudgetedCol={showBudgetedCol}
show3Cols={show3Cols}
showHiddenCategories={showHiddenCategories}
// gestures={gestures}
month={month}
editMode={editMode}
editingGroupId={editingGroupId}
onEditGroup={onEditGroup}
editingCategoryId={editingCategoryId}
onEditCategory={onEditCategory}
editingBudgetCategoryId={editingBudgetCategoryId}
onEditCategoryBudget={onEditCategoryBudget}
openBudgetActionMenuId={openBudgetActionMenuId}
onOpenBudgetActionMenu={onOpenBudgetActionMenu}
onSaveCategory={onSaveCategory}
onDeleteCategory={onDeleteCategory}
onAddCategory={onAddCategory}
onAddGroup={onAddGroup}
onSaveGroup={onSaveGroup}
onDeleteGroup={onDeleteGroup}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onBudgetAction={onBudgetAction}
/>
</View>
) : (
// </ScrollView>
// <DragDrop>
// {({
// dragging,
// onGestureEvent,
// onHandlerStateChange,
// scrollRef,
// onScroll
// }) => (
<View>
<BudgetGroups
type={type}
categoryGroups={categoryGroups}
showBudgetedCol={showBudgetedCol}
show3Cols={show3Cols}
showHiddenCategories={showHiddenCategories}
// gestures={gestures}
editMode={editMode}
editingGroupId={editingGroupId}
onEditGroup={onEditGroup}
editingCategoryId={editingCategoryId}
onEditCategory={onEditCategory}
editingBudgetCategoryId={editingBudgetCategoryId}
onEditCategoryBudget={onEditCategoryBudget}
onSaveCategory={onSaveCategory}
onDeleteCategory={onDeleteCategory}
onAddCategory={onAddCategory}
onAddGroup={onAddGroup}
onSaveGroup={onSaveGroup}
onDeleteGroup={onDeleteGroup}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onBudgetAction={onBudgetAction}
/>
</View>
<PullToRefresh onRefresh={onRefresh}>
{!editMode ? (
// <ScrollView
// ref={el => (this.list = el)}
// keyboardShouldPersistTaps="always"
// refreshControl={refreshControl}
// style={{ backgroundColor: colors.n10 }}
// automaticallyAdjustContentInsets={false}
// >
<View data-testid="budget-table">
<BudgetGroups
type={type}
categoryGroups={categoryGroups}
showBudgetedCol={showBudgetedCol}
show3Cols={show3Cols}
showHiddenCategories={showHiddenCategories}
// gestures={gestures}
month={month}
editMode={editMode}
editingGroupId={editingGroupId}
onEditGroup={onEditGroup}
editingCategoryId={editingCategoryId}
onEditCategory={onEditCategory}
editingBudgetCategoryId={editingBudgetCategoryId}
onEditCategoryBudget={onEditCategoryBudget}
openBudgetActionMenuId={openBudgetActionMenuId}
onOpenBudgetActionMenu={onOpenBudgetActionMenu}
onSaveCategory={onSaveCategory}
onDeleteCategory={onDeleteCategory}
onAddCategory={onAddCategory}
onAddGroup={onAddGroup}
onSaveGroup={onSaveGroup}
onDeleteGroup={onDeleteGroup}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onBudgetAction={onBudgetAction}
/>
</View>
) : (
// </ScrollView>
// <DragDrop>
// {({
// dragging,
// onGestureEvent,
// onHandlerStateChange,
// scrollRef,
// onScroll
// }) => (
<View>
<BudgetGroups
type={type}
categoryGroups={categoryGroups}
showBudgetedCol={showBudgetedCol}
show3Cols={show3Cols}
showHiddenCategories={showHiddenCategories}
// gestures={gestures}
editMode={editMode}
editingGroupId={editingGroupId}
onEditGroup={onEditGroup}
editingCategoryId={editingCategoryId}
onEditCategory={onEditCategory}
editingBudgetCategoryId={editingBudgetCategoryId}
onEditCategoryBudget={onEditCategoryBudget}
onSaveCategory={onSaveCategory}
onDeleteCategory={onDeleteCategory}
onAddCategory={onAddCategory}
onAddGroup={onAddGroup}
onSaveGroup={onSaveGroup}
onDeleteGroup={onDeleteGroup}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onBudgetAction={onBudgetAction}
/>
</View>
// <DragDropHighlight />
// </DragDrop>
)}
</PullToRefresh>
</View>
</View>
// <DragDropHighlight />
// </DragDrop>
)}
</PullToRefresh>
</Page>
</NamespaceContext.Provider>
);
}
const LEFT_RIGHT_FLEX_WIDTH = 80;
const BUDGET_HEADER_HEIGHT = 50;
function BudgetHeader({
currentMonth,
monthBounds,
onPrevMonth,
onNextMonth,
editMode,
function BudgetMenu({
onEditMode,
showHiddenCategories,
savePrefs,
pushModal,
onToggleHiddenCategories,
onSwitchBudgetType,
}) {
let serverURL = useServerURL();
let prevEnabled = currentMonth > monthBounds.start;
let nextEnabled = currentMonth < monthUtils.subMonths(monthBounds.end, 1);
let buttonStyle = {
padding: 10,
margin: 2,
};
let toggleHiddenCategories = () => {
savePrefs({
'budget.showHiddenCategories': !showHiddenCategories,
});
};
let tooltip = useTooltip();
let isReportBudgetEnabled = useFeatureFlag('reportBudget');
@@ -2000,181 +2005,123 @@ function BudgetHeader({
onEditMode?.(true);
break;
case 'toggle-hidden-categories':
toggleHiddenCategories();
onToggleHiddenCategories?.();
break;
case 'switch-budget-type':
pushModal('switch-budget-type', {
onSwitch: onSwitchBudgetType,
});
onSwitchBudgetType?.();
break;
default:
throw new Error(`Unrecognized menu option: ${name}`);
}
};
return (
<>
<Button
type="bare"
style={{
...styles.noTapHighlight,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
{...tooltip.getOpenEvents()}
>
<DotsHorizontalTriple
width="20"
height="20"
style={{ color: theme.mobileHeaderText }}
/>
</Button>
{tooltip.isOpen && (
<Tooltip
position="bottom-right"
width={250}
style={{ padding: 0 }}
onClose={tooltip.close}
>
<Menu
onMenuSelect={onMenuSelect}
items={[
{ name: 'edit-mode', text: 'Edit mode' },
{
name: 'toggle-hidden-categories',
text: 'Toggle hidden categories',
},
isReportBudgetEnabled && {
name: 'switch-budget-type',
text: 'Switch budget type',
},
]}
/>
</Tooltip>
)}
</>
);
}
function MonthSelector({ month, monthBounds, onPrevMonth, onNextMonth }) {
let prevEnabled = month > monthBounds.start;
let nextEnabled = month < monthUtils.subMonths(monthBounds.end, 1);
let arrowButtonStyle = {
padding: 10,
margin: 2,
};
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
flexShrink: 0,
height: BUDGET_HEADER_HEIGHT,
backgroundColor: theme.mobileHeaderBackground,
}}
>
<View
<Button
type="bare"
onClick={prevEnabled && onPrevMonth}
style={{
width: LEFT_RIGHT_FLEX_WIDTH,
flexDirection: 'row',
...styles.noTapHighlight,
...arrowButtonStyle,
opacity: prevEnabled ? 1 : 0.6,
color: theme.mobileHeaderText,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
>
{serverURL && (
<SyncButton
isMobile
style={{
color: theme.mobileHeaderText,
paddingLeft: 12,
paddingRight: 12,
}}
/>
)}
<View
style={{
flex: 1,
}}
/>
</View>
<View
<ArrowThinLeft width="15" height="15" style={{ margin: -5 }} />
</Button>
<Text
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
color: theme.mobileHeaderText,
textAlign: 'center',
fontSize: 16,
fontWeight: 500,
}}
>
<Button
type="bare"
// hitSlop={{ top: 5, bottom: 5, left: 0, right: 30 }}
onClick={prevEnabled && onPrevMonth}
style={{
...buttonStyle,
opacity: prevEnabled ? 1 : 0.6,
color: theme.mobileHeaderText,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
>
<ArrowThinLeft width="15" height="15" style={{ margin: -5 }} />
</Button>
<Text
style={{
color: theme.mobileHeaderText,
textAlign: 'center',
fontSize: 16,
fontWeight: 500,
// zIndex: -1
}}
>
{/* eslint-disable-next-line rulesdir/typography */}
{monthUtils.format(currentMonth, "MMMM ''yy")}
</Text>
<Button
type="bare"
onClick={nextEnabled && onNextMonth}
// hitSlop={{ top: 5, bottom: 5, left: 30, right: 5 }}
style={{
...buttonStyle,
opacity: nextEnabled ? 1 : 0.6,
color: theme.mobileHeaderText,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
>
<ArrowThinRight width="15" height="15" style={{ margin: -5 }} />
</Button>
</View>
<View
{/* eslint-disable-next-line rulesdir/typography */}
{monthUtils.format(month, "MMMM ''yy")}
</Text>
<Button
type="bare"
onClick={nextEnabled && onNextMonth}
style={{
width: LEFT_RIGHT_FLEX_WIDTH,
flexDirection: 'row',
...styles.noTapHighlight,
...arrowButtonStyle,
opacity: nextEnabled ? 1 : 0.6,
color: theme.mobileHeaderText,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
>
<View
style={{
flex: 1,
}}
/>
{!editMode ? (
<>
<Button
type="bare"
aria-label="Menu"
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
style={{
paddingTop: 15,
paddingBottom: 15,
margin: 10,
}}
{...tooltip.getOpenEvents()}
>
<DotsHorizontalTriple
width="20"
height="20"
style={{ color: theme.mobileHeaderText }}
/>
</Button>
{tooltip.isOpen && (
<Tooltip
position="bottom-right"
width={200}
style={{ padding: 0 }}
onClose={tooltip.close}
>
<Menu
onMenuSelect={onMenuSelect}
items={[
{ name: 'edit-mode', text: 'Edit mode' },
{
name: 'toggle-hidden-categories',
text: 'Toggle hidden categories',
},
isReportBudgetEnabled && {
name: 'switch-budget-type',
text: 'Switch budget type',
},
]}
/>
</Tooltip>
)}
</>
) : (
<Button
type="bare"
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
style={{
backgroundColor: 'transparent',
padding: 10,
paddingTop: 15,
paddingBottom: 15,
margin: 10,
...styles.text,
color: theme.mobileHeaderText,
}}
onClick={() => onEditMode?.(false)}
>
Done
</Button>
)}
</View>
<ArrowThinRight width="15" height="15" style={{ margin: -5 }} />
</Button>
</View>
);
}

View File

@@ -33,7 +33,6 @@ export const Setting = ({ primaryAction, style, children }: SettingProps) => {
<View
style={{
marginBottom: primaryAction ? 10 : 0,
maxWidth: 500,
lineHeight: 1.5,
gap: 10,
}}

View File

@@ -137,61 +137,48 @@ export default function Settings() {
useSetThemeColor(theme.mobileViewTheme);
return (
<View
<Page
title="Settings"
style={{
backgroundColor: isNarrowWidth && theme.mobilePageBackground,
marginInline: floatingSidebar && !isNarrowWidth ? 'auto' : 0,
}}
>
<Page
title="Settings"
titleStyle={
isNarrowWidth
? {
backgroundColor: theme.mobileHeaderBackground,
color: theme.mobileHeaderText,
fontSize: 16,
fontWeight: 500,
}
: undefined
}
>
<View style={{ flexShrink: 0, gap: 30 }}>
{isNarrowWidth && (
<View
style={{ gap: 10, flexDirection: 'row', alignItems: 'flex-end' }}
>
{/* The only spot to close a budget on mobile */}
<FormField>
<FormLabel title="Budget Name" />
<Input
value={budgetName}
disabled
style={{ color: theme.buttonNormalDisabledText }}
/>
</FormField>
<Button onClick={closeBudget}>Close Budget</Button>
</View>
)}
<View style={{ flexShrink: 0, maxWidth: 530, gap: 30 }}>
{isNarrowWidth && (
<View
style={{ gap: 10, flexDirection: 'row', alignItems: 'flex-end' }}
>
{/* The only spot to close a budget on mobile */}
<FormField>
<FormLabel title="Budget Name" />
<Input
value={budgetName}
disabled
style={{ color: theme.buttonNormalDisabledText }}
/>
</FormField>
<Button onClick={closeBudget}>Close Budget</Button>
</View>
)}
<About />
<About />
{!Platform.isBrowser && <GlobalSettings />}
{!Platform.isBrowser && <GlobalSettings />}
<ThemeSettings />
<FormatSettings />
<EncryptionSettings />
<ExportBudget />
<ThemeSettings />
<FormatSettings />
<EncryptionSettings />
<ExportBudget />
<AdvancedToggle>
<AdvancedAbout />
<ResetCache />
<ResetSync />
<FixSplitsTool />
<ExperimentalFeatures />
</AdvancedToggle>
</View>
</Page>
</View>
<AdvancedToggle>
<AdvancedAbout />
<ResetCache />
<ResetSync />
<FixSplitsTool />
<ExperimentalFeatures />
</AdvancedToggle>
</View>
</Page>
);
}

View File

@@ -7,7 +7,7 @@ import React, {
useRef,
} from 'react';
import { useSelector } from 'react-redux';
import { useParams, Link } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { useFocusRing } from '@react-aria/focus';
import { useListBox, useListBoxSection, useOption } from '@react-aria/listbox';
@@ -48,7 +48,6 @@ import useCategories from '../../hooks/useCategories';
import useNavigate from '../../hooks/useNavigate';
import { useSetThemeColor } from '../../hooks/useSetThemeColor';
import SvgAdd from '../../icons/v1/Add';
import CheveronLeft from '../../icons/v1/CheveronLeft';
import SvgTrash from '../../icons/v1/Trash';
import ArrowsSynchronize from '../../icons/v2/ArrowsSynchronize';
import CheckCircle1 from '../../icons/v2/CheckCircle1';
@@ -66,6 +65,8 @@ import {
InputField,
BooleanField,
} from '../mobile/MobileForms';
import MobileBackButton from '../MobileBackButton';
import { Page } from '../Page';
const zIndices = { SECTION_HEADING: 10 };
@@ -165,9 +166,6 @@ function Status({ status }) {
);
}
const LEFT_RIGHT_FLEX_WIDTH = 70;
const BUDGET_HEADER_HEIGHT = 50;
class TransactionEditInner extends PureComponent {
constructor(props) {
super(props);
@@ -325,9 +323,7 @@ class TransactionEditInner extends PureComponent {
};
render() {
const { adding, categories, accounts, payees, renderChildEdit, navigate } =
this.props;
const { editingChild } = this.state;
const { adding, categories, accounts, payees } = this.props;
const transactions = this.serializeTransactions(
this.state.transactions || [],
);
@@ -336,7 +332,7 @@ class TransactionEditInner extends PureComponent {
// Child transactions should always default to the signage
// of the parent transaction
const forcedSign = transaction.amount < 0 ? 'negative' : 'positive';
// const forcedSign = transaction.amount < 0 ? 'negative' : 'positive';
const account = getAccountsById(accounts)[accountId];
const isOffBudget = account && !!account.offbudget;
@@ -360,350 +356,24 @@ class TransactionEditInner extends PureComponent {
const dateDefaultValue = monthUtils.dayFromDate(transactionDate);
return (
// <KeyboardAvoidingView>
<View
style={{
backgroundColor: theme.mobilePageBackground,
flexGrow: 1,
// This shadow make the card "pop" off of the screen below
// it
shadowColor: theme.cardShadow,
shadowOffset: { width: 0, height: 0 },
shadowRadius: 4,
shadowOpacity: 1,
<Page
title={
payeeId == null
? adding
? 'New Transaction'
: 'Transaction'
: descriptionPretty
}
titleStyle={{
fontSize: 16,
fontWeight: 500,
}}
>
<View
style={{
overflow: 'hidden',
display: 'flex',
flexGrow: 1,
}}
>
<View
style={{
flexShrink: 0,
height: BUDGET_HEADER_HEIGHT,
flexDirection: 'row',
width: '100%',
backgroundColor: theme.mobileHeaderBackground,
}}
>
<View
style={{
width: LEFT_RIGHT_FLEX_WIDTH,
flexDirection: 'row',
}}
>
<Button
type="bare"
style={{
...styles.noTapHighlight,
color: theme.mobileHeaderText,
justifyContent: 'center',
margin: 10,
paddingLeft: 5,
paddingRight: 3,
}}
hoveredStyle={{
color: theme.mobileHeaderText,
background: theme.mobileHeaderTextHover,
}}
>
<Link
to={-1}
style={{
alignItems: 'center',
display: 'flex',
textDecoration: 'none',
}}
>
<CheveronLeft
style={{
width: 30,
height: 30,
margin: -10,
marginLeft: -5,
}}
/>
<Text
style={{
...styles.text,
fontWeight: 500,
marginLeft: 5,
marginRight: 5,
}}
>
Back
</Text>
</Link>
</Button>
<View
style={{
flex: 1,
}}
/>
</View>
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
color: theme.mobileHeaderText,
}}
>
<TextOneLine
style={{
fontSize: 16,
fontWeight: 500,
userSelect: 'none',
}}
role="heading"
>
{payeeId == null
? adding
? 'New Transaction'
: 'Transaction'
: descriptionPretty}
</TextOneLine>
</View>
{/* For centering the transaction title */}
<View
style={{
width: LEFT_RIGHT_FLEX_WIDTH,
}}
/>
</View>
{/* <ScrollView
ref={el => (this.scrollView = el)}
automaticallyAdjustContentInsets={false}
keyboardShouldPersistTaps="always"
style={{
flexGrow: 1,
overflow: 'hidden',
}}
contentContainerStyle={{ flexGrow: 1 }}
> */}
<View
style={{
overflowY: 'auto',
overflowX: 'hidden',
display: 'block',
}}
>
<View
style={{
alignItems: 'center',
marginTop: 20,
}}
>
<FieldLabel
title="Amount"
flush
style={{ marginBottom: 0, paddingLeft: 0 }}
/>
<FocusableAmountInput
ref={el => (this.amount = el)}
value={transaction.amount}
zeroIsNegative={true}
onBlur={value =>
this.onEdit(transaction, 'amount', value.toString())
}
onChange={value =>
this.onQueueChange(transaction, 'amount', value)
}
style={{ transform: [] }}
focusedStyle={{
width: 'auto',
padding: '5px',
paddingLeft: '20px',
paddingRight: '20px',
minWidth: 120,
transform: [{ translateY: -0.5 }],
}}
textStyle={{ fontSize: 30, textAlign: 'center' }}
/>
</View>
<View>
<FieldLabel title="Payee" />
<TapField
value={descriptionPretty}
onClick={() => this.onClick(transaction.id, 'payee')}
data-testid="payee-field"
/>
</View>
<View>
<FieldLabel
title={
transaction.is_parent ? 'Categories (split)' : 'Category'
}
/>
{!transaction.is_parent ? (
<TapField
style={{
...((isBudgetTransfer || isOffBudget) && {
fontStyle: 'italic',
color: theme.pageTextSubdued,
fontWeight: 300,
}),
}}
value={
isOffBudget
? 'Off Budget'
: isBudgetTransfer
? 'Transfer'
: lookupName(categories, category)
}
disabled={isBudgetTransfer || isOffBudget}
// TODO: the button to turn this transaction into a split
// transaction was on top of the category button in the native
// app, on the right-hand side
//
// On the web this doesn't work well and react gets upset if
// nest a button in a button.
//
// rightContent={
// <Button
// contentStyle={{
// paddingVertical: 4,
// paddingHorizontal: 15,
// margin: 0,
// }}
// onPress={this.onSplit}
// >
// Split
// </Button>
// }
onClick={() => this.onClick(transaction.id, 'category')}
data-testid="category-field"
/>
) : (
<Text style={{ paddingLeft: styles.mobileEditingPadding }}>
Split transaction editing is not supported on mobile at this
time.
</Text>
)}
</View>
<View>
<FieldLabel title="Account" />
<TapField
disabled={!adding}
value={account ? account.name : null}
onClick={() => this.onClick(transaction.id, 'account')}
data-testid="account-field"
/>
</View>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }}>
<FieldLabel title="Date" />
<InputField
type="date"
required
style={{ color: theme.tableText, minWidth: '150px' }}
defaultValue={dateDefaultValue}
onUpdate={value =>
this.onEdit(
transaction,
'date',
formatDate(parseISO(value), this.props.dateFormat),
)
}
onChange={e =>
this.onQueueChange(
transaction,
'date',
formatDate(
parseISO(e.target.value),
this.props.dateFormat,
),
)
}
/>
</View>
{transaction.reconciled ? (
<View style={{ marginLeft: 0, marginRight: 8 }}>
<FieldLabel title="Reconciled" />
<BooleanField
checked
style={{
margin: 'auto',
width: 22,
height: 22,
}}
disabled
/>
</View>
) : (
<View style={{ marginLeft: 0, marginRight: 8 }}>
<FieldLabel title="Cleared" />
<BooleanField
checked={transaction.cleared}
onUpdate={checked =>
this.onEdit(transaction, 'cleared', checked)
}
style={{
margin: 'auto',
width: 22,
height: 22,
}}
/>
</View>
)}
</View>
<View>
<FieldLabel title="Notes" />
<InputField
defaultValue={transaction.notes}
onUpdate={value => this.onEdit(transaction, 'notes', value)}
onChange={e =>
this.onQueueChange(transaction, 'notes', e.target.value)
}
style={{ marginBottom: 10 }}
/>
</View>
{!adding && (
<View style={{ alignItems: 'center' }}>
<Button
onClick={() => this.onDelete()}
style={{
height: 40,
borderWidth: 0,
paddingVertical: 5,
marginLeft: styles.mobileEditingPadding,
marginRight: styles.mobileEditingPadding,
marginTop: 10,
marginBottom: 15,
backgroundColor: 'transparent',
}}
type="bare"
>
<SvgTrash
width={17}
height={17}
style={{ color: theme.errorText }}
/>
<Text
style={{
color: theme.errorText,
marginLeft: 5,
userSelect: 'none',
}}
>
Delete transaction
</Text>
</Button>
</View>
)}
</View>
style={{
flex: 1,
backgroundColor: theme.mobilePageBackground,
}}
headerLeftContent={<MobileBackButton />}
footer={
<View
style={{
paddingLeft: styles.mobileEditingPadding,
@@ -713,8 +383,6 @@ class TransactionEditInner extends PureComponent {
backgroundColor: theme.tableHeaderBackground,
borderTopWidth: 1,
borderColor: theme.tableBorder,
marginTop: 'auto',
flexShrink: 0,
}}
>
{adding ? (
@@ -737,7 +405,11 @@ class TransactionEditInner extends PureComponent {
) : (
<Button style={{ height: 40 }} onClick={() => this.onSave()}>
<SvgPencilWriteAlternate
style={{ width: 16, height: 16, color: theme.formInputText }}
style={{
width: 16,
height: 16,
color: theme.formInputText,
}}
/>
<Text
style={{
@@ -751,27 +423,212 @@ class TransactionEditInner extends PureComponent {
</Button>
)}
</View>
{/* <ExitTransition
alive={editingChild}
withProps={{
transaction:
editingChild && transactions.find(t => t.id === editingChild),
}
padding={0}
>
<View style={{ flexShrink: 0, marginTop: 20, marginBottom: 20 }}>
<View
style={{
alignItems: 'center',
}}
> */}
{renderChildEdit({
transaction:
editingChild && transactions.find(t => t.id === editingChild),
amountSign: forcedSign,
getCategoryName: id => lookupName(categories, id),
navigate,
onEdit: this.onEdit,
onStartClose: this.onSaveChild,
})}
{/* </ExitTransition> */}
>
<FieldLabel
title="Amount"
flush
style={{ marginBottom: 0, paddingLeft: 0 }}
/>
<FocusableAmountInput
ref={el => (this.amount = el)}
value={transaction.amount}
zeroIsNegative={true}
onBlur={value =>
this.onEdit(transaction, 'amount', value.toString())
}
onChange={value =>
this.onQueueChange(transaction, 'amount', value)
}
style={{ transform: [] }}
focusedStyle={{
width: 'auto',
padding: '5px',
paddingLeft: '20px',
paddingRight: '20px',
minWidth: 120,
transform: [{ translateY: -0.5 }],
}}
textStyle={{ fontSize: 30, textAlign: 'center' }}
/>
</View>
<View>
<FieldLabel title="Payee" />
<TapField
value={descriptionPretty}
onClick={() => this.onClick(transaction.id, 'payee')}
data-testid="payee-field"
/>
</View>
<View>
<FieldLabel
title={transaction.is_parent ? 'Categories (split)' : 'Category'}
/>
{!transaction.is_parent ? (
<TapField
style={{
...((isBudgetTransfer || isOffBudget) && {
fontStyle: 'italic',
color: theme.pageTextSubdued,
fontWeight: 300,
}),
}}
value={
isOffBudget
? 'Off Budget'
: isBudgetTransfer
? 'Transfer'
: lookupName(categories, category)
}
disabled={isBudgetTransfer || isOffBudget}
// TODO: the button to turn this transaction into a split
// transaction was on top of the category button in the native
// app, on the right-hand side
//
// On the web this doesn't work well and react gets upset if
// nest a button in a button.
//
// rightContent={
// <Button
// contentStyle={{
// paddingVertical: 4,
// paddingHorizontal: 15,
// margin: 0,
// }}
// onPress={this.onSplit}
// >
// Split
// </Button>
// }
onClick={() => this.onClick(transaction.id, 'category')}
data-testid="category-field"
/>
) : (
<Text style={{ paddingLeft: styles.mobileEditingPadding }}>
Split transaction editing is not supported on mobile at this
time.
</Text>
)}
</View>
<View>
<FieldLabel title="Account" />
<TapField
disabled={!adding}
value={account ? account.name : null}
onClick={() => this.onClick(transaction.id, 'account')}
data-testid="account-field"
/>
</View>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }}>
<FieldLabel title="Date" />
<InputField
type="date"
required
style={{ color: theme.tableText, minWidth: '150px' }}
defaultValue={dateDefaultValue}
onUpdate={value =>
this.onEdit(
transaction,
'date',
formatDate(parseISO(value), this.props.dateFormat),
)
}
onChange={e =>
this.onQueueChange(
transaction,
'date',
formatDate(parseISO(e.target.value), this.props.dateFormat),
)
}
/>
</View>
{transaction.reconciled ? (
<View style={{ marginLeft: 0, marginRight: 8 }}>
<FieldLabel title="Reconciled" />
<BooleanField
checked
style={{
margin: 'auto',
width: 22,
height: 22,
}}
disabled
/>
</View>
) : (
<View style={{ marginLeft: 0, marginRight: 8 }}>
<FieldLabel title="Cleared" />
<BooleanField
checked={transaction.cleared}
onUpdate={checked =>
this.onEdit(transaction, 'cleared', checked)
}
style={{
margin: 'auto',
width: 22,
height: 22,
}}
/>
</View>
)}
</View>
<View>
<FieldLabel title="Notes" />
<InputField
defaultValue={transaction.notes}
onUpdate={value => this.onEdit(transaction, 'notes', value)}
onChange={e =>
this.onQueueChange(transaction, 'notes', e.target.value)
}
/>
</View>
{!adding && (
<View style={{ alignItems: 'center' }}>
<Button
onClick={() => this.onDelete()}
style={{
height: 40,
borderWidth: 0,
marginLeft: styles.mobileEditingPadding,
marginRight: styles.mobileEditingPadding,
marginTop: 10,
backgroundColor: 'transparent',
}}
type="bare"
>
<SvgTrash
width={17}
height={17}
style={{ color: theme.errorText }}
/>
<Text
style={{
color: theme.errorText,
marginLeft: 5,
userSelect: 'none',
}}
>
Delete transaction
</Text>
</Button>
</View>
)}
</View>
</View>
// </KeyboardAvoidingView>
</Page>
);
}
}

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Use Page component for mobile pages