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

[Search v1] Add SearchFilters component #40419

Merged
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
1 change: 1 addition & 0 deletions assets/images/all.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
@@ -1,4 +1,5 @@
import AddReaction from '@assets/images/add-reaction.svg';
import All from '@assets/images/all.svg';
import Android from '@assets/images/android.svg';
import Apple from '@assets/images/apple.svg';
import ArrowRightLong from '@assets/images/arrow-right-long.svg';
Expand Down Expand Up @@ -165,6 +166,7 @@ export {
ActiveRoomAvatar,
AddReaction,
AdminRoomAvatar,
All,
Android,
AnnounceRoomAvatar,
Apple,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ type TopBarOnyxProps = {
};

// eslint-disable-next-line react/no-unused-prop-types
type TopBarProps = {activeWorkspaceID?: string} & TopBarOnyxProps;
type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean} & TopBarOnyxProps;

function TopBar({policy, session}: TopBarProps) {
function TopBar({policy, session, breadcrumbLabel, shouldDisplaySearch = true}: TopBarProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
Expand All @@ -39,6 +39,9 @@ function TopBar({policy, session}: TopBarProps) {
type: CONST.BREADCRUMB_TYPE.ROOT,
};

const displaySignIn = isAnonymousUser;
const displaySearch = !isAnonymousUser && shouldDisplaySearch;

return (
<View style={styles.w100}>
<View
Expand All @@ -53,15 +56,14 @@ function TopBar({policy, session}: TopBarProps) {
breadcrumbs={[
headerBreadcrumb,
{
text: translate('common.chats'),
text: breadcrumbLabel,
},
]}
/>
</View>
</View>
{isAnonymousUser ? (
<SignInButton />
) : (
{displaySignIn && <SignInButton />}
{displaySearch && (
<Tooltip text={translate('common.search')}>
<PressableWithoutFeedback
accessibilityLabel={translate('sidebarScreen.buttonSearch')}
Expand Down
88 changes: 88 additions & 0 deletions src/pages/Search/SearchFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import {View} from 'react-native';
import MenuItem from '@components/MenuItem';
import useActiveRoute from '@hooks/useActiveRoute';
import useSingleExecution from '@hooks/useSingleExecution';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import * as Expensicons from '@src/components/Icon/Expensicons';
import CONST from '@src/CONST';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import type IconAsset from '@src/types/utils/IconAsset';
import SearchFiltersNarrow from './SearchFiltersNarrow';

type SearchMenuFilterItem = {
title: string;
icon: IconAsset;
route: Route;
};

const filterItems: SearchMenuFilterItem[] = [
{
title: 'All',
icon: Expensicons.All,
route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL),
},
// More tabs prepared for final version but in v1 we support only "All"
// {
// title: 'Sent',
// icon: Expensicons.Send,
// route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.SENT),
// },
// {
// title: 'Drafts',
// icon: Expensicons.Pencil,
// route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.DRAFTS),
// },
Comment on lines +28 to +38
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB I think we can remove this since it'll be added as part of phase 2.

];

function SearchFilters() {
const styles = useThemeStyles();
const {singleExecution} = useSingleExecution();
const activeRoute = useActiveRoute();
const {isSmallScreenWidth} = useWindowDimensions();

const currentQuery = activeRoute?.params && 'query' in activeRoute.params ? activeRoute?.params?.query : '';

if (isSmallScreenWidth) {
const activeItemLabel = String(currentQuery);

return (
<SearchFiltersNarrow
filterItems={filterItems}
activeItemLabel={activeItemLabel}
/>
);
}

return (
<View style={[styles.pb4, styles.mh3, styles.mt3]}>
{filterItems.map((item) => {
const isActive = item.title.toLowerCase() === currentQuery;
const onPress = singleExecution(() => Navigation.navigate(item.route));

return (
<MenuItem
key={item.title}
disabled={false}
interactive
title={item.title}
icon={item.icon}
wrapperStyle={styles.sectionMenuItem}
focused={isActive}
hoverAndPressStyle={styles.hoveredComponentBG}
onPress={onPress}
isPaneMenu
/>
);
})}
</View>
);
}

SearchFilters.displayName = 'SearchFilters';

export default SearchFilters;
export type {SearchMenuFilterItem};
79 changes: 79 additions & 0 deletions src/pages/Search/SearchFiltersNarrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, {useRef, useState} from 'react';
import {Animated, StyleSheet, View} from 'react-native';
import Icon from '@components/Icon';
import PopoverMenu from '@components/PopoverMenu';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Text from '@components/Text';
import useSingleExecution from '@hooks/useSingleExecution';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import * as Expensicons from '@src/components/Icon/Expensicons';
import type {SearchMenuFilterItem} from './SearchFilters';

type SearchFiltersNarrowProps = {
filterItems: SearchMenuFilterItem[];
activeItemLabel: string;
};

function SearchFiltersNarrow({filterItems, activeItemLabel}: SearchFiltersNarrowProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {singleExecution} = useSingleExecution();
const {windowHeight} = useWindowDimensions();

const [isPopoverVisible, setIsPopoverVisible] = useState(false);
const buttonRef = useRef<HTMLDivElement>(null);

const openMenu = () => setIsPopoverVisible(true);
const closeMenu = () => setIsPopoverVisible(false);

const activeItem = filterItems.find((item) => item.title.toLowerCase() === activeItemLabel);
const popoverMenuItems = filterItems.map((item) => ({
text: item.title,
onSelected: singleExecution(() => Navigation.navigate(item.route)),
icon: item.icon,
}));

return (
<>
<PressableWithFeedback
accessible
accessibilityLabel={activeItem?.title ?? ''}
style={[styles.tabSelectorButton]}
wrapperStyle={[styles.flex1]}
ref={buttonRef}
onPress={openMenu}
>
{({hovered}) => (
<Animated.View style={[styles.tabSelectorButton, StyleSheet.absoluteFill, styles.tabBackground(hovered, true, theme.border), styles.mh3]}>
<View style={[styles.flexRow]}>
<Icon
src={activeItem?.icon ?? Expensicons.All}
fill={theme.icon}
/>
<Text style={[styles.searchFiltersButtonText]}>{activeItem?.title}</Text>
<Icon
src={Expensicons.DownArrow}
fill={theme.icon}
/>
</View>
</Animated.View>
)}
</PressableWithFeedback>
<PopoverMenu
menuItems={popoverMenuItems}
isVisible={isPopoverVisible}
anchorPosition={styles.createMenuPositionSidebar(windowHeight)}
onClose={closeMenu}
onItemSelected={closeMenu}
anchorRef={buttonRef}
/>
</>
);
}

SearchFiltersNarrow.displayName = 'SearchFiltersNarrow';

export default SearchFiltersNarrow;
64 changes: 10 additions & 54 deletions src/pages/Search/SearchPageBottomTab.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,23 @@
import React from 'react';
import {View} from 'react-native';
import MenuItem from '@components/MenuItem';
import ScreenWrapper from '@components/ScreenWrapper';
import useActiveRoute from '@hooks/useActiveRoute';
import useSingleExecution from '@hooks/useSingleExecution';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as Expensicons from '@src/components/Icon/Expensicons';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type IconAsset from '@src/types/utils/IconAsset';
import useLocalize from '@hooks/useLocalize';
import TopBar from '@navigation/AppNavigator/createCustomBottomTabNavigator/TopBar';
import SearchFilters from './SearchFilters';

// import EmptySearchView from './EmptySearchView';

type SearchMenuItem = {
title: string;
icon: IconAsset;
action: () => void;
};

function SearchPageBottomTab() {
const styles = useThemeStyles();
const {singleExecution} = useSingleExecution();
const activeRoute = useActiveRoute();
const currentQuery = activeRoute?.params && 'query' in activeRoute.params ? activeRoute?.params?.query : '';

const searchMenuItems: SearchMenuItem[] = [
{
title: 'All',
icon: Expensicons.ExpensifyLogoNew,
action: singleExecution(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL))),
},
{
title: 'Sent',
icon: Expensicons.ExpensifyLogoNew,
action: singleExecution(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.SENT))),
},
{
title: 'Drafts',
icon: Expensicons.ExpensifyLogoNew,
action: singleExecution(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.DRAFTS))),
},
];
const {translate} = useLocalize();

return (
<ScreenWrapper testID={SearchPageBottomTab.displayName}>
<View style={[styles.pb4, styles.mh3, styles.mt3]}>
{searchMenuItems.map((item) => (
<MenuItem
key={item.title}
disabled={false}
interactive
title={item.title}
icon={item.icon}
onPress={item.action}
wrapperStyle={styles.sectionMenuItem}
focused={item.title.toLowerCase() === currentQuery}
hoverAndPressStyle={styles.hoveredComponentBG}
isPaneMenu
/>
))}
</View>
<TopBar
breadcrumbLabel={translate('common.search')}
shouldDisplaySearch={false}
/>
<SearchFilters />
{/* <EmptySearchView /> */}
{/* Search results list goes here */}
</ScreenWrapper>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Browser from '@libs/Browser';
import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar';
Expand All @@ -21,6 +22,8 @@ const startTimer = () => {
function BaseSidebarScreen() {
const styles = useThemeStyles();
const activeWorkspaceID = getPolicyIDFromNavigationState();
const {translate} = useLocalize();

useEffect(() => {
Performance.markStart(CONST.TIMING.SIDEBAR_LOADED);
Timing.start(CONST.TIMING.SIDEBAR_LOADED, true);
Expand All @@ -36,7 +39,10 @@ function BaseSidebarScreen() {
>
{({insets}) => (
<>
<TopBar activeWorkspaceID={activeWorkspaceID} />
<TopBar
breadcrumbLabel={translate('common.chats')}
activeWorkspaceID={activeWorkspaceID}
/>
<View style={[styles.flex1]}>
<SidebarLinksData
onLinkClick={startTimer}
Expand Down
6 changes: 6 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4874,6 +4874,12 @@ const styles = (theme: ThemeColors) =>
textLineThrough: {
textDecorationLine: 'line-through',
},

searchFiltersButtonText: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of declaring this specific style, let's reuse what we currently have, i.e. [styles.mh1, styles.textStrong]

marginLeft: 4,
marginRight: 4,
fontWeight: 'bold',
},
} satisfies Styles);

type ThemeStyles = ReturnType<typeof styles>;
Expand Down
Loading