Skip to content

Commit

Permalink
refactor(components): refactor menu (element-plus#3639)
Browse files Browse the repository at this point in the history
* refactor(menu): rename to kebab-case

* refactor(menu): rename RootMenuProvider to MenuProvider

* refactor(menu): rename menu.type to types

* refactor(menu): extract menu props and emits

* refactor(menu): change sub-menu.vue to ts

* refactor(menu): extract menu-item-group props and emits

* refactor(menu): extract menu-item props and emits

* refactor(menu): extract sub-menu props and emits

* refactor(menu): rename type RegisterMenuItem to MenuItemRegistered

* refactor(menu): MenuProvider ref to reactive

* refactor(menu): MenuProvider remove methods

* refactor(menu): change submenus to subMenus

* refactor(menu): remove RootMenuProps type

* refactor(menu): MenuProvider improve types & rename submenu to subMenu

* refactor(menu): menu add block to provide

* refactor(menu): menu improve expose

* refactor(menu): menu improve render types

* refactor(menu): menu refactor types & change handle(Sub)MenuItemClick params

* refactor(menu): menu refactor types

* refactor(menu): menu-item-group improve types

* refactor(menu): menu-item improve types

* refactor(menu): sub-menu improve types

* refactor(menu): use-menu improve types

* refactor(menu): sub-menu fix types

* refactor(menu): menu-collapse-transition improve types

* refactor(menu): menu-item-group improve template

* refactor(menu): menu-item rename emit param

* refactor(menu): finally improve types

* fix lint

* chore: re-order import

* chore: remove reactive
  • Loading branch information
sxzz authored Sep 28, 2021
1 parent 8e29813 commit c68d59c
Show file tree
Hide file tree
Showing 17 changed files with 939 additions and 1,014 deletions.
14 changes: 8 additions & 6 deletions packages/components/menu/__tests__/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { sleep } from '@element-plus/test-utils'
import { rAF } from '@element-plus/test-utils/tick'

import Menu from '../src/menu'
import MenuGroup from '../src/menuItemGroup.vue'
import MenuItem from '../src/menuItem.vue'
import SubMenu from '../src/submenu.vue'
import MenuGroup from '../src/menu-item-group.vue'
import MenuItem from '../src/menu-item.vue'
import SubMenu from '../src/sub-menu'

const _mount = (template: string, options = {}) =>
mount({
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('menu', () => {
default-active="2"
class="el-menu-vertical-demo"
>
<el-sub-menu index="1">
<el-sub-menu index="1" ref="subMenu">
<template #title>
<i class="el-icon-location"></i>
<span>导航一</span>
Expand Down Expand Up @@ -153,11 +153,13 @@ describe('menu', () => {
},
}
)
const elSubMenu = wrapper.findComponent({ name: 'ElSubMenu' })
const elSubMenu = wrapper.findComponent({ ref: 'subMenu' })
const instance: any = elSubMenu.vm

const button = wrapper.find('button')
button.trigger('click')

await nextTick()
const instance = elSubMenu.vm as any
expect(instance.opened).toBeTruthy()
})

Expand Down
12 changes: 8 additions & 4 deletions packages/components/menu/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { withInstall, withNoopInstall } from '@element-plus/utils/with-install'

import Menu from './src/menu'
import MenuItem from './src/menuItem.vue'
import MenuItemGroup from './src/menuItemGroup.vue'
import SubMenu from './src/submenu.vue'
import MenuItem from './src/menu-item.vue'
import MenuItemGroup from './src/menu-item-group.vue'
import SubMenu from './src/sub-menu'

export const ElMenu = withInstall(Menu, {
MenuItem,
Expand All @@ -15,4 +15,8 @@ export const ElMenuItem = withNoopInstall(MenuItem)
export const ElMenuItemGroup = withNoopInstall(MenuItemGroup)
export const ElSubMenu = withNoopInstall(SubMenu)

export * from './src/menu.type'
export * from './src/menu'
export * from './src/menu-item'
export * from './src/menu-item-group'
export * from './src/sub-menu'
export * from './src/types'
89 changes: 45 additions & 44 deletions packages/components/menu/src/menu-collapse-transition.vue
Original file line number Diff line number Diff line change
@@ -1,58 +1,59 @@
<template>
<transition mode="out-in" v-on="on">
<slot></slot>
<transition mode="out-in" v-bind="listeners">
<slot />
</transition>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { addClass, removeClass, hasClass } from '@element-plus/utils/dom'
import type { TransitionProps, BaseTransitionProps } from 'vue'
export default defineComponent({
name: 'ElMenuCollapseTransition',
setup() {
return {
on: {
beforeEnter(el: HTMLElement) {
el.style.opacity = '0.2'
},
enter(el: HTMLElement, done) {
addClass(el, 'el-opacity-transition')
el.style.opacity = '1'
done()
},
afterEnter(el: HTMLElement) {
removeClass(el, 'el-opacity-transition')
el.style.opacity = ''
},
beforeLeave(el: HTMLElement) {
if (!el.dataset) {
;(el as any).dataset = {}
}
if (hasClass(el, 'el-menu--collapse')) {
removeClass(el, 'el-menu--collapse')
el.dataset.oldOverflow = el.style.overflow
el.dataset.scrollWidth = el.clientWidth.toString()
addClass(el, 'el-menu--collapse')
} else {
addClass(el, 'el-menu--collapse')
el.dataset.oldOverflow = el.style.overflow
el.dataset.scrollWidth = el.clientWidth.toString()
removeClass(el, 'el-menu--collapse')
}
el.style.width = `${el.scrollWidth}px`
el.style.overflow = 'hidden'
},
leave(el: HTMLElement) {
addClass(el, 'horizontal-collapse-transition')
el.style.width = `${el.dataset.scrollWidth}px`
},
const listeners = {
onBeforeEnter: (el) => (el.style.opacity = '0.2'),
onEnter(el, done) {
addClass(el, 'el-opacity-transition')
el.style.opacity = '1'
done()
},
onAfterEnter(el) {
removeClass(el, 'el-opacity-transition')
el.style.opacity = ''
},
onBeforeLeave(el) {
if (!el.dataset) {
;(el as any).dataset = {}
}
if (hasClass(el, 'el-menu--collapse')) {
removeClass(el, 'el-menu--collapse')
el.dataset.oldOverflow = el.style.overflow
el.dataset.scrollWidth = el.clientWidth.toString()
addClass(el, 'el-menu--collapse')
} else {
addClass(el, 'el-menu--collapse')
el.dataset.oldOverflow = el.style.overflow
el.dataset.scrollWidth = el.clientWidth.toString()
removeClass(el, 'el-menu--collapse')
}
el.style.width = `${el.scrollWidth}px`
el.style.overflow = 'hidden'
},
onLeave(el: HTMLElement) {
addClass(el, 'horizontal-collapse-transition')
el.style.width = `${el.dataset.scrollWidth}px`
},
} as BaseTransitionProps<HTMLElement> as TransitionProps
return {
listeners,
}
},
})
Expand Down
6 changes: 6 additions & 0 deletions packages/components/menu/src/menu-item-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ExtractPropTypes } from 'vue'

export const menuItemGroupProps = {
title: String,
} as const
export type MenuItemGroupProps = ExtractPropTypes<typeof menuItemGroupProps>
53 changes: 53 additions & 0 deletions packages/components/menu/src/menu-item-group.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<li class="el-menu-item-group">
<div
class="el-menu-item-group__title"
:style="{ paddingLeft: `${levelPadding}px` }"
>
<template v-if="!$slots.title">{{ title }}</template>
<slot v-else name="title" />
</div>
<ul>
<slot />
</ul>
</li>
</template>

<script lang="ts">
import { defineComponent, computed, getCurrentInstance, inject } from 'vue'
import { throwError } from '@element-plus/utils/error'
import { menuItemGroupProps } from './menu-item-group'
import type { MenuProvider } from './types'
const COMPONENT_NAME = 'ElMenuItemGroup'
export default defineComponent({
name: COMPONENT_NAME,
props: menuItemGroupProps,
setup() {
const instance = getCurrentInstance()!
const menu = inject<MenuProvider>('rootMenu')
if (!menu) throwError(COMPONENT_NAME, 'can not inject root menu')
const levelPadding = computed(() => {
if (menu.props.collapse) return 20
let padding = 20
let parent = instance.parent
while (parent && parent.type.name !== 'ElMenu') {
if (parent.type.name === 'ElSubMenu') {
padding += 20
}
parent = parent.parent
}
return padding
})
return {
levelPadding,
}
},
})
</script>
24 changes: 24 additions & 0 deletions packages/components/menu/src/menu-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { buildProp, definePropType } from '@element-plus/utils/props'
import { isString } from '@element-plus/utils/util'

import type { ExtractPropTypes } from 'vue'
import type { RouteLocationRaw } from 'vue-router'
import type { MenuItemRegistered } from './types'

export const menuItemProps = {
index: {
type: String,
default: null,
},
route: buildProp({
type: definePropType<RouteLocationRaw>([String, Object]),
}),
disabled: Boolean,
} as const
export type MenuItemProps = ExtractPropTypes<typeof menuItemProps>

export const menuItemEmits = {
click: (item: MenuItemRegistered) =>
isString(item.index) && Array.isArray(item.indexPath),
}
export type MenuItemEmits = typeof menuItemEmits
128 changes: 128 additions & 0 deletions packages/components/menu/src/menu-item.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<template>
<li
class="el-menu-item"
role="menuitem"
tabindex="-1"
:style="paddingStyle"
:class="{
'is-active': active,
'is-disabled': disabled,
}"
@click="handleClick"
>
<el-tooltip
v-if="
parentMenu.type.name === 'ElMenu' &&
rootMenu.props.collapse &&
$slots.title
"
:effect="Effect.DARK"
placement="right"
>
<template #content>
<slot name="title" />
</template>
<div
:style="{
position: 'absolute',
left: 0,
top: 0,
height: '100%',
width: '100%',
display: 'inline-block',
boxSizing: 'border-box',
padding: '0 20px',
}"
>
<slot />
</div>
</el-tooltip>
<template v-else>
<slot />
<slot name="title" />
</template>
</li>
</template>

<script lang="ts">
import {
defineComponent,
computed,
onMounted,
onBeforeUnmount,
inject,
getCurrentInstance,
toRef,
reactive,
} from 'vue'
import ElTooltip from '@element-plus/components/tooltip'
import { Effect } from '@element-plus/components/popper'
import { throwError } from '@element-plus/utils/error'
import useMenu from './use-menu'
import { menuItemEmits, menuItemProps } from './menu-item'
import type { MenuItemRegistered, MenuProvider, SubMenuProvider } from './types'
const COMPONENT_NAME = 'ElMenuItem'
export default defineComponent({
name: COMPONENT_NAME,
components: {
ElTooltip,
},
props: menuItemProps,
emits: menuItemEmits,
setup(props, { emit }) {
const instance = getCurrentInstance()!
const rootMenu = inject<MenuProvider>('rootMenu')
if (!rootMenu) throwError(COMPONENT_NAME, 'can not inject root menu')
const { parentMenu, paddingStyle, indexPath } = useMenu(
instance,
toRef(props, 'index')
)
const subMenu = inject<SubMenuProvider>(`subMenu:${parentMenu.value.uid}`)
if (!subMenu) throwError(COMPONENT_NAME, 'can not inject sub menu')
const active = computed(() => props.index === rootMenu.activeIndex)
const item: MenuItemRegistered = reactive({
index: props.index,
indexPath,
active,
})
const handleClick = () => {
if (!props.disabled) {
rootMenu.handleMenuItemClick({
index: props.index,
indexPath: indexPath.value,
route: props.route,
})
emit('click', item)
}
}
onMounted(() => {
subMenu.addSubMenu(item)
rootMenu.addMenuItem(item)
})
onBeforeUnmount(() => {
subMenu.removeSubMenu(item)
rootMenu.removeMenuItem(item)
})
return {
Effect,
parentMenu,
rootMenu,
paddingStyle,
active,
handleClick,
}
},
})
</script>
Loading

0 comments on commit c68d59c

Please sign in to comment.