فهرست منبع

chore: merge main

林艳 1 سال پیش
والد
کامیت
2f8866de81

+ 6 - 5
.github/workflows/cypress.yml

@@ -23,8 +23,8 @@ jobs:
     install:
         runs-on: ubuntu-latest
         container:
-            # cypress-docker-images/browsers at master · cypress-io/cypress-docker-images
-            image: cypress/browsers:node-20.14.0-chrome-125.0.6422.141-1-ff-126.0.1-edge-125.0.2535.85-1
+            # https://github.com/cypress-io/cypress-docker-images/tree/master/browsers
+            image: cypress/browsers:node-20.17.0-chrome-128.0.6613.113-1-ff-129.0.2-edge-128.0.2739.42-1
         if: ${{ github.repository_owner == 'DouyinFE' }}
         steps:
             - name: Checkout
@@ -33,6 +33,7 @@ jobs:
               run: 
                   npm i -g lerna@^6
                   corepack enable
+              run: npm i -g lerna@^6
             - name: Build storybook
               run: |
                   yarn bootstrap
@@ -44,8 +45,8 @@ jobs:
                   name: storybook-static
                   if-no-files-found: error
                   path: storybook
-            - name: Reclaim cache directory
-              run: chown -R 1001:1001 /github/home/.cache && echo "pwn dat cache"
+            # - name: Reclaim cache directory
+            #   run: chown -R 1001:1001 /github/home/.cache && echo "pwn dat cache"
             - name: Cypress install
               uses: cypress-io/github-action@v6
               with:
@@ -54,7 +55,7 @@ jobs:
     chrome-tests:
         runs-on: ubuntu-latest
         container:
-            image: cypress/browsers:node-20.14.0-chrome-125.0.6422.141-1-ff-126.0.1-edge-125.0.2535.85-1
+            image: cypress/browsers:node-20.17.0-chrome-128.0.6613.113-1-ff-129.0.2-edge-128.0.2739.42-1
             options: --user 1001
         needs: install
         steps:

+ 45 - 4
content/navigation/tabs/index-en-US.md

@@ -18,7 +18,7 @@ import { Tabs, TabPane } from '@douyinfe/semi-ui';
 
 ### Basic Usage
 
-Tbs supports three types of styles: `line`, `button`, and `card`. By default, the first tab is selected.
+Tbs supports three types of styles: `line`, `button`, `card`, and `slash`. By default, the first tab is selected.
 
 Tabs supports two declare ways, and the rendering process of the two is different:
 
@@ -168,6 +168,44 @@ class TabDemo extends React.Component {
 }
 ```
 
+```jsx live=true
+import React from 'react';
+import { Tabs } from '@douyinfe/semi-ui';
+
+class TabDemo extends React.Component {
+    constructor() {
+        super();
+        this.state = { key: '1' };
+        this.onTabClick = this.onTabClick.bind(this);
+    }
+
+    onTabClick(key, type) {
+        this.setState({ [type]: key });
+    }
+
+    render() {
+        // eslint-disable-next-line react/jsx-key
+        const contentList = [<div>Document</div>, <div>Quick Start</div>, <div>Help</div>];
+        const tabList = [
+            { tab: 'Document', itemKey: '1' },
+            { tab: 'Quick Start', itemKey: '2' },
+            { tab: 'Help', itemKey: '3' },
+        ];
+        return (
+            <Tabs
+                type="slash"
+                tabList={tabList}
+                onChange={key => {
+                    this.onTabClick(key, 'key');
+                }}
+            >
+                {contentList[this.state.key - 1]}
+            </Tabs>
+        );
+    }
+}
+```
+
 ### With Icon
 
 ```jsx live=true
@@ -274,7 +312,7 @@ function Demo() {
 
 ### Vertical mode
 
-Support two positions: `tabPosition='left|top'`
+When `type` is `line`, `card`, or `button`, horizontal and vertical modes are supported, `tabPosition='left|top'`,default is top. When `type` is `slash`, only horizontal mode is supported.
 
 ```jsx live=true
 import React from 'react';
@@ -381,7 +419,9 @@ class App extends React.Component {
 
 **Modify the scrolling rendering Arrow**
 
-`renderArrow` modifies the Arrow, with the input parameters being the overflowed items and position
+`renderArrow` modifies the Arrow, with the input parameters being the overflowed items, position, click function, and defaultNode.
+ 
+**Attention**: The first three parameters of renderArrow are supported since 2.61.0,while defaultNode parameter is supported since 2.66.0.
 
 ```jsx live=true dir="column"
 import React from 'react';
@@ -701,6 +741,7 @@ class App extends React.Component {
 | activeKey | The itemKey value of the currently active tab page | string | None |
 | className | class name | string | None |
 | collapsible | collapsed Tabs, **>=1.1.0** | boolean | false |
+| dropdownProps | In collapsible mode, It is used to transparently transmit parameters to the Dropdown component of the drop-down menu, support since 2.66.0 | DropDownProps | { start: DropdownProps, end: DropdownProps } |
 | visibleTabsStyle | Overall scrolling area style **>=2.61.0** | style: CSSProperties | None |
 | contentStyle | The outer style object of the content area | CSSProperties | None |
 | defaultActiveKey | Initialize the key value of the selected tab page | string | '1' |
@@ -708,7 +749,7 @@ class App extends React.Component {
 | lazyRender | Lazy rendering, only when the panel is activated will it be rendered in the DOM tree, **>=1.0.0** | boolean | false |
 | more | Render a portion of the Tab into a drop-down menu ** >= 2.59.0** | number \| {count:number,render:()=>ReactNode,dropdownProps:DropDownProps} | - |
 | renderTabBar | Used for secondary packaging tab bar | (tabBarProps: object, defaultTabBar: React.ComponentType) => ReactNode | None |
-| renderArrow | Customize how overflow items indicator are rendered externally. By default, the overflow items are expanded when the arrow button is hovered. **>=2.61.0** | (items: OverflowItem[],pos:"start"\|"end", handleArrowClick:()=>void)=> ReactNode | None |
+| renderArrow | Customize how overflow items indicator are rendered externally. By default, the overflow items are expanded when the arrow button is hovered. The first three parameters of renderArrow are supported since **>=2.61.0**, defaultNode is supported since **>=2.66.0** | (items: OverflowItem[],pos:"start"\|"end", handleArrowClick:()=>void, defaultNode: ReactNode)=> ReactNode | None |
 | preventScroll | Indicates whether the browser should scroll the document to display the newly focused element, acting on the focus method inside the component, excluding the component passed in by the user | boolean |
 | showRestInDropdown | Whether to display the collapsed Tab in the drop-down menu (only effective when collapsible is true) **>= 2.61.0** | boolean | true |
 | size | Size, providing three types of `large`, `medium`, and `small`, **>=1.11.0, currently only supports linear Tabs** | string | `large` |

+ 47 - 6
content/navigation/tabs/index.md

@@ -17,7 +17,7 @@ import { Tabs, TabPane } from '@douyinfe/semi-ui';
 
 ### 基本用法
 
-标签栏支持三种样式的显示:线条式,按钮式,卡片式。默认选中第一项。  
+标签栏支持三种样式的显示:线条式,按钮式,卡片式,斜线式。默认选中第一项。  
 标签页支持两种传入方式,两者渲染流程上有所区别:
 
 -   通过 `tabList` 传入标签页对象的数组,当使用 `tabList` 时每次只渲染当前传入的节点
@@ -153,6 +153,44 @@ class TabDemo extends React.Component {
 }
 ```
 
+```jsx live=true
+import React from 'react';
+import { Tabs } from '@douyinfe/semi-ui';
+
+class TabDemo extends React.Component {
+    constructor() {
+        super();
+        this.state = { key: '1' };
+        this.onTabClick = this.onTabClick.bind(this);
+    }
+
+    onTabClick(key, type) {
+        this.setState({ [type]: key });
+    }
+
+    render() {
+        // eslint-disable-next-line react/jsx-key
+        const contentList = [<div>文档</div>, <div>快速起步</div>, <div>帮助</div>];
+        const tabList = [
+            { tab: '文档', itemKey: '1' },
+            { tab: '快速起步', itemKey: '2' },
+            { tab: '帮助', itemKey: '3' },
+        ];
+        return (
+            <Tabs
+                type="slash"
+                tabList={tabList}
+                onChange={key => {
+                    this.onTabClick(key, 'key');
+                }}
+            >
+                {contentList[this.state.key - 1]}
+            </Tabs>
+        );
+    }
+}
+```
+
 ### 带图标的
 
 有图标的标签栏。
@@ -257,7 +295,7 @@ function Demo() {
 
 ### 垂直的标签栏
 
-支持水平和垂直两种模式, `tabPosition='left|top'`
+`type` 为 `line`, `card`, `button` 支持水平和垂直两种模式,`tabPosition='left|top'`, 默认为 `top`。`type` 为 `slash` 仅支持水平模式,无需设置。
 
 ```jsx live=true
 import React from 'react';
@@ -404,7 +442,9 @@ class App extends React.Component {
 
 **自定义滚动箭头渲染**
 
-通过 renderArrow 修改滚动折叠模式下,左右切换箭头的渲染,入参为溢出的 items 和 位置
+通过 renderArrow 修改滚动折叠模式下,左右切换箭头的渲染,入参为溢出的 items 和 位置, 点击处理函数,以及 defaultNode。
+
+**注**:renderArrow 的前三个参数自 2.61.0 支持,defaultNode 自 2.66.0 支持。
 
 ```jsx live=true dir="column"
 import React from 'react';
@@ -412,7 +452,7 @@ import { Tabs, TabPane, Dropdown } from '@douyinfe/semi-ui';
 
 () => {
     const [activeKey, setActiveKey] = useState('Tab-0');
-    const renderArrow = (items, pos, handleArrowClick) => {
+    const renderArrow = (items, pos, handleArrowClick, defaultNode) => {
         const style = {
             width: 32,
             height: 32,
@@ -718,6 +758,7 @@ class App extends React.Component {
 | arrowPosition | 折叠模式下,左右切换箭头渲染位置 **>=2.61.0** | "start" "end" "both" | 无 |
 | className | 类名 | string | 无 |
 | collapsible | 折叠的 Tabs,**>=1.1.0** | boolean | false |
+| dropdownProps | 用于在折叠模式下透传参数到下拉菜单的 Dropdown 组件 | { start: DropdownProps, end: DropdownProps } | 无 |
 | visibleTabsStyle | 整体滚动区域 Style **>=2.61.0** | style: CSSProperties | 无 |
 | contentStyle | 内容区域外层样式对象 | CSSProperties | 无 |
 | defaultActiveKey | 初始化选中的 tab 页的 key 值 | string | '1' |
@@ -725,7 +766,7 @@ class App extends React.Component {
 | lazyRender | 懒渲染,仅当面板激活过才被渲染在 DOM 树中  | boolean | false |
 | more | 将一部分 Tab 渲染到下拉菜单中 ** >= 2.59.0** | number \| {count:number,render:()=>ReactNode,dropdownProps:DropDownProps} | - |
 | renderTabBar | 用于二次封装标签栏 | (tabBarProps: object, defaultTabBar: React.ComponentType) => ReactNode | 无 |
-| renderArrow | 折叠滚动模式下,自定义左右切换箭头如何渲染,默认为箭头按钮 hover 时展开溢出项 **>=2.61.0** | (items: OverflowItem[],pos:"start"\|"end", handleArrowClick:()=>void)=> ReactNode | 无 |
+| renderArrow | 折叠滚动模式下,自定义左右切换箭头如何渲染,默认为箭头按钮 hover 时展开溢出项。前三个参数自 **>=2.61.0** 支持,defaultNode 参数自 **>=2.66.0** 支持| (items: OverflowItem[],pos:"start"\|"end", handleArrowClick:()=>void, defaultNode: ReactNode)=> ReactNode | 无 |
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
 | showRestInDropdown | 是否将收起的 Tab 展示在下拉菜单中(仅当 collapsible 为 true 时生效) **>= 2.61.0** | boolean | true |
 | size | 大小,提供 `large`、`medium`、`small` 三种类型,**>=1.11.0,目前仅支持线性 Tabs** | string | `large` |
@@ -734,7 +775,7 @@ class App extends React.Component {
 | tabList | 标签页对象组成的数组,该对象支持 itemKey(对应 activeKey,tab(标签页文字)及 icon(标签页图标) | TabPane[] | 无 |
 | tabPaneMotion | 是否使用动画切换 tabs | boolean | true |
 | tabPosition | tab 的位置,支持`top`(水平), `left`(垂直) | string | `top` |
-| type | 标签栏的样式,可选`line`、 `card`、 `button` | string | `line` |
+| type | 标签栏的样式,可选`line`、 `card`、 `button`、`slash` | string | `line` |
 | onChange | 切换 tab 页时的回调函数 | function(activeKey: string) | 无 |
 | onTabClick | 单击事件 | function(key: string, e: Event) | 无 |
 | onTabClose | 关闭 tab 页时的回调函数 **>=2.1.0** | function(tabKey: string) | 无 |

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

@@ -456,7 +456,7 @@ import { Avatar, AvatarGroup } from '@douyinfe/semi-ui';
 | overlapFrom | Set the coverage direction of the avatars, one of `start`, `end` | string | `start` |
 | renderMore | Customize the more tag  | (restNumber: number, restAvatars: ReactNode[]) => ReactNode | - |
 | shape      | Shape of the avatar, one of `circle`, `square`                                      | string | `circle` |
-| size       | Size of the avatar, one of `extra-extra-small`, `extra-small`, `small`, `default`, `medium`, `large`, `extra-large` | string | `medium` |
+| size       | Size of the avatar, one of `extra-extra-small`, `extra-small`, `small`, `default`, `medium`, `large`, `extra-large` and valid value like "10px" | string | `medium` |
 
 ## Accessibility
 

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

@@ -452,7 +452,7 @@ import { AvatarGroup, Avatar } from '@douyinfe/semi-ui';
 | overlapFrom | 设置头像覆盖方向,支持 `start`, `end` | string | `start` |
 | renderMore | 自定义渲染 more 标签 | (restNumber: number, restAvatars: ReactNode[]) => ReactNode | - |
 | shape | 指定头像的形状,支持`circle`、`square` | string | `circle` |
-| size | 设置头像的大小,支持 `extra-extra-small`, `extra-small`、`small`、`default`、`medium`、`large`、`extra-large` | string | `medium` |
+| size | 设置头像的大小,支持 `extra-extra-small`, `extra-small`、`small`、`default`、`medium`、`large`、`extra-large` 和合法的 width 属性值例,如 "10px"| string | `medium` |
 
 ## Accessibility
 

+ 5 - 5
cypress/e2e/tabs.spec.js

@@ -74,13 +74,13 @@ describe('tabs', () => {
         cy.get('[id=semiTab1]').should('be.focused');
     });
 
-    it('keyboard test when the tabs is closable', () => {
+    it.only('keyboard test when the tabs is closable', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--tab-closable&args=&viewMode=story');
-        cy.get('[id=semiTab1]').click();
-        cy.get('[id=semiTab1]').should('be.focused');
+        cy.get('[data-tabkey=semiTab1]').eq(0).click();
+        cy.get('[data-tabkey=semiTab1]').eq(0).should('be.focused');
 
-        cy.get('[id=semiTab1]').type('{backspace}');
-        cy.get('[id=semiTab1]').should('not.exist');
+        cy.get('[data-tabkey=semiTab1]').eq(0).type('{backspace}');
+        cy.get('[data-tabkey=semiTab1]').should('not.exist');
     });
 
     it('collapsible', () => {

+ 3 - 4
packages/semi-foundation/overflowList/foundation.ts

@@ -33,15 +33,14 @@ class OverflowListFoundation extends BaseFoundation<OverflowListAdapter> {
             return overflow;
         }
 
-        const cloneItems = copy(items);
 
-        const visibleStateArr = cloneItems.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
+        const visibleStateArr = items.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
         const visibleStart = visibleStateArr.indexOf(true);
         const visibleEnd = visibleStateArr.lastIndexOf(true);
 
         const overflowList = [];
-        overflowList[0] = visibleStart >= 0 ? cloneItems.slice(0, visibleStart) : [];
-        overflowList[1] = visibleEnd >= 0 ? cloneItems.slice(visibleEnd + 1, cloneItems.length) : cloneItems;
+        overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
+        overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items.slice();
         return overflowList;
     }
 

+ 2 - 1
packages/semi-foundation/tabs/constants.ts

@@ -6,6 +6,7 @@ const cssClasses = {
     TABS_BAR_LINE: `${BASE_CLASS_PREFIX}-tabs-bar-line`,
     TABS_BAR_CARD: `${BASE_CLASS_PREFIX}-tabs-bar-card`,
     TABS_BAR_BUTTON: `${BASE_CLASS_PREFIX}-tabs-bar-button`,
+    TABS_BAR_SLASH: `${BASE_CLASS_PREFIX}-tabs-bar-slash`,
     TABS_BAR_EXTRA: `${BASE_CLASS_PREFIX}-tabs-bar-extra`,
     TABS_TAB: `${BASE_CLASS_PREFIX}-tabs-tab`,
     TABS_TAB_ACTIVE: `${BASE_CLASS_PREFIX}-tabs-tab-active`,
@@ -29,7 +30,7 @@ const numbers = {
 };
 
 const strings = {
-    TYPE_MAP: ['line', 'card', 'button'],
+    TYPE_MAP: ['line', 'card', 'button', 'slash'],
     SIZE: ['small', 'medium', 'large'],
     POSITION_MAP: ['top', 'left']
 };

+ 42 - 0
packages/semi-foundation/tabs/tabs.scss

@@ -174,6 +174,10 @@ $ignoreIcon: '.#{$prefix}-icon-checkbox_tick, .#{$prefix}-icon-radio, .#{$prefix
                 color: var(--semi-color-text-2);
                 margin-left: 10px;
                 cursor: pointer;
+
+                &.#{$prefix}-icon-close:hover {
+                    color: var(--semi-color-text-0);
+                }
             }
 
             &:hover {
@@ -182,6 +186,10 @@ $ignoreIcon: '.#{$prefix}-icon-checkbox_tick, .#{$prefix}-icon-radio, .#{$prefix
                 .#{$prefix}-icon:not(#{$ignoreIcon}) {
                     color: $color-tabs_tab-icon-hover;
                 }
+
+                .#{$prefix}-icon.#{$module}-tab-icon-close {
+                    color: var(--semi-color-text-2);
+                }
             }
 
             &:active {
@@ -190,6 +198,10 @@ $ignoreIcon: '.#{$prefix}-icon-checkbox_tick, .#{$prefix}-icon-radio, .#{$prefix
                 .#{$prefix}-icon:not(#{$ignoreIcon}) {
                     color: $color-tabs_tab-icon-active;
                 }
+
+                .#{$prefix}-icon.#{$module}-tab-icon-close {
+                    color: var(--semi-color-text-2);
+                }
             }
         }
 
@@ -583,6 +595,36 @@ $ignoreIcon: '.#{$prefix}-icon-checkbox_tick, .#{$prefix}-icon-radio, .#{$prefix
         }
     }
 
+    &-bar-slash {
+
+        .#{$module}-tab {
+            padding: $spacing-tabs_bar_slash_tab-paddingY $spacing-tabs_bar_slash_tab-paddingX;
+
+            &:nth-of-type(1) {
+                padding-left: 0;
+            }  
+
+            &:not(:last-of-type) {
+                margin-right: $spacing-tabs_bar_slash-marginRight;
+
+                &:after {  
+                    content: "";
+                    margin-left: $spacing-tabs_bar_slash_line_marginLeft;
+                    display: inline-block;
+                    height: $height-tabs_tab_slash_line;
+                    width: $width-tabs_tab_slash_line;
+                    margin-top: $spacing-tabs_bar_slash_line_marginTop;
+                    margin-bottom: $spacing-tabs_bar_slash_line_marginBottom;
+                    vertical-align: bottom;
+                    // Get diagonal slash
+                    background: linear-gradient(to bottom right, transparent 0%, 
+                        transparent calc(50% - 1px), $color-tabs_tab_slash_line 50%,
+                        transparent calc(50% + 1px), transparent 100%);
+                }
+            }
+        }
+    }
+
     &-content {
         width: 100%;
         padding: $spacing-tabs_content-paddingY $spacing-tabs_content-paddingX;

+ 12 - 0
packages/semi-foundation/tabs/variables.scss

@@ -65,6 +65,8 @@ $color-tabs_tab-pane_arrow_disabled-bg-hover:  transparent;
 $color-tabs_tab-pane_arrow_disabled-text-default: var(--semi-color-disabled-text);
 $color-tabs_tab-pane_arrow_disabled-text-hover:  var(--semi-color-disabled-text);
 
+$color-tabs_tab_slash_line: var(--semi-color-text-2); //斜线式页签分割线颜色
+
 $font-tabs_tab-fontWeight: $font-weight-regular; // 页签文本字重 - 默认
 $font-tabs_tab_active-fontWeight: $font-weight-bold; // 页签文本字重 - 选中
 
@@ -79,6 +81,9 @@ $width-tabs-outline-offset: -2px; // 聚焦轮廓偏移宽度
 $width-tabs_bar_line-outline-offset: -1px; // 线条式页签聚焦轮廓偏移宽度
 $width-tabs_tab-pane_arrow-border:0px; // 滚动折叠箭头边框宽度
 
+$width-tabs_tab_slash_line: 8px; // 斜线式页签分割线宽度
+$height-tabs_tab_slash_line: 14px; // 斜线式页签分割线高度
+
 $height-tabs_bar_extra_large: 50px; // 大尺寸页签高度
 $font-tabs_bar_extra_large-lineHeight: $height-tabs_bar_extra_large; // 大尺寸页签文字行高
 
@@ -118,6 +123,13 @@ $spacing-tabs_bar_line_tab_left-padding: 12px; // 垂直线条式页签左侧内
 $spacing-tabs_bar_line_tab_left_small-padding: $spacing-tight - 2px;  // 小尺寸垂直线条式页签左侧内边距
 $spacing-tabs_bar_line_tab_left_medium-padding: $spacing-base-tight - 2px; // 中等尺寸垂直线条式页签左侧内边距
 
+$spacing-tabs_bar_slash_tab-paddingY: 12px; // 斜线式页签上下内边距
+$spacing-tabs_bar_slash_tab-paddingX: 0px; // 斜线式页签水平内边距
+$spacing-tabs_bar_slash-marginRight: 16px; // 斜线式页签右侧外边距
+$spacing-tabs_bar_slash_line_marginLeft: 16px; // 斜线式页签斜线左侧外边距
+$spacing-tabs_bar_slash_line_marginTop: 3px; // 斜线式页签斜线顶部外边距
+$spacing-tabs_bar_slash_line_marginBottom: 3px; // 斜线式页签斜线底部外边距
+
 $spacing-tabs_content-paddingY: 5px; // 页签内容区垂直方向内边距
 $spacing-tabs_content-paddingX: 0; // 页签内容区水平方向内边距
 

+ 2 - 0
packages/semi-foundation/upload/upload.scss

@@ -347,6 +347,7 @@ $module: #{$prefix}-upload;
             height: $height-upload_file_pic_card;
             width: $width-upload_file_pic_card;
             border-radius: $radius-upload_picture_file_card_img;
+            box-sizing: border-box;
             position: relative;
             overflow: hidden;
 
@@ -474,6 +475,7 @@ $module: #{$prefix}-upload;
             }
 
             &-error {
+                border: 1px solid $color-upload_picture_file_card_error-border;
                 outline: 1px solid $color-upload_picture_file_card_error-border;
             }
 

+ 11 - 0
packages/semi-ui/avatar/_story/avatar.stories.tsx

@@ -1,7 +1,18 @@
 import * as React from 'react';
 import { storiesOf } from '@storybook/react';
 import Demo from './Demo';
+import Avatar from '../index';
+import AvatarGroup from '../avatarGroup';
 
 const stories = storiesOf('Avatar', module);
 
 stories.add('Avatar', () => <Demo />);
+
+stories.add('Avatar', () => <>
+    <Avatar size={'6rem'} />
+    <Avatar size="small" />
+    <AvatarGroup size="6rem">
+        <Avatar color="red" alt='Lisa LeBlanc'>LL</Avatar>
+        <Avatar alt='Caroline Xiao'>CX</Avatar>
+    </AvatarGroup>
+</>);

+ 2 - 2
packages/semi-ui/avatar/interface.ts

@@ -25,7 +25,7 @@ export interface AvatarProps extends BaseProps {
     children?: React.ReactNode;
     color?: AvatarColor;
     shape?: AvatarShape;
-    size?: AvatarSize;
+    size?: string;
     hoverMask?: React.ReactNode;
     src?: string;
     srcSet?: string;
@@ -68,7 +68,7 @@ export type AvatarGroupOverlapFrom = 'start' | 'end';
 export interface AvatarGroupProps {
     children?: React.ReactNode;
     shape?: AvatarGroupShape;
-    size?: AvatarGroupSize;
+    size?: string;
     overlapFrom?: AvatarGroupOverlapFrom;
     maxCount?: number;
     renderMore?: (restNumber?: number, restAvatars?: React.ReactNode[]) => React.ReactNode

+ 9 - 4
packages/semi-ui/tabs/TabBar.tsx

@@ -161,7 +161,7 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
                 </div>
             );
         }
-        const { dropdownClassName, dropdownStyle, showRestInDropdown } = this.props;
+        const { dropdownClassName, dropdownStyle, showRestInDropdown, dropdownProps } = this.props;
         const { rePosKey } = this.state;
         const disabled = !items.length;
         const menu = (
@@ -197,6 +197,8 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
             [`${cssClasses.TABS_BAR}-dropdown`]: true,
         });
 
+        const customDropdownProps = dropdownProps?.[pos] ?? {};
+
         return (
             <>
                 {showRestInDropdown ? (
@@ -211,6 +213,7 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
                         style={dropdownStyle}
                         trigger={'hover'}
                         disableFocusListener // prevent the panel from popping up again after clicking
+                        {...customDropdownProps}
                     >
                         {button}
                     </Dropdown>
@@ -221,11 +224,12 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
 
     renderOverflow = (items: any[]): Array<ReactNode> => items.map((item, index) => {
         const pos = index === 0 ? 'start' : 'end';
+        const icon = index === 0 ? <IconChevronLeft/> : <IconChevronRight/>;
+        const overflowNode = this.renderCollapse(item, icon, pos);
         if (this.props.renderArrow) {
-            return this.props.renderArrow(item, pos, ()=>this.handleArrowClick(item, pos));
+            return this.props.renderArrow(item, pos, ()=>this.handleArrowClick(item, pos), overflowNode);
         }
-        const icon = index === 0 ? <IconChevronLeft/> : <IconChevronRight/>;
-        return this.renderCollapse(item, icon, pos);
+        return overflowNode;
     });
 
 
@@ -318,6 +322,7 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
             [cssClasses.TABS_BAR_LINE]: type === 'line',
             [cssClasses.TABS_BAR_CARD]: type === 'card',
             [cssClasses.TABS_BAR_BUTTON]: type === 'button',
+            [cssClasses.TABS_BAR_SLASH]: type === 'slash',
             [`${cssClasses.TABS_BAR}-${tabPosition}`]: tabPosition,
             [`${cssClasses.TABS_BAR}-collapse`]: collapsible,
         });

+ 1 - 1
packages/semi-ui/tabs/TabItem.tsx

@@ -37,7 +37,7 @@ const TabItem = (props: TabItemProps, ref: LegacyRef<HTMLDivElement>) => {
     } = props;
 
     const closableIcon = useMemo(() => {
-        return (type === 'card' && closable) ?
+        return closable ?
             <IconClose 
                 aria-label="Close" 
                 role="button" 

+ 91 - 6
packages/semi-ui/tabs/_story/tabs.stories.jsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useCallback } from 'react';
 import Tabs from '../index';
 import Button from '@douyinfe/semi-ui/button/index';
 import Typography from '@douyinfe/semi-ui/typography/index';
@@ -363,12 +363,40 @@ Level2Card.story = {
   name: 'Level 2-卡片Tab',
 };
 
+export const SlashTab = () => {
+  return (
+  <>
+    <Tabs defaultActiveKey="1" type="slash">
+      <TabPane tab="文档" itemKey="1">文档</TabPane>
+      <TabPane tab="快速起步" itemKey="2" disabled>快速起步</TabPane>
+      <TabPane tab="帮助" itemKey="3">帮助</TabPane>
+      <TabPane tab="关于" itemKey="4">关于</TabPane>
+      <TabPane tab="资源工具" itemKey="5">资源工具</TabPane>
+    </Tabs>
+    <br />
+    <br />
+    <Tabs defaultActiveKey="1" type="slash">
+      <TabPane tab={<span><IconFile />文档</span>} itemKey="1">文档</TabPane>
+      <TabPane tab={<span><IconGlobe />快速起步</span>} itemKey="2" disabled>快速起步</TabPane>
+      <TabPane tab={<span><IconHelpCircle />帮助</span>} itemKey="3">帮助</TabPane>
+    </Tabs>
+    <br />
+    <Tabs style={{ width: '400px'}} type="slash" collapsible dropdownProps={{ start: { showTick: false}, end: { showTick: false}}}>
+          {['文档', "快速起步", "帮助", "关于", "资源工具", "主页"].map((i, index) => (
+              <TabPane tab={i} itemKey={i} key={i} >
+                  Content of card tab {i}
+              </TabPane>
+          ))}
+    </Tabs>
+  </>)
+}
+
 export const Level3ButtonTab = () => (
   <Tabs style={style} defaultActiveKey="1" type="button">
     <TabPane tab="文档" itemKey="1">
       文档
     </TabPane>
-    <TabPane tab="快速起步" itemKey="2">
+    <TabPane tab="快速起步" itemKey="2" disabled>
       快速起步
     </TabPane>
     <TabPane tab="帮助" itemKey="3">
@@ -889,11 +917,35 @@ class TabClosableDemo extends React.Component {
 
   render() {
     return (
-      <Tabs type="card" defaultActiveKey="1" onTabClose={this.close.bind(this)}>
+      <>
+        <br />
+        <Tabs type="card" defaultActiveKey="1" onTabClose={this.close.bind(this)}>
           {
-            this.state.tabList.map(t=><TabPane closable={t.closable} tab={t.tab} itemKey={t.itemKey} key={t.itemKey}>{t.text}</TabPane>)
+            this.state.tabList.map(t=><TabPane closable={t.closable} tab={t.tab} itemKey={t.itemKey} key={t.itemKey} >{t.text}</TabPane>)
           }
-      </Tabs>
+        </Tabs>
+        <br />
+        <br />
+        <Tabs type="line" defaultActiveKey="1" onTabClose={this.close.bind(this)}>
+          {
+            this.state.tabList.map(t=><TabPane closable={t.closable} tab={t.tab} itemKey={t.itemKey} key={t.itemKey} >{t.text}</TabPane>)
+          }
+        </Tabs>
+        <br />
+        <br />
+        <Tabs type="button" defaultActiveKey="1" onTabClose={this.close.bind(this)}>
+          {
+            this.state.tabList.map(t=><TabPane closable={t.closable} tab={t.tab} itemKey={t.itemKey} key={t.itemKey} >{t.text}</TabPane>)
+          }
+        </Tabs>
+        <br />
+        <br />
+        <Tabs type="slash" defaultActiveKey="1" onTabClose={this.close.bind(this)}>
+          {
+            this.state.tabList.map(t=><TabPane  closable={t.closable} tab={t.tab} itemKey={t.itemKey} key={t.itemKey} >{t.text}</TabPane>)
+          }
+        </Tabs>
+      </>
     );
   }
 }
@@ -1070,4 +1122,37 @@ export const Fix2415 = () => {
         ))}    
     </Tabs>
   )
-}
+}
+export const DynamicShowArrow = () => {
+  const [hidden, setHidden] = useState(true);
+  const renderArrow = (items, pos, handleArrowClick, defaultNode) => {
+      const style = { visibility: hidden ? 'hidden': 'visible'};
+      return <span style={style}>
+        {defaultNode}
+      </span>
+  };
+  const onVisibleTabsChange = useCallback((visibleState) => {
+      let values = Object.values(Object.fromEntries(visibleState));
+      if (values.includes(false)) {
+          setHidden(false);
+      } else {
+          setHidden(true);
+      }
+  }, []);
+  return (
+      <Tabs
+          renderArrow={renderArrow}
+          style={{ margin: '20px' }}
+          type="line"
+          arrowPosition={"end"}
+          collapsible
+          onVisibleTabsChange={onVisibleTabsChange}
+      >
+          {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => (
+              <TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`} key={i}>
+                  Content of card tab {i}
+              </TabPane>
+          ))}
+      </Tabs>
+  );
+};

+ 4 - 1
packages/semi-ui/tabs/index.tsx

@@ -56,6 +56,7 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
         more: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
         arrowPosition: PropTypes.string,
         renderArrow: PropTypes.func,
+        dropdownProps: PropTypes.object,
     };
     static __SemiComponentName__ = "Tabs";
     static defaultProps: TabsProps = getDefaultPropsFromGlobalConfig(Tabs.__SemiComponentName__, {
@@ -265,6 +266,7 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
             visibleTabsStyle,
             arrowPosition,
             renderArrow,
+            dropdownProps,
             ...restProps
         } = this.props;
         const { panes, activeKey } = this.state;
@@ -296,7 +298,8 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
             onVisibleTabsChange,
             visibleTabsStyle,
             arrowPosition,
-            renderArrow
+            renderArrow,
+            dropdownProps,
         } as TabBarProps;
 
         const tabBar = renderTabBar ? renderTabBar(tabBarProps, TabBar) : <TabBar {...tabBarProps} />;

+ 9 - 4
packages/semi-ui/tabs/interface.ts

@@ -4,7 +4,7 @@ import TabBar, { OverflowItem } from './TabBar';
 import { DropdownProps } from "../dropdown";
 import { OverflowListProps } from "../overflowList";
 
-export type TabType = 'line' | 'card' | 'button';
+export type TabType = 'line' | 'card' | 'button' | 'slash';
 export type TabSize = 'small' | 'medium' | 'large';
 export type TabPosition = 'top' | 'left';
 
@@ -16,6 +16,10 @@ export interface PlainTab {
     closable?: boolean
 }
 
+interface TabsDropDownProps {
+    start: DropdownProps;
+    end: DropdownProps
+}
 
 export interface TabsProps {
     activeKey?: string;
@@ -45,7 +49,8 @@ export interface TabsProps {
     onVisibleTabsChange?: TabBarProps["onVisibleTabsChange"];
     visibleTabsStyle?: TabBarProps['visibleTabsStyle'];
     arrowPosition?: TabBarProps['arrowPosition'];
-    renderArrow?: TabBarProps['renderArrow']
+    renderArrow?: TabBarProps['renderArrow'];
+    dropdownProps?: TabsDropDownProps
 }
 
 export interface TabBarProps {
@@ -69,8 +74,8 @@ export interface TabBarProps {
     onVisibleTabsChange?: (visibleState: Map<string, boolean>) => void;
     visibleTabsStyle?: CSSProperties;
     arrowPosition?: OverflowListProps['overflowRenderDirection'];
-    renderArrow?: (items: OverflowItem[], pos: "start"|"end", handleArrowClick: () => void) => ReactNode
-
+    renderArrow?: (items: OverflowItem[], pos: "start"|"end", handleArrowClick: () => void, defaultNode: ReactNode) => ReactNode;
+    dropdownProps?: TabsDropDownProps
 }
 
 export interface TabPaneProps {

+ 14 - 1
packages/semi-ui/upload/_story/upload.stories.jsx

@@ -1323,4 +1323,17 @@ export const Unmount = () => {
             <button onClick={toggle}>toggle upload mount</button>
         </>
     );
-};
+};
+
+export const fix2448 = () => {
+  let action = 'http://xxx';
+    return (
+        <>
+          <Form>
+            <Form.Upload action={action} listType="picture" accept="image/*" multiple >
+              <IconPlus size="extra-large" />
+            </Form.Upload>
+          </Form>
+        </>
+    );
+}