Skip to content

Commit dcb69ed

Browse files
Merge branch 'main' into dsrn/badge-count
2 parents 49e0a51 + 7514f25 commit dcb69ed

File tree

18 files changed

+851
-8
lines changed

18 files changed

+851
-8
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const getStories = () => {
5454
"./../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx"),
5555
"./../../packages/design-system-react-native/src/components/AvatarToken/AvatarToken.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarToken/AvatarToken.stories.tsx"),
5656
"./../../packages/design-system-react-native/src/components/BadgeCount/BadgeCount.stories.tsx": require("../../../packages/design-system-react-native/src/components/BadgeCount/BadgeCount.stories.tsx"),
57+
"./../../packages/design-system-react-native/src/components/BadgeNetwork/BadgeNetwork.stories.tsx": require("../../../packages/design-system-react-native/src/components/BadgeNetwork/BadgeNetwork.stories.tsx"),
58+
"./../../packages/design-system-react-native/src/components/BadgeStatus/BadgeStatus.stories.tsx": require("../../../packages/design-system-react-native/src/components/BadgeStatus/BadgeStatus.stories.tsx"),
5759
"./../../packages/design-system-react-native/src/components/Button/Button.stories.tsx": require("../../../packages/design-system-react-native/src/components/Button/Button.stories.tsx"),
5860
"./../../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"),
5961
"./../../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"),
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { Meta, StoryObj } from '@storybook/react-native';
2+
import { ImageSourcePropType, View } from 'react-native';
3+
4+
import { SAMPLE_AVATARNETWORK_URIS } from '../AvatarNetwork/AvatarNetwork.constants';
5+
import BadgeNetwork from './BadgeNetwork';
6+
import type { BadgeNetworkProps } from './BadgeNetwork.types';
7+
8+
const meta: Meta<BadgeNetworkProps> = {
9+
title: 'Components/BadgeNetwork',
10+
component: BadgeNetwork,
11+
argTypes: {
12+
twClassName: {
13+
control: 'text',
14+
},
15+
},
16+
};
17+
18+
export default meta;
19+
20+
type Story = StoryObj<BadgeNetworkProps>;
21+
const storyImageSource: ImageSourcePropType = {
22+
uri: 'https://cryptologos.cc/logos/ethereum-eth-logo.svg',
23+
};
24+
25+
export const Default: Story = {
26+
args: {
27+
twClassName: '',
28+
},
29+
render: (args) => <BadgeNetwork {...args} src={storyImageSource} />,
30+
};
31+
32+
export const SampleNetworks: Story = {
33+
render: () => (
34+
<View style={{ gap: 16 }}>
35+
{SAMPLE_AVATARNETWORK_URIS.map((networkUri) => (
36+
<BadgeNetwork
37+
src={{
38+
uri: networkUri,
39+
}}
40+
key={networkUri}
41+
/>
42+
))}
43+
</View>
44+
),
45+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// BadgeNetwork.test.tsx
2+
3+
import React from 'react';
4+
import { render } from '@testing-library/react-native';
5+
import BadgeNetwork from './BadgeNetwork';
6+
import { AvatarNetworkSize } from '../AvatarNetwork';
7+
8+
const remoteImageSrc = { uri: 'https://example.com/photo.png' };
9+
describe('BadgeNetwork', () => {
10+
it('renders an AvatarNetwork with size forced to Xs and forwards additional props', () => {
11+
const { getByTestId } = render(
12+
<BadgeNetwork
13+
src={remoteImageSrc}
14+
testID="badge-network"
15+
imageProps={{ testID: 'image-or-svg' }}
16+
/>,
17+
);
18+
const renderedComponent = getByTestId('badge-network');
19+
expect(renderedComponent).toBeTruthy();
20+
expect(renderedComponent.props.style[0].height.toString()).toStrictEqual(
21+
(Number(AvatarNetworkSize.Xs) + 2).toString(),
22+
);
23+
24+
expect(renderedComponent.props.children[1].props.src).toStrictEqual(
25+
remoteImageSrc,
26+
);
27+
});
28+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
2+
import React from 'react';
3+
4+
import AvatarNetwork, { AvatarNetworkSize } from '../AvatarNetwork';
5+
import type { BadgeNetworkProps } from './BadgeNetwork.types';
6+
7+
const BadgeNetwork = ({
8+
name,
9+
fallbackText,
10+
fallbackTextProps,
11+
...props
12+
}: BadgeNetworkProps) => (
13+
<AvatarNetwork
14+
name={name}
15+
fallbackText={fallbackText}
16+
fallbackTextProps={fallbackTextProps}
17+
{...props}
18+
size={AvatarNetworkSize.Xs}
19+
hasSolidBackgroundColor
20+
hasBorder
21+
/>
22+
);
23+
24+
export default BadgeNetwork;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { AvatarNetworkProps } from '../AvatarNetwork';
2+
3+
export type BadgeNetworkProps = Omit<AvatarNetworkProps, 'size' | 'shape'>;
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# BadgeNetwork
2+
3+
The `BadgeNetwork` component is reserved for badged representing networks. It provides a fallback mechanism in case of an image load failure, ensuring a graceful degradation by displaying either an alternative text or a fallback icon.
4+
5+
---
6+
7+
## Props
8+
9+
The `BadgeNetwork` component accepts the following props:
10+
11+
### `src` (Required)
12+
13+
The source of the image or SVG. It determines whether a local image, a local SVG component, or a remote image/SVG (via URI) is rendered.
14+
15+
| TYPE | REQUIRED | DEFAULT |
16+
| :------------------------------------------------------ | :------- | :------ |
17+
| `number \| ComponentType<SvgProps> \| { uri?: string }` | Yes | `N/A` |
18+
19+
---
20+
21+
### `name` (Optional)
22+
23+
Used to generate fallback text when the image or SVG fails to load.
24+
25+
| TYPE | REQUIRED | DEFAULT |
26+
| :------- | :------- | :---------- |
27+
| `string` | No | `undefined` |
28+
29+
---
30+
31+
### `fallbackText` (Optional)
32+
33+
Custom fallback text displayed when the image fails to load.
34+
35+
| TYPE | REQUIRED | DEFAULT |
36+
| :------- | :------- | :---------- |
37+
| `string` | No | `name?.[0]` |
38+
39+
---
40+
41+
### `fallbackTextProps` (Optional)
42+
43+
Additional props for customizing the fallback text.
44+
45+
| TYPE | REQUIRED | DEFAULT |
46+
| :------- | :------- | :------ |
47+
| `object` | No | `{}` |
48+
49+
---
50+
51+
### `imageProps` (Optional)
52+
53+
Additional properties for the image component.
54+
55+
| TYPE | REQUIRED | DEFAULT |
56+
| :----------- | :------- | :-------------------------- |
57+
| `ImageProps` | No | `{ resizeMode: 'contain' }` |
58+
59+
---
60+
61+
### `onImageError` (Optional)
62+
63+
Callback function triggered when the image fails to load.
64+
65+
| TYPE | REQUIRED | DEFAULT |
66+
| :------------------------------------------------------- | :------- | :---------- |
67+
| `(e: NativeSyntheticEvent<ImageErrorEventData>) => void` | No | `undefined` |
68+
69+
---
70+
71+
### `onSvgError` (Optional)
72+
73+
Callback function triggered when the SVG fails to load.
74+
75+
| TYPE | REQUIRED | DEFAULT |
76+
| :----------------- | :------- | :---------- |
77+
| `(e: any) => void` | No | `undefined` |
78+
79+
---
80+
81+
### Other Props
82+
83+
`BadgeNetwork` supports all other props from [`AvatarNetwork`](#) and [`ImageOrSvg`](#), such as:
84+
85+
- **`twClassName`** – Tailwind class names for styling.
86+
- **`testID`** – Identifier used for testing purposes.
87+
- **`style`** – Custom styles for the Badge container.
88+
89+
---
90+
91+
## Accessibility
92+
93+
To ensure proper accessibility, the following React Native accessibility props can be passed:
94+
95+
- **`accessibilityLabel`**: Describes the Badge for screen readers.
96+
- **`accessible`**: Set to `true` if the Badge represents meaningful content.
97+
98+
---
99+
100+
## Usage
101+
102+
### Basic Usage
103+
104+
```tsx
105+
import React from 'react';
106+
import BadgeNetwork from '@metamask/design-system-react-native/badge-network';
107+
108+
const App = () => (
109+
<BadgeNetwork
110+
name="MetaMask"
111+
source={{ uri: 'https://example.com/network.png' }}
112+
/>
113+
);
114+
115+
export default App;
116+
```
117+
118+
---
119+
120+
### Handling Image Errors
121+
122+
```tsx
123+
import React from 'react';
124+
import BadgeNetwork from '@metamask/design-system-react-native/badge-network';
125+
126+
const handleError = () => {
127+
console.log('Image failed to load');
128+
};
129+
130+
const App = () => (
131+
<BadgeNetwork
132+
name="ETH"
133+
source={{ uri: 'https://invalid-url.com' }}
134+
onImageError={handleError}
135+
/>
136+
);
137+
138+
export default App;
139+
```
140+
141+
---
142+
143+
## Notes
144+
145+
- **Fallback Mechanism:**
146+
If an image or SVG fails to load, the component falls back to displaying text derived from the `name` prop.
147+
- **Customization:**
148+
Supports various props for shape, size, and additional styling.
149+
150+
- **Extensibility:**
151+
Any additional `ImageOrSvg` props are forwarded for greater flexibility.
152+
153+
---
154+
155+
## Contributing
156+
157+
1. Add tests for any new features or modifications.
158+
2. Update this README to reflect any changes in the API.
159+
3. Follow the project's coding guidelines and best practices.
160+
161+
---
162+
163+
For further details, refer to the [React Native documentation](https://reactnative.dev/docs/image).
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default } from './BadgeNetwork';
2+
export type { BadgeNetworkProps } from './BadgeNetwork.types';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { BadgeStatusStatus, BadgeStatusSize } from './BadgeStatus.types';
2+
3+
// Mappings
4+
export const TWCLASSMAP_BADGESTATUS_STATUS_CIRCLE: Record<
5+
BadgeStatusStatus,
6+
string
7+
> = {
8+
[BadgeStatusStatus.Active]: 'bg-success-default border-success-default',
9+
[BadgeStatusStatus.PartiallyActive]:
10+
'bg-background-default border-success-default',
11+
[BadgeStatusStatus.Inactive]: 'bg-icon-muted border-icon-muted',
12+
[BadgeStatusStatus.New]: 'bg-primary-default border-primary-default',
13+
[BadgeStatusStatus.Attention]: 'bg-error-default border-error-default',
14+
};
15+
16+
export const TWCLASSMAP_BADGESTATUS_SIZE: Record<BadgeStatusSize, string> = {
17+
[BadgeStatusSize.Md]: 'h-2 w-2', // 8px width and height
18+
[BadgeStatusSize.Lg]: 'h-2.5 w-2.5', // 10px width and height
19+
};

0 commit comments

Comments
 (0)