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

Ideal nav merge search bar #11

Merged
merged 15 commits into from
Dec 21, 2023
Merged
2 changes: 1 addition & 1 deletion assets/images/new-expensify.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import EReceiptIcon from '@assets/images/eReceiptIcon.svg';
import Exclamation from '@assets/images/exclamation.svg';
import Exit from '@assets/images/exit.svg';
import Expand from '@assets/images/expand.svg';
import ExpensifyAppIcon from '@assets/images/expensify-app-icon.svg';
import ExpensifyFooterLogoVertical from '@assets/images/expensify-footer-logo-vertical.svg';
import ExpensifyFooterLogo from '@assets/images/expensify-footer-logo.svg';
import ExpensifyWordmark from '@assets/images/expensify-wordmark.svg';
Expand Down Expand Up @@ -185,6 +186,7 @@ export {
EmptyStateAttachReceipt,
Exclamation,
Exit,
ExpensifyAppIcon,
ExpensifyCard,
ExpensifyWordmark,
ExpensifyFooterLogo,
Expand Down
1 change: 1 addition & 0 deletions src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,5 @@ function Icon({

Icon.displayName = 'Icon';

export type {IconProps};
export default Icon;
4 changes: 2 additions & 2 deletions src/components/IllustratedHeaderPageLayout.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import useTheme from '../hooks/useTheme';
import useThemeStyles from '../hooks/useThemeStyles';
import HeaderPageLayout from './HeaderPageLayout';
import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBackButtonPropTypes';
import Lottie from './Lottie';
Expand Down
8 changes: 7 additions & 1 deletion src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const defaultProps = {
shouldShowRightComponent: false,
titleWithTooltips: [],
shouldCheckActionAllowedOnPress: true,
isPaneMenu: false,
};

const MenuItem = React.forwardRef((props, ref) => {
Expand Down Expand Up @@ -234,6 +235,7 @@ const MenuItem = React.forwardRef((props, ref) => {
StyleUtils.getIconFillColor(
getButtonState(props.focused || isHovered, pressed, props.success, props.disabled, props.interactive),
true,
props.isPaneMenu,
)
}
/>
Expand Down Expand Up @@ -266,7 +268,11 @@ const MenuItem = React.forwardRef((props, ref) => {
height={props.iconHeight}
fill={
props.secondaryIconFill ||
StyleUtils.getIconFillColor(getButtonState(props.focused || isHovered, pressed, props.success, props.disabled, props.interactive), true)
StyleUtils.getIconFillColor(
getButtonState(props.focused || isHovered, pressed, props.success, props.disabled, props.interactive),
true,
props.isPaneMenu,
)
}
/>
</View>
Expand Down
86 changes: 64 additions & 22 deletions src/components/SubscriptAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@ import type {AvatarSource} from '@libs/UserUtils';
import CONST from '@src/CONST';
import {AvatarType} from '@src/types/onyx/OnyxCommon';
import Avatar from './Avatar';
import Icon, {IconProps} from './Icon';
import UserDetailsTooltip from './UserDetailsTooltip';

type SubIcon = {
/** Avatar source to display */
source: IconProps['src'];

/** Width of the icon */
width?: number;

/** Height of the icon */
height?: number;
};

type SubAvatar = {
/** Avatar source to display */
source?: AvatarSource;
Expand All @@ -31,23 +43,26 @@ type SubscriptAvatarProps = {
/** Avatar URL or icon */
mainAvatar?: SubAvatar;

/** Subscript avatar URL or icon */
secondaryAvatar?: SubAvatar;

/** Set the size of avatars */
size?: ValueOf<typeof CONST.AVATAR_SIZE>;

/** Background color used for subscript avatar border */
backgroundColor?: string;

/** Subscript avatar URL or icon */
secondaryAvatar?: SubAvatar;

/** Subscript icon type */
subscriptIcon?: SubIcon;

/** Removes margin from around the avatar, used for the chat view */
noMargin?: boolean;

/** Whether to show the tooltip */
showTooltip?: boolean;
};

function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) {
function SubscriptAvatar({mainAvatar = {}, secondaryAvatar, subscriptIcon, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand All @@ -73,31 +88,58 @@ function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AV
/>
</View>
</UserDetailsTooltip>
<UserDetailsTooltip
shouldRender={showTooltip}
accountID={secondaryAvatar.id ?? -1}
icon={secondaryAvatar}
>
{secondaryAvatar && (
<UserDetailsTooltip
shouldRender={showTooltip}
accountID={secondaryAvatar.id ?? -1}
icon={secondaryAvatar}
>
<View
style={[size === CONST.AVATAR_SIZE.SMALL_NORMAL ? styles.flex1 : {}, isSmall ? styles.secondAvatarSubscriptCompact : subscriptStyle]}
// Hover on overflowed part of icon will not work on Electron if dragArea is true
// https://stackoverflow.com/questions/56338939/hover-in-css-is-not-working-with-electron
dataSet={{dragArea: false}}
>
<Avatar
iconAdditionalStyles={[
StyleUtils.getAvatarBorderWidth(isSmall ? CONST.AVATAR_SIZE.SMALL_SUBSCRIPT : CONST.AVATAR_SIZE.SUBSCRIPT),
StyleUtils.getBorderColorStyle(backgroundColor ?? theme.componentBG),
]}
source={secondaryAvatar.source}
size={isSmall ? CONST.AVATAR_SIZE.SMALL_SUBSCRIPT : CONST.AVATAR_SIZE.SUBSCRIPT}
fill={theme.iconSuccessFill}
name={secondaryAvatar.name}
type={secondaryAvatar.type}
fallbackIcon={secondaryAvatar.fallbackIcon}
/>
</View>
</UserDetailsTooltip>
)}
{subscriptIcon && (
<View
style={[size === CONST.AVATAR_SIZE.SMALL_NORMAL ? styles.flex1 : {}, isSmall ? styles.secondAvatarSubscriptCompact : subscriptStyle]}
style={[
size === CONST.AVATAR_SIZE.SMALL_NORMAL ? styles.flex1 : {},
StyleUtils.getAvatarBorderStyle(CONST.AVATAR_SIZE.SMALL, CONST.ICON_TYPE_AVATAR),
StyleUtils.getAvatarBorderWidth(CONST.AVATAR_SIZE.SMALL),
// Nullish coalescing thinks that empty strings are truthy, thus I'm using OR operator
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
StyleUtils.getBorderColorStyle(backgroundColor || theme.sidebar),
styles.subscriptIcon,
styles.dFlex,
styles.justifyContentCenter,
]}
// Hover on overflowed part of icon will not work on Electron if dragArea is true
// https://stackoverflow.com/questions/56338939/hover-in-css-is-not-working-with-electron
dataSet={{dragArea: false}}
>
<Avatar
iconAdditionalStyles={[
StyleUtils.getAvatarBorderWidth(isSmall ? CONST.AVATAR_SIZE.SMALL_SUBSCRIPT : CONST.AVATAR_SIZE.SUBSCRIPT),
StyleUtils.getBorderColorStyle(backgroundColor ?? theme.componentBG),
]}
source={secondaryAvatar.source}
size={isSmall ? CONST.AVATAR_SIZE.SMALL_SUBSCRIPT : CONST.AVATAR_SIZE.SUBSCRIPT}
fill={theme.iconSuccessFill}
name={secondaryAvatar.name}
type={secondaryAvatar.type}
fallbackIcon={secondaryAvatar.fallbackIcon}
<Icon
src={subscriptIcon.source}
width={subscriptIcon.width}
height={subscriptIcon.height}
additionalStyles={styles.alignSelfCenter}
/>
</View>
</UserDetailsTooltip>
)}
</View>
);
}
Expand Down
30 changes: 30 additions & 0 deletions src/components/WorkspaceSwitcherButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import useLocalize from '@hooks/useLocalize';
import CONST from '@src/CONST';
import * as Expensicons from './Icon/Expensicons';
import {PressableWithFeedback} from './Pressable';
import SubscriptAvatar from './SubscriptAvatar';

function WorkspaceSwitcherButton() {
const {translate} = useLocalize();

return (
<PressableWithFeedback
accessibilityRole={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.workspaces')}
accessible
onPress={() => {}}
>
<SubscriptAvatar
mainAvatar={{source: Expensicons.ExpensifyAppIcon, name: 'Expensify', type: CONST.ICON_TYPE_AVATAR}}
subscriptIcon={{source: Expensicons.DownArrow, width: 8, height: 8}}
showTooltip={false}
noMargin
/>
</PressableWithFeedback>
);
}

WorkspaceSwitcherButton.displayName = 'WorkspaceSwitcherButton';

export default WorkspaceSwitcherButton;
3 changes: 3 additions & 0 deletions src/components/menuItemPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ const propTypes = {

/** Should check anonymous user in onPress function */
shouldCheckActionAllowedOnPress: PropTypes.bool,

/** Is this menu item in the settings pane */
isPaneMenu: PropTypes.bool,
};

export default propTypes;
1 change: 0 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1996,7 +1996,6 @@ export default {
transactionDate: 'Transaction date',
},
breadcrumbs: {
// TODO-IDEAL: Verify translations in Spanish
chats: 'Chats',
},
referralProgram: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import PropTypes from 'prop-types';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can TopBar.js be written in TS?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, the SignInOrAvatarWithOptionalStatus is written in JS.

import React, {useCallback} from 'react';
import {View} from 'react-native';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Search from '@components/Search';
import SubscriptAvatar from '@components/SubscriptAvatar';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import SignInOrAvatarWithOptionalStatus from '@pages/home/sidebar/SignInOrAvatarWithOptionalStatus';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

// TODO-IDEAL: isCreateMenuOpen wasn't used before
function TopBar({isCreateMenuOpen = false}) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const showSearchPage = useCallback(() => {
if (isCreateMenuOpen) {
// Prevent opening Search page when click Search icon quickly after clicking FAB icon
return;
}

Navigation.navigate(ROUTES.SEARCH);
}, [isCreateMenuOpen]);

return (
<View
style={[styles.gap6, styles.flexRow, styles.ph5, styles.pv3, styles.justifyContentBetween, styles.alignItemsCenter]}
dataSet={{dragArea: true}}
>
<PressableWithFeedback role={CONST.ROLE.BUTTON}>
<SubscriptAvatar
mainAvatar={{source: Expensicons.ExpensifyAppIcon, name: 'Expensify', type: CONST.ICON_TYPE_AVATAR}}
subscriptIcon={{source: Expensicons.DownArrow, width: 8, height: 8}}
showTooltip={false}
noMargin
/>
</PressableWithFeedback>
<Search
placeholder={translate('sidebarScreen.buttonSearch')}
onPress={Session.checkIfActionIsAllowed(showSearchPage)}
style={{flex: 1}}
/>
<SignInOrAvatarWithOptionalStatus isCreateMenuOpen={isCreateMenuOpen} />
</View>
);
}

TopBar.displayName = 'TopBar';
TopBar.propTypes = {
isCreateMenuOpen: PropTypes.bool,
};
TopBar.defaultProps = {
isCreateMenuOpen: false,
};

export default TopBar;
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {View} from 'react-native';
import {NavigationStateRoute} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';
import BottomTabBar from './BottomTabBar';
import TopBar from './TopBar';

type CustomNavigatorProps = DefaultNavigatorOptions<ParamListBase, StackNavigationState<ParamListBase>, StackNavigationOptions, StackNavigationEventMap> & {
initialRouteName: string;
Expand Down Expand Up @@ -67,6 +68,7 @@ function CustomBottomTabNavigator({initialRouteName, children, screenOptions, ..

return (
<View style={{flex: 1}}>
<TopBar />
<NavigationContent>
<StackView
// eslint-disable-next-line react/jsx-props-no-spreading
Expand Down
9 changes: 9 additions & 0 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@ function goBack(fallbackRoute: Route, shouldEnforceFallback = false, shouldPopTo
navigationRef.current.goBack();
}

/**
* Close the full screen modal.
*/
function closeFullScreen() {
const rootState = navigationRef.getRootState();
navigationRef.dispatch({...StackActions.pop(), target: rootState.key});
}

/**
* Update route params for the specified route.
*/
Expand Down Expand Up @@ -307,6 +315,7 @@ export default {
getRouteNameFromStateEvent,
getTopmostReportActionId,
waitForProtectedRoutes,
closeFullScreen,
};

export {navigationRef};
Loading
Loading