From 5e3485a8e299f6b1438cdd6ae5a377b2419d2c65 Mon Sep 17 00:00:00 2001 From: Robert Dyer <rdyer@unl.edu> Date: Thu, 15 Aug 2024 08:36:09 -0500 Subject: [PATCH] Automatically focus inputs, or the primary button, in modals. (#2974) * Automatically focus inputs, or the primary button, in modals. * Set focus on more modals. * focus mobile transaction edits * add release note * fix linter * fix linter --- .../src/components/accounts/Account.jsx | 7 +++++- .../components/accounts/AccountSyncCheck.jsx | 1 + .../src/components/accounts/Reconcile.jsx | 2 +- .../src/components/manager/ImportActual.tsx | 1 + .../src/components/manager/ImportYNAB4.tsx | 1 + .../src/components/manager/ImportYNAB5.tsx | 1 + .../src/components/manager/WelcomeScreen.tsx | 2 +- .../mobile/transactions/TransactionEdit.jsx | 2 +- .../components/modals/CloseAccountModal.tsx | 1 + .../modals/ConfirmCategoryDelete.tsx | 3 ++- .../modals/ConfirmTransactionDelete.tsx | 23 +++++++++-------- .../modals/ConfirmTransactionEdit.tsx | 23 +++++++++-------- .../modals/ConfirmUnlinkAccount.tsx | 21 +++++++++------- .../components/modals/CreateAccountModal.tsx | 25 +++++++++++-------- .../modals/GoCardlessExternalMsg.tsx | 2 ++ .../modals/GoCardlessInitialise.tsx | 21 +++++++++------- .../components/modals/ImportTransactions.jsx | 1 + .../components/modals/MergeUnusedPayees.jsx | 1 + .../components/modals/SimpleFinInitialise.tsx | 1 + .../components/reports/SaveReportDelete.tsx | 4 +-- .../schedules/PostsOfflineNotification.jsx | 1 + .../components/schedules/ScheduleDetails.jsx | 21 +++++++++------- .../src/components/schedules/ScheduleLink.tsx | 19 ++++++++------ .../select/RecurringSchedulePicker.jsx | 19 ++++++++------ upcoming-release-notes/2974.md | 6 +++++ 25 files changed, 128 insertions(+), 81 deletions(-) create mode 100644 upcoming-release-notes/2974.md diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.jsx index 06fddb35e..3153fd749 100644 --- a/packages/desktop-client/src/components/accounts/Account.jsx +++ b/packages/desktop-client/src/components/accounts/Account.jsx @@ -75,7 +75,12 @@ function EmptyMessage({ onAdd }) { manage it locally yourself. </Text> - <Button variant="primary" style={{ marginTop: 20 }} onPress={onAdd}> + <Button + variant="primary" + style={{ marginTop: 20 }} + autoFocus + onPress={onAdd} + > Add account </Button> diff --git a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx index 046f047eb..e8812ecad 100644 --- a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx +++ b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx @@ -140,6 +140,7 @@ export function AccountSyncCheck() { <Button onPress={unlink}>Unlink</Button> <Button variant="primary" + autoFocus onPress={reauth} style={{ marginLeft: 5 }} > diff --git a/packages/desktop-client/src/components/accounts/Reconcile.jsx b/packages/desktop-client/src/components/accounts/Reconcile.jsx index ab7127858..5ebcd31fa 100644 --- a/packages/desktop-client/src/components/accounts/Reconcile.jsx +++ b/packages/desktop-client/src/components/accounts/Reconcile.jsx @@ -78,7 +78,7 @@ export function ReconcilingMessage({ </View> )} <View style={{ marginLeft: 15 }}> - <Button variant="primary" onPress={onDone}> + <Button variant="primary" autoFocus onPress={onDone}> Done Reconciling </Button> </View> diff --git a/packages/desktop-client/src/components/manager/ImportActual.tsx b/packages/desktop-client/src/components/manager/ImportActual.tsx index 360f7d7a4..78305fdeb 100644 --- a/packages/desktop-client/src/components/manager/ImportActual.tsx +++ b/packages/desktop-client/src/components/manager/ImportActual.tsx @@ -84,6 +84,7 @@ export function ImportActual({ modalProps }: ImportProps) { <View style={{ alignSelf: 'center' }}> <ButtonWithLoading variant="primary" + autoFocus isLoading={importing} onPress={onImport} > diff --git a/packages/desktop-client/src/components/manager/ImportYNAB4.tsx b/packages/desktop-client/src/components/manager/ImportYNAB4.tsx index e145977c8..b0a8478ba 100644 --- a/packages/desktop-client/src/components/manager/ImportYNAB4.tsx +++ b/packages/desktop-client/src/components/manager/ImportYNAB4.tsx @@ -73,6 +73,7 @@ export function ImportYNAB4({ modalProps }: ImportProps) { <View> <ButtonWithLoading variant="primary" + autoFocus isLoading={importing} onPress={onImport} > diff --git a/packages/desktop-client/src/components/manager/ImportYNAB5.tsx b/packages/desktop-client/src/components/manager/ImportYNAB5.tsx index 3876f337a..5771e99c9 100644 --- a/packages/desktop-client/src/components/manager/ImportYNAB5.tsx +++ b/packages/desktop-client/src/components/manager/ImportYNAB5.tsx @@ -83,6 +83,7 @@ export function ImportYNAB5({ modalProps }: ImportProps) { <View> <ButtonWithLoading variant="primary" + autoFocus isLoading={importing} onPress={onImport} > diff --git a/packages/desktop-client/src/components/manager/WelcomeScreen.tsx b/packages/desktop-client/src/components/manager/WelcomeScreen.tsx index 8dcc9f4f3..71fda245e 100644 --- a/packages/desktop-client/src/components/manager/WelcomeScreen.tsx +++ b/packages/desktop-client/src/components/manager/WelcomeScreen.tsx @@ -90,7 +90,7 @@ export function WelcomeScreen() { <Button onPress={() => createBudget({ testMode: true })}> {t('View demo')} </Button> - <Button variant="primary" onPress={() => createBudget()}> + <Button variant="primary" autoFocus onPress={() => createBudget()}> {t('Start fresh')} </Button> </View> diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx index f80218f6b..3cb7db764 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx @@ -460,7 +460,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ const { editingField, onRequestActiveEdit, onClearActiveEdit } = useSingleActiveEditForm(); - const [totalAmountFocused, setTotalAmountFocused] = useState(false); + const [totalAmountFocused, setTotalAmountFocused] = useState(true); const childTransactionElementRefMap = useRef({}); const payeesById = useMemo(() => groupById(payees), [payees]); diff --git a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx index c1668a701..c51ec6dba 100644 --- a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx @@ -151,6 +151,7 @@ export function CloseAccountModal({ value={transferAccountId} inputProps={{ placeholder: 'Select account...', + autoFocus: true, ...(isNarrowWidth && { value: transferAccount?.name || '', style: { diff --git a/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx b/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx index 7713a6a09..d1191938f 100644 --- a/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx @@ -67,7 +67,7 @@ export function ConfirmCategoryDelete({ {group ? ( <Block> Categories in the group <strong>{group.name}</strong> are used - by existing transaction + by existing transactions {!isIncome && ' or it has a positive leftover balance currently'} . <strong>Are you sure you want to delete it?</strong> If so, @@ -115,6 +115,7 @@ export function ConfirmCategoryDelete({ })) } value={transferCategory} + focused={true} inputProps={{ placeholder: 'Select category...', }} diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx index 26f2ecaf2..7c1b43ef8 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useResponsive } from '../../ResponsiveProvider'; import { styles } from '../../style'; import { Button } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { View } from '../common/View'; @@ -48,16 +49,18 @@ export function ConfirmTransactionDelete({ > Cancel </Button> - <Button - variant="primary" - style={narrowButtonStyle} - onPress={() => { - onConfirm(); - close(); - }} - > - Delete - </Button> + <InitialFocus> + <Button + variant="primary" + style={narrowButtonStyle} + onPress={() => { + onConfirm(); + close(); + }} + > + Delete + </Button> + </InitialFocus> </View> </View> </> diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx index 6b445bda6..4d4568591 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Block } from '../common/Block'; import { Button } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; @@ -88,16 +89,18 @@ export function ConfirmTransactionEdit({ > Cancel </Button> - <Button - aria-label="Confirm" - variant="primary" - onPress={() => { - close(); - onConfirm(); - }} - > - Confirm - </Button> + <InitialFocus> + <Button + aria-label="Confirm" + variant="primary" + onPress={() => { + close(); + onConfirm(); + }} + > + Confirm + </Button> + </InitialFocus> </View> </View> </View> diff --git a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx index e3d5fb535..6a51ded8b 100644 --- a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Button } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { View } from '../common/View'; @@ -44,15 +45,17 @@ export function ConfirmUnlinkAccount({ <Button style={{ marginRight: 10 }} onPress={close}> Cancel </Button> - <Button - variant="primary" - onPress={() => { - onUnlink(); - close(); - }} - > - Unlink - </Button> + <InitialFocus> + <Button + variant="primary" + onPress={() => { + onUnlink(); + close(); + }} + > + Unlink + </Button> + </InitialFocus> </View> </View> </> diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index c23e67aaf..ed88da037 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -12,6 +12,7 @@ import { type SyncServerStatus } from '../../hooks/useSyncServerStatus'; import { SvgDotsHorizontalTriple } from '../../icons/v1'; import { theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Link } from '../common/Link'; import { Menu } from '../common/Menu'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; @@ -186,17 +187,19 @@ export function CreateAccountModal({ <View style={{ maxWidth: 500, gap: 30, color: theme.pageText }}> {upgradingAccountId == null && ( <View style={{ gap: 10 }}> - <Button - variant="primary" - style={{ - padding: '10px 0', - fontSize: 15, - fontWeight: 600, - }} - onPress={onCreateLocalAccount} - > - Create local account - </Button> + <InitialFocus> + <Button + variant="primary" + style={{ + padding: '10px 0', + fontSize: 15, + fontWeight: 600, + }} + onPress={onCreateLocalAccount} + > + Create local account + </Button> + </InitialFocus> <View style={{ lineHeight: '1.4em', fontSize: 15 }}> <Text> <strong>Create a local account</strong> if you want to add diff --git a/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx b/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx index 2dbbc049e..ef3bef944 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx @@ -206,6 +206,7 @@ export function GoCardlessExternalMsg({ <View style={{ flexDirection: 'row', gap: 10, alignItems: 'center' }}> <Button variant="primary" + autoFocus style={{ padding: '10px 0', fontSize: 15, @@ -272,6 +273,7 @@ export function GoCardlessExternalMsg({ ) : success ? ( <Button variant="primary" + autoFocus style={{ padding: '10px 0', fontSize: 15, diff --git a/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx b/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx index fc91a66a1..6906b26c8 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx @@ -5,6 +5,7 @@ import { send } from 'loot-core/src/platform/client/fetch'; import { Error } from '../alerts'; import { ButtonWithLoading } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { @@ -78,15 +79,17 @@ export const GoCardlessInitialise = ({ <FormField> <FormLabel title="Secret ID:" htmlFor="secret-id-field" /> - <Input - id="secret-id-field" - type="password" - value={secretId} - onChangeValue={value => { - setSecretId(value); - setIsValid(true); - }} - /> + <InitialFocus> + <Input + id="secret-id-field" + type="password" + value={secretId} + onChangeValue={value => { + setSecretId(value); + setIsValid(true); + }} + /> + </InitialFocus> </FormField> <FormField> diff --git a/packages/desktop-client/src/components/modals/ImportTransactions.jsx b/packages/desktop-client/src/components/modals/ImportTransactions.jsx index 88ffb4788..d3f281a59 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactions.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactions.jsx @@ -1719,6 +1719,7 @@ export function ImportTransactions({ options }) { > <ButtonWithLoading variant="primary" + autoFocus isDisabled={ transactions?.filter(trans => !trans.isMatchedTransaction) .length === 0 diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx b/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx index 2a8256869..0b236b598 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx @@ -151,6 +151,7 @@ export function MergeUnusedPayees({ payeeIds, targetPayeeId }) { <ModalButtons style={{ marginTop: 20 }} focusButton> <Button variant="primary" + autoFocus style={{ marginRight: 10 }} onPress={() => { onMerge(); diff --git a/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx b/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx index 21613af5c..f6587c726 100644 --- a/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx +++ b/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx @@ -88,6 +88,7 @@ export const SimpleFinInitialise = ({ <ModalButtons> <ButtonWithLoading variant="primary" + autoFocus isLoading={isLoading} onPress={() => { onSubmit(close); diff --git a/packages/desktop-client/src/components/reports/SaveReportDelete.tsx b/packages/desktop-client/src/components/reports/SaveReportDelete.tsx index 7a8da58a5..436b3ef95 100644 --- a/packages/desktop-client/src/components/reports/SaveReportDelete.tsx +++ b/packages/desktop-client/src/components/reports/SaveReportDelete.tsx @@ -21,7 +21,7 @@ export function SaveReportDelete({ <> <View style={{ align: 'center' }}> <Text style={{ color: theme.errorText, marginBottom: 5 }}> - Do you want to delete report: + Are you sure you want to delete report: </Text> <View>{name}</View> </View> @@ -33,7 +33,7 @@ export function SaveReportDelete({ style={{ marginTop: 15 }} > <View style={{ flex: 1 }} /> - <Button variant="primary" onPress={onDelete}> + <Button variant="primary" autoFocus onPress={onDelete}> Yes </Button> <Button variant="primary" onPress={onClose}> diff --git a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx index b19c0dc9e..29ad0f5da 100644 --- a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx +++ b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx @@ -81,6 +81,7 @@ export function PostsOfflineNotification() { <Button onPress={close}>Decide later</Button> <Button variant="primary" + autoFocus onPress={() => { onPost(); close(); diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx index a91aeb3b6..67900bf27 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx @@ -18,6 +18,7 @@ import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete'; import { Button } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; @@ -462,15 +463,17 @@ export function ScheduleDetails({ id, transaction }) { <Stack direction="row" style={{ marginTop: 10 }}> <FormField style={{ flex: 1 }}> <FormLabel title="Schedule Name" htmlFor="name-field" /> - <GenericInput - field="string" - type="string" - value={state.fields.name} - multi={false} - onChange={e => { - dispatch({ type: 'set-field', field: 'name', value: e }); - }} - /> + <InitialFocus> + <GenericInput + field="string" + type="string" + value={state.fields.name} + multi={false} + onChange={e => { + dispatch({ type: 'set-field', field: 'name', value: e }); + }} + /> + </InitialFocus> </FormField> </Stack> <Stack direction="row" style={{ marginTop: 20 }}> diff --git a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx index 74d22edd7..15b3409a7 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx @@ -13,6 +13,7 @@ import { import { SvgAdd } from '../../icons/v0'; import { Button } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Search } from '../common/Search'; import { Text } from '../common/Text'; @@ -93,14 +94,16 @@ export function ScheduleLink({ : `this transaction belongs`}{' '} to: </Text> - <Search - inputRef={searchInput} - isInModal - width={300} - placeholder="Filter schedules…" - value={filter} - onChange={setFilter} - /> + <InitialFocus> + <Search + inputRef={searchInput} + isInModal + width={300} + placeholder="Filter schedules…" + value={filter} + onChange={setFilter} + /> + </InitialFocus> {ids.length === 1 && ( <Button variant="primary" diff --git a/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx b/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx index 20244423f..036b875c8 100644 --- a/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx +++ b/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx @@ -8,6 +8,7 @@ import { useDateFormat } from '../../hooks/useDateFormat'; import { SvgAdd, SvgSubtract } from '../../icons/v0'; import { theme } from '../../style'; import { Button } from '../common/Button2'; +import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Menu } from '../common/Menu'; import { Popover } from '../common/Popover'; @@ -310,14 +311,16 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) { <> <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}> <label htmlFor="start">From</label> - <DateSelect - id="start" - inputProps={{ placeholder: 'Start Date' }} - value={config.start} - onSelect={value => updateField('start', value)} - containerProps={{ style: { width: 100 } }} - dateFormat={dateFormat} - /> + <InitialFocus> + <DateSelect + id="start" + inputProps={{ placeholder: 'Start Date' }} + value={config.start} + onSelect={value => updateField('start', value)} + containerProps={{ style: { width: 100 } }} + dateFormat={dateFormat} + /> + </InitialFocus> <Select id="repeat_end_dropdown" options={[ diff --git a/upcoming-release-notes/2974.md b/upcoming-release-notes/2974.md new file mode 100644 index 000000000..ec6472220 --- /dev/null +++ b/upcoming-release-notes/2974.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [psybers] +--- + +Fix: Automatically focus inputs, or the primary button, in modals. -- GitLab