Skip to content
This repository was archived by the owner on Jul 29, 2025. It is now read-only.

Commit ba058d4

Browse files
author
Matt Goo
committed
feat(switch): typescript (#530)
1 parent cffefa0 commit ba058d4

File tree

9 files changed

+176
-172
lines changed

9 files changed

+176
-172
lines changed

packages/switch/NativeControl.js renamed to packages/switch/NativeControl.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,14 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2121
// THE SOFTWARE.
2222

23-
import React from 'react';
24-
import PropTypes from 'prop-types';
25-
26-
27-
const NativeControl = (props) => {
28-
const {
29-
rippleActivatorRef,
30-
...otherProps
31-
} = props;
23+
import * as React from 'react';
24+
export interface NativeControlProps extends React.HTMLProps<HTMLInputElement> {
25+
rippleActivatorRef?: React.RefObject<HTMLInputElement>;
26+
}
3227

28+
const NativeControl: React.FunctionComponent<NativeControlProps> = ({
29+
rippleActivatorRef, ...otherProps // eslint-disable-line react/prop-types
30+
}) => {
3331
return (
3432
<input
3533
type='checkbox'
@@ -41,18 +39,9 @@ const NativeControl = (props) => {
4139
);
4240
};
4341

44-
NativeControl.propTypes = {
45-
checked: PropTypes.bool,
46-
disabled: PropTypes.bool,
47-
id: PropTypes.string,
48-
rippleActivatorRef: PropTypes.object,
49-
};
50-
5142
NativeControl.defaultProps = {
5243
checked: false,
5344
disabled: false,
54-
id: null,
55-
rippleActivatorRef: null,
5645
};
5746

5847
export default NativeControl;

packages/switch/ThumbUnderlay.js renamed to packages/switch/ThumbUnderlay.tsx

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,28 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2121
// THE SOFTWARE.
2222

23-
import React from 'react';
24-
import PropTypes from 'prop-types';
25-
import classnames from 'classnames';
26-
import {withRipple} from '@material/react-ripple';
23+
import * as React from 'react';
24+
import * as classnames from 'classnames';
25+
import * as Ripple from '@material/react-ripple';
2726

28-
export class ThumbUnderlay extends React.Component {
29-
init = (el) => {
30-
this.props.initRipple(el, this.props.rippleActivator.current);
31-
}
27+
export interface ThumbUnderlayProps
28+
extends Ripple.InjectedProps<HTMLDivElement, HTMLInputElement>, React.HTMLProps<HTMLDivElement> {
29+
rippleActivator: React.RefObject<HTMLInputElement>;
30+
initRipple: (surface: HTMLDivElement, activator?: HTMLInputElement) => void;
31+
}
32+
33+
export class ThumbUnderlay extends React.Component<ThumbUnderlayProps, {}> {
34+
static defaultProps: Partial<ThumbUnderlayProps> = {
35+
className: '',
36+
initRipple: () => {},
37+
unbounded: true,
38+
};
39+
40+
init = (el: HTMLDivElement) => {
41+
if (this.props.rippleActivator.current) {
42+
this.props.initRipple(el, this.props.rippleActivator.current);
43+
}
44+
};
3245

3346
get classes() {
3447
return classnames('mdc-switch__thumb-underlay', this.props.className);
@@ -45,34 +58,12 @@ export class ThumbUnderlay extends React.Component {
4558
/* eslint-enable */
4659
...otherProps
4760
} = this.props;
48-
4961
return (
50-
<div
51-
className={this.classes}
52-
ref={this.init}
53-
{...otherProps}
54-
>
55-
<div className='mdc-switch__thumb'>
56-
{children}
57-
</div>
62+
<div className={this.classes} ref={this.init} {...otherProps}>
63+
<div className='mdc-switch__thumb'>{children}</div>
5864
</div>
5965
);
6066
}
6167
}
6268

63-
ThumbUnderlay.propTypes = {
64-
children: PropTypes.node,
65-
className: PropTypes.string,
66-
initRipple: PropTypes.func,
67-
unbounded: PropTypes.bool,
68-
rippleActivator: PropTypes.object,
69-
};
70-
71-
ThumbUnderlay.defaultProps = {
72-
className: '',
73-
onChange: () => {},
74-
initRipple: () => {},
75-
unbounded: true,
76-
};
77-
78-
export default withRipple(ThumbUnderlay);
69+
export default Ripple.withRipple<ThumbUnderlayProps, HTMLDivElement, HTMLInputElement>(ThumbUnderlay);

packages/switch/index.js renamed to packages/switch/index.tsx

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,35 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2121
// THE SOFTWARE.
2222

23-
import React, {Component} from 'react';
24-
import classnames from 'classnames';
25-
import PropTypes from 'prop-types';
23+
import * as React from 'react';
24+
import * as classnames from 'classnames';
25+
// No mdc .d.ts files
26+
// @ts-ignore
2627
import {MDCSwitchFoundation} from '@material/switch/dist/mdc.switch';
27-
2828
import ThumbUnderlay from './ThumbUnderlay';
2929
import NativeControl from './NativeControl';
3030

31-
export default class Switch extends Component {
32-
constructor(props) {
31+
export interface SwitchProps extends React.HTMLProps<HTMLInputElement> {
32+
checked: boolean;
33+
className: string;
34+
disabled: boolean;
35+
nativeControlId?: string;
36+
}
37+
38+
interface SwitchState {
39+
nativeControlChecked: boolean;
40+
checked: boolean;
41+
classList: Set<string>;
42+
disabled: boolean;
43+
nativeControlDisabled: boolean;
44+
}
45+
46+
export default class Switch extends React.Component<SwitchProps, SwitchState> {
47+
rippleActivator: React.RefObject<HTMLInputElement> = React.createRef();
48+
foundation?: MDCSwitchFoundation;
49+
50+
constructor(props: SwitchProps) {
3351
super(props);
34-
this.rippleActivator = React.createRef();
35-
this.foundation_ = null;
3652
this.state = {
3753
checked: props.checked,
3854
classList: new Set(),
@@ -42,24 +58,30 @@ export default class Switch extends Component {
4258
};
4359
}
4460

61+
static defaultProps: Partial<SwitchProps> = {
62+
checked: false,
63+
className: '',
64+
disabled: false,
65+
};
66+
4567
componentDidMount() {
46-
this.foundation_ = new MDCSwitchFoundation(this.adapter);
47-
this.foundation_.init();
48-
this.foundation_.setChecked(this.props.checked);
49-
this.foundation_.setDisabled(this.props.disabled);
68+
this.foundation = new MDCSwitchFoundation(this.adapter);
69+
this.foundation.init();
70+
this.foundation.setChecked(this.props.checked);
71+
this.foundation.setDisabled(this.props.disabled);
5072
}
5173

52-
componentDidUpdate(prevProps, prevState) {
74+
componentDidUpdate(prevProps: SwitchProps) {
5375
if (this.props.checked !== prevProps.checked) {
54-
this.foundation_.setChecked(this.props.checked);
76+
this.foundation.setChecked(this.props.checked);
5577
}
5678
if (this.props.disabled !== prevProps.disabled) {
57-
this.foundation_.setDisabled(this.props.disabled);
79+
this.foundation.setDisabled(this.props.disabled);
5880
}
5981
}
6082

6183
componentWillUnmount() {
62-
this.foundation_.destroy();
84+
this.foundation.destroy();
6385
}
6486

6587
get classes() {
@@ -70,25 +92,30 @@ export default class Switch extends Component {
7092

7193
get adapter() {
7294
return {
73-
addClass: (className) => {
95+
addClass: (className: string) => {
7496
const {classList} = this.state;
7597
classList.add(className);
7698
this.setState({classList});
7799
},
78-
removeClass: (className) => {
100+
removeClass: (className: string) => {
79101
const {classList} = this.state;
80102
classList.delete(className);
81103
this.setState({classList});
82104
},
83-
setNativeControlChecked: (nativeControlChecked) => {
105+
setNativeControlChecked: (nativeControlChecked: boolean) => {
84106
this.setState({nativeControlChecked});
85107
},
86-
setNativeControlDisabled: (nativeControlDisabled) => {
108+
setNativeControlDisabled: (nativeControlDisabled: boolean) => {
87109
this.setState({nativeControlDisabled});
88110
},
89111
};
90112
}
91113

114+
onChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
115+
this.setState({nativeControlChecked: evt.target.checked});
116+
this.foundation && this.foundation.handleChange(evt);
117+
};
118+
92119
render() {
93120
const {
94121
/* eslint-disable */
@@ -99,41 +126,19 @@ export default class Switch extends Component {
99126
nativeControlId,
100127
...otherProps
101128
} = this.props;
102-
103129
return (
104-
<div
105-
className={this.classes}
106-
{...otherProps}
107-
>
130+
<div className={this.classes} {...otherProps}>
108131
<div className='mdc-switch__track' />
109-
<ThumbUnderlay
110-
rippleActivator={this.rippleActivator}>
132+
<ThumbUnderlay rippleActivator={this.rippleActivator}>
111133
<NativeControl
112134
id={nativeControlId}
113135
checked={this.state.nativeControlChecked}
114136
disabled={this.state.nativeControlDisabled}
115-
onChange={(evt) => {
116-
this.setState({nativeControlChecked: evt.target.checked});
117-
this.foundation_ && this.foundation_.handleChange(evt);
118-
}}
137+
onChange={this.onChange}
119138
rippleActivatorRef={this.rippleActivator}
120139
/>
121140
</ThumbUnderlay>
122141
</div>
123142
);
124143
}
125144
}
126-
127-
Switch.propTypes = {
128-
checked: PropTypes.bool,
129-
className: PropTypes.string,
130-
disabled: PropTypes.bool,
131-
nativeControlId: PropTypes.string,
132-
};
133-
134-
Switch.defaultProps = {
135-
checked: false,
136-
className: '',
137-
disabled: false,
138-
nativeControlId: null,
139-
};

test/screenshot/switch/index.js

Lines changed: 0 additions & 23 deletions
This file was deleted.

test/screenshot/switch/index.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react';
2+
import './index.scss';
3+
4+
import Switch from '../../../packages/switch';
5+
6+
class SelectTest extends React.Component<{}, {}> {
7+
render() {
8+
return (
9+
<div>
10+
<Switch />
11+
<Switch checked />
12+
<Switch disabled />
13+
<div dir='rtl'>
14+
<Switch />
15+
<Switch checked />
16+
<Switch disabled />
17+
</div>
18+
<Switch
19+
className='custom-switch'
20+
nativeControlId='custom-switch-input'
21+
checked
22+
/>
23+
<label htmlFor='custom-switch-input'>Custom switch with label</label>
24+
</div>
25+
);
26+
}
27+
}
28+
export default SelectTest;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React from 'react';
1+
import * as React from 'react';
22
import {assert} from 'chai';
33
import {shallow} from 'enzyme';
44
import NativeControl from '../../../packages/switch/NativeControl';
55

66
suite('Switch Native Control');
77

88
test('has mdc-switch__native-control class', () => {
9-
const wrapper = shallow(<NativeControl/>);
9+
const wrapper = shallow(<NativeControl />);
1010
assert.isTrue(wrapper.hasClass('mdc-switch__native-control'));
1111
});

test/unit/switch/ThumbUnderlay.test.js

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as React from 'react';
2+
import {assert} from 'chai';
3+
import {mount} from 'enzyme';
4+
import ThumbUnderlay from '../../../packages/switch/ThumbUnderlay';
5+
import {coerceForTesting} from '../helpers/types';
6+
7+
suite('Switch Thumb Underlay');
8+
9+
test('has mdc-switch__thumb-underlay class', () => {
10+
const rippleActivator = coerceForTesting<React.RefObject<HTMLInputElement>>({
11+
current: <input />,
12+
});
13+
const wrapper = mount(<ThumbUnderlay rippleActivator={rippleActivator} />);
14+
assert.exists(wrapper.find('.mdc-switch__thumb-underlay'));
15+
});
16+
17+
test('classNames adds classes', () => {
18+
const rippleActivator = coerceForTesting<React.RefObject<HTMLInputElement>>({
19+
current: <input />,
20+
});
21+
const wrapper = mount(
22+
<ThumbUnderlay className='test-class-name' rippleActivator={rippleActivator} />
23+
);
24+
assert.isTrue(wrapper.hasClass('test-class-name'));
25+
});

0 commit comments

Comments
 (0)