<Button /> supports icons directly to style hover states

This commit is contained in:
Andrew Nicolaou 2020-04-26 15:32:50 +02:00
parent 56865047fd
commit 7cf3a0bea0
7 changed files with 52 additions and 80 deletions

View file

@ -4,6 +4,7 @@ import styled from 'styled-components';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { remSize, prop } from '../theme'; import { remSize, prop } from '../theme';
import Icon, { ValidIconNameType } from './Icon';
// The '&&&' will increase the specificity of the // The '&&&' will increase the specificity of the
// component's CSS so that it overrides the more // component's CSS so that it overrides the more
@ -28,6 +29,10 @@ const StyledButton = styled.button`
&:hover:not(:disabled) { &:hover:not(:disabled) {
color: ${prop('buttonHoverColor')}; color: ${prop('buttonHoverColor')};
background-color: ${prop('buttonHoverColorBackground')}; background-color: ${prop('buttonHoverColorBackground')};
svg * {
fill: ${prop('buttonHoverColor')};
}
} }
&:disabled { &:disabled {
@ -46,27 +51,36 @@ const StyledButton = styled.button`
* A Button performs an primary action * A Button performs an primary action
*/ */
const Button = ({ const Button = ({
children, href, label, to, type, ...props children, href, iconAfterName, iconBeforeName, label, to, type, ...props
}) => { }) => {
const iconAfter = iconAfterName && <Icon name={iconAfterName} />;
const iconBefore = iconBeforeName && <Icon name={iconBeforeName} />;
const content = <>{iconBefore}<span>{children}</span>{iconAfter}</>;
if (href) { if (href) {
return <StyledButton as="a" aria-label={label} href={href} {...props}>{children}</StyledButton>; return <StyledButton as="a" aria-label={label} href={href} {...props}>{content}</StyledButton>;
} }
if (to) { if (to) {
return <StyledButton as={Link} aria-label={label} to={to} {...props}>{children}</StyledButton>; return <StyledButton as={Link} aria-label={label} to={to} {...props}>{content}</StyledButton>;
} }
return <StyledButton aria-label={label} type={type} {...props}>{children}</StyledButton>; return <StyledButton aria-label={label} type={type} {...props}>{content}</StyledButton>;
}; };
Button.defaultProps = { Button.defaultProps = {
disabled: false, disabled: false,
iconAfterName: null,
iconBeforeName: null,
href: null, href: null,
label: null, label: null,
to: null, to: null,
type: 'button', type: 'button',
}; };
Button.iconNames = Icon.names;
Button.propTypes = { Button.propTypes = {
/** /**
* The visible part of the button, telling the user what * The visible part of the button, telling the user what
@ -77,6 +91,15 @@ Button.propTypes = {
If the button can be activated or not If the button can be activated or not
*/ */
disabled: PropTypes.bool, disabled: PropTypes.bool,
/**
* Name of icon to place before child content
*/
iconAfterName: ValidIconNameType,
/**
* Name of icon to place after child content
*/
iconBeforeName: ValidIconNameType,
/** /**
* Specifying an href will use an <a> to link to the URL * Specifying an href will use an <a> to link to the URL
*/ */

View file

@ -35,12 +35,10 @@ export const ReactRouterLink = () => (
<Button to="./somewhere" label="submit">Actually a Link</Button> <Button to="./somewhere" label="submit">Actually a Link</Button>
); );
export const InternalElementMargin = () => ( export const ButtonWithIconBefore = () => (
<Button> <Button iconBeforeName={Button.iconNames.github}>Create</Button>
<span>Internal</span> );
<span>elements</span>
<span>have</span> export const ButtonWithIconAfter = () => (
<span>right</span> <Button iconAfterName={Button.iconNames.github}>Create</Button>
<span>margins</span>
</Button>
); );

View file

@ -22,6 +22,8 @@ const icons = {
*/ */
const names = lodash.mapValues(icons, (value, key) => key); const names = lodash.mapValues(icons, (value, key) => key);
export const ValidIconNameType = PropTypes.oneOf(Object.keys(names));
function Icon({ name, ...props }) { function Icon({ name, ...props }) {
return ( return (
@ -29,10 +31,11 @@ function Icon({ name, ...props }) {
); );
} }
Icon.names = names; Icon.names = names;
Icon.propTypes = { Icon.propTypes = {
name: PropTypes.oneOf(Object.keys(names)).isRequired name: ValidIconNameType.isRequired
}; };
export default Icon; export default Icon;

View file

@ -1,47 +1,9 @@
<?xml version="1.0" encoding="iso-8859-1"?> <?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#FBBB00;" d="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211,0,299.9,0,256
c0-42.451,10.324-82.483,28.624-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501-8.215,32.141-8.215,49.456
C103.821,274.792,107.225,292.797,113.47,309.408z"/>
<path style="fill:#518EF8;" d="M507.527,208.176C510.467,223.662,512,239.655,512,256c0,18.328-1.927,36.206-5.598,53.451
c-12.462,58.683-45.025,109.925-90.134,146.187l-0.014-0.014l-73.044-3.727l-10.338-64.535
c29.932-17.554,53.324-45.025,65.646-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z"/>
<path style="fill:#28B446;" d="M416.253,455.624l0.014,0.014C372.396,490.901,316.666,512,256,512
c-97.491,0-182.252-54.491-225.491-134.681l82.961-67.91c21.619,57.698,77.278,98.771,142.53,98.771
c28.047,0,54.323-7.582,76.87-20.818L416.253,455.624z"/>
<path style="fill:#F14336;" d="M419.404,58.936l-82.933,67.896c-23.335-14.586-50.919-23.012-80.471-23.012
c-66.729,0-123.429,42.957-143.965,102.724l-83.397-68.276h-0.014C71.23,56.123,157.06,0,256,0
C318.115,0,375.068,22.126,419.404,58.936z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g> <g>
<path d="M7.091875,19.338 L5.978,23.49625 L1.9068125,23.582375 C0.690125,21.3256875 0,18.74375 0,16 C0,13.3468125 0.64525,10.8448125 1.789,8.64175 L1.789875,8.64175 L5.414375,9.30625 L7.002125,12.909 C6.6698125,13.8778125 6.4886875,14.9178125 6.4886875,16 C6.4888125,17.1745 6.7015625,18.2998125 7.091875,19.338 Z" fill="#FBBB00"></path>
<path d="M31.7204375,13.011 C31.9041875,13.978875 32,14.9784375 32,16 C32,17.1455 31.8795625,18.262875 31.650125,19.3406875 C30.87125,23.008375 28.8360625,26.211 26.01675,28.477375 L26.015875,28.4765 L21.450625,28.2435625 L20.8045,24.210125 C22.67525,23.113 24.13725,21.3960625 24.907375,19.3406875 L16.35175,19.3406875 L16.35175,13.011 L25.0321875,13.011 L31.7204375,13.011 Z" fill="#518EF8"></path>
<path d="M26.0158125,28.4765 L26.0166875,28.477375 C23.27475,30.6813125 19.791625,32 16,32 C9.9068125,32 4.60925,28.5943125 1.9068125,23.5824375 L7.091875,19.3380625 C8.4430625,22.9441875 11.92175,25.51125 16,25.51125 C17.7529375,25.51125 19.3951875,25.037375 20.804375,24.210125 L26.0158125,28.4765 Z" fill="#28B446"></path>
<path d="M26.21275,3.6835 L21.0294375,7.927 C19.571,7.015375 17.847,6.48875 16,6.48875 C11.8294375,6.48875 8.2856875,9.1735625 7.0021875,12.909 L1.789875,8.64175 L1.789,8.64175 C4.451875,3.5076875 9.81625,0 16,0 C19.8821875,0 23.44175,1.382875 26.21275,3.6835 Z" fill="#F14336"></path>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,9 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import InlineSVG from 'react-inlinesvg';
import Button from '../../../common/Button'; import Button from '../../../common/Button';
import Icon from '../../../common/Icon';
import CopyableInput from '../../IDE/components/CopyableInput'; import CopyableInput from '../../IDE/components/CopyableInput';
import APIKeyList from './APIKeyList'; import APIKeyList from './APIKeyList';
@ -82,13 +80,12 @@ class APIKeyForm extends React.Component {
value={this.state.keyLabel} value={this.state.keyLabel}
/> />
<Button <Button
iconBeforeName={Button.iconNames.plus}
disabled={this.state.keyLabel === ''} disabled={this.state.keyLabel === ''}
type="submit" type="submit"
label="Create new key" label="Create new key"
> >
<Icon name={Icon.names.plus} /> Create
<span>Create</span>
</Button> </Button>
</form> </form>

View file

@ -9,7 +9,6 @@ import { bindActionCreators } from 'redux';
import classNames from 'classnames'; import classNames from 'classnames';
import Button from '../../../common/Button'; import Button from '../../../common/Button';
import Icon from '../../../common/Icon';
import * as ProjectActions from '../../IDE/actions/project'; import * as ProjectActions from '../../IDE/actions/project';
import * as ProjectsActions from '../../IDE/actions/projects'; import * as ProjectsActions from '../../IDE/actions/projects';
import * as CollectionsActions from '../../IDE/actions/collections'; import * as CollectionsActions from '../../IDE/actions/collections';
@ -54,9 +53,9 @@ const ShareURL = ({ value }) => {
return ( return (
<div className="collection-share" ref={node}> <div className="collection-share" ref={node}>
<Button <Button
iconAfterName={Button.iconNames.sortArrowDown}
onClick={() => setShowURL(!showURL)} onClick={() => setShowURL(!showURL)}
><span>Share</span> >Share
<Icon name={Icon.names.sortArrowDown} />
</Button> </Button>
{ showURL && { showURL &&
<div className="collection__share-dropdown"> <div className="collection__share-dropdown">

View file

@ -2,10 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { remSize, prop } from '../../../theme'; import { remSize } from '../../../theme';
import Button from '../../../common/Button'; import Button from '../../../common/Button';
import Icon from '../../../common/Icon';
const authUrls = { const authUrls = {
github: '/auth-github', github: '/auth-github',
@ -24,24 +23,15 @@ const services = {
const StyledButton = styled(Button)` const StyledButton = styled(Button)`
width: ${remSize(300)}; width: ${remSize(300)};
> * + * {
margin-left: ${remSize(10)};
}
`;
const StyledIcon = styled(Icon)`
width: ${remSize(32)};
height: ${remSize(32)};
`; `;
function SocialAuthButton({ service }) { function SocialAuthButton({ service }) {
return ( return (
<StyledButton <StyledButton
iconBeforeName={Button.iconNames[service]}
href={authUrls[service]} href={authUrls[service]}
> >
<StyledIcon name={Icon.names[service]} /> {labels[service]}
<span>{labels[service]}</span>
</StyledButton> </StyledButton>
); );
} }