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 = () => (
+
+);