Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UX: Multichain: Address Copy Button #18153

Merged
merged 17 commits into from
Mar 29, 2023
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { ICON_NAMES, ButtonBase } from '../../component-library';
import {
BackgroundColor,
TextVariant,
TextColor,
Size,
BorderRadius,
AlignItems,
} from '../../../helpers/constants/design-system';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
import { shortenAddress } from '../../../helpers/utils/util';
import Tooltip from '../../ui/tooltip/tooltip';
import { useI18nContext } from '../../../hooks/useI18nContext';

export const AddressCopyButton = ({
address,
shorten = false,
wrap = false,
}) => {
const displayAddress = shorten ? shortenAddress(address) : address;
const [copied, handleCopy] = useCopyToClipboard();
const t = useI18nContext();

return (
<Tooltip position="bottom" title={copied ? t('copiedExclamation') : null}>
<ButtonBase
backgroundColor={BackgroundColor.primaryMuted}
onClick={() => handleCopy(address)}
paddingRight={4}
paddingLeft={4}
size={Size.SM}
variant={TextVariant.bodyXs}
color={TextColor.primaryDefault}
endIconName={copied ? ICON_NAMES.COPY_SUCCESS : ICON_NAMES.COPY}
className={classnames('multichain-address-copy-button', {
'multichain-address-copy-button__address--wrap': wrap,
})}
borderRadius={BorderRadius.pill}
alignItems={AlignItems.center}
data-testid="address-copy-button-text"
>
{displayAddress}
</ButtonBase>
</Tooltip>
);
};

AddressCopyButton.propTypes = {
/**
* Address to be copied
*/
address: PropTypes.string.isRequired,
/**
* Represents if the address should be shortened
*/
shorten: PropTypes.bool,
/**
* Represents if the element should wrap to multiple lines
*/
wrap: PropTypes.bool,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { AddressCopyButton } from '.';

export default {
title: 'Components/Multichain/AddressCopyButton',
component: AddressCopyButton,
argTypes: {
address: {
control: 'text',
},
shorten: {
control: 'boolean',
},
wrap: {
control: 'boolean',
},
},
args: {
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
},
};

export const DefaultStory = (args) => <AddressCopyButton {...args} />;
DefaultStory.storyName = 'Default';

export const ShortenedStory = (args) => <AddressCopyButton {...args} />;
ShortenedStory.storyName = 'Shortened';
ShortenedStory.args = { shorten: true };

export const WrappedStory = (args) => (
<div style={{ width: '200px' }}>
<AddressCopyButton {...args} />
</div>
);
WrappedStory.storyName = 'Wrapped';
WrappedStory.args = { wrap: true };
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { AddressCopyButton } from '.';

const SAMPLE_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc';

describe('AccountListItem', () => {
it('renders the full address by default', () => {
render(<AddressCopyButton address={SAMPLE_ADDRESS} />);
expect(
document.querySelector('[data-testid="address-copy-button-text"]')
.textContent,
).toStrictEqual(SAMPLE_ADDRESS);
});

it('renders a shortened address when it should', () => {
render(<AddressCopyButton address={SAMPLE_ADDRESS} shorten />);
expect(
document.querySelector('[data-testid="address-copy-button-text"]')
.textContent,
).toStrictEqual('0x0dc...e7bc');
});

it('changes icon when clicked', () => {
render(<AddressCopyButton address={SAMPLE_ADDRESS} />);
fireEvent.click(document.querySelector('button'));
expect(document.querySelector('.mm-icon').style.maskImage).toContain(
'copy-success.svg',
);
});
});
1 change: 1 addition & 0 deletions ui/components/multichain/address-copy-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AddressCopyButton } from './address-copy-button';
7 changes: 7 additions & 0 deletions ui/components/multichain/address-copy-button/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.multichain-address-copy-button {
&__address--wrap {
word-break: break-word;
min-height: 32px;
height: auto;
}
}
1 change: 1 addition & 0 deletions ui/components/multichain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { AccountListMenu } from './account-list-menu';
export { DetectedTokensBanner } from './detected-token-banner';
export { MultichainImportTokenLink } from './multichain-import-token-link';
export { MultichainTokenListItem } from './multichain-token-list-item';
export { AddressCopyButton } from './address-copy-button';
1 change: 1 addition & 0 deletions ui/components/multichain/multichain-components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* This will help improve specificity and reduce the chance of
* unintended overrides.
**/
@import 'address-copy-button/index';
@import 'account-list-item/index';
@import 'account-list-menu/index';
@import 'multichain-token-list-item/multichain-token-list-item';
44 changes: 26 additions & 18 deletions ui/components/ui/qr-code/qr-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils
import Tooltip from '../tooltip';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { Icon, ICON_NAMES, ICON_SIZES } from '../../component-library';
import { AddressCopyButton } from '../../multichain/address-copy-button';
import Box from '../box/box';

export default connect(mapStateToProps)(QrCodeView);

Expand Down Expand Up @@ -56,25 +58,31 @@ function QrCodeView(props) {
__html: qrImage.createTableTag(4),
}}
/>
<Tooltip
wrapperClassName="qr-code__address-container__tooltip-wrapper"
position="bottom"
title={copied ? t('copiedExclamation') : t('copyToClipboard')}
>
<div
className="qr-code__address-container"
onClick={() => {
handleCopy(toChecksumHexAddress(data));
}}
{process.env.MULTICHAIN ? (
<Box marginLeft={2} marginRight={2}>
<AddressCopyButton wrap address={toChecksumHexAddress(data)} />
</Box>
) : (
<Tooltip
wrapperClassName="qr-code__address-container__tooltip-wrapper"
position="bottom"
title={copied ? t('copiedExclamation') : t('copyToClipboard')}
>
<div className="qr-code__address">{toChecksumHexAddress(data)}</div>
<Icon
name={copied ? ICON_NAMES.COPY_SUCCESS : ICON_NAMES.COPY}
size={ICON_SIZES.SM}
marginInlineStart={3}
/>
</div>
</Tooltip>
<div
className="qr-code__address-container"
onClick={() => {
handleCopy(toChecksumHexAddress(data));
}}
>
<div className="qr-code__address">{toChecksumHexAddress(data)}</div>
<Icon
name={copied ? ICON_NAMES.COPY_SUCCESS : ICON_NAMES.COPY}
size={ICON_SIZES.SM}
marginInlineStart={3}
/>
</div>
</Tooltip>
)}
georgewrmarshall marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
}
Expand Down