Merge pull request #4845 from MetaMask/button-group

Add ButtonGroup component
feature/default_network_editable
Dan J Miller 6 years ago committed by GitHub
commit 1f9c52fbf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 61
      ui/app/components/button-group/button-group.component.js
  2. 49
      ui/app/components/button-group/button-group.stories.js
  3. 1
      ui/app/components/button-group/index.js
  4. 38
      ui/app/components/button-group/index.scss
  5. 97
      ui/app/components/button-group/tests/button-group-component.test.js
  6. 2
      ui/app/components/index.scss

@ -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)
})
})
})

@ -1,3 +1,5 @@
@import './button-group/index';
@import './export-text-container/index';
@import './selected-account/index';

Loading…
Cancel
Save