Skip to content

Commit b701393

Browse files
authored
fix: show sort direction in filter bar in trending list (#29809)
## **Description** Add sort direction indicators to trending token filter buttons to improve UX by making the current sort order immediately visible. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Adds sort icons on filter bar in trending list ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3152 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/2b7ac0c1-fe27-4ec8-bcd3-2bcd459ce306 ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk UI enhancement that only adds derived state and icon rendering for the sort button; minimal chance of regressions outside the Trending filter bar layout. > > **Overview** > Adds a sort-direction indicator to the Trending token list price-change filter. > > `FilterButton`/`FilterBar` now accept an optional `iconName`/`priceChangeIconName` and render that icon before the label. `useTokenListFilters` exposes a new `priceChangeSortDirectionIcon` (up/down) derived from `priceChangeSortDirection`, `TokenListPageLayout` wires it into the `FilterBar`, and tests cover the new icon mapping. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0c6e926. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent d55db0f commit b701393

4 files changed

Lines changed: 67 additions & 0 deletions

File tree

app/components/UI/Trending/components/FilterBar/FilterBar.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface FilterButtonProps {
1717
ellipsizeMode?: 'tail' | 'head' | 'middle' | 'clip';
1818
/** Optional Tailwind class overrides for layout in custom contexts */
1919
twClassName?: string;
20+
/** Optional icon name to show before the label (e.g., for sort direction indicators) */
21+
iconName?: IconName;
2022
}
2123

2224
export const FilterButton: React.FC<FilterButtonProps> = ({
@@ -27,6 +29,7 @@ export const FilterButton: React.FC<FilterButtonProps> = ({
2729
numberOfLines,
2830
ellipsizeMode,
2931
twClassName,
32+
iconName,
3033
}) => {
3134
const tw = useTailwind();
3235

@@ -43,6 +46,9 @@ export const FilterButton: React.FC<FilterButtonProps> = ({
4346
disabled={disabled}
4447
>
4548
<View style={tw`flex-row items-center justify-center gap-1`}>
49+
{iconName && (
50+
<Icon name={iconName} color={IconColor.Default} size={IconSize.Sm} />
51+
)}
4652
<Text
4753
style={tw`min-w-0 shrink text-[14px] font-medium text-default`}
4854
numberOfLines={numberOfLines}
@@ -64,6 +70,8 @@ export interface FilterBarProps {
6470
priceChangeButtonText: string;
6571
onPriceChangePress: () => void;
6672
isPriceChangeDisabled?: boolean;
73+
/** Optional icon name for the price change button */
74+
priceChangeIconName?: IconName;
6775

6876
networkName: string;
6977
onNetworkPress: () => void;
@@ -81,6 +89,7 @@ const FilterBar: React.FC<FilterBarProps> = ({
8189
priceChangeButtonText,
8290
onPriceChangePress,
8391
isPriceChangeDisabled = false,
92+
priceChangeIconName,
8493
networkName,
8594
onNetworkPress,
8695
extraFilters,
@@ -95,6 +104,7 @@ const FilterBar: React.FC<FilterBarProps> = ({
95104
label={priceChangeButtonText}
96105
onPress={onPriceChangePress}
97106
disabled={isPriceChangeDisabled}
107+
iconName={priceChangeIconName}
98108
/>
99109
<View style={tw`ml-2 min-w-0 shrink flex-row items-center gap-2`}>
100110
<FilterButton

app/components/UI/Trending/components/TokenListPageLayout/TokenListPageLayout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const TokenListPageLayout: React.FC<TokenListPageLayoutProps> = ({
8888
priceChangeButtonText={filters.priceChangeButtonText}
8989
onPriceChangePress={filters.handlePriceChangePress}
9090
isPriceChangeDisabled={searchResults.length === 0}
91+
priceChangeIconName={filters.priceChangeSortDirectionIcon}
9192
networkName={filters.selectedNetworkName}
9293
onNetworkPress={filters.handleAllNetworksPress}
9394
extraFilters={extraFilters}

app/components/UI/Trending/hooks/useTokenListFilters/useTokenListFilters.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TimeOption,
88
} from '../../components/TrendingTokensBottomSheet';
99
import type { CaipChainId } from '@metamask/utils';
10+
import { IconName } from '../../../../../component-library/components/Icons/Icon';
1011

1112
const mockGoBack = jest.fn();
1213
jest.mock('@react-navigation/native', () => ({
@@ -284,6 +285,50 @@ describe('useTokenListFilters', () => {
284285
});
285286
});
286287

288+
describe('priceChangeSortDirectionIcon', () => {
289+
it('returns Arrow2Down for descending sort direction', () => {
290+
const { result } = renderFilters();
291+
292+
expect(result.current.priceChangeSortDirectionIcon).toBe(
293+
IconName.Arrow2Down,
294+
);
295+
});
296+
297+
it('returns Arrow2Up for ascending sort direction', () => {
298+
const { result } = renderFilters();
299+
300+
act(() =>
301+
result.current.handlePriceChangeSelect(
302+
PriceChangeOption.Volume,
303+
SortDirection.Ascending,
304+
),
305+
);
306+
307+
expect(result.current.priceChangeSortDirectionIcon).toBe(
308+
IconName.Arrow2Up,
309+
);
310+
});
311+
312+
it('updates when sort direction changes', () => {
313+
const { result } = renderFilters();
314+
315+
expect(result.current.priceChangeSortDirectionIcon).toBe(
316+
IconName.Arrow2Down,
317+
);
318+
319+
act(() =>
320+
result.current.handlePriceChangeSelect(
321+
PriceChangeOption.PriceChange,
322+
SortDirection.Ascending,
323+
),
324+
);
325+
326+
expect(result.current.priceChangeSortDirectionIcon).toBe(
327+
IconName.Arrow2Up,
328+
);
329+
});
330+
});
331+
287332
describe('filterContext', () => {
288333
it('reflects current filter state', () => {
289334
const { result } = renderFilters();

app/components/UI/Trending/hooks/useTokenListFilters/useTokenListFilters.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import type { TrendingFilterContext } from '../../components/TrendingTokensList/TrendingTokensList';
1212
import TrendingFeedSessionManager from '../../services/TrendingFeedSessionManager';
1313
import { useNetworkName } from '../useNetworkName/useNetworkName';
14+
import { IconName } from '../../../../../component-library/components/Icons/Icon';
1415

1516
interface UseTokenListFiltersOptions {
1617
/**
@@ -49,6 +50,7 @@ export interface TokenListFilters {
4950
) => void;
5051
handlePriceChangePress: () => void;
5152
priceChangeButtonText: string;
53+
priceChangeSortDirectionIcon: IconName;
5254

5355
// Time
5456
selectedTimeOption: TimeOption;
@@ -189,6 +191,14 @@ export const useTokenListFilters = (
189191
}
190192
}, [selectedPriceChangeOption]);
191193

194+
const priceChangeSortDirectionIcon = useMemo(
195+
() =>
196+
priceChangeSortDirection === SortDirection.Ascending
197+
? IconName.Arrow2Up
198+
: IconName.Arrow2Down,
199+
[priceChangeSortDirection],
200+
);
201+
192202
const filterContext: TrendingFilterContext = useMemo(
193203
() => ({
194204
timeFilter: selectedTimeOption,
@@ -226,6 +236,7 @@ export const useTokenListFilters = (
226236
handlePriceChangeSelect,
227237
handlePriceChangePress,
228238
priceChangeButtonText,
239+
priceChangeSortDirectionIcon,
229240
selectedTimeOption,
230241
setSelectedTimeOption,
231242
refreshing,

0 commit comments

Comments
 (0)