| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 | /* 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<CarouselProps, CarouselState> {    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<CarouselProps, CarouselState> {        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<CarouselState> {        const states: Partial<CarouselState> = {};        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.getIsControlledComponent()){            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.getIsControlledComponent()){            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 (                <div className={carouselIndicatorCls}>                    <CarouselIndicator                        type={indicatorType}                        total={children.length}                        activeIndex={activeIndex}                        position={indicatorPosition}                        trigger={trigger}                        size={indicatorSize}                        theme={theme}                        onIndicatorChange={this.onIndicatorChange}                    />                </div>            );        }        return null;    }    renderArrow = () => {        const { children } = this.state;        const { showArrow, arrowType, theme, arrowProps } = this.props;        if (showArrow && children.length > 1){            return (                <CarouselArrow                     type={arrowType}                     theme={theme}                     prev={this.prev}                     next={this.next}                    arrowProps={arrowProps}                />            );        }        return null;    };    render(): ReactNode {        const { animation, className, style, slideDirection } = this.props;        const { isReverse } = this.state;        const carouselWrapperCls = cls(className, {            [cssClasses.CAROUSEL]: true        });        return (            <div                 // role='listbox'                // tabIndex={0}                className={carouselWrapperCls}                 style={style}                 onMouseEnter={debounce(this.handleMouseEnter, 400)}                onMouseLeave={debounce(this.handleMouseLeave, 400)}                // onMouseEnter={this.handleMouseEnter}                // onMouseLeave={this.handleMouseLeave}                // onKeyDown={e => this.foundation.handleKeyDown(e)}            >                <div                     className={cls([`${cssClasses.CAROUSEL_CONTENT}-${animation}`], {                        [`${cssClasses.CAROUSEL_CONTENT}`]: true,                        [`${cssClasses.CAROUSEL_CONTENT}-reverse`]: slideDirection === 'left' ? isReverse : !isReverse,                    })}                    x-semi-prop="children"                >                    {this.renderChildren()}                </div>                {this.renderIndicator()}                {this.renderArrow()}            </div>        );    }}export default Carousel;
 |