Merge pull request #4845 from MetaMask/button-group
Add ButtonGroup componentfeature/default_network_editable
commit
1f9c52fbf0
@ -0,0 +1,61 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import classnames from 'classnames' |
||||
|
||||
export default class ButtonGroup extends PureComponent { |
||||
static propTypes = { |
||||
defaultActiveButtonIndex: PropTypes.number, |
||||
disabled: PropTypes.bool, |
||||
children: PropTypes.array, |
||||
className: PropTypes.string, |
||||
style: PropTypes.object, |
||||
} |
||||
|
||||
static defaultProps = { |
||||
className: 'button-group', |
||||
} |
||||
|
||||
state = { |
||||
activeButtonIndex: this.props.defaultActiveButtonIndex || 0, |
||||
} |
||||
|
||||
handleButtonClick (activeButtonIndex) { |
||||
this.setState({ activeButtonIndex }) |
||||
} |
||||
|
||||
renderButtons () { |
||||
const { children, disabled } = this.props |
||||
|
||||
return React.Children.map(children, (child, index) => { |
||||
return child && ( |
||||
<button |
||||
className={classnames( |
||||
'button-group__button', |
||||
{ 'button-group__button--active': index === this.state.activeButtonIndex }, |
||||
)} |
||||
onClick={() => { |
||||
this.handleButtonClick(index) |
||||
child.props.onClick && child.props.onClick() |
||||
}} |
||||
disabled={disabled || child.props.disabled} |
||||
key={index} |
||||
> |
||||
{ child.props.children } |
||||
</button> |
||||
) |
||||
}) |
||||
} |
||||
|
||||
render () { |
||||
const { className, style } = this.props |
||||
|
||||
return ( |
||||
<div |
||||
className={className} |
||||
style={style} |
||||
> |
||||
{ this.renderButtons() } |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
import React from 'react' |
||||
import { storiesOf } from '@storybook/react' |
||||
import { action } from '@storybook/addon-actions' |
||||
import ButtonGroup from './' |
||||
import Button from '../button' |
||||
import { text, boolean } from '@storybook/addon-knobs/react' |
||||
|
||||
storiesOf('ButtonGroup', module) |
||||
.add('with Buttons', () => |
||||
<ButtonGroup |
||||
style={{ width: '300px' }} |
||||
disabled={boolean('Disabled', false)} |
||||
defaultActiveButtonIndex={1} |
||||
> |
||||
<Button |
||||
onClick={action('cheap')} |
||||
> |
||||
{text('Button1', 'Cheap')} |
||||
</Button> |
||||
<Button |
||||
onClick={action('average')} |
||||
> |
||||
{text('Button2', 'Average')} |
||||
</Button> |
||||
<Button |
||||
onClick={action('fast')} |
||||
> |
||||
{text('Button3', 'Fast')} |
||||
</Button> |
||||
</ButtonGroup> |
||||
) |
||||
.add('with a disabled Button', () => |
||||
<ButtonGroup |
||||
style={{ width: '300px' }} |
||||
disabled={boolean('Disabled', false)} |
||||
> |
||||
<Button |
||||
onClick={action('enabled')} |
||||
> |
||||
{text('Button1', 'Enabled')} |
||||
</Button> |
||||
<Button |
||||
onClick={action('disabled')} |
||||
disabled |
||||
> |
||||
{text('Button2', 'Disabled')} |
||||
</Button> |
||||
</ButtonGroup> |
||||
) |
@ -0,0 +1 @@ |
||||
export { default } from './button-group.component' |
@ -0,0 +1,38 @@ |
||||
.button-group { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
|
||||
&__button { |
||||
font-family: Roboto; |
||||
font-size: 1rem; |
||||
color: $tundora; |
||||
border-style: solid; |
||||
border-color: $alto; |
||||
border-width: 1px 1px 1px; |
||||
border-left: 0; |
||||
flex: 1; |
||||
padding: 12px; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
|
||||
&:first-child { |
||||
border-left: 1px solid $alto; |
||||
border-radius: 4px 0 0 4px; |
||||
} |
||||
|
||||
&:last-child { |
||||
border-radius: 0 4px 4px 0; |
||||
} |
||||
|
||||
&--active { |
||||
background-color: $dodger-blue; |
||||
color: $white; |
||||
} |
||||
|
||||
&:disabled { |
||||
opacity: .5; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,97 @@ |
||||
import React from 'react' |
||||
import assert from 'assert' |
||||
import { shallow } from 'enzyme' |
||||
import sinon from 'sinon' |
||||
import ButtonGroup from '../button-group.component.js' |
||||
|
||||
const childButtonSpies = { |
||||
onClick: sinon.spy(), |
||||
} |
||||
|
||||
sinon.spy(ButtonGroup.prototype, 'handleButtonClick') |
||||
sinon.spy(ButtonGroup.prototype, 'renderButtons') |
||||
|
||||
const mockButtons = [ |
||||
<button onClick={childButtonSpies.onClick} key={'a'}><div className="mockClass" /></button>, |
||||
<button onClick={childButtonSpies.onClick} key={'b'}></button>, |
||||
<button onClick={childButtonSpies.onClick} key={'c'}></button>, |
||||
] |
||||
|
||||
describe('ButtonGroup Component', function () { |
||||
let wrapper |
||||
|
||||
beforeEach(() => { |
||||
wrapper = shallow(<ButtonGroup |
||||
defaultActiveButtonIndex={1} |
||||
disabled={false} |
||||
className="someClassName" |
||||
style={ { color: 'red' } } |
||||
>{mockButtons}</ButtonGroup>) |
||||
}) |
||||
|
||||
afterEach(() => { |
||||
childButtonSpies.onClick.resetHistory() |
||||
ButtonGroup.prototype.handleButtonClick.resetHistory() |
||||
ButtonGroup.prototype.renderButtons.resetHistory() |
||||
}) |
||||
|
||||
describe('handleButtonClick', () => { |
||||
it('should set the activeButtonIndex', () => { |
||||
assert.equal(wrapper.state('activeButtonIndex'), 1) |
||||
wrapper.instance().handleButtonClick(2) |
||||
assert.equal(wrapper.state('activeButtonIndex'), 2) |
||||
}) |
||||
}) |
||||
|
||||
describe('renderButtons', () => { |
||||
it('should render a button for each child', () => { |
||||
const childButtons = wrapper.find('.button-group__button') |
||||
assert.equal(childButtons.length, 3) |
||||
}) |
||||
|
||||
it('should render the correct button with an active state', () => { |
||||
const childButtons = wrapper.find('.button-group__button') |
||||
const activeChildButton = wrapper.find('.button-group__button--active') |
||||
assert.deepEqual(childButtons.get(1), activeChildButton.get(0)) |
||||
}) |
||||
|
||||
it('should call handleButtonClick and the respective button\'s onClick method when a button is clicked', () => { |
||||
assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0) |
||||
assert.equal(childButtonSpies.onClick.callCount, 0) |
||||
const childButtons = wrapper.find('.button-group__button') |
||||
childButtons.at(0).props().onClick() |
||||
childButtons.at(1).props().onClick() |
||||
childButtons.at(2).props().onClick() |
||||
assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 3) |
||||
assert.equal(childButtonSpies.onClick.callCount, 3) |
||||
}) |
||||
|
||||
it('should render all child buttons as disabled if props.disabled is true', () => { |
||||
const childButtons = wrapper.find('.button-group__button') |
||||
childButtons.forEach(button => { |
||||
assert.equal(button.props().disabled, undefined) |
||||
}) |
||||
wrapper.setProps({ disabled: true }) |
||||
const disabledChildButtons = wrapper.find('[disabled=true]') |
||||
assert.equal(disabledChildButtons.length, 3) |
||||
}) |
||||
|
||||
it('should render the children of the button', () => { |
||||
const mockClass = wrapper.find('.mockClass') |
||||
assert.equal(mockClass.length, 1) |
||||
}) |
||||
}) |
||||
|
||||
describe('render', () => { |
||||
it('should render a div with the expected class and style', () => { |
||||
assert.equal(wrapper.find('div').at(0).props().className, 'someClassName') |
||||
assert.deepEqual(wrapper.find('div').at(0).props().style, { color: 'red' }) |
||||
}) |
||||
|
||||
it('should call renderButtons when rendering', () => { |
||||
assert.equal(ButtonGroup.prototype.renderButtons.callCount, 1) |
||||
wrapper.instance().render() |
||||
assert.equal(ButtonGroup.prototype.renderButtons.callCount, 2) |
||||
}) |
||||
}) |
||||
}) |
Loading…
Reference in new issue