Skip to content

Commit c04c119

Browse files
gambinishsalimtb
andauthored
feat: Token Network Filter UI [Extension] (#27884)
## **Description** Adds Token network filter controls. Note that this is not fully functional, and is currently blocked by two PRs before it can be fully integrated: 1. #27785 2. MetaMask/core#4832 In the meantime, this PR is set behind a feature flag `FILTER_TOKENS_TOGGLE` and can be run as follows: `FILTER_TOKENS_TOGGLE=1 yarn webpack --watch` Alternatively: `FILTER_TOKENS_TOGGLE=1 yarn start` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27884?quickstart=1) Included in this PR: 1. Adds new `tokenNetworkFilter` preference to PreferencesController to manage which chains should be considered when filtering tokens 2. Adds an action to update this preference by All Networks (no filters) and Current Network `{ [chainId]: true) }` this is meant to be flexible enough to support multiple chains in the future. 3. Adds `filterAssets` function in a similar style to `sortAssets` it should be configuration based, and should be extensible enough to support filtering assets by deeply nested values (NFT traits), and to also support complex filter types (like price ranges). 4. Dropdown should show the balance for the selected network Not included in this PR: 1. Aggregated balance across chains. Blocked by MetaMask/core#4832 and currently hardcoded to $1000 2. Token lists will not be filtered in this PR. Blocked by #27785 ## **Related issues** https://github.com/orgs/MetaMask/projects/85/views/35?pane=issue&itemId=82217837 https://consensyssoftware.atlassian.net/browse/MMASSETS-430 ## **Manual testing steps** Token Filter selection should persist through refresh Current chain balance should reflect the balance of the current chain Should visibly match designs: https://www.figma.com/design/aMYisczaJyEsYl1TYdcPUL/Portfolio-View?node-id=5750-47217&node-type=canvas&t=EjOUPnqy7tWZE6sV-0 ## **Screenshots/Recordings** https://github.com/user-attachments/assets/4b132e47-0dcf-4e9c-8755-ccb2be1d5dc1 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] 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. --------- Co-authored-by: Salim TOUBAL <[email protected]>
1 parent 5227d6f commit c04c119

File tree

14 files changed

+450
-33
lines changed

14 files changed

+450
-33
lines changed

app/_locales/en/messages.json

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/scripts/controllers/preferences-controller.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ describe('preferences controller', () => {
750750
order: 'dsc',
751751
sortCallback: 'stringNumeric',
752752
},
753+
tokenNetworkFilter: {},
753754
});
754755
});
755756

@@ -779,6 +780,7 @@ describe('preferences controller', () => {
779780
order: 'dsc',
780781
sortCallback: 'stringNumeric',
781782
},
783+
tokenNetworkFilter: {},
782784
});
783785
});
784786
});

app/scripts/controllers/preferences-controller.ts

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export type Preferences = {
120120
order: string;
121121
sortCallback: string;
122122
};
123+
tokenNetworkFilter: Record<string, boolean>;
123124
shouldShowAggregatedBalancePopover: boolean;
124125
};
125126

@@ -222,6 +223,7 @@ export const getDefaultPreferencesControllerState =
222223
order: 'dsc',
223224
sortCallback: 'stringNumeric',
224225
},
226+
tokenNetworkFilter: {},
225227
},
226228
// ENS decentralized website resolution
227229
ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,

builds.yml

+2
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ env:
273273
- BARAD_DUR: ''
274274
# Determines if feature flagged Chain permissions
275275
- CHAIN_PERMISSIONS: ''
276+
# Determines if feature flagged Filter toggle
277+
- FILTER_TOKENS_TOGGLE: ''
276278
# Enables use of test gas fee flow to debug gas fee estimation
277279
- TEST_GAS_FEE_FLOWS: false
278280
# Temporary mechanism to enable security alerts API prior to release

ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx

+90-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React, { useRef, useState } from 'react';
2+
import { useSelector } from 'react-redux';
3+
import { getCurrentNetwork, getPreferences } from '../../../../../selectors';
24
import {
35
Box,
46
ButtonBase,
@@ -25,62 +27,124 @@ import {
2527
ENVIRONMENT_TYPE_NOTIFICATION,
2628
ENVIRONMENT_TYPE_POPUP,
2729
} from '../../../../../../shared/constants/app';
30+
import NetworkFilter from '../network-filter';
2831

2932
type AssetListControlBarProps = {
3033
showTokensLinks?: boolean;
3134
};
3235

3336
const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => {
3437
const t = useI18nContext();
35-
const controlBarRef = useRef<HTMLDivElement>(null); // Create a ref
36-
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
38+
const popoverRef = useRef<HTMLDivElement>(null);
39+
const currentNetwork = useSelector(getCurrentNetwork);
40+
const { tokenNetworkFilter } = useSelector(getPreferences);
41+
const [isTokenSortPopoverOpen, setIsTokenSortPopoverOpen] = useState(false);
42+
const [isNetworkFilterPopoverOpen, setIsNetworkFilterPopoverOpen] =
43+
useState(false);
44+
45+
const allNetworksFilterShown = Object.keys(tokenNetworkFilter ?? {}).length;
3746

3847
const windowType = getEnvironmentType();
3948
const isFullScreen =
4049
windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
4150
windowType !== ENVIRONMENT_TYPE_POPUP;
4251

43-
const handleOpenPopover = () => {
44-
setIsPopoverOpen(!isPopoverOpen);
52+
const toggleTokenSortPopover = () => {
53+
setIsNetworkFilterPopoverOpen(false);
54+
setIsTokenSortPopoverOpen(!isTokenSortPopoverOpen);
55+
};
56+
57+
const toggleNetworkFilterPopover = () => {
58+
setIsTokenSortPopoverOpen(false);
59+
setIsNetworkFilterPopoverOpen(!isNetworkFilterPopoverOpen);
4560
};
4661

4762
const closePopover = () => {
48-
setIsPopoverOpen(false);
63+
setIsTokenSortPopoverOpen(false);
64+
setIsNetworkFilterPopoverOpen(false);
4965
};
5066

5167
return (
5268
<Box
5369
className="asset-list-control-bar"
54-
ref={controlBarRef}
55-
display={Display.Flex}
56-
justifyContent={JustifyContent.spaceBetween}
5770
marginLeft={4}
5871
marginRight={4}
5972
paddingTop={4}
73+
ref={popoverRef}
6074
>
61-
<ButtonBase
62-
data-testid="sort-by-popover-toggle"
63-
className="asset-list-control-bar__button"
64-
onClick={handleOpenPopover}
65-
size={ButtonBaseSize.Sm}
66-
endIconName={IconName.ArrowDown}
67-
backgroundColor={
68-
isPopoverOpen
69-
? BackgroundColor.backgroundPressed
70-
: BackgroundColor.backgroundDefault
75+
<Box
76+
display={Display.Flex}
77+
justifyContent={
78+
isFullScreen ? JustifyContent.flexStart : JustifyContent.spaceBetween
7179
}
72-
borderColor={BorderColor.borderMuted}
73-
borderStyle={BorderStyle.solid}
74-
color={TextColor.textDefault}
7580
>
76-
{t('sortBy')}
77-
</ButtonBase>
78-
<ImportControl showTokensLinks={showTokensLinks} />
81+
{process.env.FILTER_TOKENS_TOGGLE && (
82+
<ButtonBase
83+
data-testid="sort-by-popover-toggle"
84+
className="asset-list-control-bar__button"
85+
onClick={toggleNetworkFilterPopover}
86+
size={ButtonBaseSize.Sm}
87+
endIconName={IconName.ArrowDown}
88+
backgroundColor={
89+
isNetworkFilterPopoverOpen
90+
? BackgroundColor.backgroundPressed
91+
: BackgroundColor.backgroundDefault
92+
}
93+
borderColor={BorderColor.borderMuted}
94+
borderStyle={BorderStyle.solid}
95+
color={TextColor.textDefault}
96+
marginRight={isFullScreen ? 2 : null}
97+
ellipsis
98+
>
99+
{allNetworksFilterShown
100+
? currentNetwork?.nickname ?? t('currentNetwork')
101+
: t('allNetworks')}
102+
</ButtonBase>
103+
)}
104+
105+
<ButtonBase
106+
data-testid="sort-by-popover-toggle"
107+
className="asset-list-control-bar__button"
108+
onClick={toggleTokenSortPopover}
109+
size={ButtonBaseSize.Sm}
110+
endIconName={IconName.ArrowDown}
111+
backgroundColor={
112+
isTokenSortPopoverOpen
113+
? BackgroundColor.backgroundPressed
114+
: BackgroundColor.backgroundDefault
115+
}
116+
borderColor={BorderColor.borderMuted}
117+
borderStyle={BorderStyle.solid}
118+
color={TextColor.textDefault}
119+
marginRight={isFullScreen ? 2 : null}
120+
>
121+
{t('sortBy')}
122+
</ButtonBase>
123+
124+
<ImportControl showTokensLinks={showTokensLinks} />
125+
</Box>
126+
127+
<Popover
128+
onClickOutside={closePopover}
129+
isOpen={isNetworkFilterPopoverOpen}
130+
position={PopoverPosition.BottomStart}
131+
referenceElement={popoverRef.current}
132+
matchWidth={!isFullScreen}
133+
style={{
134+
zIndex: 10,
135+
display: 'flex',
136+
flexDirection: 'column',
137+
padding: 0,
138+
minWidth: isFullScreen ? '325px' : '',
139+
}}
140+
>
141+
<NetworkFilter handleClose={closePopover} />
142+
</Popover>
79143
<Popover
80144
onClickOutside={closePopover}
81-
isOpen={isPopoverOpen}
145+
isOpen={isTokenSortPopoverOpen}
82146
position={PopoverPosition.BottomStart}
83-
referenceElement={controlBarRef.current}
147+
referenceElement={popoverRef.current}
84148
matchWidth={!isFullScreen}
85149
style={{
86150
zIndex: 10,

ui/components/app/assets/asset-list/asset-list-control-bar/index.scss

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
padding-top: 8px;
33
padding-bottom: 8px;
44

5+
&__button {
6+
// using percentage here to allow for full network name to show when full screen, but ellipsize on extension view
7+
max-width: 35%;
8+
}
9+
510
&__button:hover {
611
background-color: var(--color-background-hover);
712
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.selectable-list-item-wrapper {
2+
position: relative;
3+
}
4+
5+
.selectable-list-item {
6+
cursor: pointer;
7+
padding: 16px;
8+
9+
&--selected {
10+
background: var(--color-primary-muted);
11+
}
12+
13+
&:not(.selectable-list-item--selected) {
14+
&:hover,
15+
&:focus-within {
16+
background: var(--color-background-default-hover);
17+
}
18+
}
19+
20+
&__selected-indicator {
21+
width: 4px;
22+
height: calc(100% - 8px);
23+
position: absolute;
24+
top: 4px;
25+
left: 4px;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './network-filter';

0 commit comments

Comments
 (0)