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

Updating IconWithFallback to functional component, adding stories and docs (#12797)

This commit is contained in:
George Marshall 2021-11-24 11:43:22 -08:00 committed by GitHub
parent e7b82915fa
commit 07f16d37be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 288 additions and 57 deletions

View File

@ -1,8 +1,9 @@
.icon-border {
border-radius: 50%;
border: 1px solid #f2f3f4;
background: #ececf0;
border: 1px solid $ui-1;
background: $ui-1;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}

View File

@ -0,0 +1,27 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import IconWithFallback from '.';
# IconWithFallback
Icon component that takes an image src and uses `onError` to fallback to the first letter of the icon `name`
<Canvas>
<Story id="ui-components-ui-icon-with-fallback-icon-with-fallback-stories-js--default-story" />
</Canvas>
## Component API
<ArgsTable of={IconWithFallback} />
## Usage
The following describes the props and example usage for this component.
### Fallback
If the image src errors `onError` the image tag will be replace with a span and the first letter of the `name` prop
<Canvas>
<Story id="ui-components-ui-icon-with-fallback-icon-with-fallback-stories-js--fallback" />
</Canvas>

View File

@ -1,46 +1,61 @@
import React, { PureComponent } from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
export default class IconWithFallback extends PureComponent {
static propTypes = {
icon: PropTypes.string,
name: PropTypes.string,
size: PropTypes.number,
className: PropTypes.string,
fallbackClassName: PropTypes.string,
const IconWithFallback = ({
name = '',
icon = null,
size,
className,
fallbackClassName,
...props
}) => {
const [iconError, setIconError] = useState(false);
const style = size ? { height: `${size}px`, width: `${size}px` } : {};
const handleOnError = () => {
setIconError(true);
};
static defaultProps = {
name: '',
icon: null,
};
return !iconError && icon ? (
<img
onError={handleOnError}
src={icon}
style={style}
className={className}
alt={name.length ? name : 'icon'}
{...props}
/>
) : (
<span
className={classnames('icon-with-fallback__fallback', fallbackClassName)}
>
{name.length ? name.charAt(0).toUpperCase() : ''}
</span>
);
};
state = {
iconError: false,
};
IconWithFallback.propTypes = {
/**
* The img src of the icon
*/
icon: PropTypes.string,
/**
* The name of the icon also used for the alt attribute of the image
*/
name: PropTypes.string,
/**
* The size of the icon. Recommended sizes adhere to 8px grid: 16, 24, 32, 40
*/
size: PropTypes.number,
/**
* className to apply to the image tag
*/
className: PropTypes.string,
/**
* Additional className to apply to the fallback span tag
*/
fallbackClassName: PropTypes.string,
};
render() {
const { icon, name, size, className, fallbackClassName } = this.props;
const style = size ? { height: `${size}px`, width: `${size}px` } : {};
return !this.state.iconError && icon ? (
<img
onError={() => this.setState({ iconError: true })}
src={icon}
style={style}
className={className}
alt=""
/>
) : (
<i
className={classnames(
'icon-with-fallback__fallback',
fallbackClassName,
)}
>
{name.length ? name.charAt(0).toUpperCase() : ''}
</i>
);
}
}
export default IconWithFallback;

View File

@ -1,5 +1,5 @@
.icon-with-fallback {
&__fallback {
color: black;
color: $ui-black;
}
}

View File

@ -0,0 +1,49 @@
import React from 'react';
import README from './README.mdx';
import IconWithFallback from '.';
export default {
title: 'Components/UI/IconWithFallback',
id: __filename,
component: IconWithFallback,
parameters: {
docs: {
page: README,
},
},
argTypes: {
icon: {
control: 'text',
},
name: {
control: 'text',
},
size: {
control: 'number',
},
className: {
control: 'text',
},
fallbackClassName: {
control: 'text',
},
},
};
export const DefaultStory = (args) => <IconWithFallback {...args} />;
DefaultStory.storyName = 'Default';
DefaultStory.args = {
name: 'ast',
icon: './AST.png',
size: 24,
};
export const Fallback = (args) => <IconWithFallback {...args} />;
Fallback.args = {
name: 'ast',
size: 24,
};

View File

@ -0,0 +1,39 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import IconWithFallback from '.';
describe('IconWithFallback', () => {
const args = {
name: 'Snap name',
icon: './AST.png',
className: 'classname-test',
fallbackClassName: 'fallback-classname-test',
};
it('should render without crashing', () => {
const { container } = render(<IconWithFallback />);
expect(container.querySelector('span')).toBeDefined();
});
it('should render an icon image', () => {
const { getByAltText } = render(<IconWithFallback {...args} />);
const image = getByAltText(args.name);
expect(image).toBeDefined();
expect(image).toHaveAttribute('src', args.icon);
});
it('should render with a fallback letter from the name prop', () => {
const { getByText } = render(<IconWithFallback {...args} icon="" />);
expect(getByText('S')).toBeDefined();
});
it('should render with a classname', () => {
const { getByAltText } = render(<IconWithFallback {...args} />);
expect(getByAltText(args.name)).toHaveClass(args.className);
});
it('should render with a fallback classname', () => {
const { getByText } = render(<IconWithFallback {...args} icon="" />);
expect(getByText('S')).toHaveClass(args.fallbackClassName);
});
});

View File

@ -0,0 +1,27 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import SiteIcon from '.';
# SiteIcon
SiteIcon uses the `IconBorder` and `IconWithFallback` components to create an icon within a gray ellipse
<Canvas>
<Story id="ui-components-ui-site-icon-site-icon-stories-js--default-story" />
</Canvas>
## Component API
<ArgsTable of={SiteIcon} />
## Usage
The following describes the props and example usage for this component.
### Fallback
`SiteIcon` wraps the `IconWithFallback` component which has a fallback `onError` and will display the first letter of the `name` prop
<Canvas>
<Story id="ui-components-ui-site-icon-site-icon-stories-js--fallback" />
</Canvas>

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import IconBorder from '../icon-border';
import IconWithFallback from '../icon-with-fallback';
export default function SiteIcon({ icon, name, size }) {
export default function SiteIcon({ icon = null, name = '', size }) {
const iconSize = Math.floor(size * 0.75);
return (
<IconBorder size={size}>
@ -13,12 +13,19 @@ export default function SiteIcon({ icon, name, size }) {
}
SiteIcon.propTypes = {
/**
* The img src of the icon.
* Used in IconWithFallback
*/
icon: PropTypes.string,
/**
* The name of the icon also used for the alt tag of the image and fallback letter.
* Used in IconWithFallback
*/
name: PropTypes.string,
/**
* The size of the icon.
* Used in IconWithFallback
*/
size: PropTypes.number.isRequired,
};
SiteIcon.defaultProps = {
icon: undefined,
name: undefined,
};

View File

@ -0,0 +1,43 @@
import React from 'react';
import README from './README.mdx';
import SiteIcon from '.';
export default {
title: 'Components/UI/SiteIcon',
id: __filename,
component: SiteIcon,
parameters: {
docs: {
page: README,
},
},
argTypes: {
icon: {
control: 'text',
},
name: {
control: 'text',
},
size: {
control: 'number',
},
},
};
export const DefaultStory = (args) => <SiteIcon {...args} />;
DefaultStory.storyName = 'Default';
DefaultStory.args = {
name: 'eth',
icon: './images/eth_logo.svg',
size: 24,
};
export const Fallback = (args) => <SiteIcon {...args} />;
Fallback.args = {
name: 'eth',
size: 24,
};

View File

@ -0,0 +1,24 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import SiteIcon from '.';
describe('SiteIcon', () => {
const args = {
name: 'Snap name',
icon: './images/eth_logo.svg',
className: 'classname-test',
fallbackClassName: 'fallback-classname-test',
};
it('should render without crashing', () => {
const { getByText } = render(<SiteIcon name={args.name} />);
expect(getByText('S')).toBeDefined();
});
it('should render an icon image', () => {
const { getByAltText } = render(<SiteIcon {...args} />);
const image = getByAltText(args.name);
expect(image).toBeDefined();
expect(image).toHaveAttribute('src', args.icon);
});
});

View File

@ -24,7 +24,6 @@ describe('Typography', () => {
<Typography tag="div">div tag</Typography>
<Typography tag="dt">dt tag</Typography>
<Typography tag="dd">dd tag</Typography>
<Typography tag="i">i tag</Typography>
</>,
);
expect(container.querySelector('p')).toBeDefined();

View File

@ -13,7 +13,7 @@ exports[`DropdownSearchList renders the component with initial props 1`] = `
class="dropdown-search-list__selector-closed"
>
<img
alt=""
alt="symbol"
class="url-icon dropdown-search-list__selector-closed-icon"
src="iconUrl"
/>

View File

@ -11,11 +11,11 @@ exports[`MainQuoteSummary renders the component with initial props 1`] = `
>
2
</span>
<i
<span
class="icon-with-fallback__fallback url-icon__fallback main-quote-summary__icon-fallback"
>
E
</i>
</span>
<span
class="main-quote-summary__source-row-symbol"
title="ETH"
@ -29,11 +29,11 @@ exports[`MainQuoteSummary renders the component with initial props 2`] = `
<div
class="main-quote-summary__destination-row"
>
<i
<span
class="icon-with-fallback__fallback url-icon__fallback main-quote-summary__icon-fallback"
>
B
</i>
</span>
<span
class="main-quote-summary__destination-row-symbol"
>

View File

@ -37,7 +37,7 @@ exports[`SearchableItemList renders the component with initial props 2`] = `
tabindex="0"
>
<img
alt=""
alt="primaryLabel"
class="url-icon"
src="iconUrl"
/>

View File

@ -12,7 +12,7 @@ exports[`ViewQuote renders the component with EIP-1559 enabled 1`] = `
10
</span>
<img
alt=""
alt="DAI"
class="url-icon main-quote-summary__icon"
src="https://foo.bar/logo.png"
/>
@ -113,7 +113,7 @@ exports[`ViewQuote renders the component with initial props 1`] = `
10
</span>
<img
alt=""
alt="DAI"
class="url-icon main-quote-summary__icon"
src="https://foo.bar/logo.png"
/>