diff --git a/components/tabs/src/TabNavList/OperationNode.tsx b/components/tabs/src/TabNavList/OperationNode.tsx index eec013d85..6ef15dc30 100644 --- a/components/tabs/src/TabNavList/OperationNode.tsx +++ b/components/tabs/src/TabNavList/OperationNode.tsx @@ -2,47 +2,37 @@ import Menu, { MenuItem } from '../../../menu'; import Dropdown from '../../../vc-dropdown'; import type { Tab, TabsLocale, EditableConfig } from '../interface'; import AddButton from './AddButton'; -import type { Key, VueNode } from '../../../_util/type'; +import type { Key } from '../../../_util/type'; import KeyCode from '../../../_util/KeyCode'; -import type { CSSProperties, PropType } from 'vue'; +import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; import classNames from '../../../_util/classNames'; import { defineComponent, watch, computed, onMounted } from 'vue'; import PropTypes from '../../../_util/vue-types'; import useState from '../../../_util/hooks/useState'; import { EllipsisOutlined } from '@ant-design/icons-vue'; -export interface OperationNodeProps { - prefixCls: string; - id: string; - tabs: Tab[]; - rtl: boolean; - tabBarGutter?: number; - activeKey: Key; - mobile: boolean; - moreIcon?: VueNode; - moreTransitionName?: string; - editable?: EditableConfig; - locale?: TabsLocale; - onTabClick: (key: Key, e: MouseEvent | KeyboardEvent) => void; -} +const operationNodeProps = { + prefixCls: { type: String }, + id: { type: String }, + tabs: { type: Object as PropType }, + rtl: { type: Boolean }, + tabBarGutter: { type: Number }, + activeKey: { type: [String, Number] }, + mobile: { type: Boolean }, + moreIcon: PropTypes.any, + moreTransitionName: { type: String }, + editable: { type: Object as PropType }, + locale: { type: Object as PropType, default: undefined as TabsLocale }, + removeAriaLabel: String, + onTabClick: { type: Function as PropType<(key: Key, e: MouseEvent | KeyboardEvent) => void> }, +}; + +export type OperationNodeProps = Partial>; export default defineComponent({ name: 'OperationNode', inheritAttrs: false, - props: { - prefixCls: { type: String }, - id: { type: String }, - tabs: { type: Object as PropType }, - rtl: { type: Boolean }, - tabBarGutter: { type: Number }, - activeKey: { type: [String, Number] }, - mobile: { type: Boolean }, - moreIcon: PropTypes.any, - moreTransitionName: { type: String }, - editable: { type: Object as PropType }, - locale: { type: Object as PropType, default: undefined as TabsLocale }, - onTabClick: { type: Function as PropType<(key: Key, e: MouseEvent | KeyboardEvent) => void> }, - }, + props: operationNodeProps, emits: ['tabClick'], slots: ['moreIcon'], setup(props, { attrs, slots }) { @@ -99,6 +89,15 @@ export default defineComponent({ selectedKey.value !== null ? `${popupId.value}-${selectedKey.value}` : null, ); + const onRemoveTab = (event: MouseEvent | KeyboardEvent, key: Key) => { + event.preventDefault(); + event.stopPropagation(); + props.editable.onEdit('remove', { + key, + event, + }); + }; + onMounted(() => { watch( selectedKey, @@ -174,17 +173,34 @@ export default defineComponent({ dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown' } > - {tabs.map(tab => ( - - {typeof tab.tab === 'function' ? tab.tab() : tab.tab} - - ))} + {tabs.map(tab => { + const removable = editable && tab.closable !== false && !tab.disabled; + return ( + + {typeof tab.tab === 'function' ? tab.tab() : tab.tab} + {removable && ( + + )} + + ); + })} ), default: () => ( diff --git a/components/tabs/src/TabNavList/index.tsx b/components/tabs/src/TabNavList/index.tsx index 227930239..664bd7cb9 100644 --- a/components/tabs/src/TabNavList/index.tsx +++ b/components/tabs/src/TabNavList/index.tsx @@ -529,6 +529,7 @@ export default defineComponent({ .@{tab-prefix-cls}-nav, diff --git a/components/tabs/style/dropdown.less b/components/tabs/style/dropdown.less index 7cbd90941..b0444afcb 100644 --- a/components/tabs/style/dropdown.less +++ b/components/tabs/style/dropdown.less @@ -1,6 +1,7 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; -@import './index'; + +@tab-prefix-cls: ~'@{ant-prefix}-tabs'; .@{tab-prefix-cls}-dropdown { .reset-component(); @@ -30,6 +31,8 @@ box-shadow: @box-shadow-base; &-item { + display: flex; + align-items: center; min-width: 120px; margin: 0; padding: @dropdown-vertical-padding @control-padding-horizontal; @@ -43,6 +46,25 @@ cursor: pointer; transition: all 0.3s; + > span { + flex: 1; + white-space: nowrap; + } + + &-remove { + flex: none; + margin-left: @margin-sm; + color: @text-color-secondary; + font-size: @font-size-sm; + background: transparent; + border: 0; + cursor: pointer; + + &:hover { + color: @tabs-hover-color; + } + } + &:hover { background: @item-hover-bg; } diff --git a/components/tabs/style/index.less b/components/tabs/style/index.less index 44645b330..d6ffe501c 100644 --- a/components/tabs/style/index.less +++ b/components/tabs/style/index.less @@ -81,6 +81,7 @@ .@{tab-prefix-cls}-nav-add { min-width: @tabs-card-height; + margin-left: @tabs-card-gutter; padding: 0 @padding-xs; background: @tabs-card-head-background; border: @border-width-base @border-style-base @border-color-split; diff --git a/components/tabs/style/position.less b/components/tabs/style/position.less index 9da9bb8ad..9087f43ba 100644 --- a/components/tabs/style/position.less +++ b/components/tabs/style/position.less @@ -1,4 +1,5 @@ -@import './index'; +@import '../../style/themes/index'; +@tab-prefix-cls: ~'@{ant-prefix}-tabs'; .@{tab-prefix-cls} { // ========================== Top & Bottom ========================== @@ -39,6 +40,7 @@ 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%); @@ -122,6 +124,7 @@ 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%); diff --git a/components/tabs/style/rtl.less b/components/tabs/style/rtl.less index b49e77b57..bfe4d84ff 100644 --- a/components/tabs/style/rtl.less +++ b/components/tabs/style/rtl.less @@ -58,8 +58,14 @@ > div > .@{tab-prefix-cls}-nav { .@{tab-prefix-cls}-tab + .@{tab-prefix-cls}-tab { .@{tab-prefix-cls}-rtl& { - margin-right: 0; - margin-left: @tabs-card-gutter; + margin-right: @tabs-card-gutter; + margin-left: 0; + } + } + .@{tab-prefix-cls}-nav-add { + .@{tab-prefix-cls}-rtl& { + margin-right: @tabs-card-gutter; + margin-left: 0; } } } @@ -71,6 +77,7 @@ &-rtl { direction: rtl; } + &-menu-item { .@{tab-prefix-cls}-dropdown-rtl & { text-align: right; diff --git a/components/tabs/style/size.less b/components/tabs/style/size.less index 43a75f179..03c0fea9a 100644 --- a/components/tabs/style/size.less +++ b/components/tabs/style/size.less @@ -1,6 +1,7 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; -@import './index'; + +@tab-prefix-cls: ~'@{ant-prefix}-tabs'; .@{tab-prefix-cls} { &-small {