215 lines
5.8 KiB
Vue
215 lines
5.8 KiB
Vue
|
|
<script setup>
|
|||
|
|
import {
|
|||
|
|
computePosition,
|
|||
|
|
flip,
|
|||
|
|
offset,
|
|||
|
|
shift,
|
|||
|
|
} from '@floating-ui/dom'
|
|||
|
|
import { useLayoutConfigStore } from '@layouts/stores/config'
|
|||
|
|
import { themeConfig } from '@themeConfig'
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
popperInlineEnd: {
|
|||
|
|
type: Boolean,
|
|||
|
|
required: false,
|
|||
|
|
default: false,
|
|||
|
|
},
|
|||
|
|
tag: {
|
|||
|
|
type: String,
|
|||
|
|
required: false,
|
|||
|
|
default: 'div',
|
|||
|
|
},
|
|||
|
|
contentContainerTag: {
|
|||
|
|
type: String,
|
|||
|
|
required: false,
|
|||
|
|
default: 'div',
|
|||
|
|
},
|
|||
|
|
isRtl: {
|
|||
|
|
type: Boolean,
|
|||
|
|
required: false,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const configStore = useLayoutConfigStore()
|
|||
|
|
const refPopperContainer = ref()
|
|||
|
|
const refPopper = ref()
|
|||
|
|
|
|||
|
|
const popperContentStyles = ref({
|
|||
|
|
left: '0px',
|
|||
|
|
top: '0px',
|
|||
|
|
|
|||
|
|
/*ℹ️ Why we are not using fixed positioning?
|
|||
|
|
|
|||
|
|
`position: fixed` doesn't work as expected when some CSS properties like `transform` is applied on its parent element.
|
|||
|
|
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/position#values <= See `fixed` value description
|
|||
|
|
|
|||
|
|
Hence, when we use transitions where transition apply `transform` on its parent element, fixed positioning will not work.
|
|||
|
|
(Popper content moves away from the element when parent element transition)
|
|||
|
|
|
|||
|
|
To avoid this, we use `position: absolute` instead of `position: fixed`.
|
|||
|
|
|
|||
|
|
NOTE: This issue starts from third level children (Top Level > Sub item > Sub item).
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// strategy: 'fixed',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const updatePopper = async () => {
|
|||
|
|
if (refPopperContainer.value !== undefined && refPopper.value !== undefined) {
|
|||
|
|
const { x, y } = await computePosition(refPopperContainer.value, refPopper.value, {
|
|||
|
|
placement: props.popperInlineEnd ? props.isRtl ? 'left-start' : 'right-start' : 'bottom-start',
|
|||
|
|
middleware: [
|
|||
|
|
...configStore.horizontalNavPopoverOffset ? [offset(configStore.horizontalNavPopoverOffset)] : [],
|
|||
|
|
flip({
|
|||
|
|
boundary: document.querySelector('body'),
|
|||
|
|
padding: { bottom: 16 },
|
|||
|
|
}),
|
|||
|
|
shift({
|
|||
|
|
boundary: document.querySelector('body'),
|
|||
|
|
padding: { bottom: 16 },
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
|
|||
|
|
/*ℹ️ Why we are not using fixed positioning?
|
|||
|
|
|
|||
|
|
`position: fixed` doesn't work as expected when some CSS properties like `transform` is applied on its parent element.
|
|||
|
|
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/position#values <= See `fixed` value description
|
|||
|
|
|
|||
|
|
Hence, when we use transitions where transition apply `transform` on its parent element, fixed positioning will not work.
|
|||
|
|
(Popper content moves away from the element when parent element transition)
|
|||
|
|
|
|||
|
|
To avoid this, we use `position: absolute` instead of `position: fixed`.
|
|||
|
|
|
|||
|
|
NOTE: This issue starts from third level children (Top Level > Sub item > Sub item).
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// strategy: 'fixed',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
popperContentStyles.value.left = `${ x }px`
|
|||
|
|
popperContentStyles.value.top = `${ y }px`
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
until(() => configStore.horizontalNavType).toMatch(type => type === 'static').then(() => {
|
|||
|
|
useEventListener('scroll', updatePopper)
|
|||
|
|
|
|||
|
|
/*ℹ️ Why we are not using fixed positioning?
|
|||
|
|
|
|||
|
|
`position: fixed` doesn't work as expected when some CSS properties like `transform` is applied on its parent element.
|
|||
|
|
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/position#values <= See `fixed` value description
|
|||
|
|
|
|||
|
|
Hence, when we use transitions where transition apply `transform` on its parent element, fixed positioning will not work.
|
|||
|
|
(Popper content moves away from the element when parent element transition)
|
|||
|
|
|
|||
|
|
To avoid this, we use `position: absolute` instead of `position: fixed`.
|
|||
|
|
|
|||
|
|
NOTE: This issue starts from third level children (Top Level > Sub item > Sub item).
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// strategy: 'fixed',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const isContentShown = ref(false)
|
|||
|
|
|
|||
|
|
const showContent = () => {
|
|||
|
|
isContentShown.value = true
|
|||
|
|
updatePopper()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const hideContent = () => {
|
|||
|
|
isContentShown.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(updatePopper)
|
|||
|
|
|
|||
|
|
// ℹ️ Recalculate popper position when it's triggerer changes its position
|
|||
|
|
watch([
|
|||
|
|
() => configStore.isAppRTL,
|
|||
|
|
() => configStore.appContentWidth,
|
|||
|
|
], updatePopper)
|
|||
|
|
|
|||
|
|
// Watch for route changes and close popper content if route is changed
|
|||
|
|
const route = useRoute()
|
|||
|
|
|
|||
|
|
watch(() => route.fullPath, hideContent)
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<div
|
|||
|
|
class="nav-popper"
|
|||
|
|
:class="[{
|
|||
|
|
'popper-inline-end': popperInlineEnd,
|
|||
|
|
'show-content': isContentShown,
|
|||
|
|
}]"
|
|||
|
|
>
|
|||
|
|
<div
|
|||
|
|
ref="refPopperContainer"
|
|||
|
|
class="popper-triggerer"
|
|||
|
|
@mouseenter="showContent"
|
|||
|
|
@mouseleave="hideContent"
|
|||
|
|
>
|
|||
|
|
<slot />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- SECTION Popper Content -->
|
|||
|
|
<!-- 👉 Without transition -->
|
|||
|
|
<template v-if="!themeConfig.horizontalNav.transition">
|
|||
|
|
<div
|
|||
|
|
ref="refPopper"
|
|||
|
|
class="popper-content"
|
|||
|
|
:style="popperContentStyles"
|
|||
|
|
@mouseenter="showContent"
|
|||
|
|
@mouseleave="hideContent"
|
|||
|
|
>
|
|||
|
|
<div>
|
|||
|
|
<slot name="content" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- 👉 CSS Transition -->
|
|||
|
|
<template v-else-if="typeof themeConfig.horizontalNav.transition === 'string'">
|
|||
|
|
<Transition :name="themeConfig.horizontalNav.transition">
|
|||
|
|
<div
|
|||
|
|
v-show="isContentShown"
|
|||
|
|
ref="refPopper"
|
|||
|
|
class="popper-content"
|
|||
|
|
:style="popperContentStyles"
|
|||
|
|
@mouseenter="showContent"
|
|||
|
|
@mouseleave="hideContent"
|
|||
|
|
>
|
|||
|
|
<div>
|
|||
|
|
<slot name="content" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Transition>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- 👉 Transition Component -->
|
|||
|
|
<template v-else>
|
|||
|
|
<Component :is="themeConfig.horizontalNav.transition">
|
|||
|
|
<div
|
|||
|
|
v-show="isContentShown"
|
|||
|
|
ref="refPopper"
|
|||
|
|
class="popper-content"
|
|||
|
|
:style="popperContentStyles"
|
|||
|
|
@mouseenter="showContent"
|
|||
|
|
@mouseleave="hideContent"
|
|||
|
|
>
|
|||
|
|
<div>
|
|||
|
|
<slot name="content" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Component>
|
|||
|
|
</template>
|
|||
|
|
<!-- !SECTION -->
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style lang="scss">
|
|||
|
|
.popper-content {
|
|||
|
|
position: absolute;
|
|||
|
|
}
|
|||
|
|
</style>
|