Skip to content

Commit dcecb08

Browse files
authored
fix: hovered address list component (#37539)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** This PR introduces a new address list component that displays aggregated account addresses by chain type. EVM addresses will now be grouped together. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/37539?quickstart=1) ## **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 a hovered component when the address link is hovered over. ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MUL-1226?atlOrigin=eyJpIjoiNGFiYzMyNTBlODg1NDI1NjhjZDRlNjhkOTI1NDk5YWEiLCJwIjoiaiJ9 Fixes: ## **Manual testing steps** 1. Open the wallet 2. See the network address link 3. Hover over the link and see and address list appear 4. Click on view all to go to the full screen address page. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/642ebac2-7682-46d9-96c6-015bf37b8938 <!-- [screenshots/recordings] --> ## **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/main/.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/main/.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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds a hoverable multichain address list in the header that aggregates EVM networks with avatars, copy action, and a "View all" link, plus supporting components, i18n, styles, stories, and tests. > > - **UI/Components**: > - **MultichainHoveredAddressRowsList**: New hover popover listing aggregated addresses per network group with priority ordering, copy support, and "View all" navigation. > - **MultichainAggregatedAddressListRow**: Row showing grouped network avatars, derived group name (incl. Bitcoin SegWit), truncated address, and copy feedback. > - **MultichainAccountNetworkGroup**: New reusable avatar group for networks (CAIP→hex conversion, priority sorting, limit display). > - **App Header Integration**: > - Replaces networks link/count with hoverable address list + network avatars; removes usage of `networkAddress(es)` strings. > - **i18n**: > - Adds `multichainAddressViewAll`, `networkNameBitcoinSegwit`; removes `networkAddress`/`networkAddresses`. > - **Styles**: > - Adds hover/row styling for multichain address list and header subtitle. > - **Types/Exports**: > - Exports `CopyParams` from `multichain-address-row`. > - **Stories/Tests**: > - Comprehensive Storybook stories and unit tests for new components and behaviors (hover, copy, sorting, navigation). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 34b0287. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 85857ac commit dcecb08

17 files changed

+2835
-62
lines changed

app/_locales/en/messages.json

Lines changed: 6 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/_locales/en_GB/messages.json

Lines changed: 6 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/_locales/ga/messages.json

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { MultichainAccountNetworkGroup } from './multichain-account-network-group';
2+
export type { MultichainAccountNetworkGroupProps } from './multichain-account-network-group';
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import React, { useMemo } from 'react';
2+
import { useSelector } from 'react-redux';
3+
import { AccountGroupId } from '@metamask/account-api';
4+
import { Box } from '@metamask/design-system-react';
5+
import { AvatarGroup } from '../../multichain/avatar-group';
6+
import { AvatarType } from '../../multichain/avatar-group/avatar-group.types';
7+
import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network';
8+
import { convertCaipToHexChainId } from '../../../../shared/modules/network.utils';
9+
import { getInternalAccountListSpreadByScopesByGroupId } from '../../../selectors/multichain-accounts/account-tree';
10+
11+
export type MultichainAccountNetworkGroupProps = {
12+
/**
13+
* The account group ID to fetch networks for
14+
*/
15+
groupId?: AccountGroupId;
16+
/**
17+
* Array of specific chain IDs to display
18+
* - If provided with groupId: shows only chains that exist in both the group and this list
19+
* - If provided without groupId: shows only these specific chains
20+
*/
21+
chainIds?: string[];
22+
/**
23+
* Whether to exclude test networks (default: true)
24+
*/
25+
excludeTestNetworks?: boolean;
26+
/**
27+
* Maximum number of avatars to display before showing "+X"
28+
*/
29+
limit?: number;
30+
/**
31+
* Optional className for additional styling
32+
*/
33+
className?: string;
34+
};
35+
36+
/**
37+
* A reusable component that displays a group of network avatars.
38+
* Can fetch networks based on account group ID or accept explicit chain IDs.
39+
* Handles conversion from CAIP chain IDs to hex format for EVM chains.
40+
*
41+
* @param props - The component props
42+
* @param props.groupId - The account group ID to fetch networks for. When provided, fetches chain IDs from the account group.
43+
* @param props.chainIds - Array of specific chain IDs to display. Behavior depends on groupId:
44+
* - If provided with groupId: shows only chains that exist in both the group and this list (intersection)
45+
* - If provided without groupId: shows only these specific chains
46+
* @param props.excludeTestNetworks - Whether to exclude test networks from display. Defaults to true.
47+
* @param props.limit - Maximum number of avatars to display before showing "+X" indicator. Defaults to 4.
48+
* @param props.className - Optional CSS class name for additional styling
49+
* @returns A React component displaying network avatars in a group
50+
*/
51+
export const MultichainAccountNetworkGroup: React.FC<
52+
MultichainAccountNetworkGroupProps
53+
> = ({
54+
groupId,
55+
chainIds,
56+
excludeTestNetworks = true,
57+
limit = 4,
58+
className,
59+
}) => {
60+
// Fetch chain IDs from account group if groupId is provided
61+
const accountGroupScopes = useSelector((state) =>
62+
groupId
63+
? getInternalAccountListSpreadByScopesByGroupId(state, groupId)
64+
: [],
65+
);
66+
67+
const filteredChainIds = useMemo(() => {
68+
// If only filterChainIds is provided (no groupId), show those chains
69+
if (chainIds && !groupId) {
70+
return chainIds;
71+
}
72+
73+
// If groupId is provided
74+
if (groupId && accountGroupScopes.length > 0) {
75+
// Extract unique chain IDs from account group scopes
76+
const groupChainIds = new Set<string>();
77+
accountGroupScopes.forEach((item) => {
78+
groupChainIds.add(item.scope);
79+
});
80+
81+
// If filterChainIds is also provided, show intersection
82+
if (chainIds) {
83+
const filterSet = new Set(chainIds);
84+
return Array.from(groupChainIds).filter((chainId) =>
85+
filterSet.has(chainId),
86+
);
87+
}
88+
89+
// Otherwise, show all chains from the group
90+
return Array.from(groupChainIds);
91+
}
92+
93+
return [];
94+
}, [chainIds, groupId, accountGroupScopes]);
95+
96+
const networkData = useMemo(() => {
97+
if (excludeTestNetworks) {
98+
// TODO: Add test network filtering logic here
99+
// For now, we'll keep all networks
100+
}
101+
102+
// Define chain priority - these chains will appear first in this order
103+
const chainPriority: Record<string, number> = {
104+
// Ethereum mainnet
105+
'eip155:1': 1,
106+
'0x1': 1,
107+
// Linea mainnet
108+
'eip155:59144': 2,
109+
'0xe708': 2,
110+
// Solana mainnet
111+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 3,
112+
// Bitcoin mainnet
113+
'bip122:000000000019d6689c085ae165831e93': 4,
114+
};
115+
116+
// Sort chainIds based on priority
117+
const sortedChainIds = [...filteredChainIds].sort((a, b) => {
118+
const priorityA = chainPriority[a] || 999;
119+
const priorityB = chainPriority[b] || 999;
120+
return priorityA - priorityB;
121+
});
122+
123+
return sortedChainIds
124+
.map((chain) => {
125+
let hexChainId = chain;
126+
// Convert CAIP chain ID to hex format for EVM chains
127+
if (chain.startsWith('eip155:')) {
128+
try {
129+
hexChainId = convertCaipToHexChainId(
130+
chain as `${string}:${string}`,
131+
);
132+
} catch {
133+
// If conversion fails, fall back to using the original chain ID
134+
hexChainId = chain;
135+
}
136+
}
137+
return {
138+
avatarValue:
139+
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[
140+
hexChainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP
141+
],
142+
};
143+
})
144+
.filter((network) => network.avatarValue); // Only include networks with valid avatar images
145+
}, [filteredChainIds, excludeTestNetworks]);
146+
147+
return (
148+
<Box
149+
style={{
150+
flexShrink: 1,
151+
width: 'fit-content',
152+
}}
153+
>
154+
<AvatarGroup
155+
limit={limit}
156+
members={networkData}
157+
avatarType={AvatarType.NETWORK}
158+
className={className}
159+
/>
160+
</Box>
161+
);
162+
};

ui/components/multichain-accounts/multichain-accounts.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
@import "multichain-account-menu-items";
44
@import "account-details-row";
55
@import "add-multichain-account";
6+
@import "multichain-address-rows-hovered-list";

ui/components/multichain-accounts/multichain-address-row/multichain-address-row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { getImageForChainId } from '../../../selectors/multichain';
2727
import { convertCaipToHexChainId } from '../../../../shared/modules/network.utils';
2828
import { useI18nContext } from '../../../hooks/useI18nContext';
2929

30-
type CopyParams = {
30+
export type CopyParams = {
3131
/**
3232
* Message to display when the copy callback is executed
3333
*/
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.multichain-address-row {
2+
transition: background-color 0.3s ease-in-out;
3+
}
4+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { MultichainAggregatedAddressListRow } from './multichain-aggregated-list-row';
2+
export { MultichainHoveredAddressRowsList } from './multichain-hovered-address-rows-hovered-list';

0 commit comments

Comments
 (0)