react-tabtab
Version:
A mobile support, draggable, editable and api based Tab for ReactJS
410 lines (356 loc) • 16.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ActionButtonStyle = exports.TabListStyle = undefined;
var _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; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _templateObject = _taggedTemplateLiteral(['\n background-color: white;\n text-align: left;\n position: relative;\n white-space: nowrap;\n overflow: hidden;\n width: auto;\n padding: ', ';\n'], ['\n background-color: white;\n text-align: left;\n position: relative;\n white-space: nowrap;\n overflow: hidden;\n width: auto;\n padding: ', ';\n']),
_templateObject2 = _taggedTemplateLiteral(['\n overflow: hidden;\n'], ['\n overflow: hidden;\n']),
_templateObject3 = _taggedTemplateLiteral(['\n padding-left: 0;\n position: relative;\n margin: 0;\n list-style: none;\n display: inline-block;\n transition: transform .3s cubic-bezier(.42, 0, .58, 1);\n'], ['\n padding-left: 0;\n position: relative;\n margin: 0;\n list-style: none;\n display: inline-block;\n transition: transform .3s cubic-bezier(.42, 0, .58, 1);\n']),
_templateObject4 = _taggedTemplateLiteral(['\n height: 100%;\n width ', 'px;\n text-align: center;\n border: 1px solid #d9d9d9;\n border-bottom: 0;\n border-radius: 4px 4px 0 0;\n background: #f9f9f9;\n > svg {\n padding-top: 11px;\n }\n'], ['\n height: 100%;\n width ', 'px;\n text-align: center;\n border: 1px solid #d9d9d9;\n border-bottom: 0;\n border-radius: 4px 4px 0 0;\n background: #f9f9f9;\n > svg {\n padding-top: 11px;\n }\n']),
_templateObject5 = _taggedTemplateLiteral(['\n display: inline-block;\n filter: none;\n position: absolute;\n ', ';\n &:hover {\n cursor: pointer;\n }\n'], ['\n display: inline-block;\n filter: none;\n position: absolute;\n ', ';\n &:hover {\n cursor: pointer;\n }\n']),
_templateObject6 = _taggedTemplateLiteral(['\n display: inline-block;\n filter: none;\n position: absolute;\n left: 0;\n &:hover {\n cursor: pointer;\n }\n'], ['\n display: inline-block;\n filter: none;\n position: absolute;\n left: 0;\n &:hover {\n cursor: pointer;\n }\n']);
var _react = require('react');
var React = _interopRequireWildcard(_react);
var _styledComponents = require('styled-components');
var _styledComponents2 = _interopRequireDefault(_styledComponents);
var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var _IconSvg = require('./IconSvg');
var _isType = require('./utils/isType');
var _TabModal = require('./TabModal');
var _TabModal2 = _interopRequireDefault(_TabModal);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
var buttonWidth = 35;
var getPadding = function getPadding(_ref) {
var showModalButton = _ref.showModalButton,
showArrowButton = _ref.showArrowButton;
var paddingLeft = 0;
var paddingRight = 0;
if (showModalButton) {
paddingLeft += buttonWidth;
}
if (showArrowButton) {
paddingLeft += buttonWidth;
paddingRight += buttonWidth;
if (showModalButton) {
paddingLeft += 2;
}
}
if (paddingLeft > 0) {
paddingLeft += 3;
}
if (paddingRight > 0) {
paddingRight += 3;
}
return '0 ' + paddingRight + 'px 0 ' + paddingLeft + 'px';
};
var TabListStyle = _styledComponents2.default.div(_templateObject, function (props) {
return getPadding(props);
});
var ListInner = _styledComponents2.default.div(_templateObject2);
var ListScroll = _styledComponents2.default.ul(_templateObject3);
var ActionButtonStyle = _styledComponents2.default.div(_templateObject4, buttonWidth);
var makeScrollButton = function makeScrollButton(ActionButton) {
return (0, _styledComponents2.default)(ActionButton)(_templateObject5, function (props) {
return props.left ? props.showModalButton ? 'left: ' + (buttonWidth + 2) + 'px' : 'left: 0' : 'right: 0';
});
};
var makeFoldButton = function makeFoldButton(ActionButton) {
return (0, _styledComponents2.default)(ActionButton)(_templateObject6);
};
var TabListComponent = function (_React$Component) {
_inherits(TabListComponent, _React$Component);
function TabListComponent(props) {
_classCallCheck(this, TabListComponent);
var _this = _possibleConstructorReturn(this, (TabListComponent.__proto__ || Object.getPrototypeOf(TabListComponent)).call(this, props));
_this.handleScroll = _this.handleScroll.bind(_this);
_this.toggleModal = _this.toggleModal.bind(_this);
_this.renderTabs = _this.renderTabs.bind(_this);
_this.renderArrowButton = _this.renderArrowButton.bind(_this);
_this.isShowModalButton = _this.isShowModalButton.bind(_this);
_this.isShowArrowButton = _this.isShowArrowButton.bind(_this);
_this.scrollPosition = 0;
_this.tabRefs = [];
_this.state = {
modalIsOpen: false,
showArrowButton: false,
showModalButton: false
};
return _this;
}
_createClass(TabListComponent, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.isShowArrowButton();
this.isShowModalButton();
if (this.props.activeIndex > 0) this.scrollToIndex(this.props.activeIndex, 'left');
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
if (prevProps.children.length !== this.props.children.length) {
this.isShowArrowButton();
this.isShowModalButton();
}
if (prevProps.activeIndex !== this.props.activeIndex) {
//if we scroll to the last tab, alignment is set to the right side of the tab
var rectSide = this.props.activeIndex === this.props.children.length - 1 ? 'right' : 'left';
this.scrollToIndex(this.props.activeIndex, rectSide);
}
// if prev state show arrow button, and current state doesn't show
// need to reset the scroll position, or some tabs will be hided by container.
if (prevState.showArrowButton && !this.state.showArrowButton) {
this.scrollToZero();
}
if (prevProps.showModalButton !== this.props.showModalButton) {
this.isShowModalButton();
}
if (prevProps.showArrowButton !== this.props.showArrowButton) {
this.isShowArrowButton();
}
}
}, {
key: 'getTabNode',
value: function getTabNode(tab) {
if (tab.__INTERNAL_NODE) {
// normal tab
return tab.__INTERNAL_NODE;
} else if (tab.__DRAG_TAB_INTERNAL_NODE) {
// drag tab
return tab.__DRAG_TAB_INTERNAL_NODE.node;
}
}
}, {
key: 'unifyScrollMax',
value: function unifyScrollMax(width) {
return parseFloat(width / 3 * 2);
}
}, {
key: 'handleScroll',
value: function handleScroll(direction) {
var leftMove = 0;
var containerOffset = this.listContainer.getBoundingClientRect();
var containerWidth = this.listContainer.offsetWidth;
var tabFirstOffset = this.getTabNode(this.tabRefs[0]).getBoundingClientRect();
var tabLastOffset = this.getTabNode(this.tabRefs[this.tabRefs.length - 1]).getBoundingClientRect();
if (direction === 'right') {
leftMove = tabLastOffset.right - containerOffset.right;
if (leftMove > containerWidth) {
leftMove = this.unifyScrollMax(containerWidth);
}
} else if (direction === 'left') {
leftMove = tabFirstOffset.left - containerOffset.left;
if (-leftMove > containerWidth) {
leftMove = -this.unifyScrollMax(containerWidth);
}
}
this.scrollPosition += leftMove;
if (this.scrollPosition < 0) {
this.scrollPosition = 0;
}
this.listScroll.style.transform = 'translate3d(-' + this.scrollPosition + 'px, 0, 0)';
}
// $FlowFixMe
}, {
key: 'scrollToIndex',
value: function scrollToIndex(index, rectSide) {
var tabOffset = this.getTabNode(this.tabRefs[index]).getBoundingClientRect();
var containerOffset = this.listContainer.getBoundingClientRect();
// Cancel scrolling if the tab is visible
if (tabOffset.right < containerOffset.right && tabOffset.left > containerOffset.left) return;
var leftMove = tabOffset[rectSide] - containerOffset[rectSide];
this.scrollPosition += leftMove;
if (this.scrollPosition < 0) {
this.scrollPosition = 0;
}
this.listScroll.style.transform = 'translate3d(-' + this.scrollPosition + 'px, 0, 0)';
}
}, {
key: 'scrollToZero',
value: function scrollToZero() {
this.listScroll.style.transform = 'translate3d(0, 0, 0)';
}
}, {
key: 'toggleModal',
value: function toggleModal(open) {
var _this2 = this;
this.setState({ modalIsOpen: open }, function () {
if (!open) {
// $FlowFixMe
_this2.scrollToIndex(_this2.props.activeIndex, 'right');
}
});
}
}, {
key: 'isShowModalButton',
value: function isShowModalButton() {
var showModalButton = this.props.showModalButton;
if ((0, _isType.isNumber)(showModalButton)) {
// $FlowFixMe, weired. currently set showModalButton as number | bool, but don't know why flow only can recognize it as bool
showModalButton = this.props.children.length >= showModalButton;
}
this.setState({ showModalButton: showModalButton });
}
}, {
key: 'isShowArrowButton',
value: function isShowArrowButton() {
var showArrowButton = this.props.showArrowButton;
if (showArrowButton === 'auto') {
var tabWidth = 0;
var containerWidth = this.listContainer.offsetWidth;
showArrowButton = false;
for (var index = 0; index < this.tabRefs.length; index++) {
var tab = this.getTabNode(this.tabRefs[index]);
tabWidth += tab.offsetWidth;
if (tabWidth >= containerWidth) {
showArrowButton = true;
break;
}
}
}
// $FlowFixMe: flow will show 'auto' is not bool, but with this logic, showArrowButton will never be 'auto'
this.setState({ showArrowButton: showArrowButton });
}
}, {
key: 'renderTabs',
value: function renderTabs() {
var _this3 = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var isModal = arguments[1];
var _props = this.props,
children = _props.children,
activeIndex = _props.activeIndex,
handleTabChange = _props.handleTabChange,
handleEdit = _props.handleEdit,
customStyle = _props.customStyle;
var props = {
handleTabChange: handleTabChange,
handleEdit: handleEdit,
//$FlowFixMe
CustomTabStyle: customStyle.Tab
};
if (!isModal) {
this.tabRefs = [];
}
return React.Children.map(children, function (child, index) {
return React.cloneElement(child, _extends({
key: index,
active: index === activeIndex,
index: index,
tabIndex: index,
ref: function ref(node) {
if (!isModal && node) {
_this3.tabRefs.push(node);
}
}
}, props, options));
});
}
}, {
key: 'renderArrowButton',
value: function renderArrowButton(ScrollButton) {
var _this4 = this;
var showArrowButton = this.state.showArrowButton;
if (showArrowButton) {
return React.createElement(
'div',
null,
React.createElement(
ScrollButton,
{ left: true,
onClick: function onClick() {
_this4.handleScroll('left');
},
ref: function ref(node) {
return _this4.leftArrowNode = node;
},
showModalButton: this.state.showModalButton },
React.createElement(_IconSvg.LeftIcon, null)
),
React.createElement(
ScrollButton,
{ onClick: function onClick() {
_this4.handleScroll('right');
},
ref: function ref(node) {
return _this4.rightArrowNode = node;
} },
React.createElement(_IconSvg.RightIcon, null)
)
);
}
return null;
}
}, {
key: 'render',
value: function render() {
var _this5 = this;
var _props2 = this.props,
customStyle = _props2.customStyle,
activeIndex = _props2.activeIndex,
handleTabChange = _props2.handleTabChange,
handleTabSequence = _props2.handleTabSequence,
ExtraButton = _props2.ExtraButton;
var modalIsOpen = this.state.modalIsOpen;
var TabList = customStyle.TabList || TabListStyle;
var ActionButton = customStyle.ActionButton || ActionButtonStyle;
var ScrollButton = makeScrollButton(ActionButton);
var FoldButton = makeFoldButton(ActionButton);
(0, _invariant2.default)(this.props.children, 'React-tabtab Error: You MUST pass at least one tab');
return React.createElement(
'div',
null,
ExtraButton ? ExtraButton : null,
React.createElement(
TabList,
{ hasExtraButton: !!ExtraButton,
showModalButton: this.state.showModalButton,
showArrowButton: this.state.showArrowButton },
this.state.showModalButton ? React.createElement(
FoldButton,
{ ref: function ref(node) {
return _this5.foldNode = node;
},
onClick: this.toggleModal.bind(this, true),
showArrowButton: this.state.showArrowButton },
React.createElement(_IconSvg.BulletIcon, null)
) : null,
this.renderArrowButton(ScrollButton),
React.createElement(
ListInner,
{ ref: function ref(node) {
return _this5.listContainer = node;
} },
React.createElement(
ListScroll,
{ ref: function ref(node) {
return _this5.listScroll = node;
}, role: 'tablist' },
this.renderTabs()
)
)
),
modalIsOpen ? React.createElement(
_TabModal2.default,
{ closeModal: this.toggleModal.bind(this, false),
handleTabSequence: handleTabSequence,
handleTabChange: handleTabChange,
activeIndex: activeIndex },
this.renderTabs({ vertical: true }, true)
) : null
);
}
}]);
return TabListComponent;
}(React.Component);
exports.default = TabListComponent;
TabListComponent.displayName = 'TabList';
exports.TabListStyle = TabListStyle;
exports.ActionButtonStyle = ActionButtonStyle;