import React, { Component } from 'react'; import PropTypes from 'prop-types'; import observeResize from 'simple-element-resize-detector'; import { FaAngleDoubleRight } from 'react-icons/fa'; import ContextMenu from '../ContextMenu'; import createStyledComponent from '../utils/createStyledComponent'; import * as styles from './styles'; const TabsWrapper = createStyledComponent(styles); export type ReactButtonElement = React.ReactElement< JSX.IntrinsicElements['button'], 'button' >; export interface Tab

{ name: string; value?: string; component?: React.ComponentType

; selector?: (tab: this) => P; } interface Props

{ tabs: ReactButtonElement[]; items: Tab

[]; main: boolean | undefined; onClick: (value: string) => void; position: 'left' | 'right' | 'center'; collapsible: boolean | undefined; selected: string | undefined; } interface State { visibleTabs: ReactButtonElement[]; hiddenTabs: ReactButtonElement[]; subMenuOpened: boolean; contextMenu: { top: number; left: number } | undefined; } export default class TabsHeader

extends Component, State> { state: State = { visibleTabs: this.props.tabs.slice(), hiddenTabs: [], subMenuOpened: false, contextMenu: undefined, }; iconWidth = 0; hiddenTabsWidth: number[] = []; tabsWrapperRef?: HTMLDivElement | null; tabsRef?: HTMLDivElement | null; resizeDetector?: HTMLIFrameElement; UNSAFE_componentWillReceiveProps(nextProps: Props

) { if ( nextProps.tabs !== this.props.tabs || nextProps.selected !== this.props.selected || nextProps.collapsible !== this.props.collapsible ) { this.setState({ hiddenTabs: [], visibleTabs: nextProps.tabs.slice() }); } } componentDidMount() { if (this.props.collapsible) { this.collapse(); this.enableResizeEvents(); } } componentDidUpdate(prevProps: Props

) { 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] as HTMLButtonElement) .value === 'expandIcon' ) { this.iconWidth = tabButtons[tabButtons.length - 1].getBoundingClientRect().width; 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() { if (this.props.collapsible) { 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 ( this.props.position === 'right' && hiddenTabs.length > 0 && tabsRef!.getBoundingClientRect().width + this.hiddenTabsWidth[0] < tabsWrapperRef!.getBoundingClientRect().width ) { while ( i < tabs.length - 1 && tabsRef!.getBoundingClientRect().width + this.hiddenTabsWidth[0] < tabsWrapperRef!.getBoundingClientRect().width ) { hiddenTab = hiddenTabs.shift(); visibleTabs.splice(Number(hiddenTab!.key), 0, hiddenTab!); i++; } } else { while ( i > 0 && tabButtons[i] && tabButtons[i].getBoundingClientRect().right >= tabsWrapperRight - this.iconWidth ) { if ((tabButtons[i] as HTMLButtonElement).value !== selected) { hiddenTabs.unshift(...visibleTabs.splice(i, 1)); this.hiddenTabsWidth.unshift( tabButtons[i].getBoundingClientRect().width ); } else { tabsWrapperRight -= tabButtons[i].getBoundingClientRect().width; } i--; } } } else { while ( i < tabs.length - 1 && tabButtons[i] && tabButtons[i].getBoundingClientRect().right + this.hiddenTabsWidth[0] < tabsWrapperRight - this.iconWidth ) { 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: React.RefCallback = (node) => { this.tabsWrapperRef = node; }; getTabsRef: React.RefCallback = (node) => { this.tabsRef = node; }; expandMenu: React.MouseEventHandler = (e) => { 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 (

{visibleTabs} {this.props.collapsible && visibleTabs.length < this.props.items.length && ( )}
{this.props.collapsible && contextMenu && ( )} ); } static propTypes = { tabs: PropTypes.array.isRequired, items: PropTypes.array.isRequired, main: PropTypes.bool, onClick: PropTypes.func, position: PropTypes.string, collapsible: PropTypes.bool, selected: PropTypes.string, }; }