feat: Tabs support items (#36889)

* refactor: Using items

* docs: replace with items

* docs: deprecated demo

* test: Update snapshot

* docs: simple basic demo

* test: coverage
This commit is contained in:
二货爱吃白萝卜 2022-08-05 10:49:08 +08:00 committed by GitHub
parent 37cc36ac7e
commit 628968f460
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 702 additions and 276 deletions

View File

@ -0,0 +1,12 @@
import type * as React from 'react';
import type { TabPaneProps } from 'rc-tabs/lib/TabPanelList/TabPane';
const TabPane: React.FC<TabPaneProps> = () => null;
if (process.env.NODE_ENV !== 'production') {
TabPane.displayName = 'DeprecatedTabPane';
}
export { TabPaneProps };
export default TabPane;

View File

@ -1088,6 +1088,150 @@ exports[`renders ./components/tabs/demo/custom-tab-bar-node.md extend context co
</div>
`;
exports[`renders ./components/tabs/demo/deprecated.md extend context correctly 1`] = `
<div
class="ant-tabs ant-tabs-top"
>
<div
class="ant-tabs-nav"
role="tablist"
>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-tab ant-tabs-tab-active"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
>
Tab 1
</div>
</div>
<div
class="ant-tabs-tab"
>
<div
aria-selected="false"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
>
Tab 2
</div>
</div>
<div
class="ant-tabs-tab"
>
<div
aria-selected="false"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
>
Tab 3
</div>
</div>
<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"
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-dropdown"
style="opacity:0"
>
<ul
aria-label="expanded dropdown"
class="ant-tabs-dropdown-menu ant-tabs-dropdown-menu-root ant-tabs-dropdown-menu-vertical"
data-menu-list="true"
id="null-more-popup"
role="listbox"
tabindex="-1"
/>
<div
aria-hidden="true"
style="display:none"
/>
</div>
</div>
</div>
</div>
<div
class="ant-tabs-content-holder"
>
<div
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
>
Content of Tab Pane 1
</div>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
`;
exports[`renders ./components/tabs/demo/disabled.md extend context correctly 1`] = `
<div
class="ant-tabs ant-tabs-top"

View File

@ -955,6 +955,131 @@ exports[`renders ./components/tabs/demo/custom-tab-bar-node.md correctly 1`] = `
</div>
`;
exports[`renders ./components/tabs/demo/deprecated.md correctly 1`] = `
<div
class="ant-tabs ant-tabs-top"
>
<div
class="ant-tabs-nav"
role="tablist"
>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-tab ant-tabs-tab-active"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
>
Tab 1
</div>
</div>
<div
class="ant-tabs-tab"
>
<div
aria-selected="false"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
>
Tab 2
</div>
</div>
<div
class="ant-tabs-tab"
>
<div
aria-selected="false"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
>
Tab 3
</div>
</div>
<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"
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-holder"
>
<div
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
>
Content of Tab Pane 1
</div>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
`;
exports[`renders ./components/tabs/demo/disabled.md correctly 1`] = `
<div
class="ant-tabs ant-tabs-top"

View File

@ -3,6 +3,7 @@ import Tabs from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
const { TabPane } = Tabs;
@ -105,4 +106,23 @@ describe('Tabs', () => {
);
expect(wrapper2.firstChild).toMatchSnapshot();
});
it('deprecated warning', () => {
resetWarned();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(
<Tabs>
<TabPane />
invalidate
</Tabs>,
);
expect(container.querySelectorAll('.ant-tabs-tab')).toHaveLength(1);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Tabs] Tabs.TabPane is deprecated. Please use `items` directly.',
);
errorSpy.mockRestore();
});
});

View File

@ -17,24 +17,32 @@ Default activate first tab.
import { Tabs } from 'antd';
import React from 'react';
const { TabPane } = Tabs;
const onChange = (key: string) => {
console.log(key);
};
const App: React.FC = () => (
<Tabs defaultActiveKey="1" onChange={onChange}>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
<Tabs
defaultActiveKey="1"
onChange={onChange}
items={[
{
label: `Tab 1`,
key: '1',
children: `Content of Tab Pane 1`,
},
{
label: `Tab 2`,
key: '2',
children: `Content of Tab Pane 2`,
},
{
label: `Tab 3`,
key: '3',
children: `Content of Tab Pane 3`,
},
]}
/>
);
export default App;

View File

@ -17,27 +17,24 @@ Should be used at the top of container, needs to override styles.
import { Tabs } from 'antd';
import React from 'react';
const { TabPane } = Tabs;
const items = new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab Title ${id}`,
key: id,
children: (
<>
<p>Content of Tab Pane {id}</p>
<p>Content of Tab Pane {id}</p>
<p>Content of Tab Pane {id}</p>
</>
),
};
});
const App: React.FC = () => (
<div className="card-container">
<Tabs type="card">
<TabPane tab="Tab Title 1" key="1">
<p>Content of Tab Pane 1</p>
<p>Content of Tab Pane 1</p>
<p>Content of Tab Pane 1</p>
</TabPane>
<TabPane tab="Tab Title 2" key="2">
<p>Content of Tab Pane 2</p>
<p>Content of Tab Pane 2</p>
<p>Content of Tab Pane 2</p>
</TabPane>
<TabPane tab="Tab Title 3" key="3">
<p>Content of Tab Pane 3</p>
<p>Content of Tab Pane 3</p>
<p>Content of Tab Pane 3</p>
</TabPane>
</Tabs>
<Tabs type="card" items={items} />
</div>
);

View File

@ -17,24 +17,23 @@ Another type of Tabs, which doesn't support vertical mode.
import { Tabs } from 'antd';
import React from 'react';
const { TabPane } = Tabs;
const onChange = (key: string) => {
console.log(key);
};
const App: React.FC = () => (
<Tabs onChange={onChange} type="card">
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
<Tabs
onChange={onChange}
type="card"
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
);
export default App;

View File

@ -17,20 +17,19 @@ Centered tabs.
import { Tabs } from 'antd';
import React from 'react';
const { TabPane } = Tabs;
const App: React.FC = () => (
<Tabs defaultActiveKey="1" centered>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
<Tabs
defaultActiveKey="1"
centered
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
);
export default App;

View File

@ -17,16 +17,14 @@ Hide default plus icon, and bind event for customized trigger.
import { Button, Tabs } from 'antd';
import React, { useRef, useState } from 'react';
const { TabPane } = Tabs;
const defaultPanes = Array.from({ length: 2 }).map((_, index) => {
const defaultPanes = new Array(2).fill(null).map((_, index) => {
const id = String(index + 1);
return { title: `Tab ${id}`, content: `Content of Tab Pane ${index + 1}`, key: id };
return { label: `Tab ${id}`, children: `Content of Tab Pane ${index + 1}`, key: id };
});
const App: React.FC = () => {
const [activeKey, setActiveKey] = useState(defaultPanes[0].key);
const [panes, setPanes] = useState(defaultPanes);
const [items, setItems] = useState(defaultPanes);
const newTabIndex = useRef(0);
const onChange = (key: string) => {
@ -35,18 +33,18 @@ const App: React.FC = () => {
const add = () => {
const newActiveKey = `newTab${newTabIndex.current++}`;
setPanes([...panes, { title: 'New Tab', content: 'New Tab Pane', key: newActiveKey }]);
setItems([...items, { label: 'New Tab', children: 'New Tab Pane', key: newActiveKey }]);
setActiveKey(newActiveKey);
};
const remove = (targetKey: string) => {
const targetIndex = panes.findIndex(pane => pane.key === targetKey);
const newPanes = panes.filter(pane => pane.key !== targetKey);
const targetIndex = items.findIndex(pane => pane.key === targetKey);
const newPanes = items.filter(pane => pane.key !== targetKey);
if (newPanes.length && targetKey === activeKey) {
const { key } = newPanes[targetIndex === newPanes.length ? targetIndex - 1 : targetIndex];
setActiveKey(key);
}
setPanes(newPanes);
setItems(newPanes);
};
const onEdit = (targetKey: string, action: 'add' | 'remove') => {
@ -62,13 +60,14 @@ const App: React.FC = () => {
<div style={{ marginBottom: 16 }}>
<Button onClick={add}>ADD</Button>
</div>
<Tabs hideAdd onChange={onChange} activeKey={activeKey} type="editable-card" onEdit={onEdit}>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
<Tabs
hideAdd
onChange={onChange}
activeKey={activeKey}
type="editable-card"
onEdit={onEdit}
items={items}
/>
</div>
);
};

View File

@ -20,8 +20,6 @@ import React, { useRef, useState } from 'react';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
const { TabPane } = Tabs;
const type = 'DraggableTabNode';
interface DraggableTabPaneProps extends React.HTMLAttributes<HTMLDivElement> {
index: React.Key;
@ -62,16 +60,16 @@ const DraggableTabNode = ({ index, children, moveNode }: DraggableTabPaneProps)
);
};
const DraggableTabs: React.FC<{ children: React.ReactNode }> = props => {
const { children } = props;
const DraggableTabs: React.FC<TabsProps> = props => {
const { items = [] } = props;
const [order, setOrder] = useState<React.Key[]>([]);
const moveTabNode = (dragKey: React.Key, hoverKey: React.Key) => {
const newOrder = order.slice();
React.Children.forEach(children, (c: React.ReactElement) => {
if (c.key && newOrder.indexOf(c.key) === -1) {
newOrder.push(c.key);
items.forEach(item => {
if (item.key && newOrder.indexOf(item.key) === -1) {
newOrder.push(item.key);
}
});
@ -94,12 +92,7 @@ const DraggableTabs: React.FC<{ children: React.ReactNode }> = props => {
</DefaultTabBar>
);
const tabs: React.ReactElement[] = [];
React.Children.forEach(children, (c: React.ReactElement) => {
tabs.push(c);
});
const orderTabs = tabs.slice().sort((a, b) => {
const orderItems = [...items].sort((a, b) => {
const orderA = order.indexOf(a.key!);
const orderB = order.indexOf(b.key!);
@ -113,33 +106,30 @@ const DraggableTabs: React.FC<{ children: React.ReactNode }> = props => {
return 1;
}
const ia = tabs.indexOf(a);
const ib = tabs.indexOf(b);
const ia = items.indexOf(a);
const ib = items.indexOf(b);
return ia - ib;
});
return (
<DndProvider backend={HTML5Backend}>
<Tabs renderTabBar={renderTabBar} {...props}>
{orderTabs}
</Tabs>
<Tabs renderTabBar={renderTabBar} {...props} items={orderItems} />
</DndProvider>
);
};
const App: React.FC = () => (
<DraggableTabs>
<TabPane tab="tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</DraggableTabs>
<DraggableTabs
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
);
export default App;

View File

@ -19,8 +19,6 @@ import { Tabs } from 'antd';
import React from 'react';
import { Sticky, StickyContainer } from 'react-sticky';
const { TabPane } = Tabs;
const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
<Sticky bottomOffset={80}>
{({ style }) => (
@ -29,19 +27,19 @@ const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
</Sticky>
);
const items = new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
style: i === 0 ? { height: 200 } : undefined,
};
});
const App: React.FC = () => (
<StickyContainer>
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar}>
<TabPane tab="Tab 1" key="1" style={{ height: 200 }}>
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar} items={items} />
</StickyContainer>
);

View File

@ -0,0 +1,36 @@
---
order: -1
title:
zh-CN: 基础用法(废弃的语法糖)
en-US: Basic usage (deprecated syntactic sugar)
version: < 4.23.0
---
## zh-CN
默认选中第一项。
## en-US
Default activate first tab.
```tsx
import { Tabs } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
);
export default App;
```

View File

@ -17,20 +17,28 @@ Disabled a tab.
import { Tabs } from 'antd';
import React from 'react';
const { TabPane } = Tabs;
const App: React.FC = () => (
<Tabs defaultActiveKey="1">
<TabPane tab="Tab 1" key="1">
Tab 1
</TabPane>
<TabPane tab="Tab 2" disabled key="2">
Tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Tab 3
</TabPane>
</Tabs>
<Tabs
defaultActiveKey="1"
items={[
{
label: 'Tab 1',
key: '1',
children: 'Tab 1',
},
{
label: 'Tab 2',
key: '2',
children: 'Tab 2',
disabled: true,
},
{
label: 'Tab 3',
key: '3',
children: 'Tab 3',
},
]}
/>
);
export default App;

View File

@ -17,22 +17,20 @@ Only card type Tabs support adding & closable. +Use `closable={false}` to disabl
import { Tabs } from 'antd';
import React, { useRef, useState } from 'react';
const { TabPane } = Tabs;
const initialPanes = [
{ title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
{ title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
const initialItems = [
{ label: 'Tab 1', children: 'Content of Tab 1', key: '1' },
{ label: 'Tab 2', children: 'Content of Tab 2', key: '2' },
{
title: 'Tab 3',
content: 'Content of Tab 3',
label: 'Tab 3',
children: 'Content of Tab 3',
key: '3',
closable: false,
},
];
const App: React.FC = () => {
const [activeKey, setActiveKey] = useState(initialPanes[0].key);
const [panes, setPanes] = useState(initialPanes);
const [activeKey, setActiveKey] = useState(initialItems[0].key);
const [items, setItems] = useState(initialItems);
const newTabIndex = useRef(0);
const onChange = (newActiveKey: string) => {
@ -41,21 +39,21 @@ const App: React.FC = () => {
const add = () => {
const newActiveKey = `newTab${newTabIndex.current++}`;
const newPanes = [...panes];
newPanes.push({ title: 'New Tab', content: 'Content of new Tab', key: newActiveKey });
setPanes(newPanes);
const newPanes = [...items];
newPanes.push({ label: 'New Tab', children: 'Content of new Tab', key: newActiveKey });
setItems(newPanes);
setActiveKey(newActiveKey);
};
const remove = (targetKey: string) => {
let newActiveKey = activeKey;
let lastIndex = -1;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
items.forEach((item, i) => {
if (item.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = panes.filter(pane => pane.key !== targetKey);
const newPanes = items.filter(item => item.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
@ -63,7 +61,7 @@ const App: React.FC = () => {
newActiveKey = newPanes[0].key;
}
}
setPanes(newPanes);
setItems(newPanes);
setActiveKey(newActiveKey);
};
@ -76,13 +74,13 @@ const App: React.FC = () => {
};
return (
<Tabs type="editable-card" onChange={onChange} activeKey={activeKey} onEdit={onEdit}>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
{pane.content}
</TabPane>
))}
</Tabs>
<Tabs
type="editable-card"
onChange={onChange}
activeKey={activeKey}
onEdit={onEdit}
items={items}
/>
);
};

View File

@ -17,8 +17,6 @@ You can add extra actions to the right or left or even both side of Tabs.
import { Button, Checkbox, Divider, Tabs } from 'antd';
import React, { useMemo, useState } from 'react';
const { TabPane } = Tabs;
const CheckboxGroup = Checkbox.Group;
const operations = <Button>Extra Action</Button>;
@ -32,6 +30,15 @@ const options = ['left', 'right'];
type PositionType = 'left' | 'right';
const items = new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
});
const App: React.FC = () => {
const [position, setPosition] = useState<PositionType[]>(['left', 'right']);
@ -46,17 +53,7 @@ const App: React.FC = () => {
return (
<>
<Tabs tabBarExtraContent={operations}>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
</Tabs>
<Tabs tabBarExtraContent={operations} items={items} />
<br />
<br />
<br />
@ -71,17 +68,7 @@ const App: React.FC = () => {
/>
<br />
<br />
<Tabs tabBarExtraContent={slot}>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
</Tabs>
<Tabs tabBarExtraContent={slot} items={items} />
</>
);
};

View File

@ -18,33 +18,24 @@ import { AndroidOutlined, AppleOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';
import React from 'react';
const { TabPane } = Tabs;
const App: React.FC = () => (
<Tabs defaultActiveKey="2">
<TabPane
tab={
<Tabs
defaultActiveKey="2"
items={[AppleOutlined, AndroidOutlined].map((Icon, i) => {
const id = String(i + 1);
return {
label: (
<span>
<AppleOutlined />
Tab 1
<Icon />
Tab {id}
</span>
}
key="1"
>
Tab 1
</TabPane>
<TabPane
tab={
<span>
<AndroidOutlined />
Tab 2
</span>
}
key="2"
>
Tab 2
</TabPane>
</Tabs>
),
key: id,
children: `Tab ${id}`,
};
})}
/>
);
export default App;

View File

@ -18,13 +18,10 @@ Default activate first tab.
import { Select, Tabs } from 'antd';
import React, { useState } from 'react';
const { TabPane } = Tabs;
const { Option } = Select;
const positionList = ['left', 'right', 'top', 'bottom'];
const list = Array.from({ length: 20 }).map((_, index) => index);
const App: React.FC = () => {
const [parentPos, setParentPos] = useState(undefined);
const [childPos, setChildPos] = useState(undefined);
@ -81,25 +78,39 @@ const App: React.FC = () => {
<Option value="editable-card">Parent - card edit</Option>
</Select>
<Tabs defaultActiveKey="1" tabPosition={parentPos} type={parentType}>
<TabPane tab="Tab 1" key="1">
<Tabs
defaultActiveKey="1"
tabPosition={parentPos}
type={parentType}
items={[
{
label: 'Tab 1',
key: '1',
children: (
<Tabs
defaultActiveKey="1"
tabPosition={childPos}
type={childType}
style={{ height: 300 }}
>
{list.map(key => (
<TabPane tab={`Tab ${key}`} key={key}>
TTTT {key}
</TabPane>
))}
</Tabs>
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
</Tabs>
items={new Array(20).fill(null).map((_, index) => {
const key = String(index);
return {
label: `Tab ${key}`,
key,
children: `TTTT ${key}`,
};
})}
/>
),
},
{
label: 'Tab 2',
key: '2',
children: 'Content of Tab Pane 2',
},
]}
/>
</div>
);
};

View File

@ -18,8 +18,6 @@ import type { RadioChangeEvent } from 'antd';
import { Radio, Space, Tabs } from 'antd';
import React, { useState } from 'react';
const { TabPane } = Tabs;
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
const App: React.FC = () => {
@ -40,17 +38,17 @@ const App: React.FC = () => {
<Radio.Button value="right">right</Radio.Button>
</Radio.Group>
</Space>
<Tabs tabPosition={tabPosition}>
<TabPane tab="Tab 1" key="1">
Content of Tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab 3
</TabPane>
</Tabs>
<Tabs
tabPosition={tabPosition}
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab ${id}`,
};
})}
/>
</>
);
};

View File

@ -19,8 +19,6 @@ import { Radio, Tabs } from 'antd';
import type { SizeType } from 'antd/es/config-provider/SizeContext';
import React, { useState } from 'react';
const { TabPane } = Tabs;
const App: React.FC = () => {
const [size, setSize] = useState<SizeType>('small');
@ -35,28 +33,32 @@ const App: React.FC = () => {
<Radio.Button value="middle">Middle</Radio.Button>
<Radio.Button value="large">Large</Radio.Button>
</Radio.Group>
<Tabs defaultActiveKey="1" size={size} style={{ marginBottom: 32 }}>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
</Tabs>
<Tabs defaultActiveKey="1" type="card" size={size}>
<TabPane tab="Card Tab 1" key="1">
Content of card tab 1
</TabPane>
<TabPane tab="Card Tab 2" key="2">
Content of card tab 2
</TabPane>
<TabPane tab="Card Tab 3" key="3">
Content of card tab 3
</TabPane>
</Tabs>
<Tabs
defaultActiveKey="1"
size={size}
style={{ marginBottom: 32 }}
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
})}
/>
<Tabs
defaultActiveKey="1"
type="card"
size={size}
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Card Tab ${id}`,
key: id,
children: `Content of card tab ${id}`,
};
})}
/>
</div>
);
};

View File

@ -18,8 +18,6 @@ import type { RadioChangeEvent } from 'antd';
import { Radio, Tabs } from 'antd';
import React, { useState } from 'react';
const { TabPane } = Tabs;
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
const App: React.FC = () => {
@ -35,13 +33,20 @@ const App: React.FC = () => {
<Radio.Button value="top">Horizontal</Radio.Button>
<Radio.Button value="left">Vertical</Radio.Button>
</Radio.Group>
<Tabs defaultActiveKey="1" tabPosition={mode} style={{ height: 220 }}>
{[...Array.from({ length: 30 }, (_, i) => i)].map(i => (
<TabPane tab={`Tab-${i}`} key={i} disabled={i === 28}>
Content of tab {i}
</TabPane>
))}
</Tabs>
<Tabs
defaultActiveKey="1"
tabPosition={mode}
style={{ height: 220 }}
items={new Array(30).fill(null).map((_, i) => {
const id = String(i);
return {
label: `Tab-${id}`,
key: id,
disabled: i === 28,
children: `Content of tab ${id}`,
};
})}
/>
</div>
);
};

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import toArray from 'rc-util/lib/Children/toArray';
import type { Tab } from 'rc-tabs/lib/interface';
import type { TabsProps, TabPaneProps } from '..';
import warning from '../../_util/warning';
function filter<T>(items: (T | null)[]): T[] {
return items.filter(item => item) as T[];
}
export default function useLegacyItems(items?: TabsProps['items'], children?: React.ReactNode) {
if (items) {
return items;
}
warning(!children, 'Tabs', 'Tabs.TabPane is deprecated. Please use `items` directly.');
const childrenItems = toArray(children).map((node: React.ReactElement<TabPaneProps>) => {
if (React.isValidElement(node)) {
const { key, props } = node;
const { tab, ...restProps } = props || {};
const item: Tab = {
key: String(key),
...restProps,
label: tab,
};
return item;
}
return null;
});
return filter(childrenItems);
}

View File

@ -16,6 +16,32 @@ Ant Design has 3 types of Tabs for different situations.
- Normal Tabs: for functional aspects of a page.
- [Radio.Button](/components/radio/#components-radio-demo-radiobutton): for secondary tabs.
### Usage upgrade after 4.23.0
```__react
import Alert from '../alert';
ReactDOM.render(<Alert message="After version 4.23.0, we provide a simpler usage <Tabs items={[...]} /> with better performance and potential of writing simpler code style in your applications. Meanwhile, we deprecated the old usage in browser console, we will remove it in antd 5.0." />, mountNode);
```
```jsx
// works when >=4.23.0, recommended ✅
const items = [
{ label: 'Tab 1', key: 'item-1', children: 'Content 1' }, // remember to pass the key prop
{ label: 'Tab 2', key: 'item-2', children: 'Content 2' },
];
return <Tabs items={items} />;
// works when <4.23.0, deprecated when >=4.23.0 🙅🏻‍♀️
<Tabs>
<Tabs.TabPane tab="Tab 1" key="item-1">
Content 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="item-2">
Content 2
</Tabs.TabPane>
</Tabs>;
```
## API
### Tabs

View File

@ -3,7 +3,7 @@ import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
import PlusOutlined from '@ant-design/icons/PlusOutlined';
import classNames from 'classnames';
import type { TabsProps as RcTabsProps } from 'rc-tabs';
import RcTabs, { TabPane, TabPaneProps } from 'rc-tabs';
import RcTabs from 'rc-tabs';
import type { EditableConfig } from 'rc-tabs/lib/interface';
import * as React from 'react';
@ -11,6 +11,8 @@ import { ConfigContext } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import warning from '../_util/warning';
import useLegacyItems from './hooks/useLegacyItems';
import TabPane, { TabPaneProps } from './TabPane';
export type TabsType = 'line' | 'card' | 'editable-card';
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';
@ -24,6 +26,7 @@ export interface TabsProps extends Omit<RcTabsProps, 'editable'> {
centered?: boolean;
addIcon?: React.ReactNode;
onEdit?: (e: React.MouseEvent | React.KeyboardEvent | string, action: 'add' | 'remove') => void;
children?: React.ReactNode;
}
function Tabs({
@ -34,6 +37,8 @@ function Tabs({
hideAdd,
centered,
addIcon,
children,
items,
...props
}: TabsProps) {
const { prefixCls: customizePrefixCls, moreIcon = <EllipsisOutlined /> } = props;
@ -59,6 +64,8 @@ function Tabs({
'`onPrevClick` and `onNextClick` has been removed. Please use `onTabScroll` instead.',
);
const mergedItems = useLegacyItems(items, children);
return (
<SizeContext.Consumer>
{contextSize => {
@ -68,6 +75,7 @@ function Tabs({
direction={direction}
moreTransitionName={`${rootPrefixCls}-slide-up`}
{...props}
items={mergedItems}
className={classNames(
{
[`${prefixCls}-${size}`]: size,

View File

@ -19,6 +19,32 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
- 既可用于容器顶部,也可用于容器内部,是最通用的 Tabs。
- [Radio.Button](/components/radio/#components-radio-demo-radiobutton) 可作为更次级的页签来使用。
### 4.23.0 用法升级
```__react
import Alert from '../alert';
ReactDOM.render(<Alert message="在 4.23.0 版本后,我们提供了 <Tabs items={[...]} /> 的简写方式,有更好的性能和更方便的数据组织方式,开发者不再需要自行拼接 JSX。同时我们废弃了原先的写法你还是可以在 4.x 继续使用,但会在控制台看到警告,并会在 5.0 后移除。" />, mountNode);
```
```jsx
// >=4.23.0 可用,推荐的写法 ✅
const items = [
{ label: '项目 1', key: 'item-1', children: '内容 1' }, // 务必填写 key
{ label: '项目 2', key: 'item-2', children: '内容 2' },
];
return <Tabs items={items} />;
// <4.23.0 可用>=4.23.0 时不推荐 🙅🏻‍♀️
<Tabs>
<Tabs.TabPane tab="项目 1" key="item-1">
内容 1
</Tabs.TabPane>
<Tabs.TabPane tab="项目 2" key="item-2">
内容 2
</Tabs.TabPane>
</Tabs>;
```
## API
### Tabs
@ -31,6 +57,7 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| centered | 标签居中展示 | boolean | false | 4.4.0 |
| defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | `第一个面板` | |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | |
| items | 配置选项卡内容 | [TabItem](#TabItem) | [] | 4.23.0 |
| moreIcon | 自定义折叠 icon | ReactNode | &lt;EllipsisOutlined /> | 4.14.0 |
| popupClassName | 更多菜单的 `className` | string | - | 4.21.0 |
| renderTabBar | 替换 TabBar用于二次封装标签头 | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | |
@ -48,7 +75,7 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
> 更多属性查看 [rc-tabs tabs](https://github.com/react-component/tabs#tabs)
### Tabs.TabPane
### TabItem
| 参数 | 说明 | 类型 | 默认值 |
| ----------- | ----------------------------------------------- | --------- | ------ |
@ -56,6 +83,5 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| disabled | 禁用某一项 | boolean | false |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
| key | 对应 activeKey | string | - |
| tab | 选项卡头显示文字 | ReactNode | - |
> 更多属性查看 [rc-tabs tabpane](https://github.com/react-component/tabs#tabpane)
| label | 选项卡头显示文字 | ReactNode | - |
| children | 选项卡头显示内容 | ReactNode | - |

View File

@ -149,7 +149,7 @@
"rc-steps": "~4.1.0",
"rc-switch": "~3.2.0",
"rc-table": "~7.26.0",
"rc-tabs": "~11.16.0",
"rc-tabs": "~12.0.0-alpha.1",
"rc-textarea": "~0.3.0",
"rc-tooltip": "~5.2.0",
"rc-tree": "~5.6.5",

View File

@ -499,7 +499,11 @@ ReactDOM.render(<Demo />, document.getElementById('container'));
);
if (meta.version) {
codeBox = <Badge.Ribbon text={meta.version}>{codeBox}</Badge.Ribbon>;
codeBox = (
<Badge.Ribbon text={meta.version} color={meta.version.includes('<') ? 'red' : null}>
{codeBox}
</Badge.Ribbon>
);
}
return codeBox;