From 2ab77978f2eb4ce634753e8b00fe5f24b5112ac4 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Sat, 15 May 2021 23:05:54 +0800 Subject: [PATCH] refactor: menu --- components/menu/src/Menu.tsx | 35 +++++++++-- components/menu/src/MenuItem.tsx | 66 ++++++++++++++++++++- components/menu/src/SubMenu.tsx | 2 + components/menu/src/hooks/useKeyPath.ts | 23 +++++++ components/menu/src/hooks/useMenuContext.ts | 15 ++--- components/menu/src/interface.ts | 2 + examples/App.vue | 1 + v2-doc | 2 +- 8 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 components/menu/src/hooks/useKeyPath.ts diff --git a/components/menu/src/Menu.tsx b/components/menu/src/Menu.tsx index 918adb0bc..cd2bf72be 100644 --- a/components/menu/src/Menu.tsx +++ b/components/menu/src/Menu.tsx @@ -1,9 +1,14 @@ -import usePrefixCls from 'ant-design-vue/es/_util/hooks/usePrefixCls'; -import { defineComponent, ExtractPropTypes } from 'vue'; +import { Key } from '../../_util/type'; +import { computed, defineComponent, ExtractPropTypes, ref, PropType } from 'vue'; import useProvideMenu from './hooks/useMenuContext'; +import useConfigInject from '../../_util/hooks/useConfigInject'; +import { MenuTheme, MenuMode } from './interface'; export const menuProps = { prefixCls: String, + disabled: Boolean, + theme: { type: String as PropType, default: 'light' }, + mode: { type: String as PropType, default: 'vertical' }, }; export type MenuProps = Partial>; @@ -12,10 +17,30 @@ export default defineComponent({ name: 'AMenu', props: menuProps, setup(props, { slots }) { - const prefixCls = usePrefixCls('menu', props); - useProvideMenu({ prefixCls }); + const { prefixCls, direction } = useConfigInject('menu', props); + const activeKeys = ref([]); + const openKeys = ref([]); + const selectedKeys = ref([]); + const changeActiveKeys = (keys: Key[]) => { + activeKeys.value = keys; + }; + const disabled = computed(() => !!props.disabled); + useProvideMenu({ prefixCls, activeKeys, openKeys, selectedKeys, changeActiveKeys, disabled }); + const isRtl = computed(() => direction.value === 'rtl'); + const mergedMode = ref('vertical'); + const mergedInlineCollapsed = ref(false); + const className = computed(() => { + return { + [`${prefixCls.value}`]: true, + [`${prefixCls.value}-root`]: true, + [`${prefixCls.value}-${mergedMode.value}`]: true, + [`${prefixCls.value}-inline-collapsed`]: mergedInlineCollapsed.value, + [`${prefixCls.value}-rtl`]: isRtl.value, + [`${prefixCls.value}-${props.theme}`]: true, + }; + }); return () => { - return
{slots.default?.()}
; + return
    {slots.default?.()}
; }; }, }); diff --git a/components/menu/src/MenuItem.tsx b/components/menu/src/MenuItem.tsx index ecdb8916c..415b32b1d 100644 --- a/components/menu/src/MenuItem.tsx +++ b/components/menu/src/MenuItem.tsx @@ -1,16 +1,76 @@ -import { defineComponent, getCurrentInstance } from 'vue'; +import { computed, defineComponent, getCurrentInstance, ref, watch } from 'vue'; +import { useInjectKeyPath } from './hooks/useKeyPath'; +import { useInjectMenu } from './hooks/useMenuContext'; let indexGuid = 0; export default defineComponent({ name: 'AMenuItem', - setup(props, { slots }) { + props: { + role: String, + disabled: Boolean, + }, + emits: ['mouseenter', 'mouseleave'], + setup(props, { slots, emit }) { const instance = getCurrentInstance(); const key = instance.vnode.key; const uniKey = `menu_item_${++indexGuid}`; + const parentKeys = useInjectKeyPath(); + console.log(parentKeys.value); + const { prefixCls, activeKeys, disabled, changeActiveKeys } = useInjectMenu(); + const isActive = ref(false); + watch( + activeKeys, + () => { + isActive.value = !!activeKeys.value.find(val => val === key); + }, + { immediate: true }, + ); + const mergedDisabled = computed(() => disabled.value || props.disabled); + const selected = computed(() => false); + const classNames = computed(() => { + const itemCls = `${prefixCls.value}-item`; + return { + [`${itemCls}`]: true, + [`${itemCls}-active`]: isActive.value, + [`${itemCls}-selected`]: selected.value, + [`${itemCls}-disabled`]: mergedDisabled.value, + }; + }); + const onMouseEnter = (event: MouseEvent) => { + if (!mergedDisabled.value) { + changeActiveKeys([...parentKeys.value, key]); + emit('mouseenter', event); + } + }; + const onMouseLeave = (event: MouseEvent) => { + if (!mergedDisabled.value) { + changeActiveKeys([]); + emit('mouseleave', event); + } + }; return () => { - return
  • {slots.default?.()}
  • ; + // ============================ Render ============================ + const optionRoleProps = {}; + + if (props.role === 'option') { + optionRoleProps['aria-selected'] = selected.value; + } + return ( +
  • + {slots.default?.()} +
  • + ); }; }, }); diff --git a/components/menu/src/SubMenu.tsx b/components/menu/src/SubMenu.tsx index b88004338..51e186a94 100644 --- a/components/menu/src/SubMenu.tsx +++ b/components/menu/src/SubMenu.tsx @@ -1,8 +1,10 @@ import { defineComponent } from 'vue'; +import useProvideKeyPath from './hooks/useKeyPath'; export default defineComponent({ name: 'ASubMenu', setup(props, { slots }) { + useProvideKeyPath(); return () => { return
      {slots.default?.()}
    ; }; diff --git a/components/menu/src/hooks/useKeyPath.ts b/components/menu/src/hooks/useKeyPath.ts new file mode 100644 index 000000000..a9ecfcee7 --- /dev/null +++ b/components/menu/src/hooks/useKeyPath.ts @@ -0,0 +1,23 @@ +import { Key } from '../../../_util/type'; +import { computed, ComputedRef, getCurrentInstance, inject, InjectionKey, provide } from 'vue'; + +const KeyPathContext: InjectionKey> = Symbol('KeyPathContext'); + +const useInjectKeyPath = () => { + return inject( + KeyPathContext, + computed(() => []), + ); +}; + +const useProvideKeyPath = () => { + const parentKeys = useInjectKeyPath(); + const key = getCurrentInstance().vnode.key; + const keys = computed(() => [...parentKeys.value, key]); + provide(KeyPathContext, keys); + return keys; +}; + +export { useProvideKeyPath, useInjectKeyPath, KeyPathContext }; + +export default useProvideKeyPath; diff --git a/components/menu/src/hooks/useMenuContext.ts b/components/menu/src/hooks/useMenuContext.ts index 0864a82d8..76c959631 100644 --- a/components/menu/src/hooks/useMenuContext.ts +++ b/components/menu/src/hooks/useMenuContext.ts @@ -1,4 +1,5 @@ -import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue'; +import { Key } from '../../../_util/type'; +import { ComputedRef, inject, InjectionKey, provide, Ref } from 'vue'; // import { // BuiltinPlacements, @@ -10,19 +11,21 @@ import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue'; export interface MenuContextProps { prefixCls: ComputedRef; - // openKeys: string[]; + openKeys: Ref; + selectedKeys: Ref; // rtl?: boolean; // // Mode // mode: MenuMode; // // Disabled - // disabled?: boolean; + disabled?: ComputedRef; // // Used for overflow only. Prevent hidden node trigger open // overflowDisabled?: boolean; // // Active - // activeKey: string; + activeKeys: Ref; + changeActiveKeys: (keys: Key[]) => void; // onActive: (key: string) => void; // onInactive: (key: string) => void; @@ -60,9 +63,7 @@ const useProvideMenu = (props: MenuContextProps) => { }; const useInjectMenu = () => { - return inject(MenuContextKey, { - prefixCls: computed(() => 'ant'), - }); + return inject(MenuContextKey); }; export { useProvideMenu, MenuContextKey, useInjectMenu }; diff --git a/components/menu/src/interface.ts b/components/menu/src/interface.ts index e1db27739..d0f9c377d 100644 --- a/components/menu/src/interface.ts +++ b/components/menu/src/interface.ts @@ -1,3 +1,5 @@ +export type MenuTheme = 'light' | 'dark'; + // ========================== Basic ========================== export type MenuMode = 'horizontal' | 'vertical' | 'inline'; diff --git a/examples/App.vue b/examples/App.vue index e84532658..3caf71cb9 100644 --- a/examples/App.vue +++ b/examples/App.vue @@ -7,6 +7,7 @@ mode="inline" @click="handleClick" > + Option 0