From ca7455aa67b30cddea74db2a974bc320e6eba783 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Fri, 8 Sep 2023 11:53:21 -0700 Subject: [PATCH 01/12] Autocomplete components on mobile --- .../autocomplete/AccountAutocomplete.js | 25 ++- .../autocomplete/CategoryAutocomplete.tsx | 40 +++- .../autocomplete/PayeeAutocomplete.js | 33 ++- .../src/components/mobile/MobileForms.js | 3 +- .../transactions/MobileTransaction.js | 208 +++++++++++++----- 5 files changed, 250 insertions(+), 59 deletions(-) diff --git a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.js b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.js index 44799d7145f..4518273cf42 100644 --- a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.js +++ b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { css } from 'glamor'; @@ -14,6 +14,8 @@ function AccountList({ getItemProps, highlightedIndex, embedded, + style, + itemStyle, groupHeaderStyle, }) { let lastItem = null; @@ -25,6 +27,7 @@ function AccountList({ overflow: 'auto', padding: '5px 0', ...(!embedded && { maxHeight: 175 }), + ...style, }} > {items.map((item, idx) => { @@ -93,6 +96,7 @@ function AccountList({ paddingLeft: 20, borderRadius: embedded ? 4 : 0, }, + itemStyle, ])}`} data-testid={ 'account-item' + @@ -111,8 +115,11 @@ function AccountList({ export default function AccountAutocomplete({ embedded, includeClosedAccounts = true, + accountListStyle, + accountListItemStyle, groupHeaderStyle, closeOnBlur, + inputProps, ...props }) { let accounts = useCachedAccounts() || []; @@ -131,6 +138,8 @@ export default function AccountAutocomplete({ } }); + const [accountFieldFocused, setAccountFieldFocused] = useState(false); + return ( { + setAccountFieldFocused(false); + inputProps?.onBlur?.(e); + }, + onFocus: e => { + setAccountFieldFocused(true); + inputProps?.onFocus?.(e); + }, + }} + focused={accountFieldFocused} renderItems={(items, getItemProps, highlightedIndex) => ( )} diff --git a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx index 99041a45872..65ffa1be82e 100644 --- a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx @@ -3,12 +3,13 @@ import React, { Fragment, useMemo, type ReactNode, + useState, } from 'react'; import { css } from 'glamor'; import Split from '../../icons/v0/Split'; -import { theme } from '../../style'; +import { type CSSProperties, theme } from '../../style'; import Text from '../common/Text'; import View from '../common/View'; @@ -33,7 +34,10 @@ export type CategoryListProps = { highlightedIndex: number; embedded: boolean; footer?: ReactNode; - groupHeaderStyle?: object; + style?: CSSProperties; + groupHeaderStyle?: CSSProperties; + itemStyle?: CSSProperties; + splitButtonStyle?: CSSProperties; }; function CategoryList({ items, @@ -41,7 +45,10 @@ function CategoryList({ highlightedIndex, embedded, footer, + style, groupHeaderStyle, + itemStyle, + splitButtonStyle, }: CategoryListProps) { let lastGroup = null; @@ -52,6 +59,7 @@ function CategoryList({ overflow: 'auto', padding: '5px 0', ...(!embedded && { maxHeight: 175 }), + ...style, }} > {items.map((item, idx) => { @@ -98,6 +106,7 @@ function CategoryList({ ':active': { backgroundColor: 'rgba(100, 100, 100, .25)', }, + ...splitButtonStyle, }} data-testid="split-transaction-button" > @@ -139,6 +148,7 @@ function CategoryList({ paddingLeft: 20, borderRadius: embedded ? 4 : 0, }, + itemStyle, ])}`} data-testid={ 'category-item' + @@ -159,14 +169,21 @@ function CategoryList({ type CategoryAutocompleteProps = ComponentProps & { categoryGroups: CategoryGroup[]; showSplitOption?: boolean; - groupHeaderStyle?: object; + categoryListStyle?: CSSProperties; + categoryListItemStyle?: CSSProperties; + groupHeaderStyle?: CSSProperties; + splitButtonStyle?: CSSProperties; }; export default function CategoryAutocomplete({ categoryGroups, showSplitOption, embedded, closeOnBlur, + inputProps, + categoryListStyle, groupHeaderStyle, + categoryListItemStyle, + splitButtonStyle, ...props }: CategoryAutocompleteProps) { let categorySuggestions = useMemo( @@ -184,6 +201,8 @@ export default function CategoryAutocomplete({ [categoryGroups], ); + const [categoryFieldFocused, setCategoryFieldFocused] = useState(false); + return ( { + setCategoryFieldFocused(true); + inputProps?.onFocus?.(e); + }, + onBlur: e => { + setCategoryFieldFocused(false); + inputProps?.onBlur?.(e); + }, + }} + focused={categoryFieldFocused} suggestions={categorySuggestions} renderItems={(items, getItemProps, highlightedIndex) => ( )} {...props} diff --git a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js index 16f5aa67ab7..86f83ca1308 100644 --- a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js +++ b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js @@ -51,7 +51,10 @@ function PayeeList({ highlightedIndex, embedded, inputValue, + style, + itemStyle, groupHeaderStyle, + createPayeeButtonStyle, footer, }) { const { isNarrowWidth } = useResponsive(); @@ -78,6 +81,7 @@ function PayeeList({ overflow: 'auto', padding: '5px 0', ...(!embedded && { maxHeight: 175 }), + ...style, }} > {createNew && ( @@ -94,6 +98,7 @@ function PayeeList({ ':active': { backgroundColor: 'rgba(100, 100, 100, .25)', }, + ...createPayeeButtonStyle, }} > {item.name} @@ -213,7 +219,12 @@ export default function PayeeAutocomplete({ onUpdate, onSelect, onManagePayees, + payeeListStyle, + payeeListItemStyle, groupHeaderStyle, + createPayeeButtonStyle, + makeTransferButtonStyle, + managePayeesButtonStyle, accounts, payees, ...props @@ -285,11 +296,15 @@ export default function PayeeAutocomplete({ focused={payeeFieldFocused} inputProps={{ ...inputProps, - onBlur: () => { + onBlur: e => { setRawPayee(''); setPayeeFieldFocused(false); + inputProps?.onBlur?.(e); + }, + onFocus: e => { + setPayeeFieldFocused(true); + inputProps?.onFocus?.(e); }, - onFocus: () => setPayeeFieldFocused(true), onChange: setRawPayee, }} onUpdate={(value, inputValue) => @@ -360,13 +375,19 @@ export default function PayeeAutocomplete({ highlightedIndex={highlightedIndex} inputValue={inputValue} embedded={embedded} + style={payeeListStyle} + itemStyle={payeeListItemStyle} + createPayeeButtonStyle={createPayeeButtonStyle} groupHeaderStyle={groupHeaderStyle} footer={ {showMakeTransfer && ( )} {showManagePayees && ( - )} diff --git a/packages/desktop-client/src/components/mobile/MobileForms.js b/packages/desktop-client/src/components/mobile/MobileForms.js index 4114b2571fc..76e604df7fb 100644 --- a/packages/desktop-client/src/components/mobile/MobileForms.js +++ b/packages/desktop-client/src/components/mobile/MobileForms.js @@ -64,7 +64,8 @@ export const InputField = forwardRef(function InputField( ); }); -export function TapField({ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function TapField({ value, children, disabled, diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.js b/packages/desktop-client/src/components/transactions/MobileTransaction.js index e5d7b45b352..7a573a578e1 100644 --- a/packages/desktop-client/src/components/transactions/MobileTransaction.js +++ b/packages/desktop-client/src/components/transactions/MobileTransaction.js @@ -5,6 +5,7 @@ import React, { useEffect, useState, useRef, + createRef, } from 'react'; import { useSelector } from 'react-redux'; import { useNavigate, useParams, Link } from 'react-router-dom'; @@ -53,6 +54,9 @@ import ArrowsSynchronize from '../../icons/v2/ArrowsSynchronize'; import CheckCircle1 from '../../icons/v2/CheckCircle1'; import SvgPencilWriteAlternate from '../../icons/v2/PencilWriteAlternate'; import { styles, colors, theme } from '../../style'; +import AccountAutocomplete from '../autocomplete/AccountAutocomplete'; +import CategoryAutocomplete from '../autocomplete/CategoryAutocomplete'; +import PayeeAutocomplete from '../autocomplete/PayeeAutocomplete'; import Button from '../common/Button'; import Text from '../common/Text'; import TextOneLine from '../common/TextOneLine'; @@ -60,7 +64,6 @@ import View from '../common/View'; import { FocusableAmountInput } from '../mobile/MobileAmountInput'; import { FieldLabel, - TapField, InputField, BooleanField, EDITING_PADDING, @@ -188,6 +191,10 @@ class TransactionEditInner extends PureComponent { transactions: props.transactions, editingChild: null, }; + + this.payeeInputRef = createRef(); + this.categoryInputRef = createRef(); + this.accountInputRef = createRef(); } serializeTransactions = memoizeOne(transactions => { @@ -306,14 +313,26 @@ class TransactionEditInner extends PureComponent { }; render() { - const { adding, categories, accounts, payees, renderChildEdit, navigate } = - this.props; + const { + isAdding, + categories, + categoryGroups, + accounts, + payees, + renderChildEdit, + navigate, + dateFormat, + } = this.props; const { editingChild } = this.state; const transactions = this.serializeTransactions( this.state.transactions || [], ); const [transaction, ..._childTransactions] = transactions; - const { payee: payeeId, category, account: accountId } = transaction; + const { + payee: payeeId, + category: categoryId, + account: accountId, + } = transaction; // Child transactions should always default to the signage // of the parent transaction @@ -332,13 +351,13 @@ class TransactionEditInner extends PureComponent { transferAcct, ); - const transactionDate = parseDate( - transaction.date, - this.props.dateFormat, - new Date(), - ); + const transactionDate = parseDate(transaction.date, dateFormat, new Date()); const dateDefaultValue = monthUtils.dayFromDate(transactionDate); + const autocompletePaddingStyle = { + padding: '7px 7px', + }; + return ( // {payeeId == null - ? adding + ? isAdding ? 'New Transaction' : 'Transaction' : descriptionPretty} @@ -477,10 +496,36 @@ class TransactionEditInner extends PureComponent { - this.onClick(transaction.id, 'payee')} data-testid="payee-field" + /> */} + this.payeeInputRef.current?.focus(), + 'data-testid': 'payee-field', + }} + // showManagePayees={true} + tableBehavior={true} + // focused={focusedField === 'payee'} + onUpdate={payeeId => this.onEdit(transaction, 'payee', payeeId)} + onSelect={payeeId => this.onEdit(transaction, 'payee', payeeId)} + // onManagePayees={() => onManagePayees(payeeId)} + isCreatable + menuPortalTarget={undefined} + groupHeaderStyle={autocompletePaddingStyle} + payeeListItemStyle={autocompletePaddingStyle} + createPayeeButtonStyle={autocompletePaddingStyle} + makeTransferButtonStyle={autocompletePaddingStyle} + managePayeesButtonStyle={autocompletePaddingStyle} /> @@ -491,30 +536,60 @@ class TransactionEditInner extends PureComponent { } /> {!transaction.is_parent ? ( - - // Split - // - // } - onClick={() => this.onClick(transaction.id, 'category')} - data-testid="category-field" + // + // // Split + // // + // // } + // onClick={() => this.onClick(transaction.id, 'category')} + // data-testid="category-field" + // /> + this.categoryInputRef.current?.focus(), + 'data-testid': 'category-field', + }} + groupHeaderStyle={autocompletePaddingStyle} + categoryListItemStyle={autocompletePaddingStyle} + splitButtonStyle={autocompletePaddingStyle} + onUpdate={categoryId => + this.onEdit(transaction, 'category', categoryId) + } + onSelect={categoryId => + this.onEdit(transaction, 'category', categoryId) + } + menuPortalTarget={undefined} /> ) : ( @@ -526,11 +601,38 @@ class TransactionEditInner extends PureComponent { - this.onClick(transaction.id, 'account')} data-testid="account-field" + /> */} + this.accountInputRef.current?.focus(), + 'data-testid': 'account-field', + }} + onUpdate={payeeId => + this.onEdit(transaction, 'account', payeeId) + } + onSelect={payeeId => + this.onEdit(transaction, 'account', payeeId) + } + menuPortalTarget={undefined} + accountListItemStyle={autocompletePaddingStyle} /> @@ -546,17 +648,14 @@ class TransactionEditInner extends PureComponent { this.onEdit( transaction, 'date', - formatDate(parseISO(value), this.props.dateFormat), + formatDate(parseISO(value), dateFormat), ) } onChange={e => this.onQueueChange( transaction, 'date', - formatDate( - parseISO(e.target.value), - this.props.dateFormat, - ), + formatDate(parseISO(e.target.value), dateFormat), ) } /> @@ -585,7 +684,7 @@ class TransactionEditInner extends PureComponent { /> - {!adding && ( + {!isAdding && (