Skip to content

Commit

Permalink
feat(Dropdown): handle manual mode
Browse files Browse the repository at this point in the history
Resolves #1143
  • Loading branch information
benjamincanac committed Jan 3, 2024
1 parent 84e6392 commit 3844714
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 11 deletions.
22 changes: 22 additions & 0 deletions docs/components/content/examples/DropdownExampleOpen.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup>
const items = [
[{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
}]
]
const open = ref(true)
defineShortcuts({
o: () => open.value = !open.value
})
</script>

<template>
<UDropdown v-model:open="open" :items="items" :popper="{ placement: 'bottom-start' }">
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
</UDropdown>
</template>
6 changes: 6 additions & 0 deletions docs/content/2.elements/6.dropdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ Use the `mode` prop to switch between `click` and `hover` modes.

:component-example{component="dropdown-example-mode"}

### Manual :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}

Use a `v-model:open` to manually control the state. In this example, press :shortcut{value="O"} to toggle the dropdown.

:component-example{component="dropdown-example-open"}

## Popper

Use the `popper` prop to customize the popper instance.
Expand Down
48 changes: 37 additions & 11 deletions src/runtime/components/elements/Dropdown.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<template>
<!-- eslint-disable-next-line vue/no-template-shadow -->
<HMenu v-slot="{ open }" as="div" :class="ui.wrapper" v-bind="attrs" @mouseleave="onMouseLeave">
<HMenuButton
ref="trigger"
Expand All @@ -19,6 +20,7 @@
<Transition appear v-bind="ui.transition">
<div>
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />

<HMenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.height]" static>
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="omit(item, ['label', 'labelClass', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'class', 'click'])" custom>
Expand Down Expand Up @@ -53,7 +55,7 @@
</template>
<script lang="ts">
import { defineComponent, ref, computed, toRef, onMounted, resolveComponent } from 'vue'
import { defineComponent, ref, computed, watch, toRef, onMounted, resolveComponent } from 'vue'
import type { PropType } from 'vue'
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem } from '@headlessui/vue'
import { defu } from 'defu'
Expand Down Expand Up @@ -92,6 +94,10 @@ export default defineComponent({
default: 'click',
validator: (value: string) => ['click', 'hover'].includes(value)
},
open: {
type: Boolean,
default: undefined
},
disabled: {
type: Boolean,
default: false
Expand All @@ -117,7 +123,8 @@ export default defineComponent({
default: () => ({})
}
},
setup (props) {
emits: ['update:open'],
setup (props, { emit }) {
const { ui, attrs } = useUI('dropdown', toRef(props, 'ui'), config, toRef(props, 'class'))
const popper = computed<PopperOptions>(() => defu(props.mode === 'hover' ? { offsetDistance: 0 } : {}, props.popper, ui.value.popper as PopperOptions))
Expand All @@ -131,15 +138,17 @@ export default defineComponent({
let closeTimeout: NodeJS.Timeout | null = null
onMounted(() => {
setTimeout(() => {
// @ts-expect-error internals
const menuProvides = trigger.value?.$.provides
if (!menuProvides) {
return
}
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
}, 200)
// @ts-expect-error internals
const menuProvides = trigger.value?.$.provides
if (!menuProvides) {
return
}
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
if (props.open) {
menuApi.value?.openMenu()
}
})
const containerStyle = computed(() => {
Expand Down Expand Up @@ -200,6 +209,23 @@ export default defineComponent({
}
}
watch(() => props.open, (newValue: boolean, oldValue: boolean) => {
if (!menuApi.value) return
if (oldValue === undefined || newValue === oldValue) return
if (newValue) {
menuApi.value.openMenu()
} else {
menuApi.value.closeMenu()
}
})
watch(() => menuApi.value?.menuState, (newValue: number, oldValue: number) => {
if (oldValue === undefined || newValue === oldValue) return
emit('update:open', newValue === 0)
})
const NuxtLink = resolveComponent('NuxtLink')
return {
Expand Down

0 comments on commit 3844714

Please sign in to comment.