Skip to content

Commit

Permalink
Merge branch 'master' of github.com:dustinengle/bulwark-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinengle committed Sep 4, 2018
2 parents 47e53a2 + 06b50cd commit fc62d19
Show file tree
Hide file tree
Showing 13 changed files with 568 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"node-sass": "^4.9.2",
"prop-types": "^15.6.2",
"sass-loader": "^7.0.3",
"sinon": "^6.1.6",
"standard": "^11.0.1",
"style-loader": "^0.21.0",
"webpack": "^4.16.1",
Expand Down
63 changes: 63 additions & 0 deletions react/Checkbox.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import PropTypes from 'prop-types'
import React from 'react'
import {pickRest} from '../lib/utils'

export default class Checkbox extends React.Component {
static defaultProps = {
as: 'span',
defaultChecked: false
}

static propTypes = {
as: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
primary: PropTypes.bool,
secondary: PropTypes.bool,
red: PropTypes.bool,
disabled: PropTypes.bool,
indeterminate: PropTypes.bool,
defaultChecked: PropTypes.bool,
checked: PropTypes.bool,
value: PropTypes.any,
toggle: PropTypes.bool
}

state = {
checked: false
}

componentWillMount () {
if (this.props.defaultChecked) this.setState({checked: true})
}

canToggle = () => !this.props.disabled && !this.isControlled()

isControlled = () => this.props.checked !== undefined

handleClick = (ev) => {
const controlled = this.isControlled()
const checked = controlled ? this.props.checked : this.state.checked
if (!controlled) {
if (this.canToggle()) {
this.setState({
checked: !checked
})
}
}
if (this.props.onChange) {
this.props.onChange(ev, {...this.props, checked: !checked})
}
}

handleKeyDown = (ev) => {
if (ev.key === 'Enter' || ev.key === ' ') {
this.handleClick(ev)
}
}

render () {
const [mods, { children, as, checked, onChange, value, ...rest }] = pickRest(this.props, ['primary', 'secondary', 'red', 'disabled', 'indeterminate', 'switch'])
mods.checked = this.isControlled() ? checked : this.state.checked
const As = as
return <As block='checkbox' mods={mods} onClick={this.handleClick} onKeyDown={this.handleKeyDown} tabIndex={0} {...rest} />
}
}
62 changes: 62 additions & 0 deletions react/Radio.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import PropTypes from 'prop-types'
import React from 'react'
import {pickRest} from '../lib/utils'

export default class Radio extends React.Component {
static defaultProps = {
as: 'span',
defaultChecked: false
}

static propTypes = {
as: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
primary: PropTypes.bool,
secondary: PropTypes.bool,
red: PropTypes.bool,
disabled: PropTypes.bool,
defaultChecked: PropTypes.bool,
checked: PropTypes.bool,
value: PropTypes.any
}

state = {
checked: false
}

componentWillMount() {
if (this.props.defaultChecked || this.props.checked) {
this.setState({checked: this.props.defaultChecked || this.props.checked})
}
}

canToggle = () => !this.props.disabled && !this.isControlled() && !this.state.checked

isControlled = () => this.props.checked !== undefined

handleClick = (ev) => {
const controlled = this.isControlled()
if (!controlled) {
if (this.canToggle()) {
this.setState({
checked: true
})
}
}
if (this.props.onChange) {
this.props.onChange(ev, {...this.props, checked: true})
}
}

handleKeyDown = (ev) => {
if (ev.key === 'Enter' || ev.key === ' ') {
this.handleClick(ev)
}
}

render () {
const [mods, {children, as, checked, onChange, value, ...rest}] = pickRest(this.props, ['primary', 'secondary', 'red', 'disabled', 'radio'])
mods.checked = this.isControlled() ? checked : this.state.checked
const As = as
return <As block='radio' mods={mods} onClick={this.handleClick} onKeyDown={this.handleKeyDown} tabIndex={0} {...rest} />
}
}
2 changes: 2 additions & 0 deletions sass/_library.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import 'inc/badge';
@import 'inc/button';
@import 'inc/card';
@import 'inc/checkbox';
@import 'inc/drawer';
@import 'inc/grid';
@import 'inc/input';
Expand All @@ -12,6 +13,7 @@
@import 'inc/navbar';
@import 'inc/pagination';
@import 'inc/panel';
@import 'inc/radio';
@import 'inc/reset';
@import 'inc/stepper';
@import 'inc/tab';
Expand Down
3 changes: 2 additions & 1 deletion sass/inc/_animation.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
$animation-button-hover: background-color .3s;
$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$animation-checkbox-click: transform .3s;
75 changes: 75 additions & 0 deletions sass/inc/_checkbox.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.checkbox {
display: inline-block;
height: 24px;
width: 24px;
border: 1px solid $black;
border-radius: 5px;
margin: 0 5px;
color: $white;
text-align: center;
vertical-align: top;

&--primary { border-color: $primary }
&--secondary { border-color: $secondary }
&--red { border-color: $red }
&--disabled {
border-color: #C8C8C8;
cursor: not-allowed;
}

$self: &;

&--checked:not(#{$self}--switch) {
border: 0;
background-color: $black;
&#{$self}--primary { background-color: $primary }
&#{$self}--secondary { background-color: $secondary }
&#{$self}--red { background-color: $red }
&#{$self}--disabled { background-color: #C8C8C8; }

&::after {
content: "\2713";
}
&#{$self}--indeterminate::after {
content: "\2015";
}
}

&--switch {
background-color: #D8D8D8;
width: 48px;
border-radius: 12px;
border: none;

&::after {
content: "";
display: block;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: $white;
box-shadow: 0 0 5px 0 rgba(0,0,0,0.25);
transition: $animation-checkbox-click;
box-sizing: content-box;
// background-clip: padding-box;
}

&#{$self}--checked {
&::after {
background-color: $black;
transform: translate(24px);
}
&#{$self}--primary::after { background-color: $primary }
&#{$self}--secondary::after { background-color: $secondary }
&#{$self}--red::after { background-color: $red }
}

// &#{$self}:focus-visible::after {
// margin-left: -10px;
// margin-top: -10px;
// border: 10px solid rgba(0,0,0,0.05);
// }

&#{$self}--disabled::after { background-color: #C8C8C8; }
}
}
38 changes: 38 additions & 0 deletions sass/inc/_radio.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.radio {
display: inline-block;
height: 24px;
width: 24px;
border: 1px solid $black;
border-radius: 50%;
margin: 0 5px;
color: $white;
text-align: center;
vertical-align: top;

&--primary { border-color: $primary; }
&--secondary { border-color: $secondary; }
&--red { border-color: $red; }
&--disabled {
border-color: #C8C8C8;
cursor: not-allowed;
}

$self: &;

&--checked {
&::after {
content: "";
border-radius: 50%;
display: block;
margin: 4px;
height: 14px;
width: 14px;
background-color: $black;
}

&#{$self}--primary::after { background-color: $primary; }
&#{$self}--secondary::after { background-color: $secondary; }
&#{$self}--red::after { background-color: $red; }
&#{$self}--disabled::after { background-color: #C8C8C8; }
}
}
91 changes: 91 additions & 0 deletions test/Checkbox.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* global describe, it */
import React from 'react'
import expect from 'must'
import { shallow } from 'enzyme'
import { spy } from 'sinon'
import Checkbox from '../react/Checkbox'

describe('<Checkbox />', () => {
it('renders as a span', () => {
const wrapper = shallow(<Checkbox />)
expect(wrapper.is('span')).to.be.true()
})

it('augments itself', () => {
const wrapper = shallow(<Checkbox as='a' />)
expect(wrapper.is('a')).to.be.true()
})

it('renders classes', () => {
const wrapper = shallow(<Checkbox primary secondary red disabled indeterminate />)
expect(wrapper.hasClass('checkbox')).to.be.true()
expect(wrapper.hasClass('checkbox--primary')).to.be.true()
expect(wrapper.hasClass('checkbox--secondary')).to.be.true()
expect(wrapper.hasClass('checkbox--red')).to.be.true()
expect(wrapper.hasClass('checkbox--disabled')).to.be.true()
expect(wrapper.hasClass('checkbox--indeterminate')).to.be.true()
})

it('passes extra props down', () => {
const fakeFunc = () => {}
const wrapper = shallow(<Checkbox onClick={fakeFunc} />)
expect(wrapper.find('span').prop('onClick')).to.equal(fakeFunc)
})

it('starts checked when defaultChecked passed', () => {
const wrapper = shallow(<Checkbox defaultChecked />)
expect(wrapper.state('checked')).to.be.true()
})

it('toggles state on click', () => {
const wrapper = shallow(<Checkbox />)
wrapper.find('span').simulate('click')
expect(wrapper.state('checked')).to.be.true()
})

it('doesn\'t toggle state on disabled click', () => {
const wrapper = shallow(<Checkbox disabled />)
wrapper.find('span').simulate('click')
expect(wrapper.state('checked')).to.be.false()
})

it('onChange callback is called', () => {
const callback = spy()
const wrapper = shallow(<Checkbox onChange={callback} />)
wrapper.find('span').simulate('click')
expect(callback.calledOnce).to.be.true()
})

describe('controlled components', () => {
it('can be controlled', () => {
const wrapper = shallow(<Checkbox checked />)
expect(wrapper.hasClass('checkbox--checked')).to.be.true()
})

it('don\'t toggle state when clicked', () => {
const wrapper = shallow(<Checkbox checked />)
wrapper.find('span').simulate('click')
expect(wrapper.state('checked')).to.be.false()
})
})

describe('keyboard', () => {
it('activates on enter', () => {
const wrapper = shallow(<Checkbox />)
wrapper.find('span').simulate('keyDown', {key: 'Enter'})
expect(wrapper.state('checked')).to.be.true()
})

it('activates on spacebar', () => {
const wrapper = shallow(<Checkbox />)
wrapper.find('span').simulate('keyDown', {key: ' '})
expect(wrapper.state('checked')).to.be.true()
})

it('doesn\'t activate on other', () => {
const wrapper = shallow(<Checkbox />)
wrapper.find('span').simulate('keyDown', {key: 'f'})
expect(wrapper.state('checked')).to.be.false()
})
})
})
Loading

0 comments on commit fc62d19

Please sign in to comment.