Skip to content

Commit

Permalink
feat(radio): typescript (#506)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo committed Dec 28, 2018
1 parent 94d006d commit af94a3a
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import * as React from 'react';
import * as classnames from 'classnames';

const NativeControl = (props) => {
const {
rippleActivatorRef,
className,
...otherProps
} = props;
export interface NativeControlProps extends React.HTMLProps<HTMLInputElement> {
className?: string,
rippleActivatorRef?: React.RefObject<HTMLInputElement>
};

const NativeControl: React.FunctionComponent<NativeControlProps> = ({
rippleActivatorRef, className = '', ...otherProps // eslint-disable-line react/prop-types
}) => {
return (
<input
type='radio'
Expand All @@ -41,14 +41,4 @@ const NativeControl = (props) => {
);
};

NativeControl.propTypes = {
className: PropTypes.string,
rippleActivatorRef: PropTypes.object,
};

NativeControl.defaultProps = {
className: '',
rippleActivatorRef: null,
};

export default NativeControl;
107 changes: 58 additions & 49 deletions packages/radio/index.js → packages/radio/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,53 +20,83 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as React from 'react';
import * as classnames from 'classnames';
// @ts-ignore no .d.ts file
import {MDCRadioFoundation} from '@material/radio/dist/mdc.radio';
import {withRipple} from '@material/react-ripple';
import NativeControl from './NativeControl';
import * as Ripple from '@material/react-ripple';
import NativeControl, {NativeControlProps} from './NativeControl'; // eslint-disable-line no-unused-vars

class Radio extends React.Component {
foundation_ = null;
radioElement_ = React.createRef();
rippleActivatorRef = React.createRef();
export interface RadioProps
extends Ripple.InjectedProps<HTMLDivElement, HTMLInputElement>, React.HTMLProps<HTMLDivElement> {
label?: string;
initRipple: (surface: HTMLDivElement, rippleActivatorRef?: HTMLInputElement) => void;
wrapperClasses?: string;
children: React.ReactElement<NativeControlProps>;
}

interface RadioState {
nativeControlId: string;
classList: Set<string>;
disabled: boolean;
}

class Radio extends React.Component<RadioProps, RadioState> {
foundation: MDCRadioFoundation;
private radioElement: React.RefObject<HTMLDivElement> = React.createRef();
rippleActivatorRef: React.RefObject<HTMLInputElement> = React.createRef();

state = {
state: RadioState = {
classList: new Set(),
disabled: false,
nativeControlId: '',
};

constructor(props) {
constructor(props: RadioProps) {
super(props);
this.foundation_ = new MDCRadioFoundation(this.adapter);
this.foundation = new MDCRadioFoundation(this.adapter);
}

static defaultProps: Partial<RadioProps> = {
label: '',
initRipple: () => {},
className: '',
wrapperClasses: '',
unbounded: true,
};

componentDidMount() {
this.foundation_.init();
this.foundation.init();
const childProps = this.props.children.props;
if (childProps.disabled) {
this.foundation_.setDisabled(childProps.disabled);
this.foundation.setDisabled(childProps.disabled);
}
if (childProps.id) {
this.setState({nativeControlId: childProps.id});
}
if (this.rippleActivatorRef && this.rippleActivatorRef.current) {
this.props.initRipple(this.radioElement_.current, this.rippleActivatorRef.current);
this.props.initRipple(
this.radioElement.current as HTMLDivElement,
this.rippleActivatorRef.current
);
}
}

componentWillUnmount() {
if (this.foundation_) {
this.foundation_.destroy();
if (this.foundation) {
this.foundation.destroy();
}
}

componentDidUpdate(prevProps, prevState) {
const childProps = this.props.children.props;
componentDidUpdate(prevProps: RadioProps) {
const {children} = this.props;
if (!children) {
React.Children.only(children);
return;
}
const childProps = children.props;
if (childProps.disabled !== prevProps.children.props.disabled) {
this.foundation_.setDisabled(childProps.disabled);
this.foundation.setDisabled(childProps.disabled);
}
if (childProps.id !== prevProps.children.props.id) {
this.setState({nativeControlId: childProps.id});
Expand All @@ -81,17 +111,17 @@ class Radio extends React.Component {

get adapter() {
return {
addClass: (className) => {
addClass: (className: string) => {
const classList = new Set(this.state.classList);
classList.add(className);
this.setState({classList});
},
removeClass: (className) => {
removeClass: (className: string) => {
const classList = new Set(this.state.classList);
classList.delete(className);
this.setState({classList});
},
setNativeControlDisabled: (disabled) => this.setState({disabled}),
setNativeControlDisabled: (disabled: boolean) => this.setState({disabled}),
};
}

Expand All @@ -107,14 +137,13 @@ class Radio extends React.Component {
wrapperClasses,
...otherProps
} = this.props;

return (
<div className={classnames('mdc-form-field', wrapperClasses)}>
<div className={this.classes} ref={this.radioElement_} {...otherProps}>
<div className={this.classes} ref={this.radioElement} {...otherProps}>
{this.renderNativeControl()}
<div className='mdc-radio__background'>
<div className='mdc-radio__outer-circle'></div>
<div className='mdc-radio__inner-circle'></div>
<div className='mdc-radio__outer-circle' />
<div className='mdc-radio__inner-circle' />
</div>
</div>
{label ? <label htmlFor={nativeControlId}>{label}</label> : null}
Expand All @@ -128,29 +157,9 @@ class Radio extends React.Component {
disabled: this.state.disabled,
rippleActivatorRef: this.rippleActivatorRef,
});
return (
React.cloneElement(children, updatedProps)
);
return React.cloneElement(children, updatedProps);
}
}

Radio.propTypes = {
label: PropTypes.string,
initRipple: PropTypes.func,
className: PropTypes.string,
wrapperClasses: PropTypes.string,
unbounded: PropTypes.bool,
children: PropTypes.element.isRequired,
};

Radio.defaultProps = {
label: '',
initRipple: () => {},
className: '',
wrapperClasses: '',
unbounded: true,
children: null,
};

export default withRipple(Radio);
export default Ripple.withRipple<RadioProps, HTMLDivElement, HTMLInputElement>(Radio);
export {Radio, NativeControl as NativeRadioControl};
47 changes: 30 additions & 17 deletions test/screenshot/radio/index.js → test/screenshot/radio/index.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import React from 'react';
import * as React from 'react';
import './index.scss';
import '../../../packages/list/index.scss';

import Radio, {NativeRadioControl} from '../../../packages/radio/index';

class PetsRadio extends React.Component {
constructor(props) {
type PetsRadioProps = {
name: string,
disabled?: boolean,
petValue?: string
};

type PetsRadioState = {
petValue?: string
};

class PetsRadio extends React.Component<PetsRadioProps, PetsRadioState> {
constructor(props: PetsRadioProps) {
super(props);
this.state = {
petValue: props.petValue, // eslint-disable-line react/prop-types
petValue: props.petValue,
};
}

render() {
const {petValue} = this.state;
const {name, disabled} = this.props; // eslint-disable-line react/prop-types
const pets = [{
value: 'dogs',
label: 'Dogs',
id: 'radio-dogs',
}, {
value: 'cats',
label: 'Cats',
id: 'radio-cats',
}];
const pets = [
{
value: 'dogs',
label: 'Dogs',
id: 'radio-dogs',
},
{
value: 'cats',
label: 'Cats',
id: 'radio-cats',
},
];

return (
<div>
Expand All @@ -35,7 +47,7 @@ class PetsRadio extends React.Component {
checked={petValue === pet.value}
value={pet.value}
id={`${pet.id}-${name}`}
onChange={(e) => this.setState({petValue: e.target.value})}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setState({petValue: e.target.value})}
/>
</Radio>
))}
Expand All @@ -44,14 +56,15 @@ class PetsRadio extends React.Component {
);
}
}

const RadioScreenshotTest = () => {
return (
<div>
<h3>Pet Radio Buttons</h3>
<PetsRadio name='pets'/>
<PetsRadio name='pets' />

<h3>Preselected Radio Buttons</h3>
<PetsRadio name='pets-preselect' petValue={'cats'}/>
<PetsRadio name='pets-preselect' petValue='cats' />

<h3>Disabled Radio Buttons</h3>
<PetsRadio name='pets-disabled' disabled />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import {assert} from 'chai';
import {shallow} from 'enzyme';
import {NativeRadioControl} from '../../../packages/radio/index';
Expand Down
Loading

0 comments on commit af94a3a

Please sign in to comment.