Skip to content

Commit

Permalink
feat(checkbox): typescript support (#490)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo committed Dec 28, 2018
1 parent d58bf1b commit b218e16
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,25 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import * as React from 'react';

import React from 'react';
import PropTypes from 'prop-types';
export interface NativeControlProps extends React.HTMLProps<HTMLInputElement>{
checked: boolean;
disabled: boolean;
id: string;
rippleActivatorRef: React.RefObject<HTMLInputElement>;
onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
};

export class NativeControl extends React.Component {
render() {
const {
rippleActivatorRef,
...otherProps
} = this.props;
export class NativeControl extends React.Component<NativeControlProps, {}> {
static defaultProps: Partial<NativeControlProps> = {
checked: false,
disabled: false,
onChange: () => {},
};

render() {
const {rippleActivatorRef, ...otherProps} = this.props;
return (
<input
type='checkbox'
Expand All @@ -39,20 +47,6 @@ export class NativeControl extends React.Component {
/>
);
}
};

NativeControl.propTypes = {
checked: PropTypes.bool,
disabled: PropTypes.bool,
id: PropTypes.string,
rippleActivatorRef: PropTypes.object,
};

NativeControl.defaultProps = {
checked: false,
disabled: false,
id: null,
rippleActivatorRef: null,
};
}

export default NativeControl;
149 changes: 85 additions & 64 deletions packages/checkbox/index.js → packages/checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,57 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import {MDCCheckboxFoundation} from '@material/checkbox/dist/mdc.checkbox';
import * as React from 'react';
import * as classnames from 'classnames';
// @ts-ignore
import {MDCCheckboxFoundation, MDCCheckboxAdapter} from '@material/checkbox/dist/mdc.checkbox';
// TODO: fix with #528
// @ts-ignore
import withRipple from '@material/react-ripple';

import NativeControl from './NativeControl';

export class Checkbox extends Component {
constructor(props) {
export interface CheckboxProps {
checked: boolean;
className: string;
disabled: boolean;
indeterminate: boolean;
nativeControlId?: string;
onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
initRipple: (surface: HTMLDivElement, activator: HTMLInputElement) => void;
unbounded: boolean;
};

interface CheckboxState {
checked: boolean;
indeterminate: boolean;
classList: Set<string>;
'aria-checked': boolean;
};

export class Checkbox extends React.Component<CheckboxProps, CheckboxState> {
inputElement_: React.RefObject<HTMLInputElement> = React.createRef();
foundation_ = MDCCheckboxFoundation;

constructor(props: CheckboxProps) {
super(props);
this.inputElement_ = React.createRef();
this.foundation_ = null;
this.state = {
checked: props.checked,
indeterminate: props.indeterminate,
classList: new Set(),
['aria-checked']: false,
'checked': props.checked,
'indeterminate': props.indeterminate,
'classList': new Set(),
'aria-checked': false,
};
}

static defaultProps: Partial<CheckboxProps> = {
checked: false,
className: '',
disabled: false,
indeterminate: false,
onChange: () => {},
initRipple: () => {},
unbounded: true,
};

componentDidMount() {
this.foundation_ = new MDCCheckboxFoundation(this.adapter);
this.foundation_.init();
Expand All @@ -52,14 +81,12 @@ export class Checkbox extends Component {
}
}

componentDidUpdate(prevProps) {
const {
checked,
indeterminate,
disabled,
} = this.props;

if (checked !== prevProps.checked || indeterminate !== prevProps.indeterminate) {
componentDidUpdate(prevProps: CheckboxProps) {
const {checked, indeterminate, disabled} = this.props;
if (
checked !== prevProps.checked ||
indeterminate !== prevProps.indeterminate
) {
this.handleChange(checked, indeterminate);
}
if (disabled !== prevProps.disabled) {
Expand All @@ -73,33 +100,43 @@ export class Checkbox extends Component {
}
}

init = (el) => {
init = (el: HTMLDivElement) => {
if (!this.inputElement_.current) return;
this.props.initRipple(el, this.inputElement_.current);
}
};

handleChange = (checked, indeterminate) => {
handleChange = (checked: boolean, indeterminate: boolean) => {
this.setState({checked, indeterminate}, () => {
this.foundation_.handleChange();
if (this.inputElement_.current) {
this.inputElement_.current.indeterminate = indeterminate;
}
});
}
};

get classes() {
get classes(): string {
const {classList} = this.state;
const {className} = this.props;
return classnames('mdc-checkbox', Array.from(classList), className);
}

get adapter() {
updateState = (key: keyof CheckboxState, value: string | boolean) => {
this.setState((prevState) => ({
...prevState,
[key]: value,
}));
}

removeState = (key: keyof CheckboxState) => this.updateState(key, false);

get adapter(): MDCCheckboxAdapter {
return {
addClass: (className) => {
addClass: (className: string) => {
const {classList} = this.state;
classList.add(className);
this.setState({classList});
},
removeClass: (className) => {
removeClass: (className: string) => {
const {classList} = this.state;
classList.delete(className);
this.setState({classList});
Expand All @@ -110,23 +147,30 @@ export class Checkbox extends Component {
isAttachedToDOM: () => true,
isChecked: () => this.state.checked,
isIndeterminate: () => this.state.indeterminate,
setNativeControlAttr: (attr, value) => this.setState({[attr]: value}),
removeNativeControlAttr: (attr) => this.setState({[attr]: false}),
setNativeControlAttr: this.updateState,
removeNativeControlAttr: this.removeState,
};
}

onChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const {onChange} = this.props;
const {checked, indeterminate} = evt.target;
this.handleChange(checked, indeterminate);
onChange(evt);
}

render() {
const {
/* eslint-disable no-unused-vars */
className,
checked,
indeterminate,
initRipple,
onChange,
unbounded,
/* eslint-enable no-unused-vars */
disabled,
nativeControlId,
onChange,
...otherProps
} = this.props;

Expand All @@ -142,49 +186,26 @@ export class Checkbox extends Component {
checked={this.state.checked}
disabled={disabled}
aria-checked={this.state['aria-checked']}
onChange={(evt) => {
const {checked, indeterminate} = evt.target;
this.handleChange(checked, indeterminate);
onChange(evt);
}}
onChange={this.onChange}
rippleActivatorRef={this.inputElement_}
/>
<div className='mdc-checkbox__background'>
<svg className='mdc-checkbox__checkmark'
<svg
className='mdc-checkbox__checkmark'
viewBox='0 0 24 24'
focusable='false'
>
<path className='mdc-checkbox__checkmark-path'
<path
className='mdc-checkbox__checkmark-path'
fill='none'
d='M1.73,12.91 8.1,19.28 22.79,4.59'/>
d='M1.73,12.91 8.1,19.28 22.79,4.59'
/>
</svg>
<div className='mdc-checkbox__mixedmark'></div>
<div className='mdc-checkbox__mixedmark' />
</div>
</div>
);
}
}

Checkbox.propTypes = {
checked: PropTypes.bool,
className: PropTypes.string,
disabled: PropTypes.bool,
indeterminate: PropTypes.bool,
nativeControlId: PropTypes.string,
onChange: PropTypes.func,
initRipple: PropTypes.func,
unbounded: PropTypes.bool,
};

Checkbox.defaultProps = {
checked: false,
className: '',
disabled: false,
indeterminate: false,
nativeControlId: null,
onChange: () => {},
initRipple: () => {},
unbounded: true,
};

export default withRipple(Checkbox);
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import * as React from 'react';
import './index.scss';

import Checkbox from '../../../packages/checkbox/index';

const CheckboxScreenshotTest = () => {
const CheckboxScreenshotTest: React.FunctionComponent = () => {
return (
<div>
<Checkbox />
Expand All @@ -18,10 +17,13 @@ const CheckboxScreenshotTest = () => {
<Checkbox checked />
<Checkbox disabled />
</div>
<Checkbox className='custom-checkbox' nativeControlId='custom-checkbox-input' checked />
<Checkbox
className='custom-checkbox'
nativeControlId='custom-checkbox-input'
checked
/>
<label htmlFor='custom-checkbox-input'>Custom checkbox with label</label>
</div>
);
};

export default CheckboxScreenshotTest;
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import * as React from 'react';
import {assert} from 'chai';
import {shallow} from 'enzyme';
import NativeControl from '../../../packages/checkbox/NativeControl';

suite('Checkbox Native Control');

test('has mdc-checkbox__native-control class', () => {
const wrapper = shallow(<NativeControl/>);
const wrapper = shallow(<NativeControl />);
assert.isTrue(wrapper.hasClass('mdc-checkbox__native-control'));
});
Loading

0 comments on commit b218e16

Please sign in to comment.