Jelajahi Sumber

Merge branch 'release'

pointhalo 3 tahun lalu
induk
melakukan
b9890dd344
100 mengubah file dengan 3670 tambahan dan 99 penghapusan
  1. 3 1
      content/feedback/banner/index-en-US.md
  2. 6 1
      content/feedback/banner/index.md
  3. 1 1
      content/feedback/notification/index-en-US.md
  4. 1 1
      content/feedback/notification/index.md
  5. 1 1
      content/feedback/popconfirm/index-en-US.md
  6. 1 1
      content/feedback/popconfirm/index.md
  7. 1 1
      content/feedback/progress/index-en-US.md
  8. 1 1
      content/feedback/progress/index.md
  9. 1 1
      content/feedback/skeleton/index-en-US.md
  10. 1 1
      content/feedback/skeleton/index.md
  11. 1 1
      content/feedback/spin/index-en-US.md
  12. 1 1
      content/feedback/spin/index.md
  13. 1 1
      content/feedback/toast/index-en-US.md
  14. 1 1
      content/feedback/toast/index.md
  15. 4 0
      content/input/switch/index-en-US.md
  16. 8 3
      content/input/switch/index.md
  17. 1 0
      content/order.js
  18. 1 1
      content/other/configprovider/index-en-US.md
  19. 1 1
      content/other/configprovider/index.md
  20. 1 1
      content/other/locale/index-en-US.md
  21. 1 1
      content/other/locale/index.md
  22. 696 0
      content/show/carousel/index-en-US.md
  23. 695 0
      content/show/carousel/index.md
  24. 1 1
      content/show/collapse/index-en-US.md
  25. 1 1
      content/show/collapse/index.md
  26. 1 1
      content/show/collapsible/index-en-US.md
  27. 1 1
      content/show/collapsible/index.md
  28. 1 1
      content/show/descriptions/index-en-US.md
  29. 1 1
      content/show/descriptions/index.md
  30. 1 1
      content/show/dropdown/index-en-US.md
  31. 1 1
      content/show/dropdown/index.md
  32. 1 1
      content/show/empty/index-en-US.md
  33. 1 1
      content/show/empty/index.md
  34. 1 1
      content/show/list/index-en-US.md
  35. 1 1
      content/show/list/index.md
  36. 1 1
      content/show/modal/index-en-US.md
  37. 1 1
      content/show/modal/index.md
  38. 1 1
      content/show/overflowlist/index-en-US.md
  39. 1 1
      content/show/overflowlist/index.md
  40. 1 1
      content/show/popover/index-en-US.md
  41. 1 1
      content/show/popover/index.md
  42. 1 1
      content/show/scrolllist/index-en-US.md
  43. 1 1
      content/show/scrolllist/index.md
  44. 1 1
      content/show/sidesheet/index-en-US.md
  45. 1 1
      content/show/sidesheet/index.md
  46. 1 1
      content/show/table/index-en-US.md
  47. 1 1
      content/show/table/index.md
  48. 1 1
      content/show/tag/index-en-US.md
  49. 1 1
      content/show/tag/index.md
  50. 1 1
      content/show/timeline/index-en-US.md
  51. 1 1
      content/show/timeline/index.md
  52. 1 1
      content/show/tooltip/index-en-US.md
  53. 1 1
      content/show/tooltip/index.md
  54. 25 3
      content/start/changelog/index-en-US.md
  55. 28 5
      content/start/changelog/index.md
  56. 4 1
      content/start/faq/index-en-US.md
  57. 4 1
      content/start/faq/index.md
  58. 1 0
      content/start/overview/index-en-US.md
  59. 1 0
      content/start/overview/index.md
  60. 103 0
      cypress/integration/carousel.spec.js
  61. 16 0
      cypress/integration/inputNumber.spec.js
  62. 1 1
      lerna.json
  63. 3 3
      packages/semi-animation-react/package.json
  64. 1 1
      packages/semi-animation-styled/package.json
  65. 1 1
      packages/semi-animation/package.json
  66. 425 0
      packages/semi-foundation/carousel/carousel.scss
  67. 32 0
      packages/semi-foundation/carousel/constants.ts
  68. 164 0
      packages/semi-foundation/carousel/foundation.ts
  69. 47 0
      packages/semi-foundation/carousel/rtl.scss
  70. 46 0
      packages/semi-foundation/carousel/variables.scss
  71. 4 0
      packages/semi-foundation/form/foundation.ts
  72. 1 0
      packages/semi-foundation/form/interface.ts
  73. 1 1
      packages/semi-foundation/inputNumber/foundation.ts
  74. 2 2
      packages/semi-foundation/package.json
  75. 1 0
      packages/semi-foundation/switch/constants.ts
  76. 16 0
      packages/semi-foundation/switch/foundation.ts
  77. 4 0
      packages/semi-foundation/switch/switch.scss
  78. 2 0
      packages/semi-foundation/switch/variables.scss
  79. 2 2
      packages/semi-icons/package.json
  80. 1 1
      packages/semi-illustrations/package.json
  81. 2 2
      packages/semi-next/package.json
  82. 1 1
      packages/semi-scss-compile/package.json
  83. 1 1
      packages/semi-theme-default/package.json
  84. 62 1
      packages/semi-ui/banner/_story/banner.stories.js
  85. 5 5
      packages/semi-ui/banner/index.tsx
  86. 2 2
      packages/semi-ui/button/buttonGroup.tsx
  87. 62 0
      packages/semi-ui/carousel/CarouselArrow.tsx
  88. 83 0
      packages/semi-ui/carousel/CarouselIndicator.tsx
  89. 159 0
      packages/semi-ui/carousel/__test__/carousel.test.js
  90. 486 0
      packages/semi-ui/carousel/_story/carousel.stories.js
  91. 1 0
      packages/semi-ui/carousel/index-en-US.md
  92. 1 0
      packages/semi-ui/carousel/index.md
  93. 292 0
      packages/semi-ui/carousel/index.tsx
  94. 63 0
      packages/semi-ui/carousel/interface.ts
  95. 10 1
      packages/semi-ui/form/baseForm.tsx
  96. 2 0
      packages/semi-ui/index.ts
  97. 14 1
      packages/semi-ui/inputNumber/_story/inputNumber.stories.js
  98. 8 8
      packages/semi-ui/package.json
  99. 20 3
      packages/semi-ui/switch/index.tsx
  100. 1 1
      packages/semi-webpack/package.json

+ 3 - 1
content/feedback/banner/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 62
+order: 63
 category: Feedback
 title:  Banner
 subTitle: Banner
@@ -191,5 +191,7 @@ import { Banner } from '@douyinfe/semi-ui';
 - The component has a `role` of 'alert'.
 - The close icon has a `aria-label` of 'Close'.
 
+### Keyboard and Focus
+- The close button of the Banner can be focused with the `Tab` key. After the button is focused, hit the `Enter` key or the `Space` key to close the banner.
 ## Design Tokens
 <DesignToken/>

+ 6 - 1
content/feedback/banner/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 62
+order: 63
 category: 反馈类
 title:  Banner 通知横幅
 icon: doc-banner
@@ -190,5 +190,10 @@ import { Banner } from '@douyinfe/semi-ui';
 - 组件的 `role` 为 'alert'
 - 关闭按钮的 `aria-label` 为 'Close'
 
+### 键盘和焦点
+
+- Banner 的关闭按钮可以使用 `Tab` 键聚焦,按钮聚焦后,敲击 `Enter` 键或 `Space` 键可以关闭 banner
+
+
 ## 设计变量
 <DesignToken/>

+ 1 - 1
content/feedback/notification/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 63
+order: 64
 category: Feedback
 title:  Notification
 subTitle: Notification

+ 1 - 1
content/feedback/notification/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 63
+order: 64
 category: 反馈类
 title: Notification 通知
 icon: doc-notification

+ 1 - 1
content/feedback/popconfirm/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 64
+order: 65
 category: Feedback
 title:  Popconfirm
 subTitle: Popconfirm

+ 1 - 1
content/feedback/popconfirm/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 64
+order: 65
 category: 反馈类
 title:  Popconfirm 气泡确认框
 icon: doc-popconfirm

+ 1 - 1
content/feedback/progress/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 65
+order: 66
 category: Feedback
 title: Progress
 subTitle: Progress

+ 1 - 1
content/feedback/progress/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 65
+order: 66
 category: 反馈类
 title: Progress 进度条
 icon: doc-progress

+ 1 - 1
content/feedback/skeleton/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 66
+order: 67
 category: Feedback
 title:  Skeleton
 subTitle: Skeleton

+ 1 - 1
content/feedback/skeleton/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 66
+order: 67
 category: 反馈类
 title: Skeleton 骨架屏
 icon: doc-skeleton

+ 1 - 1
content/feedback/spin/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 67
+order: 68
 category: Feedback
 title: Spin
 subTitle: Spin

+ 1 - 1
content/feedback/spin/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 67
+order: 68
 category: 反馈类
 title: Spin 加载器
 icon: doc-spin

+ 1 - 1
content/feedback/toast/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 68
+order: 69
 category: Feedback
 title: Toast
 subTitle: Toast

+ 1 - 1
content/feedback/toast/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 68
+order: 69
 category: 反馈类
 title: Toast 提示
 icon: doc-toast

+ 4 - 0
content/input/switch/index-en-US.md

@@ -197,6 +197,10 @@ import { Switch } from '@douyinfe/semi-ui';
 - Switch has a `switch` role, when checked is true, `aria-checked` will be automatically set to true, and vice versa.
 - As a form field, it should have a Label, which will be automatically brought on when you use Form.Switch.
 - If you use Switch alone, it is recommended to use `aria-label` to describe the current label function.
+
+### Keyboard and Focus
+- Keyboard users can use `Tab` and `Shift + Tab` to switch focus.
+- When focusing, you can switch on or off by pressing the `Space` key.
 ## Design Tokens
 
 <DesignToken/>

+ 8 - 3
content/input/switch/index.md

@@ -191,9 +191,14 @@ import { Switch } from '@douyinfe/semi-ui';
 
 ### ARIA
 
--   Switch 具有 `switch` role,当 checked 为 true 时,`aria-checked` 将被自动设置为 true,反之亦然.
--   作为表单控件应该带有 Label,当你使用 Form.Switch 时会自动被带上。
--   如果你单独使用 Switch,建议使用 `aria-label` 描述当前标签作用。
+-   Switch 具有 `switch` role,当 checked 为 true 时,`aria-checked` 将被自动设置为 true,反之亦然
+-   作为表单控件应该带有 Label,当你使用 Form.Switch 时会自动被带上
+-   如果你单独使用 Switch,建议使用 `aria-label` 描述当前标签作用
+
+### 键盘和焦点
+
+-   键盘用户可以使用 `Tab` 及 `Shift + Tab` 切换焦点
+-   聚焦时可以通过 `Space` 键切换开启或关闭状态
 
 ## 设计变量
 

+ 1 - 0
content/order.js

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

+ 1 - 1
content/other/configprovider/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 69
+order: 70
 category: Other
 title: ConfigProvider
 icon: doc-configprovider

+ 1 - 1
content/other/configprovider/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 69
+order: 70
 category: 其他
 title:  ConfigProvider 全局配置
 icon: doc-configprovider

+ 1 - 1
content/other/locale/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 70
+order: 71
 category: Other
 title: LocaleProvider
 subTitle: LocaleProvider

+ 1 - 1
content/other/locale/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 70
+order: 71
 category: 其他
 title:  LocaleProvider 多语言
 icon: doc-i18n

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

@@ -0,0 +1,696 @@
+---
+localeCode: en-US
+order: 47
+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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 System Management', 'From Semi Design, To Any Design', 'Quickly define your design system and apply it to 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 | { 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|
+
+**Method()**
+
+| Method             | Instructions                    | Version |
+| ------------------ | ------------------------------- | ------- |
+| play()             | Play                            | 2.10.0 |
+| stop()             | Stop                            | 2.10.0 |
+| goTo(targetIndex)  | Go to target index              | 2.10.0 |
+| prev()             | Go to previous index            | 2.10.0 |
+| next()             | Go to next index                | 2.10.0 |
+
+
+## Design Tokens
+<DesignToken/>

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

@@ -0,0 +1,695 @@
+---
+localeCode: zh-CN
+order: 47
+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: 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 属性控制动画,可选值有 `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 | { 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|
+
+**Method()**
+
+| 方法               | 说明                        | 版本    |
+| ----------------- | --------------------------  | ------ |
+| play()            | 播放                        | 2.10.0 |
+| stop()            | 停止播放                     | 2.10.0 |
+| goTo(targetIndex) | 切换到指定位置                | 2.10.0 |
+| prev()            | 切换到上一位置                | 2.10.0 |
+| next()            | 切换到下一位置                | 2.10.0 |
+
+## 设计变量
+
+<DesignToken/>

+ 1 - 1
content/show/collapse/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 47
+order: 48
 category: Show
 title: Collapse
 subTitle: Collapse

+ 1 - 1
content/show/collapse/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 47
+order: 48
 category: 展示类
 title: Collapse 折叠面板
 icon: doc-accordion

+ 1 - 1
content/show/collapsible/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 48
+order: 49
 category: Show
 title: Collapsible
 subTitle: Collapsible

+ 1 - 1
content/show/collapsible/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 48
+order: 49
 category: 展示类
 title: Collapsible 折叠
 icon: doc-collapsible

+ 1 - 1
content/show/descriptions/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 49
+order: 50
 category: Show
 title: Description
 subTitle: Descriptions

+ 1 - 1
content/show/descriptions/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 49
+order: 50
 category: 展示类
 title: Descriptions 描述列表
 icon: doc-descriptions

+ 1 - 1
content/show/dropdown/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 50
+order: 51
 category: Show
 title: Dropdown
 subTitle: Dropdown

+ 1 - 1
content/show/dropdown/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 50
+order: 51
 category: 展示类
 title: Dropdown 下拉框
 icon: doc-dropdown

+ 1 - 1
content/show/empty/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 51
+order: 52
 category: Show
 title: Empty
 subTitle: Empty

+ 1 - 1
content/show/empty/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 51
+order: 52
 category: 展示类
 title: Empty 空状态
 icon: doc-empty

+ 1 - 1
content/show/list/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 52
+order: 53
 category: Show
 title: List
 subTitle: List

+ 1 - 1
content/show/list/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 52
+order: 53
 category: 展示类
 title: List 列表
 icon: doc-list

+ 1 - 1
content/show/modal/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 53
+order: 54
 category: Show
 title:  Modal
 subTitle: Modal

+ 1 - 1
content/show/modal/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 53
+order: 54
 category: 展示类
 title:  Modal 模态对话框
 icon: doc-modal

+ 1 - 1
content/show/overflowlist/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 54
+order: 55
 category: Show
 title: OverflowList
 subTitle: OverflowList

+ 1 - 1
content/show/overflowlist/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 54
+order: 55
 category: 展示类
 title: OverflowList 折叠列表
 icon: doc-overflowList

+ 1 - 1
content/show/popover/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 55
+order: 56
 category: Show
 title: Popover
 subTitle: Popover

+ 1 - 1
content/show/popover/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 55
+order: 56
 category: 展示类
 title: Popover 气泡卡片
 icon: doc-popover

+ 1 - 1
content/show/scrolllist/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 56
+order: 57
 category: Show
 title:  ScrollList
 subTitle: ScrollList

+ 1 - 1
content/show/scrolllist/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 56
+order: 57
 category: 展示类
 title: ScrollList 滚动列表
 icon: doc-scrolllist

+ 1 - 1
content/show/sidesheet/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 57
+order: 58
 category: Show
 title: SideSheet
 subTitle: SideSheet

+ 1 - 1
content/show/sidesheet/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 57
+order: 58
 category: 展示类
 title: SideSheet 滑动侧边栏
 icon: doc-sidesheet

+ 1 - 1
content/show/table/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 58
+order: 59
 category: Show
 title: Table
 subTitle: Table

+ 1 - 1
content/show/table/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 58
+order: 59
 category: 展示类
 title:  Table 表格
 icon: doc-table

+ 1 - 1
content/show/tag/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 59
+order: 60
 category: Show
 title: Tag
 subTitle: Tag

+ 1 - 1
content/show/tag/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 59
+order: 60
 category: 展示类
 title:  Tag 标签
 icon: doc-tag

+ 1 - 1
content/show/timeline/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 60
+order: 61
 category: Show
 title:  Timeline
 subTitle: Timeline

+ 1 - 1
content/show/timeline/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 60
+order: 61
 category: 展示类
 title: Timeline 时间轴
 icon: doc-timeline

+ 1 - 1
content/show/tooltip/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 61
+order: 62
 category: Show
 title: Tooltip
 subTitle: Tooltip

+ 1 - 1
content/show/tooltip/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 61
+order: 62
 category: 展示类
 title: Tooltip 工具提示
 icon: doc-tooltip

+ 25 - 3
content/start/changelog/index-en-US.md

@@ -16,25 +16,47 @@ Version:Major.Minor.Patch
 
 ---
 
+#### 🎉 2.10.0 (2022-05-07)
+- 【Fix】
+    - Fixed the error that `x-form-id` does not match on the server side and the client side when the Form component is used in `Nextjs`  [#808](https://github.com/DouyinFE/semi-design/issues/808) [@xuerzong](https://github.com/xuerzong)
+    - Fixed the problem that when InputNumber is configured with precision, the input illegal characters will not be blank [#786](https://github.com/DouyinFE/semi-design/issues/786) [@MuxinFeng](https://github.com/MuxinFeng)
+    - Fixed an issue where clicking the arrow switch or the indicator switch did not respond instantly when Carousel autoplayed
+    - Fix InputNumber controlled use and set the minimum value, the problem of not notifyChange when formatting the incoming value into the range  [#812](https://github.com/DouyinFE/semi-design/issues/812)
+    - Fix the problem of ts type checking error when ButtonGroup passes in multiple children [#811](https://github.com/DouyinFE/semi-design/issues/811)
+- 【Docs】
+    - A11y:Switch, Banner added keyboard and focus behavior description
+    - Update Tabs component FAQ
+#### 🎉 2.10.0-beta.0 (2022-4-29)
+- 【New Component】
+    - new component Carousel  [#678](https://github.com/DouyinFE/semi-design/issues/678)
+- 【Fix】
+    - fix cascader's displayProp error when multiple selection
+- 【Feat】
+    - Switch adds A11y keyboard and focus adaptation  [#205](https://github.com/DouyinFE/semi-design/issues/205)
+    - Banner adds A11y keyboard and focus adaptation  [#205](https://github.com/DouyinFE/semi-design/issues/205)
+- 【Chore】
+  - @douyinfe/semi-icons, @douyinfe/semi-illustrations update react version statement in peerDependency: 16/17 -> 16/17/18
+
 #### 🎉 2.9.1 (2022-04-26)
 - 【Fix】
     - Fix the Tooltip flickering when there is a probability that the overlay is opened when there is animation
     - Fix the problem that pm/am could not be set correctly under the TimePicker component use12Hours [#776](https://github.com/DouyinFE/semi-design/issues/776), fix the problem that the TimePicker component could not return to the expected position after selecting the option upwards and clicking clear
-    - Fix the problem that the old value is used internally in the special case of Form Validate
+    - Fix the problem that the old value is used internally in the special case of Form Field validate [#796](https://github.com/DouyinFE/semi-design/issues/796)
 - 【Style】
     - Fix the problem that the border color is not right when Select focus and hover are applied at the same time
 
 #### 🎉 2.9.0 (2022-04-22)
 - 【Fix】
-    - Fix the issue that when TagInput is used in a Form, hitting enter would cause the submit event to be triggered
+    - Fix the issue that when TagInput is used in a Form, hitting enter would cause the submit event to be triggered  [#767](https://github.com/DouyinFE/semi-design/issues/767)
     - Fix the problem that the useless div is left behind after the Modal imperative call
     - Fix the problem that Collapse DOM has useless attributes
     - Remove Form label `user-select:none`, allow user select
     - Fix Cascader clear button keyboard event not responding
 
 #### 🎉 2.9.0-beta.0 (2022-04-18)
-- 【Feat】
+- 【New Component】
     - New Component Divider. [#721](https://github.com/DouyinFE/semi-design/issues/721) [@ZeroCodeLin](https://github.com/ZeroCodeLin)
+- 【Feat】
     - Added support for ReactNode as key value for Descriptions' data prop [#734](https://github.com/DouyinFE/semi-design/issues/734) [@oddguan](https://github.com/oddguan)
 - 【Fix】
     - Fix controlled slider component can still trigger value change by clicking track. [#768](https://github.com/DouyinFE/semi-design/issues/768)

+ 28 - 5
content/start/changelog/index.md

@@ -15,25 +15,48 @@ Semi 版本号遵循**Semver**规范(主版本号-次版本号-修订版本号
 
 ---
 
+
+#### 🎉 2.10.0 (2022-05-07)
+- 【Fix】
+    - 修复Form组件在`Nextjs`中使用时,`x-form-id`在服务端和客户端不匹配的报错问题  [#808](https://github.com/DouyinFE/semi-design/issues/808) [@xuerzong](https://github.com/xuerzong)
+    - 修复 InputNumber 在有 precision 配置时,输入非法字符不会置空的问题 [#786](https://github.com/DouyinFE/semi-design/issues/786) [@MuxinFeng](https://github.com/MuxinFeng)
+    - 修复 Carousel 自动播放时点击箭头切换或指示器切换不能即时响应问题
+    - 修复 InputNumber 受控使用且设置最小值,格式化传入的值到范围内时未 notifyChange 问题  [#812](https://github.com/DouyinFE/semi-design/issues/812)
+    - 修复 ButtonGroup 传入 多个children 时 ts 类型检查报错的问题 [#811](https://github.com/DouyinFE/semi-design/issues/811)
+- 【Docs】
+    - A11y:Switch、Bannner 增加 键盘和焦点 行为说明
+    - 更新 Tabs 组件 FAQ
+#### 🎉 2.10.0-beta.0 (2022-04-29)
+- 【New Component】
+    - 新组件轮播图  [#678](https://github.com/DouyinFE/semi-design/issues/678)
+- 【Fix】
+    - 修复 Cascader 在多选时,设定 displayProp 非value/label时候出错问题
+- 【Feat】
+    - Switch 新增 A11y 键盘和焦点适配  [#205](https://github.com/DouyinFE/semi-design/issues/205)
+    - Banner 新增 A11y 键盘和焦点适配  [#205](https://github.com/DouyinFE/semi-design/issues/205)
+- 【Chore】
+  - @douyinfe/semi-icons、@douyinfe/semi-illustrations 更新 peerDependency中的react版本声明:16/17 -> 16/17/18
+
 #### 🎉 2.9.1 (2022-04-26)
 - 【Fix】
-    - 修复Tooltip在有动画情况下有概率浮层打开时闪烁
-    - 修复TimePicker组件use12Hours下,pm/am无法正确设置问题 [#776](https://github.com/DouyinFE/semi-design/issues/776), 修复TimePicker组件向上选择选项后点击清除无法回到预期位置问题
-    - 修复 Form Validate 特殊情景下内部使用旧值的问题
+    - 修复 Tooltip 在有动画情况下有概率浮层打开时闪烁
+    - 修复 TimePicker 组件 use12Hours 下,pm/am无法正确设置问题 [#776](https://github.com/DouyinFE/semi-design/issues/776), 修复TimePicker组件向上选择选项后点击清除无法回到预期位置问题
+    - 修复 Form Field validate 特殊情景下内部使用旧值的问题 [#796](https://github.com/DouyinFE/semi-design/issues/796)
 - 【Style】
     - 修复 Select focus 和 hover 同时应用,border 颜色不正常的问题
 
 #### 🎉 2.9.0 (2022-04-22)
 - 【Fix】
-    - 修复 TagInput 在 Form 内使用时,敲击回车会导致 submit 事件触发的问题
+    - 修复 TagInput 在 Form 内使用时,敲击回车会导致 submit 事件触发的问题 [#767](https://github.com/DouyinFE/semi-design/issues/767)
     - 修复 Modal 命令式调用后遗留无用 div 的问题
     - 修复 Collapse 将部分 props 透传至 DOM 导致存在无用属性 warning 的问题
     - 去除 Form label `user-select:none` 默认样式,允许用户选中
     - 修复 Cascader 清除按钮键盘事件不响应问题
 
 #### 🎉 2.9.0-beta.0 (2022-04-18)
-- 【Feat】
+- 【New Component】
     - 新增分割线组件 [#721](https://github.com/DouyinFE/semi-design/issues/721) [@ZeroCodeLin](https://github.com/ZeroCodeLin)
+- 【Feat】
     - Description 组件的 data 键值支持传入 ReactNode [#734](https://github.com/DouyinFE/semi-design/issues/734) [@oddguan](https://github.com/oddguan)
 - 【Fix】
     - 修复 Slider 组件受控情况下点击滑轨部分仍然可以触发值变更的问题 [#768](https://github.com/DouyinFE/semi-design/issues/768)

+ 4 - 1
content/start/faq/index-en-US.md

@@ -25,9 +25,12 @@ order: 8
 
 #### Semi's default theme style does not match the positioning of our system. Can i configure another theme?
 
-- Please refer to [Custom theme](/en-US/start/customize-theme). Semi provides **up to 2300+ Design Tokens to allow users to perform in-depth customization**, whether you are a R&D or a designer, you can easily configure the style layer in [Semi DSM](/dsm), and in code, Figma always keep two-way sync. Based on Semi, you can **customize your own Design System at low cost** 
+- Please refer to [Custom theme](/en-US/start/customize-theme). Semi provides **up to 2300+ Design Tokens to allow users to perform in-depth customization**, whether you are a R&D or a designer, you can easily configure the style layer in [Semi DSM](/dsm), and in code, Figma always keep two-way sync. Based on Semi, you can **customize your own Design System at low cost**  Make `Semi Design` to `Any Design`
 - And when using, you only need to specify the theme package name used in webpack.config.js to complete the access (the Semi plugin needs to be connected).
 
+#### TS type check reports an error, indicating that the attribute children does not exist on xxx or that XXX cannot be used as a JSX component
+This is due to the breaking changes of `@types/react` v18. In most cases, two different versions of @types/react will be installed in your project, resulting in no match. Please refer to [Issue 793](https://github.com/DouyinFE/semi-design/issues/793) to lock the version, ensure that only a single version exists
+
 #### Why is defaultValue, default XXX not working?
 
 Property like `defaultValue`, `defaultXXX` will only be consumed once when the component mount. If your `defaultXXX` property is updated asynchronously later, the component will not reconsume the value. If necessary, you should use controlled `value`, controlled `XXX`.

+ 4 - 1
content/start/faq/index.md

@@ -23,7 +23,7 @@ order: 8
 
 #### Semi 的默认的主题风格跟我们系统的定位不符,可以配置另外的主题吗?
 
-- 具体请参考 [定制主题](/zh-CN/start/customize-theme) 。Semi 提供**多达 2300+ Design Token 允许用户进行深度定制**,无论你是研发还是设计师,在[Semi DSM](/dsm) 里可以非常方便地进行样式层配置,并在代码、设计稿始终保持双向同步。基于 Semi 你可以**低成本定制属于你自己的 Design System**
+- 具体请参考 [定制主题](/zh-CN/start/customize-theme) 。Semi 提供**多达 2300+ Design Token 允许用户进行深度定制**,无论你是研发还是设计师,在[Semi DSM](/dsm) 里可以非常方便地进行样式层配置,并在代码、设计稿始终保持双向同步。基于 Semi 你可以**低成本定制属于你自己的 Design System** 将 `Semi Design` 定制为 `Any Design`
 - 并且在使用时,你也只需要在 webpack.config.js 里指定使用的主题包名即可完成接入(需接入 Semi 插件)。
 
 #### Semi 是否支持 Tree Shaking
@@ -35,6 +35,9 @@ order: 8
 
 Semi 组件中,所有的 defaultValue、defaultXXX 传参只会在组件被 mounted 时进行消费(即仅消费一次)。如果你的 defaultXXX 属性是后期进行异步更新的,组件不会重新进行消费该值。如有需要,你应该使用受控的 value,受控的 xxx。或者直接通过传入一个不一样的`key`值,强制 React 重新挂载该组件。
 
+#### TS 类型检查报错,提示 xxx 上不存在属性 children 或 XXX 不能用作 JSX 组件
+这是由于 `@types/react` v18 进行了 breaking change,大部分情况下你的项目里会安装了两个不同版本的 @types/react,导致无法匹配。请参考 [Issue 793](https://github.com/DouyinFE/semi-design/issues/793) 锁定版本确保只有单个版本存在
+
 #### 安装新版本 Semi 后,提示 can't resolve date-fns/esm/\_libs/cloneObject.js 或其他有 date-fns 相关的依赖错误
 
 检查下项目中的 package-lock.json,是否有其他包依赖了 date-fns(大概率是 1.x 的),导致 semi 依赖声明的 date-fns 2.x 没有被安装上。手动 install date-fns,确保是 2.x 版本的即可 `npm install date-fns date-fns-tz`

+ 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');
+    });
+
+});

+ 16 - 0
cypress/integration/inputNumber.spec.js

@@ -0,0 +1,16 @@
+// inputNumber.spec.js created with Cypress
+//
+// Start writing your Cypress tests below!
+// If you're unfamiliar with how Cypress works,
+// check out the link below and learn how to write your first test:
+// https://on.cypress.io/writing-first-test
+
+describe('inputNumber', () => {
+    it('fix precision delete bug', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=inputnumber--fix-precision-786&viewMode=story');
+        cy.get('[data-cy=fix-precision-786] .semi-input').click().clear();
+        cy.get('[data-cy=fix-precision-786] .semi-input').type('aaa');
+        cy.get('[data-cy=fix-precision-786] .semi-input').blur();
+        cy.get('[data-cy=fix-precision-786] .semi-input').should('have.value', '');
+    })
+});

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.9.1"
+    "version": "2.10.0"
 }

+ 3 - 3
packages/semi-animation-react/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-react",
-  "version": "2.9.1",
+  "version": "2.10.0",
   "description": "motion library for semi-ui-react",
   "keywords": [
     "motion",
@@ -26,8 +26,8 @@
   },
   "dependencies": {
     "@babel/runtime-corejs3": "^7.15.4",
-    "@douyinfe/semi-animation": "2.9.1",
-    "@douyinfe/semi-animation-styled": "2.9.1",
+    "@douyinfe/semi-animation": "2.10.0",
+    "@douyinfe/semi-animation-styled": "2.10.0",
     "classnames": "^2.2.6"
   },
   "peerDependencies": {

+ 1 - 1
packages/semi-animation-styled/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-styled",
-  "version": "2.9.1",
+  "version": "2.10.0",
   "description": "semi styled animation",
   "keywords": [
     "semi",

+ 1 - 1
packages/semi-animation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation",
-  "version": "2.9.1",
+  "version": "2.10.0",
   "description": "animation base library for semi-ui",
   "keywords": [
     "animation",

+ 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 };

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

@@ -0,0 +1,164 @@
+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> {
+
+    constructor(adapter: CarouselAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    _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

+ 4 - 0
packages/semi-foundation/form/foundation.ts

@@ -75,6 +75,10 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
         this.scrollToField = this.scrollToField.bind(this);
     }
 
+    init() {
+        this._adapter.initFormId();
+    }
+
     getField(field: string): FieldStaff | undefined {
         const targetField = this.fields.get(field);
         return targetField;

+ 1 - 0
packages/semi-foundation/form/interface.ts

@@ -20,6 +20,7 @@ export interface BaseFormAdapter<P = Record<string, any>, S = Record<string, any
     getFormProps: (keys: undefined | string | Array<string>) => any;
     getAllErrorDOM: () => NodeList;
     getFieldDOM: (field: string) => Node;
+    initFormId: () => void;
 }
 
 export interface FormState<T extends Record<string, any> = any> {

+ 1 - 1
packages/semi-foundation/inputNumber/foundation.ts

@@ -444,7 +444,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
 
     _adjustPrec(num: string | number) {
         const precision = this.getProp('precision');
-        if (typeof precision === 'number' && num !== '') {
+        if (typeof precision === 'number' && num !== '' && num !== null && !Number.isNaN(Number(num))) {
             num = Number(num).toFixed(precision);
         }
         return toString(num);

+ 2 - 2
packages/semi-foundation/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.9.1",
+    "version": "2.10.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
@@ -8,7 +8,7 @@
     },
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
-        "@douyinfe/semi-animation": "2.9.1",
+        "@douyinfe/semi-animation": "2.10.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "date-fns": "^2.9.0",

+ 1 - 0
packages/semi-foundation/switch/constants.ts

@@ -2,6 +2,7 @@ import { BASE_CLASS_PREFIX } from '../base/constants';
 
 const cssClasses = {
     PREFIX: `${BASE_CLASS_PREFIX}-switch`,
+    FOCUS: `${BASE_CLASS_PREFIX}-switch-focus`,
     LARGE: `${BASE_CLASS_PREFIX}-switch-large`,
     SMALL: `${BASE_CLASS_PREFIX}-switch-small`,
     CHECKED: `${BASE_CLASS_PREFIX}-switch-checked`,

+ 16 - 0
packages/semi-foundation/switch/foundation.ts

@@ -3,6 +3,7 @@ import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 export interface SwitchAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     setNativeControlChecked: (nativeControlChecked: boolean | undefined) => void;
     setNativeControlDisabled: (nativeControlDisabled: boolean | undefined) => void;
+    setFocusVisible: (focusVisible: boolean) => void;
     notifyChange: (checked: boolean, e: any) => void;
 }
 
@@ -37,6 +38,21 @@ export default class SwitchFoundation<P = Record<string, any>, S = Record<string
         }
     }
 
+    handleFocusVisible = (event: any) => {
+        const { target } = event;
+        try {
+            if (target.matches(':focus-visible')) {
+                this._adapter.setFocusVisible(true);
+            }
+        } catch (error){
+            console.warn('The current browser does not support the focus-visible');
+        }
+    }
+
+    handleBlur = () => {
+        this._adapter.setFocusVisible(false);
+    }
+
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     destroy(): void {}
 }

+ 4 - 0
packages/semi-foundation/switch/switch.scss

@@ -28,6 +28,10 @@ $module: #{$prefix}-switch;
         }
     }
 
+    &-focus {
+        outline: $width-switch-outline solid $color-switch_primary-outline-focus;
+    }
+
     &-checked {
         background-color: $color-switch_checked-bg-default;
 

+ 2 - 0
packages/semi-foundation/switch/variables.scss

@@ -37,6 +37,7 @@ $color-switch_unchecked-text-default: var(--semi-color-text-2); // 关闭态开
 $color-switch_loading_spin-default: var(--semi-color-white); // 加载态开关loading图标颜色
 $color-switch_spin_checked-bg-default: var(--semi-color-success-hover); // 已开启开关加载态loading背景颜色
 $color-switch_spin_unchecked-bg-default: var(--semi-color-fill-1); // 已关闭开关加载态loading背景颜色
+$color-switch_primary-outline-focus: var(--semi-color-primary-light-active); // 开关轮廓 - 聚焦
 
 // Width/Height
 $width-switch: 40px; // 开关宽度
@@ -57,6 +58,7 @@ $width-switch_checked_unchecked_text: 26px; // 开关按钮文字宽度
 $width-switch_spin-small: 10px; // 小尺寸开关加载 spin 宽度
 $width-switch_spin-default: 18px; // 默认尺寸开关加载 spin 宽度
 $width-switch_spin-large: 28px; // 大尺寸开关加载 spin 宽度
+$width-switch-outline: 2px; // 开关轮廓宽度
 
 
 // Spacing

+ 2 - 2
packages/semi-icons/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons",
-  "version": "2.9.1",
+  "version": "2.10.0",
   "description": "semi icons",
   "keywords": [
     "semi",
@@ -38,7 +38,7 @@
     "@babel/plugin-transform-runtime": "^7.15.8",
     "@babel/preset-env": "^7.15.8",
     "@babel/preset-react": "^7.14.5",
-    "@douyinfe/semi-webpack-plugin": "2.9.1",
+    "@douyinfe/semi-webpack-plugin": "2.10.0",
     "babel-loader": "^8.2.2",
     "css-loader": "4.3.0",
     "del": "^6.0.0",

+ 1 - 1
packages/semi-illustrations/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-illustrations",
-  "version": "2.9.1",
+  "version": "2.10.0",
   "description": "semi illustrations",
   "keywords": [
     "semi",

+ 2 - 2
packages/semi-next/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.9.1",
+    "version": "2.10.0",
     "description": "Plugin that support Semi Design in Next.js",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",
@@ -23,7 +23,7 @@
         "typescript": "^4"
     },
     "dependencies": {
-        "@douyinfe/semi-webpack-plugin": "2.9.1"
+        "@douyinfe/semi-webpack-plugin": "2.10.0"
     },
     "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 1 - 1
packages/semi-scss-compile/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-scss-compile",
-  "version": "2.9.1",
+  "version": "2.10.0",
   "description": "compile semi scss to css",
   "author": "[email protected]",
   "license": "MIT",

+ 1 - 1
packages/semi-theme-default/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-theme-default",
-    "version": "2.9.1",
+    "version": "2.10.0",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 62 - 1
packages/semi-ui/banner/_story/banner.stories.js

@@ -1,7 +1,9 @@
-import React from 'react';
+import React, { useState } from 'react';
 
 import Banner from '../index';
 import Button from '@douyinfe/semi-ui/button/index';
+import { Layout } from '@douyinfe/semi-ui';
+
 
 export default {
   title: 'Banner',
@@ -43,3 +45,62 @@ export const InContainerAndBordered = () => (
     <Button onClick={e => e.stopPropagation()}>test</Button>
   </Banner>
 );
+
+export const ShowAndHideBanner = () => {
+    const [visible, setVisible] = useState(false);
+    const changeVisible = () => {
+        setVisible(!visible);
+    };
+    const { Header, Footer, Content } = Layout;
+    const banner = (
+        <Banner 
+            onClose={changeVisible}
+            description="A pre-released version is available"
+        />
+    );
+
+    return (
+        <>
+            <Layout>
+                <Header>Header</Header>
+                {visible? banner : null}
+                <Content>Content</Content>
+                <Footer>Footer</Footer>
+            </Layout>
+            <Button
+                onClick={changeVisible}
+                style={{
+                    display: 'block',
+                    width: '120px',
+                    margin: '0 auto'
+                }}
+            >
+                { visible ? 'Hide Banner' : 'Show Banner' }
+            </Button>
+        </>
+    );
+};
+
+export const MultiTypeBanner = () => (
+    <>
+        <Banner 
+            type="info"
+            description="A pre-released version is available."
+        />
+        <br/>
+        <Banner 
+            type="warning"
+            description="This version of the document is going to expire after 4 days."
+        />
+        <br/>
+        <Banner 
+            type="danger"
+            description="This document was deprecated since Jan 1, 2019."
+        />
+        <br/>
+        <Banner 
+            type="success"
+            description="You are viewing the latest version of this document."
+        />
+    </>
+);

+ 5 - 5
packages/semi-ui/banner/index.tsx

@@ -109,7 +109,7 @@ export default class Banner extends BaseComponent<BannerProps, BannerState> {
             <Button
                 className={`${prefixCls}-close`}
                 onClick={this.remove}
-                icon={closeIcon || <IconClose />}
+                icon={closeIcon || <IconClose aria-hidden={true}/>}
                 theme="borderless"
                 size="small"
                 type="tertiary"
@@ -122,10 +122,10 @@ export default class Banner extends BaseComponent<BannerProps, BannerState> {
     renderIcon() {
         const { type, icon } = this.props;
         const iconMap = {
-            warning: <IconAlertTriangle size="large" />,
-            success: <IconTickCircle size="large" />,
-            info: <IconInfoCircle size="large" />,
-            danger: <IconAlertCircle size="large" />
+            warning: <IconAlertTriangle size="large" aria-label='warning'/>,
+            success: <IconTickCircle size="large" aria-label='success'/>,
+            info: <IconInfoCircle size="large" aria-label='info'/>,
+            danger: <IconAlertCircle size="large" aria-label='danger'/>
         };
         let iconType: React.ReactNode = iconMap[type];
         const iconCls = cls({

+ 2 - 2
packages/semi-ui/button/buttonGroup.tsx

@@ -15,7 +15,7 @@ export interface ButtonGroupProps extends BaseProps {
     size?: Size;
     theme?: Theme;
     className?: string;
-    children?: React.ReactChild;
+    children?: React.ReactNode;
     'aria-label'?: React.AriaAttributes['aria-label'];
 }
 
@@ -44,7 +44,7 @@ export default class ButtonGroup extends BaseComponent<ButtonGroupProps> {
         const cls = classNames(`${prefixCls}-group`, className);
 
         if (children) {
-            inner = ((Array.isArray(children) ? children : [children])).map((itm: React.ReactChild, index) => (
+            inner = ((Array.isArray(children) ? children : [children])).map((itm: React.ReactNode, index) => (
                 isValidElement(itm)
                     ? cloneElement(itm, { disabled, size, type, ...itm.props, ...rest, key: index })
                     : itm

+ 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 } 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 } = 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={prev}
+                    {...get(this.props, 'arrowProps.leftArrow.props')}
+                >
+                    {this.renderLeftIcon()}
+                </div>
+                <div 
+                    // role='button'
+                    // tabIndex={0} 
+                    className={rightClassNames} 
+                    onClick={next}
+                    {...get(this.props, 'arrowProps.rightArrow.props')}
+                >
+                    {this.renderRightIcon()}
+                </div>
+            </div>
+        );
+    }
+
+}
+
+export default CarouselArrow;

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

@@ -0,0 +1,83 @@
+/* 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";
+
+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

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

@@ -0,0 +1,292 @@
+/* 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.onIndicatorChange(activeIndex);
+    };
+
+    getChildren  = (): (ReactChild | ReactFragment | ReactPortal)[] => {
+        const { children: originChildren } = this.props;
+        return Children.toArray(originChildren).filter(child=>{
+            return React.isValidElement(child);
+        });
+    }
+
+    getValidIndex = (activeIndex: number): number => {
+        return this.foundation.getValidIndex(activeIndex);
+    };
+
+
+    renderChildren = () => {
+        const { speed, animation } = this.props;
+        const { activeIndex, children, preIndex, isInit } = this.state;
+
+        return (
+            <>
+                {children.map((child: any, index: number) => {
+                    const isCurrent = index === activeIndex;
+                    const isPrev = index === this.getValidIndex(activeIndex - 1);
+                    const isNext = index === this.getValidIndex(activeIndex + 1); 
+
+                    const animateStyle = {
+                        transitionTimingFunction: 'ease',
+                        transitionDuration: `${speed}ms`,
+                        animationTimingFunction: 'ease',
+                        animationDuration: `${speed}ms`,
+                    };
+
+                    return React.cloneElement(child, {
+                        style: {
+                            ...child.props.style,
+                            ...animateStyle,
+                        },
+                        className: cls(child.props.className, {
+                            [`${cssClasses.CAROUSEL_CONTENT}-item-prev`]: isPrev,
+                            [`${cssClasses.CAROUSEL_CONTENT}-item-next`]: isNext,
+                            [`${cssClasses.CAROUSEL_CONTENT}-item-current`]: isCurrent,
+                            [`${cssClasses.CAROUSEL_CONTENT}-item`]: true,
+                            [`${cssClasses.CAROUSEL_CONTENT}-item-active`]: isCurrent,
+                            [`${cssClasses.CAROUSEL_CONTENT}-item-slide-in`]:animation === 'slide' && !isInit && isCurrent,
+                            [`${cssClasses.CAROUSEL_CONTENT}-item-slide-out`]:animation === 'slide'  && !isInit && index === preIndex,
+                        })
+                    });
+                })}
+            </>
+        );
+    }
+
+    renderIndicator = () => {
+        const { children, activeIndex } = this.state;
+        const { showIndicator, indicatorType, theme, indicatorPosition, indicatorSize, trigger } = this.props;
+
+        const carouselIndicatorCls = cls({
+            [cssClasses.CAROUSEL_INDICATOR]: true
+        });
+
+        if (showIndicator && children.length > 1){
+            return (
+                <div className={carouselIndicatorCls}>
+                    <CarouselIndicator
+                        type={indicatorType}
+                        total={children.length}
+                        activeIndex={activeIndex}
+                        position={indicatorPosition}
+                        trigger={trigger}
+                        size={indicatorSize}
+                        theme={theme}
+                        onIndicatorChange={this.onIndicatorChange}
+                    />
+                </div>
+            );
+        }
+        return null;
+    }
+
+    renderArrow = () => {
+        const { children } = this.state;
+        const { showArrow, arrowType, theme, arrowProps } = this.props;
+
+        if (showArrow && children.length > 1){
+            return (
+                <CarouselArrow 
+                    type={arrowType} 
+                    theme={theme} 
+                    prev={this.prev} 
+                    next={this.next}
+                    arrowProps={arrowProps}
+                />
+            );
+        }
+        return null;
+    };
+
+
+    render(): ReactNode {
+        const { animation, className, style, slideDirection } = this.props;
+        const { isReverse } = this.state;
+
+        const carouselWrapperCls = cls(className, {
+            [cssClasses.CAROUSEL]: true
+        });
+
+        return (
+            <div 
+                // role='listbox'
+                // tabIndex={0}
+                className={carouselWrapperCls} 
+                style={style} 
+                onMouseEnter={debounce(this.handleMouseEnter, 400)}
+                onMouseLeave={debounce(this.handleMouseLeave, 400)}
+                // onMouseEnter={this.handleMouseEnter}
+                // onMouseLeave={this.handleMouseLeave}
+                // onKeyDown={e => this.foundation.handleKeyDown(e)}
+            >
+                <div 
+                    className={cls([`${cssClasses.CAROUSEL_CONTENT}-${animation}`], {
+                        [`${cssClasses.CAROUSEL_CONTENT}`]: true,
+                        [`${cssClasses.CAROUSEL_CONTENT}-reverse`]: slideDirection === 'left' ? isReverse : !isReverse,
+                    })}
+                >
+                    {this.renderChildren()}
+                </div>
+                {this.renderIndicator()}
+                {this.renderArrow()}
+            </div>
+        );
+    }
+}
+
+export default Carousel;

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

@@ -0,0 +1,63 @@
+import React, { ReactNode } from "react";
+import { strings } from '@douyinfe/semi-foundation/carousel/constants';
+
+export interface CarouselMethod {
+    next?: () => void;
+    prev?: () => void;
+    goTo?: (tagetIndex: number) => 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;
+}
+
+export interface ArrowButton {
+    props?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
+    children?: React.ReactNode;
+}
+export interface ArrowProps {
+    leftArrow?: ArrowButton;
+    rightArrow?: ArrowButton;
+}

+ 10 - 1
packages/semi-ui/form/baseForm.tsx

@@ -122,7 +122,7 @@ class Form extends BaseComponent<BaseFormProps, BaseFormState> {
     constructor(props: BaseFormProps) {
         super(props);
         this.state = {
-            formId: getUuidv4(),
+            formId: '',
         };
         warning(
             Boolean(props.component && props.render),
@@ -145,6 +145,10 @@ class Form extends BaseComponent<BaseFormProps, BaseFormState> {
         }
     }
 
+    componentDidMount() {
+        this.foundation.init();
+    }
+
     componentWillUnmount() {
         this.foundation.destroy();
         this.foundation = null;
@@ -173,6 +177,11 @@ class Form extends BaseComponent<BaseFormProps, BaseFormState> {
             notifyReset: () => {
                 this.props.onReset();
             },
+            initFormId: () => {
+                this.setState({
+                    formId: getUuidv4()
+                });
+            },
             getInitValues: () => this.props.initValues,
             getFormProps: (keys: undefined | string | Array<string>) => {
                 if (typeof keys === 'undefined') {

+ 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 */

+ 14 - 1
packages/semi-ui/inputNumber/_story/inputNumber.stories.js

@@ -698,4 +698,17 @@ export const FixMinValue = () => {
       </div>
   );
 }
-FixMinValue.storyName = 'fix min value';
+FixMinValue.storyName = 'fix min value';
+
+/**
+ * fix InputNumber precision 删除后,输入非法字符显示 0.00
+ * https://github.com/DouyinFE/semi-design/issues/786
+ */
+export const FixPrecision786 = () => {
+  return (
+    <div data-cy="fix-precision-786">
+        <InputNumber defaultValue={10.00} precision={2} />
+    </div>
+  );
+}
+FixPrecision786.storyName = 'fix precision 删除后输入非法值会显示 0.00';

+ 8 - 8
packages/semi-ui/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.9.1",
+    "version": "2.10.0",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -14,12 +14,12 @@
     },
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
-        "@douyinfe/semi-animation": "2.9.1",
-        "@douyinfe/semi-animation-react": "2.9.1",
-        "@douyinfe/semi-foundation": "2.9.1",
-        "@douyinfe/semi-icons": "2.9.1",
-        "@douyinfe/semi-illustrations": "2.9.1",
-        "@douyinfe/semi-theme-default": "2.9.1",
+        "@douyinfe/semi-animation": "2.10.0",
+        "@douyinfe/semi-animation-react": "2.10.0",
+        "@douyinfe/semi-foundation": "2.10.0",
+        "@douyinfe/semi-icons": "2.10.0",
+        "@douyinfe/semi-illustrations": "2.10.0",
+        "@douyinfe/semi-theme-default": "2.10.0",
         "@types/react-window": "^1.8.2",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
@@ -75,7 +75,7 @@
         "@babel/plugin-transform-runtime": "^7.15.8",
         "@babel/preset-env": "^7.15.8",
         "@babel/preset-react": "^7.14.5",
-        "@douyinfe/semi-scss-compile": "2.9.1",
+        "@douyinfe/semi-scss-compile": "2.10.0",
         "@storybook/addon-knobs": "^6.3.1",
         "@types/lodash": "^4.14.176",
         "babel-loader": "^8.2.2",

+ 20 - 3
packages/semi-ui/switch/index.tsx

@@ -1,4 +1,4 @@
-/* eslint-disable max-len, jsx-a11y/role-supports-aria-props */
+/* eslint-disable max-len */
 import React from 'react';
 import cls from 'classnames';
 import PropTypes from 'prop-types';
@@ -33,6 +33,7 @@ export interface SwitchProps {
 export interface SwitchState {
     nativeControlChecked: boolean;
     nativeControlDisabled: boolean;
+    focusVisible: boolean;
 }
 
 class Switch extends BaseComponent<SwitchProps, SwitchState> {
@@ -74,6 +75,7 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
         this.state = {
             nativeControlChecked: false,
             nativeControlDisabled: false,
+            focusVisible: false
         };
         this.switchRef = React.createRef();
         this.foundation = new SwitchFoudation(this.adapter);
@@ -105,14 +107,25 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
             setNativeControlDisabled: (nativeControlDisabled: boolean): void => {
                 this.setState({ nativeControlDisabled });
             },
+            setFocusVisible: (focusVisible: boolean): void => {
+                this.setState({ focusVisible });
+            },
             notifyChange: (checked: boolean, e: React.ChangeEvent<HTMLInputElement>): void => {
                 this.props.onChange(checked, e);
             },
         };
     }
 
+    handleFocusVisible = (event: React.FocusEvent) => {
+        this.foundation.handleFocusVisible(event);
+    }
+
+    handleBlur = (event: React.FocusEvent) => {
+        this.foundation.handleBlur();
+    }
+
     render() {
-        const { nativeControlChecked, nativeControlDisabled } = this.state;
+        const { nativeControlChecked, nativeControlDisabled, focusVisible } = this.state;
         const { className, style, onMouseEnter, onMouseLeave, size, checkedText, uncheckedText, loading, id } = this.props;
         const wrapperCls = cls(className, {
             [cssClasses.PREFIX]: true,
@@ -121,10 +134,10 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
             [cssClasses.LARGE]: size === 'large',
             [cssClasses.SMALL]: size === 'small',
             [cssClasses.LOADING]: loading,
+            [cssClasses.FOCUS]: focusVisible,
         });
         const switchProps = {
             type: 'checkbox',
-            role: 'switch',
             className: cssClasses.NATIVE_CONTROL,
             disabled: nativeControlDisabled || loading,
             checked: nativeControlChecked || false,
@@ -157,13 +170,17 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
                     {...switchProps}
                     ref={this.switchRef}
                     id={id}
+                    role='switch'
                     aria-checked={nativeControlChecked}
                     aria-invalid={this.props['aria-invalid']}
                     aria-errormessage={this.props['aria-errormessage']}
                     aria-label={this.props['aria-label']}
                     aria-labelledby={this.props['aria-labelledby']}
                     aria-describedby={this.props["aria-describedby"]}
+                    aria-disabled={this.props['disabled']}
                     onChange={e => this.foundation.handleChange(e.target.checked, e)}
+                    onFocus={e => this.handleFocusVisible(e)}
+                    onBlur={e => this.handleBlur(e)}
                 />
             </div>
         );

+ 1 - 1
packages/semi-webpack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-webpack-plugin",
-    "version": "2.9.1",
+    "version": "2.10.0",
     "description": "",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini