feat: tabs overflow node support remove

doc-form
tangjinzhou 2021-12-20 16:19:35 +08:00
parent 02e6fecc6d
commit 89435951ae
9 changed files with 100 additions and 48 deletions

View File

@ -2,47 +2,37 @@ import Menu, { MenuItem } from '../../../menu';
import Dropdown from '../../../vc-dropdown'; import Dropdown from '../../../vc-dropdown';
import type { Tab, TabsLocale, EditableConfig } from '../interface'; import type { Tab, TabsLocale, EditableConfig } from '../interface';
import AddButton from './AddButton'; import AddButton from './AddButton';
import type { Key, VueNode } from '../../../_util/type'; import type { Key } from '../../../_util/type';
import KeyCode from '../../../_util/KeyCode'; import KeyCode from '../../../_util/KeyCode';
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import classNames from '../../../_util/classNames'; import classNames from '../../../_util/classNames';
import { defineComponent, watch, computed, onMounted } from 'vue'; import { defineComponent, watch, computed, onMounted } from 'vue';
import PropTypes from '../../../_util/vue-types'; import PropTypes from '../../../_util/vue-types';
import useState from '../../../_util/hooks/useState'; import useState from '../../../_util/hooks/useState';
import { EllipsisOutlined } from '@ant-design/icons-vue'; import { EllipsisOutlined } from '@ant-design/icons-vue';
export interface OperationNodeProps { const operationNodeProps = {
prefixCls: string; prefixCls: { type: String },
id: string; id: { type: String },
tabs: Tab[]; tabs: { type: Object as PropType<Tab[]> },
rtl: boolean; rtl: { type: Boolean },
tabBarGutter?: number; tabBarGutter: { type: Number },
activeKey: Key; activeKey: { type: [String, Number] },
mobile: boolean; mobile: { type: Boolean },
moreIcon?: VueNode; moreIcon: PropTypes.any,
moreTransitionName?: string; moreTransitionName: { type: String },
editable?: EditableConfig; editable: { type: Object as PropType<EditableConfig> },
locale?: TabsLocale; locale: { type: Object as PropType<TabsLocale>, default: undefined as TabsLocale },
onTabClick: (key: Key, e: MouseEvent | KeyboardEvent) => void; removeAriaLabel: String,
} onTabClick: { type: Function as PropType<(key: Key, e: MouseEvent | KeyboardEvent) => void> },
};
export type OperationNodeProps = Partial<ExtractPropTypes<typeof operationNodeProps>>;
export default defineComponent({ export default defineComponent({
name: 'OperationNode', name: 'OperationNode',
inheritAttrs: false, inheritAttrs: false,
props: { props: 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 },
onTabClick: { type: Function as PropType<(key: Key, e: MouseEvent | KeyboardEvent) => void> },
},
emits: ['tabClick'], emits: ['tabClick'],
slots: ['moreIcon'], slots: ['moreIcon'],
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
@ -99,6 +89,15 @@ export default defineComponent({
selectedKey.value !== null ? `${popupId.value}-${selectedKey.value}` : null, 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(() => { onMounted(() => {
watch( watch(
selectedKey, selectedKey,
@ -174,17 +173,34 @@ export default defineComponent({
dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown' dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown'
} }
> >
{tabs.map(tab => ( {tabs.map(tab => {
<MenuItem const removable = editable && tab.closable !== false && !tab.disabled;
key={tab.key} return (
id={`${popupId.value}-${tab.key}`} <MenuItem
role="option" key={tab.key}
aria-controls={id && `${id}-panel-${tab.key}`} id={`${popupId.value}-${tab.key}`}
disabled={tab.disabled} role="option"
> aria-controls={id && `${id}-panel-${tab.key}`}
{typeof tab.tab === 'function' ? tab.tab() : tab.tab} disabled={tab.disabled}
</MenuItem> >
))} <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> </Menu>
), ),
default: () => ( default: () => (

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
@import '../../style/themes/index'; @import '../../style/themes/index';
@import '../../style/mixins/index'; @import '../../style/mixins/index';
@import './index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
.@{tab-prefix-cls}-dropdown { .@{tab-prefix-cls}-dropdown {
.reset-component(); .reset-component();
@ -30,6 +31,8 @@
box-shadow: @box-shadow-base; box-shadow: @box-shadow-base;
&-item { &-item {
display: flex;
align-items: center;
min-width: 120px; min-width: 120px;
margin: 0; margin: 0;
padding: @dropdown-vertical-padding @control-padding-horizontal; padding: @dropdown-vertical-padding @control-padding-horizontal;
@ -43,6 +46,25 @@
cursor: pointer; cursor: pointer;
transition: all 0.3s; 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 { &:hover {
background: @item-hover-bg; background: @item-hover-bg;
} }

View File

@ -81,6 +81,7 @@
.@{tab-prefix-cls}-nav-add { .@{tab-prefix-cls}-nav-add {
min-width: @tabs-card-height; min-width: @tabs-card-height;
margin-left: @tabs-card-gutter;
padding: 0 @padding-xs; padding: 0 @padding-xs;
background: @tabs-card-head-background; background: @tabs-card-head-background;
border: @border-width-base @border-style-base @border-color-split; border: @border-width-base @border-style-base @border-color-split;

View File

@ -1,4 +1,5 @@
@import './index'; @import '../../style/themes/index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
.@{tab-prefix-cls} { .@{tab-prefix-cls} {
// ========================== Top & Bottom ========================== // ========================== Top & Bottom ==========================
@ -39,6 +40,7 @@
left: 0; left: 0;
box-shadow: inset 10px 0 8px -8px fade(@shadow-color, 8%); box-shadow: inset 10px 0 8px -8px fade(@shadow-color, 8%);
} }
&::after { &::after {
right: 0; right: 0;
box-shadow: inset -10px 0 8px -8px fade(@shadow-color, 8%); box-shadow: inset -10px 0 8px -8px fade(@shadow-color, 8%);
@ -122,6 +124,7 @@
top: 0; top: 0;
box-shadow: inset 0 10px 8px -8px fade(@shadow-color, 8%); box-shadow: inset 0 10px 8px -8px fade(@shadow-color, 8%);
} }
&::after { &::after {
bottom: 0; bottom: 0;
box-shadow: inset 0 -10px 8px -8px fade(@shadow-color, 8%); box-shadow: inset 0 -10px 8px -8px fade(@shadow-color, 8%);

View File

@ -58,8 +58,14 @@
> div > .@{tab-prefix-cls}-nav { > div > .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab + .@{tab-prefix-cls}-tab { .@{tab-prefix-cls}-tab + .@{tab-prefix-cls}-tab {
.@{tab-prefix-cls}-rtl& { .@{tab-prefix-cls}-rtl& {
margin-right: 0; margin-right: @tabs-card-gutter;
margin-left: @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 { &-rtl {
direction: rtl; direction: rtl;
} }
&-menu-item { &-menu-item {
.@{tab-prefix-cls}-dropdown-rtl & { .@{tab-prefix-cls}-dropdown-rtl & {
text-align: right; text-align: right;

View File

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