Tab 选项卡
如果项目中使用的是 0.x 版本的基础组件(@icedesign/base, @ali/ice, @alife/next),请在左侧导航顶部切换组件版本。
安装方法
- 在命令行中执行以下命令
npm install @alifd/next@latest -S
Guide
TAB 让用户可以在不同子任务、视图、模式之间切换,它具有全局导航的作用,是全局功能的主要展示和切换区域,一个TAB标记一个核心功能,TAB之间可以快速点击切换。该窗口包含2个以上的选项卡,所有选项卡可以排列在一行中,即使该用户界面被本地化后也是如此。提供平级的区域将大块内容进行收纳和展现,保持界面整洁。
何时使用
Fusion 提供了三级选项卡,分别用于不同的场景。
普通选项卡,引领整页面的内容,起导航的作用。
文本型选项卡。
包裹型选项卡,在页面结构中,只是局部展示,需要和其他内容结合出现。
胶囊型选项卡。
关于动效
如果您不想开启动效,可以通过设置 animation
属性值为 false
来关闭。
API
Tab
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
activeKey | 被激活的选项卡的 key, 赋值则tab为受控组件, 用户无法切换 | Number/String | - |
size | 尺寸可选值:'small', 'medium' | Enum | 'medium' |
shape | 外观形态可选值:'pure', 'wrapped', 'text', 'capsule' | Enum | 'pure' |
defaultActiveKey | 初始化时被激活的选项卡的 key | Number/String | - |
animation | 是否开启动效 | Boolean | true |
excessMode | 选项卡过多时的滑动模式可选值:'slide', 'dropdown' | Enum | 'slide' |
tabPosition | 导航选项卡的位置,只适用于包裹型(wrapped)选项卡可选值:'top', 'bottom', 'left', 'right' | Enum | 'top' |
triggerType | 激活选项卡的触发方式可选值:'hover', 'click' | Enum | 'click' |
lazyLoad | 是否延迟加载 TabPane 的内容, 默认开启, 即不提前渲染 | Boolean | true |
unmountInactiveTabs | 是否自动卸载未处于激活状态的选项卡 | Boolean | false |
navStyle | 导航条的自定义样式 | Object | - |
navClassName | 导航条的自定义样式类 | String | - |
contentStyle | 内容区容器的自定义样式 | Object | - |
contentClassName | 内容区容器的自定义样式类 | String | - |
extra | 导航栏附加内容 | ReactNode | - |
onClick | 点击单个选项卡时触发的回调签名:Function() => void | Function | () => {} |
onChange | 选项卡发生切换时的事件回调签名:Function(key: String/Number) => void参数:key: {String/Number} 改变后的 key | Function | () => {} |
onClose | 选项卡被关闭时的事件回调签名:Function(key: String/Number) => void参数:key: {String/Number} 关闭的选项卡的 key | Function | () => {} |
tabRender | 自定义选项卡模板渲染函数签名:Function(key: String, props: Object) => ReactNode参数:key: {String} 当前 Tab.Item 的 key 值props: {Object} 传给 Tab.Item 的所有属性键值对返回值:{ReactNode} 返回自定义组件 | Function | - |
popupProps | 弹层属性透传, 只有当 excessMode 为 dropdown 时生效 | Object | - |
Tab.Item
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
title | 选项卡标题 | ReactNode | - |
closeable | 单个选项卡是否可关闭 | Boolean | false |
disabled | 选项卡是否被禁用 | Boolean | - |
ARIA and KeyBoard
按键 | 说明 |
---|---|
Right Arrow | 切换至前一项Tab.Item |
Left Arrow | 切换至后一项Tab.Item |
代码示例
使用 Tab 最简单的例子。
查看源码在线预览
import { Tab } from '@alifd/next';
ReactDOM.render(
<Tab>
<Tab.Item title="Home" key="1">Home content</Tab.Item>
<Tab.Item title="Documentation" key="2">Doc content</Tab.Item>
<Tab.Item title="Help" key="3">Help Content</Tab.Item>
</Tab>
, mountNode);
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
根据使用场景及触发控件的类型,可以通过 shape
属性配置选项卡的类型,主要包括:
pure
普通选项卡(默认)wrapped
包裹型选项卡text
文本型选项卡capsule
胶囊型选项卡
查看源码在线预览
import { Tab } from '@alifd/next';
function onChange(key) {
console.log(key);
}
const tabs = [
{ tab: 'Home', key: 'home', content: 'This is home page' },
{ tab: 'Document', key: 'doc', content: 'This is document page' },
{ tab: 'API', key: 'api', content: 'This is api page' },
{ tab: 'Repo', key: 'repo', content: 'This ia repo link' }
];
const shapes = ['pure', 'wrapped', 'text', 'capsule'];
ReactDOM.render(
<div className="fusion-demo">
{
shapes.map(shape => (<div key={shape} className="fusion-demo-item">
<Tab shape={shape} onChange={onChange}>
{
tabs.map(tab => <Tab.Item title={tab.tab} key={tab.key}>{tab.content}</Tab.Item>)
}
</Tab>
</div>))
}
</div>
, mountNode);
.fusion-demo-item {
margin: 14px 0;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
组件尺寸,可以通过size
属性设置,提供medium
(默认)和small
两种尺寸,small
尺寸的选项卡组件可以用在弹出框等较狭窄的容器内。
查看源码在线预览
import { Tab } from '@alifd/next';
const tabs = [
{ tab: 'Home', key: 'home', content: 'This is home page' },
{ tab: 'Document', key: 'doc', content: 'This is document page' },
{ tab: 'API', key: 'api', content: 'This is api page' }
];
ReactDOM.render(
<div>
<Tab size="small">
{tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)}
</Tab>
<br />
<Tab size="small" shape="wrapped">
{tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)}
</Tab>
<br />
<Tab size="small" shape="text">
{tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)}
</Tab>
<br />
<Tab size="small" shape="capsule">
{tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)}
</Tab>
</div>, mountNode);
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
默认情况 Tab 不会提前渲染好所有的内容,而是根据 Tab 的激活情况依次进行渲染。某些时候,如果你想一次渲染好所有内容,可以设置 lazyLoad={false}
进行关闭。此外,某些时候,如果你想每次切换选项卡时自动卸载未激活的 Tab,可以设置 unmountInactiveTabs
开启。
查看源码在线预览
import { Tab } from '@alifd/next';
const tabs = [
{ tab: 'Home', key: 0, content: 'This is home page' },
{ tab: 'Document', key: 1, content: 'This is document page' },
{ tab: 'API', key: 2, content: 'This is api page' }
];
ReactDOM.render(
<div>
<div>Render all tab contents</div>
<Tab lazyLoad={false}>
{
tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)
}
</Tab>
<div>Unmount inactive tabs</div>
<Tab unmountInactiveTabs>
{
tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)
}
</Tab>
</div>
, mountNode);
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
包裹型选项卡支持通过 tabPosition
属性设置选项卡的位置,支持 top | right | bottom | left
四个方向。
查看源码在线预览
import { Tab, Select } from '@alifd/next';
class Demo extends React.Component {
state = {
position: 'top'
}
changePosition = (val) => {
this.setState({
position: val
});
}
render() {
return (<div>
<Select onChange={this.changePosition} placeholder="Choose Positon of Tab">
{
['top', 'bottom', 'left', 'right'].map(item => <Select.Option value={item} key={item}>{item}</Select.Option>)
}
</Select><br /><br />
<Tab tabPosition={this.state.position} shape="wrapped" contentClassName="custom-tab-content">
<Tab.Item title="Tab 1" key="1">Tab 1 Content</Tab.Item>
<Tab.Item title="Tab 2" key="2">Tab 2 Content</Tab.Item>
<Tab.Item title="Tab 3" key="3">Tab 3 Content</Tab.Item>
</Tab>
</div>);
}
}
ReactDOM.render(<Demo />, mountNode);
.custom-tab-content {
min-height: 50px;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
当 Tab 标签非常多时,组件会自动增加滑动支持。可以用过 excessMode
属性切换滑动模式,该属性仅在tabPosition
为top
或者bottom
时生效。
查看源码在线预览
import { Tab } from '@alifd/next';
const tabs = [
{ tab: 'Home', key: 1 },
{ tab: 'Documnet', key: 2 },
{ tab: 'Setting', key: 3 },
{ tab: 'Help', key: 4 },
{ tab: 'Admin', key: 5 },
{ tab: 'More 1', key: 6 },
{ tab: 'More 2', key: 7 },
{ tab: 'More 3', key: 8 },
{ tab: 'More 4', key: 9 },
{ tab: 'More 5', key: 10 },
{ tab: 'More 6', key: 11 },
{ tab: 'More 7', key: 12 },
{ tab: 'More 8', key: 13 },
{ tab: 'More 9', key: 14 },
{ tab: 'More 10', key: 15 },
{ tab: 'More 11', key: 16 }
];
function onClick(key) {
console.log(key);
}
ReactDOM.render(<div className="fusion-demo" style={{ maxWidth: '520px' }}>
<div className="demo-item-title">Dropdown mode</div>
<Tab excessMode="dropdown">
{
tabs.map(item => <Tab.Item key={item.key} title={item.tab} onClick={onClick}>{item.tab} content, content, content</Tab.Item>)
}
</Tab>
<div className="demo-item-title">Slide mode</div>
<Tab excessMode="slide">
{
tabs.map(item => <Tab.Item key={item.key} title={item.tab} onClick={onClick}>{item.tab} content, content, content</Tab.Item>)
}
</Tab>
</div>
, mountNode);
.fusion-demo .demo-item-title {
font-size: 16px;
color: #333;
padding: 8px;
margin: 14px 0;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
可关闭选项卡,可以通过在 Tab.Item
上设置 closeable
属性设置该选项卡是否可关闭。
查看源码在线预览
import { Tab, Button, Icon } from '@alifd/next';
const panes = [
{ tab: 'Mail', key: 1, closeable: false },
{ tab: 'Message', key: 2, closeable: true },
{ tab: 'Setting', key: 3, closeable: true },
{ tab: 'Unread', key: 4, closeable: true }
];
class CloseableTab extends React.Component {
constructor(props) {
super(props);
this.state = {
panes: panes,
activeKey: panes[0].key
};
}
/*eslint-disable eqeqeq */
remove(targetKey) {
let activeKey = this.state.activeKey;
const panes = [];
this.state.panes.forEach(pane => {
if (pane.key != targetKey) {
panes.push(pane);
}
});
if (targetKey == activeKey) {
activeKey = panes[0].key;
}
this.setState({ panes, activeKey });
}
onClose = (targetKey) => {
this.remove(targetKey);
}
onChange = (activeKey) => {
this.setState({ activeKey });
}
addTabpane = () => {
this.setState(prevState => {
const { panes } = prevState;
panes.push({ tab: 'new tab', key: Math.random(), closeable: true });
return { panes };
});
}
render() {
const state = this.state;
return (
<div>
<Button onClick={this.addTabpane} size="large" type="primary"><Icon type="add"/> New Tab</Button>
<Tab
shape="wrapped"
activeKey={state.activeKey}
onChange={this.onChange}
onClose={this.onClose}
className="custom-tab">
{state.panes.map(item => <Tab.Item title={item.tab} key={item.key} closeable={item.closeable}>{item.tab} content</Tab.Item>)}
</Tab>
</div>
);
}
}
ReactDOM.render(<CloseableTab />, mountNode);
.custom-tab {
margin-top: 14px;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
Tab 支持 click
切换和 hover
切换两种触发类型,默认为 click
触发,您可以使用 triggerType
属性修改默认的触发类型。
查看源码在线预览
import { Tab } from '@alifd/next';
const tabs = [
{ tab: 'Home', key: 0, content: 'This is home page' },
{ tab: 'Document', key: 1, content: 'This is document page' },
{ tab: 'API', key: 2, content: 'This is api page' }
];
function onChange(key) {
console.log('change', key);
}
function handleClick(key) {
console.log('click', key);
}
function onMouseEnter(key, e) {
console.log('enter', e.target, key);
}
function onMouseLeave(key, e) {
console.log('leave', e.target, key);
}
ReactDOM.render(
<div className="fusion-demo">
<div className="demo-item-title">Click to trigger change</div>
<Tab triggerType="click" onChange={onChange}>
{
tabs.map(item => <Tab.Item key={item.key} title={item.tab} onClick={handleClick}>{item.content}</Tab.Item>)
}
</Tab>
<div className="demo-item-title">Hover to trigger change</div>
<Tab triggerType="hover" onChange={onChange}>
{
tabs.map(item => (<Tab.Item
key={item.key}
title={item.tab}
onClick={handleClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}>
{item.content}
</Tab.Item>))
}
</Tab>
</div>
, mountNode);
.fusion-demo .demo-item-title {
font-size: 16px;
color: #333;
padding: 8px;
margin: 14px 0;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
可以通过 disabled
属性禁用某一个选型卡。
查看源码在线预览
import { Tab } from '@alifd/next';
ReactDOM.render(
<Tab>
<Tab.Item title="Tab 1" disabled key="1">Tab 1 content</Tab.Item>
<Tab.Item title="Tab 2" key="2">Tab 2 content</Tab.Item>
<Tab.Item title="Tab 3" key="3">Tab 3 content</Tab.Item>
</Tab>
, mountNode);
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
在 Tab 已有样式的基础上,可以通过 contentStyle
, contentClassName
等属性自由的进行样式自定义。
查看源码在线预览
import { Tab } from '@alifd/next';
const panes = [
{
tab: 'Todo Tasks',
key: 0
},
{
tab: 'Finished Tasks',
key: 1
},
{
tab: 'Unread Messages',
key: 2
},
{
tab: 'Past Messages',
key: 3
},
{
tab: 'All Messages',
key: 4
}
];
const detachedContentStyle = {
border: '1px solid #DCDEE3',
padding: '20px'
};
ReactDOM.render(<div className="fusion-demo">
<div className="demo-item-title">Customize with contentStyle or contentClassName</div>
<Tab shape="wrapped" contentStyle={detachedContentStyle}>
{
panes.map(pane => <Tab.Item title={pane.tab} key={pane.key}>{pane.tab}</Tab.Item>)
}
</Tab>
<div className="demo-item-title">Setting className and style in Tab.Item</div>
<Tab shape="wrapped" navStyle={{ background: '#DEE8FF' }}>
{
panes.map(pane => {
return (<Tab.Item
title={pane.tab}
key={pane.key}
className="custom-tab-item"
style={{background: '#FFF'}}>{pane.tab}</Tab.Item>
);
})
}
</Tab>
<div className="demo-item-title">Tabs with equal width</div>
<Tab shape="capsule">
{
panes.map(pane => <Tab.Item title={pane.tab} key={pane.key} className="justify-tabs-tab">{pane.tab}</Tab.Item>)
}
</Tab>
</div>
, mountNode);
.fusion-demo .demo-item-title {
font-size: 16px;
color: #333;
padding: 8px;
margin: 14px 0;
}
.custom-content {
padding: 15px;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
.custom-tab-item {
margin-right: -1px !important;
}
.justify-tabs-tab {
width: 140px;
text-align: center;
}
Tab 支持使用 tabRender
属性返回自定义组件作为选项卡内容,注意该属性接收函数作为属性值。
查看源码在线预览
import { Tab } from '@alifd/next';
function CustomTabItem({ title, desc }) {
return (<div className="custom-tab-item">
<div className="tab-title">{title}</div>
<div className="tab-desc">{desc}</div>
</div>);
}
const panes = [
{ key: 'e-checking', title: 'Alipay', desc: 'The fee to be paid is $15' },
{ key: 'brand-card', title: 'Bank Card', desc: 'The fee to be paid is $17' }
];
ReactDOM.render(
<Tab shape="wrapped" tabRender={(key, props) => <CustomTabItem key={key} {...props} />}>
{
panes.map(pane => <Tab.Item key={pane.key} {...pane} tabStyle={{ height: '60px' }}>{pane.desc}</Tab.Item>)
}
</Tab>
, mountNode);
.custom-tab-item {
padding: 10px;
}
.tab-title {
font-size: 20px;
}
.tab-desc {
margin: 10px 0 0 0;
font-size: 12px;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
通过 extra
属性添加附加内容,请确保只在有限选项卡的情况下才使用附加内容, 该功能在选项卡溢出时会和溢出导航的按钮冲突。
查看源码在线预览
import { Tab, Button } from '@alifd/next';
function handleChange(key) {
console.log(key);
}
function handleClick() {
console.log('hello world');
}
const extraContent = <Button type="primary" onClick={handleClick}>Hello world</Button>;
ReactDOM.render(
<div className="fusion-demo">
<div className="demo-item-title">Extra in Horizontal</div>
<Tab shape="wrapped" onChange={handleChange} extra={extraContent}>
<Tab.Item title="Tab 1" key="1">Tab 1 Content</Tab.Item>
<Tab.Item title="Tab 2" key="2">Tab 2 Content</Tab.Item>
<Tab.Item title="Tab 3" key="3">Tab 3 Content</Tab.Item>
</Tab>
<div className="demo-item-title">Extra in Vertical</div>
<Tab shape="wrapped" tabPosition="left" onChange={handleChange} extra={extraContent} contentClassName="custom-tab-content">
<Tab.Item title="Tab 1" key="1">Tab 1 Content</Tab.Item>
<Tab.Item title="Tab 2" key="2">Tab 2 Content</Tab.Item>
<Tab.Item title="Tab 3" key="3">Tab 3 Content</Tab.Item>
</Tab>
</div>
, mountNode);
.fusion-demo .demo-item-title {
font-size: 16px;
color: #333;
padding: 8px;
margin: 14px 0;
}
.custom-tab-content {
min-height: 150px;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
Tab 允许开发者在上层进行自由的行为控制,例如用户可以基于 Tab 开发一个标题部分双击可编辑的 Tab ,此时用户只要传入自定义组件给 TabPane 即可,Tab 可以将底层事件对象传递给用户的自定义组件。
查看源码在线预览
import { Tab, Input } from '@alifd/next';
class EditableTabPane extends React.Component {
constructor(props) {
super(props);
this.state = {
tabTitle: props.defaultTitle,
editable: false
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.defaultTitle !== this.state.tabTitle) {
this.setState({
tabTitle: nextProps.defaultTitle
});
}
}
onKeyDown = (e) => {
const { keyCode } = e;
// Stop bubble up the events of keyUp, keyDown, keyLeft, and keyRight
if (keyCode > 36 && keyCode < 41) {
e.stopPropagation();
}
}
onBlur = (e) => {
this.setState({
editable: false,
tabTitle: e.target.value
});
}
onDblClick = () => {
this.setState({
editable: true
});
}
render() {
const { tabTitle, editable } = this.state;
if (editable) {
return <Input defaultValue={tabTitle} onKeyDown={this.onKeyDown} onBlur={this.onBlur} />;
}
return <span onDoubleClick={this.onDblClick}>{tabTitle}</span>;
}
}
const tabRender = (key, { title }) => (<div key={key} className="editable-tab-wrapper">
<EditableTabPane defaultTitle={title} />
</div>);
ReactDOM.render(<Tab defaultActiveKey="1" tabRender={tabRender}>
<Tab.Item title="Double Click To Edit Me" key="1">Editable tab</Tab.Item>
<Tab.Item title="Double Click To Edit Me" key="2">Editable tab</Tab.Item>
</Tab>, mountNode);
.editable-tab-wrapper {
padding: 10px;
}
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}
当 Tab 位于 Grid 组件的布局中时,由于 Grid 默认使用 flex
布局方式,当选项卡数量过多时,会导致内层元素撑起整个 flex
容器,此时需要给容器添加自定义样式 overflow: hidden
。
查看源码在线预览
import { Tab, Grid } from '@alifd/next';
const { Row, Col } = Grid;
const tabs = function(length) {
const arr = [];
for (let i = 1; i < length; i++) {
arr.push({ tab: `tab ${i}`, key: i, content: `tab ${i} content` });
}
return arr;
}(15);
ReactDOM.render(
<div className="custom-wrapper">
<Row className="custom-row">
<Col fixedSpan="12" className="custom-col-sidebar">Sidebar</Col>
<Col className="custom-col-content">
<Tab>
{
tabs.map(item => <Tab.Item key={item.key} title={item.tab}>{item.content}</Tab.Item>)
}
</Tab>
</Col>
</Row>
</div>
, mountNode);
.custom-row {
border: 1px solid #ccc;
}
.custom-col-sidebar {
border-right: 1px solid #ccc;
height: 130px;
line-height: 130px;
text-align: center;
}
.custom-col-content {
overflow: hidden;
}
可以将不同类型的选项卡进行嵌套
查看源码在线预览
import { Tab } from '@alifd/next';
function callback(key) {
console.log(key);
}
ReactDOM.render(
<Tab onChange={callback} shape="wrapped">
<Tab.Item title="Tab 1" key="1">
<Tab shape="wrapped">
<Tab.Item title="1-1" key="11">1-1</Tab.Item>
<Tab.Item title="1-2" key="12">1-2</Tab.Item>
<Tab.Item title="1-3" key="13">1-3</Tab.Item>
</Tab>
</Tab.Item>
<Tab.Item title="Tab 2" key="2">
<Tab shape="pure">
<Tab.Item title="2-1" key="21">2-1</Tab.Item>
<Tab.Item title="2-2" key="22">2-2</Tab.Item>
<Tab.Item title="3-3" key="23">2-3</Tab.Item>
</Tab>
</Tab.Item>
<Tab.Item title="Tab 3" key="3">
<Tab shape="capsule">
<Tab.Item title="3-1" key="31">3-1</Tab.Item>
<Tab.Item title="3-2" key="32">3-2</Tab.Item>
<Tab.Item title="3-3" key="33">3-3</Tab.Item>
</Tab>
</Tab.Item>
<Tab.Item title="Tab 4" key="4">
<Tab shape="text">
<Tab.Item title="4-1" key="41">4-1</Tab.Item>
<Tab.Item title="4-2" key="42">4-2</Tab.Item>
<Tab.Item title="4-3" key="43">4-3</Tab.Item>
</Tab>
</Tab.Item>
</Tab>
, mountNode);
.next-tabs-content {
color: #333;
font-size: 12px;
padding: 12px;
}