2019-01-03 16:00:55 +03:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import observeResize from 'simple-element-resize-detector';
|
|
|
|
import CollapseIcon from 'react-icons/lib/fa/angle-double-right';
|
|
|
|
import ContextMenu from '../ContextMenu';
|
|
|
|
import createStyledComponent from '../utils/createStyledComponent';
|
|
|
|
import * as styles from './styles';
|
|
|
|
|
|
|
|
const TabsWrapper = createStyledComponent(styles);
|
|
|
|
|
|
|
|
export default class TabsHeader extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
visibleTabs: props.tabs.slice(),
|
|
|
|
hiddenTabs: [],
|
|
|
|
subMenuOpened: false,
|
|
|
|
contextMenu: undefined
|
|
|
|
};
|
|
|
|
this.iconWidth = 0;
|
|
|
|
this.hiddenTabsWidth = [];
|
|
|
|
}
|
|
|
|
|
2020-08-01 21:21:04 +03:00
|
|
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
2019-01-10 21:51:14 +03:00
|
|
|
if (
|
|
|
|
nextProps.tabs !== this.props.tabs ||
|
2019-01-03 16:00:55 +03:00
|
|
|
nextProps.selected !== this.props.selected ||
|
2019-01-10 21:51:14 +03:00
|
|
|
nextProps.collapsible !== this.props.collapsible
|
|
|
|
) {
|
2019-01-03 16:00:55 +03:00
|
|
|
this.setState({ hiddenTabs: [], visibleTabs: nextProps.tabs.slice() });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
if (this.props.collapsible) {
|
|
|
|
this.collapse();
|
|
|
|
this.enableResizeEvents();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
const { collapsible } = this.props;
|
|
|
|
if (!collapsible) {
|
|
|
|
if (prevProps.collapsible !== collapsible) this.disableResizeEvents();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let shouldCollapse = false;
|
|
|
|
if (this.iconWidth === 0) {
|
|
|
|
const tabButtons = this.tabsRef.children;
|
|
|
|
if (this.tabsRef.children[tabButtons.length - 1].value === 'expandIcon') {
|
2019-01-10 21:51:14 +03:00
|
|
|
this.iconWidth = tabButtons[
|
|
|
|
tabButtons.length - 1
|
|
|
|
].getBoundingClientRect().width;
|
2019-01-03 16:00:55 +03:00
|
|
|
shouldCollapse = true;
|
|
|
|
}
|
|
|
|
} else if (this.state.hiddenTabs.length === 0) {
|
|
|
|
this.iconWidth = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prevProps.collapsible !== collapsible) {
|
|
|
|
this.enableResizeEvents();
|
|
|
|
shouldCollapse = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldCollapse || this.props.selected !== prevProps.selected) {
|
|
|
|
this.collapse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.disableResizeEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
enableResizeEvents() {
|
|
|
|
this.resizeDetector = observeResize(this.tabsWrapperRef, this.collapse);
|
|
|
|
window.addEventListener('mousedown', this.hideSubmenu);
|
|
|
|
}
|
|
|
|
|
|
|
|
disableResizeEvents() {
|
|
|
|
this.resizeDetector.remove();
|
|
|
|
window.removeEventListener('mousedown', this.hideSubmenu);
|
|
|
|
}
|
|
|
|
|
|
|
|
collapse = () => {
|
|
|
|
if (this.state.subMenuOpened) this.hideSubmenu();
|
|
|
|
|
|
|
|
const { selected, tabs } = this.props;
|
|
|
|
const tabsWrapperRef = this.tabsWrapperRef;
|
|
|
|
const tabsRef = this.tabsRef;
|
|
|
|
const tabButtons = this.tabsRef.children;
|
|
|
|
const visibleTabs = this.state.visibleTabs;
|
|
|
|
const hiddenTabs = this.state.hiddenTabs;
|
|
|
|
let tabsWrapperRight = tabsWrapperRef.getBoundingClientRect().right;
|
|
|
|
if (!tabsWrapperRight) return; // tabs are hidden
|
|
|
|
|
|
|
|
const tabsRefRight = tabsRef.getBoundingClientRect().right;
|
|
|
|
let i = visibleTabs.length - 1;
|
|
|
|
let hiddenTab;
|
|
|
|
|
|
|
|
if (tabsRefRight >= tabsWrapperRight - this.iconWidth) {
|
|
|
|
if (
|
2019-01-10 21:51:14 +03:00
|
|
|
this.props.position === 'right' &&
|
|
|
|
hiddenTabs.length > 0 &&
|
2019-01-03 16:00:55 +03:00
|
|
|
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
|
2019-01-10 21:51:14 +03:00
|
|
|
tabsWrapperRef.getBoundingClientRect().width
|
2019-01-03 16:00:55 +03:00
|
|
|
) {
|
|
|
|
while (
|
|
|
|
i < tabs.length - 1 &&
|
|
|
|
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
|
2019-01-10 21:51:14 +03:00
|
|
|
tabsWrapperRef.getBoundingClientRect().width
|
2019-01-03 16:00:55 +03:00
|
|
|
) {
|
|
|
|
hiddenTab = hiddenTabs.shift();
|
|
|
|
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while (
|
2019-01-10 21:51:14 +03:00
|
|
|
i > 0 &&
|
|
|
|
tabButtons[i] &&
|
|
|
|
tabButtons[i].getBoundingClientRect().right >=
|
|
|
|
tabsWrapperRight - this.iconWidth
|
2019-01-03 16:00:55 +03:00
|
|
|
) {
|
|
|
|
if (tabButtons[i].value !== selected) {
|
|
|
|
hiddenTabs.unshift(...visibleTabs.splice(i, 1));
|
2019-01-10 21:51:14 +03:00
|
|
|
this.hiddenTabsWidth.unshift(
|
|
|
|
tabButtons[i].getBoundingClientRect().width
|
|
|
|
);
|
2019-01-03 16:00:55 +03:00
|
|
|
} else {
|
|
|
|
tabsWrapperRight -= tabButtons[i].getBoundingClientRect().width;
|
|
|
|
}
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while (
|
2019-01-10 21:51:14 +03:00
|
|
|
i < tabs.length - 1 &&
|
|
|
|
tabButtons[i] &&
|
|
|
|
tabButtons[i].getBoundingClientRect().right + this.hiddenTabsWidth[0] <
|
|
|
|
tabsWrapperRight - this.iconWidth
|
2019-01-03 16:00:55 +03:00
|
|
|
) {
|
|
|
|
hiddenTab = hiddenTabs.shift();
|
|
|
|
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab);
|
|
|
|
this.hiddenTabsWidth.shift();
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.setState({ visibleTabs, hiddenTabs });
|
|
|
|
};
|
|
|
|
|
|
|
|
hideSubmenu = () => {
|
|
|
|
this.setState({ subMenuOpened: false, contextMenu: undefined });
|
|
|
|
};
|
|
|
|
|
|
|
|
getTabsWrapperRef = node => {
|
|
|
|
this.tabsWrapperRef = node;
|
|
|
|
};
|
|
|
|
|
|
|
|
getTabsRef = node => {
|
|
|
|
this.tabsRef = node;
|
|
|
|
};
|
|
|
|
|
2019-01-10 21:51:14 +03:00
|
|
|
expandMenu = e => {
|
2019-01-03 16:00:55 +03:00
|
|
|
const rect = e.currentTarget.children[0].getBoundingClientRect();
|
|
|
|
this.setState({
|
|
|
|
contextMenu: {
|
|
|
|
top: rect.top + 10,
|
|
|
|
left: rect.left
|
|
|
|
},
|
|
|
|
subMenuOpened: true
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { visibleTabs, hiddenTabs, contextMenu } = this.state;
|
|
|
|
return (
|
|
|
|
<TabsWrapper
|
|
|
|
innerRef={this.getTabsWrapperRef}
|
|
|
|
main={this.props.main}
|
|
|
|
position={this.props.position}
|
|
|
|
>
|
|
|
|
<div ref={this.getTabsRef}>
|
|
|
|
{visibleTabs}
|
2019-01-10 21:51:14 +03:00
|
|
|
{this.props.collapsible &&
|
|
|
|
visibleTabs.length < this.props.items.length && (
|
|
|
|
<button onClick={this.expandMenu} value="expandIcon">
|
|
|
|
<CollapseIcon />
|
|
|
|
</button>
|
|
|
|
)}
|
2019-01-03 16:00:55 +03:00
|
|
|
</div>
|
2019-01-10 21:51:14 +03:00
|
|
|
{this.props.collapsible && contextMenu && (
|
2019-01-03 16:00:55 +03:00
|
|
|
<ContextMenu
|
|
|
|
items={hiddenTabs}
|
|
|
|
onClick={this.props.onClick}
|
|
|
|
x={contextMenu.left}
|
|
|
|
y={contextMenu.top}
|
|
|
|
visible={this.state.subMenuOpened}
|
|
|
|
/>
|
2019-01-10 21:51:14 +03:00
|
|
|
)}
|
2019-01-03 16:00:55 +03:00
|
|
|
</TabsWrapper>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TabsHeader.propTypes = {
|
|
|
|
tabs: PropTypes.array.isRequired,
|
|
|
|
items: PropTypes.array.isRequired,
|
|
|
|
main: PropTypes.bool,
|
|
|
|
onClick: PropTypes.func,
|
|
|
|
position: PropTypes.string,
|
|
|
|
collapsible: PropTypes.bool,
|
|
|
|
selected: PropTypes.string
|
|
|
|
};
|