mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-23 02:10:12 +01:00
Updating IconWithFallback to functional component, adding stories and docs (#12797)
This commit is contained in:
parent
e7b82915fa
commit
07f16d37be
@ -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;
|
||||
}
|
||||
|
27
ui/components/ui/icon-with-fallback/README.mdx
Normal file
27
ui/components/ui/icon-with-fallback/README.mdx
Normal 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>
|
@ -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,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
name: '',
|
||||
icon: null,
|
||||
};
|
||||
|
||||
state = {
|
||||
iconError: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { icon, name, size, className, fallbackClassName } = this.props;
|
||||
const IconWithFallback = ({
|
||||
name = '',
|
||||
icon = null,
|
||||
size,
|
||||
className,
|
||||
fallbackClassName,
|
||||
...props
|
||||
}) => {
|
||||
const [iconError, setIconError] = useState(false);
|
||||
const style = size ? { height: `${size}px`, width: `${size}px` } : {};
|
||||
|
||||
return !this.state.iconError && icon ? (
|
||||
const handleOnError = () => {
|
||||
setIconError(true);
|
||||
};
|
||||
|
||||
return !iconError && icon ? (
|
||||
<img
|
||||
onError={() => this.setState({ iconError: true })}
|
||||
onError={handleOnError}
|
||||
src={icon}
|
||||
style={style}
|
||||
className={className}
|
||||
alt=""
|
||||
alt={name.length ? name : 'icon'}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<i
|
||||
className={classnames(
|
||||
'icon-with-fallback__fallback',
|
||||
fallbackClassName,
|
||||
)}
|
||||
<span
|
||||
className={classnames('icon-with-fallback__fallback', fallbackClassName)}
|
||||
>
|
||||
{name.length ? name.charAt(0).toUpperCase() : ''}
|
||||
</i>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
export default IconWithFallback;
|
||||
|
@ -1,5 +1,5 @@
|
||||
.icon-with-fallback {
|
||||
&__fallback {
|
||||
color: black;
|
||||
color: $ui-black;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
@ -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);
|
||||
});
|
||||
});
|
27
ui/components/ui/site-icon/README.mdx
Normal file
27
ui/components/ui/site-icon/README.mdx
Normal 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>
|
@ -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,
|
||||
};
|
||||
|
43
ui/components/ui/site-icon/site-icon.stories.js
Normal file
43
ui/components/ui/site-icon/site-icon.stories.js
Normal 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,
|
||||
};
|
24
ui/components/ui/site-icon/site-icon.test.js
Normal file
24
ui/components/ui/site-icon/site-icon.test.js
Normal 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);
|
||||
});
|
||||
});
|
@ -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();
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -37,7 +37,7 @@ exports[`SearchableItemList renders the component with initial props 2`] = `
|
||||
tabindex="0"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
alt="primaryLabel"
|
||||
class="url-icon"
|
||||
src="iconUrl"
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user