Skip to content

Commit

Permalink
feat: Token Network Filter UI [Extension] (#27884)
Browse files Browse the repository at this point in the history
## **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]>
  • Loading branch information
gambinish and salimtb authored Oct 30, 2024
1 parent 5227d6f commit c04c119
Show file tree
Hide file tree
Showing 14 changed files with 450 additions and 33 deletions.
8 changes: 8 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/scripts/controllers/preferences-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ describe('preferences controller', () => {
order: 'dsc',
sortCallback: 'stringNumeric',
},
tokenNetworkFilter: {},
});
});

Expand Down Expand Up @@ -779,6 +780,7 @@ describe('preferences controller', () => {
order: 'dsc',
sortCallback: 'stringNumeric',
},
tokenNetworkFilter: {},
});
});
});
Expand Down
2 changes: 2 additions & 0 deletions app/scripts/controllers/preferences-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export type Preferences = {
order: string;
sortCallback: string;
};
tokenNetworkFilter: Record<string, boolean>;
shouldShowAggregatedBalancePopover: boolean;
};

Expand Down Expand Up @@ -222,6 +223,7 @@ export const getDefaultPreferencesControllerState =
order: 'dsc',
sortCallback: 'stringNumeric',
},
tokenNetworkFilter: {},
},
// ENS decentralized website resolution
ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,
Expand Down
2 changes: 2 additions & 0 deletions builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ env:
- BARAD_DUR: ''
# Determines if feature flagged Chain permissions
- CHAIN_PERMISSIONS: ''
# Determines if feature flagged Filter toggle
- FILTER_TOKENS_TOGGLE: ''
# Enables use of test gas fee flow to debug gas fee estimation
- TEST_GAS_FEE_FLOWS: false
# Temporary mechanism to enable security alerts API prior to release
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { getCurrentNetwork, getPreferences } from '../../../../../selectors';
import {
Box,
ButtonBase,
Expand All @@ -25,62 +27,124 @@ import {
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP,
} from '../../../../../../shared/constants/app';
import NetworkFilter from '../network-filter';

type AssetListControlBarProps = {
showTokensLinks?: boolean;
};

const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => {
const t = useI18nContext();
const controlBarRef = useRef<HTMLDivElement>(null); // Create a ref
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const popoverRef = useRef<HTMLDivElement>(null);
const currentNetwork = useSelector(getCurrentNetwork);
const { tokenNetworkFilter } = useSelector(getPreferences);
const [isTokenSortPopoverOpen, setIsTokenSortPopoverOpen] = useState(false);
const [isNetworkFilterPopoverOpen, setIsNetworkFilterPopoverOpen] =
useState(false);

const allNetworksFilterShown = Object.keys(tokenNetworkFilter ?? {}).length;

const windowType = getEnvironmentType();
const isFullScreen =
windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP;

const handleOpenPopover = () => {
setIsPopoverOpen(!isPopoverOpen);
const toggleTokenSortPopover = () => {
setIsNetworkFilterPopoverOpen(false);
setIsTokenSortPopoverOpen(!isTokenSortPopoverOpen);
};

const toggleNetworkFilterPopover = () => {
setIsTokenSortPopoverOpen(false);
setIsNetworkFilterPopoverOpen(!isNetworkFilterPopoverOpen);
};

const closePopover = () => {
setIsPopoverOpen(false);
setIsTokenSortPopoverOpen(false);
setIsNetworkFilterPopoverOpen(false);
};

return (
<Box
className="asset-list-control-bar"
ref={controlBarRef}
display={Display.Flex}
justifyContent={JustifyContent.spaceBetween}
marginLeft={4}
marginRight={4}
paddingTop={4}
ref={popoverRef}
>
<ButtonBase
data-testid="sort-by-popover-toggle"
className="asset-list-control-bar__button"
onClick={handleOpenPopover}
size={ButtonBaseSize.Sm}
endIconName={IconName.ArrowDown}
backgroundColor={
isPopoverOpen
? BackgroundColor.backgroundPressed
: BackgroundColor.backgroundDefault
<Box
display={Display.Flex}
justifyContent={
isFullScreen ? JustifyContent.flexStart : JustifyContent.spaceBetween
}
borderColor={BorderColor.borderMuted}
borderStyle={BorderStyle.solid}
color={TextColor.textDefault}
>
{t('sortBy')}
</ButtonBase>
<ImportControl showTokensLinks={showTokensLinks} />
{process.env.FILTER_TOKENS_TOGGLE && (
<ButtonBase
data-testid="sort-by-popover-toggle"
className="asset-list-control-bar__button"
onClick={toggleNetworkFilterPopover}
size={ButtonBaseSize.Sm}
endIconName={IconName.ArrowDown}
backgroundColor={
isNetworkFilterPopoverOpen
? BackgroundColor.backgroundPressed
: BackgroundColor.backgroundDefault
}
borderColor={BorderColor.borderMuted}
borderStyle={BorderStyle.solid}
color={TextColor.textDefault}
marginRight={isFullScreen ? 2 : null}
ellipsis
>
{allNetworksFilterShown
? currentNetwork?.nickname ?? t('currentNetwork')
: t('allNetworks')}
</ButtonBase>
)}

<ButtonBase
data-testid="sort-by-popover-toggle"
className="asset-list-control-bar__button"
onClick={toggleTokenSortPopover}
size={ButtonBaseSize.Sm}
endIconName={IconName.ArrowDown}
backgroundColor={
isTokenSortPopoverOpen
? BackgroundColor.backgroundPressed
: BackgroundColor.backgroundDefault
}
borderColor={BorderColor.borderMuted}
borderStyle={BorderStyle.solid}
color={TextColor.textDefault}
marginRight={isFullScreen ? 2 : null}
>
{t('sortBy')}
</ButtonBase>

<ImportControl showTokensLinks={showTokensLinks} />
</Box>

<Popover
onClickOutside={closePopover}
isOpen={isNetworkFilterPopoverOpen}
position={PopoverPosition.BottomStart}
referenceElement={popoverRef.current}
matchWidth={!isFullScreen}
style={{
zIndex: 10,
display: 'flex',
flexDirection: 'column',
padding: 0,
minWidth: isFullScreen ? '325px' : '',
}}
>
<NetworkFilter handleClose={closePopover} />
</Popover>
<Popover
onClickOutside={closePopover}
isOpen={isPopoverOpen}
isOpen={isTokenSortPopoverOpen}
position={PopoverPosition.BottomStart}
referenceElement={controlBarRef.current}
referenceElement={popoverRef.current}
matchWidth={!isFullScreen}
style={{
zIndex: 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
padding-top: 8px;
padding-bottom: 8px;

&__button {
// using percentage here to allow for full network name to show when full screen, but ellipsize on extension view
max-width: 35%;
}

&__button:hover {
background-color: var(--color-background-hover);
}
Expand Down
27 changes: 27 additions & 0 deletions ui/components/app/assets/asset-list/network-filter/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.selectable-list-item-wrapper {
position: relative;
}

.selectable-list-item {
cursor: pointer;
padding: 16px;

&--selected {
background: var(--color-primary-muted);
}

&:not(.selectable-list-item--selected) {
&:hover,
&:focus-within {
background: var(--color-background-default-hover);
}
}

&__selected-indicator {
width: 4px;
height: calc(100% - 8px);
position: absolute;
top: 4px;
left: 4px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './network-filter';
Loading

0 comments on commit c04c119

Please sign in to comment.