172 lines
4.8 KiB
Vue
172 lines
4.8 KiB
Vue
<script lang="ts" setup>
|
|
import { AppConfigInput } from '@nuxt/schema'
|
|
|
|
// state
|
|
const app = useAppConfig() as AppConfigInput
|
|
const navbar = ref(null)
|
|
const showDrawer = useState<boolean>('navbar.showDrawer', () => false)
|
|
const showOptions = useState<boolean>('navbar.showOptions', () => false)
|
|
|
|
// lifecycle
|
|
let timer: NodeJS.Timer
|
|
onMounted(() => {
|
|
if (!navbar.value) return
|
|
|
|
// scroll
|
|
const { onScroll } = useSticky(navbar.value, 0)
|
|
setTimeout(() => onScroll(), 50)
|
|
|
|
// on show on mobile
|
|
setInterval(() => {
|
|
// must in mobile
|
|
const minW = 1024
|
|
if (window.innerWidth < minW) {
|
|
updateDrawerOptions()
|
|
}
|
|
}, 100)
|
|
})
|
|
onBeforeUnmount(() => {
|
|
if (timer) clearInterval(timer)
|
|
})
|
|
|
|
// methods
|
|
const updateDrawerOptions = () => {
|
|
// drawer
|
|
if (showDrawer.value || showOptions.value) {
|
|
document.body.classList.add('overflow-hidden')
|
|
} else {
|
|
document.body.classList.remove('overflow-hidden')
|
|
}
|
|
}
|
|
const toggleDrawer = () => (showDrawer.value = !showDrawer.value)
|
|
const toggleOptions = (show?: boolean) => {
|
|
if (show) {
|
|
showOptions.value = show
|
|
} else {
|
|
showOptions.value = !showOptions.value
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="navbar"
|
|
class="backdrop-filter backdrop-blur-md top-0 z-40 w-full flex-none transition-colors duration-300 lg:z-50 border-b border-gray-900/10 dark:border-gray-50/[0.2] bg-white/[0.5] dark:bg-slate-900/[0.5]"
|
|
>
|
|
<div id="navbar-banner" class="banner">
|
|
<slot name="banner" />
|
|
</div>
|
|
<div class="max-w-8xl w-full mx-auto">
|
|
<div class="py-3 lg:px-8 mx-4 lg:mx-0">
|
|
<div class="relative flex items-center">
|
|
<!-- drawer:toggle -->
|
|
<div
|
|
v-if="$slots['drawer']"
|
|
class="lg:hidden flex items-center self-center justify-center mr-2"
|
|
>
|
|
<button
|
|
class="flex items-center focus:outline-none"
|
|
aria-label="Toggle Drawer Menu"
|
|
@click="toggleDrawer()"
|
|
>
|
|
<span
|
|
class="flex items-center text-gray-600 dark:text-gray-300 text-lg"
|
|
aria-hidden="true"
|
|
>
|
|
<IconUil:bars v-if="!showDrawer" />
|
|
<IconUil:times v-else />
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<!-- title -->
|
|
<slot name="title">
|
|
<NuxtLink
|
|
tag="a"
|
|
class="mr-3 flex-none overflow-hidden md:w-auto text-md font-bold text-gray-900 dark:text-gray-200"
|
|
:to="{ name: 'index' }"
|
|
>
|
|
<span class="sr-only">home</span>
|
|
<span class="flex items-center">
|
|
<IconSimpleIcons:nuxtdotjs
|
|
class="inline-block mr-2 text-lg text-primary-500"
|
|
/>
|
|
{{ app.name }}
|
|
</span>
|
|
</NuxtLink>
|
|
</slot>
|
|
<!-- menu -->
|
|
<slot name="menu" />
|
|
<!-- options:toggle -->
|
|
<div
|
|
v-if="$slots['options']"
|
|
class="flex-1 flex justify-end lg:hidden"
|
|
>
|
|
<button
|
|
class="flex items-center focus:outline-none"
|
|
aria-label="Toggle Options Menu"
|
|
@click="toggleOptions()"
|
|
>
|
|
<span
|
|
class="flex items-center text-gray-600 dark:text-gray-300 text-sm"
|
|
aria-hidden="true"
|
|
>
|
|
<icon-fa-solid:ellipsis-v />
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ClientOnly>
|
|
<Teleport to="#app-after">
|
|
<!-- drawer -->
|
|
<Transition name="slide-fade-from-up" mode="out-in">
|
|
<div
|
|
v-if="showDrawer && $slots['drawer']"
|
|
class="fixed lg:hidden bg-gray-100 dark:bg-slate-800 pt-12 top-0 left-0 w-screen h-screen z-30 flex flex-col"
|
|
>
|
|
<div class="flex-1 flex flex-col relative overflow-y-auto">
|
|
<slot name="drawer" :toggle-drawer="toggleDrawer" />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- options -->
|
|
<div v-if="showOptions && $slots['options']">
|
|
<slot
|
|
name="options"
|
|
:toggle-options="toggleOptions"
|
|
:show-options="showOptions"
|
|
/>
|
|
</div>
|
|
</Teleport>
|
|
</ClientOnly>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.slide-fade-from-up-enter-active {
|
|
transition: all 0.3s ease-out;
|
|
}
|
|
.slide-fade-from-up-leave-active {
|
|
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
|
|
}
|
|
.slide-fade-from-up-enter-from,
|
|
.slide-fade-from-up-leave-to {
|
|
transform: translateY(-20px);
|
|
opacity: 0;
|
|
}
|
|
|
|
a.router-link-active {
|
|
font-weight: bold;
|
|
}
|
|
a.router-link-exact-active {
|
|
color: theme('colors.slate.900');
|
|
}
|
|
html.dark {
|
|
a.router-link-exact-active {
|
|
color: theme('colors.white');
|
|
}
|
|
}
|
|
</style>
|