Skip to content

Commit

Permalink
UX Multichain: Added avatar group component (#21342)
Browse files Browse the repository at this point in the history
## **Description**
This PR is to add a avatar group component required for the [avatars in
account-list-item](MetaMask/MetaMask-planning#1425)

## **Manual testing steps**

_1. Step1:_ Go to Storybook build
_2. Step2:_ Look for AvatarGroup Component
_3. Step3:_ Check if components are in a stacked manner
_4. Step4:_ Change the limit and check extra appear in a text like +2
format


## **Screenshots/Recordings**
![Screenshot 2023-10-12 at 4 29 52
PM](https://github.com/MetaMask/metamask-extension/assets/39872794/031c01ea-8665-48bd-991d-61ba40fc2b75)


## **Related issues**

_Fixes #21323 

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've clearly explained:
  - [x] What problem this PR is solving.
  - [x] How this problem was solved.
  - [x] How reviewers can test my changes.
- [x] I’ve indicated what issue this PR is linked to: Fixes #???
- [x] I’ve included tests if applicable.
- [x] I’ve documented any added code.
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
- [x] I’ve properly set the pull request status:
  - [x] In case it's not yet "ready for review", I've set it to "draft".
- [ ] In case it's "ready for review", I've changed it from "draft" to
"non-draft".

## **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.
  • Loading branch information
NidhiKJha authored Oct 13, 2023
1 parent a3b78f0 commit 384ba3d
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`AvatarGroup should render AvatarGroup component 1`] = `
<div>
<div
class="mm-box multichain-avatar-group mm-box--display-flex mm-box--align-items-center"
data-testid="avatar-group"
>
<div
class="mm-box mm-box--display-flex"
>
<div
class="mm-box mm-box--rounded-full"
style="margin-left: 0px;"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-token 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-transparent box--border-style-solid box--border-width-1"
>
<img
alt="AVAX logo"
class="mm-avatar-token__token-image"
src="./images/avax-token.png"
/>
</div>
</div>
<div
class="mm-box mm-box--rounded-full"
style="margin-left: -8px;"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-token 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-transparent box--border-style-solid box--border-width-1"
>
<img
alt="OP logo"
class="mm-avatar-token__token-image"
src="./images/optimism.svg"
/>
</div>
</div>
<div
class="mm-box mm-box--rounded-full"
style="margin-left: -8px;"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-token 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-transparent box--border-style-solid box--border-width-1"
>
<img
alt="MATIC logo"
class="mm-avatar-token__token-image"
src="./images/matic-token.png"
/>
</div>
</div>
<div
class="mm-box mm-box--rounded-full"
style="margin-left: -8px;"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-token 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-transparent box--border-style-solid box--border-width-1"
>
<img
alt="ETH logo"
class="mm-avatar-token__token-image"
src="./images/eth_logo.png"
/>
</div>
</div>
</div>
<div
class="mm-box"
>
<p
class="mm-box mm-text mm-text--body-sm mm-box--color-text-alternative"
>
+1
</p>
</div>
</div>
</div>
`;
57 changes: 57 additions & 0 deletions ui/components/multichain/avatar-group/avatar-group.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { AvatarGroup } from '.';

export default {
title: 'Components/Multichain/AvatarGroup',
component: AvatarGroup,
argTypes: {
limit: {
control: 'number',
},
members: {
control: 'object',
},
},
args: {
members: [
{ symbol: 'ETH', image: './images/eth_logo.png' },
{ symbol: 'MATIC', image: './images/matic-token.png' },
{ symbol: 'OP', image: './images/optimism.svg' },
{ symbol: 'AVAX', image: './images/avax-token.png' },
],
limit: 4,
},
} as Meta<typeof AvatarGroup>;

const Template = (args) => <AvatarGroup {...args} />;

export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';

export const WithTag: StoryFn<typeof AvatarGroup> = (args) => (
<AvatarGroup {...args} />
);
WithTag.args = {
members: [
{ symbol: 'ETH', image: './images/eth_logo.png' },
{ symbol: 'MATIC', image: './images/matic-token.png' },
{ symbol: 'OP', image: './images/optimism.svg' },
{ symbol: 'AVAX', image: './images/avax-token.png' },
{ symbol: 'PALM', image: './images/palm.svg' },
],
limit: 2,
};

export const TokenWithOutSrc: StoryFn<typeof AvatarGroup> = (args) => (
<AvatarGroup {...args} />
);
TokenWithOutSrc.args = {
members: [
{ symbol: 'ETH', image: '' },
{ symbol: 'MATIC', image: '' },
{ symbol: 'OP', image: '' },
{ symbol: 'AVAX', image: '' },
],
limit: 2,
};
34 changes: 34 additions & 0 deletions ui/components/multichain/avatar-group/avatar-group.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable jest/require-top-level-describe */
import { render, screen } from '@testing-library/react';
import React from 'react';

import { AvatarGroup } from './avatar-group';

const members = [
{ symbol: 'ETH', image: './images/eth_logo.png' },
{ symbol: 'MATIC', image: './images/matic-token.png' },
{ symbol: 'OP', image: './images/optimism.svg' },
{ symbol: 'AVAX', image: './images/avax-token.png' },
{ symbol: 'PALM', image: './images/palm.svg' },
];

describe('AvatarGroup', () => {
it('should render AvatarGroup component', () => {
const { getByTestId, container } = render(
<AvatarGroup members={members} limit={4} />,
);
expect(getByTestId('avatar-group')).toBeDefined();
expect(container).toMatchSnapshot();
});

it('should render the tag +1 if members has a length greater than limit', () => {
render(<AvatarGroup members={members} limit={4} />);

expect(screen.getByText('+1')).toBeDefined();
});

it('should not render the tag if members has a length less than or equal to limit', () => {
const { queryByText } = render(<AvatarGroup members={members} limit={5} />);
expect(queryByText('+1')).not.toBeInTheDocument();
});
});
72 changes: 72 additions & 0 deletions ui/components/multichain/avatar-group/avatar-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as React from 'react';
import classnames from 'classnames';
import {
Text,
Box,
AvatarToken,
AvatarTokenSize,
} from '../../component-library';
import {
AlignItems,
BorderColor,
BorderRadius,
Display,
TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
import { AvatarGroupProps } from './avatar-group.types';

export const AvatarGroup: React.FC<AvatarGroupProps> = ({
className = '',
limit = 4,
members = [],
size = AvatarTokenSize.Xs,
borderColor = BorderColor.transparent,
}): JSX.Element => {
const membersCount = members.length;
const visibleMembers = members.slice(0, limit).reverse();
const showTag = membersCount > limit;
let marginLeftValue = '';
if (AvatarTokenSize.Xs) {
marginLeftValue = '-8px';
} else if (AvatarTokenSize.Sm) {
marginLeftValue = '-12px';
} else {
marginLeftValue = '-16px';
}
const tagValue = `+${(membersCount - limit).toLocaleString()}`;
return (
<Box
alignItems={AlignItems.center}
display={Display.Flex}
className={classnames('multichain-avatar-group', className)}
data-testid="avatar-group"
>
<Box display={Display.Flex}>
{visibleMembers.map((member, i) => (
<Box
borderRadius={BorderRadius.full}
key={member.symbol}
style={
i === 0 ? { marginLeft: '0' } : { marginLeft: marginLeftValue }
}
>
<AvatarToken
src={member.image}
name={member.symbol}
size={size}
borderColor={borderColor}
/>
</Box>
))}
</Box>
{showTag ? (
<Box>
<Text variant={TextVariant.bodySm} color={TextColor.textAlternative}>
{tagValue}
</Text>
</Box>
) : null}
</Box>
);
};
21 changes: 21 additions & 0 deletions ui/components/multichain/avatar-group/avatar-group.types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BorderColor } from '../../../helpers/constants/design-system';
import { AvatarTokenSize } from '../../component-library';
import type { StyleUtilityProps } from '../../component-library/box';

export interface AvatarGroupProps extends StyleUtilityProps {
/** * Additional class name for the AvatarGroup component */
className?: string;
/** * Limit to show only a certain number of tokens and extras in Text */
limit: number;
/** * List of Avatar Tokens */
members: {
/** * Image of Avatar Token */
image: string;
/** * Symbol of Avatar Token */
symbol?: string;
}[];
/** * Size of Avatar Tokens. For AvatarGroup we are considering AvatarTokenSize.Xs, AvatarTokenSize.Sm, AvatarTokenSize.Md */
size?: AvatarTokenSize;
/** * Border Color of Avatar Tokens */
borderColor?: BorderColor;
}
1 change: 1 addition & 0 deletions ui/components/multichain/avatar-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AvatarGroup } from './avatar-group';
1 change: 1 addition & 0 deletions ui/components/multichain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export { ImportTokensModal } from './import-tokens-modal';
export { SelectActionModal } from './select-action-modal';
export { SelectActionModalItem } from './select-action-modal-item';
export { AssetListConversionButton } from './asset-list-conversion-button';
export { AvatarGroup } from './avatar-group';

0 comments on commit 384ba3d

Please sign in to comment.