Updating IconWithFallback to functional component, adding stories and docs (#12797)
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; |
||||
} |
||||
|
@ -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, |
||||
}; |
||||
const IconWithFallback = ({ |
||||
name = '', |
||||
icon = null, |
||||
size, |
||||
className, |
||||
fallbackClassName, |
||||
...props |
||||
}) => { |
||||
const [iconError, setIconError] = useState(false); |
||||
const style = size ? { height: `${size}px`, width: `${size}px` } : {}; |
||||
|
||||
state = { |
||||
iconError: false, |
||||
const handleOnError = () => { |
||||
setIconError(true); |
||||
}; |
||||
|
||||
render() { |
||||
const { icon, name, size, className, fallbackClassName } = this.props; |
||||
const style = size ? { height: `${size}px`, width: `${size}px` } : {}; |
||||
|
||||
return !this.state.iconError && icon ? ( |
||||
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); |
||||
}); |
||||
}); |
@ -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> |
@ -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, |
||||
}; |
@ -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); |
||||
}); |
||||
}); |
Loading…
Reference in new issue