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

Commit

Permalink
Merge b33e4e5 into d4496bc
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Quach committed Apr 16, 2019
2 parents d4496bc + b33e4e5 commit c9693ce
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 162 deletions.
32 changes: 32 additions & 0 deletions 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'
Expand Down Expand Up @@ -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)}
Expand Down Expand Up @@ -471,6 +484,12 @@ export const ButtonContentUI = styled('span')`
pointer-events: none;
}
`};
${({ isLoading }) =>
isLoading &&
`
opacity: 0;
`};
`

export const FocusUI = styled('span')`
Expand Down Expand Up @@ -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,
}
26 changes: 24 additions & 2 deletions src/components/Button/ButtonV2.js
Expand Up @@ -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'

Expand All @@ -20,15 +25,18 @@ type Props = {
children?: any,
className?: string,
disabled: boolean,
disableOnLoading: boolean,
kind: ButtonKind,
innerRef: (ref: any) => void,
isActive: boolean,
isBlock: boolean,
isFirst: boolean,
isNotOnly: boolean,
isLast: boolean,
isLoading: boolean,
isSuffix: boolean,
size: ButtonSize,
spinButtonOnLoading: boolean,
state?: UIState,
submit: boolean,
theme?: string,
Expand All @@ -40,6 +48,7 @@ class Button extends Component<Props> {
buttonRef: noop,
canRenderFocus: true,
disable: false,
disableOnLoading: true,
kind: 'default',
innerRef: noop,
isActive: false,
Expand All @@ -49,6 +58,7 @@ class Button extends Component<Props> {
isLast: false,
isSuffix: false,
size: 'md',
spinButtonOnLoading: false,
submit: false,
}

Expand Down Expand Up @@ -132,15 +142,19 @@ class Button extends Component<Props> {
allowContentEventPropogation,
children,
className,
disabled,
disableOnLoading,
kind,
innerRef,
isActive,
isBlock,
isFirst,
isNotOnly,
isLast,
isLoading,
isSuffix,
size,
spinButtonOnLoading,
state,
submit,
theme,
Expand All @@ -149,16 +163,21 @@ class Button extends Component<Props> {
...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
Expand All @@ -172,12 +191,15 @@ class Button extends Component<Props> {
<ButtonUI
{...getValidProps(rest)}
className={componentClassName}
disabled={isDisabled}
innerRef={this.setInnerRef}
type={type}
>
{isLoading ? <SpinnerUI /> : null}
<ButtonContentUI
className="c-ButtonV2__content"
allowContentEventPropogation={allowContentEventPropogation}
className="c-ButtonV2__content"
isLoading={isLoading}
>
{this.getChildrenMarkup()}
</ButtonContentUI>
Expand Down
44 changes: 44 additions & 0 deletions src/components/Button/__tests__/ButtonV2.test.js
Expand Up @@ -323,3 +323,47 @@ describe('Link', () => {
expect(wrapper.find('button').length).toBeTruthy()
})
})

describe('Loading', () => {
test('Add loading className, if isLoading', () => {
const wrapper = mount(<Button isLoading />)
const el = wrapper.find('button')

expect(el.hasClass('is-loading')).toBeTruthy()
})

test('Renders a spinner if isLoading', () => {
const wrapper = mount(<Button isLoading />)
const el = wrapper.find('div.c-Spinner')

expect(el.length).toBeTruthy()
})

test('Does not renders a spinner if not isLoading', () => {
const wrapper = mount(<Button isLoading={false} />)
const el = wrapper.find('div.c-Spinner')

expect(el.length).toBeFalsy()
})

test('Becomes disabled if isLoading, by default', () => {
const wrapper = mount(<Button isLoading />)
const el = wrapper.find('button')

expect(el.prop('disabled')).toBe(true)
})

test('Does not become disabled, if specified', () => {
const wrapper = mount(<Button isLoading disableOnLoading={false} />)
const el = wrapper.find('button')

expect(el.prop('disabled')).toBe(false)
})

test('Add special spinButtonOnLoading, if isLoading and enabled', () => {
const wrapper = mount(<Button isLoading spinButtonOnLoading />)
const el = wrapper.find('button')

expect(el.hasClass('is-spinButtonOnLoading')).toBeTruthy()
})
})
13 changes: 8 additions & 5 deletions src/components/Button/docs/ButtonV2.md
Expand Up @@ -26,21 +26,24 @@ Alternatively, [PropProvider](../../PropProvider) can be used to set this prop a

| Prop | Type | Description |
| ---------------------------- | ---------- | ------------------------------------------------------------------------------- |
| allowContentEventPropogation | `bool` | Enables child events to pass through to Button. Default `true`. |
| allowContentEventPropogation | `boolean` | Enables child events to pass through to Button. Default `true`. |
| className | `string` | Custom class names to be added to the component. |
| disabled | `bool` | Disable the button so it can't be clicked. |
| disabled | `boolean` | Disable the button so it can't be clicked. |
| disabledOnLoading | `boolean` | Disables the button when `isLoading` is true. Default `true`. |
| fetch | `function` | function which returns a promise, will be invoked before routing the `to` route |
| href | `string` | Hyperlink for the button. This transforms the button to a `<a>` selector. |
| innerRef | `function` | Retrieves the `button` DOM node. |
| isFocused | `bool` | Renders the focused style. |
| isSuffix | `bool` | Renders suffix styles. |
| isFocused | `boolean` | Renders the focused style. |
| isLoading | `boolean` | Renders a loading [Spinner](../../Spinner). |
| isSuffix | `boolean` | Renders suffix styles. |
| onBlur | `function` | `onBlur` event handler. |
| onClick | `function` | `onClick` event handler. |
| onFocus | `function` | `onFocus` event handler. |
| kind | `string` | Applies the specified style to the button. |
| size | `string` | Sets the size of the button. Can be one of `"sm"`, `"md"` or `"lg"`. |
| spinButtonOnLoading | `boolean` | A special property that... spins the button if `isLoading`. |
| state | `string` | Applies state styles to the button. |
| submit | `bool` | Sets the `type` of the button to `"submit"`. |
| submit | `boolean` | Sets the `type` of the button to `"submit"`. |
| version | `number` | Applies the version `2` variant of the button. |
| theme | `string` | Applies a theme based style to the button. |
| to | `string` | React Router path to navigate on click. |
Expand Down
25 changes: 8 additions & 17 deletions src/components/Spinner/README.md
Expand Up @@ -2,7 +2,6 @@

A Spinner component provides the UI to indicate a loading state.


## Example

```jsx
Expand All @@ -12,21 +11,13 @@ A Spinner component provides the UI to indicate a loading state.
</div>
```


## Props

| Prop | Type | Description |
| --- | --- | --- |
| className | `string` | Custom class names to be added to the component. |
| size | `string` | Determines the size of the spinner [Icon](../Icon). Default `md`. |


### Sizes

| Prop | Description |
| --- | --- |
| `xl` | Renders a 32x32 (px) spinner. |
| `lg` | Renders a 24x24 (px) spinner. |
| `md` | Renders a 20x20 (px) spinner. |
| `sm` | Renders a 14x14 (px) spinner. |
| `xs` | Renders a 10x10 (px) spinner. |
| Prop | Type | Description |
| --------- | ----------------- | ---------------------------------------------------------- |
| className | `string` | Custom class names to be added to the component. |
| color | `string` | Color of the spinner. Default `currentColor`. |
| isRounded | `boolean` | Rounds the stroke ends of the spinner SVG. Default `true`. |
| shade | `string` | Determines the opacity of the spinner. |
| size | `number`/`string` | Determines the size of the spinner. Default `16`. |
| speed | `number` | Speed of the spinning animation (in `ms`). Default `1400`. |
85 changes: 85 additions & 0 deletions src/components/Spinner/Spinner.css.ts
@@ -0,0 +1,85 @@
import baseStyles from '../../styles/resets/base.css.js'
import styled from '../styled'

export const config = {
defaultSize: 16,
shades: {
extraMuted: '0.3',
faint: '0.4',
muted: '0.5',
slightlyMuted: '0.6',
subtle: '0.7',
},
}

export const SpinnerUI = styled('div')(props => {
const { spinnerSize } = props

return `
${baseStyles};
display: block;
width: ${spinnerSize}px;
height: ${spinnerSize}px;
`
})

export const SpinnerSVGUI = styled('svg')(props => {
const { speed, spinnerSize } = props

return `
${baseStyles};
animation: SpinnerUIAnimation ${speed}ms linear infinite;
display: block;
width: ${spinnerSize}px;
height: ${spinnerSize}px;
x: 0px;
y: 0px;
will-change: transform;
@keyframes SpinnerUIAnimation {
100% {
transform: rotate(360deg);
}
}
`
})

export const SpinnerCircleUI = styled('circle')(props => {
const { color, shade, isRounded, speed } = props

const lineCap = isRounded ? 'round' : 'square'
const opacity = config.shades[shade]

return `
${baseStyles};
animation: SpinnerCircleUIAnimation ${speed}ms ease-in-out infinite;
display: block;
fill: transparent;
opacity: ${opacity};
stroke: ${color};
stroke-width: 3.6;
stroke-linecap: ${lineCap};
stroke-dasharray: 80px, 200px;
stroke-dashoffset: 0px;
will-change: transform, stroke-dashoffset;
@keyframes SpinnerCircleUIAnimation {
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0px;
opacity: 0;
}
0.1% {
opacity: 1;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -125px;
}
}
`
})

0 comments on commit c9693ce

Please sign in to comment.