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,34 +2,16 @@ 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;
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;
}
export default defineComponent({
name: 'OperationNode',
inheritAttrs: false,
props: {
prefixCls: { type: String }, prefixCls: { type: String },
id: { type: String }, id: { type: String },
tabs: { type: Object as PropType<Tab[]> }, tabs: { type: Object as PropType<Tab[]> },
@ -41,8 +23,16 @@ export default defineComponent({
moreTransitionName: { type: String }, moreTransitionName: { type: String },
editable: { type: Object as PropType<EditableConfig> }, editable: { type: Object as PropType<EditableConfig> },
locale: { type: Object as PropType<TabsLocale>, default: undefined as TabsLocale }, locale: { type: Object as PropType<TabsLocale>, default: undefined as TabsLocale },
removeAriaLabel: String,
onTabClick: { type: Function as PropType<(key: Key, e: MouseEvent | KeyboardEvent) => void> }, 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: operationNodeProps,
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,7 +173,9 @@ export default defineComponent({
dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown' dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown'
} }
> >
{tabs.map(tab => ( {tabs.map(tab => {
const removable = editable && tab.closable !== false && !tab.disabled;
return (
<MenuItem <MenuItem
key={tab.key} key={tab.key}
id={`${popupId.value}-${tab.key}`} id={`${popupId.value}-${tab.key}`}
@ -182,9 +183,24 @@ export default defineComponent({
aria-controls={id && `${id}-panel-${tab.key}`} aria-controls={id && `${id}-panel-${tab.key}`}
disabled={tab.disabled} disabled={tab.disabled}
> >
{typeof tab.tab === 'function' ? tab.tab() : tab.tab} <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> </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 {