Skip to content

Commit

Permalink
Added <Switch> component, tests, and storybook (#433)
Browse files Browse the repository at this point in the history
* Added <Switch> component, tests, and storybook

* Update package.json version

* Update after code review

* Correct table props warning

* fix: bump up size limitation to account for new code
  • Loading branch information
mwislek authored Sep 18, 2020
1 parent f14db6e commit d50df25
Show file tree
Hide file tree
Showing 10 changed files with 510 additions and 280 deletions.
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Author Checklist

- [ ] Add unit test(s)
- [ ] Update version in package.json
- [ ] Update version in package.json (see the [versioning guidelines](https://github.com/LaunchPadLab/opex-public/blob/master/gists/npm-package-guidelines.md#pull-requests-and-deployments))
- [ ] Update documentation (if necessary)
- [ ] Add story to storybook (if necessary)
- [ ] Assign dev reviewer
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = [
{
path: 'lib/index.js',
limit: '185 KB',
limit: '200 KB',
ignore: ['react-dom'],
},
]
595 changes: 320 additions & 275 deletions docs.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@launchpadlab/lp-components",
"version": "3.33.0",
"version": "3.34.0",
"engines": {
"node": "^8.0.0 || ^10.13.0 || ^12.0.0"
},
Expand Down Expand Up @@ -50,6 +50,7 @@
"react-datepicker": "^1.0.3",
"react-modal": "^3.11.2",
"react-router": "^3.2.1",
"react-switch": "^5.0.1",
"redux-flash": "^2.0.1"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion src/forms/inputs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export RangeInput from './range-input'
export RadioGroup from './radio-group'
export Select from './select'
export SetterLink from './setter-link'
export Textarea from './textarea'
export Switch from './switch'
export Textarea from './textarea'
77 changes: 77 additions & 0 deletions src/forms/inputs/switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react'
import PropTypes from 'prop-types'
import { default as BaseSwitch } from 'react-switch'
import {
blurDirty,
hasInputError,
fieldPropTypesWithValue,
omitLabelProps,
replaceEmptyStringValue,
} from '../helpers'
import { LabeledField } from '../labels'
import { compose, generateInputErrorId } from '../../utils'

/**
*
* A switch input that can be used in a `redux-forms`-controlled form.
*
* This input only accepts and stores boolean values.
*
* See the [react-switch](https://github.com/markusenglund/react-switch) documentation for additional styling properties.
*
* @name Switch
* @type Function
* @param {Object} input - A `redux-forms` [input](http://redux-form.com/6.5.0/docs/api/Field.md/#input-props) object
* @param {Object} meta - A `redux-forms` [meta](http://redux-form.com/6.5.0/docs/api/Field.md/#meta-props) object
* @param {Element | Boolean} checkedIcon - An icon displayed when the switch is checked. Set to `false` if no check icon is desired.
* @param {Element | Boolean} uncheckedIcon - An icon displayed when the switch is unchecked. Set to `false` if no uncheck icon is desired.
*
* @example
*
* function CoolPersonForm ({ handleSubmit, pristine, invalid, submitting }) {
* return (
* <form onSubmit={ handleSubmit }>
* <Field name="isCool" component={ Switch } />
* <SubmitButton {...{ pristine, invalid, submitting }}>
* Submit
* </SubmitButton>
* </form>
* )
* }
*
* export default CoolPersonForm
*/

const propTypes = {
...fieldPropTypesWithValue(PropTypes.bool),
label: PropTypes.node,
}

function Switch (props) {
const {
input: { name, value, onBlur, onChange },
meta, // eslint-disable-line no-unused-vars
...rest
} = omitLabelProps(props)
return (
<LabeledField className="switch" { ...props }>
<BaseSwitch {...{
id: name,
name,
checked: value,
onBlur,
onChange: (checked) => onChange(checked),
'aria-describedby': hasInputError(meta) ? generateInputErrorId(name) : null,
...rest
}}
/>
</LabeledField>
)
}

Switch.propTypes = propTypes

export default compose(
blurDirty(),
replaceEmptyStringValue(false),
)(Switch)
64 changes: 64 additions & 0 deletions stories/forms/inputs/switch.story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { Switch as StaticSwitch } from 'src'
import dynamicInput from '../../dynamic-input'

const Switch = dynamicInput({
initialValue: false,
valuePath: 'input.value',
onChangePath: 'input.onChange'
})(StaticSwitch)

const inputProps = {
name: 'person.selected',
onChange: action('switch clicked')
}

storiesOf('Switch', module)
.add('with default label', () => (
<Switch
input={ inputProps }
meta={{}}
/>
))
.add('with custom label', () => (
<Switch
input={ inputProps }
meta={{}}
label="Custom Label"
/>
))
.add('with no label', () => (
<Switch
input={ inputProps }
meta={{}}
label={false}
/>
))
.add('with no icons', () => (
<Switch
input={ inputProps }
meta={{}}
checkedIcon={false}
uncheckedIcon={false}
/>
))
.add('with error', () => (
<Switch
input={ inputProps }
meta={{
invalid: true,
touched: true,
error: 'Invalid input'
}}
value="0000"
/>
))
.add('with a tooltip', () => (
<Switch
input={ inputProps }
meta={{}}
tooltip="I am a tooltip"
/>
))
35 changes: 35 additions & 0 deletions test/forms/inputs/switch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { mount } from 'enzyme'
import { Switch } from '../../../src'

test('Switch toggles value when clicked', () => {
const onChange = jest.fn()
const props = {
input: {
name: 'test',
value: false,
onChange,
},
meta: {}
}
const wrapper = mount(<Switch { ...props }/>)
wrapper.find('input').simulate('change')
const newValue = onChange.mock.calls[0][0]
expect(newValue).toEqual(true)
})

test('Switch is given an aria-describedby attribute when there is an input error', () => {
const name = "test"
const props = {
input: {
name,
value: false,
},
meta: {
touched: true,
invalid: true,
}
}
const wrapper = mount(<Switch { ...props }/>)
expect(wrapper.find('input').prop('aria-describedby')).toContain(name)
})
2 changes: 1 addition & 1 deletion test/tables/sortable-table.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ test('`valueGetter` column can be the initial column and is sorted ascending', (
const wrapper = mount(
<SortableTable data={data} initialColumn="opportunityName">
<Column name="opportunityName" valueGetter={myValueGetter} />
<Column accountName="accountName" />
<Column name="accountName" />
<Column name="name" />
</SortableTable>
)
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11030,6 +11030,13 @@ react-sizeme@^2.5.2, react-sizeme@^2.6.7:
shallowequal "^1.1.0"
throttle-debounce "^2.1.0"

react-switch@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-switch/-/react-switch-5.0.1.tgz#449277f4c3aed5286fffd0f50d5cbc2a23330406"
integrity sha512-Pa5kvqRfX85QUCK1Jv0rxyeElbC3aNpCP5hV0LoJpU/Y6kydf0t4kRriQ6ZYA4kxWwAYk/cH51T4/sPzV9mCgQ==
dependencies:
prop-types "^15.6.2"

react-syntax-highlighter@^8.0.1:
version "8.1.0"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-8.1.0.tgz#59103ff17a828a27ed7c8f035ae2558f09b6b78c"
Expand Down

0 comments on commit d50df25

Please sign in to comment.