1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +01:00

UX: Multichain: Add address to account picker, change connection to copy button (#20520)

* UX: Multichain: Add address to account picker, change connection to copy button

* Fix for very long account names
This commit is contained in:
David Walsh 2023-08-23 11:36:03 -05:00 committed by GitHub
parent 0489911cc8
commit 79d9c18cb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 216 additions and 102 deletions

View File

@ -287,6 +287,9 @@
"address": {
"message": "Address"
},
"addressCopied": {
"message": "Address copied!"
},
"advanced": {
"message": "Advanced"
},

View File

@ -237,6 +237,8 @@ env:
- BLOCKAID_FILE_CDN
# Blockaid public key for verifying signatures of data files downloaded from CDN
- BLOCKAID_PUBLIC_KEY
# Determines if feature flagged Multichain UI should be used
- MULTICHAIN: ''
###
# Meta variables

View File

@ -10,30 +10,38 @@ exports[`AccountPicker renders properly 1`] = `
class="mm-box mm-text mm-text--inherit mm-text--ellipsis mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--color-primary-inverse"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-account mm-text--body-xs mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-background-default box--border-style-solid box--border-width-1"
class="mm-box multichain-account-picker-container mm-box--display-flex mm-box--flex-direction-column"
>
<canvas
height="32"
style="display: none;"
width="32"
/>
<img
alt=""
height="16"
src="data:image/png;base64,00"
style="border-radius: 50%;"
width="16"
/>
<div
class="mm-box mm-box--display-flex mm-box--gap-1 mm-box--align-items-center"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-account mm-text--body-xs mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-background-default box--border-style-solid box--border-width-1"
>
<canvas
height="32"
style="display: none;"
width="32"
/>
<img
alt=""
height="16"
src="data:image/png;base64,00"
style="border-radius: 50%;"
width="16"
/>
</div>
<span
class="mm-box mm-text mm-text--body-md mm-text--font-weight-bold mm-text--ellipsis mm-box--color-text-default"
>
Account 1
</span>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-icon-default"
style="mask-image: url('./images/icons/arrow-down.svg');"
/>
</div>
</div>
<span
class="mm-box mm-text mm-text--body-md mm-text--font-weight-bold mm-text--ellipsis mm-box--color-text-default"
>
Account 1
</span>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-icon-default"
style="mask-image: url('./images/icons/arrow-down.svg');"
/>
</span>
</button>
</div>

View File

@ -1,7 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { toChecksumHexAddress } from '@metamask/controller-utils';
import {
Box,
Button,
AvatarAccount,
AvatarAccountVariant,
@ -14,13 +16,25 @@ import {
BackgroundColor,
BorderRadius,
Display,
FlexDirection,
FontWeight,
IconColor,
Size,
TextAlign,
TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
import { shortenAddress } from '../../../helpers/utils/util';
export const AccountPicker = ({ address, name, onClick, disabled }) => {
export const AccountPicker = ({
address,
name,
onClick,
disabled,
showAddress = false,
}) => {
const useBlockie = useSelector((state) => state.metamask.useBlockie);
const shortenedAddress = shortenAddress(toChecksumHexAddress(address));
return (
<Button
@ -37,24 +51,41 @@ export const AccountPicker = ({ address, name, onClick, disabled }) => {
}}
disabled={disabled}
>
<AvatarAccount
variant={
useBlockie
? AvatarAccountVariant.Blockies
: AvatarAccountVariant.Jazzicon
}
address={address}
size={Size.XS}
borderColor={BackgroundColor.backgroundDefault} // we currently don't have white color for border hence using backgroundDefault as the border
/>
<Text as="span" fontWeight={FontWeight.Bold} ellipsis>
{name}
</Text>
<Icon
name={IconName.ArrowDown}
color={IconColor.iconDefault}
size={Size.SM}
/>
<Box
display={Display.Flex}
className="multichain-account-picker-container"
flexDirection={FlexDirection.Column}
>
<Box display={Display.Flex} alignItems={AlignItems.center} gap={1}>
<AvatarAccount
variant={
useBlockie
? AvatarAccountVariant.Blockies
: AvatarAccountVariant.Jazzicon
}
address={address}
size={Size.XS}
borderColor={BackgroundColor.backgroundDefault} // we currently don't have white color for border hence using backgroundDefault as the border
/>
<Text as="span" fontWeight={FontWeight.Bold} ellipsis>
{name}
</Text>
<Icon
name={IconName.ArrowDown}
color={IconColor.iconDefault}
size={Size.SM}
/>
</Box>
{showAddress ? (
<Text
color={TextColor.textAlternative}
textAlign={TextAlign.Center}
variant={TextVariant.bodySm}
>
{shortenedAddress}
</Text>
) : null}
</Box>
</Button>
);
};
@ -76,4 +107,8 @@ AccountPicker.propTypes = {
* Represents if the AccountPicker should be actionable
*/
disabled: PropTypes.bool.isRequired,
/**
* Represents if the account address should display
*/
showAddress: PropTypes.bool,
};

View File

@ -21,7 +21,7 @@ export default {
},
},
args: {
address: '"0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e"',
address: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e',
name: 'Account 1',
onClick: () => undefined,
},
@ -30,6 +30,12 @@ export default {
export const DefaultStory = (args) => <AccountPicker {...args} />;
DefaultStory.storyName = 'Default';
export const WithAddressStory = (args) => <AccountPicker {...args} />;
WithAddressStory.storyName = 'With Address';
WithAddressStory.args = {
showAddress: true,
};
export const ChaosStory = (args) => (
<div
style={{ maxWidth: '300px', border: '1px solid var(--color-border-muted)' }}
@ -39,3 +45,13 @@ export const ChaosStory = (args) => (
);
ChaosStory.storyName = 'Chaos';
ChaosStory.args = { name: CHAOS_ACCOUNT.name };
export const ChaosWithAddressStory = (args) => (
<div
style={{ maxWidth: '300px', border: '1px solid var(--color-border-muted)' }}
>
<AccountPicker {...args} />
</div>
);
ChaosWithAddressStory.storyName = 'Chaos with Address';
ChaosWithAddressStory.args = { name: CHAOS_ACCOUNT.name, showAddress: true };

View File

@ -45,4 +45,9 @@ describe('AccountPicker', () => {
const { container } = render({}, { useBlockie: false });
expect(container.querySelector('svg')).toBeDefined();
});
it('should show the address in the account button for multichain', () => {
const { getByText } = render({ showAddress: true });
expect(getByText('0x0DC...E7bc')).toBeInTheDocument();
});
});

View File

@ -4,4 +4,8 @@
box-shadow: none;
background: var(--color-background-default-hover);
}
&-container {
overflow: hidden;
}
}

View File

@ -235,57 +235,65 @@ exports[`App Header should match snapshot 1`] = `
class="mm-box mm-text mm-text--inherit mm-text--ellipsis mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--color-primary-inverse"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-account mm-text--body-xs mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-background-default box--border-style-solid box--border-width-1"
class="mm-box multichain-account-picker-container mm-box--display-flex mm-box--flex-direction-column"
>
<div
class="mm-avatar-account__jazzicon"
class="mm-box mm-box--display-flex mm-box--gap-1 mm-box--align-items-center"
>
<div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 16px; height: 16px; display: inline-block; background: rgb(250, 58, 0);"
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-account mm-text--body-xs mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-background-default box--border-style-solid box--border-width-1"
>
<svg
height="16"
width="16"
x="0"
y="0"
<div
class="mm-avatar-account__jazzicon"
>
<rect
fill="#18CDF2"
height="16"
transform="translate(-0.52419675189697 -1.6521420347302493) rotate(328.9 8 8)"
width="16"
x="0"
y="0"
/>
<rect
fill="#035E56"
height="16"
transform="translate(-9.149230854416022 5.2962309358743) rotate(176.2 8 8)"
width="16"
x="0"
y="0"
/>
<rect
fill="#F26602"
height="16"
transform="translate(8.333921009111961 -7.102569861498541) rotate(468.9 8 8)"
width="16"
x="0"
y="0"
/>
</svg>
<div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 16px; height: 16px; display: inline-block; background: rgb(250, 58, 0);"
>
<svg
height="16"
width="16"
x="0"
y="0"
>
<rect
fill="#18CDF2"
height="16"
transform="translate(-0.52419675189697 -1.6521420347302493) rotate(328.9 8 8)"
width="16"
x="0"
y="0"
/>
<rect
fill="#035E56"
height="16"
transform="translate(-9.149230854416022 5.2962309358743) rotate(176.2 8 8)"
width="16"
x="0"
y="0"
/>
<rect
fill="#F26602"
height="16"
transform="translate(8.333921009111961 -7.102569861498541) rotate(468.9 8 8)"
width="16"
x="0"
y="0"
/>
</svg>
</div>
</div>
</div>
<span
class="mm-box mm-text mm-text--body-md mm-text--font-weight-bold mm-text--ellipsis mm-box--color-text-default"
>
Test Account
</span>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-icon-default"
style="mask-image: url('./images/icons/arrow-down.svg');"
/>
</div>
</div>
<span
class="mm-box mm-text mm-text--body-md mm-text--font-weight-bold mm-text--ellipsis mm-box--color-text-default"
>
Test Account
</span>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-icon-default"
style="mask-image: url('./images/icons/arrow-down.svg');"
/>
</span>
</button>
<div

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import browser from 'webextension-polyfill';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, matchPath } from 'react-router-dom';
import { toChecksumHexAddress } from '@metamask/controller-utils';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
@ -30,6 +31,7 @@ import {
IconName,
PickerNetwork,
Box,
IconSize,
} from '../../component-library';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import { getCustodianIconForAddress } from '../../../selectors/institutional/selectors';
@ -42,8 +44,8 @@ import {
getSelectedIdentity,
getShowProductTour,
getTestNetworkBackgroundColor,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
getSelectedAddress,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
getTheme,
///: END:ONLY_INCLUDE_IN
} from '../../../selectors';
@ -62,6 +64,8 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
import { getCompletedOnboarding } from '../../../ducks/metamask/metamask';
import { getSendStage, SEND_STAGES } from '../../../ducks/send';
import Tooltip from '../../ui/tooltip';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
import { MINUTE } from '../../../../shared/constants/time';
export const AppHeader = ({ location }) => {
const trackEvent = useContext(MetaMetricsContext);
@ -94,11 +98,17 @@ export const AppHeader = ({ location }) => {
const currentNetwork = useSelector(getCurrentNetwork);
const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor);
// Used for copy button
const currentAddress = useSelector(getSelectedAddress);
const checksummedCurrentAddress = toChecksumHexAddress(currentAddress);
const [copied, handleCopy] = useCopyToClipboard(MINUTE);
const popupStatus = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP;
const showStatus =
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP &&
origin &&
origin !== browser.runtime.id;
const showConnectedStatus =
process.env.MULTICHAIN ||
(getEnvironmentType() === ENVIRONMENT_TYPE_POPUP &&
origin &&
origin !== browser.runtime.id);
const showProductTour =
completedOnboarding && !onboardedInThisUISession && showProductTourPopup;
const productTourDirection = document
@ -285,6 +295,7 @@ export const AppHeader = ({ location }) => {
});
}}
disabled={disableAccountPicker}
showAddress={process.env.MULTICHAIN}
/>
) : null}
<Box
@ -293,19 +304,35 @@ export const AppHeader = ({ location }) => {
justifyContent={JustifyContent.flexEnd}
>
<Box display={Display.Flex} gap={4}>
{showStatus ? (
<Box ref={menuRef}>
<ConnectedStatusIndicator
onClick={() => {
history.push(CONNECTED_ACCOUNTS_ROUTE);
trackEvent({
event: MetaMetricsEventName.NavConnectedSitesOpened,
category: MetaMetricsEventCategory.Navigation,
});
}}
/>
</Box>
) : null}{' '}
{showConnectedStatus &&
(process.env.MULTICHAIN ? (
<Tooltip
position="left"
title={copied ? t('addressCopied') : null}
>
<ButtonIcon
onClick={() => handleCopy(checksummedCurrentAddress)}
iconName={
copied ? IconName.CopySuccess : IconName.Copy
}
size={IconSize.Sm}
data-testid="app-header-copy-button"
/>
</Tooltip>
) : (
<Box ref={menuRef}>
<ConnectedStatusIndicator
onClick={() => {
history.push(CONNECTED_ACCOUNTS_ROUTE);
trackEvent({
event:
MetaMetricsEventName.NavConnectedSitesOpened,
category: MetaMetricsEventCategory.Navigation,
});
}}
/>
</Box>
))}{' '}
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
custodianIcon && (

View File

@ -31,4 +31,10 @@ describe('App Header', () => {
const { getByTestId } = render({ send: { stage: SEND_STAGES.DRAFT } });
expect(getByTestId('account-menu-icon')).toBeEnabled();
});
it('should show the copy button for multichain', () => {
process.env.MULTICHAIN = 1;
const { getByTestId } = render({ send: { stage: SEND_STAGES.INACTIVE } });
expect(getByTestId('app-header-copy-button')).toBeEnabled();
});
});