Sfoglia il codice sorgente

feat: carousel in dev (#738)

YannLynn 3 anni fa
parent
commit
7d7057d88b

+ 1 - 0
content/order.js

@@ -45,6 +45,7 @@ const order = [
     'badge',
     'calendar',
     'card',
+    'carousel',
     'collapse',
     'collapsible',
     'descriptions',

+ 686 - 0
content/show/carousel/index-en-US.md

@@ -0,0 +1,686 @@
+---
+localeCode: en-US
+order: 46
+category: Show
+title: Carousel
+subTitle: Carousel
+icon: doc-carousel
+brief: Carousel is a media component that can display the effect of playing multiple pictures in turn in a visualization application.
+---
+
+## Demos
+
+### How to import
+```jsx import
+import { Carousel } from '@douyinfe/semi-ui';
+```
+
+### Basic Carousel
+
+Basic carousel
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px',
+        color: '#1C1F23'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }} />   
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+        ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+        ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+    ];
+
+    return (
+        <Carousel style={style} theme='dark'>
+            {
+                imgList.map((src, index) => {
+                    return (
+                        <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                            <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                {renderLogo()}
+                                <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                <Space vertical align='start'>
+                                    <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                    <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                </Space>
+                            </Space>
+                        </div>
+                    );
+                })
+            }
+        </Carousel>
+    );
+};
+```
+
+### Theme Switch
+
+Three themes are defined by default: `primary`、`light`、`dark`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, RadioGroup, Radio, Space, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+    const [theme, setTheme] = useState('primary');
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+        ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+        ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+    ];
+    
+    return (
+        <div>
+            <Carousel style={style} theme={theme} autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+            <br/>
+            <Space> 
+                <div>theme</div>
+                <RadioGroup onChange={e => setTheme(e.target.value)} value={theme}>
+                    <Radio value='primary'>primary</Radio>
+                    <Radio value='light'>light</Radio>
+                    <Radio value='dark'>dark</Radio>
+                </RadioGroup>
+            </Space>
+        </div>
+    );
+};
+```
+
+### Indicators
+
+Indicators can be adjusted for type, position, size   
+type:  `dot`、`line`、`columnar`   
+position:  `left`、`center`、`right`  
+size:  `small`、`medium`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, RadioGroup, Radio, Space, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+    const [size, setSize] = useState('small');
+    const [type, setType] = useState('dot');
+    const [position, setPosition] = useState('left');
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+        ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+        ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} indicatorType={type} indicatorPosition={position} indicatorSize={size} theme='dark' autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+            <br/>
+            <Space vertical align='start'>
+                <Space> 
+                    <div>type</div>
+                    <RadioGroup onChange={e => setType(e.target.value)} value={type}>
+                        <Radio value='dot'>dot</Radio>
+                        <Radio value='line'>line</Radio>
+                        <Radio value='columnar'>columnar</Radio>
+                    </RadioGroup>
+                </Space>
+                <Space> 
+                    <div>position</div>
+                    <RadioGroup onChange={e => setPosition(e.target.value)} value={position}>
+                        <Radio value='left'>left</Radio>
+                        <Radio value='center'>center</Radio>
+                        <Radio value='right'>right</Radio>
+                    </RadioGroup>
+                </Space>
+                <Space> 
+                    <div>size</div>
+                    <RadioGroup onChange={e => setSize(e.target.value)} value={size}>
+                        <Radio value='small'>small</Radio>
+                        <Radio value='medium'>medium</Radio>
+                    </RadioGroup>
+                </Space>
+            </Space>
+        </div>
+    );
+};
+```
+
+### Arrows
+
+Control whether the arrow is visible through the showArrow property  
+If the arrow is visible, use the arrowType property to control the timing of the arrow display
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, RadioGroup, Radio, Space, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+    const [arrowType, setArrowTypew] = useState('always');
+    const [show, setShow] = useState(true);
+  
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+        ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+        ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} showArrow={show} arrowType={arrowType} theme='dark' autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+            <br/>
+            <Space vertical align='start'>
+                <Space> 
+                    <div>arrow</div>
+                    <RadioGroup onChange={e => setShow(e.target.value)} value={show}>
+                        <Radio value={true}>show</Radio>
+                        <Radio value={false}>hide</Radio>
+                    </RadioGroup>
+                </Space>
+                <Space> 
+                    <div>show time</div>
+                    <RadioGroup onChange={e => setArrowTypew(e.target.value)} value={arrowType}>
+                        <Radio value='always'>always</Radio>
+                        <Radio value='hover'>hover</Radio>
+                    </RadioGroup>
+                </Space>
+            </Space>
+        </div>
+    );
+};
+```
+
+### Custom Arrow
+Customize arrow styles and click events through the arrowProps property
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+import { IconArrowLeft, IconArrowRight } from "@douyinfe/semi-icons";
+
+class CarouselDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.imgList = [
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+        ];
+        this.textList = [
+            ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+            ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+            ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+        ];
+        this.arrowProps = {
+            leftArrow: { children: <IconArrowLeft size='large'/> },
+            rightArrow: { children: <IconArrowRight size='large'/> },
+        };
+    };
+    
+    renderLogo() {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }} />
+        );
+    };
+
+    render() {
+        const style = {
+            width: '100%',
+            height: '400px',
+        };
+
+        const titleStyle = { 
+            position: 'absolute', 
+            top: '100px', 
+            left: '100px'
+        };
+
+        const colorStyle = {
+            color: '#1C1F23'
+        };
+
+        return (
+            <div>
+                <Carousel 
+                    theme='dark'
+                    style={style} 
+                    autoPlay={false} 
+                    arrowProps={this.arrowProps}
+                >
+                    {
+                        this.imgList.map((src, index) => {
+                            return (
+                                <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                    <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                        {this.renderLogo()}
+                                        <Typography.Title heading={2} style={colorStyle}>{this.textList[index][0]}</Typography.Title>
+                                        <Space vertical align='start'>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][1]}</Typography.Paragraph>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][2]}</Typography.Paragraph>
+                                        </Space>
+                                    </Space>
+                                </div>
+                            );
+                        })
+                    }
+                </Carousel>
+            </div>
+        );
+    }
+}
+```
+
+### Play Parameters
+Pass the parameter interval to autoPlay to control the time interval between two pictures, and pass hoverToPause to control whether to stop playing when the mouse is placed on the picture
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+        ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+        ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} autoPlay={{ interval: 1500, hoverToPause: true }} theme='dark'>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+        </div>
+    );
+};
+```
+
+### Animation and Speed
+Control the animation by giving the animation property, optional values are `fade`, `slide`  
+Control the switching time between two pictures by giving the speed attribute, the unit is ms
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+        ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+        ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} speed={1000} animation='fade' theme='dark' autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+        </div>
+    );
+};
+```
+
+### Controlled Carousel
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Space, Typography } from '@douyinfe/semi-ui';
+
+class CarouselDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.imgList = [
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+        ];
+        this.textList = [
+            ['Semi Design Management System', 'from Semi Design, to Any Design', 'quickly customize your design system and apply it in design drafts and code'],
+            ['Semi Material', 'customized components for business scenarios, support online preview and debugging', 'content co-authored by Semi Design users'],
+            ['Semi Pro (In development)', 'based on 40+ real component code design', 'one-click conversion of massive page template front-end code'],
+        ];
+        this.state = {
+            activeIndex: 0,
+        };
+    }
+
+    renderLogo() {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }} />
+        );
+    };
+
+    onChange(activeIndex){
+        this.setState({ activeIndex });
+    }
+
+    render() {
+        const style = {
+            width: '100%',
+            height: '400px',
+        };
+
+        const titleStyle = { 
+            position: 'absolute', 
+            top: '100px', 
+            left: '100px'
+        };
+
+        const colorStyle = {
+            color: '#1C1F23'
+        };
+
+        const { activeIndex } = this.state;
+        
+        return (
+            <div>
+                <Carousel style={style} activeIndex={activeIndex} autoPlay={false} theme='dark' onChange={this.onChange.bind(this)}>
+                    {
+                        this.imgList.map((src, index) => {
+                            return (
+                                <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                    <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                        {this.renderLogo()}
+                                        <Typography.Title heading={2} style={colorStyle}>{this.textList[index][0]}</Typography.Title>
+                                        <Space vertical align='start'>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][1]}</Typography.Paragraph>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][2]}</Typography.Paragraph>
+                                        </Space>
+                                    </Space>
+                                </div>
+                            );
+                        })
+                    }
+                </Carousel>
+            </div>
+        );
+    }
+}
+```
+
+
+### API reference
+
+**Carousel**
+
+|PROPERTIES        |INSTRUCTIONS                                                                         |TYPE              |DEFAULT |VERSION|
+|------------------|-------------------------------------------------------------------------------------|------------------|--------|-------|
+|activeIndex       |Controlled property                                                                  |number            |-     |2.10.0|
+|animation        |Animation, optional:`fade`, `slide`                                                   |       "fade" \|"slide"  |"slide"|2.10.0|
+|arrowProps        |Arrow parameters for custom arrow styles and click events                            |            () => {leftArrow: ArrowButton, rightArrow:ArrowButton}|-     |2.10.0|
+|autoPlay          |Whether to automatically display in a loop, or pass in { interval: Auto switch time interval(default: 2000), hoverToPause: Whether to pause automatic switching when the mouse is hovering(default: true) }|boolean |function(interval: number, hoverToPause:boolean) |true  |2.10.0|
+|className         |The className of Carousel container                                                   |string            |-      |2.10.0|
+|defaultActiveIndex|The index displayed by default when initializing                                      |number            |0     |2.10.0|
+|indicatorPosition |Indicator position, optional values are: `left`、`center`、`right`                    |       "left" \| "center" \| "right"|"center"|2.10.0|
+|indicatorSize     |Indicator size, optional values are: `small`、`medium`                                |      "small" \| "medium"|"small"|2.10.0|
+|indicatorType     |Indicator type, optional values are: `dot`、`line`、`columnar`                        |        "dot" \| "line" \| "columnar"|"dot"|2.10.0|
+|theme             |Indicator and arrow theme, optional values are:  `primary`、`light`、`dark`           |    "primary" \| "light" \| "dark" |"light"|2.10.0|
+|onChange          |Callback when image is switched                                                     |        (index: number, preIndex:number) => void |-      |2.10.0|
+|arrowType         |Arrow display timing, optional values are:  `hover`、`always`                        |       "hover" \| "always"|always |2.10.0|
+|showArrow         |Whether to show arrows                                                             |boolean          |true   |2.10.0|
+|showIndicator     |Whether to show the indicator                                                        |boolean          |true   |2.10.0|
+|slideDirection    |The direction of the slide when the animation effect is `slide`, optional: `left`、 `right` |"left" \| "right" |"left" |2.10.0|
+|speed             |Switching speed                                                                      |number            |300    |2.10.0|
+|style             |Carousel style                                                                        |CSSProperties     |-       |2.10.0|
+|trigger           |When the indicator is triggered, the optional values are: `hover`、`click`            |      "hover" \|"click" |-     |2.10.0|
+
+**ArrowButton**
+
+|PROPERTIES        |INSTRUCTIONS                                                   |TYPE             |DEFAULT|VERSION|
+|------------------|---------------------------------------------------------------|------------------|------|------|
+|props           |Parameters on the arrow div, including style, onClick events, etc                                      |DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement\>, HTMLDivElement\>         |-     |2.10.0|
+|children         |Arrow custom icon                                              |React.ReactNode      |-     |2.10.0|
+
+
+## Design Tokens
+<DesignToken/>

+ 685 - 0
content/show/carousel/index.md

@@ -0,0 +1,685 @@
+---
+localeCode: zh-CN
+order: 46
+category: 展示类
+title: Carousel 轮播图
+icon: doc-carousel
+brief: 轮播图是一种媒体组件,可以在可视化应用中展示多张图片轮流播放的效果。
+---
+
+## 代码演示
+
+### 如何引入
+```jsx import
+import { Carousel } from '@douyinfe/semi-ui';
+```
+
+### 基本用法
+
+基本用法
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px',
+        color: '#1C1F23'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }} />   
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+        ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+        ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+    ];
+
+    return (
+        <Carousel style={style} theme='dark'>
+            {
+                imgList.map((src, index) => {
+                    return (
+                        <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                            <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                {renderLogo()}
+                                <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                <Space vertical align='start'>
+                                    <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                    <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                </Space>
+                            </Space>
+                        </div>
+                    );
+                })
+            }
+        </Carousel>
+    );
+};
+```
+
+### 主题切换
+
+默认定义了三种主题:  `primary`、`light`、`dark`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, RadioGroup, Radio, Space, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+    const [theme, setTheme] = useState('primary');
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+        ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+        ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+    ];
+    
+    return (
+        <div>
+            <Carousel style={style} theme={theme} autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+            <br/>
+            <Space> 
+                <div>主题</div>
+                <RadioGroup onChange={e => setTheme(e.target.value)} value={theme}>
+                    <Radio value='primary'>primary</Radio>
+                    <Radio value='light'>light</Radio>
+                    <Radio value='dark'>dark</Radio>
+                </RadioGroup>
+            </Space>
+        </div>
+    );
+};
+```
+
+### 指示器
+
+指示器可以调节类型、位置、尺寸  
+类型:  `dot`、`line`、`columnar`  
+位置:  `left`、`center`、`right`  
+尺寸:  `small`、`medium`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, RadioGroup, Radio, Space, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+    const [size, setSize] = useState('small');
+    const [type, setType] = useState('dot');
+    const [position, setPosition] = useState('left');
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+        ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+        ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} indicatorType={type} indicatorPosition={position} indicatorSize={size} theme='dark' autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+            <br/>
+            <Space vertical align='start'>
+                <Space> 
+                    <div>类型</div>
+                    <RadioGroup onChange={e => setType(e.target.value)} value={type}>
+                        <Radio value='dot'>dot</Radio>
+                        <Radio value='line'>line</Radio>
+                        <Radio value='columnar'>columnar</Radio>
+                    </RadioGroup>
+                </Space>
+                <Space> 
+                    <div>位置</div>
+                    <RadioGroup onChange={e => setPosition(e.target.value)} value={position}>
+                        <Radio value='left'>left</Radio>
+                        <Radio value='center'>center</Radio>
+                        <Radio value='right'>right</Radio>
+                    </RadioGroup>
+                </Space>
+                <Space> 
+                    <div>尺寸</div>
+                    <RadioGroup onChange={e => setSize(e.target.value)} value={size}>
+                        <Radio value='small'>small</Radio>
+                        <Radio value='medium'>medium</Radio>
+                    </RadioGroup>
+                </Space>
+            </Space>
+        </div>
+    );
+};
+```
+
+### 箭头
+
+通过 showArrow 属性控制箭头是否可见  
+如果箭头可见,通过 arrowType 属性控制箭头展示的时机
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, RadioGroup, Radio, Space, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+    const [arrowType, setArrowTypew] = useState('always');
+    const [show, setShow] = useState(true);
+  
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+        ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+        ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} showArrow={show} arrowType={arrowType} theme='dark' autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+            <br/>
+            <Space vertical align='start'>
+                <Space> 
+                    <div>展示箭头</div>
+                    <RadioGroup onChange={e => setShow(e.target.value)} value={show}>
+                        <Radio value={true}>show</Radio>
+                        <Radio value={false}>hide</Radio>
+                    </RadioGroup>
+                </Space>
+                <Space> 
+                    <div>展示时机</div>
+                    <RadioGroup onChange={e => setArrowTypew(e.target.value)} value={arrowType}>
+                        <Radio value='always'>always</Radio>
+                        <Radio value='hover'>hover</Radio>
+                    </RadioGroup>
+                </Space>
+            </Space>
+        </div>
+    );
+};
+```
+
+### 定制箭头
+通过 arrowProps 属性定制箭头样式和点击事件
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+import { IconArrowLeft, IconArrowRight } from "@douyinfe/semi-icons";
+
+class CarouselDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.imgList = [
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+        ];
+        this.textList = [
+            ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+            ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+            ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+        ];
+        this.arrowProps = {
+            leftArrow: { children: <IconArrowLeft size='large'/> },
+            rightArrow: { children: <IconArrowRight size='large'/> },
+        };
+    };
+
+    renderLogo() {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }} />
+        );
+    };
+
+    render() {
+        const style = {
+            width: '100%',
+            height: '400px',
+        };
+
+        const titleStyle = { 
+            position: 'absolute', 
+            top: '100px', 
+            left: '100px'
+        };
+
+        const colorStyle = {
+            color: '#1C1F23'
+        };
+
+        return (
+            <div>
+                <Carousel 
+                    theme='dark'
+                    style={style} 
+                    autoPlay={false} 
+                    arrowProps={this.arrowProps}
+                >
+                    {
+                        this.imgList.map((src, index) => {
+                            return (
+                                <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                    <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                        {this.renderLogo()}
+                                        <Typography.Title heading={2} style={colorStyle}>{this.textList[index][0]}</Typography.Title>
+                                        <Space vertical align='start'>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][1]}</Typography.Paragraph>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][2]}</Typography.Paragraph>
+                                        </Space>
+                                    </Space>
+                                </div>
+                            );
+                        })
+                    }
+                </Carousel>
+            </div>
+        );
+    }
+}
+```
+
+### 播放参数
+通过给 autoPlay 传入参数 interval 控制两张图片之间的时间间隔,传入 hoverToPause 控制鼠标放置在图片上时是否停止播放
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+        ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+        ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} autoPlay={{ interval: 1500, hoverToPause: false }} theme='dark'>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+        </div>
+    );
+};
+```
+
+### 动画效果与切换速度
+通过给 animation 属性控制动画,可选值有 `fade`,`slide`  
+通过给 speed 属性控制两张图片之间的切换时间,单位为ms
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Typography, Space } from '@douyinfe/semi-ui';
+
+() => {
+    const { Title, Paragraph } = Typography;
+
+    const style = {
+        width: '100%',
+        height: '400px',
+    };
+
+    const titleStyle = { 
+        position: 'absolute', 
+        top: '100px', 
+        left: '100px'
+    };
+
+    const colorStyle = {
+        color: '#1C1F23'
+    };
+
+    const renderLogo = () => {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }}/>
+        );
+    };
+
+
+    const imgList = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+    ];
+
+    const textList = [
+        ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+        ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+        ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+    ];
+
+    return (
+        <div>
+            <Carousel style={style} speed={1000} animation='fade' theme='dark' autoPlay={false}>
+                {
+                    imgList.map((src, index) => {
+                        return (
+                            <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                    {renderLogo()}
+                                    <Title heading={2} style={colorStyle}>{textList[index][0]}</Title>
+                                    <Space vertical align='start'>
+                                        <Paragraph style={colorStyle}>{textList[index][1]}</Paragraph>
+                                        <Paragraph style={colorStyle}>{textList[index][2]}</Paragraph>
+                                    </Space>
+                                </Space>
+                            </div>
+                        );
+                    })
+                }
+            </Carousel>
+        </div>
+    );
+};
+```
+
+### 受控的轮播图
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Carousel, Space, Typography } from '@douyinfe/semi-ui';
+
+class CarouselDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.imgList = [
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-1.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-2.png',
+            'https://lf3-static.bytednsdoc.com/obj/eden-cn/hjeh7pldnulm/SemiDocs/bg-3.png',
+        ];
+        this.textList = [
+            ['Semi 设计管理系统', '从 Semi Design,到 Any Design', '快速定制你的设计系统,并应用在设计稿和代码中'],
+            ['Semi 物料市场', '面向业务场景的定制化组件,支持线上预览和调试', '内容由 Semi Design 用户共建'],
+            ['Semi Pro (开发中)', '基于 40+ 真实组件代码设计', '海量页面模板前端代码一键转'],
+        ];
+        this.state = {
+            activeIndex: 0,
+        };
+    }
+
+    renderLogo() {
+        return (
+            <img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/semi_logo.svg' alt='semi_logo' style={{ width:87, height:31 }} />
+        );
+    };
+
+    onChange(activeIndex){
+        this.setState({ activeIndex });
+    }
+
+    render() {
+        const style = {
+            width: '100%',
+            height: '400px',
+        };
+
+        const titleStyle = { 
+            position: 'absolute', 
+            top: '100px', 
+            left: '100px'
+        };
+
+        const colorStyle = {
+            color: '#1C1F23'
+        };
+
+        const { activeIndex } = this.state;
+        
+        return (
+            <div>
+                <Carousel style={style} activeIndex={activeIndex} autoPlay={false} theme='dark' onChange={this.onChange.bind(this)}>
+                    {
+                        this.imgList.map((src, index) => {
+                            return (
+                                <div key={index} style={{ backgroundSize: 'cover', backgroundImage: `url(${src})` }}>
+                                    <Space vertical align='start' spacing='medium' style={titleStyle}>
+                                        {this.renderLogo()}
+                                        <Typography.Title heading={2} style={colorStyle}>{this.textList[index][0]}</Typography.Title>
+                                        <Space vertical align='start'>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][1]}</Typography.Paragraph>
+                                            <Typography.Paragraph style={colorStyle}>{this.textList[index][2]}</Typography.Paragraph>
+                                        </Space>
+                                    </Space>
+                                </div>
+                            );
+                        })
+                    }
+                </Carousel>
+            </div>
+        );
+    }
+}
+```
+
+
+### API 参考
+
+**Carousel**
+
+|属性               |说明                                                           |类型               |默认值 |版本   |
+|------------------|---------------------------------------------------------------|------------------|------|------|
+|activeIndex       |受控属性                                                         |number            |-     |2.10.0|
+|animation         |切换动画,可选值:`fade`,`slide`                                  |"fade" \|"slide"  |"slide"|2.10.0|
+|arrowProps        |箭头参数,用于自定义箭头样式和点击事件                                |() => {leftArrow: ArrowButton, rightArrow: ArrowButton;}                                                                              |-     |2.10.0|
+|autoPlay          |是否自动循环展示,或者传入 { interval: 自动切换时间间隔(默认: 2000), hoverToPause: 鼠标悬浮时是否暂停自动切换(默认: true) }                                                                      |boolean |function(interval: number, hoverToPause: boolean)                                                                         |true  |2.10.0|
+|className         |样式类名                                                         |string            |-      |2.10.0|
+|defaultActiveIndex|初始化时默认展示的索引                                             |number            |0     |2.10.0|
+|indicatorPosition |指示器位置,可选值有: `left`、`center`、`right`                    |"left" \| "center" \| "right"                                                                                                |"center"|2.10.0|
+|indicatorSize     |指示器尺寸,可选值有: `small`、`medium`                            |"small" \| "medium"|"small"|2.10.0|
+|indicatorType     |指示器类型,可选值有: `dot`、`line`、`columnar`                    |"dot" \| "line" \| "columnar"|"dot"|2.10.0|
+|theme             |指示器和箭头主题,可选值有:  `primary`、`light`、`dark`              |"primary" \| "light" \| "dark" |"light"|2.10.0|
+|onChange          |图片切换时的回调                                                   |(index: number, preIndex:number) => void |-      |2.10.0|
+|arrowType         |箭头展示时机,可选值有:  `hover`、`always`                          |"hover" \| "always"|always |2.10.0|
+|showArrow         |是否展示箭头                                                      |boolean          |true   |2.10.0|
+|showIndicator     |是否展示指示器                                                    |boolean          |true   |2.10.0|
+|slideDirection    |动画效果为`slide`时的滑动的方向,可选值有: `left`、`right`           |"left" \| "right" |"left" |2.10.0|
+|speed             |切换速度,单位ms                                                   |number           |300    |2.10.0|
+|style             |内联样式                                                          |CSSProperties    |-       |2.10.0|
+|trigger           |指示器触发的时机,可选值有: `hover`、`click`                         |"hover" \|"click"|-     |2.10.0|
+
+**ArrowButton**
+
+|属性               |说明                                                           |类型               |默认值 |版本   |
+|------------------|---------------------------------------------------------------|------------------|------|------|
+|props             |箭头div上的可传参数,包括style, onClick事件等                                                  | React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement\>, HTMLDivElement\>       |-     |2.10.0|
+|children          |箭头自定义Icon                                                   |React.ReactNode      |-     |2.10.0|
+
+## 设计变量
+
+<DesignToken/>

+ 1 - 0
content/start/overview/index-en-US.md

@@ -60,6 +60,7 @@ Avatar,
 Badge,
 Calendar,
 Card,
+Carousel,
 Collapse,
 Collapsible,
 Descriptions,

+ 1 - 0
content/start/overview/index.md

@@ -61,6 +61,7 @@ Avatar 头像,
 Badge 徽章,
 Calendar 日历,
 Card 卡片,
+Carousel 轮播图,
 Collapse 折叠面板,
 Collapsible 折叠,
 Descriptions 描述列表,

+ 103 - 0
cypress/integration/carousel.spec.js

@@ -0,0 +1,103 @@
+describe('carousel',  () => {
+    it('ref method with control', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=carousel--controlled-usage&args=&viewMode=story');
+        cy.get('.semi-carousel-content-item-active h3').contains('1');
+        cy.get('div').contains('prev').click();
+        cy.get('.semi-carousel-content-item-active h3').contains('5');
+        cy.get('div').contains('next').click();
+        cy.get('.semi-carousel-content-item-active h3').contains('1');
+        
+        cy.clock();
+        cy.get('div').contains('play').click();
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('2');
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+        cy.get('div').contains('stop').click();
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+        cy.get('div').contains('play').click();
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('4');
+
+        cy.get('div').contains('goTo3').click();
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+    });
+
+    it('ref method without control', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=carousel--ref-usage&args=&viewMode=story');
+        cy.get('.semi-carousel-content-item-active h3').contains('1');
+
+        cy.clock();
+        cy.get('div').contains('play').click();
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('2');
+
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+        cy.get('div').contains('stop').click();
+        cy.tick(4300);
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+        cy.get('div').contains('prev').click();
+        cy.tick(300);
+        cy.get('.semi-carousel-content-item-active h3').contains('2');
+
+        cy.get('div').contains('goTo3').click();
+        cy.tick(300);
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+        cy.get('div').contains('next').click();
+        cy.tick(300);
+        cy.get('.semi-carousel-content-item-active h3').contains('4');
+
+    });
+
+    it('mouseover and mouseleave', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=carousel--ref-usage&args=&viewMode=story');
+        cy.get('.semi-carousel-content-item-active h3').contains('1');
+
+        cy.clock(new Date().getTime());
+        cy.get('div').contains('play').click();
+        cy.tick(2300);
+        cy.get('.semi-carousel-content-item-active h3').contains('2');
+
+        cy.get('.semi-carousel').trigger('mouseover');
+        cy.tick(400);
+        cy.get('.semi-carousel-content-item-active h3').contains('2');
+
+        cy.clock().invoke('restore');
+
+        // todo: mouseleave test
+        // cy.clock();
+        // cy.get('#root').trigger('mousemove', 'right');
+        // cy.wait(3000);
+        // cy.get('.semi-carousel-content-item-active h3', { timeout: 0 }).contains('3');
+
+    });
+
+    it('auto play interval', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=carousel--auto-play-example&args=&viewMode=story');
+
+        cy.get('.semi-carousel-content-item-active h3').contains('1');
+        cy.wait(1300);
+        cy.get('.semi-carousel-content-item-active h3').contains('2');
+        cy.wait(1300);
+        cy.get('.semi-carousel-content-item-active h3').contains('3');
+
+    });
+
+    it('slide direction', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=carousel--slide-direction&args=&viewMode=story');
+
+        cy.get('.semi-carousel-arrow-next').click();
+        cy.get('.semi-carousel-content-item-active h3').contains('index1');
+        cy.get('.semi-carousel-content-item-slide-out').contains('index0');
+        cy.get('.semi-carousel-content-item-slide-in').contains('index1');
+    });
+
+});

+ 425 - 0
packages/semi-foundation/carousel/carousel.scss

@@ -0,0 +1,425 @@
+@import "./variables.scss";
+
+$module: #{$prefix}-carousel;
+
+.#{$module} {
+    position: relative;
+    overflow: hidden;
+
+    &-content {
+        width: 100%;
+        height: 100%;
+        overflow: hidden;
+        position: relative;
+
+        &-item {
+            position: absolute;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+
+            &-current {
+                z-index: 1;
+            }
+
+        }
+
+        &-fade {
+            
+            > * {
+                opacity: 0;
+            }
+    
+            .#{$module}-content-item-current {
+                opacity: 1;
+            }
+        }
+
+        &-slide {
+            &>*:not(.#{$module}-content-item-current) {
+                visibility: hidden;
+            }
+
+            .#{$module}-content-item-slide-out {
+                display: block;
+                animation: #{$module}-content-item-keyframe-slide-out;
+            }
+        
+            .#{$module}-content-item-slide-in {
+                display: block;
+                animation: #{$module}-content-item-keyframe-slide-in;
+            }
+          
+        }
+
+        &-reverse {
+            .#{$module}-content-item-slide-out {
+                animation: #{$module}-content-item-keyframe-slide-out-reverse ;
+            }
+    
+            .#{$module}-content-item-slide-in {
+                animation: #{$module}-content-item-keyframe-slide-in-reverse ;
+            }
+        }
+    }
+
+    &-indicator {
+        display: flex;
+        align-items: flex-end;
+        z-index: 2;
+
+        &-left {
+            position: absolute;
+            left: $spacing-carousel_indicator-padding;
+            bottom: $spacing-carousel_indicator-padding;
+        }
+
+        &-center {
+            position: absolute;
+            left: 50%;
+            bottom: $spacing-carousel_indicator-padding;
+            transform: translate(-50%);
+        }
+
+        &-right {
+            position: absolute;
+            right: $spacing-carousel_indicator-padding;
+            bottom: $spacing-carousel_indicator-padding;
+        }
+
+    
+        &-dot {
+            .#{$module}-indicator-item {
+                border-radius: $radius-carousel_indicator_dot;
+                cursor: pointer;
+
+                &:not(:last-child) {
+                    margin-right: $spacing-carousel_indicator_dot-marginX;
+                }
+
+                &-small {
+                    width: $width-carousel_indicator_dot_small;
+                    height: $width-carousel_indicator_dot_small;
+                   
+                }
+
+                &-medium {
+                    width: $width-carousel_indicator_dot_medium;
+                    height: $width-carousel_indicator_dot_medium;
+                }
+
+                &-primary {
+                    background-color: $color-carousel_indicator_theme_primary-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_primary-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_primary-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_primary-bg-active;
+                    }
+                }
+
+                &-light {
+
+                    background-color: $color-carousel_indicator_theme_light-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_light-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_light-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_light-bg-active;
+                    }
+                }
+
+                &-dark {
+                    background-color: $color-carousel_indicator_theme_dark-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background-color: $color-carousel_indicator_theme_dark-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_dark-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_dark-bg-active;
+                    }
+                }
+            }
+
+        }
+
+        &-line {
+            width: $width-carousel_indicator_line;
+
+            .#{$module}-indicator-item {
+                flex: 1; 
+                cursor: pointer;
+
+                &:not(:last-child) {
+                    margin-right: $spacing-carousel_indicator_line-marginX;
+                }
+
+                &-small {
+                    height: $height-carousel_indicator_line_small;
+                   
+                }
+
+                &-medium {
+                    height: $height-carousel_indicator_line_medium;
+                }
+
+                &-primary {
+                    background-color: $color-carousel_indicator_theme_primary-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_primary-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_primary-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_primary-bg-active;
+                    }
+                }
+
+                &-light {
+
+                    background-color: $color-carousel_indicator_theme_light-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_light-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_light-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_light-bg-active;
+                    }
+                }
+
+                &-dark {
+
+                    background-color: $color-carousel_indicator_theme_dark-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_dark-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_dark-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_dark-bg-active;
+                    }
+                }
+        
+            }
+
+        }
+
+        &-columnar {
+            .#{$module}-indicator-item {
+                cursor: pointer;
+                
+                &:not(:last-child) {
+                    margin-right: $spacing-carousel_indicator_columnar-marginX;
+                }
+
+                &-small {
+                    width: $width-carousel_indicator_columnar_small;
+                    height: $height-carousel_indicator_columnar_small_default;
+
+                    &.#{$module}-indicator-item-active {
+                        height: $height-carousel_indicator_columnar_small_active;
+                    }
+                }
+
+                &-medium {
+                    width: $width-carousel_indicator_columnar_medium;
+                    height: $height-carousel_indicator_columnar_medium_default;
+
+                    &.#{$module}-indicator-item-active {
+                        height: $height-carousel_indicator_columnar_medium_active;
+                    }
+                }
+
+
+                &-primary {
+                    background-color: $color-carousel_indicator_theme_primary-bg-default;
+
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_primary-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_primary-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_primary-bg-active;
+                    }
+                }
+
+                &-light {
+                    background-color: $color-carousel_indicator_theme_light-bg-default;
+
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_light-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_light-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_light-bg-active;
+                    }
+                }
+
+                &-dark {
+                    background-color: $color-carousel_indicator_theme_dark-bg-default;
+                    
+                    &.#{$module}-indicator-item-active {
+                        background: $color-carousel_indicator_theme_dark-bg-active;
+                    }
+
+                    &:hover {
+                        background-color: $color-carousel_indicator_theme_dark-bg-hover;
+                    }
+
+                    &:active {
+                        background: $color-carousel_indicator_theme_dark-bg-active;
+                    }
+                }
+        
+            }
+
+        }
+    }
+
+    &-arrow {
+        display: flex;
+        font-size: $width-carousel_arrow;
+        cursor: pointer;
+
+        &-prev {
+            position: absolute;
+            top: 50%;
+            left: $spacing-carousel_arrow-left;
+            transform: translateY(-50%);
+            z-index: 2;
+        }
+
+        &-next {
+            position: absolute;
+            top: 50%;
+            right: $spacing-carousel_arrow-right;
+            transform: translateY(-50%);
+            z-index: 2;
+        }
+
+        &-light {
+            color: $color-carousel_arrow_theme_light-bg-default;
+
+            &:hover {
+                color: $color-carousel_arrow_theme_light-bg-hover;
+            }
+        }
+
+
+        &-primary {
+            color: $color-carousel_arrow_theme_primary-bg-default;
+
+            &:hover {
+                color: $color-carousel_arrow_theme_primary-bg-hover;
+            }
+        }
+
+        &-dark {
+            color: $color-carousel_arrow_theme_dark-bg-default;
+
+            &:hover {
+                color: $color-carousel_arrow_theme_dark-bg-hover;
+            }
+        }
+
+    }
+
+    &-arrow-hover div {
+        z-index: 2;
+        opacity: 0;
+        transition: all .3s;
+    }
+    
+    &:hover {
+        .#{$module}-arrow-hover div {
+            opacity: 1;
+        }
+    }
+}
+
+@keyframes #{$module}-content-item-keyframe-slide-in {
+
+    from {
+        transform: translateX(-100%);
+    }
+    
+    to {
+        transform: translateX(0);
+    }
+}
+
+@keyframes #{$module}-content-item-keyframe-slide-out {
+
+    from {
+        transform: translateX(0);
+    }
+    
+    to {
+        transform: translateX(100%);
+    }
+}
+
+@keyframes #{$module}-content-item-keyframe-slide-in-reverse {
+
+    from {
+        transform: translateX(100%);
+    }
+  
+    to {
+        transform: translateX(0);
+    }
+}
+  
+@keyframes #{$module}-content-item-keyframe-slide-out-reverse {
+
+    from {
+        transform: translateX(0);
+    }
+  
+    to {
+        transform: translateX(-100%);
+    }
+}
+
+@import "./rtl.scss";

+ 32 - 0
packages/semi-foundation/carousel/constants.ts

@@ -0,0 +1,32 @@
+import { BASE_CLASS_PREFIX } from '../base/constants';
+
+const cssClasses = {
+    CAROUSEL: `${BASE_CLASS_PREFIX}-carousel`,
+    CAROUSEL_INDICATOR: `${BASE_CLASS_PREFIX}-carousel-indicator`,
+    CAROUSEL_INDICATOR_LINE: `${BASE_CLASS_PREFIX}-carousel-indicator-line`,
+    CAROUSEL_INDICATOR_DOT: `${BASE_CLASS_PREFIX}-carousel-indicator-dot`,
+    CAROUSEL_INDICATOR_COLUMNAR: `${BASE_CLASS_PREFIX}-carousel-indicator-columnar`,
+    CAROUSEL_INDICATOR_INACTIVE: `${BASE_CLASS_PREFIX}-carousel-indicator-inactive`,
+    CAROUSEL_INDICATOR_ACTIVE: `${BASE_CLASS_PREFIX}-carousel-indicator-active`,
+    CAROUSEL_CONTENT: `${BASE_CLASS_PREFIX}-carousel-content`,
+    CAROUSEL_ARROW: `${BASE_CLASS_PREFIX}-carousel-arrow`,
+};
+
+const numbers = {
+    DEFAULT_ACTIVE_INDEX: 0,
+    DEFAULT_INTERVAL: 2000,
+    DEFAULT_SPEED: 300,
+};
+
+const strings = {
+    ANIMATION_MAP: ['slide', 'fade'],
+    DIRECTION: ['left', 'right'],
+    TYPE_MAP: ['columnar', 'line', 'dot'],
+    THEME_MAP: ['dark', 'primary', 'light'],
+    POSITION_MAP: ['left', 'center', 'right'],
+    ARROW_MAP: ['always', 'hover'],
+    SIZE: ['small', 'medium'],
+    TRIGGER: ['click', 'hover'],
+} as const;
+
+export { cssClasses, numbers, strings };

+ 166 - 0
packages/semi-foundation/carousel/foundation.ts

@@ -0,0 +1,166 @@
+import { isObject, get } from 'lodash';
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import { numbers } from './constants';
+import { throttle } from 'lodash';
+
+export interface CarouselAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    notifyChange: (activeIndex: number, preIndex: number) => void;
+    setNewActiveIndex: (activeIndex: number) => void;
+    setPreActiveIndex: (activeIndex: number) => void;
+    setIsReverse: (isReverse: boolean) => void;   
+    setIsInit: (isInit: boolean) => void;   
+}
+
+class CarouselFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CarouselAdapter<P, S>, P, S> {
+    throttleChange: any;
+
+    constructor(adapter: CarouselAdapter<P, S>) {
+        super({ ...adapter });
+        this.throttleChange = throttle(this.onIndicatorChange, this.getSwitchingTime()); 
+    }
+
+    _interval = null;
+
+    play(interval: number): void {
+        if (this._interval) {
+            clearInterval(this._interval);
+        }
+        this._interval = setInterval(() => {
+            this.next();
+        }, interval);
+    }
+
+    stop(): void {
+        if (this._interval){
+            clearInterval(this._interval);
+        }
+    }
+
+    goTo(activeIndex: number): void {
+        const { activeIndex: stateActiveIndex } = this.getStates();
+        const targetIndex = this.getValidIndex(activeIndex);
+        this._adapter.setIsReverse(stateActiveIndex > targetIndex);
+        if (this.getIsControledComponent()) {
+            this._notifyChange(targetIndex);
+        } else {
+            this._notifyChange(targetIndex);
+            this.handleNewActiveIndex(targetIndex);
+        }
+    }
+
+    next(): void {
+        const { activeIndex: stateActiveIndex } = this.getStates();
+        const targetIndex = this.getValidIndex(stateActiveIndex + 1);
+        this._adapter.setIsReverse(false);
+        if (this.getIsControledComponent()) {
+            this._notifyChange(targetIndex);
+        } else {
+            this._notifyChange(targetIndex);
+            this.handleNewActiveIndex(targetIndex);
+        }
+    }
+
+    prev(): void {
+        const { activeIndex: stateActiveIndex } = this.getStates();
+        const targetIndex = this.getValidIndex(stateActiveIndex - 1);
+        this._adapter.setIsReverse(true);
+        if (this.getIsControledComponent()) {
+            this._notifyChange(targetIndex);
+        } else {
+            this._notifyChange(targetIndex);
+            this.handleNewActiveIndex(targetIndex);
+        }
+    }
+
+    destroy(): void {
+        this._unregisterInterval();
+    }
+
+    _unregisterInterval() {
+        if (this._interval) {
+            clearInterval(this._interval);
+            this._interval = null;
+        }
+    }
+
+
+    _notifyChange(activeIndex: number): void {
+        const { activeIndex: stateActiveIndex, isInit } = this.getStates();
+        if (isInit){
+            this._adapter.setIsInit(false);
+        }
+        if (stateActiveIndex !== activeIndex) {
+            this._adapter.setPreActiveIndex(stateActiveIndex);
+            this._adapter.notifyChange(activeIndex, stateActiveIndex);
+        }
+    }
+
+    getValidIndex(index: number): number {
+        const { children } = this.getStates();
+        return (index + children.length) % children.length;
+    }
+
+    getSwitchingTime(): number {
+        const { autoPlay, speed } = this.getProps(); 
+        const autoPlayType = typeof autoPlay;
+        if (autoPlayType === 'boolean' && autoPlay){
+            return numbers.DEFAULT_INTERVAL + speed;
+        }
+        if (isObject(autoPlay)){
+            return get(autoPlay, 'interval', numbers.DEFAULT_INTERVAL) + speed;
+        }
+        return speed;
+    }
+
+    getIsControledComponent(): boolean {
+        return this._isInProps('activeIndex');
+    }
+
+    handleAutoPlay(): void { 
+        const { autoPlay } = this.getProps(); 
+        const autoPlayType = typeof autoPlay;
+        if ((autoPlayType === 'boolean' && autoPlay) || isObject(autoPlay)){
+            this.play(this.getSwitchingTime());
+        }
+    }
+
+    handleKeyDown(event: any): void{
+        if (event.key === 'ArrowLeft') {
+            this.prev();
+        }
+        if (event.key === 'ArrowRight') {
+            this.next();
+        }
+    }
+
+    onIndicatorChange(activeIndex: number): void {
+        const { activeIndex: stateActiveIndex } = this.getStates();
+        this._adapter.setIsReverse(stateActiveIndex > activeIndex);
+        this._notifyChange(activeIndex);
+        if (!this.getIsControledComponent()) {
+            this.handleNewActiveIndex(activeIndex);
+        }
+    }
+
+
+    handleNewActiveIndex(activeIndex: number): void {
+        const { activeIndex: stateActiveIndex } = this.getStates();
+        if (stateActiveIndex !== activeIndex) {
+            this._adapter.setNewActiveIndex(activeIndex);
+        }
+    }
+
+    getDefaultActiveIndex(): number {
+        let activeIndex;
+        const props = this.getProps();
+        if ('activeIndex' in props) {
+            activeIndex = props.activeIndex;
+        } else if ('defaultActiveIndex' in props) {
+            activeIndex = props.defaultActiveIndex;
+        }
+        return activeIndex;
+    }
+
+}
+
+export default CarouselFoundation;

+ 47 - 0
packages/semi-foundation/carousel/rtl.scss

@@ -0,0 +1,47 @@
+$module: #{$prefix}-carousel;
+.#{$prefix}-rtl,
+.#{$prefix}-portal-rtl {
+    .#{$module} {
+        direction: rtl;
+        
+        &-indicator {
+            display: flex;
+
+            &-dot {
+                .#{$module}-indicator-item {
+                    &:not(:last-child) {
+                        margin-right: 0;
+                        margin-left: $spacing-carousel_indicator_dot-marginX;
+                    }
+                }
+            }
+
+            &-columnar {
+                .#{$module}-indicator-item {
+                    &:not(:last-child) {
+                        margin-right: 0;
+                        margin-left: $spacing-carousel_indicator_columnar-marginX;
+                    }
+                }
+            }
+        }
+
+        &-arrow {
+            flex-direction: row-reverse;
+
+            &-prev {
+                left: auto;
+                right: $spacing-carousel_arrow-right;
+                transform: scaleX(-1) translateY(-50%);
+                z-index: 2;
+            }
+            
+            &-next {
+                left: $spacing-carousel_arrow-left;
+                transform: scaleX(-1) translateY(-50%);
+                right: auto;
+                z-index: 2;
+            }
+        }
+    }
+}

+ 46 - 0
packages/semi-foundation/carousel/variables.scss

@@ -0,0 +1,46 @@
+$color-carousel_indicator_theme_dark-bg-default: rgba(var(--semi-black), .5); // 深色主题指示器背景颜色 - 默认
+$color-carousel_indicator_theme_dark-bg-hover: rgba(var(--semi-black), .7); // 深色主题指示器背景颜色 - 悬浮
+$color-carousel_indicator_theme_dark-bg-active: rgba(var(--semi-black), 1); // 深色主题指示器背景颜色 - 选中
+
+$color-carousel_indicator_theme_primary-bg-default: rgba(var(--semi-blue-6), .4); // 主要主题指示器背景颜色 - 默认
+$color-carousel_indicator_theme_primary-bg-hover: rgba(var(--semi-blue-6), .7); // 主要主题指示器背景颜色 - 悬浮
+$color-carousel_indicator_theme_primary-bg-active: rgba(var(--semi-blue-6), 1); // 主要主题指示器背景颜色 - 选中
+
+$color-carousel_indicator_theme_light-bg-default: rgba(var(--semi-white), .4); // 浅色主题指示器背景颜色 - 默认
+$color-carousel_indicator_theme_light-bg-hover: rgba(var(--semi-white), .7); // 浅色主题指示器背景颜色 - 悬浮
+$color-carousel_indicator_theme_light-bg-active: rgba(var(--semi-white), 1); // 浅色主题指示器背景颜色 - 选中
+
+$color-carousel_arrow_theme_dark-bg-default: rgba(var(--semi-black), .5); // 深色主题箭头背景颜色 - 默认
+$color-carousel_arrow_theme_dark-bg-hover: rgba(var(--semi-black), 1); // 深色主题箭头背景颜色 - 悬浮
+
+$color-carousel_arrow_theme_primary-bg-default:  rgba(var(--semi-blue-6), .4); // 主要主题箭头背景颜色 - 默认
+$color-carousel_arrow_theme_primary-bg-hover:  rgba(var(--semi-blue-6), 1); // 主要主题箭头背景颜色 - 悬浮
+
+$color-carousel_arrow_theme_light-bg-default: rgba(var(--semi-white), .4); // 浅色主题箭头背景颜色 - 默认
+$color-carousel_arrow_theme_light-bg-hover: rgba(var(--semi-white), 1); // 浅色主题箭头背景颜色 - 悬浮
+
+$width-carousel_indicator_line: 240px; // 条状指示器最大宽度
+$width-carousel_indicator_columnar_small: 4px; // 小尺寸柱状指示器宽度
+$width-carousel_indicator_columnar_medium: 6px; // 中等尺寸柱状指示器宽度
+$width-carousel_indicator_dot_small:8px; // 小尺寸点状指示器宽度
+$width-carousel_indicator_dot_medium: 12px; // 中等尺寸点状指示器宽度
+$width-carousel_arrow: 32px; // 箭头宽度
+
+$height-carousel_indicator_columnar_small_default: 12px; // 小尺寸柱状指示器高度 - 默认
+$height-carousel_indicator_columnar_small_active: 20px; // 小尺寸柱状指示器高度 - 选中
+$height-carousel_indicator_columnar_medium_default: 20px; // 中等尺寸柱状指示器高度 - 默认
+$height-carousel_indicator_columnar_medium_active: 28px; // 中等尺寸柱状指示器高度 - 选中
+
+$height-carousel_indicator_line_small: 4px; // 小尺寸条状指示器高度
+$height-carousel_indicator_line_medium: 6px; // 中等尺寸条状指示器高度
+
+$radius-carousel_indicator_dot: 50%; // 点状指示器圆角
+
+$spacing-carousel_indicator-padding: 32px; // 指示器内边距
+
+$spacing-carousel_indicator_columnar-marginX: 4px; // 柱状指示器水平外边距
+$spacing-carousel_indicator_line-marginX: 4px; // 条状指示器水平外边距
+$spacing-carousel_indicator_dot-marginX: 8px; // 点状指示器水平外边距
+
+$spacing-carousel_arrow-left: 20px; // 左侧箭头绝对定位 - left
+$spacing-carousel_arrow-right: 20px; // 右侧箭头绝对定位 - right

+ 62 - 0
packages/semi-ui/carousel/CarouselArrow.tsx

@@ -0,0 +1,62 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+import React, { ReactNode } from "react";
+import cls from 'classnames';
+import { cssClasses } from '@douyinfe/semi-foundation/carousel/constants';
+import { CarouselArrowProps } from "./interface";
+import { IconChevronLeft, IconChevronRight } from "@douyinfe/semi-icons";
+import { get, throttle } from 'lodash';
+
+class CarouselArrow extends React.PureComponent<CarouselArrowProps> {
+    renderLeftIcon = () => {
+        return get(this.props, 'arrowProps.leftArrow.children', <IconChevronLeft aria-label="Previous index" size="inherit"/>);
+    }
+
+    renderRightIcon = () => {
+        return get(this.props, 'arrowProps.rightArrow.children', <IconChevronRight aria-label="Next index" size="inherit"/>);
+    }
+
+    render(): ReactNode {
+        const { type, theme, prev, next, timing } = this.props;
+        const classNames = cls( {
+            [cssClasses.CAROUSEL_ARROW]: true,
+            [`${cssClasses.CAROUSEL_ARROW}-${theme}`]: theme,
+            [`${cssClasses.CAROUSEL_ARROW}-hover`]: type === 'hover',
+        });
+
+        const leftClassNames = cls( {
+            [`${cssClasses.CAROUSEL_ARROW}-prev`]: true,
+            [`${cssClasses.CAROUSEL_ARROW}-${theme}`]: theme,
+        });
+
+        const rightClassNames = cls( {
+            [`${cssClasses.CAROUSEL_ARROW}-next`]: true,
+            [`${cssClasses.CAROUSEL_ARROW}-${theme}`]: theme,
+        });
+
+        return (
+            <div className={classNames}>
+                <div 
+                    // role='button'
+                    className={leftClassNames} 
+                    onClick={throttle(prev, timing)}
+                    {...get(this.props, 'arrowProps.leftArrow.props')}
+                >
+                    {this.renderLeftIcon()}
+                </div>
+                <div 
+                    // role='button'
+                    // tabIndex={0} 
+                    className={rightClassNames} 
+                    onClick={throttle(next, timing)}
+                    {...get(this.props, 'arrowProps.rightArrow.props')}
+                >
+                    {this.renderRightIcon()}
+                </div>
+            </div>
+        );
+    }
+
+}
+
+export default CarouselArrow;

+ 84 - 0
packages/semi-ui/carousel/CarouselIndicator.tsx

@@ -0,0 +1,84 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import React, { ReactNode } from "react";
+import cls from 'classnames';
+import PropTypes from 'prop-types';
+import { cssClasses, strings } from '@douyinfe/semi-foundation/carousel/constants';
+import { CarouselIndicatorProps } from "./interface";
+import getDataAttr from "@douyinfe/semi-foundation/utils/getDataAttr";
+import { throttle } from 'lodash';
+
+class CarouselIndicator extends React.PureComponent<CarouselIndicatorProps> {
+    static propTypes = {
+        activeKey: PropTypes.number,
+        className: PropTypes.string,
+        position: PropTypes.oneOf(strings.POSITION_MAP),
+        size: PropTypes.oneOf(strings.SIZE),
+        style: PropTypes.object,
+        theme: PropTypes.oneOf(strings.THEME_MAP),
+        total: PropTypes.number,
+        onIndicatorChange: PropTypes.func,
+        type: PropTypes.oneOf(strings.TYPE_MAP),
+        trigger: PropTypes.oneOf(strings.TRIGGER)
+    };
+
+    onIndicatorChange = (activeIndex: number): void => {
+        this.props.onIndicatorChange(activeIndex);
+    };
+
+    handleIndicatorClick = (activeIndex: number): void => {
+        const { trigger } = this.props;
+        if (trigger === 'click'){
+            this.onIndicatorChange(activeIndex);
+        }
+    }
+
+    handleIndicatorHover = (activeIndex: number): void => {
+        const { trigger } = this.props;
+        if (trigger === 'hover'){
+            this.onIndicatorChange(activeIndex);
+        }
+    }
+
+    renderIndicatorContent(): ReactNode {
+        const { total, theme, size, activeIndex } = this.props;
+        const indicatorContent: ReactNode[] = [];
+        for (let i = 0; i < total; i++) {
+            indicatorContent.push(
+                <span
+                    // role='none' 
+                    key={i}
+                    data-index={i}
+                    className={cls([`${cssClasses.CAROUSEL_INDICATOR}-item`], {
+                        [`${cssClasses.CAROUSEL_INDICATOR}-item-active`]: i === activeIndex,
+                        [`${cssClasses.CAROUSEL_INDICATOR}-item-${theme}`]: theme,
+                        [`${cssClasses.CAROUSEL_INDICATOR}-item-${size}`]: size,
+                    })}
+                    onClick={()=>this.handleIndicatorClick(i)}
+                    onMouseEnter={()=>this.handleIndicatorHover(i)}
+                ></span>
+            );
+        }
+        return indicatorContent;
+    }
+
+    render(): ReactNode {
+        const { type, size, theme, style, className, position, ...restProps } = this.props;
+        const classNames = cls(className, {
+            [cssClasses.CAROUSEL_INDICATOR]: true,
+            [`${cssClasses.CAROUSEL_INDICATOR}-${type}`]: type,
+            [`${cssClasses.CAROUSEL_INDICATOR}-${position}`]: position,
+        });
+
+        const indicatorContent = this.renderIndicatorContent();
+
+        return (
+            <div className={classNames} style={style} {...getDataAttr(restProps)}>
+                {indicatorContent}
+            </div>
+        );
+    }
+
+}
+
+export default CarouselIndicator;

+ 159 - 0
packages/semi-ui/carousel/__test__/carousel.test.js

@@ -0,0 +1,159 @@
+import { Carousel } from '../../index';
+import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
+
+
+function getCarousel(carouselProps) {
+    const contentStyle = {
+        display:'flex',
+        justifyContent: 'center',
+        alignItems: 'center',
+        color: '#fff',
+        background: 'lightBlue',
+    };
+
+    return <Carousel style={{ width: '600px', height: '240px'}} {...carouselProps}>
+      <div style={contentStyle}>
+        <h3>index0</h3>
+      </div>
+      <div style={contentStyle}>
+        <h3>index1</h3>
+      </div>
+      <div style={contentStyle}>
+        <h3>index2</h3>
+      </div>
+    </Carousel>
+}
+
+function getSingleCarousel(carouselProps) {
+    const contentStyle = {
+        display:'flex',
+        justifyContent: 'center',
+        alignItems: 'center',
+        color: '#fff',
+        background: 'lightBlue',
+    };
+
+    return <Carousel style={{ width: '600px', height: '240px'}} {...carouselProps}>
+      <div style={contentStyle}>
+        <h3>index0</h3>
+      </div>
+    </Carousel>
+}
+
+describe('Carousel', () => {
+
+    it('Carousel render basicly', () => {
+        let props = {};
+        const carousel = mount(getCarousel(props))
+        expect(carousel.find(`.${BASE_CLASS_PREFIX}-carousel-content`).children().length).toEqual(3);
+        expect(carousel.find(`.${BASE_CLASS_PREFIX}-carousel-content-item-active`).text()).toEqual('index0');
+        carousel.unmount();
+    });
+
+    it('Carousel with custom className & style', () => {
+        let props = {
+            className: 'test',
+            style: {
+                color: 'red'
+            }
+        };
+        const carousel = shallow(getCarousel(props));
+        expect(carousel.exists('.test')).toEqual(true);
+        expect(carousel.find('div.test')).toHaveStyle('color', 'red');
+    });
+
+    it('Carousel with defaultActiveIndex', () => {
+        let props = {
+            defaultActiveIndex: 2
+        };
+        const carousel = mount(getCarousel(props));
+        const carouselContent = carousel.find(`.${BASE_CLASS_PREFIX}-carousel-content-item-active`).text();
+        expect(carouselContent).toEqual('index2');
+    });
+
+    it('different theme', () => {
+        let primary = mount(getCarousel({ theme: 'primary' }));
+        let light = mount(getCarousel({ theme: 'light' }));
+        let dark = mount(getCarousel({ theme: 'dark' }));
+        expect(primary.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow-primary`)).toEqual(true);
+        expect(primary.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-primary`)).toEqual(true);
+        expect(light.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow-light`)).toEqual(true);
+        expect(light.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-light`)).toEqual(true);
+        expect(dark.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow-dark`)).toEqual(true);
+        expect(dark.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-dark`)).toEqual(true);
+    });
+
+    it('different indicator type', () => {
+        let dot = mount(getCarousel({ indicatorType: 'dot' }));
+        let line = mount(getCarousel({ indicatorType: 'line' }));
+        let columnar = mount(getCarousel({ indicatorType: 'columnar' }));
+        expect(dot.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-dot`)).toEqual(true);
+        expect(dot.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-line`)).toEqual(false);
+        expect(line.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-line`)).toEqual(true);
+        expect(line.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-columnar`)).toEqual(false);
+        expect(columnar.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-columnar`)).toEqual(true);
+        expect(columnar.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-dot`)).toEqual(false);
+    });
+
+    it('different indicator position', () => {
+        let left = mount(getCarousel({ indicatorPosition: 'left' }));
+        let center = mount(getCarousel({ indicatorPosition: 'center' }));
+        let right = mount(getCarousel({ indicatorPosition: 'right' }));
+        expect(left.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-left`)).toEqual(true);
+        expect(left.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-center`)).toEqual(false);
+        expect(center.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-center`)).toEqual(true);
+        expect(center.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-right`)).toEqual(false);
+        expect(right.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-right`)).toEqual(true);
+        expect(right.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-left`)).toEqual(false);
+    });
+
+    it('different indicator size', () => {
+        let small = mount(getCarousel({ indicatorSize: 'small' }));
+        let medium = mount(getCarousel({ indicatorSize: 'medium' }));
+        expect(small.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-small`)).toEqual(true);
+        expect(small.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-medium`)).toEqual(false);
+        expect(medium.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-medium`)).toEqual(true);
+        expect(medium.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator-item-small`)).toEqual(false);
+    });
+
+    it('show arrow and arrow change', () => {
+        let spyOnChange = sinon.spy(() => {})
+        let show = mount(getCarousel({ onChange: spyOnChange }));
+        let hide = mount(getCarousel({ showArrow: false }));
+        let hover = mount(getCarousel({ arrowType: 'hover' }));
+
+        expect(show.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow`)).toEqual(true);
+        expect(hide.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow`)).toEqual(false);
+        expect(hover.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow-hover`)).toEqual(true);
+
+        show.find(`.${BASE_CLASS_PREFIX}-carousel-arrow-prev`).simulate('click');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(show.find(`.${BASE_CLASS_PREFIX}-carousel-content-item-active`).text()).toEqual('index2');
+
+        show.find(`.${BASE_CLASS_PREFIX}-carousel-arrow-next`).simulate('click');
+        expect(show.find(`.${BASE_CLASS_PREFIX}-carousel-content-item-active`).text()).toEqual('index0');
+        
+    });
+
+    it('indicator change with click or trigger', () => {
+        let spyOnChange = sinon.spy(() => {})
+        let carousel = mount(getCarousel({ onChange: spyOnChange }));
+        carousel.find(`.${BASE_CLASS_PREFIX}-carousel-indicator-item`).at(2).simulate('click');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(carousel.find(`.${BASE_CLASS_PREFIX}-carousel-content-item-active`).text()).toEqual('index2');
+
+        let spyOnChangeHover = sinon.spy(() => {})
+        let carouselHover = mount(getCarousel({ onChange: spyOnChangeHover, trigger: 'hover' }));
+        carouselHover.find(`.${BASE_CLASS_PREFIX}-carousel-indicator-item`).at(2).simulate('mouseEnter', {});
+        expect(spyOnChangeHover.calledOnce).toBe(true);
+        expect(carouselHover.find(`.${BASE_CLASS_PREFIX}-carousel-content-item-active`).text()).toEqual('index2');
+    });
+
+    it('single index', () => {
+        let carousel = mount(getSingleCarousel({}));
+        expect(carousel.exists(`.${BASE_CLASS_PREFIX}-carousel-indicator`)).toEqual(false);
+        expect(carousel.exists(`.${BASE_CLASS_PREFIX}-carousel-arrow`)).toEqual(false);
+        carousel.unmount();
+    });
+
+})

+ 486 - 0
packages/semi-ui/carousel/_story/carousel.stories.js

@@ -0,0 +1,486 @@
+import React, { useState } from 'react';
+import { Radio, RadioGroup, Button } from '@douyinfe/semi-ui';
+import { IconArrowLeft, IconArrowRight } from "@douyinfe/semi-icons";
+import Carousel from '../index';
+
+export default {
+  title: 'Carousel',
+}
+
+const style = {
+  width: '600px',
+  height: '240px',
+};
+
+const contentPinkStyle = {
+  display:'flex',
+  justifyContent: 'center',
+  alignItems: 'center',
+  color: '#fff',
+  background: 'lightpink',
+};
+
+const contentBlueStyle = {
+  display:'flex',
+  justifyContent: 'center',
+  alignItems: 'center',
+  color: '#fff',
+  background: 'lightBlue',
+};
+
+const radioTitleStyle = { 
+  marginRight: 20
+}
+
+export const BasicUsage = () => (
+    <Carousel style={style}>
+      <div style={contentPinkStyle}>
+        <h3>1</h3>
+      </div>
+      <div style={contentBlueStyle}>
+        <h3>2</h3>
+      </div>
+      <div style={contentPinkStyle}>
+        <h3>3</h3>
+      </div>
+      <div style={contentBlueStyle}>
+        <h3>4</h3>
+      </div>
+      <div style={contentPinkStyle}>
+        <h3>5</h3>
+      </div>
+    </Carousel>
+);
+
+BasicUsage.story = {
+  name: 'basic usage',
+};
+
+// 主题切换
+export const theme = () => {
+  const [theme, setTheme] = useState('primary');
+
+  return (
+   <div>
+     <div> 
+       <span style={radioTitleStyle}>主题</span>
+       <RadioGroup onChange={e => setTheme(e.target.value)} value={theme}>
+        <Radio value='primary'>primary</Radio>
+        <Radio value='light'>light</Radio>
+        <Radio value='dark'>dark</Radio>
+      </RadioGroup>
+      </div>
+      <br/>
+      <Carousel style={style} theme={theme}>
+        <div style={contentPinkStyle}>
+          <h3>1</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>2</h3>
+        </div>
+        <div style={contentPinkStyle}>
+         <h3>3</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>4</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>5</h3>
+       </div>
+      </Carousel>
+   </div>
+  );
+}
+
+theme.story = {
+  name: 'theme',
+};
+
+
+// 指示器大小、类型、位置
+export const indicatorUsage = () => {
+  const [size, setSize] = useState('small');
+  const [type, setType] = useState('dot');
+  const [position, setPosition] = useState('left');
+
+  return (
+   <div>
+      <div> 
+       <span style={radioTitleStyle}>类型</span>
+       <RadioGroup onChange={e => setType(e.target.value)} value={type}>
+        <Radio value='dot'>dot</Radio>
+        <Radio value='line'>line</Radio>
+        <Radio value='columnar'>columnar</Radio>
+      </RadioGroup>
+      </div>
+       <div> 
+       <span style={radioTitleStyle}>位置</span>
+       <RadioGroup onChange={e => setPosition(e.target.value)} value={position}>
+        <Radio value='left'>left</Radio>
+        <Radio value='center'>center</Radio>
+        <Radio value='right'>right</Radio>
+      </RadioGroup>
+      </div>
+      <div> 
+       <span style={radioTitleStyle}>尺寸</span>
+       <RadioGroup onChange={e => setSize(e.target.value)} value={size}>
+        <Radio value={'small'}>small</Radio>
+        <Radio value={'medium'}>medium</Radio>
+      </RadioGroup>
+      </div>
+      <br/>
+      <Carousel style={style} indicatorType={type} indicatorPosition={position} indicatorSize={size}>
+        <div style={contentPinkStyle}>
+          <h3>1</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>2</h3>
+        </div>
+        <div style={contentPinkStyle}>
+         <h3>3</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>4</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>5</h3>
+       </div>
+      </Carousel>
+   </div>
+  );
+}
+
+indicatorUsage.story = {
+  name: 'indicator usage',
+};
+
+// 箭头主题、显示时机
+export const arrowShow = () => {
+  const [arrowType, setArrowTypew] = useState('always');
+  const [show, setShow] = useState(true);
+  
+  return (
+   <div>
+      <div> 
+        <span style={radioTitleStyle}>展示箭头</span>
+        <RadioGroup onChange={e => setShow(e.target.value)} value={show}>
+          <Radio value={true}>展示</Radio>
+          <Radio value={false}>不展示</Radio>
+        </RadioGroup>
+      </div>
+      <div> 
+        <span style={radioTitleStyle}>展示时机</span>
+        <RadioGroup onChange={e => setArrowTypew(e.target.value)} value={arrowType}>
+          <Radio value='always'>always</Radio>
+          <Radio value='hover'>hover</Radio>
+        </RadioGroup>
+      </div>
+      <br/>
+      <Carousel style={style} showArrow={show} arrowType={arrowType}>
+        <div style={contentPinkStyle}>
+          <h3>1</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>2</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>3</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>4</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>5</h3>
+        </div>
+      </Carousel>
+   </div>
+  )
+};
+
+arrowShow.story = {
+  name: 'arrow show',
+};
+
+
+// 箭头参数
+export const customArrow = () => {
+  const arrowProps = {
+    leftArrow: { children: <IconArrowLeft size='large'/>},
+    rightArrow: { children: <IconArrowRight size='large'/> },
+  };
+
+  return (
+   <div>
+      <Carousel style={style} arrowProps={arrowProps}>
+        <div style={contentPinkStyle}>
+          <h3>1</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>2</h3>
+        </div>
+        <div style={contentPinkStyle}>
+         <h3>3</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>4</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>5</h3>
+       </div>
+      </Carousel>
+   </div>
+  );
+}
+
+customArrow.story = {
+  name: 'custom arrow',
+};
+
+// 自动播放参数 
+export const autoPlayExample = () => (
+   <div>
+      <Carousel style={style} autoPlay={{ interval: 1000, hoverToPause: true }}>
+      <div style={contentPinkStyle}>
+        <h3>1</h3>
+      </div>
+      <div style={contentBlueStyle}>
+        <h3>2</h3>
+      </div>
+      <div style={contentPinkStyle}>
+        <h3>3</h3>
+      </div>
+      <div style={contentBlueStyle}>
+        <h3>4</h3>
+      </div>
+      <div style={contentPinkStyle}>
+        <h3>5</h3>
+      </div>
+    </Carousel>
+   </div>
+);
+
+autoPlayExample.story = {
+  name: 'auto play example',
+};
+
+// 动画效果与速度
+export const animationUsage = () => {
+  const [animation, setAnimation] = useState('slide');
+  const [speed, setSpeed] = useState(1000);
+  
+  return (
+   <div>
+      <div> 
+        <span style={radioTitleStyle}>动画效果</span>
+        <RadioGroup onChange={e => setAnimation(e.target.value)} value={animation}>
+          <Radio value='slide'>slide</Radio>
+          <Radio value='fade'>fade</Radio>
+        </RadioGroup>
+      </div>
+      <div> 
+        <span style={radioTitleStyle}>切换速度</span>
+        <RadioGroup onChange={e => setSpeed(e.target.value)} value={speed}>
+          <Radio value={1000}>1000ms</Radio>
+          <Radio value={2000}>2000ms</Radio>
+          <Radio value={3000}>3000ms</Radio>
+        </RadioGroup>
+      </div>
+      <br/>
+      <Carousel style={style} speed={speed} animation={animation}>
+        <div style={contentPinkStyle}>
+          <h3>1</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>2</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>3</h3>
+        </div>
+        <div style={contentBlueStyle}>
+          <h3>4</h3>
+        </div>
+        <div style={contentPinkStyle}>
+          <h3>5</h3>
+        </div>
+      </Carousel>
+   </div>
+  )
+};
+
+animationUsage.story = {
+  name: 'animation usage',
+};
+
+// 受控的轮播图
+class ControlledDemo extends React.Component {
+   constructor(props) {
+        super(props);
+        this.ref = React.createRef();
+        this.handleNext=this.handleNext.bind(this);
+        this.handlePrev=this.handlePrev.bind(this);
+        this.handleGoTo=this.handleGoTo.bind(this);
+        this.handlePlay=this.handlePlay.bind(this);
+        this.handleStop=this.handleStop.bind(this);
+        this.state = {
+            activeIndex: 0,
+        };
+    }
+
+    handleNext(){
+        this.ref.current.next();
+    }
+
+    handlePrev(){
+        this.ref.current.prev();
+    }
+
+    handleGoTo(){
+        this.ref.current.goTo(2);
+    }
+
+    handlePlay(){
+        this.ref.current.play();
+    }
+
+    handleStop(){
+        this.ref.current.stop();
+    }
+
+    onChange(activeIndex){
+      this.setState({ activeIndex });
+    }
+
+     render() {
+       return (
+        <div>
+          <Carousel style={style} animation='slide' ref={this.ref} activeIndex={this.state.activeIndex} onChange={this.onChange.bind(this)}>
+            <div style={contentPinkStyle}>
+              <h3>1</h3>
+            </div>
+            <div style={contentBlueStyle}>
+              <h3>2</h3>
+            </div>
+            <div style={contentPinkStyle}>
+              <h3>3</h3>
+            </div>
+            <div style={contentBlueStyle}>
+             <h3>4</h3>
+            </div>
+            <div style={contentPinkStyle}>
+              <h3>5</h3>
+           </div>
+          </Carousel>
+          <br/>
+          <Button onClick={this.handlePrev} style={{ marginRight: 10 }}>prev</Button>
+          <Button onClick={this.handleNext} style={{ marginRight: 10 }}>next</Button>
+          <Button onClick={this.handleGoTo} style={{ marginRight: 10 }}>goTo3</Button>
+          <Button onClick={this.handlePlay} style={{ marginRight: 10 }}>play</Button>
+          <Button onClick={this.handleStop} style={{ marginRight: 10 }}>stop</Button>
+        </div>
+        )
+     }
+}
+
+export const controlledUsage  = () => <ControlledDemo />;
+
+controlledUsage.story = {
+  name: 'controlled usage',
+};
+
+class RefDemo extends React.Component {
+   constructor(props) {
+        super(props);
+        this.ref = React.createRef();
+        this.handleNext=this.handleNext.bind(this);
+        this.handlePrev=this.handlePrev.bind(this);
+        this.handleGoTo=this.handleGoTo.bind(this);
+        this.handlePlay=this.handlePlay.bind(this);
+        this.handleStop=this.handleStop.bind(this);
+    }
+
+    handleNext(){
+        this.ref.current.next();
+    }
+
+    handlePrev(){
+        this.ref.current.prev();
+    }
+
+    handleGoTo(){
+        this.ref.current.goTo(2);
+    }
+
+    handlePlay(){
+        this.ref.current.play();
+    }
+
+    handleStop(){
+        this.ref.current.stop();
+    }
+
+
+     render() {
+       return (
+        <div>
+          <Carousel style={style} animation='slide' ref={this.ref}>
+            <div style={contentPinkStyle}>
+              <h3>1</h3>
+            </div>
+            <div style={contentBlueStyle}>
+              <h3>2</h3>
+            </div>
+            <div style={contentPinkStyle}>
+              <h3>3</h3>
+            </div>
+            <div style={contentBlueStyle}>
+             <h3>4</h3>
+            </div>
+            <div style={contentPinkStyle}>
+              <h3>5</h3>
+           </div>
+          </Carousel>
+          <br/>
+          <Button onClick={this.handlePrev} style={{ marginRight: 10 }}>prev</Button>
+          <Button onClick={this.handleNext} style={{ marginRight: 10 }}>next</Button>
+          <Button onClick={this.handleGoTo} style={{ marginRight: 10 }}>goTo3</Button>
+          <Button onClick={this.handlePlay} style={{ marginRight: 10 }}>play</Button>
+          <Button onClick={this.handleStop} style={{ marginRight: 10 }}>stop</Button>
+        </div>
+        )
+     }
+}
+
+export const refUsage  = () => <RefDemo />;
+
+refUsage.story = {
+  name: 'ref usage',
+};
+
+export const slideDirection = () => (
+    <Carousel style={style} autoPlay={false} slideDirection='right'>
+      <div style={contentPinkStyle}>
+        <h3>index0</h3>
+      </div>
+      <div style={contentPinkStyle}>
+        <h3>index1</h3>
+      </div>
+      <div style={contentPinkStyle}>
+        <h3>index2</h3>
+      </div>
+    </Carousel>
+);
+
+slideDirection.story = {
+  name: 'slide direction',
+};
+
+
+
+
+
+
+
+
+
+
+

+ 1 - 0
packages/semi-ui/carousel/index-en-US.md

@@ -0,0 +1 @@
+../../../content/show/carousel/index-en-US.md

+ 1 - 0
packages/semi-ui/carousel/index.md

@@ -0,0 +1 @@
+../../../content/show/carousel/index.md

+ 294 - 0
packages/semi-ui/carousel/index.tsx

@@ -0,0 +1,294 @@
+/* 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.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.throttleChange(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;
+        const timing = this.foundation.getSwitchingTime();
+
+        if (showArrow && children.length > 1){
+            return (
+                <CarouselArrow 
+                    type={arrowType} 
+                    theme={theme} 
+                    prev={this.prev} 
+                    next={this.next}
+                    timing={timing}
+                    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,
+                    })}
+                >
+                    {this.renderChildren()}
+                </div>
+                {this.renderIndicator()}
+                {this.renderArrow()}
+            </div>
+        );
+    }
+}
+
+export default Carousel;

+ 64 - 0
packages/semi-ui/carousel/interface.ts

@@ -0,0 +1,64 @@
+import React, { ReactNode } from "react";
+import { strings } from '@douyinfe/semi-foundation/carousel/constants';
+
+export interface CarouselMethod {
+    next?: () => void;
+    prev?: () => void;
+    goTo?: () => void;
+    play?: () => void;
+    stop?: () => void;
+}
+
+export interface CarouselProps {
+    activeIndex?: number;
+    animation?: typeof strings.ANIMATION_MAP[number];
+    arrowProps?: ArrowProps; 
+    autoPlay?: boolean | {interval?: number, hoverToPause?: boolean};
+    arrowType?: typeof strings.ARROW_MAP[number];
+    children?: ReactNode | Array<ReactNode>;
+    className?: string;
+    defaultActiveIndex?: number;
+    indicatorPosition?: typeof strings.POSITION_MAP[number];
+    indicatorSize?: typeof strings.SIZE[number];
+    theme?: typeof strings.THEME_MAP[number];
+    indicatorType?: typeof strings.TYPE_MAP[number];
+    onChange?: (index: number, preIndex: number) => void;
+    showArrow?: boolean;
+    showIndicator?: boolean;
+    slideDirection?: typeof strings.DIRECTION[number];
+    speed?: number;
+    style?: React.CSSProperties;
+    trigger?: typeof strings.TRIGGER[number];
+}
+
+export interface CarouselIndicatorProps {
+    activeIndex?: number;
+    className?: string;
+    defaultActiveIndex?: number;
+    position?: typeof strings.POSITION_MAP[number];
+    size?: typeof strings.SIZE[number];
+    total?:number;
+    theme?: typeof strings.THEME_MAP[number];
+    type?: typeof strings.TYPE_MAP[number];
+    onIndicatorChange?: (activeIndex: number) => void;
+    style?: React.CSSProperties;
+    trigger?: typeof strings.TRIGGER[number];
+}
+
+export interface CarouselArrowProps {
+    type?: typeof strings.ARROW_MAP[number];
+    theme?: typeof strings.THEME_MAP[number];
+    prev?: () => void;
+    next?: () => void;
+    arrowProps?: ArrowProps;
+    timing: number;
+}
+
+export interface ArrowButton {
+    props?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
+    children?: React.ReactNode;
+}
+export interface ArrowProps {
+    leftArrow?: ArrowButton;
+    rightArrow?: ArrowButton;
+}

+ 2 - 0
packages/semi-ui/index.ts

@@ -12,6 +12,7 @@ export { default as ButtonGroup } from './button/buttonGroup';
 export { default as Calendar } from './calendar';
 export { default as Card } from './card';
 export { default as CardGroup } from './card/cardGroup';
+export { default as Carousel } from './carousel';
 export { default as Cascader } from './cascader';
 export { default as Checkbox } from './checkbox';
 export { default as CheckboxGroup } from './checkbox/checkboxGroup';
@@ -79,6 +80,7 @@ export { default as Upload } from './upload';
 export { default as Typography } from './typography';
 export { default as Transfer } from './transfer';
 
+
 export { default as LocaleProvider } from './locale/localeProvider';
 
 /** Form */

+ 7 - 0
src/images/docIcons/doc-carousel.svg

@@ -0,0 +1,7 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="10.5" y="7.5" width="11" height="11" rx="2" fill="#DDE3E8"/>
+<rect x="2.5" y="7.5" width="12" height="11" rx="2" fill="#AAB2BF"/>
+<rect x="5" y="5.5" width="14" height="13" rx="2" fill="#6A6F7F"/>
+<path d="M12.1824 10.7059C12.768 9.76471 14.232 9.76471 14.8176 10.7059L16.7939 13.8824C17.3795 14.8235 16.6475 16 15.4763 16H11.5237C10.3525 16 9.62051 14.8235 10.2061 13.8824L12.1824 10.7059Z" fill="#DDE3E8"/>
+<circle cx="8.5" cy="9.5" r="1.5" fill="white"/>
+</svg>