Skip to content

Commit

Permalink
feat(NavigationMenu): disable hover or click props for triggers (unov…
Browse files Browse the repository at this point in the history
…ue#778)

* feat(menu-trigger): add props and basic logic

* feat(menu-trigger): add props open on click and open on hover

* feat(menu-trigger): add logic to disable triggers

* test(menu-trigger): add tests on new props

* docs(navigation_menu): add docs

* chore: remove unused code

* feat(menu-trigger): change props names

* docs(navigation_menu): update docs after changing prop names

* chore: make disable click trigger only response to mouse event

* chore: add stories, update test

---------

Co-authored-by: zernonia <[email protected]>
  • Loading branch information
Chrtyaka and zernonia authored Mar 22, 2024
1 parent 6d48c6e commit ed73054
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 3 deletions.
14 changes: 14 additions & 0 deletions docs/content/meta/NavigationMenuRoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@
'type': 'number',
'required': false,
'default': '300'
},
{
'name': 'disableClickTrigger',
'description': '<p> If true, the menu can\'t be opened by clicking on the trigger </p>\n',
'type': 'boolean',
'required': false,
'default': false
},
{
'name': 'disableHoverTrigger',
'description': '<p> If true, the menu can\'t be opened by hovering on the trigger </p>\n',
'type': 'boolean',
'required': false,
'default': false
}
]" />

Expand Down
84 changes: 83 additions & 1 deletion packages/radix-vue/src/NavigationMenu/NavigationMenu.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { axe } from 'vitest-axe'
import NavigationMenu from './story/_NavigationMenu.vue'
import type { DOMWrapper, VueWrapper } from '@vue/test-utils'
Expand All @@ -7,6 +7,18 @@ import userEvent from '@testing-library/user-event'
import { nextTick } from 'vue'
import { fireEvent } from '@testing-library/vue'
import { sleep } from '@/test'
import NavigationMenuItem from './NavigationMenuItem.vue'
import { afterEach } from 'node:test'
import { useDebounceFn } from '@vueuse/core'

vi.mock('@vueuse/core', async () => {
const actual = await vi.importActual('@vueuse/core')
return {
...actual,
useDebounceFn: vi.fn(),

}
})

describe('given default NavigationMenu', () => {
let wrapper: VueWrapper<InstanceType<typeof NavigationMenu>>
Expand All @@ -15,6 +27,17 @@ describe('given default NavigationMenu', () => {
beforeEach(() => {
document.body.innerHTML = ''
wrapper = mount(NavigationMenu, { attachTo: document.body })

// @ts-expect-error simple mock
vi.mocked(useDebounceFn).mockImplementation((cb: (val: string) => void, delay: string) => {
return function (arg: string) {
cb(arg)
}
})
})

afterEach(() => {
wrapper.unmount()
})

it('should pass axe accessibility tests', async () => {
Expand Down Expand Up @@ -78,4 +101,63 @@ describe('given default NavigationMenu', () => {
// })
// })
})

describe('menu triggers', () => {
const findMenuItem = () => wrapper.findComponent(NavigationMenuItem)

const findTriggerButton = () => findMenuItem().find('button')

const findLinkContent = () => wrapper.find('[data-dismissable-layer]')

it('should open menu on click by default', async () => {
const button = findTriggerButton()

button.trigger('click')

await wrapper.vm.$nextTick()

const content = findLinkContent()

expect(content.exists()).toBeTruthy()
})

it('should open menu on hover by default', async () => {
const button = findTriggerButton()

button.trigger('pointermove', { pointerType: 'mouse' })

await wrapper.vm.$nextTick()

const content = findLinkContent()

expect(content.exists()).toBeTruthy()
})

it('should not trigger content on click', async () => {
await wrapper.setProps({ disableClickTrigger: true })

const button = findTriggerButton()

button.trigger('click', { pointerType: 'mouse' })

await wrapper.vm.$nextTick()

const content = findLinkContent()

expect(content.exists()).toBeFalsy()
})

it('should not trigger content on hover', async () => {
await wrapper.setProps({ disableHoverTrigger: true })
const button = findTriggerButton()

button.trigger('pointermove', { pointerType: 'mouse' })

await wrapper.vm.$nextTick()

const content = findLinkContent()

expect(content.exists()).toBeFalsy()
})
})
})
19 changes: 18 additions & 1 deletion packages/radix-vue/src/NavigationMenu/NavigationMenuRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ export interface NavigationMenuRootProps extends PrimitiveProps {
* @defaultValue 300
*/
skipDelayDuration?: number
/**
* If true, menu can't be open by click on trigger
* @defaultValue false
*/
disableClickTrigger?: boolean
/**
* If true, menu can't be open by hover on trigger
* @defaultValue false
*/
disableHoverTrigger?: boolean
}
export type NavigationMenuRootEmits = {
/** Event handler called when the value changes. */
Expand All @@ -41,6 +52,8 @@ export interface NavigationMenuContext {
baseId: string
dir: Ref<Direction>
orientation: Orientation
disableClickTrigger: Ref<boolean>
disableHoverTrigger: Ref<boolean>
rootNavigationMenu: Ref<HTMLElement | undefined>
indicatorTrack: Ref<HTMLElement | undefined>
onIndicatorTrackChange(indicatorTrack: HTMLElement | undefined): void
Expand Down Expand Up @@ -74,6 +87,8 @@ const props = withDefaults(defineProps<NavigationMenuRootProps>(), {
delayDuration: 200,
skipDelayDuration: 300,
orientation: 'horizontal',
disableClickTrigger: false,
disableHoverTrigger: false,
as: 'nav',
})
const emits = defineEmits<NavigationMenuRootEmits>()
Expand All @@ -92,7 +107,7 @@ const viewport = ref<HTMLElement>()
const { createCollection } = useCollection('nav')
createCollection(indicatorTrack)
const { delayDuration, skipDelayDuration, dir: propDir } = toRefs(props)
const { delayDuration, skipDelayDuration, dir: propDir, disableClickTrigger, disableHoverTrigger } = toRefs(props)
const dir = useDirection(propDir)
const isDelaySkipped = refAutoReset(false, skipDelayDuration)
Expand All @@ -113,6 +128,8 @@ provideNavigationMenuContext({
modelValue,
previousValue,
baseId: useId(undefined, 'radix-navigation-menu'),
disableClickTrigger,
disableHoverTrigger,
dir,
orientation: props.orientation,
rootNavigationMenu,
Expand Down
15 changes: 14 additions & 1 deletion packages/radix-vue/src/NavigationMenu/NavigationMenuTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,17 @@ onMounted(() => {
})
function handlePointerEnter() {
if (menuContext.disableHoverTrigger.value)
return
wasClickCloseRef.value = false
itemContext.wasEscapeCloseRef.value = false
}
function handlePointerMove(ev: PointerEvent) {
if (menuContext.disableHoverTrigger.value)
return
if (ev.pointerType === 'mouse') {
if (
props.disabled
Expand All @@ -60,12 +66,16 @@ function handlePointerMove(ev: PointerEvent) {
|| hasPointerMoveOpenedRef.value
)
return
menuContext.onTriggerEnter(itemContext.value)
hasPointerMoveOpenedRef.value = true
}
}
function handlePointerLeave(ev: PointerEvent) {
if (menuContext.disableHoverTrigger.value)
return
if (ev.pointerType === 'mouse') {
if (props.disabled)
return
Expand All @@ -74,7 +84,10 @@ function handlePointerLeave(ev: PointerEvent) {
}
}
function handleClick() {
function handleClick(event: PointerEvent) {
if (event.pointerType === 'mouse' && menuContext.disableClickTrigger.value)
return
// if open via pointermove, we prevent click event
if (hasPointerMoveOpenedRef.value)
return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="ts">
import NavigationMenu from './_NavigationMenu.vue'
</script>

<template>
<Story title="Navigation Menu/Chromatic" :layout="{ type: 'grid', width: '50%', iframe: false }">
<Variant title="Default">
<NavigationMenu />
</Variant>

<Variant title="Disabled click trigger">
<NavigationMenu disable-click-trigger />
</Variant>

<Variant title="Disabled hover trigger">
<NavigationMenu disable-hover-trigger />
</Variant>
</Story>
</template>
11 changes: 11 additions & 0 deletions packages/radix-vue/src/NavigationMenu/story/_NavigationMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,24 @@ import {
} from '../'
import NavigationMenuListItem from './_NavigationMenuListItem.vue'
export interface TestProps {
disableClickTrigger?: boolean
disableHoverTrigger?: boolean
}
const props = withDefaults(defineProps<TestProps>(), {
disableClickTrigger: false,
disableHoverTrigger: false,
})
const currentTrigger = ref('')
</script>

<template>
<div class="w-full h-[600px]">
<NavigationMenuRoot
v-model="currentTrigger"
v-bind="props"
class="relative z-[1] flex w-full justify-center"
>
<NavigationMenuList
Expand Down

0 comments on commit ed73054

Please sign in to comment.