1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Updating AvatarWithBage to BadgeWrapper (#17851)

This commit is contained in:
George Marshall 2023-03-08 13:18:55 -08:00 committed by GitHub
parent de83546d6d
commit ed519d8b60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1092 additions and 379 deletions

View File

@ -1,75 +0,0 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { AvatarWithBadge } from './avatar-with-badge';
# AvatarWithBadge
The `AvatarWithBadge` is a wrapper component that adds badge display options to avatars.
<Canvas>
<Story id="components-componentlibrary-avatarwithbadge--default-story" />
</Canvas>
## Props
The `AvatarWithBadge` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props.
<ArgsTable of={AvatarWithBadge} />
### Badge Position
Use the `badgePosition` prop to set the position of the badge, it can have two values `Top` and `Bottom`
<Canvas>
<Story id="components-componentlibrary-avatarwithbadge--badge-position" />
</Canvas>
```jsx
import { AvatarWithBadge } from '../ui/component-library';
<AvatarWithBadge
badgePosition={AVATAR_WITH_BADGE_POSTIONS.BOTTOM}
badge={
<AvatarNetwork
size={Size.XS}
name="Arbitrum One"
src="./images/arbitrum.svg"
/>
}
>
<AvatarAccount
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
size={Size.MD}
type={TYPES.JAZZICON}
/>
</AvatarWithBadge>
<AvatarWithBadge
badgePosition={AVATAR_WITH_BADGE_POSTIONS.TOP}
badge={
<AvatarNetwork
size={Size.XS}
name="Arbitrum One"
src="./images/arbitrum.svg"
/>
}
>
<AvatarAccount
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
size={Size.MD}
type={TYPES.JAZZICON}
/>
</AvatarWithBadge>
```
### Badge
Used to define the badge component to be rendered inside the `AvatarWithBadge`.
### Badge Props
The required props to be passed to the badge.
### Children
The children to be rendered inside the AvatarWithBadge. Generally used with the `AvatarAccount`

View File

@ -1,25 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AvatarWithBadge should render correctly 1`] = `
<div>
<div
class="box mm-avatar-with-badge box--flex-direction-row"
data-testid="avatar-with-badge"
>
<div
class="box mm-avatar-with-badge__badge-wrapper--position-bottom box--flex-direction-row"
>
<div
class="box mm-avatar-base mm-avatar-base--size-md mm-avatar-network box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default box--background-color-background-alternative box--border-color-transparent box--border-style-solid box--border-width-1"
data-testid="badge"
>
<img
alt="Arbitrum One logo"
class="mm-avatar-network__network-image"
src="./images/arbitrum.svg"
/>
</div>
</div>
</div>
</div>
`;

View File

@ -1,4 +0,0 @@
export const AVATAR_WITH_BADGE_POSTIONS = {
TOP: 'top',
BOTTOM: 'bottom',
};

View File

@ -1,60 +0,0 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import Box from '../../ui/box/box';
import { AVATAR_WITH_BADGE_POSTIONS } from './avatar-with-badge.constants';
export const AvatarWithBadge = ({
children,
badgePosition,
className,
badge,
badgeWrapperProps,
...props
}) => {
return (
<Box className={classnames('mm-avatar-with-badge', className)} {...props}>
{/* Generally the AvatarAccount */}
{children}
<Box
className={
badgePosition === 'top'
? 'mm-avatar-with-badge__badge-wrapper--position-top'
: 'mm-avatar-with-badge__badge-wrapper--position-bottom'
}
{...badgeWrapperProps}
>
{/* Generally the AvatarNetwork at SIZES.XS */}
{badge}
</Box>
</Box>
);
};
AvatarWithBadge.propTypes = {
/**
* The position of the Badge
* Possible values could be 'top', 'bottom',
*/
badgePosition: PropTypes.oneOf(Object.values(AVATAR_WITH_BADGE_POSTIONS)),
/**
* The Badge Wrapper props of the component. All Box props can be used
*/
badgeWrapperProps: PropTypes.shape(Box.PropTypes),
/**
* The children to be rendered inside the AvatarWithBadge
*/
children: PropTypes.node,
/**
* The badge to be rendered inside the AvatarWithBadge
*/
badge: PropTypes.object,
/**
* Add custom css class
*/
className: PropTypes.string,
/**
* AvatarWithBadge accepts all the props from Box
*/
...Box.propTypes,
};

View File

@ -1,16 +0,0 @@
.mm-avatar-with-badge {
position: relative;
width: fit-content;
&__badge-wrapper--position-top {
position: absolute;
top: -4px;
right: -4px;
}
&__badge-wrapper--position-bottom {
position: absolute;
bottom: -4px;
right: -4px;
}
}

View File

@ -1,91 +0,0 @@
import React from 'react';
import { AvatarAccount } from '../avatar-account';
import { AVATAR_ACCOUNT_TYPES } from '../avatar-account/avatar-account.constants';
import { AvatarNetwork } from '../avatar-network';
import Box from '../../ui/box/box';
import {
AlignItems,
DISPLAY,
Size,
} from '../../../helpers/constants/design-system';
import { AVATAR_WITH_BADGE_POSTIONS } from './avatar-with-badge.constants';
import README from './README.mdx';
import { AvatarWithBadge } from './avatar-with-badge';
export default {
title: 'Components/ComponentLibrary/AvatarWithBadge',
component: AvatarWithBadge,
parameters: {
docs: {
page: README,
},
},
argTypes: {
badgePosition: {
options: Object.values(AVATAR_WITH_BADGE_POSTIONS),
control: 'select',
},
},
args: {
badgePosition: AVATAR_WITH_BADGE_POSTIONS.top,
},
};
export const DefaultStory = (args) => (
<AvatarWithBadge
badge={
<AvatarNetwork
size={Size.XS}
name="Arbitrum One"
src="./images/arbitrum.svg"
/>
}
{...args}
>
<AvatarAccount
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
size={Size.MD}
type={AVATAR_ACCOUNT_TYPES.JAZZICON}
/>
</AvatarWithBadge>
);
DefaultStory.storyName = 'Default';
export const BadgePosition = () => (
<Box display={DISPLAY.FLEX} alignItems={AlignItems.baseline} gap={1}>
<AvatarWithBadge
badgePosition={AVATAR_WITH_BADGE_POSTIONS.BOTTOM}
badge={
<AvatarNetwork
size={Size.XS}
name="Arbitrum One"
src="./images/arbitrum.svg"
/>
}
>
<AvatarAccount
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
size={Size.MD}
type={AVATAR_ACCOUNT_TYPES.JAZZICON}
/>
</AvatarWithBadge>
<AvatarWithBadge
badgePosition={AVATAR_WITH_BADGE_POSTIONS.TOP}
badge={
<AvatarNetwork
size={Size.XS}
name="Arbitrum One"
src="./images/arbitrum.svg"
/>
}
>
<AvatarAccount
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
size={Size.MD}
type={AVATAR_ACCOUNT_TYPES.JAZZICON}
/>
</AvatarWithBadge>
</Box>
);

View File

@ -1,102 +0,0 @@
/* eslint-disable jest/require-top-level-describe */
import { render } from '@testing-library/react';
import React from 'react';
import { AvatarNetwork } from '../avatar-network/avatar-network';
import { BorderColor } from '../../../helpers/constants/design-system';
import { AvatarWithBadge } from './avatar-with-badge';
import { AVATAR_WITH_BADGE_POSTIONS } from './avatar-with-badge.constants';
describe('AvatarWithBadge', () => {
it('should render correctly', () => {
const { getByTestId, container } = render(
<AvatarWithBadge
badgePosition={AVATAR_WITH_BADGE_POSTIONS.BOTTOM}
data-testid="avatar-with-badge"
badge={
<AvatarNetwork
name="Arbitrum One"
src="./images/arbitrum.svg"
data-testid="badge"
/>
}
/>,
);
expect(getByTestId('avatar-with-badge')).toBeDefined();
expect(getByTestId('badge')).toBeDefined();
expect(container).toMatchSnapshot();
});
it('should render badge network with bottom right position correctly', () => {
const { container } = render(
<AvatarWithBadge
data-testid="avatar-with-badge"
badgePosition={AVATAR_WITH_BADGE_POSTIONS.BOTTOM}
badge={
<AvatarNetwork
name="Arbitrum One"
src="./images/arbitrum.svg"
data-testid="badge"
/>
}
/>,
);
expect(
container.getElementsByClassName(
'mm-avatar-with-badge__badge-wrapper--position-bottom',
),
).toHaveLength(1);
});
it('should render badge network with top right position correctly', () => {
const { container } = render(
<AvatarWithBadge
data-testid="avatar-with-badge"
badgePosition={AVATAR_WITH_BADGE_POSTIONS.TOP}
badge={
<AvatarNetwork
name="Arbitrum One"
src="./images/arbitrum.svg"
data-testid="badge"
/>
}
/>,
);
expect(
container.getElementsByClassName(
'mm-avatar-with-badge__badge-wrapper--position-top',
),
).toHaveLength(1);
});
it('should render badge network with badgeWrapperProps', () => {
const container = (
<AvatarWithBadge
data-testid="avatar-with-badge"
badgePosition={AVATAR_WITH_BADGE_POSTIONS.TOP}
badgeWrapperProps={{ borderColor: BorderColor.errorDefault }}
badge={
<AvatarNetwork
name="Arbitrum One"
src="./images/arbitrum.svg"
data-testid="badge"
/>
}
/>
);
expect(container.props.badgeWrapperProps.borderColor).toStrictEqual(
'error-default',
);
});
// className
it('should render with custom className', () => {
const { getByTestId } = render(
<AvatarWithBadge
data-testid="avatar-with-badge"
className="test-class"
/>,
);
expect(getByTestId('avatar-with-badge')).toHaveClass('test-class');
});
});

View File

@ -1,2 +0,0 @@
export { AvatarWithBadge } from './avatar-with-badge';
export { AVATAR_WITH_BADGE_POSTIONS } from './avatar-with-badge.constants';

View File

@ -0,0 +1,378 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { BadgeWrapper } from './badge-wrapper';
# BadgeWrapper
The `BadgeWrapper` positions a badge on top of another component
<Canvas>
<Story id="components-componentlibrary-badgewrapper--default-story" />
</Canvas>
## Props
The `BadgeWrapper` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props.
<ArgsTable of={BadgeWrapper} />
### Children
Use the `children` prop to define the element to be wrapped by the `BadgeWrapper`. This element will be what the badge is positioned on top of.
<Canvas>
<Story id="components-componentlibrary-badgewrapper--children" />
</Canvas>
```jsx
import { Color, Size } from '../../../helpers/constants/design-system';
import {
AvatarAccount,
AvatarNetwork,
AvatarToken,
BadgeWrapper,
BadgeWrapperAnchorElementShape,
BadgeWrapperPosition,
} from '../../component-library';
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
/>
</BadgeWrapper>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarToken
name="Eth"
src="./images/eth_logo.svg"
borderColor={BorderColor.borderMuted}
/>
</BadgeWrapper>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.SM}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
>
<Box
as="img"
src="./catnip-spicywright.png"
borderRadius={BorderRadius.SM}
borderColor={BorderColor.borderMuted}
style={{ width: 100, height: 100 }}
/>
</BadgeWrapper>
```
### Badge
Use the `badge` prop to define the badge component to be rendered on top of the `children` component.
To access the component containing the badge, use the `badgeContainerProps` prop. The wrapping component is a `Box` and accepts all box props.
<Canvas>
<Story id="components-componentlibrary-badgewrapper--badge" />
</Canvas>
```jsx
import {
BorderColor,
BorderRadius,
Color,
IconColor,
Size,
} from '../../../helpers/constants/design-system';
import {
AvatarAccount,
AvatarNetwork,
BadgeWrapper,
BadgeWrapperAnchorElementShape,
Icon,
ICON_NAMES,
Tag,
} from '../../component-library';
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
badge={
<Box
backgroundColor={Color.successDefault}
borderRadius={BorderRadius.full}
borderColor={BorderColor.borderMuted}
borderWidth={2}
style={{ width: 12, height: 12 }}
/>
}
>
<Icon
name={ICON_NAMES.GLOBAL}
size={Size.XL}
color={IconColor.iconAlternative}
/>
</BadgeWrapper>
<Box
paddingTop={1}
paddingBottom={1}
paddingRight={1}
paddingLeft={1}
backgroundColor={Color.backgroundAlternative}
borderRadius={BorderRadius.SM}
style={{ alignSelf: 'flex-start' }}
>
<BadgeWrapper
badge={
<Tag
label="9999"
backgroundColor={Color.errorDefault}
labelProps={{ color: Color.errorInverse }}
borderColor={BorderColor.errorDefault}
/>
}
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
>
<Box
paddingTop={1}
paddingBottom={1}
paddingRight={8}
paddingLeft={8}
borderRadius={BorderRadius.SM}
borderColor={BorderColor.borderDefault}
backgroundColor={Color.backgroundDefault}
>
NFTs
</Box>
</BadgeWrapper>
</Box>
```
### Position
Use the `position` prop and the `BadgeWrapperPosition` enum to set the position of the badge. Possible positions are:
- top left `BadgeWrapperPosition.topLeft`
- top right `BadgeWrapperPosition.topRight`
- bottom left `BadgeWrapperPosition.bottomLeft`
- bottom right `BadgeWrapperPosition.bottomRight`
If you require a custom position, you can use the `positionObj` prop see [Position Obj](/docs/components-componentlibrary-badgewrapper--position-obj#position-obj) for more details.
<Canvas>
<Story id="components-componentlibrary-badgewrapper--position" />
</Canvas>
```jsx
import { BorderColor Size } from '../../../helpers/constants/design-system';
import {
AvatarAccount,
AvatarNetwork,
BadgeWrapper,
BadgeWrapperPosition
} from '../../component-library';
<BadgeWrapper
position={BadgeWrapperPosition.topLeft}
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
position={BadgeWrapperPosition.bottomLeft}
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
position={BadgeWrapperPosition.bottomRight}
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
```
### Position Obj
Use the `positionObj` prop to set a custom position for the badge. The `positionObj` prop takes an object with the following properties:
- `top` - the top position of the badge
- `right` - the right position of the badge
- `bottom` - the bottom position of the badge
- `left` - the left position of the badge
<Canvas>
<Story id="components-componentlibrary-badgewrapper--position-obj" />
</Canvas>
```jsx
import { BorderColor Size } from '../../../helpers/constants/design-system';
import {
AvatarAccount,
AvatarNetwork,
BadgeWrapper,
} from '../../component-library';
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
positionObj={{ top: 4, right: -8 }}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"/>
</BadgeWrapper>
```
### Anchor Element Shape
Use the `anchorElementShape` prop and the `BadgeWrapperAnchorElementShape` enum to set the badge position relative to the shape of the anchor element. Possible shapes are:
- circular `BadgeWrapperAnchorElementShape.circular`
- rectangular `BadgeWrapperAnchorElementShape.rectangular`
<Canvas>
<Story id="components-componentlibrary-badgewrapper--anchor-element-shape" />
</Canvas>
```jsx
import Box from '../../ui/box/box';
import { BorderRadius, Color } from '../../../helpers/constants/design-system';
import { BadgeWrapper, BadgeWrapperAnchorElementShape } from '../../component-library';
<BadgeWrapper
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 16, height: 16 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
borderRadius={BorderRadius.full}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 8, height: 8 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
borderRadius={BorderRadius.full}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 16, height: 16 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 8, height: 8 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
```

View File

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BadgeWrapper should render correctly 1`] = `
<div>
<div
class="box mm-badge-wrapper box--display-inline-block box--flex-direction-row"
>
content
<div
class="box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--circular-top-right box--flex-direction-row"
>
<div
data-testid="badge"
>
badge
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,80 @@
/**
* Mixin that renders the CSS values for badge positions and value
*/
@mixin badgePosition($position, $value) {
@if $position == top-right {
top: $value;
right: $value;
transform: scale(1) translate(50%, -50%);
transform-origin: 100% 0%;
}
@else if $position == bottom-right {
bottom: $value;
right: $value;
transform: scale(1) translate(50%, 50%);
transform-origin: 100% 100%;
}
@else if $position == top-left {
top: $value;
left: $value;
transform: scale(1) translate(-50%, -50%);
transform-origin: 0% 0%;
}
@else if $position == bottom-left {
bottom: $value;
left: $value;
transform: scale(1) translate(-50%, 50%);
transform-origin: 0% 100%;
}
}
.mm-badge-wrapper {
--badge-wrapper-position-circular: 14%;
--badge-wrapper-position-rectangular: 0;
position: relative;
align-self: start; // prevents stretching of badge-wrapper when in flexbox container to maintain badge positioning
&__badge-container {
position: absolute;
&--circular {
&-top-right {
@include badgePosition('top-right', var(--badge-wrapper-position-circular));
}
&-bottom-right {
@include badgePosition('bottom-right', var(--badge-wrapper-position-circular));
}
&-top-left {
@include badgePosition('top-left', var(--badge-wrapper-position-circular));
}
&-bottom-left {
@include badgePosition('bottom-left', var(--badge-wrapper-position-circular));
}
}
&--rectangular {
&-top-right {
@include badgePosition('top-right', var(--badge-wrapper-position-rectangular));
}
&-bottom-right {
@include badgePosition('bottom-right', var(--badge-wrapper-position-rectangular));
}
&-top-left {
@include badgePosition('top-left', var(--badge-wrapper-position-rectangular));
}
&-bottom-left {
@include badgePosition('bottom-left', var(--badge-wrapper-position-rectangular));
}
}
}
}

View File

@ -0,0 +1,360 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import {
AlignItems,
BorderColor,
BorderRadius,
Color,
DISPLAY,
IconColor,
Size,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box';
import {
AvatarAccount,
AvatarNetwork,
AvatarToken,
Icon,
ICON_NAMES,
Tag,
} from '..';
import {
BadgeWrapperAnchorElementShape,
BadgeWrapperPosition,
} from './badge-wrapper.types';
import README from './README.mdx';
import { BadgeWrapper } from './badge-wrapper';
export default {
title: 'Components/ComponentLibrary/BadgeWrapper',
component: BadgeWrapper,
parameters: {
docs: {
page: README,
},
},
argTypes: {
children: {
control: 'text',
},
badge: {
control: 'text',
},
position: {
options: Object.values(BadgeWrapperPosition),
control: 'select',
},
positionObj: {
control: 'object',
},
anchorElementShape: {
options: Object.values(BadgeWrapperAnchorElementShape),
control: 'select',
},
className: {
control: 'text',
},
},
} as ComponentMeta<typeof BadgeWrapper>;
const Template: ComponentStory<typeof BadgeWrapper> = (args) => (
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
{...args}
>
{args.children ? (
args.children
) : (
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
)}
</BadgeWrapper>
);
export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';
export const Children = () => (
<Box display={DISPLAY.FLEX} gap={4}>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarToken
name="Eth"
src="./images/eth_logo.svg"
borderColor={BorderColor.borderMuted}
/>
</BadgeWrapper>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.SM}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
>
<Box
as="img"
src="./catnip-spicywright.png"
borderRadius={BorderRadius.SM}
borderColor={BorderColor.borderMuted}
style={{ width: 100, height: 100 }}
/>
</BadgeWrapper>
</Box>
);
export const Badge = () => (
<Box display={DISPLAY.FLEX} gap={4}>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
badge={
<Box
backgroundColor={Color.successDefault}
borderRadius={BorderRadius.full}
borderColor={BorderColor.borderMuted}
borderWidth={2}
style={{ width: 12, height: 12 }}
/>
}
>
<Icon
name={ICON_NAMES.GLOBAL}
size={Size.XL}
color={IconColor.iconAlternative}
/>
</BadgeWrapper>
<Box
paddingTop={1}
paddingBottom={1}
paddingRight={1}
paddingLeft={1}
backgroundColor={Color.backgroundAlternative}
borderRadius={BorderRadius.SM}
style={{ alignSelf: 'flex-start' }}
>
<BadgeWrapper
badge={
<Tag
label="9999"
backgroundColor={Color.errorDefault}
labelProps={{ color: Color.errorInverse }}
borderColor={BorderColor.errorDefault}
/>
}
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
>
<Box
paddingTop={1}
paddingBottom={1}
paddingRight={8}
paddingLeft={8}
borderRadius={BorderRadius.SM}
borderColor={BorderColor.borderDefault}
backgroundColor={Color.backgroundDefault}
>
NFTs
</Box>
</BadgeWrapper>
</Box>
</Box>
);
export const Position = () => (
<Box display={DISPLAY.FLEX} gap={4}>
<BadgeWrapper
position={BadgeWrapperPosition.topLeft}
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
position={BadgeWrapperPosition.bottomLeft}
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
<BadgeWrapper
position={BadgeWrapperPosition.bottomRight}
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
</Box>
);
export const PositionObj = () => (
<Box display={DISPLAY.FLEX} alignItems={AlignItems.baseline} gap={4}>
<BadgeWrapper
badge={
<AvatarNetwork
size={Size.XS}
name="Avalanche"
src="./images/avax-token.png"
borderColor={BorderColor.borderMuted}
/>
}
positionObj={{ top: 4, right: -8 }}
>
<AvatarAccount address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1" />
</BadgeWrapper>
</Box>
);
export const AnchorElementShape = () => (
<Box display={DISPLAY.FLEX} gap={4}>
<BadgeWrapper
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 16, height: 16 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
borderRadius={BorderRadius.full}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 8, height: 8 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
borderRadius={BorderRadius.full}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 16, height: 16 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 8, height: 8 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
style={{ width: 40, height: 40 }}
/>
</BadgeWrapper>
<BadgeWrapper
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
badge={
<Box
backgroundColor={Color.errorDefault}
borderRadius={BorderRadius.full}
style={{ width: 16, height: 16 }}
/>
}
>
<Box
backgroundColor={Color.infoDefault}
style={{ width: 40, height: 80 }}
/>
</BadgeWrapper>
</Box>
);

View File

@ -0,0 +1,143 @@
/* eslint-disable jest/require-top-level-describe */
import { render } from '@testing-library/react';
import React from 'react';
import { BadgeWrapper } from './badge-wrapper';
import {
BadgeWrapperPosition,
BadgeWrapperAnchorElementShape,
} from './badge-wrapper.types';
describe('BadgeWrapper', () => {
it('should render correctly', () => {
const { getByText, container } = render(
<BadgeWrapper badge={<div data-testid="badge">badge</div>}>
content
</BadgeWrapper>,
);
expect(getByText('content')).toBeDefined();
expect(getByText('badge')).toBeDefined();
expect(container).toMatchSnapshot();
});
it('should render with additional className', () => {
const { getByTestId } = render(
<BadgeWrapper data-testid="badge-wrapper" className="test-class">
content
</BadgeWrapper>,
);
expect(getByTestId('badge-wrapper')).toHaveClass('test-class');
});
it('should render badge positions correctly', () => {
const { getByTestId, getByText } = render(
<>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-default' }}
>
content default
</BadgeWrapper>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-top-right' }}
position={BadgeWrapperPosition.topRight}
>
content top-right
</BadgeWrapper>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-top-left' }}
position={BadgeWrapperPosition.topLeft}
>
content top-left
</BadgeWrapper>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-bottom-right' }}
position={BadgeWrapperPosition.bottomRight}
>
content bottom-right
</BadgeWrapper>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-bottom-left' }}
position={BadgeWrapperPosition.bottomLeft}
>
content bottom-left
</BadgeWrapper>
</>,
);
expect(getByText('content default')).toBeDefined();
expect(getByTestId('badge-default')).toHaveClass(
'mm-badge-wrapper__badge-container--circular-top-right',
);
expect(getByText('content top-right')).toBeDefined();
expect(getByTestId('badge-top-right')).toHaveClass(
'mm-badge-wrapper__badge-container--circular-top-right',
);
expect(getByText('content top-left')).toBeDefined();
expect(getByTestId('badge-top-left')).toHaveClass(
'mm-badge-wrapper__badge-container--circular-top-left',
);
expect(getByText('content bottom-right')).toBeDefined();
expect(getByTestId('badge-bottom-right')).toHaveClass(
'mm-badge-wrapper__badge-container--circular-bottom-right',
);
expect(getByText('content bottom-left')).toBeDefined();
expect(getByTestId('badge-bottom-left')).toHaveClass(
'mm-badge-wrapper__badge-container--circular-bottom-left',
);
});
it('should render the badge with custom position', () => {
const { getByTestId, getByText } = render(
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-custom' }}
position={{
top: -10,
right: -10,
}}
>
content custom
</BadgeWrapper>,
);
expect(getByText('content custom')).toBeDefined();
expect(getByTestId('badge-custom')).not.toHaveClass(
'mm-badge-wrapper__badge-container--circular-top-right',
);
expect(getByTestId('badge-custom')).toHaveStyle({
top: -10,
right: -10,
});
});
it('should render badge anchor element shape correctly', () => {
const { getByTestId, getByText } = render(
<>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-circular' }}
>
content circular
</BadgeWrapper>
<BadgeWrapper
badge={<div>badge</div>}
badgeContainerProps={{ 'data-testid': 'badge-rectangular' }}
anchorElementShape={BadgeWrapperAnchorElementShape.rectangular}
>
content rectangular
</BadgeWrapper>
</>,
);
expect(getByText('content circular')).toBeDefined();
expect(getByTestId('badge-circular')).toHaveClass(
'mm-badge-wrapper__badge-container--circular-top-right',
);
expect(getByText('content rectangular')).toBeDefined();
expect(getByTestId('badge-rectangular')).toHaveClass(
'mm-badge-wrapper__badge-container--rectangular-top-right',
);
});
});

View File

@ -0,0 +1,44 @@
import React from 'react';
import classnames from 'classnames';
import { DISPLAY } from '../../../helpers/constants/design-system';
import Box from '../../ui/box';
import {
BadgeWrapperPosition,
BadgeWrapperAnchorElementShape,
BadgeWrapperProps,
} from './badge-wrapper.types';
export const BadgeWrapper = ({
children,
badge,
badgeContainerProps,
position = BadgeWrapperPosition.topRight,
positionObj,
anchorElementShape = BadgeWrapperAnchorElementShape.circular,
className = '',
color,
...props
}: BadgeWrapperProps) => (
<Box
className={classnames('mm-badge-wrapper', className)}
display={DISPLAY.INLINE_BLOCK}
{...props}
>
{/* Generally the AvatarAccount or AvatarToken */}
{children}
<Box
className={classnames('mm-badge-wrapper__badge-container', {
[`mm-badge-wrapper__badge-container--${anchorElementShape}-${position}`]:
!positionObj,
})}
style={{ ...positionObj }}
{...badgeContainerProps}
>
{/* Generally the AvatarNetwork at SIZES.XS */}
{badge}
</Box>
</Box>
);

View File

@ -0,0 +1,55 @@
import PropTypes from 'prop-types';
import Box from '../../ui/box';
import type { BoxProps } from '../../ui/box/box.d';
export enum BadgeWrapperPosition {
topRight = 'top-right',
bottomRight = 'bottom-right',
topLeft = 'top-left',
bottomLeft = 'bottom-left',
}
export enum BadgeWrapperAnchorElementShape {
rectangular = 'rectangular',
circular = 'circular',
}
export interface BadgeWrapperProps
extends PropTypes.InferProps<typeof Box.propTypes>,
React.HTMLAttributes<HTMLDivElement> {
/**
* The element to be wrapped by the BadgeWrapper and for the badge to be positioned on top of
*/
children: React.ReactNode;
/**
* Use the `badge` prop to define the badge component to be rendered on top of the `children` component
*/
badge?: React.ReactNode;
/**
* The BadgeWrapper props of the component. All Box props can be used
*/
badgeContainerProps?: BoxProps;
/**
* The position of the Badge. Possible values could be 'BadgeWrapperPosition.topRight', 'BadgeWrapperPosition.bottomRight','BadgeWrapperPosition.topLeft', 'BadgeWrapperPosition.bottomLeft'
* Defaults to 'BadgeWrapperPosition.topRight'
*/
position?: BadgeWrapperPosition;
/**
* The positionObj can be used to override the default positioning of the badge it accepts an object with the following keys { top, right, bottom, left }
*/
positionObj?: {
top?: number;
right?: number;
bottom?: number;
left?: number;
};
/**
* The shape of the anchor element. Possible values could be 'BadgeWrapperAnchorElementShape.circular', 'BadgeWrapperAnchorElementShape.square'
* Defaults to
*/
anchorElementShape?: BadgeWrapperAnchorElementShape;
/**
* Additional classNames to be added to the BadgeWrapper component
*/
className?: string;
}

View File

@ -0,0 +1,7 @@
export { BadgeWrapper } from './badge-wrapper';
export {
BadgeWrapperPosition,
BadgeWrapperAnchorElementShape,
} from './badge-wrapper.types';
export type { BadgeWrapperProps } from './badge-wrapper.types';

View File

@ -14,7 +14,7 @@
@import 'avatar-favicon/avatar-favicon';
@import 'avatar-network/avatar-network';
@import 'avatar-token/avatar-token';
@import 'avatar-with-badge/avatar-with-badge';
@import 'badge-wrapper/badge-wrapper';
@import 'button-base/button-base';
@import 'button-icon/button-icon';
@import 'button-link/button-link';

View File

@ -9,9 +9,10 @@ export { AvatarIcon, AVATAR_ICON_SIZES } from './avatar-icon';
export { AvatarNetwork, AVATAR_NETWORK_SIZES } from './avatar-network';
export { AvatarToken } from './avatar-token';
export {
AvatarWithBadge,
AVATAR_WITH_BADGE_POSTIONS,
} from './avatar-with-badge';
BadgeWrapper,
BadgeWrapperPosition,
BadgeWrapperAnchorElementShape,
} from './badge-wrapper';
export { AvatarBase } from './avatar-base';
export { Button, BUTTON_TYPES, BUTTON_SIZES } from './button';
export { ButtonBase, BUTTON_BASE_SIZES } from './button-base';