feat: menu divider add dashed

feat-css-var
tangjinzhou 2022-03-02 11:42:31 +08:00
parent c341258241
commit 32fc4fc7c4
12 changed files with 118 additions and 30 deletions

View File

@ -19,14 +19,16 @@ const collapseMotion = (name = 'ant-motion-collapse'): CSSMotionProps => {
});
},
onAfterEnter: (node: HTMLDivElement) => {
if (node) removeClass(node, name);
node.style.height = undefined;
node.style.opacity = undefined;
if (node) {
removeClass(node, name);
node.style.height = null;
node.style.opacity = null;
}
},
onBeforeLeave: (node: HTMLDivElement) => {
addClass(node, name);
node.style.height = `${node.offsetHeight}px`;
node.style.opacity = undefined;
node.style.opacity = null;
},
onLeave: (node: HTMLDivElement) => {
setTimeout(() => {
@ -35,9 +37,13 @@ const collapseMotion = (name = 'ant-motion-collapse'): CSSMotionProps => {
});
},
onAfterLeave: (node: HTMLDivElement) => {
if (node) removeClass(node, name);
node.style.height = undefined;
node.style.opacity = undefined;
if (node) {
removeClass(node, name);
if (node.style) {
node.style.height = null;
node.style.opacity = null;
}
}
},
};
};

View File

@ -10,10 +10,16 @@ A versatile menu for navigation.
## When To Use
Navigation is an important part of any website, as a good navigation setup allows users to move around the site quickly and efficiently. Ant Design offers top and side navigation options. Top navigation provides all the categories and functions of the website. Side navigation provides the multi-level structure of the website.
Navigation is an important part of any website, as a good navigation setup allows users to move around the site quickly and efficiently. Ant Design offers two navigation options: top and side. Top navigation provides all the categories and functions of the website. Side navigation provides the multi-level structure of the website.
More layouts with navigation: [Layout](/components/layout).
## Notes for developers
- Menu is rendered as a `ul` element, so it only supports [`li` and `script-supporting` elements](https://html.spec.whatwg.org/multipage/grouping-content.html#the-ul-element) as children nodes。Your customized node should be wrapped by `Menu.Item`.
- Menu needs to collect its node structure, so its children should be `Menu.*` or encapsulated HOCs.
- Must set unique key for `SubMenu`
## API
```html
@ -95,3 +101,13 @@ The children of Menu.ItemGroup must be `MenuItem`.
### Menu.Divider
Divider line in between menu items, only used in vertical popup Menu or Dropdown Menu.
| Param | Description | Type | Default value | Version |
| ------ | ---------------------- | ------- | ------------- | ------- |
| dashed | Whether line is dashed | boolean | false | 4.17.0 |
## FAQ
### Why will Menu's children be rendered twice?
Menu collects structure info with twice-render to support HOC usage. Merging into one render may cause the logic to become much more complex. Contributions to help improve the collection logic are welcomed.

View File

@ -15,6 +15,12 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
更多布局和导航的使用可以参考:[通用布局](/components/layout-cn)。
## 开发者注意事项
- Menu 元素为 `ul`,因而仅支持 [`li` 以及 `script-supporting` 子元素](https://html.spec.whatwg.org/multipage/grouping-content.html#the-ul-element)。因而你的子节点元素应该都在 `Menu.Item` 内使用。
- Menu 需要计算节点结构,因而其子元素仅支持 `Menu.*` 以及对此进行封装的 HOC 组件。
- 必须为 SubMenu 设置唯一 key
## API
```html
@ -96,3 +102,13 @@ Menu.ItemGroup 的子元素必须是 `MenuItem`.
### Menu.Divider
菜单项分割线,只用在弹出菜单内。
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ------ | -------- | ------- | ------ | ---- |
| dashed | 是否虚线 | boolean | false | 3.0 |
## FAQ
### 为何 Menu 的子元素会渲染两次?
Menu 通过二次渲染收集嵌套结构信息以支持 HOC 的结构。合并成一个推导结构会使得逻辑变得十分复杂,欢迎 PR 以协助改进该设计。

View File

@ -1,12 +1,22 @@
import { defineComponent } from 'vue';
import { useInjectMenu } from './hooks/useMenuContext';
import useConfigInject from '../../_util/hooks/useConfigInject';
import { computed, defineComponent } from 'vue';
export default defineComponent({
name: 'AMenuDivider',
setup() {
const { prefixCls } = useInjectMenu();
props: {
prefixCls: String,
dashed: Boolean,
},
setup(props) {
const { prefixCls } = useConfigInject('menu', props);
const cls = computed(() => {
return {
[`${prefixCls.value}-item-divider`]: true,
[`${prefixCls.value}-item-divider-dashed`]: !!props.dashed,
};
});
return () => {
return <li class={`${prefixCls.value}-item-divider`} />;
return <li class={cls.value} />;
};
},
});

View File

@ -17,7 +17,6 @@ import type {
} from './interface';
import devWarning from '../../vc-util/devWarning';
import type { CSSMotionProps } from '../../_util/transition';
import { collapseMotion } from '../../_util/transition';
import uniq from 'lodash-es/uniq';
import { SiderCollapsedKey } from '../../layout/injectionKey';
import { flattenChildren } from '../../_util/props-util';
@ -28,6 +27,7 @@ import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
import { cloneElement } from '../../_util/vnode';
import { OVERFLOW_KEY, PathContext } from './hooks/useKeyPath';
import type { FocusEventHandler, MouseEventHandler } from '../../_util/EventInterface';
import collapseMotion from '../../_util/collapseMotion';
export const menuProps = {
id: String,

View File

@ -71,11 +71,9 @@ export default defineComponent({
const onVisibleChange = (visible: boolean) => {
emit('visibleChange', visible);
};
const style = ref({});
const className = ref('');
const mergedMotion = computed(() => {
const m = motion.value || defaultMotions.value?.[mode.value] || defaultMotions.value?.other;
const res = typeof m === 'function' ? m(undefined, style, className) : m;
const res = typeof m === 'function' ? m() : m;
return res ? getTransitionProps(res.name, { css: true }) : undefined;
});
return () => {

View File

@ -1,5 +1,5 @@
import type { Key } from '../../../_util/type';
import type { ComputedRef, CSSProperties, InjectionKey, PropType, Ref, UnwrapRef } from 'vue';
import type { ComputedRef, InjectionKey, PropType, Ref, UnwrapRef } from 'vue';
import { defineComponent, inject, provide, toRef } from 'vue';
import type {
BuiltinPlacements,
@ -59,9 +59,7 @@ export interface MenuContextProps {
// // Motion
motion?: ComputedRef<CSSMotionProps | null>;
defaultMotions?: ComputedRef<Partial<{
[key in MenuMode | 'other']:
| CSSMotionProps
| ((name: string, style: Ref<CSSProperties>, className: Ref<string>) => CSSMotionProps);
[key in MenuMode | 'other']: CSSMotionProps | ((name?: string) => CSSMotionProps);
}> | null>;
// // Popup

View File

@ -8,6 +8,7 @@
.@{menu-prefix-cls}-submenu-title .@{menu-prefix-cls}-submenu-arrow {
opacity: 0.45;
transition: all 0.3s;
&::after,
&::before {
background: @menu-dark-arrow-color;
@ -65,6 +66,7 @@
left: 0;
margin-left: 0;
border-right: 0;
&::after {
border-right: 0;
}
@ -83,6 +85,7 @@
&-dark &-submenu-title:hover {
color: @menu-dark-highlight-color;
background-color: transparent;
> a,
> span > a {
color: @menu-dark-highlight-color;
@ -90,6 +93,7 @@
> .@{menu-prefix-cls}-submenu-title {
> .@{menu-prefix-cls}-submenu-arrow {
opacity: 1;
&::after,
&::before {
background: @menu-dark-highlight-color;
@ -97,6 +101,7 @@
}
}
}
&-dark &-item:hover {
background-color: @menu-dark-item-hover-bg;
}
@ -108,9 +113,11 @@
&-dark &-item-selected {
color: @menu-dark-highlight-color;
border-right: 0;
&::after {
border-right: 0;
}
> a,
> span > a,
> a:hover,

View File

@ -6,7 +6,7 @@
@menu-animation-duration-normal: 0.15s;
.accessibility-focus() {
box-shadow: 0 0 0 2px fade(@primary-color, 20%);
box-shadow: 0 0 0 2px @primary-1;
}
// TODO: Should remove icon style compatible in v5
@ -67,6 +67,7 @@
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,
@ -89,11 +90,17 @@
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;
@ -108,16 +115,22 @@
// 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 {
height: 1px;
overflow: hidden;
line-height: 0;
background-color: @border-color-split;
border-color: @border-color-split;
border-style: solid;
border-width: 1px 0 0;
}
&-item-divider-dashed {
border-style: dashed;
}
&-horizontal &-item,
@ -133,6 +146,7 @@
&-item-selected {
color: @menu-highlight-color;
a,
a:hover {
color: @menu-highlight-color;
@ -173,6 +187,7 @@
left: 0;
margin-left: 0;
border-right: 0;
&::after {
border-right: 0;
}
@ -209,6 +224,7 @@
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;
@ -234,12 +250,8 @@
}
& > &-item-divider {
height: 1px;
margin: 1px 0;
padding: 0;
overflow: hidden;
line-height: 0;
background-color: @border-color-split;
}
&-submenu {
@ -275,6 +287,7 @@
> .@{menu-prefix-cls} {
background-color: @menu-bg;
border-radius: @border-radius-base;
&-submenu-title::after {
transition: transform @animation-duration-slow @ease-in-out;
}
@ -309,9 +322,11 @@
color @animation-duration-slow @ease-in-out;
content: '';
}
&::before {
transform: rotate(45deg) translateY(-2.5px);
}
&::after {
transform: rotate(-45deg) translateY(2.5px);
}
@ -328,6 +343,7 @@
&::before {
transform: rotate(-45deg) translateX(2.5px);
}
&::after {
transform: rotate(45deg) translateX(-2.5px);
}
@ -340,9 +356,11 @@
&-open&-inline > &-title > &-arrow {
// ↑
transform: translateY(-2px);
&::after {
transform: rotate(-45deg) translateX(-2.5px);
}
&::before {
transform: rotate(45deg) translateX(2.5px);
}
@ -406,13 +424,16 @@
> .@{menu-prefix-cls}-item {
a {
color: @menu-item-color;
&:hover {
color: @menu-highlight-color;
}
&::before {
bottom: -2px;
}
}
&-selected a {
color: @menu-highlight-color;
}
@ -432,6 +453,7 @@
&-inline {
.@{menu-prefix-cls}-item {
position: relative;
&::after {
position: absolute;
top: 0;
@ -550,6 +572,7 @@
margin: 0;
font-size: @menu-icon-size-lg;
line-height: @menu-item-height;
+ span {
display: inline-block;
opacity: 0;
@ -569,6 +592,7 @@
.@{iconfont-css-prefix} {
display: none;
}
a {
color: @text-color-dark;
}
@ -663,6 +687,14 @@
}
}
// 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

@ -87,6 +87,7 @@
transform: rotate(-45deg) translateY(-2px);
}
}
&::after {
.@{menu-prefix-cls}-rtl & {
transform: rotate(45deg) translateY(2px);

View File

@ -1,4 +1,5 @@
@import './index';
@import (reference) '../../style/themes/index';
@menu-prefix-cls: ~'@{ant-prefix}-menu';
.@{menu-prefix-cls} {
// Danger
@ -16,6 +17,7 @@
&-selected {
color: @menu-highlight-danger-color;
> a,
> a:hover {
color: @menu-highlight-danger-color;

View File

@ -101,7 +101,9 @@ export default defineComponent({
const motion = computed(() => {
const m = typeof props.animation === 'object' ? props.animation : getMotion(props as any);
['onAfterEnter', 'onAfterLeave'].forEach(eventName => {
m[eventName] = () => {
const originFn = m[eventName];
m[eventName] = node => {
originFn?.(node);
goNextStatus();
};
});