1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 11:22:43 +02:00

[MMI] Added custody-labels and account-list components (#18197)

* Added custody-labels and account-list components

* Modified link

* Renamed custody labels and improved code

* Added snapshot

* Fixed snapshot

* Finished connect-custody account-list

* Fixing css prettier

* Fixed comments of the PR

* Fixed code

---------

Co-authored-by: António Regadas <apregadas@gmail.com>
This commit is contained in:
Albert Olivé 2023-04-17 17:06:41 +02:00 committed by GitHub
parent fedd6d4970
commit c3cb464229
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 719 additions and 0 deletions

View File

@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustodyLabels Component should render correctly 1`] = `
<div>
<label
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
for="address-index"
>
<p
class="box mm-text custody-label mm-text--h9 mm-text--font-weight-normal mm-text--text-transform-uppercase box--margin-top-1 box--margin-right-1 box--margin-bottom-2 box--padding-top-1 box--padding-right-2 box--padding-bottom-1 box--padding-left-2 box--flex-direction-row box--color-text-muted box--background-color-background-alternative box--rounded-sm"
>
value
</p>
</label>
</div>
`;

View File

@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text, Label } from '../../component-library';
import {
TEXT_TRANSFORM,
BackgroundColor,
TextColor,
FONT_WEIGHT,
BorderRadius,
TypographyVariant,
} from '../../../helpers/constants/design-system';
const CustodyLabels = (props) => {
const { labels, index, background, hideNetwork } = props;
const filteredLabels = hideNetwork
? labels.filter((item) => item.key !== 'network_name')
: labels;
return (
<Label
display={['flex']}
flexDirection={['row']}
htmlFor={`address-${index || 0}`}
>
{filteredLabels.map((item) => (
<Text
key={item.key}
textTransform={TEXT_TRANSFORM.UPPERCASE}
className="custody-label"
style={background ? { background } : {}}
marginTop={1}
marginRight={1}
marginBottom={2}
paddingTop={1}
paddingBottom={1}
paddingLeft={2}
paddingRight={2}
backgroundColor={BackgroundColor.backgroundAlternative}
color={TextColor.textMuted}
fontWeight={FONT_WEIGHT.NORMAL}
borderRadius={BorderRadius.SM}
variant={TypographyVariant.H9}
>
{item.value}
</Text>
))}
</Label>
);
};
CustodyLabels.propTypes = {
labels: PropTypes.array,
index: PropTypes.string,
background: PropTypes.string,
hideNetwork: PropTypes.bool,
};
export default CustodyLabels;

View File

@ -0,0 +1,9 @@
.custody-label {
z-index: 1;
letter-spacing: 0.5px;
white-space: nowrap;
max-width: 80px;
text-overflow: ellipsis;
overflow: hidden;
display: block;
}

View File

@ -0,0 +1,16 @@
import React from 'react';
import CustodyLabels from '.';
export default {
title: 'Components/Institutional/CustodyLabels',
component: CustodyLabels,
args: {
labels: [{ key: 'testKey', value: 'value' }],
index: 'index',
hideNetwork: 'true',
},
};
export const DefaultStory = (args) => <CustodyLabels {...args} />;
DefaultStory.storyName = 'CustodyLabels';

View File

@ -0,0 +1,17 @@
import React from 'react';
import { render } from '@testing-library/react';
import CustodyLabels from './custody-labels';
describe('CustodyLabels Component', () => {
it('should render correctly', () => {
const props = {
labels: [{ key: 'testKey', value: 'value' }],
index: 'index',
hideNetwork: 'true',
};
const { container } = render(<CustodyLabels {...props} />);
expect(container).toMatchSnapshot();
});
});

View File

@ -0,0 +1 @@
export { default } from './custody-labels';

View File

@ -0,0 +1,188 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustodyAccountList renders accounts 1`] = `
<div>
<div
class="box box--padding-top-4 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--flex-direction-row"
>
<div
class="box custody-account-list box--display-flex box--flex-direction-column box--width-full"
data-testid="custody-account-list"
>
<div
class="box custody-account-list__item box--display-flex box--flex-direction-row"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
data-testid="custody-account-list-item-radio-button"
>
<input
id="address-0"
name="selectedAccount"
type="checkbox"
value="0x1234567890123456789012345678901234567890"
/>
</div>
<div
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
>
<label
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default"
for="address-0"
>
<span
class="box mm-text custody-account-list__item__name mm-text--inherit box--padding-right-1 box--flex-direction-row box--color-text-default"
>
Test Account 1
</span>
</label>
<label
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
for="address-0"
>
<span
class="box mm-text custody-account-list__item mm-text--body-md box--display-flex box--flex-direction-row box--color-text-default"
>
<a
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
href="https://etherscan.io/address/0x1234567890123456789012345678901234567890"
rel="noopener noreferrer"
target="_blank"
>
<span
class="box mm-text mm-text--inherit box--flex-direction-row box--color-primary-default"
>
0x123...7890
<span
class="box mm-icon mm-icon--size-md box--margin-left-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/undefined.svg');"
/>
</span>
</a>
<div>
<div
aria-describedby="tippy-tooltip-1"
class=""
data-original-title="[copyToClipboard]"
data-tooltipped=""
style="display: inline; background-color: transparent;"
tabindex="0"
>
<button
class="custody-account-list__item__clipboard"
>
<span
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-muted"
style="mask-image: url('./images/icons/undefined.svg');"
/>
</button>
</div>
</div>
</span>
</label>
<div
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
/>
</div>
</div>
<div
class="box custody-account-list__item box--display-flex box--flex-direction-row"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
data-testid="custody-account-list-item-radio-button"
>
<input
id="address-1"
name="selectedAccount"
type="checkbox"
value="0x0987654321098765432109876543210987654321"
/>
</div>
<div
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
>
<label
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default"
for="address-1"
>
<span
class="box mm-text custody-account-list__item__name mm-text--inherit box--padding-right-1 box--flex-direction-row box--color-text-default"
>
Test Account 2
</span>
</label>
<label
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
for="address-1"
>
<span
class="box mm-text custody-account-list__item mm-text--body-md box--display-flex box--flex-direction-row box--color-text-default"
>
<a
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
href="https://etherscan.io/address/0x0987654321098765432109876543210987654321"
rel="noopener noreferrer"
target="_blank"
>
<span
class="box mm-text mm-text--inherit box--flex-direction-row box--color-primary-default"
>
0x098...4321
<span
class="box mm-icon mm-icon--size-md box--margin-left-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/undefined.svg');"
/>
</span>
</a>
<div>
<div
aria-describedby="tippy-tooltip-2"
class=""
data-original-title="[copyToClipboard]"
data-tooltipped=""
style="display: inline; background-color: transparent;"
tabindex="0"
>
<button
class="custody-account-list__item__clipboard"
>
<span
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-muted"
style="mask-image: url('./images/icons/undefined.svg');"
/>
</button>
</div>
</div>
</span>
</label>
<div
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
/>
</div>
</div>
</div>
</div>
<div
class="box custody-account-list__buttons box--padding-top-5 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--display-flex box--flex-direction-row box--justify-content-space-between box--width-full"
>
<button
class="button btn--rounded btn-default btn--large custody-account-list__button"
data-testid="custody-account-cancel-button"
role="button"
tabindex="0"
>
[cancel]
</button>
<button
class="button btn--rounded btn-primary btn--large custody-account-list__button"
data-testid="custody-account-connect-button"
disabled=""
role="button"
tabindex="0"
>
[connect]
</button>
</div>
</div>
`;

View File

@ -0,0 +1,218 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../../../../components/ui/button';
import CustodyLabels from '../../../../components/institutional/custody-labels';
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../shared/constants/swaps';
import { CHAIN_IDS } from '../../../../../shared/constants/network';
import { shortenAddress } from '../../../../helpers/utils/util';
import Tooltip from '../../../../components/ui/tooltip';
import {
TextVariant,
JustifyContent,
BLOCK_SIZES,
DISPLAY,
IconColor,
} from '../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import Box from '../../../../components/ui/box';
import {
Text,
Label,
Icon,
IconName,
IconSize,
ButtonLink,
} from '../../../../components/component-library';
import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard';
const getButtonLinkHref = (account) => {
const url = SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[CHAIN_IDS.MAINNET];
return `${url}address/${account.address}`;
};
export default function CustodyAccountList({
rawList,
accounts,
onAccountChange,
selectedAccounts,
onCancel,
onAddAccounts,
custody,
}) {
const t = useI18nContext();
const [copied, handleCopy] = useCopyToClipboard();
const tooltipText = copied ? t('copiedExclamation') : t('copyToClipboard');
const disabled = Object.keys(selectedAccounts).length === 0;
return (
<>
<Box paddingTop={4} paddingRight={7} paddingBottom={7} paddingLeft={7}>
<Box
display={DISPLAY.FLEX}
flexDirection={['column']}
width={BLOCK_SIZES.FULL}
className="custody-account-list"
data-testid="custody-account-list"
>
{accounts.map((account, idx) => (
<Box
display={DISPLAY.FLEX}
className="custody-account-list__item"
key={account.address}
>
<Box
display={DISPLAY.FLEX}
alignItems={['flex-start']}
data-testid="custody-account-list-item-radio-button"
>
{!rawList && (
<input
type="checkbox"
name="selectedAccount"
id={`address-${idx}`}
value={account.address}
onChange={(e) =>
onAccountChange({
name: account.name,
address: e.target.value,
custodianDetails: account.custodianDetails,
labels: account.labels,
chainId: account.chainId,
})
}
checked={
selectedAccounts && selectedAccounts[account.address]
}
/>
)}
</Box>
<Box
display={DISPLAY.FLEX}
flexDirection={['column']}
marginLeft={2}
width={BLOCK_SIZES.FULL}
>
<Label
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
marginTop={2}
marginLeft={2}
htmlFor={`address-${idx}`}
className="custody-account-list__item__title"
>
<Text
as="span"
variant={TextVariant.inherit}
size={TextVariant.bodySm}
paddingRight={1}
className="custody-account-list__item__name"
>
{account.name}
</Text>
</Label>
<Label
display={DISPLAY.FLEX}
size={TextVariant.bodySm}
marginTop={2}
marginRight={3}
htmlFor={`address-${idx}`}
>
<Text
as="span"
variant={TextVariant.bodyMd}
display={DISPLAY.FLEX}
className="custody-account-list__item"
>
<ButtonLink
href={getButtonLinkHref(account)}
target="_blank"
rel="noopener noreferrer"
>
{shortenAddress(account.address)}
<Icon
name={IconSize.EXPORT}
size={IconName.SM}
color={IconColor.primaryDefault}
marginLeft={1}
/>
</ButtonLink>
<Tooltip
position="bottom"
title={tooltipText}
style={{ backgroundColor: 'transparent' }}
>
<button
className="custody-account-list__item__clipboard"
onClick={() => handleCopy(account.address)}
>
<Icon
name={IconSize.COPY}
size={IconName.XS}
color={IconColor.iconMuted}
/>
</button>
</Tooltip>
</Text>
</Label>
<Box
display={DISPLAY.FLEX}
justifyContent={JustifyContent.spaceBetween}
>
{account.labels && (
<CustodyLabels
labels={account.labels}
index={idx.toString()}
hideNetwork
/>
)}
</Box>
</Box>
</Box>
))}
</Box>
</Box>
{!rawList && (
<Box
display={DISPLAY.FLEX}
width={BLOCK_SIZES.FULL}
justifyContent={JustifyContent.spaceBetween}
paddingTop={5}
paddingRight={7}
paddingBottom={7}
paddingLeft={7}
className="custody-account-list__buttons"
>
<Button
data-testid="custody-account-cancel-button"
type="default"
large
className="custody-account-list__button"
onClick={onCancel}
>
{t('cancel')}
</Button>
<Button
data-testid="custody-account-connect-button"
type="primary"
large
className="custody-account-list__button"
disabled={disabled}
onClick={() => onAddAccounts(custody)}
>
{t('connect')}
</Button>
</Box>
)}
</>
);
}
CustodyAccountList.propTypes = {
custody: PropTypes.string,
accounts: PropTypes.array.isRequired,
onAccountChange: PropTypes.func,
selectedAccounts: PropTypes.object,
onAddAccounts: PropTypes.func,
onCancel: PropTypes.func,
rawList: PropTypes.bool,
};

View File

@ -0,0 +1,34 @@
import React from 'react';
import CustodyAccountList from '.';
const testAccounts = [
{
address: '0x1234567890123456789012345678901234567890',
name: 'Test Account 1',
chainId: 1,
},
{
address: '0x0987654321098765432109876543210987654321',
name: 'Test Account 2',
chainId: 1,
},
];
export default {
title: 'Pages/Institutional/CustodyAccountList',
component: CustodyAccountList,
args: {
custody: 'Test',
accounts: testAccounts,
onAccountChange: () => undefined,
selectedAccounts: {},
onAddAccounts: () => undefined,
onCancel: () => undefined,
provider: 'Test',
rawList: false,
},
};
export const DefaultStory = (args) => <CustodyAccountList {...args} />;
DefaultStory.storyName = 'CustodyAccountList';

View File

@ -0,0 +1,109 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import CustodyAccountList from './account-list';
const testAccounts = [
{
address: '0x1234567890123456789012345678901234567890',
name: 'Test Account 1',
chainId: 1,
},
{
address: '0x0987654321098765432109876543210987654321',
name: 'Test Account 2',
chainId: 1,
},
];
describe('CustodyAccountList', () => {
const onAccountChangeMock = jest.fn();
const onCancelMock = jest.fn();
const onAddAccountsMock = jest.fn();
const selectedAccountsMock = {};
afterEach(() => {
jest.clearAllMocks();
});
it('renders accounts', () => {
const { container } = render(
<CustodyAccountList
accounts={testAccounts}
selectedAccounts={selectedAccountsMock}
onAccountChange={onAccountChangeMock}
onCancel={onCancelMock}
onAddAccounts={onAddAccountsMock}
custody="Test"
/>,
);
expect(container).toMatchSnapshot();
});
it('calls onAccountChange when an account is selected', () => {
render(
<CustodyAccountList
accounts={testAccounts}
selectedAccounts={selectedAccountsMock}
onAccountChange={onAccountChangeMock}
onCancel={onCancelMock}
onAddAccounts={onAddAccountsMock}
custody="Test"
/>,
);
const firstAccountCheckbox = screen.getAllByRole('checkbox')[0];
fireEvent.click(firstAccountCheckbox);
expect(onAccountChangeMock).toHaveBeenCalledTimes(1);
expect(onAccountChangeMock).toHaveBeenCalledWith({
name: 'Test Account 1',
address: '0x1234567890123456789012345678901234567890',
custodianDetails: undefined,
labels: undefined,
chainId: 1,
});
});
it('calls onCancel when the Cancel button is clicked', () => {
render(
<CustodyAccountList
accounts={testAccounts}
selectedAccounts={selectedAccountsMock}
onAccountChange={onAccountChangeMock}
onCancel={onCancelMock}
onAddAccounts={onAddAccountsMock}
custody="Test"
/>,
);
const cancelButton = screen.getByTestId('custody-account-cancel-button');
fireEvent.click(cancelButton);
expect(onCancelMock).toHaveBeenCalledTimes(1);
});
it('calls onAddAccounts when the Connect button is clicked', () => {
selectedAccountsMock['0x1234567890123456789012345678901234567890'] = true;
selectedAccountsMock['0x0987654321098765432109876543210987654321'] = true;
render(
<CustodyAccountList
accounts={testAccounts}
selectedAccounts={selectedAccountsMock}
onAccountChange={onAccountChangeMock}
onCancel={onCancelMock}
onAddAccounts={onAddAccountsMock}
custody="Test"
/>,
);
const addAccountsButton = screen.getByTestId(
'custody-account-connect-button',
);
fireEvent.click(addAccountsButton);
expect(onAddAccountsMock).toHaveBeenCalledTimes(1);
expect(onAddAccountsMock).toHaveBeenCalledWith('Test');
});
});

View File

@ -0,0 +1 @@
export { default } from './account-list';

View File

@ -0,0 +1,49 @@
.custody-account-list {
flex: 1;
max-height: 50vh;
overflow: auto;
&__item {
border-bottom: 1px solid #d2d8dd;
input {
margin: 12px 0 0 10px;
}
&__title {
flex-flow: row;
}
&__name {
display: block;
white-space: nowrap;
max-width: 200px;
text-overflow: ellipsis;
overflow: hidden;
}
&__clipboard {
background-color: transparent;
}
}
&__item:first-child {
border-top: 1px solid #d2d8dd;
}
&__item:last-child {
border: 0;
}
&__item:hover {
background-color: rgba(0, 0, 0, 0.03);
}
&__buttons {
border-top: 1px solid #d2d8dd;
}
&__button:not(:last-child) {
margin-right: 16px;
}
}

View File

@ -12,6 +12,9 @@
@import 'connected-accounts/index';
@import 'connected-sites/index';
@import 'create-account/index';
///: BEGIN:ONLY_INCLUDE_IN(mmi)
@import "create-account/institutional/connect-custody/index";
///: END:ONLY_INCLUDE_IN
@import 'error/index';
@import 'send/gas-display/index';
@import 'home/index';