diff --git a/src/components/Button/Button.css.js b/src/components/Button/Button.css.js index bc3bbcede..3489efe8c 100644 --- a/src/components/Button/Button.css.js +++ b/src/components/Button/Button.css.js @@ -1,6 +1,7 @@ // @flow import get from 'dash-get' import baseStyles from '../../styles/resets/baseStyles.css.js' +import Spinner from '../Spinner' import styled from '../styled' import { getColor } from '../../styles/utilities/color' import forEach from '../../styles/utilities/forEach' @@ -298,6 +299,18 @@ export const makeButtonUI = (selector = 'button') => { width: 100%; } + &.is-loading { + &.is-spinButtonOnLoading { + animation: SpinButtonOnLoadAnimation 700ms linear infinite; + will-change: transform; + @keyframes SpinButtonOnLoadAnimation { + 100% { + transform: rotate(360deg); + } + } + } + } + ${makeButtonSizeStyles()} ${props => makePrimaryStyles('primary', props)} @@ -471,6 +484,12 @@ export const ButtonContentUI = styled('span')` pointer-events: none; } `}; + + ${({ isLoading }) => + isLoading && + ` + opacity: 0.35; + `}; ` export const FocusUI = styled('span')` @@ -506,3 +525,16 @@ export const FocusUI = styled('span')` } } ` + +export const SpinnerUI = styled(Spinner)` + color: ${getColor('charcoal.500')}; + margin: -6px 0 0 -6px; + position: absolute; + z-index: 1; + top: 50%; + left: 50%; +` + +SpinnerUI.defaultProps = { + size: 12, +} diff --git a/src/components/Button/ButtonV2.js b/src/components/Button/ButtonV2.js index 731ded1a6..47bf16a34 100644 --- a/src/components/Button/ButtonV2.js +++ b/src/components/Button/ButtonV2.js @@ -9,7 +9,12 @@ import { includes } from '../../utilities/arrays' import { noop } from '../../utilities/other' import { memoize } from '../../utilities/memoize' import RouteWrapper from '../RouteWrapper' -import { makeButtonUI, ButtonContentUI, FocusUI } from './Button.css.js' +import { + makeButtonUI, + ButtonContentUI, + FocusUI, + SpinnerUI, +} from './Button.css.js' import { COMPONENT_KEY } from './utils' import { COMPONENT_KEY as ICON_KEY } from '../Icon/utils' @@ -20,6 +25,7 @@ type Props = { children?: any, className?: string, disabled: boolean, + disableOnLoading: boolean, kind: ButtonKind, innerRef: (ref: any) => void, isActive: boolean, @@ -27,8 +33,10 @@ type Props = { isFirst: boolean, isNotOnly: boolean, isLast: boolean, + isLoading: boolean, isSuffix: boolean, size: ButtonSize, + spinButtonOnLoading: boolean, state?: UIState, submit: boolean, theme?: string, @@ -40,6 +48,7 @@ class Button extends Component { buttonRef: noop, canRenderFocus: true, disable: false, + disableOnLoading: true, kind: 'default', innerRef: noop, isActive: false, @@ -49,6 +58,7 @@ class Button extends Component { isLast: false, isSuffix: false, size: 'md', + spinButtonOnLoading: false, submit: false, } @@ -132,6 +142,8 @@ class Button extends Component { allowContentEventPropogation, children, className, + disabled, + disableOnLoading, kind, innerRef, isActive, @@ -139,8 +151,10 @@ class Button extends Component { isFirst, isNotOnly, isLast, + isLoading, isSuffix, size, + spinButtonOnLoading, state, submit, theme, @@ -149,16 +163,21 @@ class Button extends Component { ...rest } = this.props + const isDisabled = disabled || (isLoading && disableOnLoading) + const componentClassName = classNames( 'c-ButtonV2', isActive && 'is-active', isBlock && 'is-block', + isDisabled && 'is-disabled', isFirst && 'is-first', isNotOnly && 'is-notOnly', isLast && 'is-last', + isLoading && 'is-loading', isSuffix && 'is-suffix', kind && `is-${kind}`, size && `is-${size}`, + spinButtonOnLoading && 'is-spinButtonOnLoading', state && `is-${state}`, theme && `is-${theme}`, className @@ -172,12 +191,15 @@ class Button extends Component { + {isLoading ? : null} {this.getChildrenMarkup()} diff --git a/src/components/Button/__tests__/ButtonV2.test.js b/src/components/Button/__tests__/ButtonV2.test.js index 040617951..41510d865 100644 --- a/src/components/Button/__tests__/ButtonV2.test.js +++ b/src/components/Button/__tests__/ButtonV2.test.js @@ -323,3 +323,47 @@ describe('Link', () => { expect(wrapper.find('button').length).toBeTruthy() }) }) + +describe('Loading', () => { + test('Add loading className, if isLoading', () => { + const wrapper = mount( -)) +stories.add('Default', () => { + const props = { + children: text('children', 'Button'), + disabled: boolean('disabled', false), + disableOnLoading: boolean('disableOnLoading', true), + isActive: boolean('isActive', false), + isBlock: boolean('isBlock', false), + isLoading: boolean('isLoading', false), + kind: select( + 'kind', + { + primary: 'primary', + primaryAlt: 'primaryAlt', + secondary: 'secondary', + secondaryAlt: 'secondaryAlt', + default: 'default', + link: 'link', + }, + 'secondary' + ), + size: select( + 'size', + { + xl: 'xl', + lgxl: 'lgxl', + lg: 'lg', + md: 'md', + sm: 'sm', + xs: 'xs', + }, + 'lg' + ), + spinButtonOnLoading: boolean('spinButtonOnLoading', false), + } + return