refactor: menu

feat-new-menu
tanjinzhou 2021-05-14 16:46:43 +08:00
parent ab4478c9f4
commit 994df6ff6f
29 changed files with 1039 additions and 763 deletions

View File

@ -1,322 +1,22 @@
import { defineComponent, inject, provide, toRef, App, ExtractPropTypes, Plugin } from 'vue'; import Menu from './src/Menu';
import omit from 'omit.js'; import MenuItem from './src/MenuItem';
import VcMenu, { Divider, ItemGroup } from '../vc-menu'; import SubMenu from './src/SubMenu';
import SubMenu from './SubMenu'; import ItemGroup from './src/ItemGroup';
import PropTypes from '../_util/vue-types'; import Divider from './src/Divider';
import animation from '../_util/openAnimation'; import { App } from 'vue';
import warning from '../_util/warning';
import Item from './MenuItem';
import { hasProp, getOptionProps } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import commonPropsType from '../vc-menu/commonPropsType';
import { defaultConfigProvider } from '../config-provider';
import { SiderContextProps } from '../layout/Sider';
import { tuple } from '../_util/type';
// import raf from '../_util/raf';
export const MenuMode = PropTypes.oneOf([
'vertical',
'vertical-left',
'vertical-right',
'horizontal',
'inline',
]);
export const menuProps = {
...commonPropsType,
theme: PropTypes.oneOf(tuple('light', 'dark')).def('light'),
mode: MenuMode.def('vertical'),
selectable: PropTypes.looseBool,
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
openKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
defaultOpenKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
openTransitionName: PropTypes.string,
prefixCls: PropTypes.string,
multiple: PropTypes.looseBool,
inlineIndent: PropTypes.number.def(24),
inlineCollapsed: PropTypes.looseBool,
isRootMenu: PropTypes.looseBool.def(true),
focusable: PropTypes.looseBool.def(false),
onOpenChange: PropTypes.func,
onSelect: PropTypes.func,
onDeselect: PropTypes.func,
onClick: PropTypes.func,
onMouseenter: PropTypes.func,
onSelectChange: PropTypes.func,
};
export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
const Menu = defineComponent({
name: 'AMenu',
mixins: [BaseMixin],
inheritAttrs: false,
props: menuProps,
Divider: { ...Divider, name: 'AMenuDivider' },
Item: { ...Item, name: 'AMenuItem' },
SubMenu: { ...SubMenu, name: 'ASubMenu' },
ItemGroup: { ...ItemGroup, name: 'AMenuItemGroup' },
emits: [
'update:selectedKeys',
'update:openKeys',
'mouseenter',
'openChange',
'click',
'selectChange',
'select',
'deselect',
],
setup() {
const layoutSiderContext = inject<SiderContextProps>('layoutSiderContext', {});
const layoutSiderCollapsed = toRef(layoutSiderContext, 'sCollapsed');
return {
configProvider: inject('configProvider', defaultConfigProvider),
layoutSiderContext,
layoutSiderCollapsed,
propsUpdating: false,
switchingModeFromInline: false,
leaveAnimationExecutedWhenInlineCollapsed: false,
inlineOpenKeys: [],
};
},
data() {
const props: MenuProps = getOptionProps(this);
warning(
!('inlineCollapsed' in props && props.mode !== 'inline'),
'Menu',
"`inlineCollapsed` should only be used when Menu's `mode` is inline.",
);
let sOpenKeys: (number | string)[];
if ('openKeys' in props) {
sOpenKeys = props.openKeys;
} else if ('defaultOpenKeys' in props) {
sOpenKeys = props.defaultOpenKeys;
}
return {
sOpenKeys,
};
},
// beforeUnmount() {
// raf.cancel(this.mountRafId);
// },
watch: {
mode(val, oldVal) {
if (oldVal === 'inline' && val !== 'inline') {
this.switchingModeFromInline = true;
}
},
openKeys(val) {
this.setState({ sOpenKeys: val });
},
inlineCollapsed(val) {
this.collapsedChange(val);
},
layoutSiderCollapsed(val) {
this.collapsedChange(val);
},
},
created() {
provide('getInlineCollapsed', this.getInlineCollapsed);
provide('menuPropsContext', this.$props);
},
updated() {
this.propsUpdating = false;
},
methods: {
collapsedChange(val: unknown) {
if (this.propsUpdating) {
return;
}
this.propsUpdating = true;
if (!hasProp(this, 'openKeys')) {
if (val) {
this.switchingModeFromInline = true;
this.inlineOpenKeys = this.sOpenKeys;
this.setState({ sOpenKeys: [] });
} else {
this.setState({ sOpenKeys: this.inlineOpenKeys });
this.inlineOpenKeys = [];
}
} else if (val) {
// openKeysreactopenKeysvue便openKeys
this.switchingModeFromInline = true;
}
},
restoreModeVerticalFromInline() {
if (this.switchingModeFromInline) {
this.switchingModeFromInline = false;
this.$forceUpdate();
}
},
// Restore vertical mode when menu is collapsed responsively when mounted
// https://github.com/ant-design/ant-design/issues/13104
// TODO: not a perfect solution, looking a new way to avoid setting switchingModeFromInline in this situation
handleMouseEnter(e: Event) {
this.restoreModeVerticalFromInline();
this.$emit('mouseenter', e);
},
handleTransitionEnd(e: TransitionEvent) {
// when inlineCollapsed menu width animation finished
// https://github.com/ant-design/ant-design/issues/12864
const widthCollapsed = e.propertyName === 'width' && e.target === e.currentTarget;
// Fix SVGElement e.target.className.indexOf is not a function
// https://github.com/ant-design/ant-design/issues/15699
const { className } = e.target as SVGAnimationElement | HTMLElement;
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during an animation.
const classNameValue =
Object.prototype.toString.call(className) === '[object SVGAnimatedString]'
? className.animVal
: className;
// Fix for <Menu style={{ width: '100%' }} />, the width transition won't trigger when menu is collapsed
// https://github.com/ant-design/ant-design-pro/issues/2783
const iconScaled = e.propertyName === 'font-size' && classNameValue.indexOf('anticon') >= 0;
if (widthCollapsed || iconScaled) {
this.restoreModeVerticalFromInline();
}
},
handleClick(e: Event) {
this.handleOpenChange([]);
this.$emit('click', e);
},
handleSelect(info) {
this.$emit('update:selectedKeys', info.selectedKeys);
this.$emit('select', info);
this.$emit('selectChange', info.selectedKeys);
},
handleDeselect(info) {
this.$emit('update:selectedKeys', info.selectedKeys);
this.$emit('deselect', info);
this.$emit('selectChange', info.selectedKeys);
},
handleOpenChange(openKeys: (number | string)[]) {
this.setOpenKeys(openKeys);
this.$emit('update:openKeys', openKeys);
this.$emit('openChange', openKeys);
},
setOpenKeys(openKeys: (number | string)[]) {
if (!hasProp(this, 'openKeys')) {
this.setState({ sOpenKeys: openKeys });
}
},
getRealMenuMode() {
const inlineCollapsed = this.getInlineCollapsed();
if (this.switchingModeFromInline && inlineCollapsed) {
return 'inline';
}
const { mode } = this.$props;
return inlineCollapsed ? 'vertical' : mode;
},
getInlineCollapsed() {
const { inlineCollapsed } = this.$props;
if (this.layoutSiderContext.sCollapsed !== undefined) {
return this.layoutSiderContext.sCollapsed;
}
return inlineCollapsed;
},
getMenuOpenAnimation(menuMode: string) {
const { openAnimation, openTransitionName } = this.$props;
let menuOpenAnimation = openAnimation || openTransitionName;
if (openAnimation === undefined && openTransitionName === undefined) {
if (menuMode === 'horizontal') {
menuOpenAnimation = 'slide-up';
} else if (menuMode === 'inline') {
menuOpenAnimation = animation;
} else {
// When mode switch from inline
// submenu should hide without animation
if (this.switchingModeFromInline) {
menuOpenAnimation = '';
this.switchingModeFromInline = false;
} else {
menuOpenAnimation = 'zoom-big';
}
}
}
return menuOpenAnimation;
},
},
render() {
const { layoutSiderContext } = this;
const { collapsedWidth } = layoutSiderContext;
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, theme, getPopupContainer } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('menu', customizePrefixCls);
const menuMode = this.getRealMenuMode();
const menuOpenAnimation = this.getMenuOpenAnimation(menuMode);
const { class: className, ...otherAttrs } = this.$attrs;
const menuClassName = {
[className as string]: className,
[`${prefixCls}-${theme}`]: true,
[`${prefixCls}-inline-collapsed`]: this.getInlineCollapsed(),
};
const menuProps = {
...omit(props, [
'inlineCollapsed',
'onUpdate:selectedKeys',
'onUpdate:openKeys',
'onSelectChange',
]),
getPopupContainer: getPopupContainer || getContextPopupContainer,
openKeys: this.sOpenKeys,
mode: menuMode,
prefixCls,
...otherAttrs,
onSelect: this.handleSelect,
onDeselect: this.handleDeselect,
onOpenChange: this.handleOpenChange,
onMouseenter: this.handleMouseEnter,
onTransitionend: this.handleTransitionEnd,
// children: getSlot(this),
};
if (!hasProp(this, 'selectedKeys')) {
delete menuProps.selectedKeys;
}
if (menuMode !== 'inline') {
// closing vertical popup submenu after click it
menuProps.onClick = this.handleClick;
menuProps.openTransitionName = menuOpenAnimation;
} else {
menuProps.onClick = (e: Event) => {
this.$emit('click', e);
};
menuProps.openAnimation = menuOpenAnimation;
}
// https://github.com/ant-design/ant-design/issues/8587
const hideMenu =
this.getInlineCollapsed() &&
(collapsedWidth === 0 || collapsedWidth === '0' || collapsedWidth === '0px');
if (hideMenu) {
menuProps.openKeys = [];
}
return <VcMenu {...menuProps} class={menuClassName} v-slots={this.$slots} />;
},
});
/* istanbul ignore next */ /* istanbul ignore next */
Menu.install = function(app: App) { Menu.install = function(app: App) {
app.component(Menu.name, Menu); app.component(Menu.name, Menu);
app.component(Menu.Item.name, Menu.Item); app.component(MenuItem.name, MenuItem);
app.component(Menu.SubMenu.name, Menu.SubMenu); app.component(SubMenu.name, SubMenu);
app.component(Menu.Divider.name, Menu.Divider); app.component(Divider.name, Divider);
app.component(Menu.ItemGroup.name, Menu.ItemGroup); app.component(ItemGroup.name, ItemGroup);
return app; return app;
}; };
export default Menu as typeof Menu & export default Menu as typeof Menu &
Plugin & { Plugin & {
readonly Item: typeof Item; readonly Item: typeof MenuItem;
readonly SubMenu: typeof SubMenu; readonly SubMenu: typeof SubMenu;
readonly Divider: typeof Divider; readonly Divider: typeof Divider;
readonly ItemGroup: typeof ItemGroup; readonly ItemGroup: typeof ItemGroup;

View File

@ -0,0 +1,29 @@
import { getPropsSlot } from '../../_util/props-util';
import { computed, defineComponent } from 'vue';
import PropTypes from '../../_util/vue-types';
import { useInjectMenu } from './hooks/useMenuContext';
export default defineComponent({
name: 'AMenuItemGroup',
props: {
title: PropTypes.VNodeChild,
},
slots: ['title'],
setup(props, { slots }) {
const { prefixCls } = useInjectMenu();
const groupPrefixCls = computed(() => `${prefixCls.value}-item-group`);
return () => {
return (
<li onClick={e => e.stopPropagation()} class={groupPrefixCls.value}>
<div
title={typeof props.title === 'string' ? props.title : undefined}
class={`${groupPrefixCls.value}-title`}
>
{getPropsSlot(slots, props, 'title')}
</div>
<ul class={`${groupPrefixCls.value}-list`}>{slots.default?.()}</ul>
</li>
);
};
},
});

View File

@ -0,0 +1,21 @@
import usePrefixCls from 'ant-design-vue/es/_util/hooks/usePrefixCls';
import { defineComponent, ExtractPropTypes } from 'vue';
import useProvideMenu from './hooks/useMenuContext';
export const menuProps = {
prefixCls: String,
};
export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
export default defineComponent({
name: 'AMenu',
props: menuProps,
setup(props, { slots }) {
const prefixCls = usePrefixCls('menu', props);
useProvideMenu({ prefixCls });
return () => {
return <div>{slots.default?.()}</div>;
};
},
});

View File

@ -0,0 +1,16 @@
import { defineComponent, getCurrentInstance } from 'vue';
let indexGuid = 0;
export default defineComponent({
name: 'AMenuItem',
setup(props, { slots }) {
const instance = getCurrentInstance();
const key = instance.vnode.key;
const uniKey = `menu_item_${++indexGuid}`;
return () => {
return <li>{slots.default?.()}</li>;
};
},
});

View File

@ -0,0 +1,70 @@
import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue';
// import {
// BuiltinPlacements,
// MenuClickEventHandler,
// MenuMode,
// RenderIconType,
// TriggerSubMenuAction,
// } from '../interface';
export interface MenuContextProps {
prefixCls: ComputedRef<string>;
// openKeys: string[];
// rtl?: boolean;
// // Mode
// mode: MenuMode;
// // Disabled
// disabled?: boolean;
// // Used for overflow only. Prevent hidden node trigger open
// overflowDisabled?: boolean;
// // Active
// activeKey: string;
// onActive: (key: string) => void;
// onInactive: (key: string) => void;
// // Selection
// selectedKeys: string[];
// // Level
// inlineIndent: number;
// // Motion
// // motion?: CSSMotionProps;
// // defaultMotions?: Partial<{ [key in MenuMode | 'other']: CSSMotionProps }>;
// // Popup
// subMenuOpenDelay: number;
// subMenuCloseDelay: number;
// forceSubMenuRender?: boolean;
// builtinPlacements?: BuiltinPlacements;
// triggerSubMenuAction?: TriggerSubMenuAction;
// // Icon
// itemIcon?: RenderIconType;
// expandIcon?: RenderIconType;
// // Function
// onItemClick: MenuClickEventHandler;
// onOpenChange: (key: string, open: boolean) => void;
// getPopupContainer: (node: HTMLElement) => HTMLElement;
}
const MenuContextKey: InjectionKey<MenuContextProps> = Symbol('menuContextKey');
const useProvideMenu = (props: MenuContextProps) => {
provide(MenuContextKey, props);
};
const useInjectMenu = () => {
return inject(MenuContextKey, {
prefixCls: computed(() => 'ant'),
});
};
export { useProvideMenu, MenuContextKey, useInjectMenu };
export default useProvideMenu;

View File

@ -0,0 +1,39 @@
// ========================== Basic ==========================
export type MenuMode = 'horizontal' | 'vertical' | 'inline';
export type BuiltinPlacements = Record<string, any>;
export type TriggerSubMenuAction = 'click' | 'hover';
export interface RenderIconInfo {
isSelected?: boolean;
isOpen?: boolean;
isSubMenu?: boolean;
disabled?: boolean;
}
export type RenderIconType = (props: RenderIconInfo) => any;
export interface MenuInfo {
key: string;
keyPath: string[];
domEvent: MouseEvent | KeyboardEvent;
}
export interface MenuTitleInfo {
key: string;
domEvent: MouseEvent | KeyboardEvent;
}
// ========================== Hover ==========================
export type MenuHoverEventHandler = (info: { key: string; domEvent: MouseEvent }) => void;
// ======================== Selection ========================
export interface SelectInfo extends MenuInfo {
selectedKeys: string[];
}
export type SelectEventHandler = (info: SelectInfo) => void;
// ========================== Click ==========================
export type MenuClickEventHandler = (info: MenuInfo) => void;

View File

@ -0,0 +1,52 @@
const autoAdjustOverflow = {
adjustX: 1,
adjustY: 1,
};
export const placements = {
topLeft: {
points: ['bl', 'tl'],
overflow: autoAdjustOverflow,
offset: [0, -7],
},
bottomLeft: {
points: ['tl', 'bl'],
overflow: autoAdjustOverflow,
offset: [0, 7],
},
leftTop: {
points: ['tr', 'tl'],
overflow: autoAdjustOverflow,
offset: [-4, 0],
},
rightTop: {
points: ['tl', 'tr'],
overflow: autoAdjustOverflow,
offset: [4, 0],
},
};
export const placementsRtl = {
topLeft: {
points: ['bl', 'tl'],
overflow: autoAdjustOverflow,
offset: [0, -7],
},
bottomLeft: {
points: ['tl', 'bl'],
overflow: autoAdjustOverflow,
offset: [0, 7],
},
rightTop: {
points: ['tr', 'tl'],
overflow: autoAdjustOverflow,
offset: [-4, 0],
},
leftTop: {
points: ['tl', 'tr'],
overflow: autoAdjustOverflow,
offset: [4, 0],
},
};
export default placements;

View File

@ -1,7 +1,8 @@
.@{menu-prefix-cls} { .@{menu-prefix-cls} {
// dark theme // dark theme
&-dark, &&-dark,
&-dark &-sub { &-dark &-sub,
&&-dark &-sub {
color: @menu-dark-color; color: @menu-dark-color;
background: @menu-dark-bg; background: @menu-dark-bg;
.@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow { .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
@ -19,8 +20,7 @@
} }
&-dark &-inline&-sub { &-dark &-inline&-sub {
background: @menu-dark-submenu-bg; background: @menu-dark-inline-submenu-bg;
box-shadow: 0 2px 8px fade(@black, 45%) inset;
} }
&-dark&-horizontal { &-dark&-horizontal {
@ -31,17 +31,23 @@
&-dark&-horizontal > &-submenu { &-dark&-horizontal > &-submenu {
top: 0; top: 0;
margin-top: 0; margin-top: 0;
padding: @menu-item-padding;
border-color: @menu-dark-bg; border-color: @menu-dark-bg;
border-bottom: 0; border-bottom: 0;
} }
&-dark&-horizontal > &-item:hover {
background-color: @menu-dark-item-active-bg;
}
&-dark&-horizontal > &-item > a::before { &-dark&-horizontal > &-item > a::before {
bottom: 0; bottom: 0;
} }
&-dark &-item, &-dark &-item,
&-dark &-item-group-title, &-dark &-item-group-title,
&-dark &-item > a { &-dark &-item > a,
&-dark &-item > span > a {
color: @menu-dark-color; color: @menu-dark-color;
} }
@ -77,7 +83,8 @@
&-dark &-submenu-title:hover { &-dark &-submenu-title:hover {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
background-color: transparent; background-color: transparent;
> a { > a,
> span > a {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
} }
> .@{menu-prefix-cls}-submenu-title, > .@{menu-prefix-cls}-submenu-title,
@ -95,6 +102,10 @@
background-color: @menu-dark-item-hover-bg; background-color: @menu-dark-item-hover-bg;
} }
&-dark&-dark:not(&-horizontal) &-item-selected {
background-color: @menu-dark-item-active-bg;
}
&-dark &-item-selected { &-dark &-item-selected {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
border-right: 0; border-right: 0;
@ -102,14 +113,19 @@
border-right: 0; border-right: 0;
} }
> a, > a,
> a:hover { > span > a,
> a:hover,
> span > a:hover {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
} }
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
color: @menu-dark-selected-item-icon-color; color: @menu-dark-selected-item-icon-color;
}
.@{iconfont-css-prefix} + span { + span {
color: @menu-dark-selected-item-text-color; color: @menu-dark-selected-item-text-color;
}
} }
} }
@ -122,7 +138,8 @@
&-dark &-item-disabled, &-dark &-item-disabled,
&-dark &-submenu-disabled { &-dark &-submenu-disabled {
&, &,
> a { > a,
> span > a {
color: @disabled-color-dark !important; color: @disabled-color-dark !important;
opacity: 0.8; opacity: 0.8;
} }

View File

@ -1,7 +1,15 @@
@import '../../style/themes/index'; @import '../../style/themes/index';
@import '../../style/mixins/index'; @import '../../style/mixins/index';
@import './status';
@menu-prefix-cls: ~'@{ant-prefix}-menu'; @menu-prefix-cls: ~'@{ant-prefix}-menu';
@menu-animation-duration-normal: 0.15s;
.accessibility-focus() {
box-shadow: 0 0 0 2px fade(@primary-color, 20%);
}
// TODO: Should remove icon style compatible in v5
// default theme // default theme
.@{menu-prefix-cls} { .@{menu-prefix-cls} {
@ -10,14 +18,21 @@
margin-bottom: 0; margin-bottom: 0;
padding-left: 0; // Override default ul/ol padding-left: 0; // Override default ul/ol
color: @menu-item-color; color: @menu-item-color;
font-size: @menu-item-font-size;
line-height: 0; // Fix display inline-block gap line-height: 0; // Fix display inline-block gap
text-align: left;
list-style: none; list-style: none;
background: @menu-bg; background: @menu-bg;
outline: none; outline: none;
box-shadow: @box-shadow-base; box-shadow: @box-shadow-base;
transition: background 0.3s, width 0.3s cubic-bezier(0.2, 0, 0, 1) 0s; transition: background @animation-duration-slow,
width @animation-duration-slow cubic-bezier(0.2, 0, 0, 1) 0s;
.clearfix(); .clearfix();
&&-root:focus-visible {
.accessibility-focus();
}
ul, ul,
ol { ol {
margin: 0; margin: 0;
@ -25,22 +40,29 @@
list-style: none; list-style: none;
} }
&-hidden { &-hidden,
&-submenu-hidden {
display: none; display: none;
} }
&-item-group-title { &-item-group-title {
height: @menu-item-group-height;
padding: 8px 16px; padding: 8px 16px;
color: @menu-item-group-title-color; color: @menu-item-group-title-color;
font-size: @font-size-base; font-size: @menu-item-group-title-font-size;
line-height: @line-height-base; line-height: @menu-item-group-height;
transition: all 0.3s; transition: all @animation-duration-slow;
} }
&-horizontal &-submenu {
transition: border-color @animation-duration-slow @ease-in-out,
background @animation-duration-slow @ease-in-out;
}
&-submenu, &-submenu,
&-submenu-inline { &-submenu-inline {
transition: border-color 0.3s @ease-in-out, background 0.3s @ease-in-out, transition: border-color @animation-duration-slow @ease-in-out,
padding 0.15s @ease-in-out; background @animation-duration-slow @ease-in-out,
padding @menu-animation-duration-normal @ease-in-out;
} }
&-submenu-selected { &-submenu-selected {
@ -54,11 +76,11 @@
&-submenu &-sub { &-submenu &-sub {
cursor: initial; cursor: initial;
transition: background 0.3s @ease-in-out, padding 0.3s @ease-in-out; transition: background @animation-duration-slow @ease-in-out,
padding @animation-duration-slow @ease-in-out;
} }
&-item > a { &-item a {
display: block;
color: @menu-item-color; color: @menu-item-color;
&:hover { &:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
@ -75,7 +97,7 @@
} }
// https://github.com/ant-design/ant-design/issues/19809 // https://github.com/ant-design/ant-design/issues/19809
&-item > .@{ant-prefix}-badge > a { &-item > .@{ant-prefix}-badge a {
color: @menu-item-color; color: @menu-item-color;
&:hover { &:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
@ -110,8 +132,8 @@
&-item-selected { &-item-selected {
color: @menu-highlight-color; color: @menu-highlight-color;
> a, a,
> a:hover { a:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
} }
} }
@ -125,6 +147,7 @@
&-vertical-left { &-vertical-left {
border-right: @border-width-base @border-style-base @border-color-split; border-right: @border-width-base @border-style-base @border-color-split;
} }
&-vertical-right { &-vertical-right {
border-left: @border-width-base @border-style-base @border-color-split; border-left: @border-width-base @border-style-base @border-color-split;
} }
@ -133,9 +156,17 @@
&-vertical-left&-sub, &-vertical-left&-sub,
&-vertical-right&-sub { &-vertical-right&-sub {
min-width: 160px; min-width: 160px;
max-height: calc(100vh - 100px);
padding: 0; padding: 0;
overflow: hidden;
border-right: 0; border-right: 0;
transform-origin: 0 0;
// https://github.com/ant-design/ant-design/issues/22244
// https://github.com/ant-design/ant-design/issues/26812
&:not([class*='-active']) {
overflow-x: hidden;
overflow-y: auto;
}
.@{menu-prefix-cls}-item { .@{menu-prefix-cls}-item {
left: 0; left: 0;
@ -155,26 +186,48 @@
min-width: 114px; // in case of submenu width is too big: https://codesandbox.io/s/qvpwm6mk66 min-width: 114px; // in case of submenu width is too big: https://codesandbox.io/s/qvpwm6mk66
} }
&-horizontal &-item,
&-horizontal &-submenu-title {
transition: border-color @animation-duration-slow, background @animation-duration-slow;
}
&-item, &-item,
&-submenu-title { &-submenu-title {
position: relative; position: relative;
display: block; display: block;
margin: 0; margin: 0;
padding: 0 20px; padding: @menu-item-padding;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: color 0.3s @ease-in-out, border-color 0.3s @ease-in-out, transition: border-color @animation-duration-slow, background @animation-duration-slow,
background 0.3s @ease-in-out, padding 0.15s @ease-in-out; padding @animation-duration-slow @ease-in-out;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
min-width: 14px; min-width: 14px;
margin-right: 10px;
font-size: @menu-icon-size; font-size: @menu-icon-size;
transition: font-size 0.15s @ease-out, margin 0.3s @ease-in-out; transition: font-size @menu-animation-duration-normal @ease-out,
margin @animation-duration-slow @ease-in-out, color @animation-duration-slow;
+ span { + span {
margin-left: @menu-icon-margin-right;
opacity: 1; opacity: 1;
transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out; // transition: opacity @animation-duration-slow @ease-in-out,
// width @animation-duration-slow @ease-in-out, color @animation-duration-slow;
transition: opacity @animation-duration-slow @ease-in-out, margin @animation-duration-slow,
color @animation-duration-slow;
} }
} }
&.@{menu-prefix-cls}-item-only-child {
> .@{iconfont-css-prefix},
> .@{menu-prefix-cls}-item-icon {
margin-right: 0;
}
}
&:focus-visible {
.accessibility-focus();
}
} }
& > &-item-divider { & > &-item-divider {
@ -190,94 +243,105 @@
&-popup { &-popup {
position: absolute; position: absolute;
z-index: @zindex-dropdown; z-index: @zindex-dropdown;
// background: @menu-popup-bg; background: transparent;
border-radius: @border-radius-base; border-radius: @border-radius-base;
box-shadow: none;
transform-origin: 0 0;
.submenu-title-wrapper { // https://github.com/ant-design/ant-design/issues/13955
padding-right: 20px;
}
&::before { &::before {
position: absolute; position: absolute;
top: -7px; top: -7px;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: -1;
width: 100%;
height: 100%;
opacity: 0.0001; opacity: 0.0001;
content: ' '; content: ' ';
} }
} }
// https://github.com/ant-design/ant-design/issues/13955
&-placement-rightTop::before {
top: 0;
left: -7px;
}
> .@{menu-prefix-cls} { > .@{menu-prefix-cls} {
background-color: @menu-bg; background-color: @menu-bg;
border-radius: @border-radius-base; border-radius: @border-radius-base;
&-submenu-title::after { &-submenu-title::after {
transition: transform 0.3s @ease-in-out; transition: transform @animation-duration-slow @ease-in-out;
} }
} }
&-vertical, &-popup > .@{menu-prefix-cls} {
&-vertical-left, background-color: @menu-popup-bg;
&-vertical-right, }
&-inline {
> .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow { &-expand-icon,
&-arrow {
position: absolute;
top: 50%;
right: 16px;
width: 10px;
color: @menu-item-color;
transform: translateY(-50%);
transition: transform @animation-duration-slow @ease-in-out;
}
&-arrow {
// →
&::before,
&::after {
position: absolute; position: absolute;
top: 50%; width: 6px;
right: 16px; height: 1.5px;
width: 10px; background-color: currentColor;
transition: transform 0.3s @ease-in-out; border-radius: 2px;
&::before, transition: background @animation-duration-slow @ease-in-out,
&::after { transform @animation-duration-slow @ease-in-out, top @animation-duration-slow @ease-in-out,
position: absolute; color @animation-duration-slow @ease-in-out;
width: 6px; content: '';
height: 1.5px;
// background + background-image to makes before & after cross have same color.
// Since `linear-gradient` not work on IE9, we should hack it.
// ref: https://github.com/ant-design/ant-design/issues/15910
background: @menu-bg;
background: ~'@{menu-item-color} \9';
background-image: linear-gradient(to right, @menu-item-color, @menu-item-color);
background-image: ~'none \9';
border-radius: 2px;
transition: background 0.3s @ease-in-out, transform 0.3s @ease-in-out,
top 0.3s @ease-in-out;
content: '';
}
&::before {
transform: rotate(45deg) translateY(-2px);
}
&::after {
transform: rotate(-45deg) translateY(2px);
}
} }
> .@{menu-prefix-cls}-submenu-title:hover .@{menu-prefix-cls}-submenu-arrow {
&::after,
&::before {
background: linear-gradient(to right, @menu-highlight-color, @menu-highlight-color);
}
}
}
&-inline > .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
&::before { &::before {
transform: rotate(-45deg) translateX(2px); transform: rotate(45deg) translateY(-2.5px);
} }
&::after { &::after {
transform: rotate(45deg) translateX(-2px); transform: rotate(-45deg) translateY(2.5px);
} }
} }
&-open { &:hover > &-title > &-expand-icon,
&.@{menu-prefix-cls}-submenu-inline &:hover > &-title > &-arrow {
> .@{menu-prefix-cls}-submenu-title color: @menu-highlight-color;
.@{menu-prefix-cls}-submenu-arrow { }
transform: translateY(-2px);
&::after { .@{menu-prefix-cls}-inline-collapsed &-arrow,
transform: rotate(-45deg) translateX(-2px); &-inline &-arrow {
} // ↓
&::before { &::before {
transform: rotate(45deg) translateX(2px); transform: rotate(-45deg) translateX(2.5px);
} }
&::after {
transform: rotate(45deg) translateX(-2.5px);
}
}
&-horizontal &-arrow {
display: none;
}
&-open&-inline > &-title > &-arrow {
// ↑
transform: translateY(-2px);
&::after {
transform: rotate(-45deg) translateX(-2.5px);
}
&::before {
transform: rotate(45deg) translateX(2.5px);
} }
} }
} }
@ -286,18 +350,34 @@
&-vertical-left &-submenu-selected, &-vertical-left &-submenu-selected,
&-vertical-right &-submenu-selected { &-vertical-right &-submenu-selected {
color: @menu-highlight-color; color: @menu-highlight-color;
> a {
color: @menu-highlight-color;
}
} }
&-horizontal { &-horizontal {
line-height: 46px; line-height: @menu-horizontal-line-height;
white-space: nowrap;
border: 0; border: 0;
border-bottom: @border-width-base @border-style-base @border-color-split; border-bottom: @border-width-base @border-style-base @border-color-split;
box-shadow: none; box-shadow: none;
&:not(.@{menu-prefix-cls}-dark) {
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
margin: @menu-item-padding;
margin-top: -1px;
margin-bottom: 0;
padding: @menu-item-padding;
padding-right: 0;
padding-left: 0;
&:hover,
&-active,
&-open,
&-selected {
color: @menu-highlight-color;
border-bottom: 2px solid @menu-highlight-color;
}
}
}
> .@{menu-prefix-cls}-item, > .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu { > .@{menu-prefix-cls}-submenu {
position: relative; position: relative;
@ -305,19 +385,14 @@
display: inline-block; display: inline-block;
vertical-align: bottom; vertical-align: bottom;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
}
&:hover, > .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
&-active, padding: 0;
&-open,
&-selected {
color: @menu-highlight-color;
border-bottom: 2px solid @menu-highlight-color;
}
} }
> .@{menu-prefix-cls}-item { > .@{menu-prefix-cls}-item {
> a { a {
display: block;
color: @menu-item-color; color: @menu-item-color;
&:hover { &:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
@ -326,7 +401,7 @@
bottom: -2px; bottom: -2px;
} }
} }
&-selected > a { &-selected a {
color: @menu-highlight-color; color: @menu-highlight-color;
} }
} }
@ -353,7 +428,8 @@
border-right: @menu-item-active-border-width solid @menu-highlight-color; border-right: @menu-item-active-border-width solid @menu-highlight-color;
transform: scaleY(0.0001); transform: scaleY(0.0001);
opacity: 0; opacity: 0;
transition: transform 0.15s @ease-out, opacity 0.15s @ease-out; transition: transform @menu-animation-duration-normal @ease-out,
opacity @menu-animation-duration-normal @ease-out;
content: ''; content: '';
} }
} }
@ -365,7 +441,6 @@
margin-bottom: @menu-item-vertical-margin; margin-bottom: @menu-item-vertical-margin;
padding: 0 16px; padding: 0 16px;
overflow: hidden; overflow: hidden;
font-size: @menu-item-font-size;
line-height: @menu-item-height; line-height: @menu-item-height;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@ -386,6 +461,13 @@
} }
} }
&-vertical {
.@{menu-prefix-cls}-item-group-list .@{menu-prefix-cls}-submenu-title,
.@{menu-prefix-cls}-submenu-title {
padding-right: 34px;
}
}
&-inline { &-inline {
width: 100%; width: 100%;
.@{menu-prefix-cls}-selected, .@{menu-prefix-cls}-selected,
@ -393,7 +475,8 @@
&::after { &::after {
transform: scaleY(1); transform: scaleY(1);
opacity: 1; opacity: 1;
transition: transform 0.15s @ease-in-out, opacity 0.15s @ease-in-out; transition: transform @menu-animation-duration-normal @ease-in-out,
opacity @menu-animation-duration-normal @ease-in-out;
} }
} }
@ -402,13 +485,37 @@
width: ~'calc(100% + 1px)'; width: ~'calc(100% + 1px)';
} }
.@{menu-prefix-cls}-item-group-list .@{menu-prefix-cls}-submenu-title,
.@{menu-prefix-cls}-submenu-title { .@{menu-prefix-cls}-submenu-title {
padding-right: 34px; padding-right: 34px;
} }
// Motion enhance for first level
&.@{menu-prefix-cls}-root {
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
display: flex;
align-items: center;
transition: border-color @animation-duration-slow, background @animation-duration-slow,
padding 0.1s @ease-out;
> .@{menu-prefix-cls}-title-content {
flex: auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
> * {
flex: none;
}
}
}
} }
&-inline-collapsed { &&-inline-collapsed {
width: @menu-collapsed-width; width: @menu-collapsed-width;
> .@{menu-prefix-cls}-item, > .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-item-group > .@{menu-prefix-cls}-item-group
> .@{menu-prefix-cls}-item-group-list > .@{menu-prefix-cls}-item-group-list
@ -419,24 +526,34 @@
> .@{menu-prefix-cls}-submenu-title, > .@{menu-prefix-cls}-submenu-title,
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title { > .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
left: 0; left: 0;
padding: 0 ((@menu-collapsed-width - @menu-icon-size-lg) / 2) !important; padding: 0 ~'calc(50% - @{menu-icon-size-lg} / 2)';
text-overflow: clip; text-overflow: clip;
.@{menu-prefix-cls}-submenu-arrow { .@{menu-prefix-cls}-submenu-arrow {
display: none; opacity: 0;
} }
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
margin: 0; margin: 0;
font-size: @menu-icon-size-lg; font-size: @menu-icon-size-lg;
line-height: @menu-item-height; line-height: @menu-item-height;
+ span { + span {
display: inline-block; display: inline-block;
max-width: 0;
opacity: 0; opacity: 0;
} }
} }
} }
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
display: inline-block;
}
&-tooltip { &-tooltip {
pointer-events: none; pointer-events: none;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
display: none; display: none;
} }
@ -470,8 +587,19 @@
box-shadow: none; box-shadow: none;
} }
&-root&-inline-collapsed {
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu .@{menu-prefix-cls}-submenu-title {
> .@{menu-prefix-cls}-inline-collapsed-noicon {
font-size: @menu-icon-size-lg;
text-align: center;
}
}
}
&-sub&-inline { &-sub&-inline {
padding: 0; padding: 0;
background: @menu-inline-submenu-bg;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
@ -495,7 +623,7 @@
background: none; background: none;
border-color: transparent !important; border-color: transparent !important;
cursor: not-allowed; cursor: not-allowed;
> a { a {
color: @disabled-color !important; color: @disabled-color !important;
pointer-events: none; pointer-events: none;
} }
@ -512,4 +640,12 @@
} }
} }
// Integration with header element so menu items have the same height
.@{ant-prefix}-layout-header {
.@{menu-prefix-cls} {
line-height: inherit;
}
}
@import './dark'; @import './dark';
@import './rtl';

View File

@ -1,16 +0,0 @@
import { getPropsSlot } from '../../_util/props-util';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AMenuItemGroup',
setup(props, { slots }) {
return () => {
return (
<li>
{getPropsSlot(slots, props, 'title')}
<ul>{slots.default?.()}</ul>
</li>
);
};
},
});

View File

@ -1,10 +0,0 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AMenu',
setup(props, { slots }) {
return () => {
return <div>{slots.default?.()}</div>;
};
},
});

View File

@ -1,10 +0,0 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AMenuItem',
setup(props, { slots }) {
return () => {
return <li>{slots.default?.()}</li>;
};
},
});

View File

@ -0,0 +1,323 @@
import { defineComponent, inject, provide, toRef, App, ExtractPropTypes, Plugin } from 'vue';
import omit from 'omit.js';
import VcMenu, { Divider, ItemGroup } from '../vc-menu';
import SubMenu from './SubMenu';
import PropTypes from '../_util/vue-types';
import animation from '../_util/openAnimation';
import warning from '../_util/warning';
import Item from './MenuItem';
import { hasProp, getOptionProps } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import commonPropsType from '../vc-menu/commonPropsType';
import { defaultConfigProvider } from '../config-provider';
import { SiderContextProps } from '../layout/Sider';
import { tuple } from '../_util/type';
// import raf from '../_util/raf';
export const MenuMode = PropTypes.oneOf([
'vertical',
'vertical-left',
'vertical-right',
'horizontal',
'inline',
]);
export const menuProps = {
...commonPropsType,
theme: PropTypes.oneOf(tuple('light', 'dark')).def('light'),
mode: MenuMode.def('vertical'),
selectable: PropTypes.looseBool,
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
openKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
defaultOpenKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
openTransitionName: PropTypes.string,
prefixCls: PropTypes.string,
multiple: PropTypes.looseBool,
inlineIndent: PropTypes.number.def(24),
inlineCollapsed: PropTypes.looseBool,
isRootMenu: PropTypes.looseBool.def(true),
focusable: PropTypes.looseBool.def(false),
onOpenChange: PropTypes.func,
onSelect: PropTypes.func,
onDeselect: PropTypes.func,
onClick: PropTypes.func,
onMouseenter: PropTypes.func,
onSelectChange: PropTypes.func,
};
export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
const Menu = defineComponent({
name: 'AMenu',
mixins: [BaseMixin],
inheritAttrs: false,
props: menuProps,
Divider: { ...Divider, name: 'AMenuDivider' },
Item: { ...Item, name: 'AMenuItem' },
SubMenu: { ...SubMenu, name: 'ASubMenu' },
ItemGroup: { ...ItemGroup, name: 'AMenuItemGroup' },
emits: [
'update:selectedKeys',
'update:openKeys',
'mouseenter',
'openChange',
'click',
'selectChange',
'select',
'deselect',
],
setup() {
const layoutSiderContext = inject<SiderContextProps>('layoutSiderContext', {});
const layoutSiderCollapsed = toRef(layoutSiderContext, 'sCollapsed');
return {
configProvider: inject('configProvider', defaultConfigProvider),
layoutSiderContext,
layoutSiderCollapsed,
propsUpdating: false,
switchingModeFromInline: false,
leaveAnimationExecutedWhenInlineCollapsed: false,
inlineOpenKeys: [],
};
},
data() {
const props: MenuProps = getOptionProps(this);
warning(
!('inlineCollapsed' in props && props.mode !== 'inline'),
'Menu',
"`inlineCollapsed` should only be used when Menu's `mode` is inline.",
);
let sOpenKeys: (number | string)[];
if ('openKeys' in props) {
sOpenKeys = props.openKeys;
} else if ('defaultOpenKeys' in props) {
sOpenKeys = props.defaultOpenKeys;
}
return {
sOpenKeys,
};
},
// beforeUnmount() {
// raf.cancel(this.mountRafId);
// },
watch: {
mode(val, oldVal) {
if (oldVal === 'inline' && val !== 'inline') {
this.switchingModeFromInline = true;
}
},
openKeys(val) {
this.setState({ sOpenKeys: val });
},
inlineCollapsed(val) {
this.collapsedChange(val);
},
layoutSiderCollapsed(val) {
this.collapsedChange(val);
},
},
created() {
provide('getInlineCollapsed', this.getInlineCollapsed);
provide('menuPropsContext', this.$props);
},
updated() {
this.propsUpdating = false;
},
methods: {
collapsedChange(val: unknown) {
if (this.propsUpdating) {
return;
}
this.propsUpdating = true;
if (!hasProp(this, 'openKeys')) {
if (val) {
this.switchingModeFromInline = true;
this.inlineOpenKeys = this.sOpenKeys;
this.setState({ sOpenKeys: [] });
} else {
this.setState({ sOpenKeys: this.inlineOpenKeys });
this.inlineOpenKeys = [];
}
} else if (val) {
// openKeysreactopenKeysvue便openKeys
this.switchingModeFromInline = true;
}
},
restoreModeVerticalFromInline() {
if (this.switchingModeFromInline) {
this.switchingModeFromInline = false;
this.$forceUpdate();
}
},
// Restore vertical mode when menu is collapsed responsively when mounted
// https://github.com/ant-design/ant-design/issues/13104
// TODO: not a perfect solution, looking a new way to avoid setting switchingModeFromInline in this situation
handleMouseEnter(e: Event) {
this.restoreModeVerticalFromInline();
this.$emit('mouseenter', e);
},
handleTransitionEnd(e: TransitionEvent) {
// when inlineCollapsed menu width animation finished
// https://github.com/ant-design/ant-design/issues/12864
const widthCollapsed = e.propertyName === 'width' && e.target === e.currentTarget;
// Fix SVGElement e.target.className.indexOf is not a function
// https://github.com/ant-design/ant-design/issues/15699
const { className } = e.target as SVGAnimationElement | HTMLElement;
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during an animation.
const classNameValue =
Object.prototype.toString.call(className) === '[object SVGAnimatedString]'
? className.animVal
: className;
// Fix for <Menu style={{ width: '100%' }} />, the width transition won't trigger when menu is collapsed
// https://github.com/ant-design/ant-design-pro/issues/2783
const iconScaled = e.propertyName === 'font-size' && classNameValue.indexOf('anticon') >= 0;
if (widthCollapsed || iconScaled) {
this.restoreModeVerticalFromInline();
}
},
handleClick(e: Event) {
this.handleOpenChange([]);
this.$emit('click', e);
},
handleSelect(info) {
this.$emit('update:selectedKeys', info.selectedKeys);
this.$emit('select', info);
this.$emit('selectChange', info.selectedKeys);
},
handleDeselect(info) {
this.$emit('update:selectedKeys', info.selectedKeys);
this.$emit('deselect', info);
this.$emit('selectChange', info.selectedKeys);
},
handleOpenChange(openKeys: (number | string)[]) {
this.setOpenKeys(openKeys);
this.$emit('update:openKeys', openKeys);
this.$emit('openChange', openKeys);
},
setOpenKeys(openKeys: (number | string)[]) {
if (!hasProp(this, 'openKeys')) {
this.setState({ sOpenKeys: openKeys });
}
},
getRealMenuMode() {
const inlineCollapsed = this.getInlineCollapsed();
if (this.switchingModeFromInline && inlineCollapsed) {
return 'inline';
}
const { mode } = this.$props;
return inlineCollapsed ? 'vertical' : mode;
},
getInlineCollapsed() {
const { inlineCollapsed } = this.$props;
if (this.layoutSiderContext.sCollapsed !== undefined) {
return this.layoutSiderContext.sCollapsed;
}
return inlineCollapsed;
},
getMenuOpenAnimation(menuMode: string) {
const { openAnimation, openTransitionName } = this.$props;
let menuOpenAnimation = openAnimation || openTransitionName;
if (openAnimation === undefined && openTransitionName === undefined) {
if (menuMode === 'horizontal') {
menuOpenAnimation = 'slide-up';
} else if (menuMode === 'inline') {
menuOpenAnimation = animation;
} else {
// When mode switch from inline
// submenu should hide without animation
if (this.switchingModeFromInline) {
menuOpenAnimation = '';
this.switchingModeFromInline = false;
} else {
menuOpenAnimation = 'zoom-big';
}
}
}
return menuOpenAnimation;
},
},
render() {
const { layoutSiderContext } = this;
const { collapsedWidth } = layoutSiderContext;
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, theme, getPopupContainer } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('menu', customizePrefixCls);
const menuMode = this.getRealMenuMode();
const menuOpenAnimation = this.getMenuOpenAnimation(menuMode);
const { class: className, ...otherAttrs } = this.$attrs;
const menuClassName = {
[className as string]: className,
[`${prefixCls}-${theme}`]: true,
[`${prefixCls}-inline-collapsed`]: this.getInlineCollapsed(),
};
const menuProps = {
...omit(props, [
'inlineCollapsed',
'onUpdate:selectedKeys',
'onUpdate:openKeys',
'onSelectChange',
]),
getPopupContainer: getPopupContainer || getContextPopupContainer,
openKeys: this.sOpenKeys,
mode: menuMode,
prefixCls,
...otherAttrs,
onSelect: this.handleSelect,
onDeselect: this.handleDeselect,
onOpenChange: this.handleOpenChange,
onMouseenter: this.handleMouseEnter,
onTransitionend: this.handleTransitionEnd,
// children: getSlot(this),
};
if (!hasProp(this, 'selectedKeys')) {
delete menuProps.selectedKeys;
}
if (menuMode !== 'inline') {
// closing vertical popup submenu after click it
menuProps.onClick = this.handleClick;
menuProps.openTransitionName = menuOpenAnimation;
} else {
menuProps.onClick = (e: Event) => {
this.$emit('click', e);
};
menuProps.openAnimation = menuOpenAnimation;
}
// https://github.com/ant-design/ant-design/issues/8587
const hideMenu =
this.getInlineCollapsed() &&
(collapsedWidth === 0 || collapsedWidth === '0' || collapsedWidth === '0px');
if (hideMenu) {
menuProps.openKeys = [];
}
return <VcMenu {...menuProps} class={menuClassName} v-slots={this.$slots} />;
},
});
/* istanbul ignore next */
Menu.install = function(app: App) {
app.component(Menu.name, Menu);
app.component(Menu.Item.name, Menu.Item);
app.component(Menu.SubMenu.name, Menu.SubMenu);
app.component(Menu.Divider.name, Menu.Divider);
app.component(Menu.ItemGroup.name, Menu.ItemGroup);
return app;
};
export default Menu as typeof Menu &
Plugin & {
readonly Item: typeof Item;
readonly SubMenu: typeof SubMenu;
readonly Divider: typeof Divider;
readonly ItemGroup: typeof ItemGroup;
};

View File

@ -1,8 +1,7 @@
.@{menu-prefix-cls} { .@{menu-prefix-cls} {
// dark theme // dark theme
&&-dark, &-dark,
&-dark &-sub, &-dark &-sub {
&&-dark &-sub {
color: @menu-dark-color; color: @menu-dark-color;
background: @menu-dark-bg; background: @menu-dark-bg;
.@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow { .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
@ -20,7 +19,8 @@
} }
&-dark &-inline&-sub { &-dark &-inline&-sub {
background: @menu-dark-inline-submenu-bg; background: @menu-dark-submenu-bg;
box-shadow: 0 2px 8px fade(@black, 45%) inset;
} }
&-dark&-horizontal { &-dark&-horizontal {
@ -31,23 +31,17 @@
&-dark&-horizontal > &-submenu { &-dark&-horizontal > &-submenu {
top: 0; top: 0;
margin-top: 0; margin-top: 0;
padding: @menu-item-padding;
border-color: @menu-dark-bg; border-color: @menu-dark-bg;
border-bottom: 0; border-bottom: 0;
} }
&-dark&-horizontal > &-item:hover {
background-color: @menu-dark-item-active-bg;
}
&-dark&-horizontal > &-item > a::before { &-dark&-horizontal > &-item > a::before {
bottom: 0; bottom: 0;
} }
&-dark &-item, &-dark &-item,
&-dark &-item-group-title, &-dark &-item-group-title,
&-dark &-item > a, &-dark &-item > a {
&-dark &-item > span > a {
color: @menu-dark-color; color: @menu-dark-color;
} }
@ -83,8 +77,7 @@
&-dark &-submenu-title:hover { &-dark &-submenu-title:hover {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
background-color: transparent; background-color: transparent;
> a, > a {
> span > a {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
} }
> .@{menu-prefix-cls}-submenu-title, > .@{menu-prefix-cls}-submenu-title,
@ -102,10 +95,6 @@
background-color: @menu-dark-item-hover-bg; background-color: @menu-dark-item-hover-bg;
} }
&-dark&-dark:not(&-horizontal) &-item-selected {
background-color: @menu-dark-item-active-bg;
}
&-dark &-item-selected { &-dark &-item-selected {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
border-right: 0; border-right: 0;
@ -113,19 +102,14 @@
border-right: 0; border-right: 0;
} }
> a, > a,
> span > a, > a:hover {
> a:hover,
> span > a:hover {
color: @menu-dark-highlight-color; color: @menu-dark-highlight-color;
} }
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
color: @menu-dark-selected-item-icon-color; color: @menu-dark-selected-item-icon-color;
}
+ span { .@{iconfont-css-prefix} + span {
color: @menu-dark-selected-item-text-color; color: @menu-dark-selected-item-text-color;
}
} }
} }
@ -138,8 +122,7 @@
&-dark &-item-disabled, &-dark &-item-disabled,
&-dark &-submenu-disabled { &-dark &-submenu-disabled {
&, &,
> a, > a {
> span > a {
color: @disabled-color-dark !important; color: @disabled-color-dark !important;
opacity: 0.8; opacity: 0.8;
} }

View File

@ -1,15 +1,7 @@
@import '../../style/themes/index'; @import '../../style/themes/index';
@import '../../style/mixins/index'; @import '../../style/mixins/index';
@import './status';
@menu-prefix-cls: ~'@{ant-prefix}-menu'; @menu-prefix-cls: ~'@{ant-prefix}-menu';
@menu-animation-duration-normal: 0.15s;
.accessibility-focus() {
box-shadow: 0 0 0 2px fade(@primary-color, 20%);
}
// TODO: Should remove icon style compatible in v5
// default theme // default theme
.@{menu-prefix-cls} { .@{menu-prefix-cls} {
@ -18,21 +10,14 @@
margin-bottom: 0; margin-bottom: 0;
padding-left: 0; // Override default ul/ol padding-left: 0; // Override default ul/ol
color: @menu-item-color; color: @menu-item-color;
font-size: @menu-item-font-size;
line-height: 0; // Fix display inline-block gap line-height: 0; // Fix display inline-block gap
text-align: left;
list-style: none; list-style: none;
background: @menu-bg; background: @menu-bg;
outline: none; outline: none;
box-shadow: @box-shadow-base; box-shadow: @box-shadow-base;
transition: background @animation-duration-slow, transition: background 0.3s, width 0.3s cubic-bezier(0.2, 0, 0, 1) 0s;
width @animation-duration-slow cubic-bezier(0.2, 0, 0, 1) 0s;
.clearfix(); .clearfix();
&&-root:focus-visible {
.accessibility-focus();
}
ul, ul,
ol { ol {
margin: 0; margin: 0;
@ -40,29 +25,22 @@
list-style: none; list-style: none;
} }
&-hidden, &-hidden {
&-submenu-hidden {
display: none; display: none;
} }
&-item-group-title { &-item-group-title {
height: @menu-item-group-height;
padding: 8px 16px; padding: 8px 16px;
color: @menu-item-group-title-color; color: @menu-item-group-title-color;
font-size: @menu-item-group-title-font-size; font-size: @font-size-base;
line-height: @menu-item-group-height; line-height: @line-height-base;
transition: all @animation-duration-slow; transition: all 0.3s;
} }
&-horizontal &-submenu {
transition: border-color @animation-duration-slow @ease-in-out,
background @animation-duration-slow @ease-in-out;
}
&-submenu, &-submenu,
&-submenu-inline { &-submenu-inline {
transition: border-color @animation-duration-slow @ease-in-out, transition: border-color 0.3s @ease-in-out, background 0.3s @ease-in-out,
background @animation-duration-slow @ease-in-out, padding 0.15s @ease-in-out;
padding @menu-animation-duration-normal @ease-in-out;
} }
&-submenu-selected { &-submenu-selected {
@ -76,11 +54,11 @@
&-submenu &-sub { &-submenu &-sub {
cursor: initial; cursor: initial;
transition: background @animation-duration-slow @ease-in-out, transition: background 0.3s @ease-in-out, padding 0.3s @ease-in-out;
padding @animation-duration-slow @ease-in-out;
} }
&-item a { &-item > a {
display: block;
color: @menu-item-color; color: @menu-item-color;
&:hover { &:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
@ -97,7 +75,7 @@
} }
// https://github.com/ant-design/ant-design/issues/19809 // https://github.com/ant-design/ant-design/issues/19809
&-item > .@{ant-prefix}-badge a { &-item > .@{ant-prefix}-badge > a {
color: @menu-item-color; color: @menu-item-color;
&:hover { &:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
@ -132,8 +110,8 @@
&-item-selected { &-item-selected {
color: @menu-highlight-color; color: @menu-highlight-color;
a, > a,
a:hover { > a:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
} }
} }
@ -147,7 +125,6 @@
&-vertical-left { &-vertical-left {
border-right: @border-width-base @border-style-base @border-color-split; border-right: @border-width-base @border-style-base @border-color-split;
} }
&-vertical-right { &-vertical-right {
border-left: @border-width-base @border-style-base @border-color-split; border-left: @border-width-base @border-style-base @border-color-split;
} }
@ -156,17 +133,9 @@
&-vertical-left&-sub, &-vertical-left&-sub,
&-vertical-right&-sub { &-vertical-right&-sub {
min-width: 160px; min-width: 160px;
max-height: calc(100vh - 100px);
padding: 0; padding: 0;
overflow: hidden;
border-right: 0; border-right: 0;
transform-origin: 0 0;
// https://github.com/ant-design/ant-design/issues/22244
// https://github.com/ant-design/ant-design/issues/26812
&:not([class*='-active']) {
overflow-x: hidden;
overflow-y: auto;
}
.@{menu-prefix-cls}-item { .@{menu-prefix-cls}-item {
left: 0; left: 0;
@ -186,48 +155,26 @@
min-width: 114px; // in case of submenu width is too big: https://codesandbox.io/s/qvpwm6mk66 min-width: 114px; // in case of submenu width is too big: https://codesandbox.io/s/qvpwm6mk66
} }
&-horizontal &-item,
&-horizontal &-submenu-title {
transition: border-color @animation-duration-slow, background @animation-duration-slow;
}
&-item, &-item,
&-submenu-title { &-submenu-title {
position: relative; position: relative;
display: block; display: block;
margin: 0; margin: 0;
padding: @menu-item-padding; padding: 0 20px;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: border-color @animation-duration-slow, background @animation-duration-slow, transition: color 0.3s @ease-in-out, border-color 0.3s @ease-in-out,
padding @animation-duration-slow @ease-in-out; background 0.3s @ease-in-out, padding 0.15s @ease-in-out;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
min-width: 14px; min-width: 14px;
margin-right: 10px;
font-size: @menu-icon-size; font-size: @menu-icon-size;
transition: font-size @menu-animation-duration-normal @ease-out, transition: font-size 0.15s @ease-out, margin 0.3s @ease-in-out;
margin @animation-duration-slow @ease-in-out, color @animation-duration-slow;
+ span { + span {
margin-left: @menu-icon-margin-right;
opacity: 1; opacity: 1;
// transition: opacity @animation-duration-slow @ease-in-out, transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
// width @animation-duration-slow @ease-in-out, color @animation-duration-slow;
transition: opacity @animation-duration-slow @ease-in-out, margin @animation-duration-slow,
color @animation-duration-slow;
} }
} }
&.@{menu-prefix-cls}-item-only-child {
> .@{iconfont-css-prefix},
> .@{menu-prefix-cls}-item-icon {
margin-right: 0;
}
}
&:focus-visible {
.accessibility-focus();
}
} }
& > &-item-divider { & > &-item-divider {
@ -243,105 +190,94 @@
&-popup { &-popup {
position: absolute; position: absolute;
z-index: @zindex-dropdown; z-index: @zindex-dropdown;
background: transparent; // background: @menu-popup-bg;
border-radius: @border-radius-base; border-radius: @border-radius-base;
box-shadow: none;
transform-origin: 0 0;
// https://github.com/ant-design/ant-design/issues/13955 .submenu-title-wrapper {
padding-right: 20px;
}
&::before { &::before {
position: absolute; position: absolute;
top: -7px; top: -7px;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: -1;
width: 100%;
height: 100%;
opacity: 0.0001; opacity: 0.0001;
content: ' '; content: ' ';
} }
} }
// https://github.com/ant-design/ant-design/issues/13955
&-placement-rightTop::before {
top: 0;
left: -7px;
}
> .@{menu-prefix-cls} { > .@{menu-prefix-cls} {
background-color: @menu-bg; background-color: @menu-bg;
border-radius: @border-radius-base; border-radius: @border-radius-base;
&-submenu-title::after { &-submenu-title::after {
transition: transform @animation-duration-slow @ease-in-out; transition: transform 0.3s @ease-in-out;
} }
} }
&-popup > .@{menu-prefix-cls} { &-vertical,
background-color: @menu-popup-bg; &-vertical-left,
} &-vertical-right,
&-inline {
&-expand-icon, > .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
&-arrow {
position: absolute;
top: 50%;
right: 16px;
width: 10px;
color: @menu-item-color;
transform: translateY(-50%);
transition: transform @animation-duration-slow @ease-in-out;
}
&-arrow {
// →
&::before,
&::after {
position: absolute; position: absolute;
width: 6px; top: 50%;
height: 1.5px; right: 16px;
background-color: currentColor; width: 10px;
border-radius: 2px; transition: transform 0.3s @ease-in-out;
transition: background @animation-duration-slow @ease-in-out, &::before,
transform @animation-duration-slow @ease-in-out, top @animation-duration-slow @ease-in-out, &::after {
color @animation-duration-slow @ease-in-out; position: absolute;
content: ''; width: 6px;
height: 1.5px;
// background + background-image to makes before & after cross have same color.
// Since `linear-gradient` not work on IE9, we should hack it.
// ref: https://github.com/ant-design/ant-design/issues/15910
background: @menu-bg;
background: ~'@{menu-item-color} \9';
background-image: linear-gradient(to right, @menu-item-color, @menu-item-color);
background-image: ~'none \9';
border-radius: 2px;
transition: background 0.3s @ease-in-out, transform 0.3s @ease-in-out,
top 0.3s @ease-in-out;
content: '';
}
&::before {
transform: rotate(45deg) translateY(-2px);
}
&::after {
transform: rotate(-45deg) translateY(2px);
}
} }
> .@{menu-prefix-cls}-submenu-title:hover .@{menu-prefix-cls}-submenu-arrow {
&::after,
&::before {
background: linear-gradient(to right, @menu-highlight-color, @menu-highlight-color);
}
}
}
&-inline > .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
&::before { &::before {
transform: rotate(45deg) translateY(-2.5px); transform: rotate(-45deg) translateX(2px);
} }
&::after { &::after {
transform: rotate(-45deg) translateY(2.5px); transform: rotate(45deg) translateX(-2px);
} }
} }
&:hover > &-title > &-expand-icon, &-open {
&:hover > &-title > &-arrow { &.@{menu-prefix-cls}-submenu-inline
color: @menu-highlight-color; > .@{menu-prefix-cls}-submenu-title
} .@{menu-prefix-cls}-submenu-arrow {
transform: translateY(-2px);
.@{menu-prefix-cls}-inline-collapsed &-arrow, &::after {
&-inline &-arrow { transform: rotate(-45deg) translateX(-2px);
// ↓ }
&::before { &::before {
transform: rotate(-45deg) translateX(2.5px); transform: rotate(45deg) translateX(2px);
} }
&::after {
transform: rotate(45deg) translateX(-2.5px);
}
}
&-horizontal &-arrow {
display: none;
}
&-open&-inline > &-title > &-arrow {
// ↑
transform: translateY(-2px);
&::after {
transform: rotate(-45deg) translateX(-2.5px);
}
&::before {
transform: rotate(45deg) translateX(2.5px);
} }
} }
} }
@ -350,34 +286,18 @@
&-vertical-left &-submenu-selected, &-vertical-left &-submenu-selected,
&-vertical-right &-submenu-selected { &-vertical-right &-submenu-selected {
color: @menu-highlight-color; color: @menu-highlight-color;
> a {
color: @menu-highlight-color;
}
} }
&-horizontal { &-horizontal {
line-height: @menu-horizontal-line-height; line-height: 46px;
white-space: nowrap;
border: 0; border: 0;
border-bottom: @border-width-base @border-style-base @border-color-split; border-bottom: @border-width-base @border-style-base @border-color-split;
box-shadow: none; box-shadow: none;
&:not(.@{menu-prefix-cls}-dark) {
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
margin: @menu-item-padding;
margin-top: -1px;
margin-bottom: 0;
padding: @menu-item-padding;
padding-right: 0;
padding-left: 0;
&:hover,
&-active,
&-open,
&-selected {
color: @menu-highlight-color;
border-bottom: 2px solid @menu-highlight-color;
}
}
}
> .@{menu-prefix-cls}-item, > .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu { > .@{menu-prefix-cls}-submenu {
position: relative; position: relative;
@ -385,14 +305,19 @@
display: inline-block; display: inline-block;
vertical-align: bottom; vertical-align: bottom;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
}
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title { &:hover,
padding: 0; &-active,
&-open,
&-selected {
color: @menu-highlight-color;
border-bottom: 2px solid @menu-highlight-color;
}
} }
> .@{menu-prefix-cls}-item { > .@{menu-prefix-cls}-item {
a { > a {
display: block;
color: @menu-item-color; color: @menu-item-color;
&:hover { &:hover {
color: @menu-highlight-color; color: @menu-highlight-color;
@ -401,7 +326,7 @@
bottom: -2px; bottom: -2px;
} }
} }
&-selected a { &-selected > a {
color: @menu-highlight-color; color: @menu-highlight-color;
} }
} }
@ -428,8 +353,7 @@
border-right: @menu-item-active-border-width solid @menu-highlight-color; border-right: @menu-item-active-border-width solid @menu-highlight-color;
transform: scaleY(0.0001); transform: scaleY(0.0001);
opacity: 0; opacity: 0;
transition: transform @menu-animation-duration-normal @ease-out, transition: transform 0.15s @ease-out, opacity 0.15s @ease-out;
opacity @menu-animation-duration-normal @ease-out;
content: ''; content: '';
} }
} }
@ -441,6 +365,7 @@
margin-bottom: @menu-item-vertical-margin; margin-bottom: @menu-item-vertical-margin;
padding: 0 16px; padding: 0 16px;
overflow: hidden; overflow: hidden;
font-size: @menu-item-font-size;
line-height: @menu-item-height; line-height: @menu-item-height;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@ -461,13 +386,6 @@
} }
} }
&-vertical {
.@{menu-prefix-cls}-item-group-list .@{menu-prefix-cls}-submenu-title,
.@{menu-prefix-cls}-submenu-title {
padding-right: 34px;
}
}
&-inline { &-inline {
width: 100%; width: 100%;
.@{menu-prefix-cls}-selected, .@{menu-prefix-cls}-selected,
@ -475,8 +393,7 @@
&::after { &::after {
transform: scaleY(1); transform: scaleY(1);
opacity: 1; opacity: 1;
transition: transform @menu-animation-duration-normal @ease-in-out, transition: transform 0.15s @ease-in-out, opacity 0.15s @ease-in-out;
opacity @menu-animation-duration-normal @ease-in-out;
} }
} }
@ -485,37 +402,13 @@
width: ~'calc(100% + 1px)'; width: ~'calc(100% + 1px)';
} }
.@{menu-prefix-cls}-item-group-list .@{menu-prefix-cls}-submenu-title,
.@{menu-prefix-cls}-submenu-title { .@{menu-prefix-cls}-submenu-title {
padding-right: 34px; padding-right: 34px;
} }
// Motion enhance for first level
&.@{menu-prefix-cls}-root {
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
display: flex;
align-items: center;
transition: border-color @animation-duration-slow, background @animation-duration-slow,
padding 0.1s @ease-out;
> .@{menu-prefix-cls}-title-content {
flex: auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
> * {
flex: none;
}
}
}
} }
&&-inline-collapsed { &-inline-collapsed {
width: @menu-collapsed-width; width: @menu-collapsed-width;
> .@{menu-prefix-cls}-item, > .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-item-group > .@{menu-prefix-cls}-item-group
> .@{menu-prefix-cls}-item-group-list > .@{menu-prefix-cls}-item-group-list
@ -526,34 +419,24 @@
> .@{menu-prefix-cls}-submenu-title, > .@{menu-prefix-cls}-submenu-title,
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title { > .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
left: 0; left: 0;
padding: 0 ~'calc(50% - @{menu-icon-size-lg} / 2)'; padding: 0 ((@menu-collapsed-width - @menu-icon-size-lg) / 2) !important;
text-overflow: clip; text-overflow: clip;
.@{menu-prefix-cls}-submenu-arrow { .@{menu-prefix-cls}-submenu-arrow {
opacity: 0; display: none;
} }
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
margin: 0; margin: 0;
font-size: @menu-icon-size-lg; font-size: @menu-icon-size-lg;
line-height: @menu-item-height; line-height: @menu-item-height;
+ span { + span {
display: inline-block; display: inline-block;
max-width: 0;
opacity: 0; opacity: 0;
} }
} }
} }
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
display: inline-block;
}
&-tooltip { &-tooltip {
pointer-events: none; pointer-events: none;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
display: none; display: none;
} }
@ -587,19 +470,8 @@
box-shadow: none; box-shadow: none;
} }
&-root&-inline-collapsed {
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu .@{menu-prefix-cls}-submenu-title {
> .@{menu-prefix-cls}-inline-collapsed-noicon {
font-size: @menu-icon-size-lg;
text-align: center;
}
}
}
&-sub&-inline { &-sub&-inline {
padding: 0; padding: 0;
background: @menu-inline-submenu-bg;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
@ -623,7 +495,7 @@
background: none; background: none;
border-color: transparent !important; border-color: transparent !important;
cursor: not-allowed; cursor: not-allowed;
a { > a {
color: @disabled-color !important; color: @disabled-color !important;
pointer-events: none; pointer-events: none;
} }
@ -640,12 +512,4 @@
} }
} }
// Integration with header element so menu items have the same height
.@{ant-prefix}-layout-header {
.@{menu-prefix-cls} {
line-height: inherit;
}
}
@import './dark'; @import './dark';
@import './rtl';

View File

@ -490,28 +490,37 @@
// --- // ---
@menu-inline-toplevel-item-height: 40px; @menu-inline-toplevel-item-height: 40px;
@menu-item-height: 40px; @menu-item-height: 40px;
@menu-item-group-height: @line-height-base;
@menu-collapsed-width: 80px; @menu-collapsed-width: 80px;
@menu-bg: @component-background; @menu-bg: @component-background;
@menu-popup-bg: @component-background; @menu-popup-bg: @component-background;
@menu-item-color: @text-color; @menu-item-color: @text-color;
@menu-inline-submenu-bg: @background-color-light;
@menu-highlight-color: @primary-color; @menu-highlight-color: @primary-color;
@menu-item-active-bg: @item-active-bg; @menu-highlight-danger-color: @error-color;
@menu-item-active-bg: @primary-1;
@menu-item-active-danger-bg: @red-1;
@menu-item-active-border-width: 3px; @menu-item-active-border-width: 3px;
@menu-item-group-title-color: @text-color-secondary; @menu-item-group-title-color: @text-color-secondary;
@menu-icon-size: @font-size-base;
@menu-icon-size-lg: @font-size-lg;
@menu-item-vertical-margin: 4px; @menu-item-vertical-margin: 4px;
@menu-item-font-size: @font-size-base; @menu-item-font-size: @font-size-base;
@menu-item-boundary-margin: 8px; @menu-item-boundary-margin: 8px;
@menu-item-padding: 0 20px;
@menu-horizontal-line-height: 46px;
@menu-icon-margin-right: 10px;
@menu-icon-size: @menu-item-font-size;
@menu-icon-size-lg: @font-size-lg;
@menu-item-group-title-font-size: @menu-item-font-size;
// dark theme // dark theme
@menu-dark-color: @text-color-secondary-dark; @menu-dark-color: @text-color-secondary-dark;
@menu-dark-danger-color: @error-color;
@menu-dark-bg: @layout-header-background; @menu-dark-bg: @layout-header-background;
@menu-dark-arrow-color: #fff; @menu-dark-arrow-color: #fff;
@menu-dark-submenu-bg: #000c17; @menu-dark-inline-submenu-bg: #000c17;
@menu-dark-highlight-color: #fff; @menu-dark-highlight-color: #fff;
@menu-dark-item-active-bg: @primary-color; @menu-dark-item-active-bg: @primary-color;
@menu-dark-item-active-danger-bg: @error-color;
@menu-dark-selected-item-icon-color: @white; @menu-dark-selected-item-icon-color: @white;
@menu-dark-selected-item-text-color: @white; @menu-dark-selected-item-text-color: @white;
@menu-dark-item-hover-bg: transparent; @menu-dark-item-hover-bg: transparent;

View File

@ -1,39 +1,92 @@
<template> <template>
<div> <a-menu
<demo /> id="dddddd"
</div> v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="inline"
@click="handleClick"
>
<a-sub-menu key="sub1" @titleClick="titleClick">
<template #title>
<span>
<MailOutlined />
<span>Navigation One</span>
</span>
</template>
<a-menu-item-group key="g1">
<template #title>
<QqOutlined />
<span>Item 1</span>
</template>
<a-menu-item key="1">Option 1</a-menu-item>
<a-menu-item key="2">Option 2</a-menu-item>
</a-menu-item-group>
<a-menu-item-group key="g2" title="Item 2">
<a-menu-item key="3">Option 3</a-menu-item>
<a-menu-item key="4">Option 4</a-menu-item>
</a-menu-item-group>
</a-sub-menu>
<!-- <a-sub-menu key="sub2" @titleClick="titleClick">
<template #title>
<span>
<AppstoreOutlined />
<span>Navigation Two</span>
</span>
</template>
<a-menu-item key="5">Option 5</a-menu-item>
<a-menu-item key="6">Option 6</a-menu-item>
<a-sub-menu key="sub3" title="Submenu">
<a-menu-item key="7">Option 7</a-menu-item>
<a-menu-item key="8">Option 8</a-menu-item>
</a-sub-menu>
</a-sub-menu>
<a-sub-menu key="sub4">
<template #title>
<span>
<SettingOutlined />
<span>Navigation Three</span>
</span>
</template>
<a-menu-item key="9">Option 9</a-menu-item>
<a-menu-item key="10">Option 10</a-menu-item>
<a-menu-item key="11">Option 11</a-menu-item>
<a-menu-item key="12">Option 12</a-menu-item>
</a-sub-menu> -->
</a-menu>
</template> </template>
<script> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, ref, watch } from 'vue';
import demo from '../v2-doc/src/docs/tooltip/demo/index.vue'; import { MailOutlined, QqOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons-vue';
// import Affix from '../components/affix';
export default defineComponent({ export default defineComponent({
components: { components: {
demo, MailOutlined,
// Affix, QqOutlined,
AppstoreOutlined,
SettingOutlined,
}, },
data() { setup() {
return { const selectedKeys = ref<string[]>(['1']);
visible: false, const openKeys = ref<string[]>(['sub1']);
pStyle: { const handleClick = (e: Event) => {
fontSize: '16px', console.log('click', e);
color: 'rgba(0,0,0,0.85)', };
lineHeight: '24px', const titleClick = (e: Event) => {
display: 'block', console.log('titleClick', e);
marginBottom: '16px', };
}, watch(
pStyle2: { () => openKeys,
marginBottom: '24px', val => {
}, console.log('openKeys', val);
},
);
return {
selectedKeys,
openKeys,
handleClick,
titleClick,
}; };
},
methods: {
showDrawer() {
this.visible = true;
},
onClose() {
this.visible = false;
},
}, },
}); });
</script> </script>