Skip to content
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
27 changes: 16 additions & 11 deletions src/components/FloatingActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type FloatingActionButtonProps = {
};

function FloatingActionButton({onPress, onLongPress, isActive, accessibilityLabel, role, isTooltipAllowed, ref}: FloatingActionButtonProps) {
const {success, buttonDefaultBG, textLight} = useTheme();
const {success, successHover, buttonDefaultBG, textLight} = useTheme();
const styles = useThemeStyles();
const borderRadius = styles.floatingActionButton.borderRadius;
const fabPressable = useRef<HTMLDivElement | View | Text | null>(null);
Expand Down Expand Up @@ -142,17 +142,22 @@ function FloatingActionButton({onPress, onLongPress, isActive, accessibilityLabe
shouldUseHapticsOnLongPress
testID="floating-action-button"
>
<Animated.View style={[styles.floatingActionButton, {borderRadius}, isLHBVisible && styles.floatingActionButtonSmall, animatedStyle]}>
<Svg
width={fabSize}
height={fabSize}
{({hovered}) => (
<Animated.View
style={[styles.floatingActionButton, {borderRadius}, isLHBVisible && styles.floatingActionButtonSmall, animatedStyle, hovered && {backgroundColor: successHover}]}
testID="fab-animated-container"
>
<AnimatedPath
d={isLHBVisible ? SMALL_FAB_PATH : FAB_PATH}
fill={textLight}
/>
</Svg>
</Animated.View>
<Svg
width={fabSize}
height={fabSize}
>
<AnimatedPath
d={isLHBVisible ? SMALL_FAB_PATH : FAB_PATH}
fill={textLight}
/>
</Svg>
</Animated.View>
)}
</PressableWithoutFeedback>
</EducationalTooltip>
);
Expand Down
170 changes: 101 additions & 69 deletions src/components/Navigation/NavigationTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ type NavigationTabBarProps = {
function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar = false}: NavigationTabBarProps) {
const theme = useTheme();
const styles = useThemeStyles();

const getIconFill = useCallback(
(isSelected: boolean, isHovered: boolean) => {
if (isSelected) {
return theme.iconMenu;
}
if (isHovered) {
return theme.success;
}
return theme.icon;
},
[theme],
);
const {translate, preferredLocale} = useLocalize();
const {indicatorColor: workspacesTabIndicatorColor, status: workspacesTabIndicatorStatus} = useWorkspacesTabIndicatorStatus();
const {orderedReportIDs} = useSidebarOrderedReports();
Expand Down Expand Up @@ -210,89 +223,108 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
onPress={navigateToChats}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.inbox')}
style={styles.leftNavigationTabBarItem}
style={({hovered}) => [styles.leftNavigationTabBarItem, hovered && styles.navigationTabBarItemHovered]}
>
<View>
<Icon
src={Expensicons.Inbox}
fill={selectedTab === NAVIGATION_TABS.HOME ? theme.iconMenu : theme.icon}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
{!!chatTabBrickRoad && (
<View
style={styles.navigationTabBarStatusIndicator(chatTabBrickRoad === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? theme.iconSuccessFill : theme.danger)}
/>
)}
</View>
<Text
numberOfLines={2}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === NAVIGATION_TABS.HOME ? styles.textBold : styles.textSupporting,
styles.navigationTabBarLabel,
]}
>
{translate('common.inbox')}
</Text>
{({hovered}) => (
<>
<View>
<Icon
src={Expensicons.Inbox}
fill={getIconFill(selectedTab === NAVIGATION_TABS.HOME, hovered)}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
{!!chatTabBrickRoad && (
<View
style={[
styles.navigationTabBarStatusIndicator(
chatTabBrickRoad === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? theme.iconSuccessFill : theme.danger,
),
hovered && {borderColor: theme.sidebarHover},
]}
/>
)}
</View>
<Text
numberOfLines={2}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === NAVIGATION_TABS.HOME ? styles.textBold : styles.textSupporting,
styles.navigationTabBarLabel,
]}
>
{translate('common.inbox')}
</Text>
</>
)}
</PressableWithFeedback>
</EducationalTooltip>
<PressableWithFeedback
onPress={navigateToSearch}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.reports')}
style={styles.leftNavigationTabBarItem}
style={({hovered}) => [styles.leftNavigationTabBarItem, hovered && styles.navigationTabBarItemHovered]}
>
<View>
<Icon
src={Expensicons.MoneySearch}
fill={selectedTab === NAVIGATION_TABS.SEARCH ? theme.iconMenu : theme.icon}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
</View>
<Text
numberOfLines={2}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === NAVIGATION_TABS.SEARCH ? styles.textBold : styles.textSupporting,
styles.navigationTabBarLabel,
]}
>
{translate('common.reports')}
</Text>
{({hovered}) => (
<>
<View>
<Icon
src={Expensicons.MoneySearch}
fill={getIconFill(selectedTab === NAVIGATION_TABS.SEARCH, hovered)}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
</View>
<Text
numberOfLines={2}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === NAVIGATION_TABS.SEARCH ? styles.textBold : styles.textSupporting,
styles.navigationTabBarLabel,
]}
>
{translate('common.reports')}
</Text>
</>
)}
</PressableWithFeedback>
<PressableWithFeedback
onPress={showWorkspaces}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.workspacesTabTitle')}
style={styles.leftNavigationTabBarItem}
style={({hovered}) => [styles.leftNavigationTabBarItem, hovered && styles.navigationTabBarItemHovered]}
>
<View>
<Icon
src={Expensicons.Buildings}
fill={selectedTab === NAVIGATION_TABS.WORKSPACES ? theme.iconMenu : theme.icon}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
{!!workspacesTabIndicatorStatus && <View style={styles.navigationTabBarStatusIndicator(workspacesTabIndicatorColor)} />}
</View>
<Text
numberOfLines={preferredLocale === CONST.LOCALES.DE || preferredLocale === CONST.LOCALES.NL ? 1 : 2}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === NAVIGATION_TABS.WORKSPACES ? styles.textBold : styles.textSupporting,
styles.navigationTabBarLabel,
]}
>
{translate('common.workspacesTabTitle')}
</Text>
{({hovered}) => (
<>
<View>
<Icon
src={Expensicons.Buildings}
fill={getIconFill(selectedTab === NAVIGATION_TABS.WORKSPACES, hovered)}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
{!!workspacesTabIndicatorStatus && (
<View style={[styles.navigationTabBarStatusIndicator(workspacesTabIndicatorColor), hovered && {borderColor: theme.sidebarHover}]} />
)}
</View>
<Text
numberOfLines={preferredLocale === CONST.LOCALES.DE || preferredLocale === CONST.LOCALES.NL ? 1 : 2}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === NAVIGATION_TABS.WORKSPACES ? styles.textBold : styles.textSupporting,
styles.navigationTabBarLabel,
]}
>
{translate('common.workspacesTabTitle')}
</Text>
</>
)}
</PressableWithFeedback>
<NavigationTabBarAvatar
style={styles.leftNavigationTabBarItem}
Expand Down
62 changes: 36 additions & 26 deletions src/pages/home/sidebar/NavigationTabBarAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,45 +32,55 @@ function NavigationTabBarAvatar({onPress, isSelected = false, style}: Navigation
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const emojiStatus = currentUserPersonalDetails?.status?.emojiCode ?? '';

let children;
/**
* Renders the appropriate avatar component based on user state (delegate, emoji status, or default profile)
* with the correct active (ring) state for selection and hover effects.
*/
const renderAvatar = (active: boolean) => {
if (delegateEmail) {
return (
<AvatarWithDelegateAvatar
delegateEmail={delegateEmail}
isSelected={active}
containerStyle={styles.sidebarStatusAvatarWithEmojiContainer}
/>
);
}

if (delegateEmail) {
children = (
<AvatarWithDelegateAvatar
delegateEmail={delegateEmail}
isSelected={isSelected}
containerStyle={styles.sidebarStatusAvatarWithEmojiContainer}
/>
);
} else if (emojiStatus) {
children = (
<AvatarWithOptionalStatus
emojiStatus={emojiStatus}
isSelected={isSelected}
containerStyle={styles.sidebarStatusAvatarWithEmojiContainer}
/>
);
} else {
children = (
if (emojiStatus) {
return (
<AvatarWithOptionalStatus
emojiStatus={emojiStatus}
isSelected={active}
containerStyle={styles.sidebarStatusAvatarWithEmojiContainer}
/>
);
}

return (
<ProfileAvatarWithIndicator
isSelected={isSelected}
isSelected={active}
containerStyles={styles.tn0Half}
/>
);
}
};

return (
<PressableWithFeedback
onPress={onPress}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('sidebarScreen.buttonMySettings')}
wrapperStyle={styles.flex1}
style={style}
style={({hovered}) => [style, hovered && styles.navigationTabBarItemHovered]}
>
{children}
<Text style={[styles.textSmall, styles.textAlignCenter, isSelected ? styles.textBold : styles.textSupporting, styles.mt0Half, styles.navigationTabBarLabel]}>
{translate('initialSettingsPage.account')}
</Text>
{({hovered}) => (
<>
{renderAvatar(isSelected || hovered)}
<Text style={[styles.textSmall, styles.textAlignCenter, isSelected ? styles.textBold : styles.textSupporting, styles.mt0Half, styles.navigationTabBarLabel]}>
{translate('initialSettingsPage.account')}
</Text>
</>
)}
</PressableWithFeedback>
);
}
Expand Down
7 changes: 5 additions & 2 deletions src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ type ProfileAvatarWithIndicatorProps = {
function ProfileAvatarWithIndicator({isSelected = false, containerStyles}: ProfileAvatarWithIndicatorProps) {
const styles = useThemeStyles();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [isLoading = true] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [isLoading = true] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true});

return (
<OfflineWithFeedback
pendingAction={currentUserPersonalDetails.pendingFields?.avatar}
style={containerStyles}
>
<View style={[styles.pRelative]}>
<View style={[isSelected && styles.selectedAvatarBorder, styles.pAbsolute]} />
<View
style={[isSelected && styles.selectedAvatarBorder, styles.pAbsolute]}
testID="avatar-ring"
/>
<AvatarWithIndicator
source={currentUserPersonalDetails.avatar}
accountID={currentUserPersonalDetails.accountID}
Expand Down
8 changes: 8 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,14 @@ const styles = (theme: ThemeColors) =>
paddingHorizontal: 4,
},

/**
* Background style applied to navigation tab bar items when they are hovered.
* Do not apply for the active/selected state, those already have their own styling.
*/
navigationTabBarItemHovered: {
backgroundColor: theme.sidebarHover,
},

leftNavigationTabBarContainer: {
height: '100%',
width: variables.navigationTabBarSize,
Expand Down
Loading
Loading