diff --git a/ui/components/ui/icon-border/icon-border.scss b/ui/components/ui/icon-border/icon-border.scss
index 5028617e6..1ac7e3a4b 100644
--- a/ui/components/ui/icon-border/icon-border.scss
+++ b/ui/components/ui/icon-border/icon-border.scss
@@ -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;
}
diff --git a/ui/components/ui/icon-with-fallback/README.mdx b/ui/components/ui/icon-with-fallback/README.mdx
new file mode 100644
index 000000000..10ce346a1
--- /dev/null
+++ b/ui/components/ui/icon-with-fallback/README.mdx
@@ -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`
+
+
+
+## Component API
+
+
+
+## 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
+
+
diff --git a/ui/components/ui/icon-with-fallback/icon-with-fallback.component.js b/ui/components/ui/icon-with-fallback/icon-with-fallback.component.js
index fec573cd0..62c1ba69f 100644
--- a/ui/components/ui/icon-with-fallback/icon-with-fallback.component.js
+++ b/ui/components/ui/icon-with-fallback/icon-with-fallback.component.js
@@ -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 ? (
+
+ ) : (
+
+ {name.length ? name.charAt(0).toUpperCase() : ''}
+
+ );
+};
- 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 ? (
- this.setState({ iconError: true })}
- src={icon}
- style={style}
- className={className}
- alt=""
- />
- ) : (
-
- {name.length ? name.charAt(0).toUpperCase() : ''}
-
- );
- }
-}
+export default IconWithFallback;
diff --git a/ui/components/ui/icon-with-fallback/icon-with-fallback.scss b/ui/components/ui/icon-with-fallback/icon-with-fallback.scss
index a0cb7cd42..c31671677 100644
--- a/ui/components/ui/icon-with-fallback/icon-with-fallback.scss
+++ b/ui/components/ui/icon-with-fallback/icon-with-fallback.scss
@@ -1,5 +1,5 @@
.icon-with-fallback {
&__fallback {
- color: black;
+ color: $ui-black;
}
}
diff --git a/ui/components/ui/icon-with-fallback/icon-with-fallback.stories.js b/ui/components/ui/icon-with-fallback/icon-with-fallback.stories.js
new file mode 100644
index 000000000..aa6087ec0
--- /dev/null
+++ b/ui/components/ui/icon-with-fallback/icon-with-fallback.stories.js
@@ -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) => ;
+
+DefaultStory.storyName = 'Default';
+
+DefaultStory.args = {
+ name: 'ast',
+ icon: './AST.png',
+ size: 24,
+};
+
+export const Fallback = (args) => ;
+
+Fallback.args = {
+ name: 'ast',
+ size: 24,
+};
diff --git a/ui/components/ui/icon-with-fallback/icon-with-fallback.test.js b/ui/components/ui/icon-with-fallback/icon-with-fallback.test.js
new file mode 100644
index 000000000..5cff033cb
--- /dev/null
+++ b/ui/components/ui/icon-with-fallback/icon-with-fallback.test.js
@@ -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();
+ expect(container.querySelector('span')).toBeDefined();
+ });
+
+ it('should render an icon image', () => {
+ const { getByAltText } = render();
+ 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();
+ expect(getByText('S')).toBeDefined();
+ });
+
+ it('should render with a classname', () => {
+ const { getByAltText } = render();
+ expect(getByAltText(args.name)).toHaveClass(args.className);
+ });
+
+ it('should render with a fallback classname', () => {
+ const { getByText } = render();
+ expect(getByText('S')).toHaveClass(args.fallbackClassName);
+ });
+});
diff --git a/ui/components/ui/site-icon/README.mdx b/ui/components/ui/site-icon/README.mdx
new file mode 100644
index 000000000..6ef847958
--- /dev/null
+++ b/ui/components/ui/site-icon/README.mdx
@@ -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
+
+
+
+## Component API
+
+
+
+## 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
+
+
diff --git a/ui/components/ui/site-icon/site-icon.js b/ui/components/ui/site-icon/site-icon.js
index 4101b7b30..f165ff311 100644
--- a/ui/components/ui/site-icon/site-icon.js
+++ b/ui/components/ui/site-icon/site-icon.js
@@ -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 (
@@ -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,
-};
diff --git a/ui/components/ui/site-icon/site-icon.stories.js b/ui/components/ui/site-icon/site-icon.stories.js
new file mode 100644
index 000000000..8d0bdba71
--- /dev/null
+++ b/ui/components/ui/site-icon/site-icon.stories.js
@@ -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) => ;
+
+DefaultStory.storyName = 'Default';
+
+DefaultStory.args = {
+ name: 'eth',
+ icon: './images/eth_logo.svg',
+ size: 24,
+};
+
+export const Fallback = (args) => ;
+
+Fallback.args = {
+ name: 'eth',
+ size: 24,
+};
diff --git a/ui/components/ui/site-icon/site-icon.test.js b/ui/components/ui/site-icon/site-icon.test.js
new file mode 100644
index 000000000..5c4efa156
--- /dev/null
+++ b/ui/components/ui/site-icon/site-icon.test.js
@@ -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();
+ expect(getByText('S')).toBeDefined();
+ });
+
+ it('should render an icon image', () => {
+ const { getByAltText } = render();
+ const image = getByAltText(args.name);
+ expect(image).toBeDefined();
+ expect(image).toHaveAttribute('src', args.icon);
+ });
+});
diff --git a/ui/components/ui/typography/typography.test.js b/ui/components/ui/typography/typography.test.js
index c162f69dd..d8b259b85 100644
--- a/ui/components/ui/typography/typography.test.js
+++ b/ui/components/ui/typography/typography.test.js
@@ -24,7 +24,6 @@ describe('Typography', () => {
div tag
dt tag
dd tag
- i tag
>,
);
expect(container.querySelector('p')).toBeDefined();
diff --git a/ui/pages/swaps/dropdown-search-list/__snapshots__/dropdown-search-list.test.js.snap b/ui/pages/swaps/dropdown-search-list/__snapshots__/dropdown-search-list.test.js.snap
index 6d8710731..e92d6b8cf 100644
--- a/ui/pages/swaps/dropdown-search-list/__snapshots__/dropdown-search-list.test.js.snap
+++ b/ui/pages/swaps/dropdown-search-list/__snapshots__/dropdown-search-list.test.js.snap
@@ -13,7 +13,7 @@ exports[`DropdownSearchList renders the component with initial props 1`] = `
class="dropdown-search-list__selector-closed"
>
diff --git a/ui/pages/swaps/main-quote-summary/__snapshots__/main-quote-summary.test.js.snap b/ui/pages/swaps/main-quote-summary/__snapshots__/main-quote-summary.test.js.snap
index 2e67e465f..6a89874cd 100644
--- a/ui/pages/swaps/main-quote-summary/__snapshots__/main-quote-summary.test.js.snap
+++ b/ui/pages/swaps/main-quote-summary/__snapshots__/main-quote-summary.test.js.snap
@@ -11,11 +11,11 @@ exports[`MainQuoteSummary renders the component with initial props 1`] = `
>
2
-
E
-
+
-
B
-
+
diff --git a/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap b/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap
index 29affeb5d..be6f2c9c8 100644
--- a/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap
+++ b/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap
@@ -37,7 +37,7 @@ exports[`SearchableItemList renders the component with initial props 2`] = `
tabindex="0"
>
diff --git a/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap b/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap
index b0f5bc601..c2f8f8326 100644
--- a/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap
+++ b/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap
@@ -12,7 +12,7 @@ exports[`ViewQuote renders the component with EIP-1559 enabled 1`] = `
10
@@ -113,7 +113,7 @@ exports[`ViewQuote renders the component with initial props 1`] = `
10