feat: tabs overflow node support remove
parent
02e6fecc6d
commit
89435951ae
|
@ -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: () => (
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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%);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue