diff --git a/docs/toggle.md b/docs/toggle.md new file mode 100644 index 000000000..e59b5b027 --- /dev/null +++ b/docs/toggle.md @@ -0,0 +1,24 @@ +# Toggle +### Usage + +```jsx +import { Toggle } from 'radiance-ui'; + + +``` + + + +### Proptypes +| prop | propType | required | default | description +|-----------------------|------------------|----------|-----------|------------------------------------------------------------------------------------------------------------------------------| +| checked | bool | yes | - | controls the toggle state | +| label | string | no | '' | the toggle label | +| onChange | function | yes | - | the toggle handler function, this usually toggle the checked prop value | + +### Notes +The `` component is usually wrapped in a `container` element (with a fixed `width` style for example). The toggle and label are spread in the container (`space-between`) from edge to edge. \ No newline at end of file diff --git a/package.json b/package.json index b093a0f8c..0a36a57df 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "react": "^16.6.0", "react-modal": "^3.8.1", "react-slick": "^0.23.2", + "react-toggle-button": "^2.2.0", "react-transition-group": "^2.9.0", "tinycolor2": "^1.4.1" }, diff --git a/src/shared-components/index.js b/src/shared-components/index.js index 8720fc798..597bf17e7 100644 --- a/src/shared-components/index.js +++ b/src/shared-components/index.js @@ -23,6 +23,7 @@ export { default as OptionButton } from './optionButton'; export { default as ProgressBar } from './progressBar'; export { default as RadioButton } from './radioButton'; export { default as Tabs } from './tabs'; +export { default as Toggle } from './toggle'; export { default as Tooltip } from './tooltip'; export { default as Typography, style as TYPOGRAPHY_STYLE } from './typography'; export { FadeInContainer, opacityInAnimationStyle } from './transitions'; diff --git a/src/shared-components/toggle/__snapshots__/test.js.snap b/src/shared-components/toggle/__snapshots__/test.js.snap new file mode 100644 index 000000000..63093c9a1 --- /dev/null +++ b/src/shared-components/toggle/__snapshots__/test.js.snap @@ -0,0 +1,191 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` UI snapshot renders the component 1`] = ` +.emotion-2 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-flow: row nowrap; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + padding: 1rem 0; +} + +.emotion-2 > input { + display: none; +} + +.emotion-0 { + color: #524D6E; + margin: 0; + font-size: 1rem; + line-height: 1.5rem; + text-align: left; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + margin-right: 0.5rem; +} + +
+ + Label Text + +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+`; diff --git a/src/shared-components/toggle/index.js b/src/shared-components/toggle/index.js new file mode 100644 index 000000000..6aedc4aef --- /dev/null +++ b/src/shared-components/toggle/index.js @@ -0,0 +1,51 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import ToggleButton from 'react-toggle-button'; + +import { COLORS } from '../../constants'; +import { Container, Label, trackStyle, thumbStyle } from './style'; + +const propTypes = { + checked: PropTypes.bool, + label: PropTypes.string, + onChange: PropTypes.func, +}; + +const defaultProps = { + checked: false, + label: '', +}; + +const Toggle = ({ checked, label, onChange }) => ( + + {label && } + + +); + +Toggle.propTypes = propTypes; +Toggle.defaultProps = defaultProps; + +export default Toggle; diff --git a/src/shared-components/toggle/style.js b/src/shared-components/toggle/style.js new file mode 100644 index 000000000..82afed4e5 --- /dev/null +++ b/src/shared-components/toggle/style.js @@ -0,0 +1,43 @@ +import styled from '@emotion/styled'; + +import { COLORS, SPACING } from '../../constants'; + +export const Container = styled.div` + position: relative; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + width: 100%; + padding: ${SPACING.small} 0; + + & > input { + display: none; + } +`; + +export const Label = styled.span` + color: ${COLORS.primaryTint1}; + margin: 0; + font-size: ${SPACING.small}; + line-height: ${SPACING.base}; + text-align: left; + cursor: pointer; + user-select: none; + margin-right: ${SPACING.xsmall}; +`; + +export const trackStyle = { + borderRadius: 100, + height: 24, + width: 40, +}; + +export const thumbStyle = { + height: 22, + width: 22, + border: `1px solid ${COLORS.border}`, + boxShadow: `none`, + background: COLORS.white, + backgroundColor: COLORS.white, +}; diff --git a/src/shared-components/toggle/test.js b/src/shared-components/toggle/test.js new file mode 100644 index 000000000..b668dc8e0 --- /dev/null +++ b/src/shared-components/toggle/test.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import renderer from 'react-test-renderer'; + +import Toggle from './index'; + +describe('', () => { + const labelText = 'Label Text'; + + describe('UI snapshot', () => { + it('renders the component', () => { + const component = renderer.create( + + ); + + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); + + describe('when label is undefined', () => { + test('does not render a label component', () => { + const wrapper = shallow(); + expect(wrapper.html().indexOf('label') === -1).toBe(true); + }); + }); + + describe('when label is a string', () => { + test('renders a text component', () => { + const wrapper = shallow(); + + expect(wrapper.html().indexOf(labelText) > 0).toBe(true); + }); + }); + + describe('when checkbox is clicked', () => { + test('fires onChange function with correct argument when function exists', () => { + const spy = jest.fn(); + const wrapper = mount(); + + wrapper.find('[type="checkbox"]').simulate('click'); + expect(spy).toHaveBeenCalled(); + }); + }); +}); diff --git a/stories/toggle/index.js b/stories/toggle/index.js new file mode 100644 index 000000000..2423f50cf --- /dev/null +++ b/stories/toggle/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { withDocs } from 'storybook-readme'; +import styled from '@emotion/styled'; + +import ToggleReadme from 'docs/toggle.md'; +import { Toggle, Typography } from 'src/shared-components'; +import { SPACING } from 'src/constants'; + +import ToggleExample from './toggleExample'; + +const MainContainer = styled.div` + text-align: left; +`; + +const ToggleContainer = styled.div` + width: 300px; + margin: ${SPACING.base} 0; +`; + +const stories = storiesOf('Toggle', module); + +stories.add( + 'Usage', + withDocs(ToggleReadme, () => ( + + Examples: + + + + + + {}} /> + + + {}} /> + + + )) +); diff --git a/stories/toggle/toggleExample.js b/stories/toggle/toggleExample.js new file mode 100644 index 000000000..0bb6a2cc6 --- /dev/null +++ b/stories/toggle/toggleExample.js @@ -0,0 +1,30 @@ +import React from 'react'; + +import { Toggle } from 'src/shared-components'; + +class ToggleExample extends React.Component { + state = { + checked: false, + }; + + onChange = () => { + const { checked } = this.state; + this.setState({ + checked: !checked, + }); + }; + + render() { + const { checked } = this.state; + + return ( + + ); + } +} + +export default ToggleExample; diff --git a/yarn.lock b/yarn.lock index caff6f196..0f37e6aff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7419,6 +7419,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -7762,7 +7767,7 @@ querystringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" -raf@^3.4.0: +raf@^3.1.0, raf@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" dependencies: @@ -7949,6 +7954,15 @@ react-modal@^3.8.1: react-lifecycles-compat "^3.0.0" warning "^3.0.0" +react-motion@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" + integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ== + dependencies: + performance-now "^0.2.0" + prop-types "^15.5.8" + raf "^3.1.0" + react-slick@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.23.2.tgz#8d8bdbc77a6678e8ad36f50c32578c7c0f1c54f6" @@ -7991,6 +8005,14 @@ react-textarea-autosize@^7.0.4: dependencies: prop-types "^15.6.0" +react-toggle-button@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/react-toggle-button/-/react-toggle-button-2.2.0.tgz#a1b92143aa0df414642fcb141f0879f545bc5a89" + integrity sha1-obkhQ6oN9BRkL8sUHwh59UW8Wok= + dependencies: + prop-types "^15.6.0" + react-motion "^0.5.2" + react-transition-group@^2.0.0: version "2.5.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874"