Skip to content

Commit 8b6ea8b

Browse files
[DSRN] Align AvatarIcon with DSR (#605)
<!-- 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 updated the `AvatarIcon` in the `@metamask/design-system-react-native` package with - severity Default --> Neutral - removed utilities file and updated tests - updated description <!-- 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: #596 ## **Manual testing steps** 1. Run `yarn storybook:ios` 2. Make sure that `AvatarIcon` is still functional 3. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/b9d1a00f-eaf8-4046-8978-0363e6212531 <!-- [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. --------- Co-authored-by: George Marshall <[email protected]>
1 parent 00e81a1 commit 8b6ea8b

File tree

8 files changed

+124
-112
lines changed

8 files changed

+124
-112
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR: Record<
66
AvatarIconSeverity,
77
string
88
> = {
9-
[AvatarIconSeverity.Default]: 'bg-background-muted',
9+
[AvatarIconSeverity.Neutral]: 'bg-background-muted',
1010
[AvatarIconSeverity.Info]: 'bg-info-muted',
1111
[AvatarIconSeverity.Success]: 'bg-success-muted',
1212
[AvatarIconSeverity.Error]: 'bg-error-muted',
@@ -23,7 +23,7 @@ export const MAP_AVATARICON_SEVERITY_ICONCOLOR: Record<
2323
AvatarIconSeverity,
2424
IconColor
2525
> = {
26-
[AvatarIconSeverity.Default]: IconColor.IconAlternative,
26+
[AvatarIconSeverity.Neutral]: IconColor.IconAlternative,
2727
[AvatarIconSeverity.Info]: IconColor.InfoDefault,
2828
[AvatarIconSeverity.Success]: IconColor.SuccessDefault,
2929
[AvatarIconSeverity.Error]: IconColor.ErrorDefault,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Story = StoryObj<AvatarIconProps>;
3535
export const Default: Story = {
3636
args: {
3737
size: AvatarIconSize.Md,
38-
severity: AvatarIconSeverity.Default,
38+
severity: AvatarIconSeverity.Neutral,
3939
iconName: IconName.Arrow2UpRight,
4040
twClassName: '',
4141
},
Lines changed: 107 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,124 @@
1+
import { useTailwind } from '@metamask/design-system-twrnc-preset';
2+
import { renderHook } from '@testing-library/react-hooks';
13
import { render } from '@testing-library/react-native';
4+
import React from 'react';
25

3-
import { AvatarIconSize, AvatarIconSeverity } from '../../types';
6+
import { AvatarIconSeverity, AvatarIconSize } from '../../types';
47
import { IconName } from '../Icon';
58
import AvatarIcon from './AvatarIcon';
6-
import { TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR } from './AvatarIcon.constants';
7-
import { generateAvatarIconContainerClassNames } from './AvatarIcon.utilities';
9+
import {
10+
TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR,
11+
MAP_AVATARICON_SEVERITY_ICONCOLOR,
12+
MAP_AVATARICON_SIZE_ICONSIZE,
13+
} from './AvatarIcon.constants';
814

915
describe('AvatarIcon', () => {
10-
describe('generateAvatarIconContainerClassNames', () => {
11-
it('returns correct class names for default state', () => {
12-
const classNames = generateAvatarIconContainerClassNames({});
13-
expect(classNames).toStrictEqual(
14-
`${TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[AvatarIconSeverity.Default]}`,
15-
);
16-
});
17-
18-
it('applies correct severity class', () => {
19-
Object.values(AvatarIconSeverity).forEach((severity) => {
20-
const expectedClass =
21-
TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[severity];
22-
const classNames = generateAvatarIconContainerClassNames({ severity });
23-
expect(classNames).toStrictEqual(expectedClass);
24-
});
25-
});
26-
27-
it('appends additional Tailwind class names', () => {
28-
const classNames = generateAvatarIconContainerClassNames({
29-
twClassName: 'shadow-lg ring-2',
30-
});
31-
expect(classNames).toStrictEqual(
32-
`${TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[AvatarIconSeverity.Default]} shadow-lg ring-2`,
33-
);
34-
});
35-
36-
it('applies severity and additional classes together correctly', () => {
37-
const severity = AvatarIconSeverity.Success;
38-
const classNames = generateAvatarIconContainerClassNames({
39-
severity,
40-
twClassName: 'border border-green-500',
41-
});
42-
expect(classNames).toStrictEqual(
43-
`${TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[severity]} border border-green-500`,
44-
);
45-
});
16+
it('applies default container style and default icon props', () => {
17+
const { result } = renderHook(() => useTailwind());
18+
const tw = result.current;
19+
20+
const bgClass =
21+
TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[
22+
AvatarIconSeverity.Neutral
23+
];
24+
const expectedIconBgStyle = tw`${bgClass}`;
25+
26+
const expectedIconColor =
27+
tw`${MAP_AVATARICON_SEVERITY_ICONCOLOR[AvatarIconSeverity.Neutral]}`
28+
.color;
29+
30+
const { getByTestId } = render(
31+
<AvatarIcon
32+
iconName={IconName.Add}
33+
iconProps={{ testID: 'icon' }}
34+
testID="avatar-icon"
35+
/>,
36+
);
37+
const container = getByTestId('avatar-icon');
38+
expect(container.props.style[1][0]).toStrictEqual(expectedIconBgStyle);
39+
40+
const icon = getByTestId('icon');
41+
expect(icon.props.style[0].color).toStrictEqual(expectedIconColor);
42+
expect(container.props.accessibilityRole).toStrictEqual('image');
43+
});
44+
45+
it('applies custom twClassName and style props', () => {
46+
const { result } = renderHook(() => useTailwind());
47+
const tw = result.current;
48+
49+
const bgClass =
50+
TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[
51+
AvatarIconSeverity.Neutral
52+
];
53+
const expectedIconBgStyle = tw`${bgClass} custom-class`;
54+
55+
const expectedIconColor =
56+
tw`${MAP_AVATARICON_SEVERITY_ICONCOLOR[AvatarIconSeverity.Neutral]}`
57+
.color;
58+
const customStyle = { margin: 10 };
59+
60+
const { getByTestId } = render(
61+
<AvatarIcon
62+
iconName={IconName.Add}
63+
iconProps={{ testID: 'icon' }}
64+
twClassName="custom-class"
65+
style={customStyle}
66+
accessibilityLabel="avatar"
67+
testID="avatar-icon"
68+
/>,
69+
);
70+
const container = getByTestId('avatar-icon');
71+
expect(container.props.style[1][0]).toStrictEqual(expectedIconBgStyle);
72+
expect(container.props.style[1][1]).toStrictEqual(customStyle);
73+
expect(container.props.accessibilityLabel).toStrictEqual('avatar');
74+
75+
const icon = getByTestId('icon');
76+
expect(icon.props.style[0].color).toStrictEqual(expectedIconColor);
4677
});
47-
describe('AvatarIcon Component', () => {
48-
it('renders with default props', () => {
49-
const { getByTestId: getByTestIdIcon } = render(
78+
79+
it.each(Object.values(AvatarIconSeverity))(
80+
'applies correct background and icon color for severity %s',
81+
(severity) => {
82+
const { result } = renderHook(() => useTailwind());
83+
const tw = result.current;
84+
85+
const bgClass = TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[severity];
86+
const expectedIconBgStyle = tw`${bgClass}`;
87+
88+
const expectedIconColor =
89+
tw`${MAP_AVATARICON_SEVERITY_ICONCOLOR[severity]}`.color;
90+
91+
const { getByTestId } = render(
5092
<AvatarIcon
5193
iconName={IconName.Add}
52-
iconProps={{ testID: 'inner-icon' }}
94+
iconProps={{ testID: 'icon' }}
95+
severity={severity}
96+
testID="avatar-icon"
5397
/>,
5498
);
55-
const icon = getByTestIdIcon('inner-icon');
56-
57-
expect(icon.props.name).toStrictEqual(IconName.Add);
58-
});
99+
const container = getByTestId('avatar-icon');
100+
expect(container.props.style[1][0]).toStrictEqual(expectedIconBgStyle);
59101

60-
it('renders with custom props', () => {
61-
const customSize = AvatarIconSize.Lg;
62-
const customSeverity = AvatarIconSeverity.Error;
63-
const customIconProps = { testID: 'custom-icon', extraProp: 'value' };
102+
const icon = getByTestId('icon');
103+
expect(icon.props.style[0].color).toStrictEqual(expectedIconColor);
104+
},
105+
);
64106

65-
// Render separately to test the Icon props.
66-
const { getByTestId: getIcon } = render(
107+
it.each(Object.values(AvatarIconSize))(
108+
'applies correct icon size for size %s',
109+
(size) => {
110+
const expectedSize = MAP_AVATARICON_SIZE_ICONSIZE[size].toString();
111+
const { getByTestId } = render(
67112
<AvatarIcon
68-
iconName={IconName.Close}
69-
size={customSize}
70-
severity={customSeverity}
71-
iconProps={customIconProps}
113+
iconName={IconName.Add}
114+
iconProps={{ testID: 'icon' }}
115+
size={size}
116+
testID="avatar-icon"
72117
/>,
73118
);
74-
const icon = getIcon('custom-icon');
75-
expect(icon.props.name).toStrictEqual(IconName.Close);
76-
expect(icon.props.extraProp).toStrictEqual('value');
77-
});
78-
});
119+
const icon = getByTestId('icon');
120+
expect(icon.props.style[0].width.toString()).toStrictEqual(expectedSize);
121+
expect(icon.props.style[0].height.toString()).toStrictEqual(expectedSize);
122+
},
123+
);
79124
});

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
22
import { useTailwind } from '@metamask/design-system-twrnc-preset';
3-
import React, { useMemo } from 'react';
3+
import React from 'react';
44

55
import {
66
AvatarIconSize,
@@ -12,27 +12,25 @@ import Icon from '../Icon';
1212
import {
1313
MAP_AVATARICON_SIZE_ICONSIZE,
1414
MAP_AVATARICON_SEVERITY_ICONCOLOR,
15+
TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR,
1516
} from './AvatarIcon.constants';
1617
import type { AvatarIconProps } from './AvatarIcon.types';
17-
import { generateAvatarIconContainerClassNames } from './AvatarIcon.utilities';
1818

1919
const AvatarIcon = ({
2020
size = AvatarIconSize.Md,
2121
shape = AvatarBaseShape.Circle,
22-
severity = AvatarIconSeverity.Default,
22+
severity = AvatarIconSeverity.Neutral,
2323
iconName,
2424
iconProps,
2525
twClassName = '',
2626
style,
2727
...props
2828
}: AvatarIconProps) => {
2929
const tw = useTailwind();
30-
const twContainerClassNames = useMemo(() => {
31-
return generateAvatarIconContainerClassNames({
32-
severity,
33-
twClassName,
34-
});
35-
}, [severity, twClassName]);
30+
const twContainerClassNames = `
31+
${TWCLASSMAP_AVATARICON_SEVERITY_BACKGROUNDCOLOR[severity]}
32+
${twClassName}
33+
`;
3634

3735
return (
3836
<AvatarBase

packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IconName, IconProps } from '../Icon';
88
export type AvatarIconProps = {
99
/**
1010
* Optional prop to control the severity of the avatar
11-
* @default AvatarIconSeverity.Default
11+
* @default AvatarIconSeverity.Neutral
1212
*/
1313
severity?: AvatarIconSeverity;
1414
/**

packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.utilities.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

packages/design-system-react-native/src/components/AvatarIcon/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# AvatarIcon
22

3-
The `AvatarIcon` component is reserved for representing static icons inside of an avatar. It extends the functionality of [`AvatarBase`](../AvatarBase/) by incorporating an icon and severity levels, making it useful for visually representing statuses, alerts, or simply user avatars with icons.
3+
The `AvatarIcon` component is reserved for representing static icons inside of an avatar.
4+
5+
_Developer Note: This components extends the functionality of [`AvatarBase`](../AvatarBase/) by incorporating an icon and severity levels, making it useful for visually representing statuses, alerts, or simply user avatars with icons._
46

57
---
68

@@ -12,11 +14,11 @@ Optional prop to control the severity of the avatar.
1214

1315
| TYPE | REQUIRED | DEFAULT |
1416
| :------------------- | :------- | :--------------------------- |
15-
| `AvatarIconSeverity` | No | `AvatarIconSeverity.Default` |
17+
| `AvatarIconSeverity` | No | `AvatarIconSeverity.Neutral` |
1618

1719
Available severities:
1820

19-
- `Default`
21+
- `Neutral`
2022
- `Info`
2123
- `Success`
2224
- `Error`

packages/design-system-react-native/src/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export enum AvatarGroupVariant {
6969
* AvatarIcon - severity
7070
*/
7171
export enum AvatarIconSeverity {
72-
Default = 'default',
72+
Neutral = 'neutral',
7373
Info = 'info',
7474
Success = 'success',
7575
Error = 'error',

0 commit comments

Comments
 (0)