import { IconButton } from "office-ui-fabric-react/lib/Button" ; import { initializeIcons } from '@uifabric/icons' ; initializeIcons ( ) ; import * as React from "react" ; import styles from "./Carousel.module.scss" ; import { ICarouselProps , ICarouselState , CarouselButtonsDisplay , CarouselButtonsLocation , CarouselIndicatorShape } from "." ; import { css , ICssInput } from "@uifabric/utilities/lib" ; import { ProcessingState } from "./ICarouselState" ; import { Spinner } from "office-ui-fabric-react/lib/Spinner" ; import { isArray } from "@pnp/common" ; import * as telemetry from '../../common/telemetry' ; import CarouselImage from "./CarouselImage" ; import { CarouselIndicatorsDisplay } from "./ICarouselProps" ; export class Carousel extends React . Component < ICarouselProps , ICarouselState > { private _intervalId : number | undefined ; constructor ( props ) { super ( props ) ; const currentIndex = props . startIndex ? props . startIndex : 0 ; telemetry . track ( 'ReactCarousel' , { } ) ; this . state = { currentIndex , processingState : ProcessingState . idle } ; } param prevProps public componentDidUpdate ( prevProps ) { const currProps = this . props ; const prevPropsElementKey = prevProps . triggerPageEvent && prevProps . element ? ( prevProps . element as JSX . Element ) . key : null ; const nextPropsElementKey = currProps . triggerPageEvent && currProps . element ? ( currProps . element as JSX . Element ) . key : null ; if ( this . state . processingState === ProcessingState . processing && nextPropsElementKey != null && prevPropsElementKey != nextPropsElementKey ) { this . setState ( { processingState : ProcessingState . idle } ) ; this . startCycle ( ) ; } } public componentDidMount ( ) { this . startCycle ( ) ; } public render ( ) { const { currentIndex , processingState } = this . state ; const { containerStyles , contentContainerStyles , containerButtonsStyles , prevButtonStyles , nextButtonStyles , loadingComponentContainerStyles , prevButtonIconName = 'ChevronLeft' , nextButtonIconName = 'ChevronRight' , loadingComponent = < Spinner />, pauseOnHover, interval, indicatorsDisplay, rootStyles, indicatorsContainerStyles } = this.props; const processing = processingState === ProcessingState . processing ; const prevButtonDisabled = processing || this . isCarouselButtonDisabled ( false ) ; const nextButtonDisabled = processing || this . isCarouselButtonDisabled ( true ) ; const element = this . getElementToDisplay ( currentIndex ) ; return ( < div className = { this . getMergedStyles ( styles . root , rootStyles ) } > < div className = { this . getMergedStyles ( styles . container , containerStyles ) } > < div className = { this . getMergedStyles ( this . getButtonContainerStyles ( ) , containerButtonsStyles ) } onClick = { ( ) => { if ( ! prevButtonDisabled ) { this . onCarouselButtonClicked ( false ) ; } } } > < IconButton className = { this . getMergedStyles ( this . getButtonStyles ( false ) , prevButtonStyles ) } iconProps = { { iconName : prevButtonIconName } } disabled = { prevButtonDisabled } onClick = { ( ) => { this . onCarouselButtonClicked ( false ) ; } } /> < /div> < div className = { this . getMergedStyles ( styles . contentContainer , contentContainerStyles ) } onMouseOver = { pauseOnHover && interval !== null ? this . pauseCycle : undefined } onTouchStart = { pauseOnHover && interval !== null ? this . pauseCycle : undefined } onMouseLeave = { pauseOnHover && interval !== null ? this . startCycle : undefined } onTouchEnd = { pauseOnHover && interval !== null ? this . startCycle : undefined } > { processing && < div className = { this . getMergedStyles ( styles . loadingComponent , loadingComponentContainerStyles ) } > { loadingComponent } < /div>} { ! processing && this . renderSlide ( element ) } { indicatorsDisplay !== CarouselIndicatorsDisplay . block && this . getIndicatorsElement ( ) } < /div> < div className = { this . getMergedStyles ( this . getButtonContainerStyles ( ) , containerButtonsStyles ) } onClick = { ( ) => { if ( ! nextButtonDisabled ) { this . onCarouselButtonClicked ( true ) ; } } } > < IconButton className = { this . getMergedStyles ( this . getButtonStyles ( true ) , nextButtonStyles ) } iconProps = { { iconName : nextButtonIconName } } disabled = { nextButtonDisabled } onClick = { ( ) => { this . onCarouselButtonClicked ( true ) ; } } /> < /div> < /div> { indicatorsDisplay === CarouselIndicatorsDisplay . block && < div className = { this . getMergedStyles ( styles . indicatorsContainer , indicatorsContainerStyles ) } > { this . getIndicatorsElement ( ) } < /div>} < /div>); } private renderSlide = ( element ) : JSX . Element [ ] => { const isAnimated = this . props . slide !== false && ! this . props . triggerPageEvent ; const { currentIndex , previousIndex , slideRight } = this . state ; if ( ! isAnimated || previousIndex === undefined ) { return [ < div className = { styles . slideWrapper } > { element } < /div>]; } const previousElement = this . getElementToDisplay ( previousIndex ) ; const result = [ ] ; result . push ( < div key = { currentIndex } className = { css ( styles . slideWrapper , { [ styles . slideFromLeft ] : slideRight , [ styles . slideFromRight ] : ! slideRight } ) } > { element } < /div>); if ( slideRight ) { result . push ( < div key = { previousIndex } className = { css ( styles . slideWrapper , styles . slideRight , styles . right ) } > { previousElement } < /div>); } else { result . unshift ( < div key = { previousIndex } className = { css ( styles . slideWrapper , styles . slideLeft , styles . left ) } > { previousElement } < /div>); } return result ; } ; private getIndicatorsElement = ( ) : JSX . Element | null => { const { indicators , indicatorShape = CarouselIndicatorShape . rectangle , onRenderIndicator , triggerPageEvent , indicatorClassName , indicatorStyle } = this . props ; const { currentIndex = 0 } = this . state ; if ( indicators === false ) { return null ; } const elementsCount = triggerPageEvent ? this . props . elementsCount : isArray ( this . props . element ) ? ( this . props . element as any [ ] ) . length : 1 ; const indicatorElements = [ ] ; for ( let i = 0 ; i < elementsCount ; i ++ ) { if ( onRenderIndicator ) { indicatorElements . push ( onRenderIndicator ( i , this . onIndicatorClick ) ) ; } else { indicatorElements . push ( < li className = { css ( indicatorClassName , { [ styles . active ] : i === currentIndex } ) } style = { indicatorStyle } onClick = { e => this . onIndicatorClick ( e , i ) } / > ) ; } } if ( onRenderIndicator ) { return < div className = { styles . indicators } > { indicatorElements } < /div>; } else { return < ol className = { css ( { [ styles . indicators ] : true , [ styles . circle ] : indicatorShape === CarouselIndicatorShape . circle , [ styles . rectangle ] : indicatorShape === CarouselIndicatorShape . rectangle , [ styles . square ] : indicatorShape === CarouselIndicatorShape . square } ) } > { indicatorElements } < /ol>; } } ; private onIndicatorClick = ( e , index ) : void => { this . startCycle ( ) ; if ( this . props . onSelect ) { this . props . onSelect ( index ) ; } const { currentIndex } = this . state ; this . setState ( { currentIndex : index , previousIndex : currentIndex , slideRight : index < currentIndex } ) ; } ; private getButtonContainerStyles ( ) { const buttonsDisplayMode = this . props . buttonsDisplay ? this . props . buttonsDisplay : CarouselButtonsDisplay . block ; let buttonDisplayModeCss = "" ; switch ( buttonsDisplayMode ) { case CarouselButtonsDisplay . block : buttonDisplayModeCss = styles . blockButtonsContainer ; break ; case CarouselButtonsDisplay . buttonsOnly : buttonDisplayModeCss = styles . buttonsOnlyContainer ; break ; case CarouselButtonsDisplay . hidden : buttonDisplayModeCss = styles . hiddenButtonsContainer ; break ; default : return "" ; } const buttonsLocation = this . props . buttonsLocation ? this . props . buttonsLocation : CarouselButtonsLocation . top ; let buttonsLocationCss = "" ; switch ( buttonsLocation ) { case CarouselButtonsLocation . top : buttonsLocationCss = styles . blockButtonsContainer ; break ; case CarouselButtonsLocation . center : buttonsLocationCss = styles . centralButtonsContainer ; break ; case CarouselButtonsLocation . bottom : buttonsLocationCss = styles . bottomButtonsContainer ; break ; default : return "" ; } const result = css ( buttonDisplayModeCss , buttonsLocationCss ) ; return result ; } private getButtonStyles ( nextButton ) { const buttonsDisplayMode = this . props . buttonsDisplay ? this . props . buttonsDisplay : CarouselButtonsDisplay . block ; let result = "" ; if ( buttonsDisplayMode === CarouselButtonsDisplay . buttonsOnly ) { result = nextButton ? styles . buttonsOnlyNextButton : styles . buttonsOnlyPrevButton ; } return css ( result ) ; } private getMergedStyles = ( componentStyles , userStyles ? ) : string => { const mergedStyles = userStyles ? css ( componentStyles , userStyles ) : css ( componentStyles ) ; return mergedStyles ; } ; private isCarouselButtonDisabled = ( nextButton ) : boolean => { const isInfinite = this . props . isInfinite != undefined ? this . props . isInfinite : false ; const currentIndex = this . state . currentIndex ; let result = false ; if ( nextButton ) { result = this . props . canMoveNext != undefined ? ! this . props . canMoveNext : ( currentIndex === ( this . props . element as JSX . Element [ ] ) . length - 1 ) && ! isInfinite ; } else { result = this . props . canMovePrev != undefined ? ! this . props . canMovePrev : ( 0 === currentIndex ) && ! isInfinite ; } return result ; } ; private onCarouselButtonClicked = ( nextButtonClicked ) : void => { this . startCycle ( ) ; const currentIndex = this . state . currentIndex ; let nextIndex = this . state . currentIndex ; let processingState = ProcessingState . processing ; if ( this . props . triggerPageEvent ) { const canMove = nextButtonClicked ? this . props . canMoveNext !== false : this . props . canMovePrev !== false ; if ( canMove ) { nextIndex = nextButtonClicked ? ( currentIndex + 1 ) : ( currentIndex - 1 ) ; this . props . triggerPageEvent ( nextIndex ) ; processingState = ProcessingState . processing ; } } else { nextIndex = this . getNextIndex ( nextButtonClicked ) ; if ( nextIndex !== currentIndex ) { if ( nextButtonClicked && this . props . onMoveNextClicked ) { this . props . onMoveNextClicked ( nextIndex ) ; } else if ( this . props . onMovePrevClicked ) { this . props . onMovePrevClicked ( nextIndex ) ; } } processingState = ProcessingState . idle ; } if ( nextIndex !== currentIndex ) { if ( this . props . onSelect ) { this . props . onSelect ( nextIndex ) ; } this . setState ( { currentIndex : nextIndex , previousIndex : currentIndex , slideRight : ! nextButtonClicked , processingState } ) ; } } ; private getNextIndex = ( nextButtonClicked ) : number => { const currentIndex = this . state . currentIndex ; let nextIndex = currentIndex ; const isInfinite = this . props . isInfinite !== undefined ? this . props . isInfinite : false ; const length = ( this . props . element as JSX . Element [ ] ) . length ; if ( nextButtonClicked ) { if ( currentIndex < length - 1 ) { nextIndex = currentIndex + 1 ; } else if ( isInfinite ) { nextIndex = 0 ; } } else { if ( currentIndex - 1 >= 0 ) { nextIndex = currentIndex - 1 ; } else if ( isInfinite ) { nextIndex = length - 1 ; } } return nextIndex ; } ; private getElementToDisplay = ( currentIndex ) : JSX . Element => { const { element } = this . props ; let result = null ; let arrayLen ; if ( ! element ) { result = null ; } else if ( isArray ( element ) && ( arrayLen = ( element as any [ ] ) . length ) > 0 ) { if ( currentIndex >= 0 && arrayLen > currentIndex ) { const arrayEl = element [ currentIndex ] ; result = 'props' in arrayEl ? arrayEl as JSX . Element : < CarouselImage { ... arrayEl } / > ; } } else { result = element as JSX . Element ; } return result ; } ; private startCycle = ( ) : void => { const { interval , triggerPageEvent } = this . props ; if ( this . _intervalId ) { if ( triggerPageEvent ) { clearTimeout ( this . _intervalId ) ; } else { clearInterval ( this . _intervalId ) ; } } if ( interval !== null ) { const intervalValue = interval || 5000 ; if ( ! triggerPageEvent ) { this . _intervalId = window . setInterval ( this . moveNext , intervalValue ) ; } else { this . _intervalId = window . setTimeout ( this . moveNext , intervalValue ) ; } } } ; private moveNext = ( ) : void => { if ( ! this . isCarouselButtonDisabled ( true ) ) { this . onCarouselButtonClicked ( true ) ; } else { if ( this . _intervalId ) { if ( this . props . triggerPageEvent ) { clearTimeout ( this . _intervalId ) ; } else { clearInterval ( this . _intervalId ) ; } } } } ; private pauseCycle = ( ) : void => { if ( this . _intervalId ) { if ( this . props . triggerPageEvent ) { clearTimeout ( this . _intervalId ) ; } else { clearInterval ( this . _intervalId ) ; } } } ; }