ソースを参照

feat: Tabs add slash type, renderArrow add defaultNode params, closable allow all types

zhangyumei.0319 1 年間 前
コミット
3973e29396

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

@@ -381,7 +381,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.65.0.
 
 ```jsx live=true dir="column"
 import React from 'react';
@@ -708,7 +710,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.65.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` |

+ 5 - 3
content/navigation/tabs/index.md

@@ -404,7 +404,9 @@ class App extends React.Component {
 
 **自定义滚动箭头渲染**
 
-通过 renderArrow 修改滚动折叠模式下,左右切换箭头的渲染,入参为溢出的 items 和 位置
+通过 renderArrow 修改滚动折叠模式下,左右切换箭头的渲染,入参为溢出的 items 和 位置, 点击处理函数,以及 defaultNode。
+
+**注**:renderArrow 的前三个参数自 2.61.0 支持,defaultNode 自 2.65.0 支持。
 
 ```jsx live=true dir="column"
 import React from 'react';
@@ -412,7 +414,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,
@@ -725,7 +727,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.65.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` |

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

+ 34 - 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,28 @@ $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: "/";
+                    font-weight:  $font-tabs_tab_slash_line-fontWeight;
+                    margin-left: $spacing-tabs_bar_slash_line_marginLeft;
+                    color: $color-tabs_tab_slash_line;
+                }
+            }
+        }
+    }
+
     &-content {
         width: 100%;
         padding: $spacing-tabs_content-paddingY $spacing-tabs_content-paddingX;

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

@@ -65,6 +65,9 @@ $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_slash_line-fontWeight: $font-weight-regular; //斜线式页签分割线字重
+
 $font-tabs_tab-fontWeight: $font-weight-regular; // 页签文本字重 - 默认
 $font-tabs_tab_active-fontWeight: $font-weight-bold; // 页签文本字重 - 选中
 
@@ -118,6 +121,11 @@ $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_content-paddingY: 5px; // 页签内容区垂直方向内边距
 $spacing-tabs_content-paddingX: 0; // 页签内容区水平方向内边距
 

+ 5 - 3
packages/semi-ui/tabs/TabBar.tsx

@@ -221,11 +221,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 +319,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" 

+ 93 - 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,42 @@ 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="文档" 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 />
+    <Tabs style={{ width: '400px'}} type="slash" collapsible>
+          {['文档', "快速起步", "帮助", "关于", "资源工具"].map((i, index) => (
+              <TabPane tab={`tab-${index}`} 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 +919,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 +1124,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>
+  );
+};

+ 2 - 2
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';
 
@@ -69,7 +69,7 @@ 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
 
 }