feat: add leftExtra rightExtra

refactor-tabs
tangjinzhou 2021-10-06 15:41:52 +08:00
parent eec9939a98
commit ba8a128943
12 changed files with 138 additions and 55 deletions

View File

@ -0,0 +1,20 @@
import type { Ref, ComponentPublicInstance } from 'vue';
import { onBeforeUpdate, ref } from 'vue';
import type { Key } from '../type';
type RefType = HTMLElement | ComponentPublicInstance;
export type RefsValue = Map<Key, RefType>;
type UseRef = [(key: Key) => (el: RefType) => void, Ref<RefsValue>];
const useRefs = (): UseRef => {
const refs = ref<RefsValue>(new Map());
const setRef = (key: Key) => (el: RefType) => {
refs.value.set(key, el);
};
onBeforeUpdate(() => {
refs.value = new Map();
});
return [setRef, refs];
};
export default useRefs;

View File

@ -0,0 +1,36 @@
<docs>
---
order: 2
title:
zh-CN: 居中
en-US: Centered
---
## zh-CN
标签居中展示
## en-US
Centered tabs.
</docs>
<template>
<a-tabs v-model:activeKey="activeKey" centered>
<a-tab-pane key="1" tab="Tab 1">Content of Tab Pane 1</a-tab-pane>
<a-tab-pane key="2" tab="Tab 2" force-render>Content of Tab Pane 2</a-tab-pane>
<a-tab-pane key="3" tab="Tab 3">Content of Tab Pane 3</a-tab-pane>
</a-tabs>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
return {
activeKey: ref('1'),
};
},
});
</script>

View File

@ -33,7 +33,7 @@ import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const panes = ref([ const panes = ref<{ title: string; content: string; key: string; closable?: boolean }[]>([
{ title: 'Tab 1', content: 'Content of Tab 1', key: '1' }, { title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
{ title: 'Tab 2', content: 'Content of Tab 2', key: '2' }, { title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
]); ]);

View File

@ -16,17 +16,13 @@ Customized bar of tab.
</docs> </docs>
<template> <template>
<div> <div style="height: 2000px">
<a-tabs v-model:activeKey="activeKey"> <a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="Tab 1" style="height: 200px">Content of Tab Pane 1</a-tab-pane> <a-tab-pane key="1" tab="Tab 1" style="height: 200px">Content of Tab Pane 1</a-tab-pane>
<a-tab-pane key="2" tab="Tab 2" force-render>Content of Tab Pane 2</a-tab-pane> <a-tab-pane key="2" tab="Tab 2" force-render>Content of Tab Pane 2</a-tab-pane>
<a-tab-pane key="3" tab="Tab 3">Content of Tab Pane 3</a-tab-pane> <a-tab-pane key="3" tab="Tab 3">Content of Tab Pane 3</a-tab-pane>
<template #renderTabBar="{ DefaultTabBar, ...props }"> <template #renderTabBar="{ DefaultTabBar, ...props }">
<component <component :is="DefaultTabBar" v-bind="props" :style="{ opacity: 0.5 }" />
:is="DefaultTabBar"
v-bind="props"
:style="{ zIndex: 1, background: '#fff', textAlign: 'right' }"
/>
</template> </template>
</a-tabs> </a-tabs>
</div> </div>

View File

@ -21,8 +21,11 @@ You can add extra actions to the right of Tabs.
<a-tab-pane key="1" tab="Tab 1">Content of tab 1</a-tab-pane> <a-tab-pane key="1" tab="Tab 1">Content of tab 1</a-tab-pane>
<a-tab-pane key="2" tab="Tab 2">Content of tab 2</a-tab-pane> <a-tab-pane key="2" tab="Tab 2">Content of tab 2</a-tab-pane>
<a-tab-pane key="3" tab="Tab 3">Content of tab 3</a-tab-pane> <a-tab-pane key="3" tab="Tab 3">Content of tab 3</a-tab-pane>
<template #tabBarExtraContent> <template #leftExtra>
<a-button>Extra Action</a-button> <a-button class="tabs-extra-demo-button">Left Extra Action</a-button>
</template>
<template #rightExtra>
<a-button>Right Extra Action</a-button>
</template> </template>
</a-tabs> </a-tabs>
</template> </template>
@ -37,3 +40,13 @@ export default defineComponent({
}, },
}); });
</script> </script>
<style>
.tabs-extra-demo-button {
margin-right: 16px;
}
.ant-row-rtl .tabs-extra-demo-button {
margin-right: 0;
margin-left: 16px;
}
</style>

View File

@ -19,18 +19,19 @@ Ant Design has 3 types of Tabs for different situations.
### Tabs ### Tabs
| Property | Description | Type | Default | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| activeKey(v-model) | Current TabPane's key | string | - | | activeKey(v-model) | 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"` | | animated | Whether to change tabs with animation. Only works while `tabPosition="top"\|"bottom"` | boolean \| {inkBar:boolean, tabPane:boolean} | `true`, `false` when `type="card"` | |
| defaultActiveKey | Initial active TabPane's key, if `activeKey` is not set. | string | - | | 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` | | hideAdd | Hide plus icon or not. Only works while `type="editable-card"` | boolean | `false` | } |
| size | preset tab bar size | `large` \| `default` \| `small` | `default` | | size | preset tab bar size | `large` \| `default` \| `small` | `default` | |
| tabBarExtraContent | Extra content in tab bar | slot | - | | leftExtra | Extra content in tab bar left | v-slot:leftExtra | - | 3.0 |
| tabBarStyle | Tab bar style object | object | - | | rightExtra | Extra content in tab bar right | v-slot:rightExtra | - | 3.0 |
| tabPosition | Position of tabs | `top` \| `right` \| `bottom` \| `left` | `top` | | tabBarStyle | Tab bar style object | object | - | |
| type | Basic style of tabs | `line` \| `card` \| `editable-card` | `line` | | tabPosition | Position of tabs | `top` \| `right` \| `bottom` \| `left` | `top` | |
| tabBarGutter | The gap between tabs | number | - | | type | Basic style of tabs | `line` \| `card` \| `editable-card` | `line` | |
| tabBarGutter | The gap between tabs | number | - | |
### Events ### Events

View File

@ -22,18 +22,19 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
### Tabs ### Tabs
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| activeKey(v-model) | 当前激活 tab 面板的 key | string | 无 | | activeKey(v-model) | 当前激活 tab 面板的 key | string | 无 | |
| animated | 是否使用动画切换 Tabs`tabPosition=top | bottom` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false | | animated | 是否使用动画切换 Tabs`tabPosition=top | bottom` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false | |
| defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | 第一个面板 | | defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | 第一个面板 | |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | | hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | |
| size | 大小,提供 `large` `default``small` 三种大小 | string | 'default' | | size | 大小,提供 `large` `default``small` 三种大小 | string | 'default' | |
| tabBarExtraContent | tab bar 上额外的元素 | slot | 无 | | leftExtra | tab bar 上左侧额外的元素 | v-slot:leftExtra | - | 3.0 |
| tabBarStyle | tab bar 的样式对象 | object | - | | rightExtra | tab bar 上右侧额外的元素 | v-slot:rightExtra | - | 3.0 |
| tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | 'top' | | tabBarStyle | tab bar 的样式对象 | object | - | |
| type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | 'line' | | tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | 'top' | |
| tabBarGutter | tabs 之间的间隙 | number | 无 | | type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | 'line' | |
| tabBarGutter | tabs 之间的间隙 | number | 无 | |
### 事件 ### 事件

View File

@ -112,7 +112,7 @@ export default defineComponent({
}} }}
onFocus={onFocus} onFocus={onFocus}
> >
{tab} {typeof tab === 'function' ? tab() : tab}
</div> </div>
{/* Remove Button */} {/* Remove Button */}
@ -127,7 +127,7 @@ export default defineComponent({
onRemoveTab(e); onRemoveTab(e);
}} }}
> >
{closeIcon || editable.removeIcon || '×'} {closeIcon?.() || editable.removeIcon?.() || '×'}
</button> </button>
)} )}
</div> </div>

View File

@ -35,7 +35,6 @@ const tabNavListProps = () => {
activeKey: { type: [String, Number] }, activeKey: { type: [String, Number] },
rtl: { type: Boolean }, rtl: { type: Boolean },
animated: { type: Object as PropType<AnimatedConfig>, default: undefined as AnimatedConfig }, animated: { type: Object as PropType<AnimatedConfig>, default: undefined as AnimatedConfig },
extra: PropTypes.any,
editable: { type: Object as PropType<EditableConfig> }, editable: { type: Object as PropType<EditableConfig> },
moreIcon: PropTypes.any, moreIcon: PropTypes.any,
moreTransitionName: { type: String }, moreTransitionName: { type: String },
@ -55,14 +54,14 @@ export type TabNavListProps = Partial<ExtractPropTypes<ReturnType<typeof tabNavL
interface ExtraContentProps { interface ExtraContentProps {
position: TabBarExtraPosition; position: TabBarExtraPosition;
prefixCls: string; prefixCls: string;
extra?: TabBarExtraContent; extra?: (info?: { position: 'left' | 'right' }) => TabBarExtraContent;
} }
export default defineComponent({ export default defineComponent({
name: 'TabNavList', name: 'TabNavList',
inheritAttrs: false, inheritAttrs: false,
props: tabNavListProps(), props: tabNavListProps(),
slots: ['moreIcon', 'extra'], slots: ['moreIcon', 'leftExtra', 'rightExtra', 'tabBarExtraContent'],
emits: ['tabClick', 'tabScroll'], emits: ['tabClick', 'tabScroll'],
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
const { tabs, prefixCls } = useInjectTabs(); const { tabs, prefixCls } = useInjectTabs();
@ -387,9 +386,7 @@ export default defineComponent({
const ExtraContent = ({ position, prefixCls, extra }: ExtraContentProps) => { const ExtraContent = ({ position, prefixCls, extra }: ExtraContentProps) => {
if (!extra) return null; if (!extra) return null;
const content = extra?.({ position });
const content = slots.extra?.({ position });
return content ? <div class={`${prefixCls}-extra-content`}>{content}</div> : null; return content ? <div class={`${prefixCls}-extra-content`}>{content}</div> : null;
}; };
@ -404,7 +401,6 @@ export default defineComponent({
animated, animated,
activeKey, activeKey,
rtl, rtl,
extra,
editable, editable,
locale, locale,
tabPosition, tabPosition,
@ -489,7 +485,7 @@ export default defineComponent({
doLockAnimation(); doLockAnimation();
}} }}
> >
<ExtraContent position="left" extra={extra} prefixCls={pre} /> <ExtraContent position="left" prefixCls={pre} extra={slots.leftExtra} />
<ResizeObserver onResize={onListHolderResize}> <ResizeObserver onResize={onListHolderResize}>
<div <div
@ -540,7 +536,8 @@ export default defineComponent({
class={!hasDropdown && operationsHiddenClassName.value} class={!hasDropdown && operationsHiddenClassName.value}
/> />
<ExtraContent position="right" extra={extra} prefixCls={pre} /> <ExtraContent position="right" prefixCls={pre} extra={slots.rightExtra} />
<ExtraContent position="right" prefixCls={pre} extra={slots.tabBarExtraContent} />
</div> </div>
); );
}; };

View File

@ -8,7 +8,7 @@ export interface TabPaneProps {
disabled?: boolean; disabled?: boolean;
forceRender?: boolean; forceRender?: boolean;
closable?: boolean; closable?: boolean;
closeIcon?: VueNode; closeIcon?: () => VueNode;
// Pass by TabPaneList // Pass by TabPaneList
prefixCls?: string; prefixCls?: string;

View File

@ -23,6 +23,7 @@ import devWarning from '../../vc-util/devWarning';
import type { SizeType } from '../../config-provider'; import type { SizeType } from '../../config-provider';
import { useProvideTabs } from './TabContext'; import { useProvideTabs } from './TabContext';
import type { Key } from '../../_util/type'; import type { Key } from '../../_util/type';
import pick from 'lodash-es/pick';
export type TabsType = 'line' | 'card' | 'editable-card'; export type TabsType = 'line' | 'card' | 'editable-card';
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left'; export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';
@ -121,7 +122,15 @@ const InternalTabs = defineComponent({
}), }),
tabs: { type: Array as PropType<Tab[]> }, tabs: { type: Array as PropType<Tab[]> },
}, },
slots: ['tabBarExtraContent', 'moreIcon', 'addIcon', 'removeIcon'], slots: [
'tabBarExtraContent',
'leftExtra',
'rightExtra',
'moreIcon',
'addIcon',
'removeIcon',
'renderTabBar',
],
emits: ['tabClick', 'tabScroll', 'change', 'update:activeKey'], emits: ['tabClick', 'tabScroll', 'change', 'update:activeKey'],
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
devWarning( devWarning(
@ -129,6 +138,11 @@ const InternalTabs = defineComponent({
'Tabs', 'Tabs',
'`onPrevClick / @prevClick` and `onNextClick / @nextClick` has been removed. Please use `onTabScroll / @tabScroll` instead.', '`onPrevClick / @prevClick` and `onNextClick / @nextClick` has been removed. Please use `onTabScroll / @tabScroll` instead.',
); );
devWarning(
!(slots.tabBarExtraContent !== undefined),
'Tabs',
'`tabBarExtraContent` slot is deprecated. Please use `rightExtra` instead.',
);
const { prefixCls, direction, size, rootPrefixCls } = useConfigInject('tabs', props); const { prefixCls, direction, size, rootPrefixCls } = useConfigInject('tabs', props);
const rtl = computed(() => direction.value === 'rtl'); const rtl = computed(() => direction.value === 'rtl');
const mergedAnimated = computed<AnimatedConfig>(() => { const mergedAnimated = computed<AnimatedConfig>(() => {
@ -218,7 +232,7 @@ const InternalTabs = defineComponent({
tabBarStyle, tabBarStyle,
locale, locale,
destroyInactiveTabPane, destroyInactiveTabPane,
renderTabBar, renderTabBar = slots.renderTabBar,
onTabScroll, onTabScroll,
hideAdd, hideAdd,
centered, centered,
@ -259,15 +273,12 @@ const InternalTabs = defineComponent({
}; };
if (renderTabBar) { if (renderTabBar) {
tabNavBar = renderTabBar(tabNavBarProps, TabNavList); tabNavBar = renderTabBar({ ...tabNavBarProps, DefaultTabBar: TabNavList });
} else { } else {
tabNavBar = ( tabNavBar = (
<TabNavList <TabNavList
{...tabNavBarProps} {...tabNavBarProps}
v-slots={{ v-slots={pick(slots, ['moreIcon', 'leftExtra', 'rightExtra', 'tabBarExtraContent'])}
moreIcon: slots.moreIcon,
extra: slots.tabBarExtraContent,
}}
/> />
); );
} }
@ -281,7 +292,7 @@ const InternalTabs = defineComponent({
pre, pre,
`${pre}-${mergedTabPosition.value}`, `${pre}-${mergedTabPosition.value}`,
{ {
[`${pre}-${size}`]: size.value, [`${pre}-${size.value}`]: size.value,
[`${pre}-card`]: ['card', 'editable-card'].includes(type as string), [`${pre}-card`]: ['card', 'editable-card'].includes(type as string),
[`${pre}-editable-card`]: type === 'editable-card', [`${pre}-editable-card`]: type === 'editable-card',
[`${pre}-centered`]: centered, [`${pre}-centered`]: centered,
@ -314,7 +325,15 @@ export default defineComponent({
tabPane: false, tabPane: false,
}, },
}), }),
slots: ['tabBarExtraContent', 'moreIcon', 'addIcon', 'removeIcon'], slots: [
'tabBarExtraContent',
'leftExtra',
'rightExtra',
'moreIcon',
'addIcon',
'removeIcon',
'renderTabBar',
],
emits: ['tabClick', 'tabScroll', 'change', 'update:activeKey'], emits: ['tabClick', 'tabScroll', 'change', 'update:activeKey'],
setup(props, { attrs, slots, emit }) { setup(props, { attrs, slots, emit }) {
const handleChange = (key: string) => { const handleChange = (key: string) => {

View File

@ -19,7 +19,7 @@ export interface Tab extends TabPaneProps {
node: VueNode; node: VueNode;
} }
export type RenderTabBar = (props: any, DefaultTabBar: any) => VueNode; export type RenderTabBar = (props: { DefaultTabBar: any; [key: string]: any }) => VueNode;
export interface TabsLocale { export interface TabsLocale {
dropdownAriaLabel?: string; dropdownAriaLabel?: string;