From a6b5f752bf64abc3a4ee609adeb1e24759a4d0e1 Mon Sep 17 00:00:00 2001 From: Mohit Date: Sat, 1 Feb 2025 18:15:09 +0530 Subject: [PATCH 01/16] reset tooltip position on scroll --- .../LHNOptionsList/LHNOptionsList.tsx | 6 +- .../LHNOptionsList/OptionRowLHN.tsx | 2 + .../BaseEducationalTooltip.tsx | 76 ++++++++++++++++--- .../measureTooltipCoordinate/index.android.ts | 6 ++ .../measureTooltipCoordinate/index.ts | 6 ++ src/components/Tooltip/types.ts | 7 +- src/hooks/useScrollEventEmitter.ts | 42 ++++++++++ src/pages/Search/SearchPageBottomTab.tsx | 0 src/pages/Search/SearchTypeMenuNarrow.tsx | 0 9 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 src/hooks/useScrollEventEmitter.ts create mode 100644 src/pages/Search/SearchPageBottomTab.tsx create mode 100644 src/pages/Search/SearchTypeMenuNarrow.tsx diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index f8292346ecf9..33b2b224d722 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -14,6 +14,7 @@ import TextBlock from '@components/TextBlock'; import useLHNEstimatedListSize from '@hooks/useLHNEstimatedListSize'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {isValidDraftComment} from '@libs/DraftCommentUtils'; @@ -66,6 +67,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio onFirstItemRendered(); }, [onFirstItemRendered]); + const triggerScrollEvent = useScrollEventEmitter({tooltipName: CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP}); + const emptyLHNSubtitle = useMemo( () => ( @@ -244,8 +247,9 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio if (isWebOrDesktop) { saveScrollIndex(route, Math.floor(e.nativeEvent.contentOffset.y / estimatedItemSize)); } + triggerScrollEvent(); }, - [estimatedItemSize, isWebOrDesktop, route, saveScrollIndex, saveScrollOffset], + [estimatedItemSize, isWebOrDesktop, route, saveScrollIndex, saveScrollOffset, triggerScrollEvent], ); const onLayout = useCallback(() => { diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 49f4414d0638..be0e9285fb22 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -198,6 +198,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti shiftVertical={shouldShowWokspaceChatTooltip ? 0 : variables.composerTooltipShiftVertical} wrapperStyle={styles.productTrainingTooltipWrapper} onTooltipPress={onOptionPress} + name={CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP} + shouldHideOnEdge={true} > diff --git a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx index 5033cf977e58..69d68d32586e 100644 --- a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx +++ b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx @@ -1,9 +1,12 @@ import {NavigationContext} from '@react-navigation/native'; -import React, {memo, useContext, useEffect, useRef, useState} from 'react'; -import type {LayoutRectangle, NativeSyntheticEvent} from 'react-native'; +import React, {memo, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState} from 'react'; +import {DeviceEventEmitter, Dimensions, type LayoutRectangle, NativeMethods, type NativeSyntheticEvent} from 'react-native'; import GenericTooltip from '@components/Tooltip/GenericTooltip'; -import type {EducationalTooltipProps} from '@components/Tooltip/types'; -import measureTooltipCoordinate from './measureTooltipCoordinate'; +import type {EducationalTooltipProps, GenericTooltipState} from '@components/Tooltip/types'; +import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import measureTooltipCoordinate, {getTooltipCoordiate} from './measureTooltipCoordinate'; type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>; @@ -11,17 +14,63 @@ type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle * A component used to wrap an element intended for displaying a tooltip. * This tooltip would show immediately without user's interaction and hide after 5 seconds. */ -function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNavigate = true, ...props}: EducationalTooltipProps) { - const hideTooltipRef = useRef<() => void>(); +function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNavigate = true, name = '', shouldHideOnEdge = false, ...props}: EducationalTooltipProps) { + const genericTooltipStateRef = useRef(); + const tooltipElRef = useRef>(); const [shouldMeasure, setShouldMeasure] = useState(false); const show = useRef<() => void>(); const navigator = useContext(NavigationContext); + const insets = useSafeAreaInsets(); + + const setTooltipPosition = useCallback( + (isScrolling: boolean, tooltipName: string) => { + if (tooltipName !== name || !genericTooltipStateRef.current || !tooltipElRef.current) return; + + const {hideTooltip, showTooltip, updateTargetBounds} = genericTooltipStateRef.current; + if (isScrolling) { + hideTooltip(); + } else { + getTooltipCoordiate(tooltipElRef.current, (bounds) => { + updateTargetBounds(bounds); + const {y, height} = bounds; + + const offset = 10; // Buffer space + const dimensions = Dimensions.get('window'); + const top = y - (insets.top || 0); + const bottom = y + height + insets.bottom || 0; + + // Calculate the available space at the top, considering the header height and offset + const availableHeightForTop = top - (variables.contentHeaderHeight - offset); + + // Calculate the total height available after accounting for the bottom tab and offset + const availableHeightForBottom = dimensions.height - (bottom + variables.bottomTabHeight - offset); + + if (availableHeightForTop < 0 || availableHeightForBottom < 0) { + hideTooltip(); + } else { + showTooltip(); + } + }); + } + }, + [insets, name], + ); + + useLayoutEffect(() => { + if (!shouldRender || !name || !shouldHideOnEdge) return; + setTooltipPosition(false, name); + const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, ({isScrolling, tooltipName} = {}) => { + setTooltipPosition(isScrolling, tooltipName); + }); + + return () => scrollingListener.remove(); + }, [shouldRender, name, shouldHideOnEdge, setTooltipPosition]); useEffect(() => { return () => { - hideTooltipRef.current?.(); + genericTooltipStateRef.current?.hideTooltip(); }; }, []); @@ -30,7 +79,7 @@ function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNav return; } if (!shouldRender) { - hideTooltipRef.current?.(); + genericTooltipStateRef.current?.hideTooltip(); return; } // When tooltip is used inside an animated view (e.g. popover), we need to wait for the animation to finish before measuring content. @@ -50,7 +99,7 @@ function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNav if (!shouldHideOnNavigate) { return; } - hideTooltipRef.current?.(); + genericTooltipStateRef.current?.hideTooltip(); }); return unsubscribe; }, [navigator, shouldHideOnNavigate]); @@ -63,9 +112,10 @@ function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNav // eslint-disable-next-line react/jsx-props-no-spreading {...props} > - {({showTooltip, hideTooltip, updateTargetBounds}) => { + {(genericTooltipState) => { // eslint-disable-next-line react-compiler/react-compiler - hideTooltipRef.current = hideTooltip; + const {updateTargetBounds, showTooltip} = genericTooltipState; + genericTooltipStateRef.current = genericTooltipState; return React.cloneElement(children as React.ReactElement, { onLayout: (e: LayoutChangeEventWithTarget) => { if (!shouldMeasure) { @@ -73,6 +123,10 @@ function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNav } // e.target is specific to native, use e.nativeEvent.target on web instead const target = e.target || e.nativeEvent.target; + tooltipElRef.current = target; + if (shouldHideOnEdge) { + return; + } show.current = () => measureTooltipCoordinate(target, updateTargetBounds, showTooltip); }, }); diff --git a/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.android.ts b/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.android.ts index 5cc2ab8a74a7..0346e75c387b 100644 --- a/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.android.ts +++ b/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.android.ts @@ -7,3 +7,9 @@ export default function measureTooltipCoordinate(target: React.Component & Reado showTooltip(); }); } + +export function getTooltipCoordiate(target: React.Component & Readonly, callback: (rect: LayoutRectangle) => void) { + return target?.measure((x, y, width, height, px, py) => { + callback({height, width, x: px, y: py}); + }); +} diff --git a/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.ts b/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.ts index 72cc75115e21..788999504f65 100644 --- a/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.ts +++ b/src/components/Tooltip/EducationalTooltip/measureTooltipCoordinate/index.ts @@ -7,3 +7,9 @@ export default function measureTooltipCoordinate(target: React.Component & Reado showTooltip(); }); } + +export function getTooltipCoordiate(target: React.Component & Readonly, callback: (rect: LayoutRectangle) => void) { + return target?.measureInWindow((x, y, width, height) => { + callback({height, width, x, y}); + }); +} diff --git a/src/components/Tooltip/types.ts b/src/components/Tooltip/types.ts index b9770b9f83c7..6fc8f49aaf00 100644 --- a/src/components/Tooltip/types.ts +++ b/src/components/Tooltip/types.ts @@ -87,6 +87,11 @@ type EducationalTooltipProps = ChildrenProps & /** Whether the tooltip should hide when navigating */ shouldHideOnNavigate?: boolean; + + /** This name can be used to distinguish between different tooltips */ + name?: string; + + shouldHideOnEdge?: boolean; }; type TooltipExtendedProps = (EducationalTooltipProps | TooltipProps) & { @@ -95,4 +100,4 @@ type TooltipExtendedProps = (EducationalTooltipProps | TooltipProps) & { }; export default TooltipProps; -export type {EducationalTooltipProps, GenericTooltipProps, SharedTooltipProps, TooltipExtendedProps}; +export type {EducationalTooltipProps, GenericTooltipProps, SharedTooltipProps, TooltipExtendedProps, GenericTooltipState}; diff --git a/src/hooks/useScrollEventEmitter.ts b/src/hooks/useScrollEventEmitter.ts new file mode 100644 index 000000000000..064181fc0518 --- /dev/null +++ b/src/hooks/useScrollEventEmitter.ts @@ -0,0 +1,42 @@ +import {useCallback, useEffect, useRef, useState} from 'react'; +import {DeviceEventEmitter} from 'react-native'; +import CONST from '@src/CONST'; + +/** + * This hook tracks scroll events and emits a "scrolling" event when scrolling starts and ends. + */ +const useScrollEventEmitter = (additinalEmitData: T) => { + const [lastScrollEvent, setLastScrollEvent] = useState(null); + const isScrollingRef = useRef(false); + + const triggerScrollEvent = useCallback(() => { + setLastScrollEvent(Date.now()); + }, []); + + useEffect(() => { + const emitScrolling = (isScrolling: boolean) => { + DeviceEventEmitter.emit(CONST.EVENTS.SCROLLING, { + isScrolling, + ...additinalEmitData, + }); + }; + + // Start emitting the scrolling event when the scroll begins + if (!isScrollingRef.current) { + emitScrolling(true); + isScrollingRef.current = true; + } + + // End the scroll and emit after a brief timeout to detect the end of scrolling + const timeout = setTimeout(() => { + emitScrolling(false); + isScrollingRef.current = false; + }, 250); + + return () => clearTimeout(timeout); + }, [lastScrollEvent]); + + return triggerScrollEvent; +}; + +export default useScrollEventEmitter; diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx new file mode 100644 index 000000000000..e69de29bb2d1 From e04e6dad42de80787f52811d2d344dccd6891df7 Mon Sep 17 00:00:00 2001 From: Mohit Date: Sun, 2 Feb 2025 21:13:47 +0530 Subject: [PATCH 02/16] Refactor --- .../BaseEducationalTooltip.tsx | 2 +- src/hooks/useScrollEventEmitter.ts | 18 +- src/pages/Search/SearchPageBottomTab.tsx | 168 ++++++++++++++++++ 3 files changed, 179 insertions(+), 9 deletions(-) diff --git a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx index 69d68d32586e..9c9ca276797e 100644 --- a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx +++ b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx @@ -113,8 +113,8 @@ function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNav {...props} > {(genericTooltipState) => { - // eslint-disable-next-line react-compiler/react-compiler const {updateTargetBounds, showTooltip} = genericTooltipState; + // eslint-disable-next-line react-compiler/react-compiler genericTooltipStateRef.current = genericTooltipState; return React.cloneElement(children as React.ReactElement, { onLayout: (e: LayoutChangeEventWithTarget) => { diff --git a/src/hooks/useScrollEventEmitter.ts b/src/hooks/useScrollEventEmitter.ts index 064181fc0518..6433dca823aa 100644 --- a/src/hooks/useScrollEventEmitter.ts +++ b/src/hooks/useScrollEventEmitter.ts @@ -6,14 +6,10 @@ import CONST from '@src/CONST'; * This hook tracks scroll events and emits a "scrolling" event when scrolling starts and ends. */ const useScrollEventEmitter = (additinalEmitData: T) => { - const [lastScrollEvent, setLastScrollEvent] = useState(null); const isScrollingRef = useRef(false); + let timeout: NodeJS.Timeout | null = null; // Timeout for detecting the end of scrolling const triggerScrollEvent = useCallback(() => { - setLastScrollEvent(Date.now()); - }, []); - - useEffect(() => { const emitScrolling = (isScrolling: boolean) => { DeviceEventEmitter.emit(CONST.EVENTS.SCROLLING, { isScrolling, @@ -28,13 +24,19 @@ const useScrollEventEmitter = (additinalEmitData: T) => { } // End the scroll and emit after a brief timeout to detect the end of scrolling - const timeout = setTimeout(() => { + if (timeout) { + clearTimeout(timeout); // Clear any existing timeout + } + + timeout = setTimeout(() => { emitScrolling(false); isScrollingRef.current = false; }, 250); + }, [additinalEmitData]); - return () => clearTimeout(timeout); - }, [lastScrollEvent]); + useEffect(() => { + triggerScrollEvent(); + }, [triggerScrollEvent]); return triggerScrollEvent; }; diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index e69de29bb2d1..ffc4486ed358 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -0,0 +1,168 @@ + +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import Animated, {clamp, runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Search from '@components/Search'; +import SearchStatusBar from '@components/Search/SearchStatusBar'; +import useActiveCentralPaneRoute from '@hooks/useActiveCentralPaneRoute'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import Navigation from '@libs/Navigation/Navigation'; +import type {AuthScreensParamList} from '@libs/Navigation/types'; +import {buildCannedSearchQuery, buildSearchQueryJSON, getPolicyIDFromSearchQuery, isCannedSearchQuery} from '@libs/SearchQueryUtils'; +import TopBar from '@navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; +import SearchSelectionModeHeader from './SearchSelectionModeHeader'; +import SearchTypeMenu from './SearchTypeMenu'; + +const TOO_CLOSE_TO_TOP_DISTANCE = 10; +const TOO_CLOSE_TO_BOTTOM_DISTANCE = 10; +const ANIMATION_DURATION_IN_MS = 300; + +function SearchPageBottomTab() { + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {windowHeight} = useWindowDimensions(); + const activeCentralPaneRoute = useActiveCentralPaneRoute(); + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); + + const scrollOffset = useSharedValue(0); + const topBarOffset = useSharedValue(StyleUtils.searchHeaderHeight); + const topBarAnimatedStyle = useAnimatedStyle(() => ({ + top: topBarOffset.get(), + })); + + const triggerScrollEvent = useScrollEventEmitter({tooltipName: CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP}); + + const scrollHandler = useAnimatedScrollHandler({ + onScroll: (event) => { + runOnJS(triggerScrollEvent)(); + const {contentOffset, layoutMeasurement, contentSize} = event; + if (windowHeight > contentSize.height) { + return; + } + const currentOffset = contentOffset.y; + const isScrollingDown = currentOffset > scrollOffset.get(); + const distanceScrolled = currentOffset - scrollOffset.get(); + if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) { + topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, StyleUtils.searchHeaderHeight)); + } else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) { + topBarOffset.set( + withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}, () => { + runOnJS(triggerScrollEvent)(); + }), + ); + } + scrollOffset.set(currentOffset); + }, + }); + + const onContentSizeChange = useCallback( + (w: number, h: number) => { + if (windowHeight <= h) { + return; + } + topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); + }, + [windowHeight, topBarOffset, StyleUtils.searchHeaderHeight], + ); + + const searchParams = activeCentralPaneRoute?.params as AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE]; + const parsedQuery = buildSearchQueryJSON(searchParams?.q); + const isSearchNameModified = searchParams?.name === searchParams?.q; + const searchName = isSearchNameModified ? undefined : searchParams?.name; + const policyIDFromSearchQuery = parsedQuery && getPolicyIDFromSearchQuery(parsedQuery); + const isActiveCentralPaneRoute = activeCentralPaneRoute?.name === SCREENS.SEARCH.CENTRAL_PANE; + const queryJSON = isActiveCentralPaneRoute ? parsedQuery : undefined; + const policyID = isActiveCentralPaneRoute ? policyIDFromSearchQuery : undefined; + + const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: buildCannedSearchQuery()})); + + if (!queryJSON) { + return ( + + + + ); + } + + const shouldDisplayCancelSearch = shouldUseNarrowLayout && !isCannedSearchQuery(queryJSON); + + return ( + + {!selectionMode?.isEnabled ? ( + <> + + + + {shouldUseNarrowLayout ? ( + + + { + topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); + }} + /> + + ) : ( + + )} + + ) : ( + + )} + {shouldUseNarrowLayout && ( + + )} + + ); +} + +SearchPageBottomTab.displayName = 'SearchPageBottomTab'; + +export default SearchPageBottomTab; From b5957f6982beb2b4acd89edad499a0823cfb6597 Mon Sep 17 00:00:00 2001 From: Mohit Date: Wed, 26 Feb 2025 18:55:03 +0530 Subject: [PATCH 03/16] Fix conflict issues --- src/pages/Search/SearchPageBottomTab.tsx | 168 ----------------------- 1 file changed, 168 deletions(-) delete mode 100644 src/pages/Search/SearchPageBottomTab.tsx diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx deleted file mode 100644 index ffc4486ed358..000000000000 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ /dev/null @@ -1,168 +0,0 @@ - -import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import Animated, {clamp, runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import ScreenWrapper from '@components/ScreenWrapper'; -import Search from '@components/Search'; -import SearchStatusBar from '@components/Search/SearchStatusBar'; -import useActiveCentralPaneRoute from '@hooks/useActiveCentralPaneRoute'; -import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import Navigation from '@libs/Navigation/Navigation'; -import type {AuthScreensParamList} from '@libs/Navigation/types'; -import {buildCannedSearchQuery, buildSearchQueryJSON, getPolicyIDFromSearchQuery, isCannedSearchQuery} from '@libs/SearchQueryUtils'; -import TopBar from '@navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; -import SearchSelectionModeHeader from './SearchSelectionModeHeader'; -import SearchTypeMenu from './SearchTypeMenu'; - -const TOO_CLOSE_TO_TOP_DISTANCE = 10; -const TOO_CLOSE_TO_BOTTOM_DISTANCE = 10; -const ANIMATION_DURATION_IN_MS = 300; - -function SearchPageBottomTab() { - const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {windowHeight} = useWindowDimensions(); - const activeCentralPaneRoute = useActiveCentralPaneRoute(); - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); - - const scrollOffset = useSharedValue(0); - const topBarOffset = useSharedValue(StyleUtils.searchHeaderHeight); - const topBarAnimatedStyle = useAnimatedStyle(() => ({ - top: topBarOffset.get(), - })); - - const triggerScrollEvent = useScrollEventEmitter({tooltipName: CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP}); - - const scrollHandler = useAnimatedScrollHandler({ - onScroll: (event) => { - runOnJS(triggerScrollEvent)(); - const {contentOffset, layoutMeasurement, contentSize} = event; - if (windowHeight > contentSize.height) { - return; - } - const currentOffset = contentOffset.y; - const isScrollingDown = currentOffset > scrollOffset.get(); - const distanceScrolled = currentOffset - scrollOffset.get(); - if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) { - topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, StyleUtils.searchHeaderHeight)); - } else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) { - topBarOffset.set( - withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}, () => { - runOnJS(triggerScrollEvent)(); - }), - ); - } - scrollOffset.set(currentOffset); - }, - }); - - const onContentSizeChange = useCallback( - (w: number, h: number) => { - if (windowHeight <= h) { - return; - } - topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); - }, - [windowHeight, topBarOffset, StyleUtils.searchHeaderHeight], - ); - - const searchParams = activeCentralPaneRoute?.params as AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE]; - const parsedQuery = buildSearchQueryJSON(searchParams?.q); - const isSearchNameModified = searchParams?.name === searchParams?.q; - const searchName = isSearchNameModified ? undefined : searchParams?.name; - const policyIDFromSearchQuery = parsedQuery && getPolicyIDFromSearchQuery(parsedQuery); - const isActiveCentralPaneRoute = activeCentralPaneRoute?.name === SCREENS.SEARCH.CENTRAL_PANE; - const queryJSON = isActiveCentralPaneRoute ? parsedQuery : undefined; - const policyID = isActiveCentralPaneRoute ? policyIDFromSearchQuery : undefined; - - const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: buildCannedSearchQuery()})); - - if (!queryJSON) { - return ( - - - - ); - } - - const shouldDisplayCancelSearch = shouldUseNarrowLayout && !isCannedSearchQuery(queryJSON); - - return ( - - {!selectionMode?.isEnabled ? ( - <> - - - - {shouldUseNarrowLayout ? ( - - - { - topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); - }} - /> - - ) : ( - - )} - - ) : ( - - )} - {shouldUseNarrowLayout && ( - - )} - - ); -} - -SearchPageBottomTab.displayName = 'SearchPageBottomTab'; - -export default SearchPageBottomTab; From 8aa3e133e24d6d4fe5a881d21bb95f44054faede Mon Sep 17 00:00:00 2001 From: Mohit Date: Wed, 26 Feb 2025 18:55:44 +0530 Subject: [PATCH 04/16] Fix conflict issues --- src/pages/Search/SearchTypeMenuNarrow.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/pages/Search/SearchTypeMenuNarrow.tsx diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 From 66a337fdbdf025e2f8f45834dde30f9d6f159edb Mon Sep 17 00:00:00 2001 From: Mohit Date: Wed, 26 Feb 2025 19:33:48 +0530 Subject: [PATCH 05/16] Fix merge conflict --- .../Search/SearchPageHeader/SearchPageHeader.tsx | 2 ++ src/pages/Search/SearchPageNarrow.tsx | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index a3b814650a23..b71d53c4fb1d 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -359,6 +359,8 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS wrapperStyle={styles.productTrainingTooltipWrapper} renderTooltipContent={renderProductTrainingTooltip} onTooltipPress={onFiltersButtonPress} + name={CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP} + shouldHideOnEdge >