From dab3119ac9d2614a1fb4523b27ab8d30807f6a6d Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 22 Oct 2019 14:12:06 -0500 Subject: [PATCH] Convert `EuiSwitch` to TS (#2243) * convert euiswitch to ts * remove test file * euiswitchevent; store id in state * change interface to type * CL --- src-docs/src/views/form_controls/switch.js | 4 +- src/components/form/_variables.scss | 1 + src/components/form/index.d.ts | 1 - .../switch/__snapshots__/switch.test.js.snap | 81 -------- .../switch/__snapshots__/switch.test.tsx.snap | 97 ++++++++++ src/components/form/switch/_switch.scss | 178 +++++------------- src/components/form/switch/index.d.ts | 16 -- src/components/form/switch/index.js | 1 - src/components/form/switch/index.ts | 1 + src/components/form/switch/switch.js | 89 --------- .../{switch.test.js => switch.test.tsx} | 12 +- src/components/form/switch/switch.tsx | 103 ++++++++++ src/global_styling/reset/_reset.scss | 7 +- 13 files changed, 272 insertions(+), 319 deletions(-) delete mode 100644 src/components/form/switch/__snapshots__/switch.test.js.snap create mode 100644 src/components/form/switch/__snapshots__/switch.test.tsx.snap delete mode 100644 src/components/form/switch/index.d.ts delete mode 100644 src/components/form/switch/index.js create mode 100644 src/components/form/switch/index.ts delete mode 100644 src/components/form/switch/switch.js rename src/components/form/switch/{switch.test.js => switch.test.tsx} (65%) create mode 100644 src/components/form/switch/switch.tsx diff --git a/src-docs/src/views/form_controls/switch.js b/src-docs/src/views/form_controls/switch.js index d47a81a244e..69ea2b4c741 100644 --- a/src-docs/src/views/form_controls/switch.js +++ b/src-docs/src/views/form_controls/switch.js @@ -11,9 +11,9 @@ export default class extends Component { }; } - onChange = e => { + onChange = () => { this.setState({ - checked: e.target.checked, + checked: !this.state.checked, }); }; diff --git a/src/components/form/_variables.scss b/src/components/form/_variables.scss index c556fec274e..fb1272ceb07 100644 --- a/src/components/form/_variables.scss +++ b/src/components/form/_variables.scss @@ -31,3 +31,4 @@ $euiFormControlDisabledColor: $euiColorMediumShade !default; $euiFormControlBoxShadow: 0 1px 1px -1px transparentize($euiShadowColor, .8), 0 3px 2px -2px transparentize($euiShadowColor, .8) !default; $euiFormInputGroupLabelBackground: tintOrShade($euiColorLightShade, 65%, 40%) !default; $euiFormInputGroupBorder: 1px solid shadeOrTint($euiFormInputGroupLabelBackground, 6%, 8%) !default; +$euiSwitchOffColor: lightOrDarkTheme(transparentize($euiColorMediumShade, .8), transparentize($euiColorMediumShade, .3)) !default; diff --git a/src/components/form/index.d.ts b/src/components/form/index.d.ts index 366419d36f9..a31e554069b 100644 --- a/src/components/form/index.d.ts +++ b/src/components/form/index.d.ts @@ -9,7 +9,6 @@ import { CommonProps } from '../common'; /// /// /// -/// /// import { FunctionComponent, FormHTMLAttributes, ReactNode } from 'react'; diff --git a/src/components/form/switch/__snapshots__/switch.test.js.snap b/src/components/form/switch/__snapshots__/switch.test.js.snap deleted file mode 100644 index 7d5d582e468..00000000000 --- a/src/components/form/switch/__snapshots__/switch.test.js.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EuiSwitch assigns automatically generated ID to label 1`] = ` -
- - - - - - - - -
-`; - -exports[`EuiSwitch is rendered 1`] = ` -
- - - - - - - - -
-`; diff --git a/src/components/form/switch/__snapshots__/switch.test.tsx.snap b/src/components/form/switch/__snapshots__/switch.test.tsx.snap new file mode 100644 index 00000000000..b05767ccc4d --- /dev/null +++ b/src/components/form/switch/__snapshots__/switch.test.tsx.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiSwitch assigns automatically generated ID to label 1`] = ` +
+ + +
+`; + +exports[`EuiSwitch is rendered 1`] = ` +
+ + +
+`; diff --git a/src/components/form/switch/_switch.scss b/src/components/form/switch/_switch.scss index 39da09d6202..a143b66ffee 100644 --- a/src/components/form/switch/_switch.scss +++ b/src/components/form/switch/_switch.scss @@ -4,30 +4,63 @@ min-height: $euiSwitchHeight; .euiSwitch__label { + cursor: pointer; padding-left: $euiSizeS; line-height: $euiSwitchHeight; font-size: $euiFontSizeS; vertical-align: middle; } - /** - * 1. The input is "hidden" but still focusable. - * 2. Make sure it's still hidden when [disabled]. - */ - .euiSwitch__input, - .euiSwitch__input[disabled] /* 2 */ { - position: absolute; - opacity: 0; /* 1 */ - width: 100%; - height: 100%; - cursor: pointer; - } - - .euiSwitch__input:focus + .euiSwitch__body { + .euiSwitch__button { + line-height: 0; // ensures button takes height of switch inside - .euiSwitch__thumb { + &:focus .euiSwitch__thumb { @include euiCustomControlFocused; } + + &:disabled { + &:hover, + ~ .euiSwitch__label:hover { + cursor: not-allowed; + } + + .euiSwitch__body { + background-color: $euiSwitchOffColor; + } + + .euiSwitch__thumb { + @include euiCustomControlDisabled; + background-color: $euiSwitchOffColor; + } + + .euiSwitch__icon { + fill: $euiFormCustomControlDisabledIconColor; + } + + + .euiSwitch__label { + color: $euiFormControlDisabledColor; + } + } + + &[aria-checked='false'] { + .euiSwitch__body { + background-color: $euiSwitchOffColor; + } + + // When input is not checked, we shift around the positioning of the thumb and the icon + .euiSwitch__thumb { // move the thumb left + left: 0; + } + + .euiSwitch__icon { // move the icon right + right: -$euiSizeS; + + &.euiSwitch__icon--checked { + right: auto; + left: -($euiSwitchWidth - ($euiSwitchThumbSize / 2)); + } + } + } } .euiSwitch__body { @@ -77,120 +110,13 @@ fill: $euiColorEmptyShade; } - /** - * The thumb is slightly scaled when in use, unless it's disabled. - */ - &:hover { - .euiSwitch__input:not(:disabled) ~ .euiSwitch__body { - .euiSwitch__thumb { - transform: scale(1.05); - } + &:hover .euiSwitch__button { + &:not(:disabled) .euiSwitch__thumb { + transform: scale(1.05); } - } - &:active { - .euiSwitch__thumb { + &:active .euiSwitch__thumb { transform: scale(.95); } } - - .euiSwitch__input:disabled:hover { - cursor: not-allowed; - } - - .euiSwitch__input:disabled ~ .euiSwitch__body, - .euiSwitch__input:checked:disabled ~ .euiSwitch__body { - background-color: lightOrDarkTheme(transparentize($euiColorMediumShade, .8), transparentize($euiColorMediumShade, .3)); - - .euiSwitch__thumb { - @include euiCustomControlDisabled; - - border-color: $euiFormBorderColor; - background-color: lightOrDarkTheme(transparentize($euiColorMediumShade, .8), transparentize($euiColorMediumShade, .3)); - } - - .euiSwitch__icon { - fill: $euiFormCustomControlDisabledIconColor; - } - - + label { - color: $euiFormControlDisabledColor; - } - } - - .euiSwitch__input:checked:disabled ~ .euiSwitch__body { - background-color: lightOrDarkTheme(transparentize($euiColorMediumShade, .7), transparentize($euiColorMediumShade, .4)); - } - - // Slightly darker background when in a checked state. - .euiSwitch__input:not(:checked):not(:disabled) ~ .euiSwitch__body { - background-color: lightOrDarkTheme(transparentize($euiColorMediumShade, .8), transparentize($euiColorMediumShade, .3)); - } - - /** - * When input is not checked, we shift around the positioning of sibling/child selectors. - */ - .euiSwitch__input:not(:checked) ~ .euiSwitch__body { - .euiSwitch__thumb { - left: 0; - } - - .euiSwitch__icon { - right: -$euiSizeS; - - &.euiSwitch__icon--checked { - right: auto; - left: -($euiSwitchWidth - ($euiSwitchThumbSize / 2)); - } - } - } - - // Compressed switches operate very similar to the normal versions, but are smaller, contain no icon signifiers - &.euiSwitch--compressed { - min-height: $euiSwitchHeightCompressed; - - .euiSwitch__label { - line-height: $euiSwitchHeightCompressed; - } - - .euiSwitch__body { - width: $euiSwitchWidthCompressed; - height: $euiSwitchHeightCompressed; - border-radius: $euiSwitchHeightCompressed; - } - - .euiSwitch__thumb { - @include euiCustomControl($type: 'round', $size: ($euiSwitchThumbSizeCompressed) - 2px); - - left: ($euiSwitchWidthCompressed) - (($euiSwitchThumbSizeCompressed) - 2px) - 1px; - top: 1px; - transition: border-color $euiAnimSpeedNormal $euiAnimSlightBounce, background-color $euiAnimSpeedNormal $euiAnimSlightBounce, left $euiAnimSpeedNormal $euiAnimSlightBounce, transform $euiAnimSpeedNormal $euiAnimSlightBounce; - } - - .euiSwitch__track { - border-radius: $euiSwitchHeightCompressed; - } - - .euiSwitch__input:not(:checked) ~ .euiSwitch__body { - .euiSwitch__thumb { - left: 1px; - } - } - - // Compressed switches need slightly darker borders since they don't have icons - .euiSwitch__input:not(:checked) ~ .euiSwitch__body, - .euiSwitch__input:checked:disabled ~ .euiSwitch__body { - .euiSwitch__thumb { - border-color: $euiFormCustomControlBorderColor; - } - } - - // Similar additional treatment needed while checked - .euiSwitch__input:checked ~ .euiSwitch__body { - .euiSwitch__thumb { - border-color: $euiColorPrimary; - } - } - - } } diff --git a/src/components/form/switch/index.d.ts b/src/components/form/switch/index.d.ts deleted file mode 100644 index 3afc67edcb7..00000000000 --- a/src/components/form/switch/index.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CommonProps } from '../../common'; - -import { FunctionComponent, InputHTMLAttributes, ReactNode } from 'react'; - -declare module '@elastic/eui' { - /** - * @see './switch.js' - */ - export type EuiSwitchProps = CommonProps & - InputHTMLAttributes & { - label?: ReactNode; - compressed?: boolean; - }; - - export const EuiSwitch: FunctionComponent; -} diff --git a/src/components/form/switch/index.js b/src/components/form/switch/index.js deleted file mode 100644 index 893cd7f7f6c..00000000000 --- a/src/components/form/switch/index.js +++ /dev/null @@ -1 +0,0 @@ -export { EuiSwitch } from './switch'; diff --git a/src/components/form/switch/index.ts b/src/components/form/switch/index.ts new file mode 100644 index 00000000000..6c4ce9a8635 --- /dev/null +++ b/src/components/form/switch/index.ts @@ -0,0 +1 @@ +export { EuiSwitch, EuiSwitchEvent, EuiSwitchProps } from './switch'; diff --git a/src/components/form/switch/switch.js b/src/components/form/switch/switch.js deleted file mode 100644 index 06c490887e9..00000000000 --- a/src/components/form/switch/switch.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { Component, Fragment } from 'react'; - -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import makeId from '../../form/form_row/make_id'; -import { EuiIcon } from '../../icon'; - -export class EuiSwitch extends Component { - constructor(props) { - super(props); - - this.state = { - switchId: props.id || makeId(), - }; - } - - render() { - const { - label, - id, - name, - checked, - disabled, - compressed, - onChange, - className, - ...rest - } = this.props; - - const { switchId } = this.state; - - const classes = classNames( - 'euiSwitch', - { - 'euiSwitch--compressed': compressed, - }, - className - ); - - return ( -
- - - - - - {!compressed && ( - - - - - - )} - - - - {label && ( - - )} -
- ); - } -} - -EuiSwitch.propTypes = { - name: PropTypes.string, - id: PropTypes.string, - label: PropTypes.node, - checked: PropTypes.bool, - onChange: PropTypes.func, - disabled: PropTypes.bool, - compressed: PropTypes.bool, -}; diff --git a/src/components/form/switch/switch.test.js b/src/components/form/switch/switch.test.tsx similarity index 65% rename from src/components/form/switch/switch.test.js rename to src/components/form/switch/switch.test.tsx index 50927c7fa6e..a564df98ca1 100644 --- a/src/components/form/switch/switch.test.js +++ b/src/components/form/switch/switch.test.tsx @@ -4,17 +4,25 @@ import { requiredProps } from '../../../test/required_props'; import { EuiSwitch } from './switch'; +const props = { + checked: false, + label: 'Label', + onChange: () => {}, +}; + jest.mock('../form_row/make_id', () => () => 'generated-id'); describe('EuiSwitch', () => { test('is rendered', () => { - const component = render(); + const component = render( + + ); expect(component).toMatchSnapshot(); }); test('assigns automatically generated ID to label', () => { - const component = render(); + const component = render(); expect(component).toMatchSnapshot(); }); diff --git a/src/components/form/switch/switch.tsx b/src/components/form/switch/switch.tsx new file mode 100644 index 00000000000..9c7d8dde9c0 --- /dev/null +++ b/src/components/form/switch/switch.tsx @@ -0,0 +1,103 @@ +import React, { + ButtonHTMLAttributes, + FunctionComponent, + ReactNode, + useState, +} from 'react'; +import classNames from 'classnames'; + +import { CommonProps, Omit } from '../../common'; +import makeId from '../../form/form_row/make_id'; +import { EuiIcon } from '../../icon'; + +export type EuiSwitchEvent = React.BaseSyntheticEvent< + React.MouseEvent, + HTMLButtonElement, + EventTarget & { + checked: boolean; + } +>; + +export type EuiSwitchProps = CommonProps & + Omit, 'onChange'> & { + /** + * Whether to render the render the text label + */ + showLabel?: boolean; + /** + * Must be a string if `showLabel` prop is false + */ + label: ReactNode | string; + checked: boolean; + onChange: (event: EuiSwitchEvent) => void; + disabled?: boolean; + compressed?: boolean; + }; + +export const EuiSwitch: FunctionComponent = ({ + label, + id, + name, + checked, + disabled, + compressed, + onChange, + className, + showLabel = true, + ...rest +}) => { + const [switchId] = useState(id || makeId()); + + const onClick = (e: React.MouseEvent) => { + const event = (e as unknown) as EuiSwitchEvent; + event.target.checked = !checked; + onChange(event); + }; + + const classes = classNames( + 'euiSwitch', + { + 'euiSwitch--compressed': compressed, + }, + className + ); + + if (showLabel === false && typeof label !== 'string') { + console.warn( + 'EuiSwitch `label` must be a string when `showLabel` is false.' + ); + } + + return ( +
+ + + {showLabel && ( + + )} +
+ ); +}; diff --git a/src/global_styling/reset/_reset.scss b/src/global_styling/reset/_reset.scss index 0824f6a834e..26d7b809ea5 100644 --- a/src/global_styling/reset/_reset.scss +++ b/src/global_styling/reset/_reset.scss @@ -71,6 +71,11 @@ body { *:focus { outline: none; + + // sass-lint:disable no-vendor-prefixes + &::-moz-focus-inner { + border: none; + } } a { @@ -136,4 +141,4 @@ hr { fieldset { min-inline-size: auto; -} \ No newline at end of file +}