diff --git a/client/common/Button.jsx b/client/common/Button.jsx index 16b35c8d..97a960e1 100644 --- a/client/common/Button.jsx +++ b/client/common/Button.jsx @@ -6,6 +6,12 @@ import { Link } from 'react-router'; import { remSize, prop } from '../theme'; import Icon, { ValidIconNameType } from './Icon'; +const kinds = { + block: 'block', + icon: 'icon', + inline: 'inline', +}; + // The '&&&' will increase the specificity of the // component's CSS so that it overrides the more // general global styles @@ -41,8 +47,74 @@ const StyledButton = styled.button` cursor: not-allowed; } - > *:not(:last-child) { - margin-right: ${remSize(8)}; + > * + * { + margin-left: ${remSize(8)}; + } + } +`; + +const StyledInlineButton = styled.button` + &&& { + display: flex; + justify-content: center; + align-items: center; + + text-decoration: none; + + color: ${prop('primaryTextColor')}; + cursor: pointer; + border: none; + line-height: 1; + + svg * { + fill: ${prop('primaryTextColor')}; + } + + &:disabled { + cursor: not-allowed; + } + + > * + * { + margin-left: ${remSize(8)}; + } + } +`; + +const StyledIconButton = styled.button` + &&& { + display: flex; + justify-content: center; + align-items: center; + + width: ${remSize(32)}px; + height: ${remSize(32)}px; + text-decoration: none; + + background-color: ${prop('buttonColorBackground')}; + color: ${prop('buttonColor')}; + cursor: pointer; + border: 1px solid transparen; + border-radius: 50%; + padding: ${remSize(8)} ${remSize(25)}; + line-height: 1; + + &:hover:not(:disabled) { + color: ${prop('buttonHoverColor')}; + background-color: ${prop('buttonHoverColorBackground')}; + + svg * { + fill: ${prop('buttonHoverColor')}; + } + } + + &:disabled { + color: ${prop('buttonDisabledColor')}; + background-color: ${prop('buttonDisabledColorBackground')}; + cursor: not-allowed; + } + + > * + * { + margin-left: ${remSize(8)}; } } `; @@ -51,28 +123,39 @@ const StyledButton = styled.button` * A Button performs an primary action */ const Button = ({ - children, href, iconAfterName, iconBeforeName, label, to, type, ...props + children, href, iconAfterName, iconBeforeName, kind, label, to, type, ...props }) => { const iconAfter = iconAfterName && ; const iconBefore = iconBeforeName && ; + const hasChildren = React.Children.count(children) > 0; - const content = <>{iconBefore}{children}{iconAfter}; + const content = <>{iconBefore}{hasChildren && {children}}{iconAfter}; + + let StyledComponent = StyledButton; + + if (kind === kinds.inline) { + StyledComponent = StyledInlineButton; + } else if (kind === kinds.icon) { + StyledComponent = StyledIconButton; + } if (href) { - return {content}; + return {content}; } if (to) { - return {content}; + return {content}; } - return {content}; + return {content}; }; Button.defaultProps = { + children: null, disabled: false, iconAfterName: null, iconBeforeName: null, + kind: kinds.block, href: null, label: null, to: null, @@ -80,13 +163,14 @@ Button.defaultProps = { }; Button.iconNames = Icon.names; +Button.kinds = kinds; Button.propTypes = { /** * The visible part of the button, telling the user what * the action is */ - children: PropTypes.element.isRequired, + children: PropTypes.element, /** If the button can be activated or not */ @@ -95,11 +179,14 @@ Button.propTypes = { * Name of icon to place before child content */ iconAfterName: ValidIconNameType, - /** * Name of icon to place after child content */ iconBeforeName: ValidIconNameType, + /** + * The kind of button - determines how it appears visually + */ + kind: PropTypes.oneOf(Object.values(kinds)), /** * Specifying an href will use an to link to the URL */ diff --git a/client/common/Button.stories.jsx b/client/common/Button.stories.jsx index 601d7fe9..1af34631 100644 --- a/client/common/Button.stories.jsx +++ b/client/common/Button.stories.jsx @@ -42,3 +42,11 @@ export const ButtonWithIconBefore = () => ( export const ButtonWithIconAfter = () => ( ); + +export const InlineButtonWithIconAfter = () => ( + +); + +export const InlineIconOnlyButton = () => ( +