diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 7c84d3c9..38edc63d 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -46,7 +46,7 @@ jobs:
-Dsonar.projectKey=TykTechnologies_tyk-ui
-Dsonar.sources=./src
-Dsonar.coverage.exclusions=cypress/**/*.js,**/*.test.js,src/form/components/Combobox/*.js,src/form/redux-form/**/*.js
- -Dsonar.cpd.exclusions=**/*.test.js,src/form/redux-form/**/*
+ -Dsonar.cpd.exclusions=**/*.test.js,src/form/redux-form/**/*,src/common/fonts
-Dsonar.test.inclusions=**/*.test.js
-Dsonar.tests=./src
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index a7a8e0d0..ae1c83e6 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -24,7 +24,7 @@ jobs:
-Dsonar.projectKey=TykTechnologies_tyk-ui
-Dsonar.sources=./src
-Dsonar.coverage.exclusions=cypress/**/*.js,**/*.test.js,src/form/components/Combobox/*.js,src/form/redux-form/**/*.js
- -Dsonar.cpd.exclusions=**/*.test.js,src/form/redux-form/**/*
+ -Dsonar.cpd.exclusions=**/*.test.js,src/form/redux-form/**/*,src/common/fonts
-Dsonar.test.inclusions=**/*.test.js
-Dsonar.tests=./src
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
diff --git a/src/form/components/Toggle/Toggle.test.js b/src/form/components/Toggle/Toggle.test.js
index a5460516..fd0fbea3 100644
--- a/src/form/components/Toggle/Toggle.test.js
+++ b/src/form/components/Toggle/Toggle.test.js
@@ -1,7 +1,201 @@
+import React, { useState } from 'react';
import Toggle from './index';
+// eslint-disable-next-line react/prop-types
+function Component({ children, ...rest }) {
+ const [active, setActive] = useState('option1');
+ return (
+
+ {children || (
+
+ )}
+
+ );
+}
+
+const classes = {
+ disabled: 'tyk-toggle--disabled-true',
+ labelWidth: 'tyk-form-group--label-has-width',
+ separated: 'tyk-toggle__list--separated',
+ onDark: 'tyk-toggle--on-dark',
+ active: 'tyk-toggle__item--active',
+};
+
+const selectors = {
+ component: '.tyk-toggle',
+ list: '.tyk-toggle__list',
+ error: '.tyk-form-control__error-message',
+ note: '.tyk-form-control__help-block',
+ item: '.tyk-toggle__item',
+};
+
describe('Toggle', () => {
- it('TODO', () => {
- expect(true).to.equal(true);
+ it('renders the component', () => {
+ cy.mount()
+ .get(selectors.component)
+ .should('exist');
+ });
+
+ it('can use className and/or wrapperClassName to pass css classes to the component', () => {
+ const className = 'my-class';
+ const wrapperClassName = 'my-wrapper-class';
+ cy.mount()
+ .get(selectors.component)
+ .should('have.class', className)
+ .and('have.class', wrapperClassName);
+ });
+
+ it('can be in the disabled state', () => {
+ cy.mount()
+ .get(selectors.component)
+ .should('have.class', classes.disabled);
+ });
+
+ it('in readOnly mode the component is disabled', () => {
+ cy.mount()
+ .get(selectors.component)
+ .should('have.class', classes.disabled);
+ });
+
+ it('can render with error', () => {
+ const error = 'my error';
+ cy.mount()
+ .get(selectors.error)
+ .should('have.text', error);
+ });
+
+ it('can set a custom label', () => {
+ const label = 'my label';
+ cy.mount()
+ .get(selectors.component)
+ .find('label')
+ .should('contain', label);
+ });
+
+ it('can customize the label width', () => {
+ const labelWidth = '100px';
+ cy.mount(
+ <>
+
+
+ >,
+ )
+ .get(selectors.component)
+ .find('label')
+ .should('have.css', 'width', labelWidth);
+ });
+
+ it('can set a theme', () => {
+ const theme = 'my-theme';
+ cy.mount()
+ .get(selectors.component)
+ .should('have.class', `tyk-toggle--${theme}`);
+ });
+
+ it('can have multiple items', () => {
+ cy.mount(
+
+
+
+ ,
+ )
+ .get(selectors.item)
+ .should('have.length', 2);
+ });
+
+ it('can specify a size', () => {
+ const size = 'lg';
+ cy.mount()
+ .get(selectors.component)
+ .should('have.class', `tyk-toggle--${size}`);
+ });
+
+ it('items can be separated', () => {
+ cy.mount(
+
+
+
+ ,
+ )
+ .get(selectors.list)
+ .should('have.class', classes.separated);
+ });
+
+ it('can specify a direction', () => {
+ const direction = 'column';
+ cy.mount(
+
+
+
+ ,
+ )
+ .get(selectors.component)
+ .should('have.class', `tyk-toggle--${direction}`);
+ });
+
+ it('can display on dark backgrounds', () => {
+ cy.mount()
+ .get(selectors.component)
+ .should('have.class', classes.onDark);
+ });
+
+ it('calls the onChange callback when an item is clicked', () => {
+ const onChange = cy.stub().as('onChange');
+ cy.mount()
+ .get(selectors.item)
+ .click()
+ .get('@onChange')
+ .should('be.called');
+ });
+
+ it('an item can be selected/active', () => {
+ cy.mount(
+
+
+
+ ,
+ )
+ .get(selectors.item)
+ .eq(1)
+ .click()
+ .should('have.class', classes.active);
});
});
diff --git a/src/form/components/Toggle/index.js b/src/form/components/Toggle/index.js
index 9eed5513..b1b932df 100644
--- a/src/form/components/Toggle/index.js
+++ b/src/form/components/Toggle/index.js
@@ -1,191 +1,152 @@
-import React, { Component, createRef } from 'react';
+import React, {
+ useCallback, useMemo, useRef, useState,
+} from 'react';
import PropTypes from 'prop-types';
import ToggleContext from './js/ToggleContext';
import ToggleItemWrapper from './js/ToggleItemWrapper';
-class Toggle extends Component {
- static propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- PropTypes.element,
- PropTypes.string,
- ]),
- className: PropTypes.string,
- disabled: PropTypes.bool,
- readOnly: PropTypes.bool,
- error: PropTypes.string,
- onChange: PropTypes.func,
- label: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- PropTypes.element,
- PropTypes.func,
- PropTypes.string,
- ]),
- labelwidth: PropTypes.string,
- theme: PropTypes.string,
- type: PropTypes.string, // single || multiple
- size: PropTypes.string,
- separated: PropTypes.bool,
- direction: PropTypes.string,
- value: PropTypes.oneOfType([
- PropTypes.bool,
- PropTypes.string,
- ]),
- onDark: PropTypes.bool,
- wrapperClassName: PropTypes.string,
- };
-
- static defaultProps = {
- separated: false,
- theme: 'primary',
- type: 'single',
- direction: 'row',
- };
-
- state = {
- selectedRef: null,
- }
-
- constructor(props) {
- super(props);
-
- this.notchRef = createRef();
- this.toggleRef = createRef();
- }
-
- onItemSelected(value, event) {
- const { onChange } = this.props;
-
- if (onChange) {
- onChange(value, event);
- }
- }
-
- getCssClasses() {
- const {
- className,
- disabled,
- readOnly,
- size,
- theme,
- direction,
- onDark,
- wrapperClassName = '',
- } = this.props;
-
- let cssClasses = [
- wrapperClassName,
- 'tyk-toggle',
- `tyk-toggle--disabled-${readOnly || disabled}`,
- `tyk-toggle--${size || 'md'}`,
- `tyk-toggle--${theme}`,
- `tyk-toggle--${direction}`,
- ];
-
- if (onDark) {
- cssClasses.push('tyk-toggle--on-dark');
- }
-
- if (className) {
- cssClasses = cssClasses.concat(className.split(' '));
- }
-
- return cssClasses.join(' ');
- }
-
- getLabelStyles() {
- const { labelwidth } = this.props;
- const styles = {};
-
- if (labelwidth) {
- styles.flexBasis = labelwidth;
- }
-
- return styles;
- }
-
- saveSelectedRef(ref) {
- this.setState({
- selectedRef: ref,
- });
- }
-
- positionNotch() {
- const { separated } = this.props;
- const { selectedRef } = this.state;
-
+function Toggle({
+ className,
+ disabled,
+ readOnly,
+ size,
+ theme,
+ direction,
+ onDark,
+ wrapperClassName = '',
+ onChange,
+ labelwidth,
+ label,
+ separated,
+ children,
+ type,
+ value,
+ error,
+}) {
+ const [selectedRef, setSelectedRef] = useState(null);
+ const notchRef = useRef();
+ const toggleRef = useRef();
+
+ const classes = [
+ wrapperClassName,
+ className,
+ 'tyk-toggle',
+ `tyk-toggle--disabled-${readOnly || disabled}`,
+ `tyk-toggle--${size || 'md'}`,
+ `tyk-toggle--${theme}`,
+ `tyk-toggle--${direction}`,
+ onDark && 'tyk-toggle--on-dark',
+ ].filter(Boolean).join(' ');
+
+ const onItemSelected = useCallback((itemValue, event) => {
+ if (!onChange) return;
+ onChange(itemValue, event);
+ }, [onChange]);
+
+ const getLabelStyles = useCallback(() => {
+ if (labelwidth) return { flexBasis: labelwidth };
+ return {};
+ }, [labelwidth]);
+
+ const positionNotch = useCallback(() => {
if (!selectedRef || separated) {
return {};
}
const selectedWidth = selectedRef.current.offsetWidth;
const selectedOffset = selectedRef.current.getBoundingClientRect().left;
- const toggleOffset = this.toggleRef.current.getBoundingClientRect().left;
+ const toggleOffset = toggleRef.current.getBoundingClientRect().left;
const left = selectedOffset - toggleOffset;
return {
left: `${left + 4}px`,
width: `${selectedWidth - 8}px`,
};
- }
-
- render() {
- const {
- children,
- disabled,
- readOnly,
- label,
- type,
- separated,
- value,
- error,
- } = this.props;
-
- return (
- <>
-
-
+ }, [selectedRef, separated]);
+
+ const contextValue = useMemo(() => ({
+ disabled,
+ readOnly,
+ onItemSelected,
+ saveSelectedRef: setSelectedRef,
+ separated,
+ type,
+ value,
+ }), [disabled, readOnly, onItemSelected, separated, type, value]);
+
+ return (
+ <>
+
+
+ {
+ label
+ ?
+ : null
+ }
+
+ { children }
{
- label
- ?
+ type === 'multiple' && !separated
+ ?
: null
}
-
- { children }
- {
- type === 'multiple' && !separated
- ?
- : null
- }
-
-
-
- {
- error && (
-
- { error }
-
- )
- }
- >
- );
- }
+
+
+
+ {
+ error && (
+
+ { error }
+
+ )
+ }
+ >
+ );
}
+Toggle.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.element,
+ PropTypes.string,
+ ]),
+ className: PropTypes.string,
+ disabled: PropTypes.bool,
+ readOnly: PropTypes.bool,
+ error: PropTypes.string,
+ onChange: PropTypes.func,
+ label: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.element,
+ PropTypes.func,
+ PropTypes.string,
+ ]),
+ labelwidth: PropTypes.string,
+ theme: PropTypes.string,
+ type: PropTypes.string, // single || multiple
+ size: PropTypes.string,
+ separated: PropTypes.bool,
+ direction: PropTypes.string,
+ value: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.string,
+ ]),
+ onDark: PropTypes.bool,
+ wrapperClassName: PropTypes.string,
+};
+
+Toggle.defaultProps = {
+ separated: false,
+ theme: 'primary',
+ type: 'single',
+ direction: 'row',
+};
+
Toggle.Item = ToggleItemWrapper;
+
export default Toggle;
diff --git a/src/form/components/Toggle/js/ToggleItem.js b/src/form/components/Toggle/js/ToggleItem.js
index 3a86f25e..57d5c5ee 100644
--- a/src/form/components/Toggle/js/ToggleItem.js
+++ b/src/form/components/Toggle/js/ToggleItem.js
@@ -1,24 +1,9 @@
import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
-export default class ToggleItem extends Component {
- static propTypes = {
- context: PropTypes.instanceOf(Object),
- label: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- PropTypes.element,
- PropTypes.func,
- PropTypes.string,
- ]),
- name: PropTypes.string,
- value: PropTypes.string,
- };
-
+class ToggleItem extends Component {
static getNotchCssClasses(context) {
- const cssClasses = ['tyk-toggle__item-notch', `tyk-toggle__item-notch--${context.type}`];
-
- return cssClasses.join(' ');
+ return ['tyk-toggle__item-notch', `tyk-toggle__item-notch--${context.type}`].join(' ');
}
constructor(props) {
@@ -83,3 +68,18 @@ export default class ToggleItem extends Component {
);
}
}
+
+ToggleItem.propTypes = {
+ context: PropTypes.instanceOf(Object),
+ label: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.element,
+ PropTypes.func,
+ PropTypes.string,
+ ]),
+ name: PropTypes.string,
+ value: PropTypes.string,
+};
+
+export default ToggleItem;
diff --git a/src/form/components/Toggle/js/ToggleItemWrapper.js b/src/form/components/Toggle/js/ToggleItemWrapper.js
index 530a54d3..27340be3 100644
--- a/src/form/components/Toggle/js/ToggleItemWrapper.js
+++ b/src/form/components/Toggle/js/ToggleItemWrapper.js
@@ -6,12 +6,11 @@ import ToggleItem from './ToggleItem';
const ToggleItemWrapper = React.forwardRef((props, ref) => (
- {context => (
+ {(context) => (
{props.children}
- )
- }
+ )}
));