Skip to content

Commit efa19e1

Browse files
committed
feat(component): introduce the pf4 switch component
fix #728
1 parent e40f35f commit efa19e1

File tree

13 files changed

+554
-0
lines changed

13 files changed

+554
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { HTMLProps, FormEvent, ReactNode } from 'react';
2+
import { Omit } from '../../typeUtils';
3+
4+
export interface SwitchProps extends Omit<HTMLProps<HTMLInputElement>, 'type' | 'onChange' | 'disabled' | 'label'> {
5+
isDisabled?: boolean;
6+
isChecked?: boolean;
7+
onChange?(checked: boolean, event: FormEvent<HTMLInputElement>): void;
8+
id?: string;
9+
'aria-label': string;
10+
labelLeft?: string;
11+
labelRight?: string;
12+
className?: string;
13+
}
14+
15+
declare const Switch: React.SFC<SwitchProps>;
16+
17+
export default Switch;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Switch } from '@patternfly/react-core';
2+
import SimpleSwitch from './examples/SimpleSwitch';
3+
import NoLabelSwitch from './examples/NoLabelSwitch';
4+
import DisabledSwitch from './examples/DisabledSwitch';
5+
import UncontrolledSwitch from './examples/UncontrolledSwitch';
6+
7+
export default {
8+
title: 'Switch',
9+
components: {
10+
Switch
11+
},
12+
examples: [SimpleSwitch, NoLabelSwitch, DisabledSwitch, UncontrolledSwitch]
13+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from 'react';
2+
import styles from '@patternfly/patternfly-next/components/Switch/switch.css';
3+
import { css } from '@patternfly/react-styles';
4+
import PropTypes from 'prop-types';
5+
import { getUniqueId } from '../../internal/util';
6+
7+
const propTypes = {
8+
/** id for the label. */
9+
id: PropTypes.string,
10+
/** Additional classes added to the Switch */
11+
className: PropTypes.string,
12+
/** Text value for the label on the left */
13+
labelLeft: PropTypes.string,
14+
/** Text value for the label on the right */
15+
labelRight: PropTypes.string,
16+
/** Flag to show if the Switch is checked. */
17+
isChecked: PropTypes.bool,
18+
/** Flag to show if the Switch is disabled. */
19+
isDisabled: PropTypes.bool,
20+
/** A callback for when the Switch selection changes. (isChecked, event) => {} */
21+
onChange: PropTypes.func,
22+
/** Adds accessible text to the Switch. */
23+
'aria-label': props => {
24+
if (!props.id && !props['aria-label']) {
25+
return new Error('Switch requires either an id or aria-label to be specified');
26+
}
27+
}
28+
};
29+
30+
const defaultProps = {
31+
id: '',
32+
className: '',
33+
labelLeft: '',
34+
labelRight: '',
35+
isChecked: true,
36+
isDisabled: false,
37+
onChange: () => undefined,
38+
'aria-label': ''
39+
};
40+
41+
class Switch extends React.Component {
42+
id = this.props.id || getUniqueId();
43+
44+
render() {
45+
const { id, className, labelLeft, labelRight, isChecked, isDisabled, onChange, ...props } = this.props;
46+
return (
47+
<label className={css(styles.switch, className)} htmlFor={this.id}>
48+
<input
49+
{...props}
50+
id={this.id}
51+
className={css(styles.switchInput)}
52+
type="checkbox"
53+
onChange={event => onChange(event.currentTarget.checked, event)}
54+
checked={isChecked}
55+
disabled={isDisabled}
56+
/>
57+
{labelLeft && (
58+
<span className={css(styles.switchLabel, styles.modifiers.off)} aria-hidden="true">
59+
{labelLeft}
60+
</span>
61+
)}
62+
<span className={css(styles.switchToggle)} />
63+
{labelRight && (
64+
<span className={css(styles.switchLabel, styles.modifiers.on)} aria-hidden="true">
65+
{labelRight}
66+
</span>
67+
)}
68+
</label>
69+
);
70+
}
71+
}
72+
73+
Switch.propTypes = propTypes;
74+
Switch.defaultProps = defaultProps;
75+
76+
export default Switch;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import Switch from './Switch';
4+
5+
const props = {
6+
onChange: jest.fn(),
7+
checked: false
8+
};
9+
10+
test('switch label for attribute equals input id attribute', () => {
11+
const view = shallow(<Switch id="foo" labelLeft="Off" labelRight="On" />);
12+
expect(view.find('input').prop('id')).toBe('foo');
13+
expect(view.find('label').prop('htmlFor')).toBe('foo');
14+
});
15+
16+
test('switch label id is auto generated', () => {
17+
const view = shallow(<Switch labelLeft="Off" labelRight="On" aria-label="..." />);
18+
expect(view.find('input').prop('id')).toBeDefined();
19+
});
20+
21+
test('switch is checked', () => {
22+
const view = shallow(<Switch id="switch-is-checked" labelLeft="Off" labelRight="On" isChecked />);
23+
expect(view).toMatchSnapshot();
24+
});
25+
26+
test('switch is not checked', () => {
27+
const view = shallow(<Switch id="switch-is-not-checked" labelLeft="Off" labelRight="On" isChecked={false} />);
28+
expect(view).toMatchSnapshot();
29+
});
30+
31+
test('no label switch is checked', () => {
32+
const view = shallow(<Switch id="no-label-switch-is-checked" isChecked />);
33+
expect(view).toMatchSnapshot();
34+
});
35+
36+
test('no label switch is not checked', () => {
37+
const view = shallow(<Switch id="no-label-switch-is-not-checked" isChecked={false} />);
38+
expect(view).toMatchSnapshot();
39+
});
40+
41+
test('switch is checked and disabled', () => {
42+
const view = shallow(<Switch id="switch-is-checked-and-disabled" isChecked isDisabled />);
43+
expect(view).toMatchSnapshot();
44+
});
45+
46+
test('switch is not checked and disabled', () => {
47+
const view = shallow(<Switch id="switch-is-not-checked-and-disabled" isChecked={false} isDisabled />);
48+
expect(view).toMatchSnapshot();
49+
});
50+
51+
test('switch passes value and event to onChange handler', () => {
52+
const newValue = true;
53+
const event = {
54+
currentTarget: { checked: newValue }
55+
};
56+
const view = shallow(<Switch id="onChange-switch" {...props} />);
57+
view.find('input').simulate('change', event);
58+
expect(props.onChange).toBeCalledWith(newValue, event);
59+
});

0 commit comments

Comments
 (0)