feat: tabs overflow node support remove

doc-form
tangjinzhou 3 years ago
parent 02e6fecc6d
commit 89435951ae

@ -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<Tab[]> },
rtl: { type: Boolean },
tabBarGutter: { type: Number },
activeKey: { type: [String, Number] },
mobile: { type: Boolean },
moreIcon: PropTypes.any,
moreTransitionName: { type: String },
editable: { type: Object as PropType<EditableConfig> },
locale: { type: Object as PropType<TabsLocale>, default: undefined as TabsLocale },
removeAriaLabel: String,
onTabClick: { type: Function as PropType<(key: Key, e: MouseEvent | KeyboardEvent) => void> },
};
export type OperationNodeProps = Partial<ExtractPropTypes<typeof operationNodeProps>>;
export default defineComponent({
name: 'OperationNode',
inheritAttrs: false,
props: {
prefixCls: { type: String },
id: { type: String },
tabs: { type: Object as PropType<Tab[]> },
rtl: { type: Boolean },
tabBarGutter: { type: Number },
activeKey: { type: [String, Number] },
mobile: { type: Boolean },
moreIcon: PropTypes.any,
moreTransitionName: { type: String },
editable: { type: Object as PropType<EditableConfig> },
locale: { type: Object as PropType<TabsLocale>, 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 => (
<MenuItem
key={tab.key}
id={`${popupId.value}-${tab.key}`}
role="option"
aria-controls={id && `${id}-panel-${tab.key}`}
disabled={tab.disabled}
>
{typeof tab.tab === 'function' ? tab.tab() : tab.tab}
</MenuItem>
))}
{tabs.map(tab => {
const removable = editable && tab.closable !== false && !tab.disabled;
return (
<MenuItem
key={tab.key}
id={`${popupId.value}-${tab.key}`}
role="option"
aria-controls={id && `${id}-panel-${tab.key}`}
disabled={tab.disabled}
>
<span>{typeof tab.tab === 'function' ? tab.tab() : tab.tab}</span>
{removable && (
<button
type="button"
aria-label={props.removeAriaLabel || 'remove'}
tabindex={0}
class={`${dropdownPrefix}-menu-item-remove`}
onClick={e => {
e.stopPropagation();
onRemoveTab(e, tab.key);
}}
>
{tab.closeIcon?.() || editable.removeIcon?.() || '×'}
</button>
)}
</MenuItem>
);
})}
</Menu>
),
default: () => (

@ -529,6 +529,7 @@ export default defineComponent({
</ResizeObserver>
<OperationNode
{...props}
removeAriaLabel={locale?.removeAriaLabel}
v-slots={pick(slots, ['moreIcon'])}
ref={operationsRef}
prefixCls={pre}

@ -1,4 +1,4 @@
// base rc-tabs 4.16.6
// base rc-tabs 11.10.1
import Tabs from './Tabs';
import type { TabsProps } from './Tabs';
import TabPane from './TabPanelList/TabPane';

@ -1,6 +1,7 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
.@{tab-prefix-cls}-card {
> .@{tab-prefix-cls}-nav,

@ -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;
}

@ -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;

@ -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%);

@ -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;

@ -1,6 +1,7 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
.@{tab-prefix-cls} {
&-small {

Loading…
Cancel
Save