Skip to content
This repository has been archived by the owner on Jun 5, 2023. It is now read-only.

feat: add button loading state and right arrow icon, #163 #200

Merged
merged 5 commits into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src-icons/svg/arrow-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions src/Button/README.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ Basic button types and sizes:
<Button isActive size="medium">
Done
</Button>
<Button iconRight icon="arrow-right" isActive size="medium">
Let's go
</Button>
</InlineList>
</Canvas>

Expand All @@ -95,6 +98,37 @@ Disabled:
<Button isDisabled isActive size="medium">
Done
</Button>
<Button isDisabled iconRight icon="arrow-right" isActive size="medium">
Let's go
</Button>
</InlineList>
</Canvas>

Loading:

<Canvas>
<InlineList spacing="s">
<Button isLoading color="primary" icon="ok">
Save
</Button>
<Button isLoading> Cancel</Button>
<Button isLoading size="medium" color="important">
Delete
</Button>
<Button isLoading size="medium" icon="send">
Share
</Button>
<Button isLoading icon="tools" size="medium" color="shaded" />
<Button isLoading size="small" color="transparent">
Forgot password?
</Button>
<Button isLoading round icon="plus" color="important" />
<Button isLoading isActive size="medium">
Done
</Button>
<Button isLoading iconRight icon="arrow-right" isActive size="medium">
Let's go
</Button>
</InlineList>
</Canvas>

Expand Down Expand Up @@ -137,6 +171,32 @@ Change the props in the table below to adjust the Button to your needs.
</Story>
</Canvas>

Test the button loading state.

<Canvas>
<Story name="Loading example">
{() => {
const [isLoading, setIsLoading] = React.useState(false);
const buttonClick = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, "3000")
};
return (
<Button
iconRight
icon="arrow-right"
onClick={buttonClick}
isLoading={isLoading}
>
Next
</Button>
);
}}
</Story>
</Canvas>

## Props

<ArgsTable story="Button" />
69 changes: 59 additions & 10 deletions src/Button/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -42,6 +42,7 @@ const shouldForwardProp = getPropFilter([
'round',
'square',
'fullWidth',
'isLoading',
'color',
'size',
'align',
Expand Down Expand Up @@ -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') &&
Expand Down Expand Up @@ -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 = () => (
<SpinningIcon name="spinnerdot" disablePointerEvents />
);

const IconButton = ({isLoading, name}) =>
isLoading ? <SpinnerDot /> : <Icon disablePointerEvents name={name} />;

const Button = forwardRef((props, ref) => {
const {
iconOnly,
Expand All @@ -281,6 +302,9 @@ const Button = forwardRef((props, ref) => {
fullWidth,
title,
textOverflow,
isLoading,
isDisabled,
loadingLabel,
...otherProps
} = props;

Expand All @@ -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}
>
<HoverShade />
<FocusRing color={color} />
<Content align={align}>
{icon && iconRight !== true && (
<Icon disablePointerEvents name={icon} />
<IconButton
isLoading={isLoading && (!iconRight || iconOnly)}
name={icon}
/>
)}
{children &&
(iconOnly ? (
<VisuallyHidden>{children}</VisuallyHidden>
) : (
<ButtonText textOverflow={textOverflow}>
<VisuallyHidden>
{children}
</ButtonText>
{isLoading && loadingLabel}
</VisuallyHidden>
) : (
<>
<ButtonText textOverflow={textOverflow}>
{children}
</ButtonText>
{isLoading && (
<VisuallyHidden>{loadingLabel}</VisuallyHidden>
)}
{!icon && isLoading && <SpinnerDot />}
</>
))}
{iconRight && (
<Icon
disablePointerEvents

{!iconOnly && iconRight && (
<IconButton
isLoading={isLoading}
name={hasSeparateRightIcon ? iconRight : icon}
/>
)}
Expand All @@ -327,6 +367,7 @@ Button.displayName = 'Button';
Button.defaultProps = {
color: 'default',
textOverflow: 'wrap',
loadingLabel: 'Loading…',
};

Button.propTypes = {
Expand All @@ -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
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Icon/iconMap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const iconMap = {
arrow: require('../icons/Arrow').default,
'arrow-left': require('../icons/Arrow').default,
'arrow-right': require('../icons/ArrowRight').default,
SarahHouben marked this conversation as resolved.
Show resolved Hide resolved
asset: require('../icons/AssetDefault').default,
'asset-activity': require('../icons/AssetActivity').default,
'asset-audio': require('../icons/AssetAudio').default,
Expand Down
1 change: 1 addition & 0 deletions src/docs/3-icons.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Icon from '../Icon';

<Canvas>
<Icon name="arrow" />
<Icon name="arrow-right" />
<Icon name="asset" />
<Icon name="asset-activity" />
<Icon name="asset-audio" />
Expand Down
39 changes: 39 additions & 0 deletions src/icons/ArrowRight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, {forwardRef} from 'react';

import Svg from './BaseSvg';
import { SVGRProps } from './util';

const ArrowRightIcon = forwardRef<SVGSVGElement, SVGRProps>((props, ref) => {
const {size, color, ...otherProps} = props;

// Unless the icon has an explicit ARIA label, we'll hide it visually
const ariaHidden =
!(otherProps['aria-label'] || otherProps['aria-labelledby']) ||
undefined;

return (
<Svg
{...otherProps}
ref={ref}
viewBox="0 0 18 18"
width={size}
height={size}
fill={color}
fillRule="evenodd"
clipRule="evenodd"
focusable="false"
aria-hidden={ariaHidden}
>
<path d="M15.703 9.71098L11.414 14C11.024 14.39 10.39 14.39 10 14C9.61001 13.61 9.61001 12.976 10 12.586L12.586 9.99998L3.00001 9.99998C2.44501 9.99698 2.00001 9.54998 2.00001 8.99998C2.00001 8.44998 2.44501 8.00298 3.00001 7.99998L12.586 7.99998L9.98001 5.39398C9.58901 5.00398 9.58901 4.36998 9.98001 3.97998C10.37 3.58898 11.004 3.58898 11.394 3.97998L15.703 8.28898L15.707 8.29298C15.774 8.35898 15.829 8.43298 15.873 8.51398L15.879 8.52398L15.88 8.52498L15.886 8.53498L15.912 8.59198L15.921 8.61098L15.923 8.61398L15.927 8.62298L15.945 8.67598L15.955 8.70298L15.956 8.70798L15.959 8.71498L15.971 8.76498L15.98 8.79898V8.80298L15.982 8.80898L15.983 8.81698L15.995 8.89798L16 8.99998L15.995 9.10198L15.983 9.18298L15.982 9.19098L15.98 9.19698V9.20098L15.971 9.23498L15.959 9.28498L15.956 9.29198L15.955 9.29698L15.945 9.32398L15.927 9.37698L15.923 9.38598L15.921 9.38898L15.912 9.40798L15.886 9.46498L15.88 9.47498L15.879 9.47598L15.873 9.48598C15.829 9.56698 15.774 9.64098 15.707 9.70698L15.703 9.71098V9.71098Z" />
</Svg>
);
});

ArrowRightIcon.displayName = 'ArrowRightIcon';

ArrowRightIcon.defaultProps = {
size: 18,
color: 'currentcolor',
};

export default ArrowRightIcon;
Loading