Skip to content

Commit

Permalink
feat(tab-indicator): typescript (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo committed Dec 28, 2018
1 parent 8680796 commit 056adb5
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 139 deletions.
100 changes: 50 additions & 50 deletions packages/tab-indicator/index.js → packages/tab-indicator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,56 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import classnames from 'classnames';

import {
MDCFadingTabIndicatorFoundation,
MDCSlidingTabIndicatorFoundation,
// No mdc .d.ts files
// @ts-ignore
} from '@material/tab-indicator/dist/mdc.tabIndicator';

export default class TabIndicator extends Component {
tabIndicatorElement_ = React.createRef();
export interface TabIndicatorProps extends React.HTMLProps<HTMLSpanElement> {
active?: boolean;
className?: string;
fade?: boolean;
icon?: boolean;
previousIndicatorClientRect?: ClientRect;
}

export default class TabIndicator extends React.Component<TabIndicatorProps, {}> {
private tabIndicatorElement: React.RefObject<HTMLSpanElement> = React.createRef();
foundation?: MDCFadingTabIndicatorFoundation | MDCSlidingTabIndicatorFoundation;

static defaultProps: Partial<TabIndicatorProps> = {
active: false,
className: '',
fade: false,
icon: false,
};

componentDidMount() {
if (this.props.fade) {
this.foundation_ = new MDCFadingTabIndicatorFoundation(this.adapter);
this.foundation = new MDCFadingTabIndicatorFoundation(this.adapter);
} else {
this.foundation_ = new MDCSlidingTabIndicatorFoundation(this.adapter);
this.foundation = new MDCSlidingTabIndicatorFoundation(this.adapter);
}
this.foundation_.init();

this.foundation.init();
if (this.props.active) {
this.foundation_.activate();
this.foundation.activate();
}
}

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

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: TabIndicatorProps) {
if (this.props.active !== prevProps.active) {
if (this.props.active) {
this.foundation_.activate(this.props.previousIndicatorClientRect);
this.foundation.activate(this.props.previousIndicatorClientRect);
} else {
this.foundation_.deactivate();
this.foundation.deactivate();
}
}
}
Expand All @@ -76,46 +91,51 @@ export default class TabIndicator extends Component {

get adapter() {
return {
addClass: (className) => {
if (!this.tabIndicatorElement_.current) return;
addClass: (className: string) => {
if (!this.tabIndicatorElement.current) return;
// since the sliding indicator depends on the FLIP method,
// our regular pattern of managing classes does not work here.
// setState is async, which does not work well with the FLIP method
// without a requestAnimationFrame, which was done in this PR:
// https://github.com/material-components
// /material-components-web/pull/3337/files#diff-683d792d28dad99754294121e1afbfb5L62
this.tabIndicatorElement_.current.classList.add(className);
this.tabIndicatorElement.current.classList.add(className);
this.forceUpdate();
},
removeClass: (className) => {
if (!this.tabIndicatorElement_.current) return;
this.tabIndicatorElement_.current.classList.remove(className);
removeClass: (className: string) => {
if (!this.tabIndicatorElement.current) return;
this.tabIndicatorElement.current.classList.remove(className);
this.forceUpdate();
},
computeContentClientRect: this.computeContentClientRect,
// setContentStyleProperty was using setState, but due to the method's
// async nature, its not condusive to the FLIP technique
setContentStyleProperty: (prop, value) => {
const contentElement = this.getNativeContentElement();
if (!contentElement) return;
setContentStyleProperty: (prop: keyof CSSStyleDeclaration, value: string) => {
const contentElement = this.getNativeContentElement() as HTMLElement;
// length and parentRule are readonly properties of CSSStyleDeclaration that
// cannot be set
if (!contentElement || prop === 'length' || prop === 'parentRule') {
return;
}
// https://github.com/Microsoft/TypeScript/issues/11914
contentElement.style[prop] = value;
},
};
}

getNativeContentElement = () => {
if (!this.tabIndicatorElement_.current) return;
private getNativeContentElement = () => {
if (!this.tabIndicatorElement.current) return;
// need to use getElementsByClassName since tabIndicator could be
// a non-semantic element (span, i, etc.). This is a problem since refs to a non semantic elements
// return the instance of the component.
return this.tabIndicatorElement_.current.getElementsByClassName('mdc-tab-indicator__content')[0];
}
return this.tabIndicatorElement.current.getElementsByClassName('mdc-tab-indicator__content')[0];
};

computeContentClientRect = () => {
const contentElement = this.getNativeContentElement();
if (!(contentElement && contentElement.getBoundingClientRect)) return;
return contentElement.getBoundingClientRect();
}
};

render() {
const {
Expand All @@ -129,11 +149,10 @@ export default class TabIndicator extends Component {
/* eslint-enable */
...otherProps
} = this.props;

return (
<span
className={this.classes}
ref={this.tabIndicatorElement_}
ref={this.tabIndicatorElement}
{...otherProps}
>
{this.renderContent()}
Expand All @@ -152,26 +171,7 @@ export default class TabIndicator extends Component {
if (this.props.children) {
return this.addContentClassesToChildren();
}
return (
<span className={this.contentClasses} />
);
return <span className={this.contentClasses} />;
}
}

TabIndicator.propTypes = {
active: PropTypes.bool,
className: PropTypes.string,
children: PropTypes.element,
fade: PropTypes.bool,
icon: PropTypes.bool,
previousIndicatorClientRect: PropTypes.object,
};

TabIndicator.defaultProps = {
active: false,
className: '',
children: null,
fade: false,
icon: false,
previousIndicatorClientRect: {},
};
55 changes: 0 additions & 55 deletions test/screenshot/tab-indicator/index.js

This file was deleted.

53 changes: 53 additions & 0 deletions test/screenshot/tab-indicator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import TabIndicator from '../../../packages/tab-indicator/index';
import MaterialIcon from '../../../packages/material-icon';
import '../../../packages/tab-indicator/index.scss';
import './index.scss';

const Tab: React.FunctionComponent<{
children?: React.ReactElement<any>,
index: number,
active: boolean,
icon: boolean
}> = ({children, index, active, icon}) => { // eslint-disable-line react/prop-types
return (
<div className='tab'>
<span>Tab {index}</span>
<TabIndicator active={active} icon={icon}>
{children}
</TabIndicator>
</div>
);
};

const Tabs: React.FunctionComponent<{
children?: React.ReactElement<any>,
activeIndex: number
}> = ({children, activeIndex}) => { // eslint-disable-line react/prop-types
return (
<div className='tabs'>
{[1, 2, 3].map((number, index) => (
<Tab
icon={!!children}
key={index}
index={number}
active={index === activeIndex}
>
{children}
</Tab>
))}
</div>
);
};

const TabIndicatorScreenshotTest: React.FunctionComponent = () => {
return (
<div>
<Tabs activeIndex={2}>
<MaterialIcon icon='star' />
</Tabs>
<Tabs activeIndex={1} />
</div>
);
};
export default TabIndicatorScreenshotTest;
Loading

0 comments on commit 056adb5

Please sign in to comment.