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 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;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'OperationNode',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
const operationNodeProps = {
|
||||
prefixCls: { type: String },
|
||||
id: { type: String },
|
||||
tabs: { type: Object as PropType<Tab[]> },
|
||||
|
@ -41,8 +23,16 @@ export default defineComponent({
|
|||
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: 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,7 +173,9 @@ export default defineComponent({
|
|||
dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown'
|
||||
}
|
||||
>
|
||||
{tabs.map(tab => (
|
||||
{tabs.map(tab => {
|
||||
const removable = editable && tab.closable !== false && !tab.disabled;
|
||||
return (
|
||||
<MenuItem
|
||||
key={tab.key}
|
||||
id={`${popupId.value}-${tab.key}`}
|
||||
|
@ -182,9 +183,24 @@ export default defineComponent({
|
|||
aria-controls={id && `${id}-panel-${tab.key}`}
|
||||
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>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</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…
Reference in New Issue