diff --git a/ui/components/multichain/avatar-group/__snapshots__/avatar-group.test.tsx.snap b/ui/components/multichain/avatar-group/__snapshots__/avatar-group.test.tsx.snap new file mode 100644 index 000000000000..3ef94f51374b --- /dev/null +++ b/ui/components/multichain/avatar-group/__snapshots__/avatar-group.test.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AvatarGroup should render AvatarGroup component 1`] = ` +
+
+
+
+
+ AVAX logo +
+
+
+
+ OP logo +
+
+
+
+ MATIC logo +
+
+
+
+ ETH logo +
+
+
+
+

+ +1 +

+
+
+
+`; diff --git a/ui/components/multichain/avatar-group/avatar-group.stories.tsx b/ui/components/multichain/avatar-group/avatar-group.stories.tsx new file mode 100644 index 000000000000..e83a85f2b652 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.stories.tsx @@ -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; + +const Template = (args) => ; + +export const DefaultStory = Template.bind({}); +DefaultStory.storyName = 'Default'; + +export const WithTag: StoryFn = (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 = (args) => ( + +); +TokenWithOutSrc.args = { + members: [ + { symbol: 'ETH', image: '' }, + { symbol: 'MATIC', image: '' }, + { symbol: 'OP', image: '' }, + { symbol: 'AVAX', image: '' }, + ], + limit: 2, +}; diff --git a/ui/components/multichain/avatar-group/avatar-group.test.tsx b/ui/components/multichain/avatar-group/avatar-group.test.tsx new file mode 100644 index 000000000000..a9a891468165 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.test.tsx @@ -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( + , + ); + expect(getByTestId('avatar-group')).toBeDefined(); + expect(container).toMatchSnapshot(); + }); + + it('should render the tag +1 if members has a length greater than limit', () => { + render(); + + 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(); + expect(queryByText('+1')).not.toBeInTheDocument(); + }); +}); diff --git a/ui/components/multichain/avatar-group/avatar-group.tsx b/ui/components/multichain/avatar-group/avatar-group.tsx new file mode 100644 index 000000000000..fcfbe19c0395 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.tsx @@ -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 = ({ + 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 ( + + + {visibleMembers.map((member, i) => ( + + + + ))} + + {showTag ? ( + + + {tagValue} + + + ) : null} + + ); +}; diff --git a/ui/components/multichain/avatar-group/avatar-group.types.tsx b/ui/components/multichain/avatar-group/avatar-group.types.tsx new file mode 100644 index 000000000000..b5673692de50 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.types.tsx @@ -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; +} diff --git a/ui/components/multichain/avatar-group/index.ts b/ui/components/multichain/avatar-group/index.ts new file mode 100644 index 000000000000..59d3238f30af --- /dev/null +++ b/ui/components/multichain/avatar-group/index.ts @@ -0,0 +1 @@ +export { AvatarGroup } from './avatar-group'; diff --git a/ui/components/multichain/index.js b/ui/components/multichain/index.js index a2d8c5d7db17..2151c989addf 100644 --- a/ui/components/multichain/index.js +++ b/ui/components/multichain/index.js @@ -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';