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 < P > { name : string ; value ? : string ; component ? : React . ComponentType < P > ; selector ? : ( tab : this ) => P ; } interface Props < P > { tabs : ReactButtonElement [ ] ; items : Tab < P > [ ] ; 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 < P > extends Component < Props < P > , 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 ) { 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 ) { 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 ; 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 < HTMLDivElement > = ( node ) => { this . tabsWrapperRef = node ; } ; getTabsRef : React . RefCallback < HTMLDivElement > = ( 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 ( < TabsWrapper ref = { this . getTabsWrapperRef } main = { this . props . main } position = { this . props . position } > < div ref = { this . getTabsRef } > { visibleTabs } { this . props . collapsible && visibleTabs . length < this . props . items . length && ( < button onClick = { this . expandMenu } value = "expandIcon" > < FaAngleDoubleRight /> < /button>)} < /div> { this . props . collapsible && contextMenu && ( < ContextMenu items = { hiddenTabs } onClick = { this . props . onClick } x = { contextMenu . left } y = { contextMenu . top } visible = { this . state . subMenuOpened } / > ) } < /TabsWrapper>); } 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 , } ; }