Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DSRN] Add BadgeIcon #473

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const getStories = () => {
"./../../packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.stories.tsx"),
"./../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx"),
"./../../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/BadgeIcon/BadgeIcon.stories.tsx": require("../../../packages/design-system-react-native/src/components/BadgeIcon/BadgeIcon.stories.tsx"),
"./../../packages/design-system-react-native/src/components/Button/Button.stories.tsx": require("../../../packages/design-system-react-native/src/components/Button/Button.stories.tsx"),
"./../../packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.stories.tsx": require("../../../packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.stories.tsx"),
"./../../packages/design-system-react-native/src/components/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx": require("../../../packages/design-system-react-native/src/components/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from './AvatarIcon.constants';
import type { AvatarIconProps } from './AvatarIcon.types';
import { generateAvatarIconContainerClassNames } from './AvatarIcon.utilities';
import Icon, { IconProps } from '../Icon';
import Icon from '../Icon';
import AvatarBase from '../../primitives/AvatarBase';

const AvatarIcon = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { BadgeIconProps } from './BadgeIcon.types';
import { BadgeIconVariant } from './BadgeIcon.types';
import { IconSize, IconName, IconColor } from '../Icon';

// Mappings
export const MAP_BADGEICON_VARIANT_ICONNAME: Record<
| BadgeIconVariant.Snaps
| BadgeIconVariant.Send
| BadgeIconVariant.Stake
| BadgeIconVariant.Bridge,
IconName
> = {
[BadgeIconVariant.Snaps]: IconName.Snaps,
[BadgeIconVariant.Send]: IconName.Arrow2UpRight,
[BadgeIconVariant.Stake]: IconName.Plant,
[BadgeIconVariant.Bridge]: IconName.Bridge,
};

// Defaults
export const DEFAULT_BADGEICON_PROPS: Required<
Pick<BadgeIconProps, 'iconProps'>
> = {
iconProps: {
size: IconSize.Xs,
color: IconColor.PrimaryInverse,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { View } from 'react-native';

import BadgeIcon from './BadgeIcon';
import type { BadgeIconProps } from './BadgeIcon.types';
import { IconName } from '../Icon';
import { BadgeIconVariant } from './BadgeIcon.types';

const meta: Meta<BadgeIconProps> = {
title: 'Components/BadgeIcon',
component: BadgeIcon,
argTypes: {
variant: {
control: 'select',
options: BadgeIconVariant,
},
iconName: {
control: 'select',
options: IconName,
},
twClassName: {
control: 'text',
},
},
};

export default meta;

type Story = StoryObj<BadgeIconProps>;

export const Default: Story = {
args: {
// Default to Custom so that the iconName knob is relevant initially.
variant: BadgeIconVariant.Custom,
iconName: IconName.Add,
twClassName: '',
},
render: (args) => {
// Destructure props from args
const { variant, ...rest } = args;

return variant === BadgeIconVariant.Custom ? (
<BadgeIcon variant={variant} iconName={args.iconName} {...rest} />
) : (
<BadgeIcon variant={variant} {...rest} />
);
},
};

export const CustomVariant: Story = {
args: {
variant: BadgeIconVariant.Custom,
iconName: IconName.Add,
twClassName: '',
},
};

export const NonCustomVariant: Story = {
render: () => (
<View style={{ gap: 16 }}>
{Object.values(BadgeIconVariant)
.filter((variant) => variant !== BadgeIconVariant.Custom)
.map((variant) => (
<BadgeIcon key={variant} variant={variant} />
))}
</View>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import React from 'react';
import { render } from '@testing-library/react-native';
import { useTailwind } from '@metamask/design-system-twrnc-preset';

import Text from '../Text';
import { IconName } from '../Icon';
import BadgeIcon from './BadgeIcon';
import {
MAP_BADGEICON_VARIANT_ICONNAME,
DEFAULT_BADGEICON_PROPS,
} from './BadgeIcon.constants';
import { BadgeIconVariant } from './BadgeIcon.types';

describe('BadgeIcon', () => {
it('renders with variant Custom and forwards provided iconName and iconProps', () => {
const TestComponent = () => {
const tw = useTailwind();
// Compute expected container style using an empty twClassName.
const computedExpectedContainer = tw`
h-[16px] w-[16px] bg-icon-default rounded-full items-center justify-center`;
return (
<>
<BadgeIcon
variant={BadgeIconVariant.Custom}
iconName={IconName.Add}
testID="badge-icon"
/>
<Text testID="expectedContainer">
{JSON.stringify(computedExpectedContainer)}
</Text>
</>
);
};

const { getByTestId } = render(<TestComponent />);
const expectedContainer = JSON.parse(
getByTestId('expectedContainer').props.children,
);
const badgeIcon = getByTestId('badge-icon');
// Verify container style.
expect(badgeIcon.props.style[0]).toStrictEqual(expectedContainer);
// Verify Icon receives the provided custom iconName.
const icon = badgeIcon.props.children;
expect(icon.props.name).toStrictEqual('Add');
expect(icon.props.color).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.color,
);
expect(icon.props.size).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.size,
);
});

it('renders with variant Snaps and uses mapping for final iconName', () => {
const TestComponent = () => {
const tw = useTailwind();
const computedExpectedContainer = tw`
h-[16px] w-[16px] bg-icon-default rounded-full items-center justify-center`;
return (
<>
<BadgeIcon variant={BadgeIconVariant.Snaps} testID="badge-icon" />
<Text testID="expectedContainer">
{JSON.stringify(computedExpectedContainer)}
</Text>
</>
);
};

const { getByTestId } = render(<TestComponent />);
const expectedContainer = JSON.parse(
getByTestId('expectedContainer').props.children,
);
const badgeIcon = getByTestId('badge-icon');
expect(badgeIcon.props.style[0]).toStrictEqual(expectedContainer);
const icon = badgeIcon.props.children;
const expectedIconName =
MAP_BADGEICON_VARIANT_ICONNAME[BadgeIconVariant.Snaps];
expect(icon.props.name).toStrictEqual(expectedIconName);
expect(icon.props.color).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.color,
);
expect(icon.props.size).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.size,
);
});

it('renders with variant Send and uses mapping for final iconName', () => {
const TestComponent = () => {
const tw = useTailwind();
const computedExpectedContainer = tw`
h-[16px] w-[16px] bg-icon-default rounded-full items-center justify-center`;
return (
<>
<BadgeIcon variant={BadgeIconVariant.Send} testID="badge-icon" />
<Text testID="expectedContainer">
{JSON.stringify(computedExpectedContainer)}
</Text>
</>
);
};

const { getByTestId } = render(<TestComponent />);
const expectedContainer = JSON.parse(
getByTestId('expectedContainer').props.children,
);
const badgeIcon = getByTestId('badge-icon');
expect(badgeIcon.props.style[0]).toStrictEqual(expectedContainer);
const icon = badgeIcon.props.children;
const expectedIconName =
MAP_BADGEICON_VARIANT_ICONNAME[BadgeIconVariant.Send];
expect(icon.props.name).toStrictEqual(expectedIconName);
expect(icon.props.color).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.color,
);
expect(icon.props.size).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.size,
);
});

it('renders with variant Stake and uses mapping for final iconName', () => {
const TestComponent = () => {
const tw = useTailwind();
const computedExpectedContainer = tw`
h-[16px] w-[16px] bg-icon-default rounded-full items-center justify-center`;
return (
<>
<BadgeIcon variant={BadgeIconVariant.Stake} testID="badge-icon" />
<Text testID="expectedContainer">
{JSON.stringify(computedExpectedContainer)}
</Text>
</>
);
};

const { getByTestId } = render(<TestComponent />);
const expectedContainer = JSON.parse(
getByTestId('expectedContainer').props.children,
);
const badgeIcon = getByTestId('badge-icon');
expect(badgeIcon.props.style[0]).toStrictEqual(expectedContainer);
const icon = badgeIcon.props.children;
const expectedIconName =
MAP_BADGEICON_VARIANT_ICONNAME[BadgeIconVariant.Stake];
expect(icon.props.name).toStrictEqual(expectedIconName);
expect(icon.props.color).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.color,
);
expect(icon.props.size).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.size,
);
});

it('renders with variant Bridge and uses mapping for final iconName', () => {
const TestComponent = () => {
const tw = useTailwind();
const computedExpectedContainer = tw`
h-[16px] w-[16px] bg-icon-default rounded-full items-center justify-center`;
return (
<>
<BadgeIcon variant={BadgeIconVariant.Bridge} testID="badge-icon" />
<Text testID="expectedContainer">
{JSON.stringify(computedExpectedContainer)}
</Text>
</>
);
};

const { getByTestId } = render(<TestComponent />);
const expectedContainer = JSON.parse(
getByTestId('expectedContainer').props.children,
);
const badgeIcon = getByTestId('badge-icon');
expect(badgeIcon.props.style[0]).toStrictEqual(expectedContainer);
const icon = badgeIcon.props.children;
const expectedIconName =
MAP_BADGEICON_VARIANT_ICONNAME[BadgeIconVariant.Bridge];
expect(icon.props.name).toStrictEqual(expectedIconName);
expect(icon.props.color).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.color,
);
expect(icon.props.size).toStrictEqual(
DEFAULT_BADGEICON_PROPS.iconProps.size,
);
});

it('applies custom container style and forwards extra props', () => {
const customStyle = { margin: 10 };
const extraProp = { accessibilityLabel: 'badge-icon' };
const TestComponent = () => {
return (
<BadgeIcon
variant={BadgeIconVariant.Stake}
style={customStyle}
testID="badge-icon"
{...extraProp}
/>
);
};
const { getByTestId } = render(<TestComponent />);
const badgeIcon = getByTestId('badge-icon');
expect(badgeIcon.props.style).toEqual(customStyle);
expect(badgeIcon.props.accessibilityLabel).toStrictEqual('badge-icon');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import React from 'react';
import { View } from 'react-native';

import Icon from '../Icon';
import {
DEFAULT_BADGEICON_PROPS,
MAP_BADGEICON_VARIANT_ICONNAME,
} from './BadgeIcon.constants';
import type { BadgeIconProps } from './BadgeIcon.types';
import { BadgeIconVariant } from './BadgeIcon.types';

const BadgeIcon = (props: BadgeIconProps) => {
const tw = useTailwind();
const { variant, iconProps, twClassName = '', style, ...rest } = props;
const twContainerClassNames =
`h-[16px] w-[16px] bg-icon-default rounded-full items-center justify-center ${twClassName}`.trim();

const finalIconName =
variant === BadgeIconVariant.Custom
? props.iconName
: MAP_BADGEICON_VARIANT_ICONNAME[variant];

return (
<View
style={[tw`${twContainerClassNames}`, style]}
accessibilityRole="image"
{...props}
>
<Icon
color={DEFAULT_BADGEICON_PROPS.iconProps.color}
{...iconProps}
size={DEFAULT_BADGEICON_PROPS.iconProps.size}
name={finalIconName}
/>
</View>
);
};

export default BadgeIcon;
Loading
Loading