From 2733a59d211953fb4ddcb25a804dca080d130a68 Mon Sep 17 00:00:00 2001 From: Haosheng Li <13259685+lihnick@users.noreply.github.com> Date: Fri, 17 Sep 2021 10:29:59 -0500 Subject: [PATCH] Ever 15000 rover UI add toggle switch component (#341) * add sub-component toggle to input folder * Toggle component use css variable wherever possible * allow forwarding ref and use inline-block in div wrapper * highlight toggle when child element are focused * add onChange event test and remove console log in example Co-authored-by: Haosheng Li --- example/src/App.js | 13 ++++ src/components/Input/Toggle/README.md | 9 +++ src/components/Input/Toggle/Toggle.module.css | 59 +++++++++++++++++++ src/components/Input/Toggle/Toggle.test.tsx | 48 +++++++++++++++ src/components/Input/Toggle/Toggle.tsx | 46 +++++++++++++++ src/components/Input/Toggle/index.ts | 1 + src/components/Input/Toggle/story.tsx | 59 +++++++++++++++++++ src/components/Input/index.ts | 1 + src/index.ts | 2 +- src/stories/index.js | 1 + 10 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 src/components/Input/Toggle/README.md create mode 100644 src/components/Input/Toggle/Toggle.module.css create mode 100644 src/components/Input/Toggle/Toggle.test.tsx create mode 100644 src/components/Input/Toggle/Toggle.tsx create mode 100644 src/components/Input/Toggle/index.ts create mode 100644 src/components/Input/Toggle/story.tsx diff --git a/example/src/App.js b/example/src/App.js index 7d371d96..2dbba28f 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -24,6 +24,7 @@ import { Tooltip, Callout, Input, + Toggle, InputTime, Typography, Modal, @@ -44,6 +45,7 @@ const App = () => { const [tooltipOpen, setTooltipOpen] = useState(false); const [inputValue, setInputValue] = useState(''); const [inputCheckboxValue, setInputCheckboxValue] = useState(false); + const [inputToggleValue, setInputToggleValue] = useState(false); const [inputTimeValue, setInputTimeValue] = useState(''); const [isModalOpen, setIsModalOpen] = useState(false); const [isKiteVisible, setIsKiteVisible] = useState(false); @@ -415,6 +417,17 @@ const App = () => { {JSON.stringify(inputCheckboxValue)} +
+

Input.Toggle

+ setInputToggleValue(e.target.checked)} + checked={inputToggleValue} + />{' '} + {JSON.stringify(inputToggleValue)} + + +
+
+ +A div wrapper with html input element for toggle functionality. + +Optional props + +- _checked_ default false +- _fauxDisabled_ default false + - Applies the same style as disabled, but, unlike the real thing, doesn't stop propagation of events. Useful for adding tooltips or other helpful behavior when a user tries to interact with a disabled field. Because it doesn't stop click or change events, the consumer is responsible for making faux-disabled fields read-only. diff --git a/src/components/Input/Toggle/Toggle.module.css b/src/components/Input/Toggle/Toggle.module.css new file mode 100644 index 00000000..0ff241fe --- /dev/null +++ b/src/components/Input/Toggle/Toggle.module.css @@ -0,0 +1,59 @@ +.Toggle { + display: inline-block; + position: relative; + width: 48px; + height: 24px; + background: var(--rvr-gray-40); + border-radius: 24px; +} + +.input { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + opacity: 0; + z-index: 1; + cursor: pointer; + border-radius: 24px; +} + +.notch { + pointer-events: none; + position: absolute; + width: 16px; + height: 16px; + margin: 4px; + border-radius: 8px; + background: var(--rvr-white); + transition: all var(--rvr-transition-duration-fast) var(--rvr-linear); + box-shadow: 0px 0px 3px 2px rgba(0 0 0 / 20%); +} + +.checked { + background: var(--rvr-blue); +} + +.disabled { + opacity: 0.5; +} + +.disabled > input.input { + cursor: not-allowed; +} + +.disabled > span.notch { + box-shadow: none; +} + +.checked > span.notch { + transform: translateX(24px); +} + +.Toggle:focus-within:not(.disabled) { + outline: 0 transparent none; + box-shadow: 0px 0px 0px 2px var(--rvr-white), + 0px 0px 1px 3px var(--rvr-color-primary-hover), + 0px 0px 8px 2px var(--rvr-color-primary-hover); +} diff --git a/src/components/Input/Toggle/Toggle.test.tsx b/src/components/Input/Toggle/Toggle.test.tsx new file mode 100644 index 00000000..6fd3eaff --- /dev/null +++ b/src/components/Input/Toggle/Toggle.test.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import Toggle from './Toggle'; + +describe('Toggle', () => { + it('renders', () => { + render(); + }); + + describe('when rendered with id attribute', () => { + it('should have the id attribute accessible in the dom element', () => { + const { getByTestId } = render( + {}} /> + ); + + const toggleElem = getByTestId('toggleElem'); + const toggleId = toggleElem.getAttribute('id') || ''; + expect(toggleId).toBe('toggleId'); + }); + }); + + describe('when clicked with onChange handler', () => { + it('should call the handler with the expected value', async () => { + let checkValue = false; + const changeHandler = () => { + checkValue = !checkValue; + }; + const { getByTestId } = render( + + ); + + const toggleElem = getByTestId('toggleElem'); + + toggleElem.click(); + expect(checkValue).toBe(true); + + toggleElem.click(); + expect(checkValue).toBe(false); + }); + }); +}); diff --git a/src/components/Input/Toggle/Toggle.tsx b/src/components/Input/Toggle/Toggle.tsx new file mode 100644 index 00000000..fbebb50c --- /dev/null +++ b/src/components/Input/Toggle/Toggle.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import classNames from 'classnames'; + +import type { InputProps } from '../Input'; + +import styles from './Toggle.module.css'; + +interface ToggleWithRefProps extends InputProps { + fauxDisabled?: boolean; + forwardedRef?: React.Ref; +} + +const ToggleWithRef: React.FC = ({ + checked = false, + fauxDisabled = false, + forwardedRef: ref, + className = '', + ...passedProps +}) => { + const mainClass = classNames(styles.Toggle, className, { + [styles.checked]: checked, + [styles.disabled]: fauxDisabled, + }); + + return ( +
+ + +
+ ); +}; + +const Toggle = React.forwardRef((props, ref) => ( + +)); + +export default Toggle; diff --git a/src/components/Input/Toggle/index.ts b/src/components/Input/Toggle/index.ts new file mode 100644 index 00000000..c2ec5455 --- /dev/null +++ b/src/components/Input/Toggle/index.ts @@ -0,0 +1 @@ +export { default } from './Toggle'; diff --git a/src/components/Input/Toggle/story.tsx b/src/components/Input/Toggle/story.tsx new file mode 100644 index 00000000..eb55afd1 --- /dev/null +++ b/src/components/Input/Toggle/story.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { boolean } from '@storybook/addon-knobs'; + +import Toggle from './Toggle'; +import Readme from './README.md'; + +import { Wrap } from '../../../stories/storybook-helpers'; + +storiesOf('Planets/Input/Toggle', module) + .addParameters({ + readme: { + sidebar: Readme, + }, + }) + .add( + 'Overview', + () => ( + + + + ), + { + info: { + inline: false, + source: true, + }, + } + ) + .add( + 'Examples', + () => ( + <> + + + + + + + + + + + + + + ), + { + info: { + inline: false, + source: true, + }, + } + ); diff --git a/src/components/Input/index.ts b/src/components/Input/index.ts index ae02d3a1..66e3e87b 100644 --- a/src/components/Input/index.ts +++ b/src/components/Input/index.ts @@ -1,3 +1,4 @@ export { default } from './Input'; export { default as Checkbox } from './Checkbox'; +export { default as Toggle } from './Toggle'; export type { InputProps } from './Input'; diff --git a/src/index.ts b/src/index.ts index 93928cd8..a6c5071b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ export { } from './components/TabMenu'; export { default as Tooltip, EasyRichTooltip } from './components/Tooltip'; -export { default as Input, Checkbox } from './components/Input'; +export { default as Input, Checkbox, Toggle } from './components/Input'; export { default as InputTime } from './components/InputTime'; export { default as Typography } from './components/Typography'; export { default as Modal } from './components/Modal'; diff --git a/src/stories/index.js b/src/stories/index.js index 6e41db21..539b5558 100644 --- a/src/stories/index.js +++ b/src/stories/index.js @@ -22,6 +22,7 @@ import '../components/Avatar/story'; import '../components/Tooltip/story'; import '../components/Input/story'; import '../components/Input/Checkbox/story'; +import '../components/Input/Toggle/story'; import '../components/InputTime/story'; import '../components/Select/story'; import '../components/Typography/story';