diff --git a/src-icons/svg/arrow-right.svg b/src-icons/svg/arrow-right.svg
new file mode 100644
index 00000000..28e785df
--- /dev/null
+++ b/src-icons/svg/arrow-right.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/Button/README.stories.mdx b/src/Button/README.stories.mdx
index 17a71a93..635ef19a 100644
--- a/src/Button/README.stories.mdx
+++ b/src/Button/README.stories.mdx
@@ -70,6 +70,9 @@ Basic button types and sizes:
+
@@ -95,6 +98,37 @@ Disabled:
+
+
+
+
+Loading:
+
+
@@ -137,6 +171,32 @@ Change the props in the table below to adjust the Button to your needs.
+Test the button loading state.
+
+
+
## Props
diff --git a/src/Button/index.js b/src/Button/index.js
index f6c7c875..a4e31983 100644
--- a/src/Button/index.js
+++ b/src/Button/index.js
@@ -1,6 +1,6 @@
import React, {forwardRef} from 'react';
import PropTypes from 'prop-types';
-import styled, {css} from 'styled-components';
+import styled, {css, keyframes} from 'styled-components';
import {alpha, mix, getSolidBackgroundShade, isDark} from '../utils/colors';
import {pxToRem} from '../utils/units';
@@ -42,6 +42,7 @@ const shouldForwardProp = getPropFilter([
'round',
'square',
'fullWidth',
+ 'isLoading',
'color',
'size',
'align',
@@ -169,7 +170,7 @@ const Wrapper = styled(ButtonCore).withConfig({
}
}}
- cursor: not-allowed;
+ cursor: ${p => (p.isLoading ? 'wait' : 'not-allowed')};
${p =>
(p.color === 'transparent' || p.color === 'shaded') &&
@@ -269,6 +270,26 @@ const ButtonText = styled.span.withConfig({
${p => (p.textOverflow === 'ellipsis' ? ellipsis : overflowWrap)}
`;
+const spin = keyframes`
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+`;
+
+const SpinningIcon = styled(Icon)`
+ animation: ${spin} 0.6s infinite linear both;
+`;
+
+const SpinnerDot = () => (
+
+);
+
+const IconButton = ({isLoading, name}) =>
+ isLoading ? : ;
+
const Button = forwardRef((props, ref) => {
const {
iconOnly,
@@ -281,6 +302,9 @@ const Button = forwardRef((props, ref) => {
fullWidth,
title,
textOverflow,
+ isLoading,
+ isDisabled,
+ loadingLabel,
...otherProps
} = props;
@@ -296,24 +320,40 @@ const Button = forwardRef((props, ref) => {
align={align}
fullWidth={fullWidth || textOverflow === 'ellipsis'}
{...otherProps}
+ aria-live={isLoading !== undefined ? 'polite' : undefined}
+ isDisabled={isDisabled || isLoading}
+ isLoading={isLoading}
>
{icon && iconRight !== true && (
-
+
)}
{children &&
(iconOnly ? (
- {children}
- ) : (
-
+
{children}
-
+ {isLoading && loadingLabel}
+
+ ) : (
+ <>
+
+ {children}
+
+ {isLoading && (
+ {loadingLabel}
+ )}
+ {!icon && isLoading && }
+ >
))}
- {iconRight && (
-
)}
@@ -327,6 +367,7 @@ Button.displayName = 'Button';
Button.defaultProps = {
color: 'default',
textOverflow: 'wrap',
+ loadingLabel: 'Loading…',
};
Button.propTypes = {
@@ -345,6 +386,14 @@ Button.propTypes = {
* can still be read out by screen readers.
*/
isDisabled: PropTypes.bool,
+ /**
+ * Renders the button in its loading state.
+ */
+ isLoading: PropTypes.bool,
+ /**
+ * Renders the hidden loading label for the button in its loading state.
+ */
+ loadingLabel: PropTypes.string,
/**
* Choose an icon to display next to the button's label
*/
diff --git a/src/Icon/iconMap.js b/src/Icon/iconMap.js
index 078254c8..31f13d26 100644
--- a/src/Icon/iconMap.js
+++ b/src/Icon/iconMap.js
@@ -1,5 +1,7 @@
const iconMap = {
arrow: require('../icons/Arrow').default,
+ 'arrow-left': require('../icons/Arrow').default,
+ 'arrow-right': require('../icons/ArrowRight').default,
asset: require('../icons/AssetDefault').default,
'asset-activity': require('../icons/AssetActivity').default,
'asset-audio': require('../icons/AssetAudio').default,
diff --git a/src/docs/3-icons.stories.mdx b/src/docs/3-icons.stories.mdx
index c4d7bb98..ce634651 100644
--- a/src/docs/3-icons.stories.mdx
+++ b/src/docs/3-icons.stories.mdx
@@ -13,6 +13,7 @@ import Icon from '../Icon';