zarm-web
Version:
基于 React 的桌面端UI库
413 lines (379 loc) • 11.6 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React, { Component } from 'react';
import classnames from 'classnames';
import Tab from './Tab';
import Icon from '../icon';
class Tabs extends Component {
static getSelectIndex(children) {
let selectIndex;
React.Children.forEach(children, (item, $index) => {
if (item.props && item.props.selected) {
selectIndex = $index;
}
});
return selectIndex;
}
static getDerivedStateFromProps(props) {
const newState = {
value: null
};
if ('value' in props) {
newState.value = props.value;
return newState;
}
return null;
}
constructor(props) {
super(props);
this.tabHeaderWrap = void 0;
this.tabHeaderNav = void 0;
this.activeTab = void 0;
this.handleTabClick = (e, index, disabled) => {
const {
onChange
} = this.props;
if (!disabled) {
this.updateTabBar(e.target);
if (!('value' in this.props)) {
this.setState({
value: index
});
}
typeof onChange === 'function' && onChange(index);
}
};
this.handleTabClose = (e, index, disabled) => {
e.stopPropagation();
const {
onTabClose
} = this.props;
if (!disabled) {
typeof onTabClose === 'function' && onTabClose(index);
}
};
this.scrollRightOrBottom = e => {
const {
direction,
onNextClick
} = this.props;
const {
headerWidth,
headerHeight,
scrollWidth,
scrollHeight,
scrollOffset
} = this.state;
const scrollDimension = direction === 'horizontal' ? scrollWidth : scrollHeight;
const headerDimension = direction === 'horizontal' ? headerWidth : headerHeight;
const offset = scrollDimension - scrollOffset - headerDimension;
this.setState({
scrollOffset: scrollOffset + (offset > headerDimension ? headerDimension : offset)
});
onNextClick && onNextClick(e);
};
this.scrollLeftOrTop = e => {
const {
direction,
onPrevClick
} = this.props;
const {
headerWidth,
headerHeight,
scrollOffset
} = this.state;
const headerDimension = direction === 'horizontal' ? headerWidth : headerHeight;
this.setState({
scrollOffset: scrollOffset - (scrollOffset > headerDimension ? headerDimension : scrollOffset)
});
onPrevClick && onPrevClick(e);
};
this.getActiveNode = node => {
if (node) {
if (this.activeTab !== node) {
this.activeTab = node;
this.setActiveLineStyle();
}
}
};
this.state = {
value: props.value || props.defaultValue || Tabs.getSelectIndex(props.children) || 0,
lineWidth: 0,
lineHeight: 0,
scrollOffset: 0,
headerWidth: 0,
headerHeight: 0,
scrollWidth: 0,
scrollHeight: 0,
isArrowShown: false
};
this.tabHeaderWrap = React.createRef();
this.tabHeaderNav = React.createRef();
}
componentDidMount() {
this.updateArrow();
this.setActiveLineStyle();
}
componentDidUpdate(prevProps, prevState) {
const {
size: prevSize,
children: prevChildren,
direction: prevDirection
} = prevProps;
const {
headerWidth: prevHeaderWidth,
headerHeight: prevHeaderHeight,
scrollWidth: prevScrollWidth
} = prevState;
const {
size: currentSize,
children = [],
direction
} = this.props || {};
const {
headerWidth,
headerHeight,
scrollWidth,
scrollOffset
} = this.state;
if (prevSize !== currentSize || prevDirection !== direction) {
this.setActiveLineStyle();
}
if (prevHeaderWidth !== headerWidth || prevHeaderHeight !== headerHeight || prevChildren !== children) {
this.updateArrow();
}
if (scrollWidth < prevScrollWidth) {
const result = scrollOffset - (prevScrollWidth - scrollWidth);
if (result > 0) {
this.updateScrollOffset(result);
}
}
}
setActiveLineStyle() {
const {
width = 0,
height = 0
} = this.activeTab && this.activeTab.getBoundingClientRect() || {};
this.setState({
lineWidth: width,
lineHeight: height
});
}
getScrollOffset(target) {
const {
direction
} = this.props;
const {
scrollOffset,
headerWidth,
headerHeight
} = this.state;
const {
offsetLeft,
offsetTop
} = target;
const {
width,
height
} = target.getBoundingClientRect();
if (direction === 'horizontal') {
const diff = scrollOffset + headerWidth < offsetLeft + width ? offsetLeft + width - headerWidth : scrollOffset;
return scrollOffset > offsetLeft ? offsetLeft : diff;
}
const diff = scrollOffset + headerHeight < offsetTop + height ? offsetTop + height - headerHeight : scrollOffset;
return scrollOffset > offsetTop ? offsetTop : diff;
}
getHeaderStyle() {
const {
width: headerWidth = 0,
height: headerHeight = 0
} = this.tabHeaderWrap && this.tabHeaderWrap.current && this.tabHeaderWrap.current.getBoundingClientRect() || {};
const {
width: scrollWidth = 0,
height: scrollHeight = 0
} = this.tabHeaderNav && this.tabHeaderNav.current && this.tabHeaderNav.current.getBoundingClientRect() || {};
return {
headerWidth,
headerHeight,
scrollWidth,
scrollHeight
};
}
updateScrollOffset(scrollOffset) {
this.setState({
scrollOffset
});
}
updateArrow() {
const {
direction
} = this.props;
const {
headerWidth,
headerHeight,
scrollWidth,
scrollHeight
} = this.getHeaderStyle();
const isArrowShown = direction === 'horizontal' && scrollWidth > headerWidth || direction === 'vertical' && scrollHeight > headerHeight;
this.setState({
isArrowShown
}, () => {
this.setState(this.getHeaderStyle());
this.updateTabBar(this.activeTab);
});
}
updateTabBar(target) {
const {
scrollOffset,
isArrowShown
} = this.state;
const offset = isArrowShown ? this.getScrollOffset(target) : scrollOffset;
this.updateScrollOffset(offset);
}
renderHeaderLine() {
const {
direction,
prefixCls
} = this.props;
const {
lineWidth,
lineHeight
} = this.state;
const {
offsetLeft = 0,
offsetTop = 0
} = this.activeTab || {};
const headerLineStyle = direction === 'horizontal' ? {
width: lineWidth,
height: 0,
transform: `translate3d(${offsetLeft}px,0,0)`
} : {
width: 0,
height: lineHeight,
transform: `translate3d(0,${offsetTop}px,0)`
};
return React.createElement("div", {
className: `${prefixCls}__header__line`,
style: headerLineStyle
});
}
render() {
const {
className,
children,
style,
prefixCls,
type,
direction,
size,
animated
} = this.props;
const {
value,
isArrowShown,
scrollOffset,
headerWidth,
headerHeight,
scrollWidth,
scrollHeight
} = this.state;
const headerDimension = direction === 'horizontal' ? headerWidth : headerHeight;
const scrollDimension = direction === 'horizontal' ? scrollWidth : scrollHeight;
const arrowL = direction === 'horizontal' ? 'left' : 'top';
const arrowR = direction === 'horizontal' ? 'right' : 'bottom';
const isArrowLDisabled = scrollOffset === 0;
const isArrowRDisabled = Math.floor(Math.abs(scrollOffset + headerDimension - scrollDimension)) === 0;
const animateStyle = direction === 'horizontal' ? {
marginLeft: `-${value * 100}%`
} : {};
const headerNavStyle = direction === 'horizontal' ? {
transform: `translate3d(${-scrollOffset}px,0,0)`
} : {
transform: `translate3d(0,${-scrollOffset}px,0)`
};
const cls = classnames(prefixCls, className, {
[`${prefixCls}--${direction}`]: direction,
[`${prefixCls}--${size}`]: size,
[`${prefixCls}--${type}`]: direction === 'horizontal' && type
});
const headerCls = classnames(`${prefixCls}__header`, {
[`${prefixCls}__header--arrow-mode`]: isArrowShown
});
const bodyCls = classnames(`${prefixCls}__body`, {
[`${prefixCls}__body--animated`]: animated
});
const arrowLCls = classnames(`${prefixCls}__header__arrow`, {
[`${prefixCls}__header__arrow--${arrowL}`]: arrowL,
[`${prefixCls}__header__arrow--disabled`]: isArrowLDisabled
});
const arrowRCls = classnames(`${prefixCls}__header__arrow`, {
[`${prefixCls}__header__arrow--${arrowR}`]: arrowR,
[`${prefixCls}__header__arrow--disabled`]: isArrowRDisabled
});
const items = React.Children.map(children, (item, $index) => {
const tabItemCls = classnames(`${prefixCls}__header__item`, {
[`${prefixCls}__header__item--disabled`]: !!item.props.disabled,
[`${prefixCls}__header__item--active`]: $index === value
});
const bindActiveRef = $index === value ? {
ref: this.getActiveNode
} : {};
return React.createElement("div", _extends({
key: $index.toString(),
className: tabItemCls
}, bindActiveRef, {
onClick: e => {
this.handleTabClick(e, $index, item.props.disabled);
}
}), item.props.title, direction === 'horizontal' && item.props.closable && React.createElement(Icon, {
type: "wrong",
onClick: e => {
this.handleTabClose(e, $index, item.props.disabled);
}
}));
});
const content = React.Children.map(children, (item, $index) => {
return React.createElement(Tab, _extends({}, item.props, {
selected: value === $index
}), item.props.children);
});
return React.createElement("div", {
className: cls,
style: style
}, React.createElement("div", {
className: headerCls
}, React.createElement("div", {
className: `${prefixCls}__header__scroll`,
ref: this.tabHeaderWrap
}, React.createElement("div", {
className: `${prefixCls}__header__nav`,
ref: this.tabHeaderNav,
style: isArrowShown ? headerNavStyle : {}
}, items, type === 'line' && this.renderHeaderLine())), isArrowShown && React.createElement(React.Fragment, null, React.createElement("span", {
className: arrowLCls,
onClick: e => !isArrowLDisabled && this.scrollLeftOrTop(e)
}, React.createElement(Icon, {
type: `arrow-${arrowL}`
})), React.createElement("span", {
className: arrowRCls,
onClick: e => !isArrowRDisabled && this.scrollRightOrBottom(e)
}, React.createElement(Icon, {
type: `arrow-${arrowR}`
})))), React.createElement("div", {
className: bodyCls,
style: animateStyle
}, content));
}
}
Tabs.displayName = 'Tabs';
Tabs.Tab = void 0;
Tabs.defaultProps = {
defaultValue: 0,
prefixCls: 'zw-tabs',
type: 'line',
direction: 'horizontal',
size: 'md',
onChange: () => {},
onTabClose: () => {},
animated: true
};
export default Tabs;