refactor: menu

pull/6217/head
tangjinzhou 2023-01-28 19:11:32 +08:00
parent 221b203cbb
commit 94e981e00b
18 changed files with 1220 additions and 1126 deletions

View File

@ -41,6 +41,8 @@ import type { FocusEventHandler, MouseEventHandler } from '../../_util/EventInte
import collapseMotion from '../../_util/collapseMotion';
import type { ItemType } from './hooks/useItems';
import useItems from './hooks/useItems';
import useStyle from '../style';
import { useInjectOverride } from './OverrideContext';
export const menuProps = () => ({
id: String,
@ -95,7 +97,17 @@ export default defineComponent({
props: menuProps(),
slots: ['expandIcon', 'overflowedIndicator'],
setup(props, { slots, emit, attrs }) {
const { prefixCls, direction, getPrefixCls } = useConfigInject('menu', props);
const { direction, getPrefixCls } = useConfigInject('menu', props);
const override = useInjectOverride();
const prefixCls = computed(() => {
return getPrefixCls('menu', props.prefixCls || override?.prefixCls?.value);
});
const [wrapSSR, hashId] = useStyle(
prefixCls,
computed(() => {
return !override;
}),
);
const store = shallowRef<Map<string, StoreMenuInfo>>(new Map());
const siderCollapsed = inject(SiderCollapsedKey, ref(undefined));
const inlineCollapsed = computed(() => {
@ -265,6 +277,9 @@ export default defineComponent({
mergedMode.value = props.mode;
mergedInlineCollapsed.value = false;
}
if (override?.mode?.value) {
mergedMode.value = override.mode.value;
}
});
const isInlineMode = computed(() => mergedMode.value === 'inline');
@ -346,6 +361,7 @@ export default defineComponent({
const onInternalClick = (info: MenuInfo) => {
emit('click', info);
triggerSelection(info);
override?.onClick?.();
};
const onInternalOpenChange = (key: Key, open: boolean) => {
@ -406,7 +422,7 @@ export default defineComponent({
triggerSubMenuAction: computed(() => props.triggerSubMenuAction),
getPopupContainer: computed(() => props.getPopupContainer),
inlineCollapsed: mergedInlineCollapsed,
antdMenuTheme: computed(() => props.theme),
theme: computed(() => props.theme),
siderCollapsed,
defaultMotions: computed(() => (isMounted.value ? defaultMotions.value : null)),
motion: computed(() => (isMounted.value ? props.motion : null)),
@ -419,7 +435,7 @@ export default defineComponent({
isRootMenu: ref(true),
expandIcon,
forceSubMenuRender: computed(() => props.forceSubMenuRender),
rootClassName: computed(() => ''),
rootClassName: hashId,
});
return () => {
const childList = itemsNodes.value || flattenChildren(slots.default?.());
@ -442,14 +458,14 @@ export default defineComponent({
));
const overflowedIndicator = slots.overflowedIndicator?.() || <EllipsisOutlined />;
return (
return wrapSSR(
<Overflow
{...attrs}
onMousedown={props.onMousedown}
prefixCls={`${prefixCls.value}-overflow`}
component="ul"
itemComponent={MenuItem}
class={[className.value, attrs.class]}
class={[className.value, attrs.class, hashId.value]}
role="menu"
id={props.id}
data={wrappedChildList}
@ -499,7 +515,7 @@ export default defineComponent({
<PathContext>{wrappedChildList}</PathContext>
</div>
</Teleport>
</Overflow>
</Overflow>,
);
};
},

View File

@ -0,0 +1,27 @@
import type { ComputedRef, InjectionKey } from 'vue';
import { provide, computed, inject } from 'vue';
import type { MenuProps } from './menu';
// Used for Dropdown only
export interface OverrideContextProps {
prefixCls?: ComputedRef<string>;
mode?: ComputedRef<MenuProps['mode']>;
selectable?: ComputedRef<boolean>;
validator?: (menuProps: Pick<MenuProps, 'mode'>) => void;
onClick?: () => void;
}
export const OverrideContextKey: InjectionKey<OverrideContextProps> = Symbol('OverrideContextKey');
export const useInjectOverride = () => {
return inject(OverrideContextKey, undefined);
};
export const useProvideOverride = (props: OverrideContextProps) => {
const { prefixCls, mode, selectable, validator, onClick } = useInjectOverride() || {};
provide(OverrideContextKey, {
prefixCls: computed(() => (props.prefixCls?.value ?? prefixCls?.value) as string),
mode: computed(() => props.mode?.value ?? mode?.value),
selectable: computed(() => (props.selectable?.value ?? selectable?.value) as boolean),
validator: props.validator ?? validator,
onClick: props.onClick ?? onClick,
});
};

View File

@ -93,7 +93,6 @@ export default defineComponent({
changeActiveKeys,
mode,
inlineCollapsed,
antdMenuTheme,
openKeys,
overflowDisabled,
onOpenChange,
@ -101,6 +100,7 @@ export default defineComponent({
unRegisterMenuInfo,
selectedSubMenuKeys,
expandIcon: menuExpandIcon,
theme,
} = useInjectMenu();
const hasKey = vnodeKey !== undefined && vnodeKey !== null;
@ -196,7 +196,7 @@ export default defineComponent({
const popupClassName = computed(() =>
classNames(
prefixCls.value,
`${prefixCls.value}-${props.theme || antdMenuTheme.value}`,
`${prefixCls.value}-${props.theme || theme.value}`,
props.popupClassName,
),
);
@ -279,13 +279,14 @@ export default defineComponent({
const subMenuPrefixClsValue = subMenuPrefixCls.value;
let titleNode = () => null;
if (!overflowDisabled.value && mode.value !== 'inline') {
const popupOffset = mode.value === 'horizontal' ? [0, 8] : [10, 0];
titleNode = () => (
<PopupTrigger
mode={triggerModeRef.value}
prefixCls={subMenuPrefixClsValue}
visible={!props.internalPopupClose && open.value}
popupClassName={popupClassName.value}
popupOffset={props.popupOffset}
popupOffset={props.popupOffset || popupOffset}
disabled={mergedDisabled.value}
onVisibleChange={onPopupVisibleChange}
v-slots={{

View File

@ -13,24 +13,24 @@ import { ref, shallowRef, watch } from 'vue';
import type { MenuProps } from '../Menu';
import type { StoreMenuInfo } from './useMenuContext';
interface MenuItemType extends VcMenuItemType {
export interface MenuItemType extends VcMenuItemType {
danger?: boolean;
icon?: any;
title?: string;
}
interface SubMenuType extends Omit<VcSubMenuType, 'children'> {
export interface SubMenuType extends Omit<VcSubMenuType, 'children'> {
icon?: any;
theme?: 'dark' | 'light';
children: ItemType[];
}
interface MenuItemGroupType extends Omit<VcMenuItemGroupType, 'children'> {
export interface MenuItemGroupType extends Omit<VcMenuItemGroupType, 'children'> {
children?: MenuItemType[];
key?: Key;
}
interface MenuDividerType extends VcMenuDividerType {
export interface MenuDividerType extends VcMenuDividerType {
dashed?: boolean;
key?: Key;
}

View File

@ -31,7 +31,7 @@ export interface MenuContextProps {
rtl?: ComputedRef<boolean>;
inlineCollapsed: Ref<boolean>;
antdMenuTheme?: ComputedRef<MenuTheme>;
theme?: ComputedRef<MenuTheme>;
siderCollapsed?: ComputedRef<boolean>;

View File

@ -1,177 +0,0 @@
.accessibility-focus-dark() {
box-shadow: 0 0 0 2px @primary-7;
}
.@{menu-prefix-cls} {
&&-root:focus-visible {
.accessibility-focus-dark();
}
&-dark &-item,
&-dark &-submenu-title {
&:focus-visible {
.accessibility-focus-dark();
}
}
// dark theme
&&-dark,
&-dark &-sub,
&&-dark &-sub {
color: @menu-dark-color;
background: @menu-dark-bg;
.@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
opacity: 0.45;
transition: all 0.3s;
&::after,
&::before {
background: @menu-dark-arrow-color;
}
}
}
&-dark&-submenu-popup {
background: transparent;
}
&-dark &-inline&-sub {
background: @menu-dark-inline-submenu-bg;
}
&-dark&-horizontal {
border-bottom: 0;
}
&-dark&-horizontal > &-item,
&-dark&-horizontal > &-submenu {
top: 0;
margin-top: 0;
padding: @menu-item-padding;
border-color: @menu-dark-bg;
border-bottom: 0;
}
&-dark&-horizontal > &-item:hover {
background-color: @menu-dark-item-active-bg;
}
&-dark&-horizontal > &-item > a::before {
bottom: 0;
}
&-dark &-item,
&-dark &-item-group-title,
&-dark &-item > a,
&-dark &-item > span > a {
color: @menu-dark-color;
}
&-dark&-inline,
&-dark&-vertical,
&-dark&-vertical-left,
&-dark&-vertical-right {
border-right: 0;
}
&-dark&-inline &-item,
&-dark&-vertical &-item,
&-dark&-vertical-left &-item,
&-dark&-vertical-right &-item {
left: 0;
margin-left: 0;
border-right: 0;
&::after {
border-right: 0;
}
}
&-dark&-inline &-item,
&-dark&-inline &-submenu-title {
width: 100%;
}
&-dark &-item:hover,
&-dark &-item-active,
&-dark &-submenu-active,
&-dark &-submenu-open,
&-dark &-submenu-selected,
&-dark &-submenu-title:hover {
color: @menu-dark-highlight-color;
background-color: transparent;
> a,
> span > a {
color: @menu-dark-highlight-color;
}
> .@{menu-prefix-cls}-submenu-title {
> .@{menu-prefix-cls}-submenu-arrow {
opacity: 1;
&::after,
&::before {
background: @menu-dark-highlight-color;
}
}
}
}
&-dark &-item:hover {
background-color: @menu-dark-item-hover-bg;
}
&-dark&-dark:not(&-horizontal) &-item-selected {
background-color: @menu-dark-item-active-bg;
}
&-dark &-item-selected {
color: @menu-dark-highlight-color;
border-right: 0;
&::after {
border-right: 0;
}
> a,
> span > a,
> a:hover,
> span > a:hover {
color: @menu-dark-highlight-color;
}
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
color: @menu-dark-selected-item-icon-color;
+ span {
color: @menu-dark-selected-item-text-color;
}
}
}
&&-dark &-item-selected,
&-submenu-popup&-dark &-item-selected {
background-color: @menu-dark-item-active-bg;
}
// Disabled state sets text to dark gray and nukes hover/tab effects
&-dark &-item-disabled,
&-dark &-submenu-disabled {
&,
> a,
> span > a {
color: @disabled-color-dark !important;
opacity: 0.8;
}
> .@{menu-prefix-cls}-submenu-title {
color: @disabled-color-dark !important;
> .@{menu-prefix-cls}-submenu-arrow {
&::before,
&::after {
background: @disabled-color-dark !important;
}
}
}
}
}

View File

@ -0,0 +1,57 @@
import type { MenuToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const getHorizontalStyle: GenerateStyle<MenuToken> = token => {
const {
componentCls,
motionDurationSlow,
menuHorizontalHeight,
colorSplit,
lineWidth,
lineType,
menuItemPaddingInline,
} = token;
return {
[`${componentCls}-horizontal`]: {
lineHeight: `${menuHorizontalHeight}px`,
border: 0,
borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`,
boxShadow: 'none',
'&::after': {
display: 'block',
clear: 'both',
height: 0,
content: '"\\20"',
},
// ======================= Item =======================
[`${componentCls}-item, ${componentCls}-submenu`]: {
position: 'relative',
display: 'inline-block',
verticalAlign: 'bottom',
paddingInline: menuItemPaddingInline,
},
[`> ${componentCls}-item:hover,
> ${componentCls}-item-active,
> ${componentCls}-submenu ${componentCls}-submenu-title:hover`]: {
backgroundColor: 'transparent',
},
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
transition: [`border-color ${motionDurationSlow}`, `background ${motionDurationSlow}`].join(
',',
),
},
// ===================== Sub Menu =====================
[`${componentCls}-submenu-arrow`]: {
display: 'none',
},
},
};
};
export default getHorizontalStyle;

View File

@ -1,700 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './status';
@menu-prefix-cls: ~'@{ant-prefix}-menu';
@menu-animation-duration-normal: 0.15s;
.accessibility-focus() {
box-shadow: 0 0 0 2px @primary-2;
}
// TODO: Should remove icon style compatible in v5
// default theme
.@{menu-prefix-cls} {
.reset-component();
margin-bottom: 0;
padding-left: 0; // Override default ul/ol
color: @menu-item-color;
font-size: @menu-item-font-size;
line-height: 0; // Fix display inline-block gap
text-align: left;
list-style: none;
background: @menu-bg;
outline: none;
box-shadow: @box-shadow-base;
transition: background @animation-duration-slow,
width @animation-duration-slow cubic-bezier(0.2, 0, 0, 1) 0s;
.clearfix();
&&-root:focus-visible {
.accessibility-focus();
}
ul,
ol {
margin: 0;
padding: 0;
list-style: none;
}
// Overflow ellipsis
&-overflow {
display: flex;
&-item {
flex: none;
}
}
&-hidden,
&-submenu-hidden {
display: none;
}
&-item-group-title {
height: @menu-item-group-height;
padding: 8px 16px;
color: @menu-item-group-title-color;
font-size: @menu-item-group-title-font-size;
line-height: @menu-item-group-height;
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-inline {
transition: border-color @animation-duration-slow @ease-in-out,
background @animation-duration-slow @ease-in-out,
padding @menu-animation-duration-normal @ease-in-out;
}
&-submenu-selected {
color: @menu-highlight-color;
}
&-item:active,
&-submenu-title:active {
background: @menu-item-active-bg;
}
&-submenu &-sub {
cursor: initial;
transition: background @animation-duration-slow @ease-in-out,
padding @animation-duration-slow @ease-in-out;
}
&-title-content {
transition: color @animation-duration-slow;
}
&-item a {
color: @menu-item-color;
&:hover {
color: @menu-highlight-color;
}
&::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: transparent;
content: '';
}
}
// https://github.com/ant-design/ant-design/issues/19809
&-item > .@{ant-prefix}-badge a {
color: @menu-item-color;
&:hover {
color: @menu-highlight-color;
}
}
&-item-divider {
overflow: hidden;
line-height: 0;
border-color: @border-color-split;
border-style: solid;
border-width: 1px 0 0;
}
&-item-divider-dashed {
border-style: dashed;
}
&-horizontal &-item,
&-horizontal &-submenu {
margin-top: -1px;
}
&-horizontal > &-item:hover,
&-horizontal > &-item-active,
&-horizontal > &-submenu &-submenu-title:hover {
background-color: transparent;
}
&-item-selected {
color: @menu-highlight-color;
a,
a:hover {
color: @menu-highlight-color;
}
}
&:not(&-horizontal) &-item-selected {
background-color: @menu-item-active-bg;
}
&-inline,
&-vertical,
&-vertical-left {
border-right: @border-width-base @border-style-base @border-color-split;
}
&-vertical-right {
border-left: @border-width-base @border-style-base @border-color-split;
}
&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
min-width: 160px;
max-height: calc(100vh - 100px);
padding: 0;
overflow: hidden;
border-right: 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 {
left: 0;
margin-left: 0;
border-right: 0;
&::after {
border-right: 0;
}
}
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
transform-origin: 0 0;
}
}
&-horizontal&-sub {
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,
&-submenu-title {
position: relative;
display: block;
margin: 0;
padding: @menu-item-padding;
white-space: nowrap;
cursor: pointer;
transition: border-color @animation-duration-slow, background @animation-duration-slow,
padding @animation-duration-slow @ease-in-out;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
min-width: 14px;
font-size: @menu-icon-size;
transition: font-size @menu-animation-duration-normal @ease-out,
margin @animation-duration-slow @ease-in-out, color @animation-duration-slow;
+ span {
margin-left: @menu-icon-margin-right;
opacity: 1;
transition: opacity @animation-duration-slow @ease-in-out, margin @animation-duration-slow,
color @animation-duration-slow;
}
}
.@{menu-prefix-cls}-item-icon.svg {
vertical-align: -0.125em;
}
&.@{menu-prefix-cls}-item-only-child {
> .@{iconfont-css-prefix},
> .@{menu-prefix-cls}-item-icon {
margin-right: 0;
}
}
&:focus-visible {
.accessibility-focus();
}
}
& > &-item-divider {
margin: 1px 0;
padding: 0;
}
&-submenu {
&-popup {
position: absolute;
z-index: @zindex-dropdown;
background: transparent;
border-radius: @border-radius-base;
box-shadow: none;
transform-origin: 0 0;
// https://github.com/ant-design/ant-design/issues/13955
&::before {
position: absolute;
top: -7px;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
opacity: 0.0001;
content: ' ';
}
}
// https://github.com/ant-design/ant-design/issues/13955
&-placement-rightTop::before {
top: 0;
left: -7px;
}
> .@{menu-prefix-cls} {
background-color: @menu-bg;
border-radius: @border-radius-base;
&-submenu-title::after {
transition: transform @animation-duration-slow @ease-in-out;
}
}
&-popup > .@{menu-prefix-cls} {
background-color: @menu-popup-bg;
}
&-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;
width: 6px;
height: 1.5px;
background-color: currentcolor;
border-radius: 2px;
transition: background @animation-duration-slow @ease-in-out,
transform @animation-duration-slow @ease-in-out, top @animation-duration-slow @ease-in-out,
color @animation-duration-slow @ease-in-out;
content: '';
}
&::before {
transform: rotate(45deg) translateY(-2.5px);
}
&::after {
transform: rotate(-45deg) translateY(2.5px);
}
}
&:hover > &-title > &-expand-icon,
&:hover > &-title > &-arrow {
color: @menu-highlight-color;
}
.@{menu-prefix-cls}-inline-collapsed &-arrow,
&-inline &-arrow {
// ↓
&::before {
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);
}
}
}
&-vertical &-submenu-selected,
&-vertical-left &-submenu-selected,
&-vertical-right &-submenu-selected {
color: @menu-highlight-color;
}
&-horizontal {
line-height: @menu-horizontal-line-height;
border: 0;
border-bottom: @border-width-base @border-style-base @border-color-split;
box-shadow: none;
&:not(.@{menu-prefix-cls}-dark) {
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
margin-top: -1px;
margin-bottom: 0;
padding: @menu-item-padding;
&:hover,
&-active,
&-open,
&-selected {
color: @menu-highlight-color;
&::after {
border-bottom: 2px solid @menu-highlight-color;
}
}
}
}
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
position: relative;
top: 1px;
display: inline-block;
vertical-align: bottom;
&::after {
position: absolute;
right: @menu-item-padding-horizontal;
bottom: 0;
left: @menu-item-padding-horizontal;
border-bottom: 2px solid transparent;
transition: border-color @animation-duration-slow @ease-in-out;
content: '';
}
}
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
padding: 0;
}
> .@{menu-prefix-cls}-item {
a {
color: @menu-item-color;
&:hover {
color: @menu-highlight-color;
}
&::before {
bottom: -2px;
}
}
&-selected a {
color: @menu-highlight-color;
}
}
&::after {
display: block;
clear: both;
height: 0;
content: '\20';
}
}
&-vertical,
&-vertical-left,
&-vertical-right,
&-inline {
.@{menu-prefix-cls}-item {
position: relative;
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
border-right: @menu-item-active-border-width solid @menu-highlight-color;
transform: scaleY(0.0001);
opacity: 0;
transition: transform @menu-animation-duration-normal @ease-out,
opacity @menu-animation-duration-normal @ease-out;
content: '';
}
}
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
height: @menu-item-height;
margin-top: @menu-item-vertical-margin;
margin-bottom: @menu-item-vertical-margin;
padding: 0 16px;
overflow: hidden;
line-height: @menu-item-height;
text-overflow: ellipsis;
}
// disable margin collapsed
.@{menu-prefix-cls}-submenu {
padding-bottom: 0.02px;
}
.@{menu-prefix-cls}-item:not(:last-child) {
margin-bottom: @menu-item-boundary-margin;
}
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
height: @menu-inline-toplevel-item-height;
line-height: @menu-inline-toplevel-item-height;
}
}
&-vertical {
.@{menu-prefix-cls}-item-group-list .@{menu-prefix-cls}-submenu-title,
.@{menu-prefix-cls}-submenu-title {
padding-right: 34px;
}
}
&-inline {
width: 100%;
.@{menu-prefix-cls}-selected,
.@{menu-prefix-cls}-item-selected {
&::after {
transform: scaleY(1);
opacity: 1;
transition: transform @menu-animation-duration-normal @ease-in-out,
opacity @menu-animation-duration-normal @ease-in-out;
}
}
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
width: ~'calc(100% + 1px)';
}
.@{menu-prefix-cls}-item-group-list .@{menu-prefix-cls}-submenu-title,
.@{menu-prefix-cls}-submenu-title {
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 {
width: @menu-collapsed-width;
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-item-group
> .@{menu-prefix-cls}-item-group-list
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-item-group
> .@{menu-prefix-cls}-item-group-list
> .@{menu-prefix-cls}-submenu
> .@{menu-prefix-cls}-submenu-title,
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
left: 0;
padding: 0 ~'calc(50% - @{menu-icon-size-lg} / 2)';
text-overflow: clip;
.@{menu-prefix-cls}-submenu-arrow {
opacity: 0;
}
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
margin: 0;
font-size: @menu-icon-size-lg;
line-height: @menu-item-height;
+ span {
display: inline-block;
opacity: 0;
}
}
}
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
display: inline-block;
}
&-tooltip {
pointer-events: none;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
display: none;
}
a {
color: @text-color-dark;
}
}
.@{menu-prefix-cls}-item-group-title {
padding-right: 4px;
padding-left: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&-item-group-list {
margin: 0;
padding: 0;
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
padding: 0 16px 0 28px;
}
}
&-root&-vertical,
&-root&-vertical-left,
&-root&-vertical-right,
&-root&-inline {
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 {
padding: 0;
background: @menu-inline-submenu-bg;
border: 0;
border-radius: 0;
box-shadow: none;
& > .@{menu-prefix-cls}-item,
& > .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
height: @menu-item-height;
line-height: @menu-item-height;
list-style-position: inside;
list-style-type: disc;
}
& .@{menu-prefix-cls}-item-group-title {
padding-left: 32px;
}
}
// Disabled state sets text to gray and nukes hover/tab effects
&-item-disabled,
&-submenu-disabled {
color: @disabled-color !important;
background: none;
cursor: not-allowed;
&::after {
border-color: transparent !important;
}
a {
color: @disabled-color !important;
pointer-events: none;
}
> .@{menu-prefix-cls}-submenu-title {
color: @disabled-color !important;
cursor: not-allowed;
> .@{menu-prefix-cls}-submenu-arrow {
&::before,
&::after {
background: @disabled-color !important;
}
}
}
}
}
// Integration with header element so menu items have the same height
.@{ant-prefix}-layout-header {
.@{menu-prefix-cls} {
line-height: inherit;
}
}
// https://github.com/ant-design/ant-design/issues/32950
.@{ant-prefix}-menu-inline-collapsed-tooltip {
a,
a:hover {
color: @white;
}
}
@import './light';
@import './dark';
@import './rtl';

View File

@ -0,0 +1,581 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { CSSObject } from '../../_util/cssinjs';
import { genCollapseMotion, initSlideMotion, initZoomMotion } from '../../_style/motion';
import type { FullToken, GenerateStyle, UseComponentStyleResult } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import getHorizontalStyle from './horizontal';
import getRTLStyle from './rtl';
import getThemeStyle from './theme';
import getVerticalStyle from './vertical';
import { clearFix, resetComponent, resetIcon } from '../../_style';
import { Ref } from 'vue';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
dropdownWidth: number;
zIndexPopup: number;
// Group
colorGroupTitle: string;
// radius
radiusItem: number;
radiusSubMenuItem: number;
// Item Text
// > Default
colorItemText: string;
colorItemTextHover: string;
colorItemTextHoverHorizontal: string;
colorItemTextSelected: string;
colorItemTextSelectedHorizontal: string;
// > Disabled
colorItemTextDisabled: string;
// > Danger
colorDangerItemText: string;
colorDangerItemTextHover: string;
colorDangerItemTextSelected: string;
colorDangerItemBgActive: string;
colorDangerItemBgSelected: string;
// Item Bg
colorItemBg: string;
colorItemBgHover: string;
colorSubItemBg: string;
// > Default
colorItemBgActive: string;
colorItemBgSelected: string;
colorItemBgSelectedHorizontal: string;
// Ink Bar
colorActiveBarWidth: number;
colorActiveBarHeight: number;
colorActiveBarBorderSize: number;
itemMarginInline: number;
}
export interface MenuToken extends FullToken<'Menu'> {
menuItemHeight: number;
menuHorizontalHeight: number;
menuItemPaddingInline: number;
menuArrowSize: number;
menuArrowOffset: string;
menuPanelMaskInset: number;
menuSubMenuBg: string;
}
const genMenuItemStyle = (token: MenuToken): CSSObject => {
const {
componentCls,
fontSize,
motionDurationSlow,
motionDurationMid,
motionEaseInOut,
motionEaseOut,
iconCls,
controlHeightSM,
} = token;
return {
// >>>>> Item
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
position: 'relative',
display: 'block',
margin: 0,
whiteSpace: 'nowrap',
cursor: 'pointer',
transition: [
`border-color ${motionDurationSlow}`,
`background ${motionDurationSlow}`,
`padding ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
[`${componentCls}-item-icon, ${iconCls}`]: {
minWidth: fontSize,
fontSize,
transition: [
`font-size ${motionDurationMid} ${motionEaseOut}`,
`margin ${motionDurationSlow} ${motionEaseInOut}`,
`color ${motionDurationSlow}`,
].join(','),
'+ span': {
marginInlineStart: controlHeightSM - fontSize,
opacity: 1,
transition: [
`opacity ${motionDurationSlow} ${motionEaseInOut}`,
`margin ${motionDurationSlow}`,
`color ${motionDurationSlow}`,
].join(','),
},
},
[`${componentCls}-item-icon`]: {
...resetIcon(),
},
[`&${componentCls}-item-only-child`]: {
[`> ${iconCls}, > ${componentCls}-item-icon`]: {
marginInlineEnd: 0,
},
},
},
// Disabled state sets text to gray and nukes hover/tab effects
[`${componentCls}-item-disabled, ${componentCls}-submenu-disabled`]: {
background: 'none !important',
cursor: 'not-allowed',
'&::after': {
borderColor: 'transparent !important',
},
a: {
color: 'inherit !important',
},
[`> ${componentCls}-submenu-title`]: {
color: 'inherit !important',
cursor: 'not-allowed',
},
},
};
};
const genSubMenuArrowStyle = (token: MenuToken): CSSObject => {
const {
componentCls,
motionDurationSlow,
motionEaseInOut,
borderRadius,
menuArrowSize,
menuArrowOffset,
} = token;
return {
[`${componentCls}-submenu`]: {
[`&-expand-icon, &-arrow`]: {
position: 'absolute',
top: '50%',
insetInlineEnd: token.margin,
width: menuArrowSize,
color: 'currentcolor',
transform: 'translateY(-50%)',
transition: `transform ${motionDurationSlow} ${motionEaseInOut}, opacity ${motionDurationSlow}`,
},
'&-arrow': {
// →
'&::before, &::after': {
position: 'absolute',
width: menuArrowSize * 0.6,
height: menuArrowSize * 0.15,
backgroundColor: 'currentcolor',
borderRadius,
transition: [
`background ${motionDurationSlow} ${motionEaseInOut}`,
`transform ${motionDurationSlow} ${motionEaseInOut}`,
`top ${motionDurationSlow} ${motionEaseInOut}`,
`color ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
content: '""',
},
'&::before': {
transform: `rotate(45deg) translateY(-${menuArrowOffset})`,
},
'&::after': {
transform: `rotate(-45deg) translateY(${menuArrowOffset})`,
},
},
},
};
};
// =============================== Base ===============================
const getBaseStyle: GenerateStyle<MenuToken> = token => {
const {
antCls,
componentCls,
fontSize,
motionDurationSlow,
motionDurationMid,
motionEaseInOut,
lineHeight,
paddingXS,
padding,
colorSplit,
lineWidth,
zIndexPopup,
borderRadiusLG,
radiusSubMenuItem,
menuArrowSize,
menuArrowOffset,
lineType,
menuPanelMaskInset,
} = token;
return [
// Misc
{
'': {
[`${componentCls}`]: {
...clearFix(),
// Hidden
[`&-hidden`]: {
display: 'none',
},
},
},
[`${componentCls}-submenu-hidden`]: {
display: 'none',
},
},
{
[componentCls]: {
...resetComponent(token),
...clearFix(),
marginBottom: 0,
paddingInlineStart: 0, // Override default ul/ol
fontSize,
lineHeight: 0, // Fix display inline-block gap
listStyle: 'none',
outline: 'none',
transition: [
`background ${motionDurationSlow}`,
// Magic cubic here but smooth transition
`width ${motionDurationSlow} cubic-bezier(0.2, 0, 0, 1) 0s`,
].join(','),
[`ul, ol`]: {
margin: 0,
padding: 0,
listStyle: 'none',
},
// Overflow ellipsis
[`&-overflow`]: {
display: 'flex',
[`${componentCls}-item`]: {
flex: 'none',
},
},
[`${componentCls}-item, ${componentCls}-submenu, ${componentCls}-submenu-title`]: {
borderRadius: token.radiusItem,
},
[`${componentCls}-item-group-title`]: {
padding: `${paddingXS}px ${padding}px`,
fontSize,
lineHeight,
transition: `all ${motionDurationSlow}`,
},
[`&-horizontal ${componentCls}-submenu`]: {
transition: [
`border-color ${motionDurationSlow} ${motionEaseInOut}`,
`background ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
},
[`${componentCls}-submenu, ${componentCls}-submenu-inline`]: {
transition: [
`border-color ${motionDurationSlow} ${motionEaseInOut}`,
`background ${motionDurationSlow} ${motionEaseInOut}`,
`padding ${motionDurationMid} ${motionEaseInOut}`,
].join(','),
},
[`${componentCls}-submenu ${componentCls}-sub`]: {
cursor: 'initial',
transition: [
`background ${motionDurationSlow} ${motionEaseInOut}`,
`padding ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
},
[`${componentCls}-title-content`]: {
transition: `color ${motionDurationSlow}`,
},
[`${componentCls}-item a`]: {
'&::before': {
position: 'absolute',
inset: 0,
backgroundColor: 'transparent',
content: '""',
},
},
// Removed a Badge related style seems it's safe
// https://github.com/ant-design/ant-design/issues/19809
// >>>>> Divider
[`${componentCls}-item-divider`]: {
overflow: 'hidden',
lineHeight: 0,
borderColor: colorSplit,
borderStyle: lineType,
borderWidth: 0,
borderTopWidth: lineWidth,
marginBlock: lineWidth,
padding: 0,
'&-dashed': {
borderStyle: 'dashed',
},
},
// Item
...genMenuItemStyle(token),
[`${componentCls}-item-group`]: {
[`${componentCls}-item-group-list`]: {
margin: 0,
padding: 0,
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
paddingInline: `${fontSize * 2}px ${padding}px`,
},
},
},
// ======================= Sub Menu =======================
'&-submenu': {
'&-popup': {
position: 'absolute',
zIndex: zIndexPopup,
background: 'transparent',
borderRadius: borderRadiusLG,
boxShadow: 'none',
transformOrigin: '0 0',
// https://github.com/ant-design/ant-design/issues/13955
'&::before': {
position: 'absolute',
inset: `${menuPanelMaskInset}px 0 0`,
zIndex: -1,
width: '100%',
height: '100%',
opacity: 0,
content: '""',
},
},
// https://github.com/ant-design/ant-design/issues/13955
'&-placement-rightTop::before': {
top: 0,
insetInlineStart: menuPanelMaskInset,
},
[`> ${componentCls}`]: {
borderRadius: borderRadiusLG,
...genMenuItemStyle(token),
...genSubMenuArrowStyle(token),
[`${componentCls}-item, ${componentCls}-submenu > ${componentCls}-submenu-title`]: {
borderRadius: radiusSubMenuItem,
},
[`${componentCls}-submenu-title::after`]: {
transition: `transform ${motionDurationSlow} ${motionEaseInOut}`,
},
},
},
...genSubMenuArrowStyle(token),
[`&-inline-collapsed ${componentCls}-submenu-arrow,
&-inline ${componentCls}-submenu-arrow`]: {
// ↓
'&::before': {
transform: `rotate(-45deg) translateX(${menuArrowOffset})`,
},
'&::after': {
transform: `rotate(45deg) translateX(-${menuArrowOffset})`,
},
},
[`${componentCls}-submenu-open${componentCls}-submenu-inline > ${componentCls}-submenu-title > ${componentCls}-submenu-arrow`]:
{
// ↑
transform: `translateY(-${menuArrowSize * 0.2}px)`,
'&::after': {
transform: `rotate(-45deg) translateX(-${menuArrowOffset})`,
},
'&::before': {
transform: `rotate(45deg) translateX(${menuArrowOffset})`,
},
},
},
},
// Integration with header element so menu items have the same height
{
[`${antCls}-layout-header`]: {
[componentCls]: {
lineHeight: 'inherit',
},
},
},
];
};
// ============================== Export ==============================
export default (prefixCls: Ref<string>, injectStyle: Ref<boolean>): UseComponentStyleResult => {
const useOriginHook = genComponentStyleHook(
'Menu',
(token, { overrideComponentToken }) => {
// Dropdown will handle menu style self. We do not need to handle this.
if (injectStyle.value === false) {
return [];
}
const { colorBgElevated, colorPrimary, colorError, colorErrorHover, colorTextLightSolid } =
token;
const { controlHeightLG, fontSize } = token;
const menuArrowSize = (fontSize / 7) * 5;
// Menu Token
const menuToken = mergeToken<MenuToken>(token, {
menuItemHeight: controlHeightLG,
menuItemPaddingInline: token.margin,
menuArrowSize,
menuHorizontalHeight: controlHeightLG * 1.15,
menuArrowOffset: `${menuArrowSize * 0.25}px`,
menuPanelMaskInset: -7, // Still a hardcode here since it's offset by rc-align
menuSubMenuBg: colorBgElevated,
});
const colorTextDark = new TinyColor(colorTextLightSolid).setAlpha(0.65).toRgbString();
const menuDarkToken = mergeToken<MenuToken>(
menuToken,
{
colorItemText: colorTextDark,
colorItemTextHover: colorTextLightSolid,
colorGroupTitle: colorTextDark,
colorItemTextSelected: colorTextLightSolid,
colorItemBg: '#001529',
colorSubItemBg: '#000c17',
colorItemBgActive: 'transparent',
colorItemBgSelected: colorPrimary,
colorActiveBarWidth: 0,
colorActiveBarHeight: 0,
colorActiveBarBorderSize: 0,
// Disabled
colorItemTextDisabled: new TinyColor(colorTextLightSolid).setAlpha(0.25).toRgbString(),
// Danger
colorDangerItemText: colorError,
colorDangerItemTextHover: colorErrorHover,
colorDangerItemTextSelected: colorTextLightSolid,
colorDangerItemBgActive: colorError,
colorDangerItemBgSelected: colorError,
menuSubMenuBg: '#001529',
// Horizontal
colorItemTextSelectedHorizontal: colorTextLightSolid,
colorItemBgSelectedHorizontal: colorPrimary,
},
{
...overrideComponentToken,
},
);
return [
// Basic
getBaseStyle(menuToken),
// Horizontal
getHorizontalStyle(menuToken), // Hard code for some light style
// Vertical
getVerticalStyle(menuToken), // Hard code for some light style
// Theme
getThemeStyle(menuToken, 'light'),
getThemeStyle(menuDarkToken, 'dark'),
// RTL
getRTLStyle(menuToken),
// Motion
genCollapseMotion(menuToken),
initSlideMotion(menuToken, 'slide-up'),
initSlideMotion(menuToken, 'slide-down'),
initZoomMotion(menuToken, 'zoom-big'),
];
},
token => {
const {
colorPrimary,
colorError,
colorTextDisabled,
colorErrorBg,
colorText,
colorTextDescription,
colorBgContainer,
colorFillAlter,
colorFillContent,
lineWidth,
lineWidthBold,
controlItemBgActive,
colorBgTextHover,
} = token;
return {
dropdownWidth: 160,
zIndexPopup: token.zIndexPopupBase + 50,
radiusItem: token.borderRadiusLG,
radiusSubMenuItem: token.borderRadiusSM,
colorItemText: colorText,
colorItemTextHover: colorText,
colorItemTextHoverHorizontal: colorPrimary,
colorGroupTitle: colorTextDescription,
colorItemTextSelected: colorPrimary,
colorItemTextSelectedHorizontal: colorPrimary,
colorItemBg: colorBgContainer,
colorItemBgHover: colorBgTextHover,
colorItemBgActive: colorFillContent,
colorSubItemBg: colorFillAlter,
colorItemBgSelected: controlItemBgActive,
colorItemBgSelectedHorizontal: 'transparent',
colorActiveBarWidth: 0,
colorActiveBarHeight: lineWidthBold,
colorActiveBarBorderSize: lineWidth,
// Disabled
colorItemTextDisabled: colorTextDisabled,
// Danger
colorDangerItemText: colorError,
colorDangerItemTextHover: colorError,
colorDangerItemTextSelected: colorError,
colorDangerItemBgActive: colorErrorBg,
colorDangerItemBgSelected: colorErrorBg,
itemMarginInline: token.marginXXS,
};
},
);
return useOriginHook(prefixCls);
};

View File

@ -1,6 +0,0 @@
import '../../style/index.less';
import './index.less';
// style dependencies
// deps-lint-skip: layout
import '../../tooltip/style';

View File

@ -1,12 +0,0 @@
.@{menu-prefix-cls} {
// light theme
&-light {
.@{menu-prefix-cls}-item:hover,
.@{menu-prefix-cls}-item-active,
.@{menu-prefix-cls}:not(.@{menu-prefix-cls}-inline) .@{menu-prefix-cls}-submenu-open,
.@{menu-prefix-cls}-submenu-active,
.@{menu-prefix-cls}-submenu-title:hover {
color: @menu-highlight-color;
}
}
}

View File

@ -1,165 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@menu-prefix-cls: ~'@{ant-prefix}-menu';
.@{menu-prefix-cls} {
&&-rtl {
direction: rtl;
text-align: right;
}
&-item-group-title {
.@{menu-prefix-cls}-rtl & {
text-align: right;
}
}
&-inline,
&-vertical {
.@{menu-prefix-cls}-rtl& {
border-right: none;
border-left: @border-width-base @border-style-base @border-color-split;
}
}
&-dark&-inline,
&-dark&-vertical {
.@{menu-prefix-cls}-rtl& {
border-left: none;
}
}
&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
.@{menu-prefix-cls}-rtl& {
transform-origin: top right;
}
}
}
&-item,
&-submenu-title {
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
.@{menu-prefix-cls}-rtl & {
margin-right: auto;
margin-left: @menu-icon-margin-right;
}
}
&.@{menu-prefix-cls}-item-only-child {
> .@{menu-prefix-cls}-item-icon,
> .@{iconfont-css-prefix} {
.@{menu-prefix-cls}-rtl & {
margin-left: 0;
}
}
}
}
&-submenu {
&-rtl.@{menu-prefix-cls}-submenu-popup {
transform-origin: 100% 0;
}
&-vertical,
&-vertical-left,
&-vertical-right,
&-inline {
> .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
.@{menu-prefix-cls}-rtl & {
right: auto;
left: 16px;
}
}
}
&-vertical,
&-vertical-left,
&-vertical-right {
> .@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
&::before {
.@{menu-prefix-cls}-rtl & {
transform: rotate(-45deg) translateY(-2px);
}
}
&::after {
.@{menu-prefix-cls}-rtl & {
transform: rotate(45deg) translateY(2px);
}
}
}
}
}
&-vertical,
&-vertical-left,
&-vertical-right,
&-inline {
.@{menu-prefix-cls}-item {
&::after {
.@{menu-prefix-cls}-rtl& {
right: auto;
left: 0;
}
}
}
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
.@{menu-prefix-cls}-rtl& {
text-align: right;
}
}
}
&-inline {
.@{menu-prefix-cls}-submenu-title {
.@{menu-prefix-cls}-rtl& {
padding-right: 0;
padding-left: 34px;
}
}
}
&-vertical {
.@{menu-prefix-cls}-submenu-title {
.@{menu-prefix-cls}-rtl& {
padding-right: 16px;
padding-left: 34px;
}
}
}
&-inline-collapsed&-vertical {
.@{menu-prefix-cls}-submenu-title {
.@{menu-prefix-cls}-rtl& {
padding: 0 ~'calc(50% - @{menu-icon-size-lg} / 2)';
}
}
}
&-item-group-list {
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
.@{menu-prefix-cls}-rtl & {
padding: 0 28px 0 16px;
}
}
}
&-sub&-inline {
border: 0;
& .@{menu-prefix-cls}-item-group-title {
.@{menu-prefix-cls}-rtl& {
padding-right: 32px;
padding-left: 0;
}
}
}
}

View File

@ -0,0 +1,28 @@
import type { MenuToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const getRTLStyle: GenerateStyle<MenuToken> = ({ componentCls, menuArrowOffset }) => ({
[`${componentCls}-rtl`]: {
direction: 'rtl',
},
[`${componentCls}-submenu-rtl`]: {
transformOrigin: '100% 0',
},
// Vertical Arrow
[`${componentCls}-rtl${componentCls}-vertical,
${componentCls}-submenu-rtl ${componentCls}-vertical`]: {
[`${componentCls}-submenu-arrow`]: {
'&::before': {
transform: `rotate(-45deg) translateY(-${menuArrowOffset})`,
},
'&::after': {
transform: `rotate(45deg) translateY(${menuArrowOffset})`,
},
},
},
});
export default getRTLStyle;

View File

@ -1,49 +0,0 @@
@import (reference) '../../style/themes/index';
@menu-prefix-cls: ~'@{ant-prefix}-menu';
.@{menu-prefix-cls} {
// Danger
&-item-danger&-item {
color: @menu-highlight-danger-color;
&:hover,
&-active {
color: @menu-highlight-danger-color;
}
&:active {
background: @menu-item-active-danger-bg;
}
&-selected {
color: @menu-highlight-danger-color;
> a,
> a:hover {
color: @menu-highlight-danger-color;
}
}
.@{menu-prefix-cls}:not(.@{menu-prefix-cls}-horizontal) &-selected {
background-color: @menu-item-active-danger-bg;
}
.@{menu-prefix-cls}-inline &::after {
border-right-color: @menu-highlight-danger-color;
}
}
// ==================== Dark ====================
&-dark &-item-danger&-item {
&,
&:hover,
& > a {
color: @menu-dark-danger-color;
}
}
&-dark&-dark:not(&-horizontal) &-item-danger&-item-selected {
color: @menu-dark-highlight-color;
background-color: @menu-dark-item-active-danger-bg;
}
}

View File

@ -0,0 +1,262 @@
import type { CSSInterpolation } from '../../_util/cssinjs';
import { genFocusOutline } from '../../_style';
import type { MenuToken } from '.';
const accessibilityFocus = (token: MenuToken) => ({
...genFocusOutline(token),
});
const getThemeStyle = (token: MenuToken, themeSuffix: string): CSSInterpolation => {
const {
componentCls,
colorItemText,
colorItemTextSelected,
colorGroupTitle,
colorItemBg,
colorSubItemBg,
colorItemBgSelected,
colorActiveBarHeight,
colorActiveBarWidth,
colorActiveBarBorderSize,
motionDurationSlow,
motionEaseInOut,
motionEaseOut,
menuItemPaddingInline,
motionDurationMid,
colorItemTextHover,
lineType,
colorSplit,
// Disabled
colorItemTextDisabled,
// Danger
colorDangerItemText,
colorDangerItemTextHover,
colorDangerItemTextSelected,
colorDangerItemBgActive,
colorDangerItemBgSelected,
colorItemBgHover,
menuSubMenuBg,
// Horizontal
colorItemTextSelectedHorizontal,
colorItemBgSelectedHorizontal,
} = token;
return {
[`${componentCls}-${themeSuffix}`]: {
color: colorItemText,
background: colorItemBg,
[`&${componentCls}-root:focus-visible`]: {
...accessibilityFocus(token),
},
// ======================== Item ========================
[`${componentCls}-item-group-title`]: {
color: colorGroupTitle,
},
[`${componentCls}-submenu-selected`]: {
[`> ${componentCls}-submenu-title`]: {
color: colorItemTextSelected,
},
},
// Disabled
[`${componentCls}-item-disabled, ${componentCls}-submenu-disabled`]: {
color: `${colorItemTextDisabled} !important`,
},
// Hover
[`${componentCls}-item:hover, ${componentCls}-submenu-title:hover`]: {
[`&:not(${componentCls}-item-selected):not(${componentCls}-submenu-selected)`]: {
color: colorItemTextHover,
},
},
[`&:not(${componentCls}-horizontal)`]: {
[`${componentCls}-item:not(${componentCls}-item-selected)`]: {
'&:hover': {
backgroundColor: colorItemBgHover,
},
'&:active': {
backgroundColor: colorItemBgSelected,
},
},
[`${componentCls}-submenu-title`]: {
'&:hover': {
backgroundColor: colorItemBgHover,
},
'&:active': {
backgroundColor: colorItemBgSelected,
},
},
},
// Danger - only Item has
[`${componentCls}-item-danger`]: {
color: colorDangerItemText,
[`&${componentCls}-item:hover`]: {
[`&:not(${componentCls}-item-selected):not(${componentCls}-submenu-selected)`]: {
color: colorDangerItemTextHover,
},
},
[`&${componentCls}-item:active`]: {
background: colorDangerItemBgActive,
},
},
[`${componentCls}-item a`]: {
'&, &:hover': {
color: 'inherit',
},
},
[`${componentCls}-item-selected`]: {
color: colorItemTextSelected,
// Danger
[`&${componentCls}-item-danger`]: {
color: colorDangerItemTextSelected,
},
[`a, a:hover`]: {
color: 'inherit',
},
},
[`& ${componentCls}-item-selected`]: {
backgroundColor: colorItemBgSelected,
// Danger
[`&${componentCls}-item-danger`]: {
backgroundColor: colorDangerItemBgSelected,
},
},
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
[`&:not(${componentCls}-item-disabled):focus-visible`]: {
...accessibilityFocus(token),
},
},
[`&${componentCls}-submenu > ${componentCls}`]: {
backgroundColor: menuSubMenuBg,
},
[`&${componentCls}-popup > ${componentCls}`]: {
backgroundColor: colorItemBg,
},
// ====================== Horizontal ======================
[`&${componentCls}-horizontal`]: {
...(themeSuffix === 'dark'
? {
borderBottom: 0,
}
: {}),
[`> ${componentCls}-item, > ${componentCls}-submenu`]: {
top: colorActiveBarBorderSize,
marginTop: -colorActiveBarBorderSize,
marginBottom: 0,
borderRadius: 0,
'&::after': {
position: 'absolute',
insetInline: menuItemPaddingInline,
bottom: 0,
borderBottom: `${colorActiveBarHeight}px solid transparent`,
transition: `border-color ${motionDurationSlow} ${motionEaseInOut}`,
content: '""',
},
[`&:hover, &-active, &-open`]: {
'&::after': {
borderBottomWidth: colorActiveBarHeight,
borderBottomColor: colorItemTextSelectedHorizontal,
},
},
[`&-selected`]: {
color: colorItemTextSelectedHorizontal,
backgroundColor: colorItemBgSelectedHorizontal,
'&::after': {
borderBottomWidth: colorActiveBarHeight,
borderBottomColor: colorItemTextSelectedHorizontal,
},
},
},
},
// ================== Inline & Vertical ===================
//
[`&${componentCls}-root`]: {
[`&${componentCls}-inline, &${componentCls}-vertical`]: {
borderInlineEnd: `${colorActiveBarBorderSize}px ${lineType} ${colorSplit}`,
},
},
// ======================== Inline ========================
[`&${componentCls}-inline`]: {
// Sub
[`${componentCls}-sub${componentCls}-inline`]: {
background: colorSubItemBg,
},
// Item
[`${componentCls}-item, ${componentCls}-submenu-title`]:
colorActiveBarBorderSize && colorActiveBarWidth
? {
width: `calc(100% + ${colorActiveBarBorderSize}px)`,
}
: {},
[`${componentCls}-item`]: {
position: 'relative',
'&::after': {
position: 'absolute',
insetBlock: 0,
insetInlineEnd: 0,
borderInlineEnd: `${colorActiveBarWidth}px solid ${colorItemTextSelected}`,
transform: 'scaleY(0.0001)',
opacity: 0,
transition: [
`transform ${motionDurationMid} ${motionEaseOut}`,
`opacity ${motionDurationMid} ${motionEaseOut}`,
].join(','),
content: '""',
},
// Danger
[`&${componentCls}-item-danger`]: {
'&::after': {
borderInlineEndColor: colorDangerItemTextSelected,
},
},
},
[`${componentCls}-selected, ${componentCls}-item-selected`]: {
'&::after': {
transform: 'scaleY(1)',
opacity: 1,
transition: [
`transform ${motionDurationMid} ${motionEaseInOut}`,
`opacity ${motionDurationMid} ${motionEaseInOut}`,
].join(','),
},
},
},
},
};
};
export default getThemeStyle;

View File

@ -0,0 +1,231 @@
import type { CSSObject } from '../../_util/cssinjs';
import { textEllipsis } from '../../_style';
import type { MenuToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const getVerticalInlineStyle: GenerateStyle<MenuToken, CSSObject> = token => {
const {
componentCls,
menuItemHeight,
itemMarginInline,
padding,
menuArrowSize,
marginXS,
marginXXS,
} = token;
const paddingWithArrow = padding + menuArrowSize + marginXS;
return {
[`${componentCls}-item`]: {
position: 'relative',
},
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
height: menuItemHeight,
lineHeight: `${menuItemHeight}px`,
paddingInline: padding,
overflow: 'hidden',
textOverflow: 'ellipsis',
marginInline: itemMarginInline,
marginBlock: marginXXS,
width: `calc(100% - ${itemMarginInline * 2}px)`,
},
// disable margin collapsed
[`${componentCls}-submenu`]: {
paddingBottom: 0.02,
},
[`> ${componentCls}-item,
> ${componentCls}-submenu > ${componentCls}-submenu-title`]: {
height: menuItemHeight,
lineHeight: `${menuItemHeight}px`,
},
[`${componentCls}-item-group-list ${componentCls}-submenu-title,
${componentCls}-submenu-title`]: {
paddingInlineEnd: paddingWithArrow,
},
};
};
const getVerticalStyle: GenerateStyle<MenuToken> = token => {
const {
componentCls,
iconCls,
menuItemHeight,
colorTextLightSolid,
dropdownWidth,
controlHeightLG,
motionDurationMid,
motionEaseOut,
paddingXL,
fontSizeSM,
fontSizeLG,
motionDurationSlow,
paddingXS,
boxShadowSecondary,
} = token;
const inlineItemStyle: CSSObject = {
height: menuItemHeight,
lineHeight: `${menuItemHeight}px`,
listStylePosition: 'inside',
listStyleType: 'disc',
};
return [
{
[componentCls]: {
[`&-inline, &-vertical`]: {
[`&${componentCls}-root`]: {
boxShadow: 'none',
},
...getVerticalInlineStyle(token),
},
},
[`${componentCls}-submenu-popup`]: {
[`${componentCls}-vertical`]: {
...getVerticalInlineStyle(token),
boxShadow: boxShadowSecondary,
},
},
},
// Vertical only
{
[`${componentCls}-submenu-popup ${componentCls}-vertical${componentCls}-sub`]: {
minWidth: dropdownWidth,
maxHeight: `calc(100vh - ${controlHeightLG * 2.5}px)`,
padding: '0',
overflow: 'hidden',
borderInlineEnd: 0,
// https://github.com/ant-design/ant-design/issues/22244
// https://github.com/ant-design/ant-design/issues/26812
"&:not([class*='-active'])": {
overflowX: 'hidden',
overflowY: 'auto',
},
},
},
// Inline Only
{
[`${componentCls}-inline`]: {
width: '100%',
// Motion enhance for first level
[`&${componentCls}-root`]: {
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
display: 'flex',
alignItems: 'center',
transition: [
`border-color ${motionDurationSlow}`,
`background ${motionDurationSlow}`,
`padding ${motionDurationMid} ${motionEaseOut}`,
].join(','),
[`> ${componentCls}-title-content`]: {
flex: 'auto',
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
},
'> *': {
flex: 'none',
},
},
},
// >>>>> Sub
[`${componentCls}-sub${componentCls}-inline`]: {
padding: 0,
border: 0,
borderRadius: 0,
boxShadow: 'none',
[`& > ${componentCls}-submenu > ${componentCls}-submenu-title`]: inlineItemStyle,
[`& ${componentCls}-item-group-title`]: {
paddingInlineStart: paddingXL,
},
},
// >>>>> Item
[`${componentCls}-item`]: inlineItemStyle,
},
},
// Inline Collapse Only
{
[`${componentCls}-inline-collapsed`]: {
width: menuItemHeight * 2,
[`&${componentCls}-root`]: {
[`${componentCls}-item, ${componentCls}-submenu ${componentCls}-submenu-title`]: {
[`> ${componentCls}-inline-collapsed-noicon`]: {
fontSize: fontSizeLG,
textAlign: 'center',
},
},
},
[`> ${componentCls}-item,
> ${componentCls}-item-group > ${componentCls}-item-group-list > ${componentCls}-item,
> ${componentCls}-item-group > ${componentCls}-item-group-list > ${componentCls}-submenu > ${componentCls}-submenu-title,
> ${componentCls}-submenu > ${componentCls}-submenu-title`]: {
insetInlineStart: 0,
paddingInline: `calc(50% - ${fontSizeSM}px)`,
textOverflow: 'clip',
[`
${componentCls}-submenu-arrow,
${componentCls}-submenu-expand-icon
`]: {
opacity: 0,
},
[`${componentCls}-item-icon, ${iconCls}`]: {
margin: 0,
fontSize: fontSizeLG,
lineHeight: `${menuItemHeight}px`,
'+ span': {
display: 'inline-block',
opacity: 0,
},
},
},
[`${componentCls}-item-icon, ${iconCls}`]: {
display: 'inline-block',
},
'&-tooltip': {
pointerEvents: 'none',
[`${componentCls}-item-icon, ${iconCls}`]: {
display: 'none',
},
'a, a:hover': {
color: colorTextLightSolid,
},
},
[`${componentCls}-item-group-title`]: {
...textEllipsis,
paddingInline: paddingXS,
},
},
},
];
};
export default getVerticalStyle;

View File

@ -13,7 +13,7 @@ import './input/style';
import './tooltip/style';
import './popover/style';
import './popconfirm/style';
import './menu/style';
// import './menu/style';
import './mentions/style';
import './dropdown/style';
// import './divider/style';

View File

@ -20,7 +20,7 @@ import type { ComponentToken as DividerComponentToken } from '../../divider/styl
// import type { ComponentToken as LayoutComponentToken } from '../../layout/style';
// import type { ComponentToken as ListComponentToken } from '../../list/style';
// import type { ComponentToken as MentionsComponentToken } from '../../mentions/style';
// import type { ComponentToken as MenuComponentToken } from '../../menu/style';
import type { ComponentToken as MenuComponentToken } from '../../menu/style';
import type { ComponentToken as MessageComponentToken } from '../../message/style';
import type { ComponentToken as ModalComponentToken } from '../../modal/style';
import type { ComponentToken as NotificationComponentToken } from '../../notification/style';
@ -102,7 +102,7 @@ export interface ComponentTokenMap {
// Tabs?: TabsComponentToken;
// Calendar?: CalendarComponentToken;
// Steps?: StepsComponentToken;
// Menu?: MenuComponentToken;
Menu?: MenuComponentToken;
Modal?: ModalComponentToken;
Message?: MessageComponentToken;
// Upload?: UploadComponentToken;