/* eslint-disable jsx-a11y/no-static-element-interactions */ import React, { ReactNode, Children, ReactChild, ReactFragment, ReactPortal } from 'react'; import cls from 'classnames'; import PropTypes from 'prop-types'; import BaseComponent from "../_base/baseComponent"; import { CarouselProps } from './interface'; import { cssClasses, numbers, strings } from '@douyinfe/semi-foundation/carousel/constants'; import CarouselFoundation, { CarouselAdapter } from '@douyinfe/semi-foundation/carousel/foundation'; import CarouselIndicator from './CarouselIndicator'; import CarouselArrow from './CarouselArrow'; import '@douyinfe/semi-foundation/carousel/carousel.scss'; import { debounce } from 'lodash'; import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined'; export interface CarouselState { activeIndex: number; children: (ReactChild | ReactFragment | ReactPortal)[]; preIndex: number; isReverse: boolean; isInit: boolean; } class Carousel extends BaseComponent { static propTypes = { activeIndex: PropTypes.number, animation:PropTypes.oneOf(strings.ANIMATION_MAP), arrowProps: PropTypes.object, autoPlay: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), className: PropTypes.string, defaultActiveIndex: PropTypes.number, indicatorPosition: PropTypes.oneOf(strings.POSITION_MAP), indicatorSize: PropTypes.oneOf(strings.SIZE), indicatorType: PropTypes.oneOf(strings.TYPE_MAP), theme: PropTypes.oneOf(strings.THEME_MAP), onChange: PropTypes.func, arrowType: PropTypes.oneOf(strings.ARROW_MAP), showArrow: PropTypes.bool, showIndicator: PropTypes.bool, slideDirection: PropTypes.oneOf(strings.DIRECTION), speed: PropTypes.number, style: PropTypes.object, trigger: PropTypes.oneOf(strings.TRIGGER) }; static defaultProps: CarouselProps = { children: [], animation: 'slide', autoPlay: true, arrowType: 'always', defaultActiveIndex: numbers.DEFAULT_ACTIVE_INDEX, indicatorPosition: 'center', indicatorSize: 'small', indicatorType: 'dot', theme: 'light', onChange: () => undefined, showArrow: true, showIndicator: true, slideDirection: 'left', speed: numbers.DEFAULT_SPEED, trigger: 'click' }; foundation: CarouselFoundation; constructor(props: CarouselProps) { super(props); this.foundation = new CarouselFoundation(this.adapter); const defaultActiveIndex = this.foundation.getDefaultActiveIndex(); this.state = { activeIndex: defaultActiveIndex, children: this.getChildren(), preIndex: defaultActiveIndex, isReverse: false, isInit: true }; } get adapter(): CarouselAdapter { return { ...super.adapter, notifyChange: (activeIndex: number, preIndex: number): void => { this.props.onChange(activeIndex, preIndex); }, setNewActiveIndex: (activeIndex: number): void => { this.setState({ activeIndex }); }, setPreActiveIndex: (preIndex: number): void => { this.setState({ preIndex }); }, setIsReverse: (isReverse: boolean): void => { this.setState({ isReverse }); }, setIsInit: (isInit: boolean): void => { this.setState({ isInit }); } }; } static getDerivedStateFromProps(props: CarouselProps, state: CarouselState): Partial { const states: Partial = {}; if (!isNullOrUndefined(props.activeIndex) && props.activeIndex !== state.activeIndex) { states.activeIndex = props.activeIndex; } return states; } componentDidMount(): void { this.handleAutoPlay(); } componentWillUnmount(): void { this.foundation.destroy(); } play = (): void => { return this.foundation.handleAutoPlay(); } stop = (): void => { return this.foundation.stop(); }; goTo = ( targetIndex: number): void => { return this.foundation.goTo(targetIndex); }; prev = (): void => { return this.foundation.prev(); }; next = (): void => { return this.foundation.next(); }; handleAutoPlay = (): void => { if (!this.foundation.getIsControledComponent()){ this.foundation.handleAutoPlay(); } } handleMouseEnter = (): void => { const { autoPlay } = this.props; if (typeof autoPlay !== 'object' || autoPlay.hoverToPause){ this.foundation.stop(); } } handleMouseLeave = (): void => { const { autoPlay } = this.props; if ((typeof autoPlay !== 'object' || autoPlay.hoverToPause) && !this.foundation.getIsControledComponent()){ this.foundation.handleAutoPlay(); } } onIndicatorChange = (activeIndex: number): void => { return this.foundation.onIndicatorChange(activeIndex); }; getChildren = (): (ReactChild | ReactFragment | ReactPortal)[] => { const { children: originChildren } = this.props; return Children.toArray(originChildren).filter(child=>{ return React.isValidElement(child); }); } getValidIndex = (activeIndex: number): number => { return this.foundation.getValidIndex(activeIndex); }; renderChildren = () => { const { speed, animation } = this.props; const { activeIndex, children, preIndex, isInit } = this.state; return ( <> {children.map((child: any, index: number) => { const isCurrent = index === activeIndex; const isPrev = index === this.getValidIndex(activeIndex - 1); const isNext = index === this.getValidIndex(activeIndex + 1); const animateStyle = { transitionTimingFunction: 'ease', transitionDuration: `${speed}ms`, animationTimingFunction: 'ease', animationDuration: `${speed}ms`, }; return React.cloneElement(child, { style: { ...child.props.style, ...animateStyle, }, className: cls(child.props.className, { [`${cssClasses.CAROUSEL_CONTENT}-item-prev`]: isPrev, [`${cssClasses.CAROUSEL_CONTENT}-item-next`]: isNext, [`${cssClasses.CAROUSEL_CONTENT}-item-current`]: isCurrent, [`${cssClasses.CAROUSEL_CONTENT}-item`]: true, [`${cssClasses.CAROUSEL_CONTENT}-item-active`]: isCurrent, [`${cssClasses.CAROUSEL_CONTENT}-item-slide-in`]:animation === 'slide' && !isInit && isCurrent, [`${cssClasses.CAROUSEL_CONTENT}-item-slide-out`]:animation === 'slide' && !isInit && index === preIndex, }) }); })} ); } renderIndicator = () => { const { children, activeIndex } = this.state; const { showIndicator, indicatorType, theme, indicatorPosition, indicatorSize, trigger } = this.props; const carouselIndicatorCls = cls({ [cssClasses.CAROUSEL_INDICATOR]: true }); if (showIndicator && children.length > 1){ return (
); } return null; } renderArrow = () => { const { children } = this.state; const { showArrow, arrowType, theme, arrowProps } = this.props; if (showArrow && children.length > 1){ return ( ); } return null; }; render(): ReactNode { const { animation, className, style, slideDirection } = this.props; const { isReverse } = this.state; const carouselWrapperCls = cls(className, { [cssClasses.CAROUSEL]: true }); return (
this.foundation.handleKeyDown(e)} >
{this.renderChildren()}
{this.renderIndicator()} {this.renderArrow()}
); } } export default Carousel;