chowa
Version:
UI component library based on React
320 lines (319 loc) • 14 kB
JavaScript
/**
* @license chowa v1.1.3
*
* Copyright (c) Chowa Techonlogies Co.,Ltd.(http://www.chowa.cn).
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const React = require("react");
const PropTypes = require("prop-types");
const resize_observer_polyfill_1 = require("resize-observer-polyfill");
const classnames_1 = require("classnames");
const utils_1 = require("../utils");
const tabs_panel_1 = require("./tabs-panel");
const icon_1 = require("../icon");
class Tabs extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
selfActiveIndex: undefined,
showNavScrollBtn: false,
navPageCount: 1,
navPageNumber: 1,
navWrapperStyle: {},
activeLineStyle: {},
panelWrapperStyle: {}
};
[
'onTabClickHandler',
'onCloseHandler',
'onTabAppendHandler',
'preTabPage',
'nextTabPage'
].forEach((fn) => {
this[fn] = this[fn].bind(this);
});
}
preTabPage() {
this.setState({
navPageNumber: this.state.navPageNumber - 1
}, () => {
this.updateNavPagination();
});
}
nextTabPage() {
this.setState({
navPageNumber: this.state.navPageNumber + 1
}, () => {
this.updateNavPagination();
});
}
updateNavPagination() {
const { tabPosition } = this.props;
const { navPageNumber, navPageCount } = this.state;
const { width: clientWidth, height: clientHeight } = utils_1.doms.rect(this.navScrollEle);
const { width, height } = utils_1.doms.rect(this.navWrapperEle);
let navWrapperStyle = {};
if (tabPosition === 'top' || tabPosition === 'bottom') {
navWrapperStyle = {
transform: navPageNumber === navPageCount
? `translate(-${width - clientWidth}px, 0)`
: `translate(-${(navPageNumber - 1) * clientWidth}px, 0)`
};
}
else {
navWrapperStyle = {
transform: navPageNumber === navPageCount
? `translate(0, -${height - clientHeight}px)`
: `translate(0, -${(navPageNumber - 1) * clientHeight}px)`
};
}
this.setState({
navWrapperStyle
});
}
componentDidUpdate(preProps) {
if (preProps.activeIndex !== this.props.activeIndex && this.state.selfActiveIndex !== this.props.activeIndex) {
this.setState({
selfActiveIndex: this.props.activeIndex
}, () => {
this.updateActiveTabs();
this.updateNavScrollParams();
});
}
if (React.Children.count(preProps.children) !== React.Children.count(this.props.children)) {
this.updateActiveTabs();
this.updateNavScrollParams();
}
}
componentDidMount() {
const { defaultActiveIndex, activeIndex, children } = this.props;
let firstIndex;
React.Children.forEach(children, (child, key) => {
if (utils_1.isReactElement(child) && child.type === tabs_panel_1.default && !utils_1.isExist(firstIndex)) {
firstIndex = child.props.index || key;
}
});
this.setState({
selfActiveIndex: activeIndex || defaultActiveIndex || firstIndex
});
this.resizeObserver = new resize_observer_polyfill_1.default(() => {
this.updateNavScrollParams();
this.updateActiveTabs();
});
this.resizeObserver.observe(this.navScrollEle);
}
componentWillUnmount() {
this.resizeObserver.unobserve(this.navScrollEle);
this.resizeObserver.disconnect();
}
updateNavScrollParams() {
const { tabPosition } = this.props;
const { width: parentWidth, height: parentHeight } = utils_1.doms.rect(this.containerEle);
const { top: clientTop, left: clientLeft, width: clientWidth, height: clientHeight } = utils_1.doms.rect(this.navScrollEle);
const { width, height } = utils_1.doms.rect(this.navWrapperEle);
const activeEle = this.navWrapperEle.querySelector('.' + utils_1.preClass('tabs-tab-active'));
const { left, top } = utils_1.doms.rect(activeEle);
const showNavScrollBtn = (tabPosition === 'top' || tabPosition === 'bottom')
? width > parentWidth : height > parentHeight;
const navPageCount = (tabPosition === 'top' || tabPosition === 'bottom')
? Math.ceil(width / clientWidth) : Math.ceil(height / clientHeight);
let navPageNumber = 0;
if (tabPosition === 'top' || tabPosition === 'bottom') {
const horizontalDiff = left - clientLeft;
navPageNumber = Math.ceil(horizontalDiff / clientWidth);
}
else {
const verticalDiff = top - clientTop;
navPageNumber = Math.ceil(verticalDiff / clientHeight);
}
this.setState({
showNavScrollBtn,
navPageCount,
navPageNumber
}, () => {
this.updateNavPagination();
this.updateActiveTabs();
});
}
onTabClickHandler(index) {
this.setState({
selfActiveIndex: index
}, () => {
if (this.props.onChange) {
this.props.onChange(index);
}
this.updateActiveTabs();
});
}
onCloseHandler(index, e) {
const { onBeforeClose, onClose } = this.props;
utils_1.stopReactPropagation(e);
if (onBeforeClose && !onBeforeClose(index)) {
return;
}
if (onClose) {
onClose(index);
}
}
onTabAppendHandler() {
if (this.props.onAppend) {
this.props.onAppend();
}
}
updateActiveTabs() {
const { tabPosition, mode } = this.props;
const { selfActiveIndex } = this.state;
let activeLineStyle = {};
if (mode === 'line') {
const activeEle = this.navWrapperEle.querySelector('.' + utils_1.preClass('tabs-tab-active'));
const { width, left, height, top } = utils_1.doms.rect(activeEle);
const { left: parsentOffetLeft, top: parsentOffetTop } = utils_1.doms.rect(this.navWrapperEle);
if (tabPosition === 'top' || tabPosition === 'bottom') {
activeLineStyle = {
width,
transform: `translate(${left - parsentOffetLeft}px, 0)`
};
}
else {
activeLineStyle = {
height,
transform: `translate(0, ${top - parsentOffetTop}px)`
};
}
}
const { width: panelOffsetWidth } = utils_1.doms.rect(this.panelWrapperEle);
const arrIndex = this.tabIndexs.indexOf(selfActiveIndex);
this.setState({
activeLineStyle,
panelWrapperStyle: {
transform: `translate(-${panelOffsetWidth * arrIndex}px, 0)`
}
});
}
compileTabs() {
const { children, mode, tabPosition, closable, appendable } = this.props;
const { selfActiveIndex, showNavScrollBtn, navPageCount, navPageNumber, navWrapperStyle, activeLineStyle, panelWrapperStyle } = this.state;
const tabNodes = [];
const panelNodes = [];
const renderNodes = [];
const tabIndexs = [];
React.Children.forEach(children, (child, key) => {
if (!utils_1.isReactElement(child) || child.type !== tabs_panel_1.default) {
throw new Error('Tabs elements only support Tabs.Panel');
}
const { index: originIndex, tab, disabled } = child.props;
const index = utils_1.isExist(originIndex) ? originIndex : key;
const tabClass = classnames_1.default({
[utils_1.preClass('tabs-tab')]: true,
[utils_1.preClass('tabs-tab-active')]: selfActiveIndex === index,
[utils_1.preClass('tabs-tab-disabled')]: disabled
});
const tablCloseClass = classnames_1.default({
[utils_1.preClass('tabs-tab-close')]: true,
[utils_1.preClass('tabs-tab-close-hide')]: selfActiveIndex === index
});
const panelClass = classnames_1.default({
[utils_1.preClass('tabs-panel-active')]: selfActiveIndex === index,
[utils_1.preClass('tabs-panel-inactive')]: selfActiveIndex !== index,
[child.props.className]: utils_1.isExist(child.props.className)
});
tabNodes.push(React.createElement("li", { key: index, className: tabClass, onClick: disabled || selfActiveIndex === index ? null : this.onTabClickHandler.bind(this, index) },
tab,
closable &&
React.createElement("button", { className: tablCloseClass, onClick: this.onCloseHandler.bind(this, index) },
React.createElement(icon_1.default, { type: 'close' }))));
panelNodes.push(React.cloneElement(child, {
key: index,
className: panelClass
}));
tabIndexs.push(index);
});
this.tabIndexs = tabIndexs;
if (appendable) {
tabNodes.push(React.createElement("li", { key: 'append-tab-btn', className: utils_1.preClass('tabs-tab-append'), onClick: this.onTabAppendHandler },
React.createElement(icon_1.default, { type: 'plus' })));
}
const preTabClass = classnames_1.default({
[utils_1.preClass('tabs-tab-pre')]: true,
[utils_1.preClass('tabs-tab-disabled')]: navPageNumber === 1
});
const nextTabClass = classnames_1.default({
[utils_1.preClass('tabs-tab-next')]: true,
[utils_1.preClass('tabs-tab-disabled')]: navPageNumber === navPageCount
});
const tabsNav = (React.createElement("div", { className: utils_1.preClass('tabs-tab-container'), key: 'tabs-tab', ref: (ele) => {
this.containerEle = ele;
} },
showNavScrollBtn &&
React.createElement("span", { onClick: navPageNumber !== 1 ? this.preTabPage : null, className: preTabClass },
(tabPosition === 'top' || tabPosition === 'bottom') &&
React.createElement(icon_1.default, { type: 'arrow-left' }),
(tabPosition === 'left' || tabPosition === 'right') &&
React.createElement(icon_1.default, { type: 'arrow-top' })),
React.createElement("div", { className: utils_1.preClass('tabs-tab-scroll'), ref: (ele) => {
this.navScrollEle = ele;
} },
React.createElement("div", { className: utils_1.preClass('tabs-tab-wrapper'), style: navWrapperStyle, ref: (ele) => {
this.navWrapperEle = ele;
} },
React.createElement("ul", { className: utils_1.preClass('tabs-nav') }, tabNodes),
mode === 'line' &&
React.createElement("i", { className: utils_1.preClass('tabs-tab-active-line'), style: activeLineStyle }))),
showNavScrollBtn &&
React.createElement("span", { onClick: navPageNumber !== navPageCount ? this.nextTabPage : null, className: nextTabClass },
(tabPosition === 'top' || tabPosition === 'bottom') &&
React.createElement(icon_1.default, { type: 'arrow-right' }),
(tabPosition === 'left' || tabPosition === 'right') &&
React.createElement(icon_1.default, { type: 'arrow-down' }))));
const tabsPanel = (React.createElement("div", { className: utils_1.preClass('tabs-panel-container'), key: 'tabs-panel' },
React.createElement("div", { className: utils_1.preClass('tabs-panel-wrapper'), style: panelWrapperStyle, ref: (ele) => {
this.panelWrapperEle = ele;
} }, panelNodes)));
renderNodes.push(tabsNav);
if (tabPosition === 'top' || tabPosition === 'left') {
renderNodes.push(tabsPanel);
}
else {
renderNodes.unshift(tabsPanel);
}
return renderNodes;
}
render() {
const { className, style, tabPosition, mode, tabJustified } = this.props;
const componentClass = classnames_1.default({
[utils_1.preClass('tabs')]: true,
[utils_1.preClass(`tabs-tab-${tabPosition}`)]: true,
[utils_1.preClass(`tabs-${mode}`)]: mode,
[utils_1.preClass('tabs-tab-justified')]: tabJustified,
[className]: utils_1.isExist(className)
});
return (React.createElement("section", { style: style, className: componentClass }, this.compileTabs()));
}
}
Tabs.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
tabPosition: PropTypes.oneOf(['top', 'left', 'right', 'bottom']),
tabJustified: PropTypes.bool,
defaultActiveIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
mode: PropTypes.oneOf(['line', 'card']),
closable: PropTypes.bool,
appendable: PropTypes.bool,
onChange: PropTypes.func,
onBeforeClose: PropTypes.func,
onClose: PropTypes.func,
onAppend: PropTypes.func
};
Tabs.defaultProps = {
closable: false,
tabJustified: false,
appendable: false,
tabPosition: 'top',
mode: 'line'
};
Tabs.Panel = tabs_panel_1.default;
exports.default = Tabs;