Skip to content

Commit d26405a

Browse files
[DSRN] Added AvatarGroup (#462)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** This PR adds the `AvatarGroup` component to the DSRN <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Related issues** Fixes: #371 ## **Manual testing steps** 1. Run `yarn storybook:ios` from root 2. Go to Components > AvatarGroup 3. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** Added more sample stories to different Avatar variants https://github.com/user-attachments/assets/9620df1f-0299-42b4-b347-75482318e65e AvatarGroup stories https://github.com/user-attachments/assets/f0662a4c-0d0d-4fef-beb2-a9a423eb5127 <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [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)). Not required for external contributors. ## **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.
1 parent 735fbe9 commit d26405a

31 files changed

+1429
-58
lines changed

apps/storybook-react-native/.storybook/storybook.requires.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const getStories = () => {
4949
return {
5050
"./../../packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.stories.tsx"),
5151
"./../../packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.stories.tsx"),
52+
"./../../packages/design-system-react-native/src/components/AvatarGroup/AvatarGroup.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarGroup/AvatarGroup.stories.tsx"),
5253
"./../../packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.stories.tsx"),
5354
"./../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx"),
5455
"./../../packages/design-system-react-native/src/components/AvatarToken/AvatarToken.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarToken/AvatarToken.stories.tsx"),

packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,19 @@ export const DEFAULT_AVATARACCOUNT_PROPS: Required<
1010
shape: AvatarBaseShape.Circle,
1111
variant: AvatarAccountVariant.Jazzicon,
1212
};
13+
14+
// Sample account addresses
15+
export const SAMPLE_AVATARACCOUNT_ADDRESSES = [
16+
'0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8',
17+
'0xb9b81f6bd23B953c5257C3b5E2F0c03B07E944eB',
18+
'0x360507dfEC4Bf0c03495f91154A78C672599F308',
19+
'0x50cA820Ff810F7687E7d0aDb23A830e3ac6032C3',
20+
'0x840C9Eb73729E626673714D6E4dA8afc8Ccc90d3',
21+
'0xCA0361BE89B7d47a6233d1875F0727ddeAB23377',
22+
'0xD78CBcA88eCd65c6128511e46a518CDc6c66fC74',
23+
'0xCFc8b1d1031ef2ecce3a98d5D79ce4D75Edb06bA',
24+
'0xDe53fa2E659b6134991bB56F64B691990e5C44E7',
25+
'0x8AceA3A9748294d1B5C25a08EFE37b756AafDFdd',
26+
'0xEC5CE72f2e18B0017C88F7B12d3308119C5Cf129',
27+
'0xeC56Da21c90Af6b50E4Ba5ec252bD97e735290fc',
28+
];

packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.stories.tsx

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import type { Meta, StoryObj } from '@storybook/react-native';
22
import { View } from 'react-native';
33

44
import AvatarAccount from './AvatarAccount';
5-
import { DEFAULT_AVATARACCOUNT_PROPS } from './AvatarAccount.constants';
5+
import {
6+
DEFAULT_AVATARACCOUNT_PROPS,
7+
SAMPLE_AVATARACCOUNT_ADDRESSES,
8+
} from './AvatarAccount.constants';
69
import type { AvatarAccountProps } from './AvatarAccount.types';
710
import { AvatarSize } from '../../shared/enums';
8-
import { IconName } from '../Icon';
911
import { AvatarAccountVariant } from './AvatarAccount.types';
1012

1113
const meta: Meta<AvatarAccountProps> = {
@@ -29,28 +31,16 @@ const meta: Meta<AvatarAccountProps> = {
2931
export default meta;
3032

3133
type Story = StoryObj<AvatarAccountProps>;
32-
const sampleAccountAddresses = [
33-
'0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8',
34-
'0xb9b81f6bd23B953c5257C3b5E2F0c03B07E944eB',
35-
'0x360507dfEC4Bf0c03495f91154A78C672599F308',
36-
'0x50cA820Ff810F7687E7d0aDb23A830e3ac6032C3',
37-
'0x840C9Eb73729E626673714D6E4dA8afc8Ccc90d3',
38-
'0xCA0361BE89B7d47a6233d1875F0727ddeAB23377',
39-
'0xD78CBcA88eCd65c6128511e46a518CDc6c66fC74',
40-
'0xCFc8b1d1031ef2ecce3a98d5D79ce4D75Edb06bA',
41-
'0xDe53fa2E659b6134991bB56F64B691990e5C44E7',
42-
'0x8AceA3A9748294d1B5C25a08EFE37b756AafDFdd',
43-
'0xEC5CE72f2e18B0017C88F7B12d3308119C5Cf129',
44-
'0xeC56Da21c90Af6b50E4Ba5ec252bD97e735290fc',
45-
];
4634
export const Default: Story = {
4735
args: {
4836
size: DEFAULT_AVATARACCOUNT_PROPS.size,
4937
variant: DEFAULT_AVATARACCOUNT_PROPS.variant,
5038
twClassName: '',
5139
},
5240
render: (args) => {
53-
return <AvatarAccount {...args} address={sampleAccountAddresses[0]} />;
41+
return (
42+
<AvatarAccount {...args} address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]} />
43+
);
5444
},
5545
};
5646

@@ -62,12 +52,12 @@ export const Sizes: Story = {
6252
<AvatarAccount
6353
size={AvatarSize[sizeKey as keyof typeof AvatarSize]}
6454
variant={AvatarAccountVariant.Blockies}
65-
address={sampleAccountAddresses[0]}
55+
address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]}
6656
/>
6757
<AvatarAccount
6858
size={AvatarSize[sizeKey as keyof typeof AvatarSize]}
6959
variant={AvatarAccountVariant.Jazzicon}
70-
address={sampleAccountAddresses[0]}
60+
address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]}
7161
/>
7262
</View>
7363
))}
@@ -86,7 +76,7 @@ export const Variants: Story = {
8676
variantKey as keyof typeof AvatarAccountVariant
8777
]
8878
}
89-
address={sampleAccountAddresses[0]}
79+
address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]}
9080
/>
9181
))}
9282
</View>
@@ -96,7 +86,7 @@ export const Variants: Story = {
9686
export const SampleAddresses: Story = {
9787
render: () => (
9888
<View style={{ gap: 16 }}>
99-
{sampleAccountAddresses.map((address) => (
89+
{SAMPLE_AVATARACCOUNT_ADDRESSES.map((address) => (
10090
<View key={address} style={{ flexDirection: 'row', gap: 8 }}>
10191
<AvatarAccount
10292
variant={AvatarAccountVariant.Blockies}

packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.test.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import { render } from '@testing-library/react-native';
55
import { AvatarAccountSize } from '../../shared/enums';
66
import AvatarAccount from './AvatarAccount';
77
import { AvatarAccountVariant } from './AvatarAccount.types';
8-
import { DEFAULT_AVATARACCOUNT_PROPS } from './AvatarAccount.constants';
8+
import {
9+
DEFAULT_AVATARACCOUNT_PROPS,
10+
SAMPLE_AVATARACCOUNT_ADDRESSES,
11+
} from './AvatarAccount.constants';
912

1013
describe('AvatarAccount', () => {
1114
it('renders Jazzicon by default when no variant is provided', () => {
12-
const address = '0x12345';
15+
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
1316

1417
const { getByTestId, queryByTestId } = render(
1518
<AvatarAccount address={address} />,
@@ -20,7 +23,7 @@ describe('AvatarAccount', () => {
2023
});
2124

2225
it('renders Blockies when variant is blockies', () => {
23-
const address = '0xabcdef';
26+
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
2427

2528
const { getByTestId, queryByTestId } = render(
2629
<AvatarAccount
@@ -34,7 +37,7 @@ describe('AvatarAccount', () => {
3437
});
3538

3639
it('respects the default size and shape', () => {
37-
const address = '0xlmnop';
40+
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
3841
const { getByTestId } = render(
3942
<AvatarAccount address={address} testID="avatar-account" />,
4043
);
@@ -49,7 +52,7 @@ describe('AvatarAccount', () => {
4952
});
5053

5154
it('overrides the size if provided', () => {
52-
const address = '0x999999';
55+
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
5356
const { getByTestId } = render(
5457
<AvatarAccount
5558
address={address}
@@ -71,7 +74,7 @@ describe('AvatarAccount', () => {
7174
});
7275

7376
it('passes additional props to AvatarBase', () => {
74-
const address = '0x67890';
77+
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
7578
const customStyle = { margin: 10 };
7679
const { getByTestId } = render(
7780
<AvatarAccount

packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,14 @@ export const DEFAULT_AVATARFAVICON_PROPS: Required<
1010
width: '100%',
1111
height: '100%',
1212
};
13+
14+
// Sample Favicon URIs
15+
export const SAMPLE_AVATARFAVICON_URIS = [
16+
'https://metamask.github.io/test-dapp/metamask-fox.svg',
17+
'https://www.coinbase.com/favicon.ico',
18+
'https://www.myetherwallet.com/favicon.ico',
19+
'https://www.blockchain.com/static/favicon.ico',
20+
'https://trezor.io/favicon.ico',
21+
'https://electrum.org/favicon.ico',
22+
'https://cryptologos.cc/logos/ethereum-eth-logo.svg',
23+
];

packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.stories.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-native';
22
import { ImageSourcePropType, View } from 'react-native';
33

44
import AvatarFavicon from './AvatarFavicon';
5-
import { DEFAULT_AVATARFAVICON_PROPS } from './AvatarFavicon.constants';
5+
import {
6+
DEFAULT_AVATARFAVICON_PROPS,
7+
SAMPLE_AVATARFAVICON_URIS,
8+
} from './AvatarFavicon.constants';
69
import type { AvatarFaviconProps } from './AvatarFavicon.types';
710
import { AvatarSize } from '../../shared/enums';
811

@@ -24,7 +27,7 @@ export default meta;
2427

2528
type Story = StoryObj<AvatarFaviconProps>;
2629
const storyImageSource: ImageSourcePropType = {
27-
uri: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
30+
uri: SAMPLE_AVATARFAVICON_URIS[0],
2831
};
2932

3033
export const Default: Story = {
@@ -48,3 +51,18 @@ export const Sizes: Story = {
4851
</View>
4952
),
5053
};
54+
55+
export const SampleFavicons: Story = {
56+
render: () => (
57+
<View style={{ gap: 16 }}>
58+
{SAMPLE_AVATARFAVICON_URIS.map((faviconUri) => (
59+
<AvatarFavicon
60+
src={{
61+
uri: faviconUri,
62+
}}
63+
key={faviconUri}
64+
/>
65+
))}
66+
</View>
67+
),
68+
};

packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('AvatarFavicon Component', () => {
5353

5454
const avatarBase = getByTestId('avatar-base');
5555

56-
expect(avatarBase.props.children.props.children).toStrictEqual(fallback);
56+
expect(avatarBase.props.children[1].props.children).toStrictEqual(fallback);
5757
});
5858

5959
it('updates fallback text on svg error when fallbackText is provided', () => {
@@ -76,7 +76,7 @@ describe('AvatarFavicon Component', () => {
7676
expect(onSvgErrorMock).toHaveBeenCalledTimes(1);
7777
expect(onSvgErrorMock).toHaveBeenCalledWith(errorEvent);
7878
const avatarBase = getByTestId('avatar-base');
79-
expect(avatarBase.props.children.props.children).toStrictEqual(fallback);
79+
expect(avatarBase.props.children[1].props.children).toStrictEqual(fallback);
8080
});
8181

8282
it('computes backupFallbackText from name when fallbackText is not provided', () => {
@@ -96,7 +96,7 @@ describe('AvatarFavicon Component', () => {
9696
fireEvent(imageOrSvg, 'onImageError', errorEvent);
9797

9898
const avatarBase = getByTestId('avatar-base');
99-
expect(avatarBase.props.children.props.children).toStrictEqual('E');
99+
expect(avatarBase.props.children[1].props.children).toStrictEqual('E');
100100
});
101101

102102
it('passes additional AvatarBase props correctly', () => {

packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const AvatarFavicon = ({
1212
shape = DEFAULT_AVATARFAVICON_PROPS.shape,
1313
fallbackText,
1414
fallbackTextProps,
15+
hasBorder,
16+
hasSolidBackgroundColor,
1517
twClassName,
1618
testID,
1719
style,
@@ -45,6 +47,8 @@ const AvatarFavicon = ({
4547
fallbackText={finalFallbackText}
4648
fallbackTextProps={fallbackTextProps}
4749
twClassName={twClassName}
50+
hasBorder={hasBorder}
51+
hasSolidBackgroundColor={hasSolidBackgroundColor}
4852
testID={testID}
4953
style={style}
5054
>

0 commit comments

Comments
 (0)