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.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