Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix focus on footer link selection #33743

Merged
merged 2 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/pages/signin/LoginForm/BaseLoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@ function LoginForm(props) {
isInputFocused() {
return input.current && input.current.isFocused();
},
clearDataAndFocus(clearLogin = true) {
if (!input.current) {
return;
}
if (clearLogin) {
Session.clearSignInData();
}
input.current.focus();
},
}));

const formErrorText = useMemo(() => (formError ? translate(formError) : ''), [formError, translate]);
Expand Down
20 changes: 18 additions & 2 deletions src/pages/signin/LoginForm/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import PropTypes from 'prop-types';
import React from 'react';
import refPropTypes from '@components/refPropTypes';
import BaseLoginForm from './BaseLoginForm';

const propTypes = {
/** Function used to scroll to the top of the page */
scrollPageToTop: PropTypes.func,

/** A reference so we can expose clearDataAndFocus */
innerRef: refPropTypes,
};
const defaultProps = {
scrollPageToTop: undefined,
innerRef: () => {},
};

function LoginForm(props) {
function LoginForm({innerRef, ...props}) {
return (
<BaseLoginForm
ref={innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
Expand All @@ -23,4 +29,14 @@ LoginForm.displayName = 'LoginForm';
LoginForm.propTypes = propTypes;
LoginForm.defaultProps = defaultProps;

export default LoginForm;
const LoginFormWithRef = React.forwardRef((props, ref) => (
<LoginForm
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
innerRef={ref}
/>
));

LoginFormWithRef.displayName = 'LoginFormWithRef';

export default LoginFormWithRef;
30 changes: 27 additions & 3 deletions src/pages/signin/LoginForm/index.native.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import PropTypes from 'prop-types';
import React, {useEffect, useRef} from 'react';
import _ from 'underscore';
import refPropTypes from '@components/refPropTypes';
import AppStateMonitor from '@libs/AppStateMonitor';
import BaseLoginForm from './BaseLoginForm';

const propTypes = {
/** Function used to scroll to the top of the page */
scrollPageToTop: PropTypes.func,

/** A reference so we can expose clearDataAndFocus */
innerRef: refPropTypes,
};
const defaultProps = {
scrollPageToTop: undefined,
innerRef: () => {},
};

function LoginForm(props) {
function LoginForm({innerRef, ...props}) {
const loginFormRef = useRef();
const {scrollPageToTop} = props;

Expand All @@ -36,7 +42,15 @@ function LoginForm(props) {
<BaseLoginForm
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={(ref) => (loginFormRef.current = ref)}
ref={(ref) => {
loginFormRef.current = ref;
if (typeof innerRef === 'function') {
innerRef(ref);
} else if (innerRef && _.has(innerRef, 'current')) {
// eslint-disable-next-line no-param-reassign
innerRef.current = ref;
}
}}
/>
);
}
Expand All @@ -45,4 +59,14 @@ LoginForm.displayName = 'LoginForm';
LoginForm.propTypes = propTypes;
LoginForm.defaultProps = defaultProps;

export default LoginForm;
const LoginFormWithRef = React.forwardRef((props, ref) => (
<LoginForm
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
innerRef={ref}
/>
));

LoginFormWithRef.displayName = 'LoginFormWithRef';

export default LoginFormWithRef;
8 changes: 8 additions & 0 deletions src/pages/signin/SignInPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ function SignInPageInner({credentials, account, isInModal, activeClients, prefer
const shouldShowSmallScreen = isSmallScreenWidth || isInModal;
const safeAreaInsets = useSafeAreaInsets();
const signInPageLayoutRef = useRef();
const loginFormRef = useRef();
/** This state is needed to keep track of if user is using recovery code instead of 2fa code,
* and we need it here since welcome text(`welcomeText`) also depends on it */
const [isUsingRecoveryCode, setIsUsingRecoveryCode] = useState(false);
Expand Down Expand Up @@ -242,6 +243,11 @@ function SignInPageInner({credentials, account, isInModal, activeClients, prefer
Log.warn('SignInPage in unexpected state!');
}

const navigateFocus = () => {
signInPageLayoutRef.current.scrollPageToTop();
loginFormRef.current.clearDataAndFocus();
};

return (
// Bottom SafeAreaView is removed so that login screen svg displays correctly on mobile.
// The SVG should flow under the Home Indicator on iOS.
Expand All @@ -253,10 +259,12 @@ function SignInPageInner({credentials, account, isInModal, activeClients, prefer
shouldShowWelcomeText={shouldShowWelcomeText}
ref={signInPageLayoutRef}
shouldShowSmallScreen={shouldShowSmallScreen}
navigateFocus={navigateFocus}
>
{/* LoginForm must use the isVisible prop. This keeps it mounted, but visually hidden
so that password managers can access the values. Conditionally rendering this component will break this feature. */}
<LoginForm
ref={loginFormRef}
isInModal={isInModal}
isVisible={shouldShowLoginForm}
blurOnSubmit={account.validated === false}
Expand Down
18 changes: 5 additions & 13 deletions src/pages/signin/SignInPageLayout/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,19 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Licenses from '@pages/signin/Licenses';
import Socials from '@pages/signin/Socials';
import variables from '@styles/variables';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';

const propTypes = {
...withLocalizePropTypes,
scrollPageToTop: PropTypes.func.isRequired,
navigateFocus: PropTypes.func.isRequired,
shouldShowSmallScreen: PropTypes.bool,
};

const defaultProps = {
shouldShowSmallScreen: false,
};

const navigateHome = (scrollPageToTop) => {
scrollPageToTop();

// We need to clear sign in data in case the user is already in the ValidateCodeForm or PasswordForm pages
Session.clearSignInData();
};

const columns = ({scrollPageToTop}) => [
const columns = ({navigateFocus}) => [
{
translationPath: 'footer.features',
rows: [
Expand Down Expand Up @@ -135,11 +127,11 @@ const columns = ({scrollPageToTop}) => [
translationPath: 'footer.getStarted',
rows: [
{
onPress: () => navigateHome(scrollPageToTop),
onPress: () => navigateFocus(),
translationPath: 'footer.createAccount',
},
{
onPress: () => navigateHome(scrollPageToTop),
onPress: () => navigateFocus(),
translationPath: 'footer.logIn',
},
],
Expand Down Expand Up @@ -172,7 +164,7 @@ function Footer(props) {
) : null}
<View style={pageFooterWrapper}>
<View style={footerColumns}>
{_.map(columns({scrollPageToTop: props.scrollPageToTop}), (column, i) => (
{_.map(columns({navigateFocus: props.navigateFocus}), (column, i) => (
<View
key={column.translationPath}
style={footerColumn}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/signin/SignInPageLayout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ function SignInPageLayout(props) {
customHeadline={props.customHeadline}
customHeroBody={props.customHeroBody}
/>
<Footer scrollPageToTop={scrollPageToTop} />
<Footer navigateFocus={props.navigateFocus} />
</View>
</View>
</View>
Expand Down Expand Up @@ -179,7 +179,7 @@ function SignInPageLayout(props) {
</View>
<View style={[styles.flex0]}>
<Footer
scrollPageToTop={scrollPageToTop}
navigateFocus={props.navigateFocus}
shouldShowSmallScreen
/>
</View>
Expand Down