import React from 'react' ; import classnames from 'classnames' ; import TabIndicator from '@material/react-tab-indicator' ; import { MDCTabFoundation } from '@material/tab/foundation' ; import { MDCTabAdapter } from '@material/tab/adapter' ; import TabRipple , { TabRippleProps } from './TabRipple' ; export interface TabProps extends React . HTMLProps < HTMLButtonElement | HTMLAnchorElement > { active ? : boolean ; focusOnActivate ? : boolean ; isFadingIndicator ? : boolean ; indicatorContent ? : React . ReactNode ; minWidth ? : boolean ; isMinWidthIndicator ? : boolean ; stacked ? : boolean ; tag ? : 'button' | 'a' ; previousIndicatorClientRect ? : ClientRect ; onInteraction ? : ( ) => void ; } interface MDCTabElementAttributes { 'aria-selected' ? : 'false' | 'true' ; tabIndex ? : number ; } interface TabState extends MDCTabElementAttributes { classList : Set < string > ; activateIndicator : boolean ; previousIndicatorClientRect ? : ClientRect ; } export default class Tab extends React . Component < TabProps , TabState > { foundation ! : MDCTabFoundation ; tabRef : React . RefObject < HTMLButtonElement > = React . createRef ( ) ; tabContentRef : React . RefObject < HTMLSpanElement > = React . createRef ( ) ; tabIndicatorRef : React . RefObject < TabIndicator > = React . createRef ( ) ; tabRippleRef : React . RefObject < TabRipple > = React . createRef ( ) ; static defaultProps : Partial < TabProps > = { active : false , focusOnActivate : true , className : '' , isFadingIndicator : false , indicatorContent : null , minWidth : false , isMinWidthIndicator : false , stacked : false , tag : 'button' , onInteraction : ( ) => null , } ; state : TabState = { classList : new Set ( ) , 'aria-selected' : 'false' , activateIndicator : false , previousIndicatorClientRect : this . props . previousIndicatorClientRect , tabIndex : - 1 , } ; componentDidMount ( ) { const { active , focusOnActivate } = this . props ; this . foundation = new MDCTabFoundation ( this . adapter ) ; this . foundation . init ( ) ; this . foundation . setFocusOnActivate ( focusOnActivate ! ) ; if ( active ) { this . foundation . activate ( ) ; } } componentWillUnmount ( ) { this . foundation . destroy ( ) ; } componentDidUpdate ( prevProps ) { const { active , focusOnActivate , previousIndicatorClientRect } = this . props ; if ( focusOnActivate !== prevProps . focusOnActivate ) { this . foundation . setFocusOnActivate ( focusOnActivate ! ) ; } if ( active !== prevProps . active ) { if ( active ) { this . activate ( previousIndicatorClientRect ) ; } else { this . deactivate ( ) ; } } } get classes ( ) { const { classList } = this . state ; const { className , minWidth , stacked } = this . props ; return classnames ( 'mdc-tab' , Array . from ( classList ) , className , { 'mdc-tab--min-width' : minWidth , 'mdc-tab--stacked' : stacked , } ) ; } get adapter ( ) : MDCTabAdapter { return { addClass : ( className ) => { const classList = new Set ( this . state . classList ) ; classList . add ( className ) ; this . setState ( { classList } ) ; } , removeClass : ( className ) => { const classList = new Set ( this . state . classList ) ; classList . delete ( className ) ; this . setState ( { classList } ) ; } , hasClass : ( className ) => this . classes . split ( ' ' ) . includes ( className ) , setAttr : ( attr , value ? ) => this . setState ( ( prevState ) => ( { ... prevState , [ attr ] : value } ) ) , getOffsetLeft : ( ) => Number ( this . tabRef . current && this . tabRef . current . offsetLeft ) , getOffsetWidth : ( ) => Number ( this . tabRef . current && this . tabRef . current . offsetWidth ) , getContentOffsetLeft : ( ) => this . tabContentRef . current ? this . tabContentRef . current . offsetLeft : 0 , getContentOffsetWidth : ( ) => this . tabContentRef . current ? this . tabContentRef . current . offsetWidth : 0 , focus : ( ) => this . tabRef . current && this . tabRef . current . focus ( ) , notifyInteracted : this . props . onInteraction ! , activateIndicator : ( previousIndicatorClientRect ) => this . setState ( { activateIndicator : true , previousIndicatorClientRect , } ) , deactivateIndicator : ( ) => this . setState ( { activateIndicator : false } ) , } ; } activate ( computeIndicatorClientRect ? ) { this . foundation . activate ( computeIndicatorClientRect ) ; } deactivate ( ) { this . foundation . deactivate ( ) ; } computeIndicatorClientRect = ( ) => { if ( ! this . tabIndicatorRef . current ) return { } as ClientRect ; return this . tabIndicatorRef . current . computeContentClientRect ( ) ; } ; computeDimensions = ( ) => { return this . foundation . computeDimensions ( ) ; } ; focus = ( ) => { this . tabRef . current && this . tabRef . current . focus ( ) ; } ; onFocus = ( e ) => { this . tabRippleRef . current && this . tabRippleRef . current . handleFocus ( e ) ; } ; onBlur = ( e ) => { this . tabRippleRef . current && this . tabRippleRef . current . handleBlur ( e ) ; } ; render ( ) { const { active , focusOnActivate , previousIndicatorClientRect , className , isFadingIndicator , indicatorContent , minWidth , onInteraction , stacked , tag : Tag , children , isMinWidthIndicator , ... otherProps } = this . props ; const { tabIndex , [ 'aria-selected' ] : ariaSelected } = this . state ; return ( < Tag className = { this . classes } role = 'tab' aria - selected = { ariaSelected } tabIndex = { tabIndex } onFocus = { this . onFocus } onBlur = { this . onBlur } ref = { this . tabRef } { ... otherProps } > < span className = 'mdc-tab__content' ref = { this . tabContentRef } > { children } { isMinWidthIndicator ? this . renderIndicator ( ) : null } < /span> { isMinWidthIndicator ? null : this . renderIndicator ( ) } < TabRipple ref = { this . tabRippleRef } /> < /Tag>); } renderIndicator ( ) { const { isFadingIndicator , indicatorContent } = this . props ; const { activateIndicator , previousIndicatorClientRect } = this . state ; return ( < TabIndicator icon = { ! ! indicatorContent } fade = { isFadingIndicator } active = { activateIndicator } previousIndicatorClientRect = { previousIndicatorClientRect } ref = { this . tabIndicatorRef } > { indicatorContent } < /TabIndicator>); } } export { TabRipple , Tab , TabRippleProps } ;