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({
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 2', content: 'Content of Tab 2', key: '2' },
]);

View File

@ -16,17 +16,13 @@ Customized bar of tab.
</docs>
<template>
<div>
<div style="height: 2000px">
<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="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>
<template #renderTabBar="{ DefaultTabBar, ...props }">
<component
:is="DefaultTabBar"
v-bind="props"
:style="{ zIndex: 1, background: '#fff', textAlign: 'right' }"
/>
<component :is="DefaultTabBar" v-bind="props" :style="{ opacity: 0.5 }" />
</template>
</a-tabs>
</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="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>
<template #tabBarExtraContent>
<a-button>Extra Action</a-button>
<template #leftExtra>
<a-button class="tabs-extra-demo-button">Left Extra Action</a-button>
</template>
<template #rightExtra>
<a-button>Right Extra Action</a-button>
</template>
</a-tabs>
</template>
@ -37,3 +40,13 @@ export default defineComponent({
},
});
</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
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| 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"` |
| 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 | slot | - |
| 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` |
| tabBarGutter | The gap between tabs | number | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| 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"` | |
| 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` | |
| leftExtra | Extra content in tab bar left | v-slot:leftExtra | - | 3.0 |
| rightExtra | Extra content in tab bar right | v-slot:rightExtra | - | 3.0 |
| 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` | |
| tabBarGutter | The gap between tabs | number | - | |
### Events

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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