diff --git a/docs/components/Layout/index.jsx b/docs/components/Layout/index.jsx index 8b22486e9..e9c456517 100644 --- a/docs/components/Layout/index.jsx +++ b/docs/components/Layout/index.jsx @@ -10,7 +10,6 @@ import SearchResultCard from '../SearchResultCard'; import ButtonExample from '../../examples/ButtonExample'; import AlertInputExample from '../../examples/AlertInputExample'; import FilePickerExample from '../../examples/FilePickerExample'; -import SpinnerButtonExample from '../../examples/SpinnerButtonExample'; import TextareaExample from '../../examples/TextareaExample'; import TextEllipsisExample from '../../examples/TextEllipsisExample'; import AlertExample from '../../examples/AlertExample'; @@ -69,7 +68,6 @@ ContentArea.propTypes = SidebarArea.propTypes; const componentsBySection = { 'form-elements': [ 'button', - 'spinner-button', 'alert-input', 'file-picker', 'textarea', @@ -170,7 +168,6 @@ class PageLayout extends React.Component { - diff --git a/docs/examples/ButtonExample.jsx b/docs/examples/ButtonExample.jsx index f811f86f7..6d79e0cb4 100644 --- a/docs/examples/ButtonExample.jsx +++ b/docs/examples/ButtonExample.jsx @@ -92,6 +92,7 @@ export const exampleProps = { propType: 'inverse', type: 'bool', note: 'Renders an inverse button. Can be used with bsStyle to create primary inverse buttons.', + defaultValue: 'false', }, { propType: 'reason', @@ -112,6 +113,12 @@ export const exampleProps = { ), }, + { + propType: 'isLoading', + type: 'bool', + defaultValue: 'false', + note: 'set this to true to display Spinner and disable the button', + }, ], }; diff --git a/docs/examples/SpinnerButtonExample.jsx b/docs/examples/SpinnerButtonExample.jsx deleted file mode 100644 index e9a27cb55..000000000 --- a/docs/examples/SpinnerButtonExample.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import Example from '../components/Example'; -import { SpinnerButton } from '../../src/dist-entry'; - -class SpinnerButtonExample extends React.Component { - constructor() { - super(); - this.state = { - isLoading: false, - }; - this.onClick = () => { - this.setState({ isLoading: true }); - setTimeout(() => { - this.setState({ isLoading: false }); - }, 400 + Math.floor(Math.random() * 1200) + 1); // simulate a random wait interval - }; - } - - render() { - const onClick = this.onClick; - const { isLoading } = this.state; - return ( - - Save Details - - ); - } -} - -const exampleProps = { - componentName: 'SpinnerButton', - notes: ( -

- Extends the above

Button
component with added loading state. -

- ), - designNotes: ( -

- Spinner button is used when we have a delayed response fetching - information/data and require to inform the user on-click that we working to deliver their action. -

- ), - exampleCodeSnippet: ` - Save Details -`, - propTypes: [ - { - propType: 'isLoading', - type: 'bool', - defaultValue: 'false', - note: ( - - When
isLoading
is
true
the button is disabled. -
- ), - }, - { - propType: 'onClick', - type: 'func', - note: ( - - Should set
isLoading
to true, and reset to
false
upon completion. -
- ), - }, - ], -}; - -export default () => ( - - - -); diff --git a/src/components/adslot-ui/SpinnerButton/index.jsx b/src/components/adslot-ui/SpinnerButton/index.jsx deleted file mode 100644 index 79f6c1219..000000000 --- a/src/components/adslot-ui/SpinnerButton/index.jsx +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable react/prop-types */ -import _ from 'lodash'; -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { Button } from 'third-party'; -import Spinner from 'alexandria/Spinner'; -import { expandDts } from 'lib/utils'; - -require('./styles.scss'); - -class SpinnerButton extends React.PureComponent { - constructor(props) { - super(props); - this.props = props; - } - - render() { - const { isLoading, children, dts } = this.props; - return ( - - ); - } -} - -SpinnerButton.propTypes = _.assign( - { - isLoading: PropTypes.bool, - dts: PropTypes.string, - }, - Button.propTypes -); - -SpinnerButton.defaultProps = { - isLoading: false, -}; - -export default SpinnerButton; diff --git a/src/components/adslot-ui/SpinnerButton/index.spec.jsx b/src/components/adslot-ui/SpinnerButton/index.spec.jsx deleted file mode 100644 index 1ae53c250..000000000 --- a/src/components/adslot-ui/SpinnerButton/index.spec.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import SpinnerButton from 'adslot-ui/SpinnerButton'; -import { Button } from 'third-party'; -import Spinner from 'alexandria/Spinner'; - -describe('SpinnerButtonComponent', () => { - it('should render with defaults', () => { - const element = shallow( - - Test - - ); - expect(element.find(Spinner)).to.have.length(0); - - const buttonElement = element.find(Button); - expect(buttonElement.prop('disabled')).to.equal(true); - expect(buttonElement.prop('reason')).to.equal('Reason 1'); - expect(buttonElement.prop('isLoading')).to.equal(undefined); - expect( - element - .children() - .last() - .text() - ).to.equal('Test'); - }); - - it('should pass props to button', () => { - const element = shallow( - - Test - - ); - expect(element.find(Spinner)).to.have.length(1); - - const buttonElement = element.find('Button[data-test-selector="test"]'); - expect(buttonElement.prop('bsStyle')).to.equal('primary'); - expect(buttonElement.prop('bsSize')).to.equal('lg'); - expect(buttonElement.prop('className')).to.equal('spinner-button-component my-class'); - expect(buttonElement.prop('isLoading')).to.equal(undefined); - }); - - it('should be disabled in loading mode', () => { - const buttonElement = shallow(Test).find('Button'); - - expect(buttonElement.prop('disabled')).to.equal(true); - }); - - it('should honour disabled when not loading', () => { - const buttonElement = shallow(Test).find('Button'); - - expect(buttonElement.prop('isLoading')).to.equal(undefined); - expect(buttonElement.prop('disabled')).to.equal(true); - }); - - it('should ignore disabled when loading', () => { - const buttonElement = shallow( - - Test - - ).find('Button'); - expect(buttonElement.prop('disabled')).to.equal(true); - }); -}); diff --git a/src/components/adslot-ui/index.js b/src/components/adslot-ui/index.js index bbf60f613..8043e0470 100644 --- a/src/components/adslot-ui/index.js +++ b/src/components/adslot-ui/index.js @@ -11,7 +11,6 @@ import PagedGrid from 'adslot-ui/PagedGrid'; import Panel from 'adslot-ui/Panel'; import Search from 'adslot-ui/Search'; import SearchBar from 'adslot-ui/SearchBar'; -import SpinnerButton from 'adslot-ui/SpinnerButton'; import SplitPane from 'adslot-ui/SplitPane'; import Textarea from 'adslot-ui/Textarea'; import TextEllipsis from 'adslot-ui/TextEllipsis'; @@ -41,7 +40,6 @@ export { Panel, Search, SearchBar, - SpinnerButton, SplitPane, Textarea, TextEllipsis, diff --git a/src/components/third-party/bootstrap/Button/index.jsx b/src/components/third-party/bootstrap/Button/index.jsx index b1e48fc35..c650b1eb4 100644 --- a/src/components/third-party/bootstrap/Button/index.jsx +++ b/src/components/third-party/bootstrap/Button/index.jsx @@ -7,9 +7,23 @@ import classNames from 'classnames'; import BootstrapButton from 'react-bootstrap/lib/Button'; import BootstrapPopover from 'react-bootstrap/lib/Popover'; import BootstrapOverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import Spinner from 'alexandria/Spinner'; import { expandDts } from 'lib/utils'; +import './styles.scss'; class Button extends React.PureComponent { + renderSpinner() { + if (this.props.isLoading) { + return ( +
+ +
+ ); + } + + return null; + } + renderWithReason() { const popover = ( @@ -24,15 +38,20 @@ class Button extends React.PureComponent { } renderButton() { - const { inverse, children, dts, className } = this.props; + const { inverse, children, dts, className, isLoading, disabled } = this.props; + const classes = classNames('button-component', className, { + 'btn-inverse': inverse && !/btn-inverse/.test(className), + }); return ( - {children} + {this.renderSpinner()} +
{children}
); } @@ -48,12 +67,14 @@ Button.propTypes = _.assign( inverse: PropTypes.bool, reason: PropTypes.string, dts: PropTypes.string, + isLoading: PropTypes.bool, }, BootstrapButton.propTypes ); Button.defaultProps = { inverse: false, + isLoading: false, }; export default Button; diff --git a/src/components/third-party/bootstrap/Button/index.spec.jsx b/src/components/third-party/bootstrap/Button/index.spec.jsx index 9bcdcd896..10debcc7f 100644 --- a/src/components/third-party/bootstrap/Button/index.spec.jsx +++ b/src/components/third-party/bootstrap/Button/index.spec.jsx @@ -1,8 +1,9 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Button } from 'third-party'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import BootstrapButton from 'react-bootstrap/lib/Button'; +import { Button } from 'third-party'; +import Spinner from 'alexandria/Spinner'; describe('ButtonComponent', () => { it('should render Bootstrap Button', () => { @@ -12,12 +13,12 @@ describe('ButtonComponent', () => { it('should support legacy classname btn-inverse for non-breaking change', () => { const element = shallow(); - expect(element.prop('className')).to.equal('btn-inverse'); + expect(element.prop('className')).to.equal('button-component btn-inverse'); }); it('should support className prop', () => { const element = shallow(); - expect(element.prop('className')).to.equal('all the-classes'); + expect(element.prop('className')).to.equal('button-component all the-classes'); }); it('should not duplicate btn-inverse class if both legacy and new are used', () => { @@ -26,12 +27,12 @@ describe('ButtonComponent', () => { Test ); - expect(element.prop('className')).to.equal('btn-inverse'); + expect(element.prop('className')).to.equal('button-component btn-inverse'); }); it('should render inverse button with btn-inverse class', () => { const element = shallow(); - expect(element.prop('className')).to.equal('btn-inverse'); + expect(element.prop('className')).to.equal('button-component btn-inverse'); }); it('should support data-test-selectors', () => { @@ -54,4 +55,19 @@ describe('ButtonComponent', () => { expect(overlay).to.have.length(1); expect(shallow(overlay.prop('overlay')).text()).to.eql('Because'); }); + + it('should render Spinner if isLoading is true', () => { + const element = shallow(); + expect(element.find(Spinner)).to.have.length(1); + }); + + it('should only allow bsSize medium or small on Spinner', () => { + const element = shallow( + + ); + const spinnerEl = element.find(Spinner); + expect(spinnerEl.prop('size')).to.equal('medium'); + }); }); diff --git a/src/components/adslot-ui/SpinnerButton/styles.scss b/src/components/third-party/bootstrap/Button/styles.scss similarity index 94% rename from src/components/adslot-ui/SpinnerButton/styles.scss rename to src/components/third-party/bootstrap/Button/styles.scss index 0252b2215..ec06d21ae 100644 --- a/src/components/adslot-ui/SpinnerButton/styles.scss +++ b/src/components/third-party/bootstrap/Button/styles.scss @@ -1,4 +1,4 @@ -.spinner-button-component { +.button-component { position: relative; &-children-container { diff --git a/src/dist-entry/core.js b/src/dist-entry/core.js index 56308e566..bb60b682f 100644 --- a/src/dist-entry/core.js +++ b/src/dist-entry/core.js @@ -61,7 +61,6 @@ import { Panel, Search, SearchBar, - SpinnerButton, SplitPane, Textarea, TextEllipsis, @@ -117,7 +116,6 @@ export { Select, Slicey, Spinner, - SpinnerButton, SplitPane, Statistic, SvgSymbol,