Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Tabs add slash type, renderArrow add defaultNode params, closab… #2416

Merged
merged 4 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions content/navigation/tabs/index-en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -701,14 +741,15 @@ 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' |
| keepDOM | Whether to render the DOM structure of the hidden panel when using TabPane writing, **>=1.0.0** | boolean | true |
| 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` |
Expand Down
53 changes: 47 additions & 6 deletions content/navigation/tabs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Tabs, TabPane } from '@douyinfe/semi-ui';

### 基本用法

标签栏支持三种样式的显示:线条式,按钮式,卡片式。默认选中第一项。
标签栏支持三种样式的显示:线条式,按钮式,卡片式,斜线式。默认选中第一项。
标签页支持两种传入方式,两者渲染流程上有所区别:

- 通过 `tabList` 传入标签页对象的数组,当使用 `tabList` 时每次只渲染当前传入的节点
Expand Down Expand Up @@ -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>
);
}
}
```

### 带图标的

有图标的标签栏。
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -404,15 +442,17 @@ 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';
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,
Expand Down Expand Up @@ -718,14 +758,15 @@ 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' |
| keepDOM | 使用 TabPane 写法时是否渲染隐藏面板的 DOM 结构 | boolean | true |
| 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` |
Expand All @@ -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) | 无 |
Expand Down
10 changes: 5 additions & 5 deletions cypress/e2e/tabs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/semi-foundation/tabs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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']
};
Expand Down
42 changes: 42 additions & 0 deletions packages/semi-foundation/tabs/tabs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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);
}
}
}

Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions packages/semi-foundation/tabs/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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; // 页签文本字重 - 选中

Expand All @@ -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; // 大尺寸页签文字行高

Expand Down Expand Up @@ -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; // 页签内容区水平方向内边距

Expand Down
13 changes: 9 additions & 4 deletions packages/semi-ui/tabs/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down Expand Up @@ -197,6 +197,8 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
[`${cssClasses.TABS_BAR}-dropdown`]: true,
});

const customDropdownProps = dropdownProps?.[pos] ?? {};

return (
<>
{showRestInDropdown ? (
Expand All @@ -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>
Expand All @@ -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;
});


Expand Down Expand Up @@ -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,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/semi-ui/tabs/TabItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading