mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-26 12:29:06 +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 {
|
.icon-border {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 1px solid #f2f3f4;
|
border: 1px solid $ui-1;
|
||||||
background: #ececf0;
|
background: $ui-1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: 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 PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
export default class IconWithFallback extends PureComponent {
|
const IconWithFallback = ({
|
||||||
static propTypes = {
|
name = '',
|
||||||
icon: PropTypes.string,
|
icon = null,
|
||||||
name: PropTypes.string,
|
size,
|
||||||
size: PropTypes.number,
|
className,
|
||||||
className: PropTypes.string,
|
fallbackClassName,
|
||||||
fallbackClassName: PropTypes.string,
|
...props
|
||||||
|
}) => {
|
||||||
|
const [iconError, setIconError] = useState(false);
|
||||||
|
const style = size ? { height: `${size}px`, width: `${size}px` } : {};
|
||||||
|
|
||||||
|
const handleOnError = () => {
|
||||||
|
setIconError(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
return !iconError && icon ? (
|
||||||
name: '',
|
<img
|
||||||
icon: null,
|
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 = {
|
IconWithFallback.propTypes = {
|
||||||
iconError: false,
|
/**
|
||||||
};
|
* 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() {
|
export default IconWithFallback;
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.icon-with-fallback {
|
.icon-with-fallback {
|
||||||
&__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 IconBorder from '../icon-border';
|
||||||
import IconWithFallback from '../icon-with-fallback';
|
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);
|
const iconSize = Math.floor(size * 0.75);
|
||||||
return (
|
return (
|
||||||
<IconBorder size={size}>
|
<IconBorder size={size}>
|
||||||
@ -13,12 +13,19 @@ export default function SiteIcon({ icon, name, size }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SiteIcon.propTypes = {
|
SiteIcon.propTypes = {
|
||||||
|
/**
|
||||||
|
* The img src of the icon.
|
||||||
|
* Used in IconWithFallback
|
||||||
|
*/
|
||||||
icon: PropTypes.string,
|
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,
|
name: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* The size of the icon.
|
||||||
|
* Used in IconWithFallback
|
||||||
|
*/
|
||||||
size: PropTypes.number.isRequired,
|
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="div">div tag</Typography>
|
||||||
<Typography tag="dt">dt tag</Typography>
|
<Typography tag="dt">dt tag</Typography>
|
||||||
<Typography tag="dd">dd tag</Typography>
|
<Typography tag="dd">dd tag</Typography>
|
||||||
<Typography tag="i">i tag</Typography>
|
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
expect(container.querySelector('p')).toBeDefined();
|
expect(container.querySelector('p')).toBeDefined();
|
||||||
|
@ -13,7 +13,7 @@ exports[`DropdownSearchList renders the component with initial props 1`] = `
|
|||||||
class="dropdown-search-list__selector-closed"
|
class="dropdown-search-list__selector-closed"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt="symbol"
|
||||||
class="url-icon dropdown-search-list__selector-closed-icon"
|
class="url-icon dropdown-search-list__selector-closed-icon"
|
||||||
src="iconUrl"
|
src="iconUrl"
|
||||||
/>
|
/>
|
||||||
|
@ -11,11 +11,11 @@ exports[`MainQuoteSummary renders the component with initial props 1`] = `
|
|||||||
>
|
>
|
||||||
2
|
2
|
||||||
</span>
|
</span>
|
||||||
<i
|
<span
|
||||||
class="icon-with-fallback__fallback url-icon__fallback main-quote-summary__icon-fallback"
|
class="icon-with-fallback__fallback url-icon__fallback main-quote-summary__icon-fallback"
|
||||||
>
|
>
|
||||||
E
|
E
|
||||||
</i>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="main-quote-summary__source-row-symbol"
|
class="main-quote-summary__source-row-symbol"
|
||||||
title="ETH"
|
title="ETH"
|
||||||
@ -29,11 +29,11 @@ exports[`MainQuoteSummary renders the component with initial props 2`] = `
|
|||||||
<div
|
<div
|
||||||
class="main-quote-summary__destination-row"
|
class="main-quote-summary__destination-row"
|
||||||
>
|
>
|
||||||
<i
|
<span
|
||||||
class="icon-with-fallback__fallback url-icon__fallback main-quote-summary__icon-fallback"
|
class="icon-with-fallback__fallback url-icon__fallback main-quote-summary__icon-fallback"
|
||||||
>
|
>
|
||||||
B
|
B
|
||||||
</i>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="main-quote-summary__destination-row-symbol"
|
class="main-quote-summary__destination-row-symbol"
|
||||||
>
|
>
|
||||||
|
@ -37,7 +37,7 @@ exports[`SearchableItemList renders the component with initial props 2`] = `
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt="primaryLabel"
|
||||||
class="url-icon"
|
class="url-icon"
|
||||||
src="iconUrl"
|
src="iconUrl"
|
||||||
/>
|
/>
|
||||||
|
@ -12,7 +12,7 @@ exports[`ViewQuote renders the component with EIP-1559 enabled 1`] = `
|
|||||||
10
|
10
|
||||||
</span>
|
</span>
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt="DAI"
|
||||||
class="url-icon main-quote-summary__icon"
|
class="url-icon main-quote-summary__icon"
|
||||||
src="https://foo.bar/logo.png"
|
src="https://foo.bar/logo.png"
|
||||||
/>
|
/>
|
||||||
@ -113,7 +113,7 @@ exports[`ViewQuote renders the component with initial props 1`] = `
|
|||||||
10
|
10
|
||||||
</span>
|
</span>
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt="DAI"
|
||||||
class="url-icon main-quote-summary__icon"
|
class="url-icon main-quote-summary__icon"
|
||||||
src="https://foo.bar/logo.png"
|
src="https://foo.bar/logo.png"
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user