Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7336dd9
Revert "Merge pull request #94562 from Expensify/revert-90777-chaitan…
c3024 Jun 26, 2026
e69a89b
Convert Search footer totals via GetTransactionsConvertedAmount command
c3024 Jun 26, 2026
30536f0
Merge remote-tracking branch 'origin/main' into chaitanya/651977-get-…
c3024 Jun 26, 2026
106e243
Trim dead and over-provisioned footer code
c3024 Jun 26, 2026
6f69123
Clear footer conversion cache on leaving Search
c3024 Jun 26, 2026
f9f45e0
Convert Search footer report selections by report
c3024 Jun 26, 2026
53ed7a7
Use the report path for fully-selected Reports searches
c3024 Jun 27, 2026
181b75d
Convert grouped-search totals in the Search footer
c3024 Jun 27, 2026
fe97a3d
Convert mixed group + transaction selections in the footer
c3024 Jun 28, 2026
4ceab90
Refetch stale footer conversions after inline edits via source stamps
c3024 Jun 28, 2026
767f7e2
Merge remote-tracking branch 'origin/main' into chaitanya/651977-get-…
c3024 Jun 30, 2026
f9a6032
Merge branch 'main' into chaitanya/651977-get-transactions-converted-…
c3024 Jun 30, 2026
0ac7f0e
Merge branch 'main' into chaitanya/651977-get-transactions-converted-…
c3024 Jul 6, 2026
c74d2c8
Add onFocus/onBlur support to composed Button for footer total button
c3024 Jul 6, 2026
41c508c
Merge remote-tracking branch 'origin/main' into chaitanya/651977-get-…
c3024 Jul 14, 2026
c852c69
Merge remote-tracking branch 'origin/main' into chaitanya/651977-get-…
c3024 Jul 16, 2026
d623a98
Bucket SEARCH_FOOTER_CONVERSION for onyx export and fix spellcheck
c3024 Jul 16, 2026
3545772
Send footer conversion source stamps to the command for atomic stale …
c3024 Jul 16, 2026
b544f43
Keep the footer on the last-known/default total when offline
c3024 Jul 16, 2026
20cf18a
Drop the footer loading state when a conversion request fails
c3024 Jul 16, 2026
5f5206c
Revert source-stamp echo; keep footer staleness App-only
c3024 Jul 17, 2026
cce882d
Merge remote-tracking branch 'origin/main' into chaitanya/651977-get-…
c3024 Jul 20, 2026
0c6c12d
add line break
c3024 Jul 20, 2026
b01e7e1
Merge remote-tracking branch 'origin/main' into chaitanya/651977-get-…
c3024 Jul 21, 2026
086addd
Rename footer conversion errors field to failedCurrencies
c3024 Jul 21, 2026
488e493
Align skeleton comment with failedCurrencies naming
c3024 Jul 21, 2026
af6527e
Remove unconvertible-group-selection guard and tighten skeleton comment
c3024 Jul 21, 2026
1e48da0
line breaks before comments
c3024 Jul 21, 2026
9d36d8a
Send searchKey with footer conversion requests
c3024 Jul 21, 2026
db800fb
Merge branch 'main' into chaitanya/651977-get-transactions-converted-…
c3024 Jul 22, 2026
c44fedf
Address review feedback on footer conversion requests and footer UI
c3024 Jul 22, 2026
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
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,9 @@ const ONYXKEYS = {
/** Stores the current search page context (e.g., whether to show the search query) */
SEARCH_CONTEXT: 'searchContext',

/** Caches converted footer-total amounts (by transaction and by whole-search query, nested by currency) for the Search footer currency picker */
SEARCH_FOOTER_CONVERSION: 'searchFooterConversion',

/** Maps each loaded search snapshot's hash to its original query string, used to fan optimistic IOU updates to every matching snapshot */
SEARCH_QUERY_BY_HASH: 'searchQueryByHash',

Expand Down Expand Up @@ -1496,6 +1499,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.RECENT_SEARCHES]: Record<string, OnyxTypes.RecentSearchItem>;
[ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch;
[ONYXKEYS.SEARCH_CONTEXT]: OnyxTypes.SearchContext;
[ONYXKEYS.SEARCH_FOOTER_CONVERSION]: OnyxTypes.SearchFooterConversion;
[ONYXKEYS.SEARCH_QUERY_BY_HASH]: Record<string, string>;
[ONYXKEYS.RECENTLY_USED_CURRENCIES]: string[];
[ONYXKEYS.ACTIVE_CLIENTS]: string[];
Expand Down
12 changes: 11 additions & 1 deletion src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type IconAsset from '@src/types/utils/IconAsset';
import type WithSentryLabel from '@src/types/utils/SentryLabel';

import type {ForwardedRef} from 'react';
import type {AccessibilityState, GestureResponderEvent, LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
import type {AccessibilityState, GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TargetedEvent, TextStyle, ViewStyle} from 'react-native';

import {useIsFocused} from '@react-navigation/native';
import React, {useState} from 'react';
Expand Down Expand Up @@ -104,6 +104,12 @@ type ButtonProps = Partial<ChildrenProps> &
/** Callback that is called when mousedown is triggered. */
onMouseDown?: (e: React.MouseEvent<Element, MouseEvent>) => void;

/** A function that is called when the button receives focus */
onFocus?: (event: NativeSyntheticEvent<TargetedEvent>) => void;

/** A function that is called when the button loses focus */
onBlur?: (event: NativeSyntheticEvent<TargetedEvent>) => void;

/** Call the onPress function when Enter key is pressed */
pressOnEnter?: boolean;

Expand Down Expand Up @@ -279,6 +285,8 @@ function Button({
onPressIn = () => {},
onPressOut = () => {},
onMouseDown = undefined,
onFocus = undefined,
onBlur = undefined,

pressOnEnter = false,
enterKeyEventListenerPriority = 0,
Expand Down Expand Up @@ -516,6 +524,8 @@ function Button({
onPressIn={onPressIn}
onPressOut={onPressOut}
onMouseDown={onMouseDown}
onFocus={onFocus}
onBlur={onBlur}
shouldBlendOpacity={shouldBlendOpacity}
disabled={isLoading || isDisabled}
wrapperStyle={[
Expand Down
4 changes: 4 additions & 0 deletions src/components/ButtonComposed/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function Button({
onPressIn = () => {},
onPressOut = () => {},
onMouseDown = undefined,
onFocus = undefined,
onBlur = undefined,
style = [],
disabledStyle,
innerStyles = [],
Expand Down Expand Up @@ -170,6 +172,8 @@ function Button({
onPressIn={onPressIn}
onPressOut={onPressOut}
onMouseDown={onMouseDown}
onFocus={onFocus}
onBlur={onBlur}
onHoverIn={!isDisabled || !stayNormalOnDisable ? () => setIsHovered(true) : undefined}
onHoverOut={!isDisabled || !stayNormalOnDisable ? () => setIsHovered(false) : undefined}
onPress={(event) => {
Expand Down
8 changes: 7 additions & 1 deletion src/components/ButtonComposed/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type WithSentryLabel from '@src/types/utils/SentryLabel';

import type {ForwardedRef} from 'react';
import type React from 'react';
import type {AccessibilityState, GestureResponderEvent, LayoutChangeEvent, StyleProp, View, ViewStyle} from 'react-native';
import type {AccessibilityState, GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TargetedEvent, View, ViewStyle} from 'react-native';
import type {ValueOf} from 'type-fest';

type ButtonEventsProps = {
Expand All @@ -26,6 +26,12 @@ type ButtonEventsProps = {

/** Invoked on mount and layout changes */
onLayout?: (event: LayoutChangeEvent) => void;

/** A function that is called when the button receives focus */
onFocus?: (event: NativeSyntheticEvent<TargetedEvent>) => void;

/** A function that is called when the button loses focus */
onBlur?: (event: NativeSyntheticEvent<TargetedEvent>) => void;
};

type ButtonBehaviorProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ type ListFilterWrapperProps = {
hasHeader?: boolean;
isSearchable?: boolean;
extraHeight?: number;
shouldUseFixedPopoverHeight?: boolean;
};

function ListFilterView({children, itemCount, itemHeight, hasTitle = true, hasHeader, isSearchable, extraHeight}: ListFilterWrapperProps) {
function ListFilterView({children, itemCount, itemHeight, hasTitle = true, hasHeader, isSearchable, extraHeight, shouldUseFixedPopoverHeight}: ListFilterWrapperProps) {
const styles = useThemeStyles();
const {windowHeight} = useWindowDimensions();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth, isInLandscapeMode} = useResponsiveLayout();
const heightItemCount = shouldUseFixedPopoverHeight ? Number.MAX_SAFE_INTEGER : itemCount || 1;

return (
<View
style={[
styles.getSelectionListPopoverHeight({
itemCount: itemCount || 1,
itemCount: heightItemCount,
itemHeight,
windowHeight,
isInLandscapeMode,
Expand Down
4 changes: 4 additions & 0 deletions src/components/Search/FilterComponents/SingleSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type SingleSelectProps<T> = SearchFilterCommonProps<SingleSelectItem<T> | undefi
/** Custom height for each item in the list */
itemHeight?: number;

/** Whether the popover keeps a fixed height instead of growing with its content */
shouldUseFixedPopoverHeight?: boolean;
Comment thread
c3024 marked this conversation as resolved.
allowDeselect?: boolean;
hasTitle?: boolean;
hasHeader?: boolean;
Expand All @@ -58,6 +60,7 @@ function SingleSelectImpl({
hasTitle,
hasHeader,
itemHeight,
shouldUseFixedPopoverHeight,
footer,
allowDeselect,
onChange,
Expand Down Expand Up @@ -132,6 +135,7 @@ function SingleSelectImpl({
hasTitle={hasTitle}
isSearchable={isSearchable}
itemHeight={itemHeight ?? variables.optionRowHeightCompact}
shouldUseFixedPopoverHeight={shouldUseFixedPopoverHeight}
>
<Activity mode={shouldShowList ? 'visible' : 'hidden'}>
<SelectionList
Expand Down
63 changes: 63 additions & 0 deletions src/components/Search/FilterDropdowns/CurrencyPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {useCurrencyListActions, useCurrencyListState} from '@components/CurrencyListContextProvider';
Comment thread
c3024 marked this conversation as resolved.
import type {SingleSelectItem} from '@components/Search/FilterComponents/SingleSelect';

import {getCurrencyOptions} from '@libs/SearchUIUtils';

import React from 'react';

import SingleSelectPopup from './SingleSelectPopup';

type CurrencyPopupProps = {
/** The label to show when in an overlay on mobile */
label?: string;

/** Function to call when the back button is pressed */
onBackButtonPress?: () => void;

/** Function to call to close the overlay */
closeOverlay: () => void;

/** Function to call when a currency is selected */
onChange: (item: SingleSelectItem<string> | undefined) => void;

/** The currently selected currency code */
value?: string;

/** The currency code to select when reset is clicked */
defaultValue?: string;

/** Search input placeholder */
searchPlaceholder?: string;

/** Whether the currency list should be visible */
shouldShowList?: boolean;
};

/**
* Searchable single-select currency picker popup, used by the Search footer's total-spend currency selector.
*/
function CurrencyPopup({label, onBackButtonPress, onChange, closeOverlay, value, defaultValue, searchPlaceholder, shouldShowList}: CurrencyPopupProps) {
const {currencyList} = useCurrencyListState();
const {getCurrencySymbol} = useCurrencyListActions();

const currencyOptions = getCurrencyOptions(currencyList, getCurrencySymbol);
const currencyValue = currencyOptions.find((option) => option.value === value);

return (
<SingleSelectPopup
items={currencyOptions}
value={currencyValue}
label={label}
onBackButtonPress={onBackButtonPress}
closeOverlay={closeOverlay}
onChange={onChange}
isSearchable
searchPlaceholder={searchPlaceholder}
defaultValue={defaultValue}
shouldShowList={shouldShowList}
shouldUseFixedPopoverHeight
/>
);
}

export default CurrencyPopup;
7 changes: 5 additions & 2 deletions src/components/Search/FilterDropdowns/DisplayPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import type {OnyxEntry} from 'react-native-onyx';

import React, {useState} from 'react';

import CurrencyPopup from './CurrencyPopup';
import GroupByPopup from './GroupByPopup';
import GroupCurrencyPopup from './GroupCurrencyPopup';
import SingleSelectPopup from './SingleSelectPopup';
import SortByPopup from './SortByPopup';
import SortOrderPopup from './SortOrderPopup';
Expand Down Expand Up @@ -193,10 +193,13 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP
);
case CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY:
return (
<GroupCurrencyPopup
<CurrencyPopup
value={searchAdvancedFilters[CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY]}
onChange={(item) => updateFilterForm({groupCurrency: item?.value})}
label={translate('common.groupCurrency')}
onBackButtonPress={goBack}
closeOverlay={closeOverlay}
searchPlaceholder={translate('common.groupCurrency')}
/>
);
case CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW:
Expand Down
51 changes: 0 additions & 51 deletions src/components/Search/FilterDropdowns/GroupCurrencyPopup.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions src/components/Search/FilterDropdowns/SingleSelectPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type SingleSelectPopupProps<T> = {

/** Whether SelectionList of popup should stay mounted when popup is not visible. */
shouldShowList?: boolean;

/** Whether the popover should keep a fixed height while filtering results. */
shouldUseFixedPopoverHeight?: boolean;
};

function SingleSelectPopup<T extends string>({
Expand All @@ -68,6 +71,7 @@ function SingleSelectPopup<T extends string>({
itemHeight,
showLabel,
shouldShowList = true,
shouldUseFixedPopoverHeight,
}: SingleSelectPopupProps<T>) {
const [selectedItem, setSelectedItem] = useState(value);

Expand Down Expand Up @@ -104,6 +108,7 @@ function SingleSelectPopup<T extends string>({
selectionListStyle={selectionListStyle}
shouldShowList={shouldShowList}
itemHeight={itemHeight}
shouldUseFixedPopoverHeight={shouldUseFixedPopoverHeight}
/>
</ListFilterHeightContextProvider>
</BasePopup>
Expand Down
Loading
Loading