feat: user-dropdown support `hover` trigger (#5143)

* feat: user-dropdown support `hover` trigger

* fix: modified type declaration
dev-v5
Netfan 2024-12-15 18:24:22 +08:00 committed by GitHub
parent 7581fb381f
commit f446cbf9e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 160 additions and 62 deletions

View File

@ -25,6 +25,7 @@
"@vben/stores": "workspace:*", "@vben/stores": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"vue": "catalog:", "vue": "catalog:",
"vue-router": "catalog:", "vue-router": "catalog:",
"watermark-js-plus": "catalog:" "watermark-js-plus": "catalog:"

View File

@ -1,6 +1,7 @@
export * from './use-app-config'; export * from './use-app-config';
export * from './use-content-maximize'; export * from './use-content-maximize';
export * from './use-design-tokens'; export * from './use-design-tokens';
export * from './use-hover-toggle';
export * from './use-pagination'; export * from './use-pagination';
export * from './use-refresh'; export * from './use-refresh';
export * from './use-tabs'; export * from './use-tabs';

View File

@ -0,0 +1,63 @@
import type { Arrayable, MaybeElementRef } from '@vueuse/core';
import { computed, onUnmounted, ref, watch } from 'vue';
import type { Ref } from 'vue';
import { isFunction } from '@vben/utils';
import { useMouseInElement } from '@vueuse/core';
/**
* true false
* @param refElement true
* @param delay
* @returns ref enable disable
*/
export function useHoverToggle(
refElement: Arrayable<MaybeElementRef>,
delay: (() => number) | number = 500,
) {
const isOutsides: Array<Ref<boolean>> = [];
const value = ref(false);
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
const refs = Array.isArray(refElement) ? refElement : [refElement];
refs.forEach((refEle) => {
const listener = useMouseInElement(refEle, { handleOutside: true });
isOutsides.push(listener.isOutside);
});
const isOutsideAll = computed(() => isOutsides.every((v) => v.value));
function setValueDelay(val: boolean) {
timer.value && clearTimeout(timer.value);
timer.value = setTimeout(
() => {
value.value = val;
timer.value = undefined;
},
isFunction(delay) ? delay() : delay,
);
}
const watcher = watch(
isOutsideAll,
(val) => {
setValueDelay(!val);
},
{ immediate: true },
);
const controller = {
enable() {
watcher.resume();
},
disable() {
watcher.pause();
},
};
onUnmounted(() => {
timer.value && clearTimeout(timer.value);
});
return [value, controller] as [typeof value, typeof controller];
}

View File

@ -2,8 +2,9 @@
import type { AnyFunction } from '@vben/types'; import type { AnyFunction } from '@vben/types';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { computed, ref } from 'vue'; import { computed, useTemplateRef, watch } from 'vue';
import { useHoverToggle } from '@vben/hooks';
import { LockKeyhole, LogOut } from '@vben/icons'; import { LockKeyhole, LogOut } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { preferences, usePreferences } from '@vben/preferences'; import { preferences, usePreferences } from '@vben/preferences';
@ -53,6 +54,10 @@ interface Props {
* 文本 * 文本
*/ */
text?: string; text?: string;
/** 触发方式 */
trigger?: 'both' | 'click' | 'hover';
/** hover触发时延迟响应的时间 */
hoverDelay?: number;
} }
defineOptions({ defineOptions({
@ -67,10 +72,11 @@ const props = withDefaults(defineProps<Props>(), {
showShortcutKey: true, showShortcutKey: true,
tagText: '', tagText: '',
text: '', text: '',
trigger: 'click',
hoverDelay: 500,
}); });
const emit = defineEmits<{ logout: [] }>(); const emit = defineEmits<{ logout: [] }>();
const openPopover = ref(false);
const { globalLockScreenShortcutKey, globalLogoutShortcutKey } = const { globalLockScreenShortcutKey, globalLogoutShortcutKey } =
usePreferences(); usePreferences();
@ -84,6 +90,27 @@ const [LogoutModal, logoutModalApi] = useVbenModal({
}, },
}); });
const refTrigger = useTemplateRef('refTrigger');
const refContent = useTemplateRef('refContent');
const [openPopover, hoverWatcher] = useHoverToggle(
[refTrigger, refContent],
() => props.hoverDelay,
);
watch(
() => props.trigger === 'hover' || props.trigger === 'both',
(val) => {
if (val) {
hoverWatcher.enable();
} else {
hoverWatcher.disable();
}
},
{
immediate: true,
},
);
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥')); const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
const enableLogoutShortcutKey = computed(() => { const enableLogoutShortcutKey = computed(() => {
@ -155,8 +182,8 @@ if (enableShortcutKey.value) {
{{ $t('ui.widgets.logoutTip') }} {{ $t('ui.widgets.logoutTip') }}
</LogoutModal> </LogoutModal>
<DropdownMenu> <DropdownMenu v-model:open="openPopover">
<DropdownMenuTrigger> <DropdownMenuTrigger ref="refTrigger" :disabled="props.trigger === 'hover'">
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5"> <div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5">
<div class="hover:text-accent-foreground flex-center"> <div class="hover:text-accent-foreground flex-center">
<VbenAvatar :alt="text" :src="avatar" class="size-8" dot /> <VbenAvatar :alt="text" :src="avatar" class="size-8" dot />
@ -164,6 +191,7 @@ if (enableShortcutKey.value) {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="mr-2 min-w-[240px] p-0 pb-1"> <DropdownMenuContent class="mr-2 min-w-[240px] p-0 pb-1">
<div ref="refContent">
<DropdownMenuLabel class="flex items-center p-3"> <DropdownMenuLabel class="flex items-center p-3">
<VbenAvatar <VbenAvatar
:alt="text" :alt="text"
@ -222,6 +250,7 @@ if (enableShortcutKey.value) {
{{ altView }} Q {{ altView }} Q
</DropdownMenuShortcut> </DropdownMenuShortcut>
</DropdownMenuItem> </DropdownMenuItem>
</div>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</template> </template>

View File

@ -132,6 +132,7 @@ watch(
:text="userStore.userInfo?.realName" :text="userStore.userInfo?.realName"
description="ann.vben@gmail.com" description="ann.vben@gmail.com"
tag-text="Pro" tag-text="Pro"
trigger="both"
@logout="handleLogout" @logout="handleLogout"
/> />
</template> </template>

View File

@ -1548,6 +1548,9 @@ importers:
'@vben/utils': '@vben/utils':
specifier: workspace:* specifier: workspace:*
version: link:../../utils version: link:../../utils
'@vueuse/core':
specifier: 'catalog:'
version: 12.0.0(typescript@5.7.2)
vue: vue:
specifier: ^3.5.13 specifier: ^3.5.13
version: 3.5.13(typescript@5.7.2) version: 3.5.13(typescript@5.7.2)
@ -10732,7 +10735,7 @@ snapshots:
'@babel/core': 7.26.0 '@babel/core': 7.26.0
'@babel/helper-compilation-targets': 7.25.9 '@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.25.9 '@babel/helper-plugin-utils': 7.25.9
debug: 4.3.7(supports-color@9.4.0) debug: 4.4.0
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
resolve: 1.22.8 resolve: 1.22.8
transitivePeerDependencies: transitivePeerDependencies: