Skip to content

Commit

Permalink
UX: Multichain: Network Menu (#18229)
Browse files Browse the repository at this point in the history
  • Loading branch information
darkwing authored Mar 31, 2023
1 parent 0bbfd38 commit a71a069
Show file tree
Hide file tree
Showing 16 changed files with 714 additions and 0 deletions.
3 changes: 3 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 ui/components/multichain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export { MultichainImportTokenLink } from './multichain-import-token-link';
export { MultichainTokenListItem } from './multichain-token-list-item';
export { AddressCopyButton } from './address-copy-button';
export { MultichainConnectedSiteMenu } from './multichain-connected-site-menu';
export { NetworkListItem } from './network-list-item';
export { NetworkListMenu } from './network-list-menu';
3 changes: 3 additions & 0 deletions ui/components/multichain/multichain-components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@
@import 'account-list-menu/index';
@import 'account-picker/index';
@import 'multichain-connected-site-menu/index';
@import 'account-list-menu/';
@import 'multichain-token-list-item/multichain-token-list-item';
@import 'network-list-item/';
@import 'network-list-menu/';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NetworkListItem renders properly 1`] = `
<div>
<div
class="box multichain-network-list-item box--padding-4 box--gap-2 box--flex-direction-row box--justify-content-space-between box--align-items-center box--width-full box--background-color-transparent box--display-flex"
>
<div
class="box mm-text mm-avatar-base mm-avatar-base--size-md mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default box--background-color-background-alternative box--rounded-full box--border-color-transparent box--border-style-solid box--border-width-1"
>
<img
alt="Polygon logo"
class="mm-avatar-network__network-image"
src="./images/matic-token.png"
/>
</div>
<div
class="box multichain-network-list-item__network-name box--flex-direction-row"
>
<button
class="box mm-text mm-button-base mm-button-base--ellipsis mm-button-link mm-button-link--size-auto mm-text--body-md mm-text--ellipsis box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default box--background-color-transparent"
>
<span
class="box mm-text mm-text--inherit mm-text--ellipsis box--flex-direction-row box--color-text-default"
>
Polygon
</span>
</button>
</div>
<button
aria-label="[deleteNetwork]"
class="box mm-button-icon mm-button-icon--size-sm multichain-network-list-item__delete box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-error-default box--background-color-transparent box--rounded-lg"
>
<span
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/trash.svg');"
/>
</button>
</div>
</div>
`;
1 change: 1 addition & 0 deletions ui/components/multichain/network-list-item/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NetworkListItem } from './network-list-item';
51 changes: 51 additions & 0 deletions ui/components/multichain/network-list-item/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.multichain-network-list-item {
position: relative;
cursor: pointer;

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

a:hover,
a:focus {
color: inherit;
}

&:hover,
&:focus,
&:focus-within {
.multichain-network-list-item__delete {
visibility: visible;
}
}

&__network-name {
width: 100%;
flex: 1;
overflow: hidden;
text-align: start;

button:hover {
opacity: 1;
}
}

&__tooltip {
display: inline;
}

&__selected-indicator {
width: 4px;
height: calc(100% - 8px);
position: absolute;
top: 4px;
left: 4px;
}

&__delete {
visibility: hidden;
}
}
108 changes: 108 additions & 0 deletions ui/components/multichain/network-list-item/network-list-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import Box from '../../ui/box/box';
import {
AlignItems,
IconColor,
BorderRadius,
Color,
Size,
JustifyContent,
TextColor,
BLOCK_SIZES,
} from '../../../helpers/constants/design-system';
import {
AvatarNetwork,
ButtonIcon,
ButtonLink,
ICON_NAMES,
} from '../../component-library';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Tooltip from '../../ui/tooltip/tooltip';

const MAXIMUM_CHARACTERS_WITHOUT_TOOLTIP = 17;

export const NetworkListItem = ({
name,
iconSrc,
selected = false,
onClick,
onDeleteClick,
}) => {
const t = useI18nContext();
return (
<Box
onClick={onClick}
padding={4}
gap={2}
backgroundColor={selected ? Color.primaryMuted : Color.transparent}
className={classnames('multichain-network-list-item', {
'multichain-network-list-item--selected': selected,
})}
alignItems={AlignItems.center}
justifyContent={JustifyContent.spaceBetween}
width={BLOCK_SIZES.FULL}
>
{selected && (
<Box
className="multichain-network-list-item__selected-indicator"
borderRadius={BorderRadius.pill}
backgroundColor={Color.primaryDefault}
/>
)}
<AvatarNetwork name={name} src={iconSrc} />
<Box className="multichain-network-list-item__network-name">
<ButtonLink onClick={onClick} color={TextColor.textDefault} ellipsis>
{name.length > MAXIMUM_CHARACTERS_WITHOUT_TOOLTIP ? (
<Tooltip
title={name}
position="bottom"
wrapperClassName="multichain-network-list-item__tooltip"
>
{name}
</Tooltip>
) : (
name
)}
</ButtonLink>
</Box>
{onDeleteClick ? (
<ButtonIcon
className="multichain-network-list-item__delete"
color={IconColor.errorDefault}
iconName={ICON_NAMES.TRASH}
ariaLabel={t('deleteNetwork')}
size={Size.SM}
onClick={(e) => {
e.stopPropagation();
onDeleteClick();
}}
/>
) : null}
</Box>
);
};

NetworkListItem.propTypes = {
/**
* The name of the network
*/
name: PropTypes.string.isRequired,
/**
* Path to the Icon image
*/
iconSrc: PropTypes.string,
/**
* Represents if the network item is selected
*/
selected: PropTypes.bool,
/**
* Executes when the item is clicked
*/
onClick: PropTypes.func.isRequired,
/**
* Executes when the delete icon is clicked
*/
onDeleteClick: PropTypes.func,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { NetworkListItem } from '.';

export default {
title: 'Components/Multichain/NetworkListItem',
component: NetworkListItem,
argTypes: {
name: {
control: 'text',
},
selected: {
control: 'boolean',
},
onClick: {
action: 'onClick',
},
onDeleteClick: {
action: 'onDeleteClick',
},
iconSrc: {
action: 'text',
},
},
args: {
name: 'Ethereum',
iconSrc: '',
selected: false,
},
};

export const DefaultStory = (args) => (
<div
style={{ width: '328px', border: '1px solid var(--color-border-muted)' }}
>
<NetworkListItem {...args} />
</div>
);

export const IconStory = (args) => (
<div
style={{ width: '328px', border: '1px solid var(--color-border-muted)' }}
>
<NetworkListItem {...args} />
</div>
);
IconStory.args = { iconSrc: './images/matic-token.png', name: 'Polygon' };

export const SelectedStory = (args) => (
<div
style={{ width: '328px', border: '1px solid var(--color-border-muted)' }}
>
<NetworkListItem {...args} />
</div>
);
SelectedStory.args = { selected: true };

export const ChaosStory = (args) => (
<div
style={{ width: '328px', border: '1px solid var(--color-border-muted)' }}
>
<NetworkListItem {...args} />
</div>
);
ChaosStory.args = {
name: 'This is a super long network name that should be ellipsized',
selected: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* eslint-disable jest/require-top-level-describe */
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import {
MATIC_TOKEN_IMAGE_URL,
POLYGON_DISPLAY_NAME,
} from '../../../../shared/constants/network';
import { NetworkListItem } from '.';

const DEFAULT_PROPS = {
name: POLYGON_DISPLAY_NAME,
iconSrc: MATIC_TOKEN_IMAGE_URL,
selected: false,
onClick: () => undefined,
onDeleteClick: () => undefined,
};

describe('NetworkListItem', () => {
it('renders properly', () => {
const { container } = render(<NetworkListItem {...DEFAULT_PROPS} />);
expect(container).toMatchSnapshot();
});

it('does not render the delete icon when no onDeleteClick is clicked', () => {
const { container } = render(
<NetworkListItem {...DEFAULT_PROPS} onDeleteClick={null} />,
);
expect(
container.querySelector('.multichain-network-list-item__delete'),
).toBeNull();
});

it('shows as selected when selected', () => {
const { container } = render(
<NetworkListItem {...DEFAULT_PROPS} selected />,
);
expect(
container.querySelector(
'.multichain-network-list-item__selected-indicator',
),
).toBeInTheDocument();
});

it('renders a tooltip when the network name is very long', () => {
const { container } = render(
<NetworkListItem
{...DEFAULT_PROPS}
name="This is a very long network name that will be truncated"
/>,
);
expect(
container.querySelector('.multichain-network-list-item__tooltip'),
).toBeInTheDocument();
});

it('executes onClick when the item is clicked', () => {
const onClick = jest.fn();
const { container } = render(
<NetworkListItem {...DEFAULT_PROPS} onClick={onClick} />,
);
fireEvent.click(container.querySelector('.multichain-network-list-item'));
expect(onClick).toHaveBeenCalled();
});

it('executes onDeleteClick when the delete button is clicked', () => {
const onDeleteClick = jest.fn();
const onClick = jest.fn();
const { container } = render(
<NetworkListItem
{...DEFAULT_PROPS}
onDeleteClick={onDeleteClick}
onClick={onClick}
/>,
);
fireEvent.click(
container.querySelector('.multichain-network-list-item__delete'),
);
expect(onDeleteClick).toHaveBeenCalledTimes(1);
expect(onClick).toHaveBeenCalledTimes(0);
});
});
1 change: 1 addition & 0 deletions ui/components/multichain/network-list-menu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NetworkListMenu } from './network-list-menu';
4 changes: 4 additions & 0 deletions ui/components/multichain/network-list-menu/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.multichain-network-list-menu {
max-height: 200px;
overflow: auto;
}
Loading

0 comments on commit a71a069

Please sign in to comment.