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

chore: added isScaled option to BadgeNetwork to allow nonscaled usage #10321

Merged
merged 5 commits into from
Jul 17, 2024
Merged
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 @@ -9,6 +9,7 @@ exports[`Badge should render badge network given the badge network variant 1`] =
},
}
}
isScaled={true}
name="Ethereum"
testID="badge-badgenetwork"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { BadgeNetworkProps } from './BadgeNetwork.types';

// Test IDs
export const BADGE_NETWORK_TEST_ID = 'badge-network';
export const BADGENETWORK_TEST_ID = 'badgenetwork';

// Defaults
export const DEFAULT_BADGENETWORK_NETWORKICON_SIZE = AvatarSize.Md;
Expand All @@ -22,4 +22,5 @@ const SAMPLE_BADGENETWORK_IMAGESOURCE = SAMPLE_AVATARNETWORK_IMAGESOURCE_LOCAL;
export const SAMPLE_BADGENETWORK_PROPS: BadgeNetworkProps = {
name: SAMPLE_BADGENETWORK_NAME,
imageSource: SAMPLE_BADGENETWORK_IMAGESOURCE,
isScaled: true,
};
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
/* eslint-disable react-native/no-inline-styles */
/* eslint-disable react/display-name */
// Third party dependencies
brianacnguyen marked this conversation as resolved.
Show resolved Hide resolved
import React from 'react';
import { View } from 'react-native';
import { Meta, Story } from '@storybook/react-native';

// Internal dependencies.
import { default as BadgeNetworkComponent } from './BadgeNetwork';
import { SAMPLE_BADGENETWORK_PROPS } from './BadgeNetwork.constants';
import BadgeNetwork from './BadgeNetwork';
import { BadgeNetworkProps } from './BadgeNetwork.types';
import { SAMPLE_BADGENETWORK_PROPS } from './BadgeNetwork.constants';

const BadgeNetworkMeta = {
title: 'Component Library / Badges',
component: BadgeNetworkComponent,
export default {
title: 'Component Library / Badges / BadgeNetwork',
component: BadgeNetwork,
argTypes: {
name: {
control: { type: 'text' },
defaultValue: SAMPLE_BADGENETWORK_PROPS.name,
},
name: { control: 'text' },
isScaled: { control: 'boolean' },
},
} as Meta;

const customRender = (args: BadgeNetworkProps) => (
<View
style={{
height: 50,
width: 50,
}}
>
<BadgeNetwork
{...args}
imageSource={SAMPLE_BADGENETWORK_PROPS.imageSource}
/>
</View>
);

const Template: Story<BadgeNetworkProps> = (args) => customRender(args);

export const Default = Template.bind({});
Default.args = {
...SAMPLE_BADGENETWORK_PROPS,
};

export const Scaled = Template.bind({});
Scaled.args = {
...SAMPLE_BADGENETWORK_PROPS,
isScaled: true,
};
export default BadgeNetworkMeta;

export const BadgeNetwork = {
render: (args: JSX.IntrinsicAttributes & BadgeNetworkProps) => (
<View
style={{
height: 50,
width: 50,
}}
>
<BadgeNetworkComponent
{...args}
imageSource={SAMPLE_BADGENETWORK_PROPS.imageSource}
/>
</View>
),
export const NotScaled = Template.bind({});
NotScaled.args = {
...SAMPLE_BADGENETWORK_PROPS,
isScaled: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { StyleSheet, ViewStyle } from 'react-native';

// External dependencies.
import { Theme } from '../../../../../../util/theme/models';
import { AvatarSize } from '../../../../Avatars/Avatar';

// Internal dependencies.
import { BadgeNetworkStyleSheetVars } from './BadgeNetwork.types';
import getScaledStyles from './BadgeNetwork.utils';

/**
* Style sheet function for BadgeNetwork component.
Expand All @@ -21,53 +21,43 @@ const styleSheet = (params: {
vars: BadgeNetworkStyleSheetVars;
}) => {
const { theme, vars } = params;
const { style, containerSize, size } = vars;
/**
* Design Requirements:
* - The Network Badge needs to be 1/2 the height of its content, up to 1/2 the height
* of the largest AvatarSize.
* - It needs to have a 1px stroke on a 16px badge.
* (Current) Solution:
* - Use invisible base wrapper and set height to 50% to get the 1/2 height measurement.
* - Scale content to a scale ratio based on the container size's height.
* - Set borderWidth to scale with given Network Icon size (always given with default).
*/
const badgeToContentScaleRatio = 0.5;
const borderWidthRatio = 1 / 16;
const borderWidth = Number(size) * borderWidthRatio;
const currentAvatarSizes = Object.values(AvatarSize).map((avatarSize) =>
Number(avatarSize),
);
const currentSmallestAvatarSize = Math.min(...currentAvatarSizes);
const currentLargestAvatarSize = Math.max(...currentAvatarSizes);
let scaleRatio = 1;
brianacnguyen marked this conversation as resolved.
Show resolved Hide resolved
const { style, containerSize, size, isScaled } = vars;

let opacity = 0;

if (containerSize) {
scaleRatio = containerSize.height / Number(size);
// This is so that the BadgeNetwork won't be visible until a containerSize is known
opacity = 1;
}

return StyleSheet.create({
base: {
minHeight: currentSmallestAvatarSize * badgeToContentScaleRatio,
maxHeight: currentLargestAvatarSize * badgeToContentScaleRatio,
height: `${(badgeToContentScaleRatio * 100).toString()}%`,
aspectRatio: 1,
let baseStyles = {};
let networkIconStyles = {};

if (isScaled) {
const scaledStyles = getScaledStyles(Number(size), containerSize);
baseStyles = {
alignItems: 'center',
justifyContent: 'center',
minHeight: scaledStyles.minHeight,
maxHeight: scaledStyles.maxHeight,
height: scaledStyles.height,
aspectRatio: 1,
opacity,
},
};
networkIconStyles = {
transform: [{ scale: scaledStyles.scaleRatio }],
borderWidth: scaledStyles.borderWidth,
borderColor: theme.colors.background.default,
...theme.shadows.size.xs,
};
}

return StyleSheet.create({
base: baseStyles,
networkIcon: Object.assign(
isScaled ? networkIconStyles : {},
{
/**
* This is to make sure scale the Network Icon.
* If the BadgeNetwork needs to have style changes specifically with dimensions,
* set transform to [{scale: 1}] first
*/
transform: [{ scale: scaleRatio }],
borderWidth,
borderWidth: Number(size) * (1 / 16),
borderColor: theme.colors.background.default,
...theme.shadows.size.xs,
} as ViewStyle,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,82 @@
// Third party dependencies.
// Third party dependencies
import React from 'react';
import { shallow } from 'enzyme';
import { View } from 'react-native';
import { render } from '@testing-library/react-native';

// External dependencies.
import {
SAMPLE_AVATARNETWORK_NAME,
SAMPLE_AVATARNETWORK_IMAGESOURCE_LOCAL,
} from '../../../../Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.constants';
// External dependencies
import { AVATARNETWORK_IMAGE_TESTID } from '../../../../Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.constants';
import { AvatarSize } from '../../../../Avatars/Avatar';

// Internal dependencies.
// Internal dependencies
import BadgeNetwork from './BadgeNetwork';
import { BADGE_NETWORK_TEST_ID } from './BadgeNetwork.constants';
import {
BADGENETWORK_TEST_ID,
SAMPLE_BADGENETWORK_PROPS,
} from './BadgeNetwork.constants';
import getScaledStyles from './BadgeNetwork.utils';

describe('BadgeNetwork', () => {
const renderComponent = (props = {}) =>
render(<BadgeNetwork {...SAMPLE_BADGENETWORK_PROPS} {...props} />);

it('should render BadgeNetwork', () => {
const { toJSON, queryByTestId } = renderComponent();
expect(toJSON()).toMatchSnapshot();
expect(queryByTestId(BADGENETWORK_TEST_ID)).not.toBe(null);
});

describe('BadgeNetwork - snapshots', () => {
it('should render badge network correctly', () => {
const wrapper = shallow(
<BadgeNetwork
name={SAMPLE_AVATARNETWORK_NAME}
imageSource={SAMPLE_AVATARNETWORK_IMAGESOURCE_LOCAL}
/>,
it('should render with correct image source', () => {
const { getByTestId } = renderComponent();
const imgElement = getByTestId(AVATARNETWORK_IMAGE_TESTID);
expect(imgElement.props.source).toEqual(
SAMPLE_BADGENETWORK_PROPS.imageSource,
);
expect(wrapper).toMatchSnapshot();
});
});

describe('BadgeNetwork', () => {
it('should render badge network with the given content', () => {
const wrapper = shallow(
<BadgeNetwork
name={SAMPLE_AVATARNETWORK_NAME}
imageSource={SAMPLE_AVATARNETWORK_IMAGESOURCE_LOCAL}
/>,
it('should apply scaled style only when isScaled is true', () => {
const containerSize = {
height: 32,
width: 32,
};
const sampleSize = AvatarSize.Md;
const { getByTestId } = render(
<View style={containerSize}>
<BadgeNetwork
{...SAMPLE_BADGENETWORK_PROPS}
isScaled
size={sampleSize}
/>
</View>,
);
const scaledStyled = getScaledStyles(Number(sampleSize), containerSize);
const badgeNetworkElement = getByTestId(BADGENETWORK_TEST_ID);
expect(badgeNetworkElement.props.style.minHeight).toBe(
scaledStyled.minHeight,
);
expect(badgeNetworkElement.props.style.maxHeight).toBe(
scaledStyled.maxHeight,
);
expect(badgeNetworkElement.props.style.height).toBe(scaledStyled.height);
});

const contentElement = wrapper.findWhere(
(node) => node.prop('testID') === BADGE_NETWORK_TEST_ID,
it('should not apply scaled style when isScaled is false', () => {
const containerSize = {
height: 32,
width: 32,
};
const sampleSize = AvatarSize.Md;
const { getByTestId } = render(
<View style={containerSize}>
<BadgeNetwork
{...SAMPLE_BADGENETWORK_PROPS}
isScaled={false}
size={sampleSize}
/>
</View>,
);
expect(contentElement.exists()).toBe(true);
const badgeNetworkElement = getByTestId(BADGENETWORK_TEST_ID);
expect(badgeNetworkElement.props.style.minHeight).not.toBeDefined();
expect(badgeNetworkElement.props.style.maxHeight).not.toBeDefined();
expect(badgeNetworkElement.props.style.height).not.toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Avatar, { AvatarVariant } from '../../../../Avatars/Avatar';
import { BadgeNetworkProps } from './BadgeNetwork.types';
import styleSheet from './BadgeNetwork.styles';
import {
BADGE_NETWORK_TEST_ID,
BADGENETWORK_TEST_ID,
DEFAULT_BADGENETWORK_NETWORKICON_SIZE,
} from './BadgeNetwork.constants';

Expand All @@ -21,18 +21,20 @@ const BadgeNetwork = ({
name,
imageSource,
size = DEFAULT_BADGENETWORK_NETWORKICON_SIZE,
isScaled = true,
}: BadgeNetworkProps) => {
const { size: containerSize, onLayout: onLayoutContainerSize } =
useComponentSize();
const { styles } = useStyles(styleSheet, {
style,
containerSize,
size,
isScaled,
});
return (
<BadgeBase
style={styles.base}
testID={BADGE_NETWORK_TEST_ID}
testID={BADGENETWORK_TEST_ID}
onLayout={onLayoutContainerSize}
>
<Avatar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ import { AvatarNetworkProps } from '../../../../Avatars/Avatar/variants/AvatarNe
*/
export interface BadgeNetworkProps
extends Omit<BadgeBaseProps, 'children'>,
AvatarNetworkProps {}
AvatarNetworkProps {
/**
* Optional prop to control whether the Badge should be scaled to the content.
* @default true
*/
isScaled?: boolean;
}

/**
* Style sheet BadgeNetwork parameters.
*/
export type BadgeNetworkStyleSheetVars = Pick<
BadgeNetworkProps,
'style' | 'size'
'style' | 'size' | 'isScaled'
> & {
containerSize: { width: number; height: number } | null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AvatarSize } from '../../../../Avatars/Avatar';

const getScaledStyles = (
size: number,
containerSize: { width: number; height: number } | null,
) => {
/**
* Design Requirements for Scaled BadgeNetwork:
* - The Network Badge needs to be 1/2 the height of its content, up to 1/2 the height
* of the largest AvatarSize.
* - It needs to have a 1px stroke on a 16px badge.
* (Current) Solution:
* - Use invisible base wrapper and set height to 50% to get the 1/2 height measurement.
* - Scale content to a scale ratio based on the container size's height.
* - Set borderWidth to scale with given Network Icon size (always given with default).
*/
const badgeToContentScaleRatio = 0.5;
const borderWidthRatio = 1 / 16;
const borderWidth = Number(size) * borderWidthRatio;

const currentAvatarSizes = Object.values(AvatarSize).map((avatarSize) =>
Number(avatarSize),
);
const smallestAvatarSize = Math.min(...currentAvatarSizes);
const largestAvatarSize = Math.max(...currentAvatarSizes);

let scaleRatio = 1;

if (containerSize) {
scaleRatio = containerSize.height / Number(size);
}

return {
minHeight: smallestAvatarSize * badgeToContentScaleRatio,
maxHeight: largestAvatarSize * badgeToContentScaleRatio,
height: `${(badgeToContentScaleRatio * 100).toString()}%`,
scaleRatio,
borderWidth,
};
};

export default getScaledStyles;
Loading
Loading