Skip to content

Commit

Permalink
[DSRN] Added MVP Blockies (#455)
Browse files Browse the repository at this point in the history
<!--
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 MVP version of `Blockies` by using `metamask-mobile`'s
version of `Blockies` under the hood. In the future, `Blockies` will be
recreated to align across platforms


<!--
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: #424 

## **Manual testing steps**

1. Run `yarn storybook:ios` from root
2. Go to Primitives > Jazzicon
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/389bcb7d-71c7-4a1d-95fc-a8eae73fd5f2

<!-- [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.
  • Loading branch information
brianacnguyen authored Feb 28, 2025
1 parent 64c05d9 commit e95866a
Show file tree
Hide file tree
Showing 9 changed files with 693 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const getStories = () => {
"./../../packages/design-system-react-native/src/components/Text/Text.stories.tsx": require("../../../packages/design-system-react-native/src/components/Text/Text.stories.tsx"),
"./../../packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx": require("../../../packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx"),
"./../../packages/design-system-react-native/src/primitives/AvatarBase/AvatarBase.stories.tsx": require("../../../packages/design-system-react-native/src/primitives/AvatarBase/AvatarBase.stories.tsx"),
"./../../packages/design-system-react-native/src/primitives/Blockies/Blockies.stories.tsx": require("../../../packages/design-system-react-native/src/primitives/Blockies/Blockies.stories.tsx"),
"./../../packages/design-system-react-native/src/primitives/ButtonAnimated/ButtonAnimated.stories.tsx": require("../../../packages/design-system-react-native/src/primitives/ButtonAnimated/ButtonAnimated.stories.tsx"),
"./../../packages/design-system-react-native/src/primitives/ButtonBase/ButtonBase.stories.tsx": require("../../../packages/design-system-react-native/src/primitives/ButtonBase/ButtonBase.stories.tsx"),
"./../../packages/design-system-react-native/src/primitives/ImageOrSvg/ImageOrSvg.stories.tsx": require("../../../packages/design-system-react-native/src/primitives/ImageOrSvg/ImageOrSvg.stories.tsx"),
Expand Down
8 changes: 8 additions & 0 deletions packages/design-system-react-native/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export {
AvatarIconSize,
} from './components/AvatarIcon';

import BlockiesComponent from './primitives/Blockies';
export const Blockies = BlockiesComponent;
export { BlockiesProps } from './primitives/Blockies';

import ButtonAnimatedComponent from './primitives/ButtonAnimated';
export const ButtonAnimated = withThemeProvider(ButtonAnimatedComponent);
export { ButtonAnimatedProps } from './primitives/ButtonAnimated';
Expand All @@ -32,6 +36,10 @@ import IconComponent from './components/Icon';
export const Icon = withThemeProvider(IconComponent);
export { IconColor, IconName, IconProps, IconSize } from './components/Icon';

import JazziconComponent from './primitives/Jazzicon';
export const Jazzicon = JazziconComponent;
export { JazziconProps } from './primitives/Jazzicon';

import TextButtonComponent from './components/TextButton';
export const TextButton = withThemeProvider(TextButtonComponent);
export { TextButtonProps } from './components/TextButton';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { View } from 'react-native';

import Blockies from './Blockies';
import type { BlockiesProps } from './Blockies.types';

const meta: Meta<BlockiesProps> = {
title: 'Primitives/Blockies',
component: Blockies,
argTypes: {
size: {
control: 'number',
},
},
};

export default meta;
type Story = StoryObj<BlockiesProps>;
const sampleAccountAddresses = [
'0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8',
'0xb9b81f6bd23B953c5257C3b5E2F0c03B07E944eB',
'0x360507dfEC4Bf0c03495f91154A78C672599F308',
'0x50cA820Ff810F7687E7d0aDb23A830e3ac6032C3',
'0x840C9Eb73729E626673714D6E4dA8afc8Ccc90d3',
'0xCA0361BE89B7d47a6233d1875F0727ddeAB23377',
'0xD78CBcA88eCd65c6128511e46a518CDc6c66fC74',
'0xCFc8b1d1031ef2ecce3a98d5D79ce4D75Edb06bA',
'0xDe53fa2E659b6134991bB56F64B691990e5C44E7',
'0x8AceA3A9748294d1B5C25a08EFE37b756AafDFdd',
'0xEC5CE72f2e18B0017C88F7B12d3308119C5Cf129',
'0xeC56Da21c90Af6b50E4Ba5ec252bD97e735290fc',
];
export const Default: Story = {
args: {
size: 32,
},
render: (args) => {
return <Blockies {...args} address={sampleAccountAddresses[0]} />;
},
};

export const SampleAddresses: Story = {
render: () => (
<View style={{ gap: 16 }}>
{sampleAccountAddresses.map((addressKey) => (
<Blockies address={addressKey} key={addressKey} size={32} />
))}
</View>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Blockies.test.tsx
import React from 'react';
import { render } from '@testing-library/react-native';
import renderer from 'react-test-renderer';

// @ts-ignore
import { toDataUrl } from './Blockies.utilities';
import Blockies from './Blockies';

// Mock the toDataUrl utility
jest.mock('./Blockies.utilities', () => ({
toDataUrl: jest.fn(() => 'data:image/png;base64,mockedBlockyImage'),
}));

describe('Blockies Component', () => {
beforeEach(() => {
(toDataUrl as jest.Mock).mockClear();
});

it('renders with default size (32) if size is not provided', () => {
const { getByTestId } = render(
<Blockies address="0x123" testID="blockies" />,
);
// toDataUrl should have been called with the address
expect(toDataUrl).toHaveBeenCalledWith('0x123');
// Verify the returned Image has width & height of 32
const image = getByTestId('blockies');
expect(image.props.width).toBe(32);
expect(image.props.height).toBe(32);
});

it('renders with a custom size', () => {
const { getByTestId } = render(
<Blockies address="0x123" size={64} testID="blockies" />,
);
const image = getByTestId('blockies');
expect(image.props.width).toBe(64);
expect(image.props.height).toBe(64);
});

it('passes additional image props correctly', () => {
const { getByTestId } = render(
<Blockies
address="0xabc"
size={40}
resizeMode="contain"
style={{ margin: 10 }}
testID="blockies"
/>,
);
const image = getByTestId('blockies');
expect(image.props.resizeMode).toBe('contain');
expect(image.props.style).toMatchObject({ margin: 10 });
});

it('calls toDataUrl with the correct address', () => {
render(<Blockies address="0xabc" testID="blockies" />);
expect(toDataUrl).toHaveBeenCalledWith('0xabc');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import React from 'react';
import { Image } from 'react-native';

// @ts-ignore
import { toDataUrl } from './Blockies.utilities';

import type { BlockiesProps } from './Blockies.types';

const Blockies = ({ address, size = 32, ...imageProps }: BlockiesProps) => {
return (
<Image
source={{ uri: toDataUrl(address) }}
width={size}
height={size}
{...imageProps}
/>
);
};

export default Blockies;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ImageProps } from 'react-native';

/**
* Blockies component props.
*/
export type BlockiesProps = {
/**
* Required address used as a unique identifier to generate the Blockies.
*/
address: string;
/**
* Optional prop to control the size of the Blockies.
*/
size?: number;
} & Omit<ImageProps, 'source' | 'width' | 'height'>;
Loading

0 comments on commit e95866a

Please sign in to comment.