feat: New Tabs (#24552)

* init style

* support size

* editable

* add shadow

* update demo

* fix nest style

* update rtl

* update snapshot

* bump

* fix hover

* fix test case

* fix style lint

* clean up

* updat docs

* add onTabScroll

* upgrade rc-dropdown

* update snapshot

* clean snapshot

* clean up

Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
二货机器人 2020-05-30 18:28:25 +08:00 committed by GitHub
parent 75d84b2041
commit 77c5adbe4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 3882 additions and 5121 deletions

View File

@ -839,133 +839,99 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
</div>
</div>
<div
class="ant-tabs ant-tabs-top ant-card-head-tabs ant-tabs-large ant-tabs-line"
class="ant-tabs ant-tabs-top ant-card-head-tabs ant-tabs-large"
>
<div
class="ant-tabs-bar ant-tabs-top-bar ant-tabs-large-bar"
class="ant-tabs-nav"
role="tablist"
tabindex="-1"
>
<div
class="ant-tabs-nav-container"
class="ant-tabs-nav-wrap"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<span
aria-label="left"
class="anticon anticon-left ant-tabs-tab-prev-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-tabs-tab-next-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-nav-scroll"
<button
aria-selected="true"
class="ant-tabs-tab ant-tabs-tab-active"
role="tab"
tabindex="0"
type="button"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-controls="tabpane-tab1"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
id="tab-tab1"
role="tab"
tabindex="0"
>
tab1
</div>
<div
aria-controls="tabpane-tab2"
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
id="tab-tab2"
role="tab"
tabindex="-1"
>
tab2
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
tab1
</button>
<button
aria-selected="false"
class="ant-tabs-tab"
role="tab"
tabindex="0"
type="button"
>
tab2
</button>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
</div>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left:0%"
class="ant-tabs-content-holder"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="visibility:visible"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
style="visibility:hidden"
tabindex="-1"
/>
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
</div>
@ -990,16 +956,89 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
class="ant-card-head-wrapper"
/>
<div
class="ant-tabs ant-tabs-top ant-card-head-tabs ant-tabs-large ant-tabs-line"
class="ant-tabs ant-tabs-top ant-card-head-tabs ant-tabs-large"
>
<div
class="ant-tabs-bar ant-tabs-top-bar ant-tabs-large-bar"
class="ant-tabs-nav"
role="tablist"
tabindex="-1"
>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<button
aria-selected="false"
class="ant-tabs-tab"
role="tab"
tabindex="0"
type="button"
>
article
</button>
<button
aria-selected="true"
class="ant-tabs-tab ant-tabs-tab-active"
role="tab"
tabindex="0"
type="button"
>
app
</button>
<button
aria-selected="false"
class="ant-tabs-tab"
role="tab"
tabindex="0"
type="button"
>
project
</button>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
<div
class="ant-tabs-extra-content"
style="float:right"
>
<a
href="#"
@ -1007,144 +1046,34 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
More
</a>
</div>
<div
class="ant-tabs-nav-container"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<span
aria-label="left"
class="anticon anticon-left ant-tabs-tab-prev-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-tabs-tab-next-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-scroll"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-controls="tabpane-article"
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
id="tab-article"
role="tab"
tabindex="-1"
>
article
</div>
<div
aria-controls="tabpane-app"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
id="tab-app"
role="tab"
tabindex="0"
>
app
</div>
<div
aria-controls="tabpane-project"
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
id="tab-project"
role="tab"
tabindex="-1"
>
project
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left:-100%"
class="ant-tabs-content-holder"
>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
style="visibility:hidden"
tabindex="-1"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="visibility:visible"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
style="visibility:hidden"
tabindex="-1"
/>
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
</div>

View File

@ -1175,133 +1175,99 @@ exports[`renders ./components/page-header/demo/responsive.md correctly 1`] = `
class="ant-page-header-footer"
>
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
class="ant-tabs ant-tabs-top"
>
<div
class="ant-tabs-bar ant-tabs-top-bar"
class="ant-tabs-nav"
role="tablist"
tabindex="-1"
>
<div
class="ant-tabs-nav-container"
class="ant-tabs-nav-wrap"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<span
aria-label="left"
class="anticon anticon-left ant-tabs-tab-prev-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-tabs-tab-next-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-nav-scroll"
<button
aria-selected="true"
class="ant-tabs-tab ant-tabs-tab-active"
role="tab"
tabindex="0"
type="button"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-controls="tabpane-1"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
id="tab-1"
role="tab"
tabindex="0"
>
Details
</div>
<div
aria-controls="tabpane-2"
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
id="tab-2"
role="tab"
tabindex="-1"
>
Rule
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
Details
</button>
<button
aria-selected="false"
class="ant-tabs-tab"
role="tab"
tabindex="0"
type="button"
>
Rule
</button>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
</div>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left:0%"
class="ant-tabs-content-holder"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="visibility:visible"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
style="visibility:hidden"
tabindex="-1"
/>
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
</div>

View File

@ -684,6 +684,7 @@
@tabs-card-horizontal-padding: (@tabs-card-height - floor(@font-size-base * @line-height-base)) / 2 -
@border-width-base @padding-md;
@tabs-card-horizontal-padding-sm: 6px @padding-md;
@tabs-card-horizontal-padding-lg: 7px @padding-md 6px;
@tabs-title-font-size: @font-size-base;
@tabs-title-font-size-lg: @font-size-lg;
@tabs-title-font-size-sm: @font-size-base;

View File

@ -1,91 +0,0 @@
import * as React from 'react';
import ScrollableInkTabBar from 'rc-tabs/lib/ScrollableInkTabBar';
import classNames from 'classnames';
import UpOutlined from '@ant-design/icons/UpOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import DownOutlined from '@ant-design/icons/DownOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import { TabsProps } from './index';
import { ConfigConsumerProps, ConfigConsumer } from '../config-provider';
import { cloneElement } from '../_util/reactNode';
export default class TabBar extends React.Component<TabsProps> {
static defaultProps = {
animated: true,
type: 'line' as TabsProps['type'],
};
renderTabBar = ({ direction }: ConfigConsumerProps) => {
const {
tabBarStyle,
animated,
renderTabBar,
tabBarExtraContent,
tabPosition,
prefixCls,
className,
size,
type,
} = this.props;
const inkBarAnimated = typeof animated === 'object' ? animated.inkBar : animated;
const isVertical = tabPosition === 'left' || tabPosition === 'right';
let prevIconComponent = isVertical ? UpOutlined : LeftOutlined;
let nextIconComponent = isVertical ? DownOutlined : RightOutlined;
if (direction === 'rtl' && !isVertical) {
prevIconComponent = RightOutlined;
nextIconComponent = LeftOutlined;
}
const prevIcon = (
<span className={`${prefixCls}-tab-prev-icon`}>
{React.createElement(prevIconComponent, {
className: `${prefixCls}-tab-prev-icon-target`,
})}
</span>
);
const nextIcon = (
<span className={`${prefixCls}-tab-next-icon`}>
{React.createElement(nextIconComponent, {
className: `${prefixCls}-tab-next-icon-target`,
})}
</span>
);
// Additional className for style usage
const cls: string = classNames(
`${prefixCls}-${tabPosition}-bar`,
{
[`${prefixCls}-${size}-bar`]: !!size,
[`${prefixCls}-card-bar`]: type && type.indexOf('card') >= 0,
},
className,
);
const renderProps = {
...this.props,
children: null,
inkBarAnimated,
extraContent: tabBarExtraContent,
style: tabBarStyle,
prevIcon,
nextIcon,
className: cls,
};
let RenderTabBar: React.ReactElement<any>;
if (renderTabBar) {
RenderTabBar = renderTabBar(renderProps, ScrollableInkTabBar);
} else {
RenderTabBar = <ScrollableInkTabBar {...renderProps} />;
}
return cloneElement(RenderTabBar);
};
render() {
return <ConfigConsumer>{this.renderTabBar}</ConfigConsumer>;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,23 +2,24 @@
exports[`Tabs renderTabBar custom-tab-bar 1`] = `
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
class="ant-tabs ant-tabs-top"
>
<div>
custom-tab-bar
</div>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left:0%"
class="ant-tabs-content-holder"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="visibility:visible"
tabindex="0"
class="ant-tabs-content ant-tabs-content-top"
>
foo
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
@ -26,218 +27,153 @@ exports[`Tabs renderTabBar custom-tab-bar 1`] = `
exports[`Tabs rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-tabs ant-tabs-top ant-tabs-line ant-tabs-rtl"
class="ant-tabs ant-tabs-top ant-tabs-rtl"
>
<div
class="ant-tabs-bar ant-tabs-top-bar"
class="ant-tabs-nav"
role="tablist"
tabindex="-1"
>
<div
class="ant-tabs-nav-container"
class="ant-tabs-nav-wrap"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-tabs-tab-prev-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<span
aria-label="left"
class="anticon anticon-left ant-tabs-tab-next-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-nav-scroll"
<button
aria-selected="false"
class="ant-tabs-tab"
role="tab"
tabindex="0"
type="button"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-controls="tabpane-xx"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
id="tab-xx"
role="tab"
tabindex="0"
>
xx
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
xx
</button>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
</div>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left:100%"
class="ant-tabs-content-holder"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="visibility:visible"
tabindex="0"
/>
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
`;
exports[`Tabs tabPosition remove card 1`] = `
<div
class="ant-tabs ant-tabs-left ant-tabs-vertical ant-tabs-line"
class="ant-tabs ant-tabs-left"
>
<div
class="ant-tabs-bar ant-tabs-left-bar"
class="ant-tabs-nav"
role="tablist"
tabindex="-1"
>
<div
class="ant-tabs-nav-container"
class="ant-tabs-nav-wrap"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<span
aria-label="up"
class="anticon anticon-up ant-tabs-tab-prev-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<span
aria-label="down"
class="anticon anticon-down ant-tabs-tab-next-icon-target"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-nav-scroll"
<button
aria-selected="false"
class="ant-tabs-tab"
role="tab"
tabindex="0"
type="button"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-controls="tabpane-1"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
id="tab-1"
role="tab"
tabindex="0"
>
foo
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
foo
</button>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
<div
class="ant-tabs-extra-content"
>
@ -245,17 +181,18 @@ exports[`Tabs tabPosition remove card 1`] = `
</div>
</div>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-left-content"
style="margin-top:0%"
class="ant-tabs-content-holder"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="visibility:visible"
tabindex="0"
class="ant-tabs-content ant-tabs-content-left"
>
foo
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>

View File

@ -37,10 +37,7 @@ describe('Tabs', () => {
});
it('add card', () => {
wrapper
.find('.ant-tabs-new-tab')
.hostNodes()
.simulate('click');
wrapper.find('.ant-tabs-nav-add').first().simulate('click');
expect(handleEdit.mock.calls[0][1]).toBe('add');
});
@ -79,4 +76,13 @@ describe('Tabs', () => {
expect(wrapper).toMatchSnapshot();
});
});
it('warning for onNextClick', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Tabs onNextClick={() => {}} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Tabs] `onPrevClick` and `onNextClick` has been removed. Please use `onTabScroll` instead.',
);
errorSpy.mockRestore();
});
});

View File

@ -22,7 +22,7 @@ function callback(key) {
console.log(key);
}
ReactDOM.render(
const Demo = () => (
<Tabs defaultActiveKey="1" onChange={callback}>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
@ -33,7 +33,8 @@ ReactDOM.render(
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>,
mountNode,
</Tabs>
);
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -46,34 +46,22 @@ ReactDOM.render(
.card-container p {
margin: 0;
}
.card-container > .ant-tabs-card > .ant-tabs-content {
.card-container > .ant-tabs-card .ant-tabs-content {
height: 120px;
margin-top: -16px;
}
[data-theme='compact'] .card-container > .ant-tabs-card > .ant-tabs-content {
height: 120px;
margin-top: -8px;
}
[data-theme='compact'] .card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane,
.card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane {
.card-container > .ant-tabs-card .ant-tabs-content > .ant-tabs-tabpane {
background: #fff;
padding: 16px;
}
[data-theme='compact'] .card-container > .ant-tabs-card > .ant-tabs-bar,
.card-container > .ant-tabs-card > .ant-tabs-bar {
border-color: #fff;
.card-container > .ant-tabs-card > .ant-tabs-nav::before {
display: none;
}
[data-theme='compact'] .card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab,
.card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
.card-container > .ant-tabs-card .ant-tabs-tab {
border-color: transparent;
background: transparent;
}
[data-theme='compact'] .card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active,
.card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active {
.card-container > .ant-tabs-card .ant-tabs-tab-active {
border-color: #fff;
background: #fff;
}
@ -85,21 +73,21 @@ ReactDOM.render(
overflow: hidden;
padding: 24px;
}
[data-theme="dark"] .card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-content {
height: 120px;
margin-top: -8px;
}
[data-theme="dark"] .card-container > .ant-tabs-card .ant-tabs-tab {
border-color: transparent;
background: transparent;
}
[data-theme="dark"] #components-tabs-demo-card-top .code-box-demo {
background: #000;
}
[data-theme="dark"] .card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane {
[data-theme="dark"] .card-container > .ant-tabs-card .ant-tabs-content > .ant-tabs-tabpane {
background: #141414;
}
[data-theme="dark"] .card-container > .ant-tabs-card > .ant-tabs-bar {
border-color: #141414;
}
[data-theme="dark"] .card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active {
[data-theme="dark"] .card-container > .ant-tabs-card .ant-tabs-tab-active {
border-color: #141414;
background: #141414;
}

View File

@ -41,7 +41,7 @@ class SlidingTabsDemo extends React.Component {
</Radio.Group>
<Tabs defaultActiveKey="1" tabPosition={mode} style={{ height: 220 }}>
{[...Array(30).keys()].map(i => (
<TabPane tab={`Tab-${i}`} key={i}>
<TabPane tab={`Tab-${i}`} key={i} disabled={i === 28}>
Content of tab {i}
</TabPane>
))}

View File

@ -19,25 +19,24 @@ Ant Design has 3 types of Tabs for different situations.
### Tabs
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| activeKey | Current TabPane's key | string | - |
| animated | Whether to change tabs with animation. Only works while `tabPosition="top"\|"bottom"` | boolean \| {inkBar:boolean, tabPane:boolean} | `true`, `false` when `type="card"` |
| renderTabBar | replace the TabBar | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - |
| defaultActiveKey | Initial active TabPane's key, if `activeKey` is not set. | string | - |
| hideAdd | Hide plus icon or not. Only works while `type="editable-card"` | boolean | `false` |
| size | preset tab bar size | `large` \| `default` \| `small` | `default` |
| tabBarExtraContent | Extra content in tab bar | React.ReactNode | - |
| tabBarGutter | The gap between tabs | number | - |
| tabBarStyle | Tab bar style object | object | - |
| tabPosition | Position of tabs | `top` \| `right` \| `bottom` \| `left` | `top` |
| type | Basic style of tabs | `line` \| `card` \| `editable-card` | `line` |
| onChange | Callback executed when active tab is changed | Function(activeKey) {} | - |
| onEdit | Callback executed when tab is added or removed. Only works while `type="editable-card"` | (targetKey, action): void | - |
| onNextClick | Callback executed when next button is clicked | Function | - |
| onPrevClick | Callback executed when prev button is clicked | Function | - |
| onTabClick | Callback executed when tab is clicked | Function(key: string, event: MouseEvent) | - |
| keyboard | whether to turn on keyboard navigation | boolean | true |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| activeKey | Current TabPane's key | string | - | |
| animated | Whether to change tabs with animation. Only works while `tabPosition="top"\|"bottom"` | boolean \| {inkBar:boolean, tabPane:boolean} | `true`, `false` when `type="card"` | |
| renderTabBar | replace the TabBar | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | |
| defaultActiveKey | Initial active TabPane's key, if `activeKey` is not set. | string | - | |
| hideAdd | Hide plus icon or not. Only works while `type="editable-card"` | boolean | `false` | |
| size | preset tab bar size | `large` \| `default` \| `small` | `default` | |
| tabBarExtraContent | Extra content in tab bar | React.ReactNode | - | |
| tabBarGutter | The gap between tabs | number | - | |
| tabBarStyle | Tab bar style object | object | - | |
| tabPosition | Position of tabs | `top` \| `right` \| `bottom` \| `left` | `top` | |
| type | Basic style of tabs | `line` \| `card` \| `editable-card` | `line` | |
| onChange | Callback executed when active tab is changed | Function(activeKey) {} | - | |
| onEdit | Callback executed when tab is added or removed. Only works while `type="editable-card"` | (targetKey, action): void | - | |
| onTabClick | Callback executed when tab is clicked | Function(key: string, event: MouseEvent) | - | |
| onTabScroll | Trigger when tab scroll | Function({ direction: 'left' \| 'right' \| 'top' \| 'bottom' }) | - | 4.3.0 |
| keyboard | whether to turn on keyboard navigation | boolean | true | |
More option at [rc-tabs option](https://github.com/react-component/tabs#tabs)

View File

@ -1,205 +1,65 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import RcTabs, { TabPane } from 'rc-tabs';
import TabContent from 'rc-tabs/lib/TabContent';
import RcTabs, { TabPane, TabsProps as RcTabsProps } from 'rc-tabs';
import { EditableConfig } from 'rc-tabs/lib/interface';
import classNames from 'classnames';
import omit from 'omit.js';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
import PlusOutlined from '@ant-design/icons/PlusOutlined';
import TabBar from './TabBar';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { isFlexSupported } from '../_util/styleChecker';
import { cloneElement, isValidElement } from '../_util/reactNode';
import devWarning from '../_util/devWarning';
import { ConfigContext } from '../config-provider';
import { SizeType } from '../config-provider/SizeContext';
export type TabsType = 'line' | 'card' | 'editable-card';
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';
export interface TabsProps {
activeKey?: string;
defaultActiveKey?: string;
hideAdd?: boolean;
onChange?: (activeKey: string) => void;
onTabClick?: Function;
onPrevClick?: React.MouseEventHandler<HTMLElement>;
onNextClick?: React.MouseEventHandler<HTMLElement>;
tabBarExtraContent?: React.ReactNode | null;
tabBarStyle?: React.CSSProperties;
export interface TabsProps extends Omit<RcTabsProps, 'editable'> {
type?: TabsType;
tabPosition?: TabsPosition;
onEdit?: (targetKey: string | React.MouseEvent<HTMLElement>, action: 'add' | 'remove') => void;
size?: 'large' | 'default' | 'small';
style?: React.CSSProperties;
prefixCls?: string;
className?: string;
animated?: boolean | { inkBar: boolean; tabPane: boolean };
tabBarGutter?: number;
renderTabBar?: (
props: TabsProps,
DefaultTabBar: React.ComponentClass<any>,
) => React.ReactElement<any>;
destroyInactiveTabPane?: boolean;
keyboard?: boolean;
size?: SizeType;
hideAdd?: boolean;
onEdit?: (e: React.MouseEvent | React.KeyboardEvent | string, action: 'add' | 'remove') => void;
}
// Tabs
export interface TabPaneProps {
/** 选项卡头显示文字 */
tab?: React.ReactNode | string;
style?: React.CSSProperties;
closable?: boolean;
className?: string;
disabled?: boolean;
forceRender?: boolean;
key?: string;
closeIcon?: React.ReactNode;
}
function Tabs({ type, className, size, onEdit, hideAdd, ...props }: TabsProps) {
const { prefixCls: customizePrefixCls } = props;
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('tabs', customizePrefixCls);
export default class Tabs extends React.Component<TabsProps, any> {
static TabPane = TabPane as React.ClassicComponentClass<TabPaneProps>;
static defaultProps = {
hideAdd: false,
tabPosition: 'top' as TabsPosition,
};
componentDidMount() {
const NO_FLEX = ' no-flex';
const tabNode = ReactDOM.findDOMNode(this) as Element;
if (tabNode && !isFlexSupported && tabNode.className.indexOf(NO_FLEX) === -1) {
tabNode.className += NO_FLEX;
}
let editable: EditableConfig | undefined;
if (type === 'editable-card') {
editable = {
onEdit: (editType, { key, event }) => {
onEdit?.(editType === 'add' ? event : key!, editType);
},
removeIcon: <CloseOutlined />,
addIcon: <PlusOutlined />,
showAdd: hideAdd !== false,
};
}
removeTab = (targetKey: string, e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
if (!targetKey) {
return;
}
devWarning(
!('onPrevClick' in props) && !('onNextClick' in props),
'Tabs',
'`onPrevClick` and `onNextClick` has been removed. Please use `onTabScroll` instead.',
);
const { onEdit } = this.props;
if (onEdit) {
onEdit(targetKey, 'remove');
}
};
handleChange = (activeKey: string) => {
const { onChange } = this.props;
if (onChange) {
onChange(activeKey);
}
};
createNewTab = (targetKey: React.MouseEvent<HTMLElement>) => {
const { onEdit } = this.props;
if (onEdit) {
onEdit(targetKey, 'add');
}
};
renderTabs = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className = '',
size,
type = 'line',
tabPosition,
children,
animated = true,
hideAdd,
} = this.props;
let { tabBarExtraContent } = this.props;
let tabPaneAnimated = typeof animated === 'object' ? animated.tabPane : animated;
// card tabs should not have animation
if (type !== 'line') {
tabPaneAnimated = 'animated' in this.props ? tabPaneAnimated : false;
}
const prefixCls = getPrefixCls('tabs', customizePrefixCls);
const cls = classNames(className, {
[`${prefixCls}-vertical`]: tabPosition === 'left' || tabPosition === 'right',
[`${prefixCls}-${size}`]: !!size,
[`${prefixCls}-card`]: type.indexOf('card') >= 0,
[`${prefixCls}-${type}`]: true,
[`${prefixCls}-no-animation`]: !tabPaneAnimated,
});
// only card type tabs can be added and closed
let childrenWithClose: React.ReactElement<any>[] = [];
if (type === 'editable-card') {
childrenWithClose = [];
React.Children.forEach(children as React.ReactNode, (child, index) => {
if (!isValidElement(child)) return child;
let { closable } = child.props;
const { closeIcon: customCloseIcon } = child.props;
closable = typeof closable === 'undefined' ? true : closable;
const customIcon = customCloseIcon ? (
React.cloneElement(customCloseIcon, {
className: `${prefixCls}-close-x`,
onClick: e => this.removeTab(child.key as string, e),
})
) : (
<CloseOutlined
className={`${prefixCls}-close-x`}
onClick={e => this.removeTab(child.key as string, e)}
/>
);
const closeIcon = closable ? customIcon : null;
childrenWithClose.push(
cloneElement(child, {
tab: (
<div className={closable ? undefined : `${prefixCls}-tab-unclosable`}>
{child.props.tab}
{closeIcon}
</div>
),
key: child.key || index,
}),
);
});
// Add new tab handler
if (!hideAdd) {
tabBarExtraContent = (
<span>
<PlusOutlined className={`${prefixCls}-new-tab`} onClick={this.createNewTab} />
{tabBarExtraContent}
</span>
);
}
}
tabBarExtraContent = tabBarExtraContent ? (
<div className={`${prefixCls}-extra-content`}>{tabBarExtraContent}</div>
) : null;
const { ...tabBarProps } = this.props;
const contentCls: string = classNames(
`${prefixCls}-${tabPosition}-content`,
type.indexOf('card') >= 0 && `${prefixCls}-card-content`,
);
return (
<RcTabs
{...this.props}
prefixCls={prefixCls}
className={cls}
tabBarPosition={tabPosition}
direction={direction}
renderTabBar={() => (
<TabBar {...omit(tabBarProps, ['className'])} tabBarExtraContent={tabBarExtraContent} />
)}
renderTabContent={() => (
<TabContent className={contentCls} animated={tabPaneAnimated} animatedWithMargin />
)}
onChange={this.handleChange}
>
{childrenWithClose.length > 0 ? childrenWithClose : children}
</RcTabs>
);
};
render() {
return <ConfigConsumer>{this.renderTabs}</ConfigConsumer>;
}
return (
<RcTabs
direction={direction}
{...props}
moreTransitionName="slide-up"
className={classNames(className, {
[`${prefixCls}-${size}`]: size,
[`${prefixCls}-card`]: ['card', 'editable-card'].includes(type as string),
[`${prefixCls}-editable-card`]: type === 'editable-card',
})}
editable={editable}
moreIcon={<EllipsisOutlined />}
prefixCls={prefixCls}
/>
);
}
Tabs.TabPane = TabPane;
export default Tabs;

View File

@ -22,25 +22,24 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
### Tabs
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| activeKey | 当前激活 tab 面板的 key | string | 无 |
| animated | 是否使用动画切换 Tabs`tabPosition=top|bottom` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false |
| renderTabBar | 替换 TabBar用于二次封装标签头 | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | 无 |
| defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | 第一个面板 |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false |
| size | 大小,提供 `large` `default``small` 三种大小 | string | 'default' |
| tabBarExtraContent | tab bar 上额外的元素 | React.ReactNode | 无 |
| tabBarGutter | tabs 之间的间隙 | number | 无 |
| tabBarStyle | tab bar 的样式对象 | object | - |
| tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | 'top' |
| type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | 'line' |
| onChange | 切换面板的回调 | Function(activeKey) {} | 无 |
| onEdit | 新增和删除页签的回调,在 `type="editable-card"` 时有效 | (targetKey, action): void | 无 |
| onNextClick | next 按钮被点击的回调 | Function | 无 |
| onPrevClick | prev 按钮被点击的回调 | Function | 无 |
| onTabClick | tab 被点击的回调 | Function(key: string, event: MouseEvent) | 无 |
| keyboard | 开启键盘切换功能 | boolean | true |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| activeKey | 当前激活 tab 面板的 key | string | - | |
| animated | 是否使用动画切换 Tabs`tabPosition=top|bottom` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false | |
| renderTabBar | 替换 TabBar用于二次封装标签头 | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | |
| defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | 第一个面板 | |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | |
| size | 大小,提供 `large` `default``small` 三种大小 | string | 'default' | |
| tabBarExtraContent | tab bar 上额外的元素 | React.ReactNode | - | |
| tabBarGutter | tabs 之间的间隙 | number | - | |
| tabBarStyle | tab bar 的样式对象 | object | - | |
| tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | 'top' | |
| type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | 'line' | |
| onChange | 切换面板的回调 | Function(activeKey) {} | - | |
| onEdit | 新增和删除页签的回调,在 `type="editable-card"` 时有效 | (targetKey, action): void | - | |
| onTabClick | tab 被点击的回调 | Function(key: string, event: MouseEvent) | - | |
| onTabScroll | tab 滚动时触发 | Function({ direction: 'left' \| 'right' \| 'top' \| 'bottom' }) | - | 4.3.0 |
| keyboard | 开启键盘切换功能 | boolean | true | |
### Tabs.TabPane

View File

@ -1,181 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
// card style
.@{tab-prefix-cls} {
&&-card &-card-bar &-ink-bar {
visibility: hidden;
}
&&-card &-card-bar &-tab {
margin: 0;
margin-right: @tabs-card-gutter;
padding: @tabs-card-horizontal-padding;
line-height: 22px;
background: @tabs-card-head-background;
border: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
transition: all 0.3s @ease-in-out;
}
&&-card &-card-bar &-tab-active {
color: @tabs-card-active-color;
background: @component-background;
border-color: @border-color-split;
border-bottom: @border-width-base solid @component-background;
&::before {
border-top: @tabs-card-tab-active-border-top;
}
}
&&-small &-card-bar &-tab {
padding: @tabs-card-horizontal-padding-sm;
}
&&-card &-card-bar &-tab-disabled {
color: @disabled-color;
}
&&-card &-card-bar &-tab-inactive {
padding: 0;
}
&&-card &-card-bar &-nav-wrap {
margin-bottom: 0;
}
&&-card &-card-bar &-tab &-close-x {
width: 16px;
height: @font-size-base;
margin-right: -5px;
margin-left: 3px;
overflow: hidden;
color: @text-color-secondary;
font-size: @font-size-sm;
vertical-align: middle;
transition: all 0.3s;
&:hover {
color: @heading-color;
}
}
&&-card &-card-content > &-tabpane,
&&-editable-card &-card-content > &-tabpane {
transition: none !important;
&-inactive {
overflow: hidden;
}
}
&&-card &-card-bar &-tab:hover .@{iconfont-css-prefix}-close {
opacity: 1;
}
&-extra-content {
line-height: @tabs-title-font-size * @line-height-base + extract(@tabs-horizontal-padding, 1) *
2;
.@{tab-prefix-cls}-new-tab {
position: relative;
width: 20px;
height: 20px;
color: @text-color;
font-size: 12px;
line-height: 20px;
text-align: center;
border: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: @tabs-card-active-color;
border-color: @tabs-card-active-color;
}
svg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
}
}
// https://github.com/ant-design/ant-design/issues/17865
&&-large &-extra-content {
line-height: @tabs-title-font-size-lg * @line-height-base +
extract(@tabs-horizontal-padding-lg, 1) * 2;
}
// https://github.com/ant-design/ant-design/issues/17865
&&-small &-extra-content {
line-height: @tabs-title-font-size-sm * @line-height-base +
extract(@tabs-horizontal-padding-sm, 1) * 2;
}
// https://github.com/ant-design/ant-design/issues/17865
&&-card &-extra-content {
line-height: @tabs-card-height;
}
// https://github.com/ant-design/ant-design/issues/4669
&-vertical&-card &-card-bar&-left-bar,
&-vertical&-card &-card-bar&-right-bar {
.@{tab-prefix-cls}-nav-container {
height: 100%;
}
.@{tab-prefix-cls}-tab {
margin-bottom: @margin-sm;
border-bottom: @border-width-base @border-style-base @border-color-split;
&-active {
padding-bottom: 4px;
}
&:last-child {
margin-bottom: @margin-sm;
}
}
.@{tab-prefix-cls}-new-tab {
width: 90%;
}
}
&-vertical&-card&-left &-card-bar&-left-bar {
.@{tab-prefix-cls}-nav-wrap {
margin-right: 0;
}
.@{tab-prefix-cls}-tab {
margin-right: 1px;
border-right: 0;
border-radius: @border-radius-base 0 0 @border-radius-base;
&-active {
margin-right: -1px;
padding-right: @padding-md + 2px;
}
}
}
&-vertical&-card&-right &-card-bar&-right-bar {
.@{tab-prefix-cls}-nav-wrap {
margin-left: 0;
}
.@{tab-prefix-cls}-tab {
margin-left: 1px;
border-left: 0;
border-radius: 0 @border-radius-base @border-radius-base 0;
&-active {
margin-left: -1px;
padding-left: @padding-md + 2px;
}
}
}
// https://github.com/ant-design/ant-design/issues/9104
& &-card-bar&-bottom-bar &-tab {
height: auto;
border-top-color: @border-color-split;
border-bottom: @border-width-base @border-style-base @border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
}
& &-card-bar&-bottom-bar &-tab-active {
color: @primary-color;
border-top-color: @component-background;
}
}

View File

@ -0,0 +1,90 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './index';
.@{tab-prefix-cls}-card {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
margin: 0;
padding: @tabs-card-horizontal-padding;
background: @tabs-card-head-background;
border: @border-width-base @border-style-base @border-color-split;
transition: all @animation-duration-slow @ease-in-out;
&-active {
color: @tabs-card-active-color;
background: @component-background;
}
}
.@{tab-prefix-cls}-ink-bar {
visibility: hidden;
}
}
// ========================== Top & Bottom ==========================
&.@{tab-prefix-cls}-top,
&.@{tab-prefix-cls}-bottom {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab:not(:last-of-type) {
margin-right: @tabs-card-gutter;
}
}
}
&.@{tab-prefix-cls}-top {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
border-radius: @border-radius-base @border-radius-base 0 0;
&-active {
border-bottom-color: @component-background;
}
}
}
}
&.@{tab-prefix-cls}-bottom {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
border-radius: 0 0 @border-radius-base @border-radius-base;
&-active {
border-top-color: @component-background;
}
}
}
}
// ========================== Left & Right ==========================
&.@{tab-prefix-cls}-left,
&.@{tab-prefix-cls}-right {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab:not(:last-of-type) {
margin-bottom: @tabs-card-gutter;
}
}
}
&.@{tab-prefix-cls}-left {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
border-radius: @border-radius-base 0 0 @border-radius-base;
&-active {
border-right-color: @component-background;
}
}
}
}
&.@{tab-prefix-cls}-right {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
border-radius: 0 @border-radius-base @border-radius-base 0;
&-active {
border-left-color: @component-background;
}
}
}
}
}

View File

@ -0,0 +1,59 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './index';
.@{tab-prefix-cls}-dropdown {
.reset-component;
position: absolute;
top: -9999px;
left: -9999px;
z-index: @zindex-dropdown;
display: block;
&-hidden {
display: none;
}
&-menu {
max-height: 200px;
margin: 0;
padding: @dropdown-edge-child-vertical-padding 0;
overflow-y: auto;
text-align: left;
list-style-type: none;
background-color: @dropdown-menu-bg;
background-clip: padding-box;
border-radius: @border-radius-base;
outline: none;
box-shadow: @box-shadow-base;
&-item {
width: 120px;
margin: 0;
padding: @dropdown-vertical-padding @control-padding-horizontal;
overflow: hidden;
color: @text-color;
font-weight: normal;
font-size: @dropdown-font-size;
line-height: @dropdown-line-height;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: @item-hover-bg;
}
&-disabled {
&,
&:hover {
color: @disabled-color;
background: transparent;
cursor: not-allowed;
}
}
}
}
}

View File

@ -1,442 +1,195 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './card-style';
@import './size';
@import './rtl';
@import './position';
@import './dropdown';
@import './card';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
// Hidden content
.tabs-hidden-content() {
height: 0;
padding: 0 !important;
overflow: hidden;
opacity: 0;
pointer-events: none;
input {
visibility: hidden;
}
}
.@{tab-prefix-cls} {
.reset-component;
position: relative;
display: flex;
overflow: hidden;
.clearfix;
&-ink-bar {
position: absolute;
bottom: 1px;
left: 0;
z-index: 1;
box-sizing: border-box;
width: 0;
height: 2px;
background-color: @tabs-ink-bar-color;
transform-origin: 0 0;
}
&-bar {
margin: @tabs-bar-margin;
border-bottom: @border-width-base @border-style-base @border-color-split;
outline: none;
transition: padding 0.3s @ease-in-out;
}
&-nav-container {
// ========================== Navigation ==========================
> .@{tab-prefix-cls}-nav {
position: relative;
box-sizing: border-box;
margin-bottom: -1px;
overflow: hidden;
font-size: @tabs-title-font-size;
line-height: @line-height-base;
white-space: nowrap;
transition: padding 0.3s @ease-in-out;
.clearfix;
display: flex;
flex: none;
align-items: center;
&-scrolling {
padding-right: @tabs-scrolling-size;
padding-left: @tabs-scrolling-size;
}
}
// https://github.com/ant-design/ant-design/issues/9104
&-bottom &-bottom-bar {
margin-top: @margin-md;
margin-bottom: 0;
border-top: @border-width-base @border-style-base @border-color-split;
border-bottom: none;
}
&-bottom &-bottom-bar &-ink-bar {
top: 1px;
bottom: auto;
}
&-bottom &-bottom-bar &-nav-container {
margin-top: -1px;
margin-bottom: 0;
}
&-tab-prev,
&-tab-next {
position: absolute;
z-index: 2;
width: 0;
height: 100%;
color: @text-color-secondary;
text-align: center;
background-color: transparent;
border: 0;
cursor: pointer;
opacity: 0;
transition: width 0.3s @ease-in-out, opacity 0.3s @ease-in-out, color 0.3s @ease-in-out;
user-select: none;
pointer-events: none;
&.@{tab-prefix-cls}-tab-arrow-show {
width: @tabs-scrolling-size;
height: 100%;
opacity: 1;
pointer-events: auto;
}
&:hover {
color: @text-color;
}
&-icon {
position: absolute;
top: 50%;
left: 50%;
font-weight: bold;
font-style: normal;
font-variant: normal;
line-height: inherit;
text-align: center;
text-transform: none;
transform: translate(-50%, -50%);
&-target {
display: block;
.iconfont-size-under-12px(10px);
}
}
}
&-tab-btn-disabled {
cursor: not-allowed;
&,
&:hover {
color: @disabled-color;
}
}
&-tab-next {
right: 2px;
}
&-tab-prev {
left: 0;
:root & {
filter: none;
}
}
&-nav-wrap {
margin-bottom: -1px;
overflow: hidden;
}
&-nav-scroll {
overflow: hidden;
white-space: nowrap;
}
&-nav {
position: relative;
display: inline-block;
box-sizing: border-box;
margin: 0;
padding-left: 0;
list-style: none;
transition: transform 0.3s @ease-in-out;
&::before,
&::after {
display: table;
content: ' ';
}
&::after {
clear: both;
}
.@{tab-prefix-cls}-tab {
.@{tab-prefix-cls}-nav-wrap {
position: relative;
display: inline-block;
box-sizing: border-box;
height: 100%;
margin: @tabs-horizontal-margin;
padding: @tabs-horizontal-padding;
text-decoration: none;
cursor: pointer;
transition: color 0.3s @ease-in-out;
display: flex;
flex: auto;
overflow: hidden;
white-space: nowrap;
transform: translate(0); // Fix chrome render bug
&::before {
// >>>>> Ping shadow
&::before,
&::after {
position: absolute;
top: -1px;
left: 0;
width: 100%;
border-top: 2px solid transparent;
border-radius: @border-radius-base @border-radius-base 0 0;
transition: all 0.3s;
z-index: 1;
opacity: 0;
transition: opacity @animation-duration-slow;
content: '';
pointer-events: none;
}
}
&:last-child {
margin-right: 0;
.@{tab-prefix-cls}-nav-list {
position: relative;
display: flex;
transition: transform @animation-duration-slow;
}
// >>>>>>>> Operations
.@{tab-prefix-cls}-nav-operations {
display: flex;
align-self: stretch;
&-hidden {
position: absolute;
visibility: hidden;
pointer-events: none;
}
}
.@{tab-prefix-cls}-nav-more {
position: relative;
padding: @tabs-card-horizontal-padding;
background: transparent;
border: 0;
&::after {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 5px;
transform: translateY(100%);
content: '';
}
}
.@{tab-prefix-cls}-nav-add {
padding: 0 @padding-xs;
background: @tabs-card-head-background;
border: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
outline: none;
cursor: pointer;
transition: all @animation-duration-slow @ease-in-out;
&:hover {
color: @tabs-hover-color;
}
&:active,
&:focus {
color: @tabs-active-color;
}
}
}
&-extra-content {
flex: none;
}
// ============================ InkBar ============================
&-ink-bar {
position: absolute;
background: @tabs-ink-bar-color;
pointer-events: none;
}
// ============================= Tabs =============================
&-tab {
position: relative;
display: inline-flex;
align-items: center;
margin: @tabs-horizontal-margin;
padding: @tabs-horizontal-padding;
font-size: @tabs-title-font-size;
background: transparent;
border: 0;
outline: none;
cursor: pointer;
&:last-of-type {
margin-right: 0;
margin-left: 0;
}
& &-remove {
flex: none;
margin-right: -@margin-xss;
margin-left: @margin-xs;
color: @text-color-secondary;
font-size: @font-size-sm;
outline: none;
transition: all @animation-duration-slow;
.@{iconfont-css-prefix} {
margin: 0;
}
&:hover {
color: @heading-color;
}
&:focus,
&:active {
color: @tabs-active-color;
}
}
.@{iconfont-css-prefix} {
margin-right: @margin-sm;
}
&:focus,
&:active {
color: @tabs-active-color;
}
&-active {
color: @tabs-highlight-color;
font-weight: 500;
outline: none;
}
&:hover {
color: @tabs-hover-color;
}
&-disabled {
&,
&:hover {
color: @disabled-color;
cursor: not-allowed;
}
}
&&-active {
color: @tabs-highlight-color;
font-weight: 500;
}
&&-disabled {
color: @disabled-color;
cursor: not-allowed;
}
.@{iconfont-css-prefix} {
margin-right: @margin-sm;
}
}
.@{tab-prefix-cls}-large-bar {
.@{tab-prefix-cls}-nav-container {
font-size: @tabs-title-font-size-lg;
// =========================== TabPanes ===========================
&-content {
&-holder {
flex: auto;
}
.@{tab-prefix-cls}-tab {
padding: @tabs-horizontal-padding-lg;
display: flex;
width: 100%;
&-animated {
transition: margin @animation-duration-slow;
}
}
.@{tab-prefix-cls}-small-bar {
.@{tab-prefix-cls}-nav-container {
font-size: @tabs-title-font-size-sm;
}
.@{tab-prefix-cls}-tab {
padding: @tabs-horizontal-padding-sm;
}
}
// Create an empty element to avoid margin collapsing
// https://github.com/ant-design/ant-design/issues/18103
&-content::before {
display: block;
overflow: hidden;
content: '';
}
.@{tab-prefix-cls}-tabpane {
&-tabpane {
flex: none;
width: 100%;
outline: none;
}
// Horizontal Content
.@{tab-prefix-cls}-top-content,
.@{tab-prefix-cls}-bottom-content {
width: 100%;
> .@{tab-prefix-cls}-tabpane {
flex-shrink: 0;
width: 100%;
-webkit-backface-visibility: hidden;
opacity: 1;
transition: opacity 0.45s;
}
> .@{tab-prefix-cls}-tabpane-inactive {
.tabs-hidden-content();
}
&.@{tab-prefix-cls}-content-animated {
display: flex;
flex-direction: row;
transition: margin-left 0.3s @ease-in-out;
will-change: margin-left;
}
}
// Vertical Bar
.@{tab-prefix-cls}-left-bar,
.@{tab-prefix-cls}-right-bar {
height: 100%;
border-bottom: 0;
.@{tab-prefix-cls}-tab-arrow-show {
width: 100%;
height: @tabs-scrolling-size;
}
.@{tab-prefix-cls}-tab {
display: block;
float: none;
margin: @tabs-vertical-margin;
padding: @tabs-vertical-padding;
text-align: center;
&:last-child {
margin-bottom: 0;
}
}
.@{tab-prefix-cls}-extra-content {
text-align: center;
}
.@{tab-prefix-cls}-nav-scroll {
width: auto;
}
.@{tab-prefix-cls}-nav-container,
.@{tab-prefix-cls}-nav-wrap {
height: 100%;
}
.@{tab-prefix-cls}-nav-container {
margin-bottom: 0;
&.@{tab-prefix-cls}-nav-container-scrolling {
padding: @tabs-scrolling-size 0;
}
}
.@{tab-prefix-cls}-nav-wrap {
margin-bottom: 0;
}
.@{tab-prefix-cls}-nav {
width: 100%;
}
.@{tab-prefix-cls}-ink-bar {
top: 0;
bottom: auto;
left: auto;
width: 2px;
height: 0;
}
.@{tab-prefix-cls}-tab-next {
right: 0;
bottom: 0;
width: 100%;
height: @tabs-scrolling-size;
}
.@{tab-prefix-cls}-tab-prev {
top: 0;
width: 100%;
height: @tabs-scrolling-size;
}
}
// Vertical Content
.@{tab-prefix-cls}-left-content,
.@{tab-prefix-cls}-right-content {
width: auto;
margin-top: 0 !important;
overflow: hidden;
}
// Vertical - Left
.@{tab-prefix-cls}-left-bar {
float: left;
margin-right: -1px;
margin-bottom: 0;
border-right: @border-width-base @border-style-base @border-color-split;
.@{tab-prefix-cls}-nav-container {
margin-right: -1px;
}
.@{tab-prefix-cls}-nav-wrap {
margin-right: -1px;
}
.@{tab-prefix-cls}-ink-bar {
right: 1px;
}
}
.@{tab-prefix-cls}-left-content {
padding-left: @padding-lg;
border-left: @border-width-base @border-style-base @border-color-split;
}
// Vertical - Right
.@{tab-prefix-cls}-right-bar {
float: right;
margin-bottom: 0;
margin-left: -1px;
border-left: @border-width-base @border-style-base @border-color-split;
.@{tab-prefix-cls}-nav-container {
margin-left: -1px;
}
.@{tab-prefix-cls}-nav-wrap {
margin-left: -1px;
}
.@{tab-prefix-cls}-ink-bar {
left: 1px;
}
}
.@{tab-prefix-cls}-right-content {
padding-right: @padding-lg;
border-right: @border-width-base @border-style-base @border-color-split;
}
}
.@{tab-prefix-cls}-top .@{tab-prefix-cls}-ink-bar-animated,
.@{tab-prefix-cls}-bottom .@{tab-prefix-cls}-ink-bar-animated {
transition: transform 0.3s @ease-in-out, width 0.2s @ease-in-out, left 0.3s @ease-in-out;
}
.@{tab-prefix-cls}-left .@{tab-prefix-cls}-ink-bar-animated,
.@{tab-prefix-cls}-right .@{tab-prefix-cls}-ink-bar-animated {
transition: transform 0.3s @ease-in-out, height 0.2s @ease-in-out, top 0.3s @ease-in-out;
}
// No animation
.tabs-no-animation() {
> .@{tab-prefix-cls}-content-animated {
margin-left: 0 !important;
transform: none !important;
}
> .@{tab-prefix-cls}-tabpane-inactive {
.tabs-hidden-content();
}
}
.no-flex,
.@{tab-prefix-cls}-no-animation {
> .@{tab-prefix-cls}-content {
.tabs-no-animation();
}
}
.@{tab-prefix-cls}-left-content,
.@{tab-prefix-cls}-right-content {
.tabs-no-animation();
}
@import './rtl.less';

View File

@ -0,0 +1,186 @@
@import './index';
.@{tab-prefix-cls} {
// ========================== Top & Bottom ==========================
&-top,
&-bottom {
flex-direction: column;
> .@{tab-prefix-cls}-nav {
margin: @tabs-bar-margin;
&::before {
position: absolute;
right: 0;
left: 0;
border-bottom: @border-width-base @border-style-base @border-color-split;
content: '';
}
.@{tab-prefix-cls}-ink-bar {
height: 2px;
&-animated {
transition: width @animation-duration-slow, left @animation-duration-slow,
right @animation-duration-slow;
}
}
.@{tab-prefix-cls}-nav-wrap {
&::before,
&::after {
top: 0;
bottom: 0;
width: 30px;
}
&::before {
left: 0;
box-shadow: inset 10px 0 8px -8px fade(@shadow-color, 8%);
}
&::after {
right: 0;
box-shadow: inset -10px 0 8px -8px fade(@shadow-color, 8%);
}
&.@{tab-prefix-cls}-nav-wrap-ping-left::before {
opacity: 1;
}
&.@{tab-prefix-cls}-nav-wrap-ping-right::after {
opacity: 1;
}
}
}
}
&-top {
> .@{tab-prefix-cls}-nav {
&::before {
bottom: 0;
}
.@{tab-prefix-cls}-ink-bar {
bottom: 0;
}
}
}
&-bottom {
> .@{tab-prefix-cls}-nav {
order: 1;
margin-top: @margin-md;
margin-bottom: 0;
&::before {
top: 0;
}
.@{tab-prefix-cls}-ink-bar {
top: 0;
}
}
> .@{tab-prefix-cls}-content-holder {
order: 0;
}
}
// ========================== Left & Right ==========================
&-left,
&-right {
> .@{tab-prefix-cls}-nav {
flex-direction: column;
min-width: 50px;
// >>>>>>>>>>> Tab
.@{tab-prefix-cls}-tab {
margin: @tabs-vertical-margin;
padding: @tabs-vertical-padding;
text-align: center;
&:last-of-type {
margin-bottom: 0;
}
}
// >>>>>>>>>>> Nav
.@{tab-prefix-cls}-nav-wrap {
flex-direction: column;
&::before,
&::after {
right: 0;
left: 0;
height: 30px;
}
&::before {
top: 0;
box-shadow: inset 0 10px 8px -8px fade(@shadow-color, 8%);
}
&::after {
bottom: 0;
box-shadow: inset 0 -10px 8px -8px fade(@shadow-color, 8%);
}
&.@{tab-prefix-cls}-nav-wrap-ping-top::before {
opacity: 1;
}
&.@{tab-prefix-cls}-nav-wrap-ping-bottom::after {
opacity: 1;
}
}
// >>>>>>>>>>> Ink Bar
.@{tab-prefix-cls}-ink-bar {
width: 2px;
&-animated {
transition: height @animation-duration-slow, top @animation-duration-slow;
}
}
.@{tab-prefix-cls}-nav-list,
.@{tab-prefix-cls}-nav-operations {
flex-direction: column;
}
}
}
&-left {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-ink-bar {
right: 0;
}
}
> .@{tab-prefix-cls}-content-holder {
margin-left: -@border-width-base;
border-left: @border-width-base @border-style-base @border-color-split;
> .@{tab-prefix-cls}-content > .@{tab-prefix-cls}-tabpane {
padding-left: @padding-lg;
}
}
}
&-right {
> .@{tab-prefix-cls}-nav {
order: 1;
.@{tab-prefix-cls}-ink-bar {
left: 0;
}
}
> .@{tab-prefix-cls}-content-holder {
order: 0;
margin-right: -@border-width-base;
border-right: @border-width-base @border-style-base @border-color-split;
> .@{tab-prefix-cls}-content > .@{tab-prefix-cls}-tabpane {
padding-right: @padding-lg;
}
}
}
}

View File

@ -1,83 +1,61 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './card-style';
@import './index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
.@{tab-prefix-cls}-rtl {
direction: rtl;
.@{tab-prefix-cls} {
&-rtl {
direction: rtl;
}
&-ink-bar {
.@{tab-prefix-cls}-rtl & {
right: 0;
left: auto;
}
}
&-tab-next {
.@{tab-prefix-cls}-rtl & {
right: auto;
left: 2px;
}
}
&-tab-prev {
.@{tab-prefix-cls}-rtl & {
right: 2px;
left: auto;
}
}
&-nav {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
.@{tab-prefix-cls}-rtl & {
margin: @tabs-horizontal-margin-rtl;
margin: @tabs-horizontal-margin-rtl;
&:last-of-type {
margin-left: 0;
}
.@{iconfont-css-prefix} {
.@{tab-prefix-cls}-rtl & {
margin-right: 0;
margin-left: @margin-xs;
margin-right: 0;
margin-left: @margin-sm;
}
.@{tab-prefix-cls}-tab-remove {
margin-right: @margin-xs;
margin-left: -@margin-xss;
.@{iconfont-css-prefix} {
margin: 0;
}
}
}
}
.@{tab-prefix-cls}-left-bar,
.@{tab-prefix-cls}-right-bar {
.@{tab-prefix-cls}-tab {
.@{tab-prefix-cls}-rtl& {
margin: @tabs-vertical-margin;
}
&:last-child {
margin-bottom: 0;
}
&.@{tab-prefix-cls}-left {
> .@{tab-prefix-cls}-nav {
order: 1;
}
> .@{tab-prefix-cls}-content-holder {
order: 0;
}
}
.@{tab-prefix-cls}-right-bar {
.@{tab-prefix-cls}-ink-bar {
.@{tab-prefix-cls}-rtl& {
right: auto;
left: 1px;
&.@{tab-prefix-cls}-right {
> .@{tab-prefix-cls}-nav {
order: 0;
}
> .@{tab-prefix-cls}-content-holder {
order: 1;
}
}
// ====================== Card ======================
&.@{tab-prefix-cls}-card {
&.@{tab-prefix-cls}-top,
&.@{tab-prefix-cls}-bottom {
> .@{tab-prefix-cls}-nav {
button.@{tab-prefix-cls}-tab:not(:last-of-type) {
margin: 0 0 0 @tabs-card-gutter;
}
}
}
}
// card
&&-card &-card-bar &-tab &-close-x {
.@{tab-prefix-cls}-rtl& {
margin-right: 3px;
margin-left: -5px;
}
}
&-extra-content {
.@{tab-prefix-cls}-rtl & {
float: left !important;
}
}
}

View File

@ -0,0 +1,41 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './index';
.@{tab-prefix-cls} {
&-small {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
padding: @tabs-horizontal-padding-sm;
font-size: @tabs-title-font-size-sm;
}
}
}
&-large {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
padding: @tabs-horizontal-padding-lg;
font-size: @tabs-title-font-size-lg;
}
}
}
&-card {
&.@{tab-prefix-cls}-small {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
padding: @tabs-card-horizontal-padding-sm;
}
}
}
&.@{tab-prefix-cls}-large {
> .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab {
padding: @tabs-card-horizontal-padding-lg;
}
}
}
}
}

View File

@ -416,15 +416,7 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
renderContent() {
const { ellipsisContent, isEllipsis, expanded } = this.state;
const {
component,
children,
className,
type,
disabled,
style,
...restProps
} = this.props;
const { component, children, className, type, disabled, style, ...restProps } = this.props;
const { direction } = this.context;
const { rows, suffix } = this.getEllipsis();

View File

@ -120,7 +120,7 @@
"rc-collapse": "~2.0.0",
"rc-dialog": "~8.0.0",
"rc-drawer": "~4.0.0",
"rc-dropdown": "~3.0.0",
"rc-dropdown": "~3.1.2",
"rc-field-form": "~1.4.1",
"rc-input-number": "~5.0.0",
"rc-mentions": "~1.2.0",
@ -136,7 +136,7 @@
"rc-steps": "~3.6.0",
"rc-switch": "~3.2.0",
"rc-table": "~7.7.2",
"rc-tabs": "~10.1.1",
"rc-tabs": "~11.2.0",
"rc-tooltip": "~4.2.0",
"rc-tree": "~3.3.0",
"rc-tree-select": "~3.2.0",

View File

@ -22,7 +22,7 @@
<link rel="stylesheet/less" type="text/css" href="{{ root }}color.less" />
<script src="https://polyfill.alicdn.com/polyfill.min.js?features=default,es2015,Intl"></script>
<script>
(function() {
(function () {
function isLocalStorageNameSupported() {
var testKey = 'test';
var storage = window.localStorage;
@ -100,10 +100,14 @@
</script>
<!-- Hotjar Tracking Code for ant.design -->
<script>
(function(h, o, t, j, a, r) {
(function (h, o, t, j, a, r) {
if (location.hostname === 'localhost') {
return;
}
h.hj =
h.hj ||
function() {
function () {
(h.hj.q = h.hj.q || []).push(arguments);
};
h._hjSettings = { hjid: 473408, hjsv: 5 };

View File

@ -37,7 +37,8 @@ const MORE_LIST: MoreProps[] = [
},
{
title: 'JCD 驱动 - 复杂系统设计应对之道',
description: '基于蚂蚁金服CTO线的业务土壤我们探索出以JCD为核心的企业级产品设计思维助力设计师在深耕业务上有章可循。',
description:
'基于蚂蚁金服CTO线的业务土壤我们探索出以JCD为核心的企业级产品设计思维助力设计师在深耕业务上有章可循。',
img: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*y-xiQoZzBCEAAAAAAAAAAABkARQnAQ',
date: '2020-01-17',
source: 'zhihu',
@ -45,8 +46,7 @@ const MORE_LIST: MoreProps[] = [
},
{
title: '折柱饼 +3 个套路,简单图表你真的会用吗?',
description:
'本文一句话概括:数据可视化中,如何用最简单的图表高效地传递信息。',
description: '本文一句话概括:数据可视化中,如何用最简单的图表高效地传递信息。',
img: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*Q1DMQpLCDQgAAAAAAAAAAABkARQnAQ',
date: '2020-11-28',
source: 'zhihu',