Skip to content

Commit 452d18d

Browse files
authored
Merge pull request Expensify#92749 from Expensify/claude-a11yPreventAutoCloseOnSelect
Prevent panel auto-close on selection (WCAG 3.2.2) for Pronouns and Cash Expense Default
2 parents 1765e75 + fe15e4f commit 452d18d

2 files changed

Lines changed: 49 additions & 17 deletions

File tree

src/pages/settings/Profile/PronounsPage.tsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, useMemo, useRef, useState} from 'react';
1+
import React, {useCallback, useEffect, useMemo, useState} from 'react';
22
import CollapsibleHeaderOnKeyboard from '@components/CollapsibleHeaderOnKeyboard';
33
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
44
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -31,7 +31,7 @@ function PronounsPage({currentUserPersonalDetails}: PronounsPageProps) {
3131
const currentPronouns = currentUserPersonalDetails?.pronouns ?? '';
3232
const currentPronounsKey = currentPronouns.substring(CONST.PRONOUNS.PREFIX.length);
3333
const [searchValue, setSearchValue] = useState('');
34-
const isOptionSelected = useRef(false);
34+
const [selectedPronouns, setSelectedPronouns] = useState(currentPronouns);
3535
const currentUserAccountID = currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID;
3636

3737
useEffect(() => {
@@ -41,6 +41,7 @@ function PronounsPage({currentUserPersonalDetails}: PronounsPageProps) {
4141
const currentPronounsText = CONST.PRONOUNS_LIST.find((value) => value === currentPronounsKey);
4242

4343
setSearchValue(currentPronounsText ? translate(`pronouns.${currentPronounsText}`) : '');
44+
setSelectedPronouns(currentPronouns);
4445

4546
// Only need to update search value when the first time the data is loaded
4647
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -49,13 +50,12 @@ function PronounsPage({currentUserPersonalDetails}: PronounsPageProps) {
4950
const filteredPronounsList = useMemo((): PronounEntry[] => {
5051
const pronouns = CONST.PRONOUNS_LIST.map((value) => {
5152
const fullPronounKey = `${CONST.PRONOUNS.PREFIX}${value}`;
52-
const isCurrentPronouns = fullPronounKey === currentPronouns;
5353

5454
return {
5555
text: translate(`pronouns.${value}`),
5656
value: fullPronounKey,
5757
keyForList: value,
58-
isSelected: isCurrentPronouns,
58+
isSelected: fullPronounKey === selectedPronouns,
5959
};
6060
}).sort((a, b) => localeCompare(a.text.toLowerCase(), b.text.toLowerCase()));
6161

@@ -65,17 +65,26 @@ function PronounsPage({currentUserPersonalDetails}: PronounsPageProps) {
6565
return [];
6666
}
6767
return pronouns.filter((pronoun) => pronoun.text.toLowerCase().indexOf(trimmedSearch.toLowerCase()) >= 0);
68-
}, [searchValue, currentPronouns, translate, localeCompare]);
68+
}, [searchValue, selectedPronouns, translate, localeCompare]);
6969

70-
const updatePronouns = (selectedPronouns: PronounEntry) => {
71-
if (isOptionSelected.current) {
72-
return;
73-
}
74-
isOptionSelected.current = true;
75-
updatePronounsPersonalDetails(selectedPronouns.keyForList === currentPronounsKey ? '' : (selectedPronouns?.value ?? ''), currentUserAccountID);
76-
Navigation.goBack();
70+
const selectPronoun = (selectedPronoun: PronounEntry) => {
71+
setSelectedPronouns(selectedPronoun.value === selectedPronouns ? '' : (selectedPronoun?.value ?? ''));
7772
};
7873

74+
const savePronouns = useCallback(() => {
75+
updatePronounsPersonalDetails(selectedPronouns, currentUserAccountID);
76+
Navigation.goBack();
77+
}, [selectedPronouns, currentUserAccountID]);
78+
79+
const confirmButtonOptions = useMemo(
80+
() => ({
81+
showButton: true,
82+
text: translate('common.save'),
83+
onConfirm: savePronouns,
84+
}),
85+
[savePronouns, translate],
86+
);
87+
7988
const textInputOptions = useMemo(
8089
() => ({
8190
label: translate('pronounsPage.pronouns'),
@@ -107,9 +116,10 @@ function PronounsPage({currentUserPersonalDetails}: PronounsPageProps) {
107116
<SelectionList
108117
data={filteredPronounsList}
109118
ListItem={SingleSelectListItem}
110-
onSelectRow={updatePronouns}
119+
onSelectRow={selectPronoun}
111120
textInputOptions={textInputOptions}
112121
initiallyFocusedItemKey={currentPronounsKey}
122+
confirmButtonOptions={confirmButtonOptions}
113123
shouldSingleExecuteRowSelect
114124
/>
115125
</>

src/pages/workspace/rules/RulesReimbursableDefaultPage.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, {useCallback, useMemo, useState} from 'react';
22
import HeaderWithBackButton from '@components/HeaderWithBackButton';
33
import ScreenWrapper from '@components/ScreenWrapper';
44
import SelectionList from '@components/SelectionList';
@@ -28,14 +28,36 @@ function RulesReimbursableDefaultPage({
2828

2929
const reimbursableMode = getCashExpenseReimbursableMode(policy);
3030

31+
// The draft holds the user's in-page selection. Until they pick a row it stays undefined and we fall back to the
32+
// persisted reimbursableMode, which also covers the page rendering before the policy is in Onyx.
33+
const [draftMode, setDraftMode] = useState<typeof reimbursableMode>();
34+
const selectedMode = draftMode ?? reimbursableMode;
35+
3136
const reimbursableModes = Object.values(CONST.POLICY.CASH_EXPENSE_REIMBURSEMENT_CHOICES).map((mode) => ({
3237
text: translate(`workspace.rules.individualExpenseRules.${mode}`),
3338
alternateText: translate(`workspace.rules.individualExpenseRules.${mode}Description`),
3439
value: mode,
35-
isSelected: reimbursableMode === mode,
40+
isSelected: selectedMode === mode,
3641
keyForList: mode,
3742
}));
3843

44+
const saveAndGoBack = useCallback(() => {
45+
if (!selectedMode) {
46+
return;
47+
}
48+
setPolicyReimbursableMode(policyID, selectedMode, policy?.defaultReimbursable, policy?.disabledFields?.reimbursable);
49+
Navigation.goBack();
50+
}, [policyID, selectedMode, policy?.defaultReimbursable, policy?.disabledFields?.reimbursable]);
51+
52+
const confirmButtonOptions = useMemo(
53+
() => ({
54+
showButton: true,
55+
text: translate('common.save'),
56+
onConfirm: saveAndGoBack,
57+
}),
58+
[saveAndGoBack, translate],
59+
);
60+
3961
return (
4062
<AccessOrNotFoundWrapper
4163
policyID={policyID}
@@ -58,9 +80,9 @@ function RulesReimbursableDefaultPage({
5880
data={reimbursableModes}
5981
ListItem={SingleSelectListItem}
6082
onSelectRow={(item) => {
61-
setPolicyReimbursableMode(policyID, item.value, policy?.defaultReimbursable, policy?.disabledFields?.reimbursable);
62-
Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack);
83+
setDraftMode(item.value);
6384
}}
85+
confirmButtonOptions={confirmButtonOptions}
6486
shouldSingleExecuteRowSelect
6587
style={{containerStyle: styles.pt3}}
6688
initiallyFocusedItemKey={reimbursableMode}

0 commit comments

Comments
 (0)