Skip to content

Commit

Permalink
feat(cdk: overlay): add cdk overlay (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaamGinghong authored Feb 5, 2021
1 parent 37b8a68 commit 9e3628e
Show file tree
Hide file tree
Showing 17 changed files with 948 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@commitlint/cli": "^11.0.0",
"@commitlint/config-angular": "^11.0.0",
"@ls-lint/ls-lint": "^1.9.0",
"@popperjs/core": "^2.6.0",
"@types/detect-port": "^1.3.0",
"@types/fs-extra": "^9.0.0",
"@types/gulp": "^4.0.0",
Expand Down
278 changes: 278 additions & 0 deletions packages/cdk/overlay/__tests__/overlay.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import type { OverlayOptions, OverlayTrigger } from '@idux/cdk/overlay'

import { defineComponent, onMounted, onUpdated, PropType, unref } from 'vue'
import { mount } from '@vue/test-utils'
import { IxButton } from '@idux/components'
import { useOverlay } from '../src/useOverlay'

const defaultOverlayOptions: OverlayOptions = {
placement: 'bottom',
scrollStrategy: 'reposition',
trigger: 'click',
offset: [0, 0],
hideDelay: 1000,
showDelay: 1000,
}

describe('useOverlay.ts', () => {
let options: OverlayOptions
let timer: (delay?: number) => Promise<void>

beforeEach(() => {
options = { ...defaultOverlayOptions }
timer = (delay = 0) => {
return new Promise<void>(resolve => {
setTimeout(resolve, delay)
})
}
})

test('init work', () => {
const instance = useOverlay(options)
expect(instance).toBeDefined()
})

test('visible work', async () => {
const TestComponent = defineComponent({
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, visibility, show, hide } = useOverlay(
options,
)

onMounted(initialize)

const handleClick = () => {
unref(visibility) ? hide(true) : show(true)
}

return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick }
},
template: `
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button>
<button id="immediate" @click="handleClick">Immediate Toggle</button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div>
`,
})
const wrapper = mount(TestComponent)
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')

await wrapper.get('#trigger').trigger('click')
await timer(1000)
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')

await wrapper.get('#trigger').trigger('click')
await timer(1000)
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')

await wrapper.get('#immediate').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')

await wrapper.get('#immediate').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')
})

test('component trigger work', async () => {
const TestComponent = defineComponent({
components: { IxButton },
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents } = useOverlay({
...options,
showDelay: 0,
hideDelay: 0,
})

onMounted(initialize)

return { overlayRef, triggerRef, triggerEvents, overlayEvents }
},
template: `
<ix-button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</ix-button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div>
`,
})
const wrapper = mount(TestComponent)
await wrapper.get('#trigger').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')

await wrapper.get('#trigger').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')
})

test('destroy work', async () => {
const TestComponent = defineComponent({
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, destroy } = useOverlay({
...options,
showDelay: 0,
hideDelay: 0,
})

onMounted(initialize)

const handleClick = () => {
destroy()
console.log('destroy')
}

return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick }
},
template: `
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button>
<button id="destroy" @click="handleClick">Destroy</button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div>
`,
})
const wrapper = mount(TestComponent)

const log = jest.spyOn(console, 'log')
await wrapper.get('#destroy').trigger('click')
expect(log).toBeCalled()
})

test('update work', async () => {
const TestComponent = defineComponent({
components: { IxButton },
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, update } = useOverlay(options)

onMounted(initialize)

const handleClick = () => {
update({ showDelay: 0 })
}

return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick }
},
template: `
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button>
<button id="update" @click="handleClick">Update</button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div>
`,
})
const wrapper = mount(TestComponent)

await wrapper.get('#trigger').trigger('click')
await timer(1000)
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')

await wrapper.get('#trigger').trigger('click')
await timer(1000)
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')

await wrapper.get('#update').trigger('click')
await wrapper.get('#trigger').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')
})

test('trigger work', async () => {
const TestComponent = defineComponent({
components: { IxButton },
props: {
trigger: {
type: String as PropType<OverlayTrigger>,
default: 'click',
},
},
setup(props) {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, update } = useOverlay({
...options,
showDelay: 0,
hideDelay: 0,
trigger: props.trigger,
})

onMounted(initialize)

onUpdated(() => {
update({ trigger: props.trigger })
})

return { overlayRef, triggerRef, triggerEvents, overlayEvents }
},
template: `
<button id="trigger" ref="triggerRef" @focus='triggerEvents.onFocus' @blur='triggerEvents.onBlur' @mouseenter='triggerEvents.onMouseEnter' @mouseleave='triggerEvents.onMouseLeave' @click="triggerEvents.onClick">Trigger</button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div>
`,
})

const wrapper = mount(TestComponent)
await wrapper.get('#trigger').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')
await wrapper.get('#trigger').trigger('click')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')

await wrapper.get('#overlay').trigger('mouseleave')

await wrapper.setProps({ trigger: 'focus' })
await wrapper.get('#trigger').trigger('focus')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')
await wrapper.get('#trigger').trigger('focus')

await wrapper.get('#trigger').trigger('blur')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')

await wrapper.setProps({ trigger: 'hover' })
await wrapper.get('#trigger').trigger('mouseenter')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')
await wrapper.get('#trigger').trigger('mouseleave')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')
})

test('hover overlay work', async () => {
const TestComponent = defineComponent({
components: { IxButton },
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents } = useOverlay({
...options,
trigger: 'hover',
visible: true,
allowEnter: true,
showDelay: 0,
hideDelay: 0,
})

onMounted(initialize)

return { overlayRef, triggerRef, triggerEvents, overlayEvents }
},
template: `
<button id="trigger" ref="triggerRef" @mouseenter='triggerEvents.onMouseEnter' @mouseleave='triggerEvents.onMouseLeave'>Trigger</button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div>
`,
})
const wrapper = mount(TestComponent)
await wrapper.get('#overlay').trigger('mouseenter')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;')

await wrapper.get('#overlay').trigger('mouseleave')
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;')
})

test('arrow work', async () => {
const TestComponent = defineComponent({
components: { IxButton },
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, arrowRef } = useOverlay({
...options,
showDelay: 0,
showArrow: true,
})

onMounted(initialize)

return { overlayRef, triggerRef, triggerEvents, overlayEvents, arrowRef }
},
template: `
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button>
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay
<div ref="arrowRef" id='arrow'></div>
</div>
`,
})
const wrapper = mount(TestComponent)
await wrapper.get('#trigger').trigger('click')
expect(wrapper.get('#arrow')).toBeDefined()
})

// todo global scroll
})
46 changes: 46 additions & 0 deletions packages/cdk/overlay/demo/Basic.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<ix-button
ref="triggerRef"
@click="triggerEvents.onClick"
@mouseenter="triggerEvents.onMouseEnter"
@mouseleave="triggerEvents.onMouseLeave"
@focus="triggerEvents.onFocus"
@blur="triggerEvents.onBlur"
>Click</ix-button
>
<ix-button @click="handleClick">Update</ix-button>
<teleport to="body">
<div ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter()" @mouseleave="overlayEvents.onMouseLeave()">
tooltip
<div ref="arrowRef"></div>
</div>
</teleport>
</template>

<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { useOverlay } from '@idux/cdk/overlay'
export default defineComponent({
name: 'Basic',
setup() {
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, update, arrowRef } = useOverlay({
visible: false,
trigger: 'click',
placement: 'bottom',
scrollStrategy: 'reposition',
offset: [5, 5],
showDelay: 0,
hideDelay: 1000,
allowEnter: true,
})
onMounted(initialize)
const handleClick = () => {
update({ trigger: 'focus' })
}
return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick, arrowRef }
},
})
</script>
26 changes: 26 additions & 0 deletions packages/cdk/overlay/demo/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
order: 0
title:
zh: 基本使用
en: Basic usage
---

## zh

- 如何创建一个浮层
- 如何初始化浮层
- 如何更新浮层
- 如何在模板中绑定对应的事件

另外,在组件中使用当前cdk,按照规范请配套使用组件Portal.

## en

- How to create an overlay
- How to initialize the overlay
- How to update the configuration of the overlay
- How to bind the events in the template

By the way, to use the cdk in components, please use the component Portal according to the specification.

## demo
Loading

0 comments on commit 9e3628e

Please sign in to comment.