-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
migrate FloatingActionButtonAndPopover to function component #21851
Changes from 3 commits
dac3d1f
fc0304a
4a3baa4
1786713
1759451
15c3a22
6353d5d
bf25b34
cc776a0
a7863fa
1d830cf
139dbeb
63b77e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,4 @@ | ||||||||||||||
import React from 'react'; | ||||||||||||||
import React, {useState, useEffect, useCallback, useImperativeHandle, forwardRef} from 'react'; | ||||||||||||||
import _ from 'underscore'; | ||||||||||||||
import {withOnyx} from 'react-native-onyx'; | ||||||||||||||
import PropTypes from 'prop-types'; | ||||||||||||||
|
@@ -25,6 +25,7 @@ import withNavigationFocus from '../../../../components/withNavigationFocus'; | |||||||||||||
import * as Task from '../../../../libs/actions/Task'; | ||||||||||||||
import * as Session from '../../../../libs/actions/Session'; | ||||||||||||||
import * as IOU from '../../../../libs/actions/IOU'; | ||||||||||||||
import usePrevious from '../../../../hooks/usePrevious'; | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* @param {Object} [policy] | ||||||||||||||
|
@@ -56,51 +57,29 @@ const propTypes = { | |||||||||||||
|
||||||||||||||
/** Indicated whether the report data is loading */ | ||||||||||||||
isLoading: PropTypes.bool, | ||||||||||||||
|
||||||||||||||
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), | ||||||||||||||
}; | ||||||||||||||
const defaultProps = { | ||||||||||||||
onHideCreateMenu: () => {}, | ||||||||||||||
onShowCreateMenu: () => {}, | ||||||||||||||
allPolicies: {}, | ||||||||||||||
betas: [], | ||||||||||||||
isLoading: false, | ||||||||||||||
innerRef: null, | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Responsible for rendering the {@link PopoverMenu}, and the accompanying | ||||||||||||||
* FAB that can open or close the menu. | ||||||||||||||
* @param {Object} props | ||||||||||||||
* @returns {JSX.Element} | ||||||||||||||
*/ | ||||||||||||||
class FloatingActionButtonAndPopover extends React.Component { | ||||||||||||||
constructor(props) { | ||||||||||||||
super(props); | ||||||||||||||
|
||||||||||||||
this.showCreateMenu = this.showCreateMenu.bind(this); | ||||||||||||||
this.hideCreateMenu = this.hideCreateMenu.bind(this); | ||||||||||||||
this.interceptAnonymousUser = this.interceptAnonymousUser.bind(this); | ||||||||||||||
|
||||||||||||||
this.state = { | ||||||||||||||
isCreateMenuActive: false, | ||||||||||||||
isAnonymousUser: Session.isAnonymousUser(), | ||||||||||||||
}; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
componentDidMount() { | ||||||||||||||
const navigationState = this.props.navigation.getState(); | ||||||||||||||
const routes = lodashGet(navigationState, 'routes', []); | ||||||||||||||
const currentRoute = routes[navigationState.index]; | ||||||||||||||
if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
Welcome.show({routes, showCreateMenu: this.showCreateMenu}); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
componentDidUpdate(prevProps) { | ||||||||||||||
if (!this.didScreenBecomeInactive(prevProps)) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
function FloatingActionButtonAndPopover(props) { | ||||||||||||||
const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); | ||||||||||||||
const isAnonymousUser = Session.isAnonymousUser(); | ||||||||||||||
|
||||||||||||||
// Hide menu manually when other pages are opened using shortcut key | ||||||||||||||
this.hideCreateMenu(); | ||||||||||||||
} | ||||||||||||||
const prevIsFocused = usePrevious(props.isFocused); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Check if LHN status changed from active to inactive. | ||||||||||||||
|
@@ -109,152 +88,185 @@ class FloatingActionButtonAndPopover extends React.Component { | |||||||||||||
* @param {Object} prevProps | ||||||||||||||
* @return {Boolean} | ||||||||||||||
*/ | ||||||||||||||
didScreenBecomeInactive(prevProps) { | ||||||||||||||
const didScreenBecomeInactive = useCallback(() => { | ||||||||||||||
// When any other page is opened over LHN | ||||||||||||||
if (!this.props.isFocused && prevProps.isFocused) { | ||||||||||||||
if (!props.isFocused && prevIsFocused) { | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated as your suggestion. |
||||||||||||||
}, [props.isFocused, prevIsFocused]); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Method called when we click the floating action button | ||||||||||||||
*/ | ||||||||||||||
showCreateMenu() { | ||||||||||||||
if (!this.props.isFocused && this.props.isSmallScreenWidth) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
this.setState({ | ||||||||||||||
isCreateMenuActive: true, | ||||||||||||||
}); | ||||||||||||||
this.props.onShowCreateMenu(); | ||||||||||||||
} | ||||||||||||||
const showCreateMenu = useCallback( | ||||||||||||||
() => { | ||||||||||||||
if (!props.isFocused && props.isSmallScreenWidth) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
setIsCreateMenuActive(true); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB: extra blank line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed extra blank line. |
||||||||||||||
|
||||||||||||||
props.onShowCreateMenu(); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB: extra blank line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed extra blank line. |
||||||||||||||
}, | ||||||||||||||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||||||||||||||
[props.isFocused, props.isSmallScreenWidth], | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Method called either when: | ||||||||||||||
* - Pressing the floating action button to open the CreateMenu modal | ||||||||||||||
* - Selecting an item on CreateMenu or closing it by clicking outside of the modal component | ||||||||||||||
*/ | ||||||||||||||
hideCreateMenu() { | ||||||||||||||
if (!this.state.isCreateMenuActive) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
this.props.onHideCreateMenu(); | ||||||||||||||
this.setState({ | ||||||||||||||
isCreateMenuActive: false, | ||||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
const hideCreateMenu = useCallback( | ||||||||||||||
() => { | ||||||||||||||
if (!isCreateMenuActive) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
props.onHideCreateMenu(); | ||||||||||||||
setIsCreateMenuActive(false); | ||||||||||||||
}, | ||||||||||||||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||||||||||||||
[isCreateMenuActive], | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Checks if user is anonymous. If true, shows the sign in modal, else, | ||||||||||||||
* executes the callback. | ||||||||||||||
* | ||||||||||||||
* @param {Function} callback | ||||||||||||||
*/ | ||||||||||||||
interceptAnonymousUser(callback) { | ||||||||||||||
if (this.state.isAnonymousUser) { | ||||||||||||||
const interceptAnonymousUser = (callback) => { | ||||||||||||||
if (isAnonymousUser) { | ||||||||||||||
Session.signOutAndRedirectToSignIn(); | ||||||||||||||
} else { | ||||||||||||||
callback(); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
useEffect( | ||||||||||||||
() => { | ||||||||||||||
const navigationState = props.navigation.getState(); | ||||||||||||||
const routes = lodashGet(navigationState, 'routes', []); | ||||||||||||||
const currentRoute = routes[navigationState.index]; | ||||||||||||||
if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
Welcome.show({routes, showCreateMenu}); | ||||||||||||||
}, | ||||||||||||||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||||||||||||||
[], | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
render() { | ||||||||||||||
// Workspaces are policies with type === 'free' | ||||||||||||||
const workspaces = _.filter(this.props.allPolicies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE); | ||||||||||||||
useEffect(() => { | ||||||||||||||
if (!didScreenBecomeInactive()) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Hide menu manually when other pages are opened using shortcut key | ||||||||||||||
hideCreateMenu(); | ||||||||||||||
}, [didScreenBecomeInactive, hideCreateMenu]); | ||||||||||||||
|
||||||||||||||
useImperativeHandle(props.innerRef, () => ({ | ||||||||||||||
hideCreateMenu() { | ||||||||||||||
hideCreateMenu(); | ||||||||||||||
}, | ||||||||||||||
})); | ||||||||||||||
// Workspaces are policies with type === 'free' | ||||||||||||||
const workspaces = _.filter(props.allPolicies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a blank line above the comment please There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a blank line. |
||||||||||||||
|
||||||||||||||
return ( | ||||||||||||||
<View> | ||||||||||||||
<PopoverMenu | ||||||||||||||
onClose={this.hideCreateMenu} | ||||||||||||||
isVisible={this.state.isCreateMenuActive} | ||||||||||||||
anchorPosition={styles.createMenuPositionSidebar(this.props.windowHeight)} | ||||||||||||||
onItemSelected={this.hideCreateMenu} | ||||||||||||||
fromSidebarMediumScreen={!this.props.isSmallScreenWidth} | ||||||||||||||
menuItems={[ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.ChatBubble, | ||||||||||||||
text: this.props.translate('sidebarScreen.newChat'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => Navigation.navigate(ROUTES.NEW_CHAT)), | ||||||||||||||
}, | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Users, | ||||||||||||||
text: this.props.translate('sidebarScreen.newGroup'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => Navigation.navigate(ROUTES.NEW_GROUP)), | ||||||||||||||
}, | ||||||||||||||
...(Permissions.canUsePolicyRooms(this.props.betas) && workspaces.length | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Hashtag, | ||||||||||||||
text: this.props.translate('sidebarScreen.newRoom'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseIOUSend(this.props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Send, | ||||||||||||||
text: this.props.translate('iou.sendMoney'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.MONEY_REQUEST_TYPE.SEND)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseIOU(this.props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.MoneyCircle, | ||||||||||||||
text: this.props.translate('iou.requestMoney'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseIOU(this.props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Receipt, | ||||||||||||||
text: this.props.translate('iou.splitBill'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseTasks(this.props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Task, | ||||||||||||||
text: this.props.translate('newTaskPage.assignTask'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => Task.clearOutTaskInfoAndNavigate()), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(!this.props.isLoading && !Policy.hasActiveFreePolicy(this.props.allPolicies) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.NewWorkspace, | ||||||||||||||
iconWidth: 46, | ||||||||||||||
iconHeight: 40, | ||||||||||||||
text: this.props.translate('workspace.new.newWorkspace'), | ||||||||||||||
description: this.props.translate('workspace.new.getTheExpensifyCardAndMore'), | ||||||||||||||
onSelected: () => this.interceptAnonymousUser(() => Policy.createWorkspace()), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
]} | ||||||||||||||
/> | ||||||||||||||
<FloatingActionButton | ||||||||||||||
accessibilityLabel={this.props.translate('sidebarScreen.fabNewChat')} | ||||||||||||||
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} | ||||||||||||||
isActive={this.state.isCreateMenuActive} | ||||||||||||||
onPress={this.showCreateMenu} | ||||||||||||||
/> | ||||||||||||||
</View> | ||||||||||||||
); | ||||||||||||||
} | ||||||||||||||
return ( | ||||||||||||||
<View> | ||||||||||||||
<PopoverMenu | ||||||||||||||
onClose={hideCreateMenu} | ||||||||||||||
isVisible={isCreateMenuActive} | ||||||||||||||
anchorPosition={styles.createMenuPositionSidebar(props.windowHeight)} | ||||||||||||||
onItemSelected={hideCreateMenu} | ||||||||||||||
fromSidebarMediumScreen={!props.isSmallScreenWidth} | ||||||||||||||
menuItems={[ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.ChatBubble, | ||||||||||||||
text: props.translate('sidebarScreen.newChat'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.NEW_CHAT)), | ||||||||||||||
}, | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Users, | ||||||||||||||
text: props.translate('sidebarScreen.newGroup'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.NEW_GROUP)), | ||||||||||||||
}, | ||||||||||||||
...(Permissions.canUsePolicyRooms(props.betas) && workspaces.length | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Hashtag, | ||||||||||||||
text: props.translate('sidebarScreen.newRoom'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseIOUSend(props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Send, | ||||||||||||||
text: props.translate('iou.sendMoney'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.MONEY_REQUEST_TYPE.SEND)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseIOU(props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.MoneyCircle, | ||||||||||||||
text: props.translate('iou.requestMoney'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseIOU(props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Receipt, | ||||||||||||||
text: props.translate('iou.splitBill'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(Permissions.canUseTasks(props.betas) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.Task, | ||||||||||||||
text: props.translate('newTaskPage.assignTask'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => Task.clearOutTaskInfoAndNavigate()), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
...(!props.isLoading && !Policy.hasActiveFreePolicy(props.allPolicies) | ||||||||||||||
? [ | ||||||||||||||
{ | ||||||||||||||
icon: Expensicons.NewWorkspace, | ||||||||||||||
iconWidth: 46, | ||||||||||||||
iconHeight: 40, | ||||||||||||||
text: props.translate('workspace.new.newWorkspace'), | ||||||||||||||
description: props.translate('workspace.new.getTheExpensifyCardAndMore'), | ||||||||||||||
onSelected: () => interceptAnonymousUser(() => Policy.createWorkspace()), | ||||||||||||||
}, | ||||||||||||||
] | ||||||||||||||
: []), | ||||||||||||||
]} | ||||||||||||||
/> | ||||||||||||||
<FloatingActionButton | ||||||||||||||
accessibilityLabel={props.translate('sidebarScreen.fabNewChat')} | ||||||||||||||
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} | ||||||||||||||
isActive={isCreateMenuActive} | ||||||||||||||
onPress={showCreateMenu} | ||||||||||||||
/> | ||||||||||||||
</View> | ||||||||||||||
); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
FloatingActionButtonAndPopover.propTypes = propTypes; | ||||||||||||||
FloatingActionButtonAndPopover.defaultProps = defaultProps; | ||||||||||||||
FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; | ||||||||||||||
|
||||||||||||||
export default compose( | ||||||||||||||
withLocalize, | ||||||||||||||
|
@@ -274,4 +286,12 @@ export default compose( | |||||||||||||
key: ONYXKEYS.IS_LOADING_REPORT_DATA, | ||||||||||||||
}, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like there is two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I checked and removed the duplication. It was from the original class component. |
||||||||||||||
}), | ||||||||||||||
)(FloatingActionButtonAndPopover); | ||||||||||||||
)( | ||||||||||||||
forwardRef((props, ref) => ( | ||||||||||||||
<FloatingActionButtonAndPopover | ||||||||||||||
// eslint-disable-next-line react/jsx-props-no-spreading | ||||||||||||||
{...props} | ||||||||||||||
innerRef={ref} | ||||||||||||||
/> | ||||||||||||||
)), | ||||||||||||||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dayana7204 Kindly add comment for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. thank you :)