chore: tweak

This commit is contained in:
pengzhanbo 2024-06-15 12:13:09 +08:00
parent 3b0208b356
commit 86a50e9601
34 changed files with 576 additions and 432 deletions

View File

@ -1,237 +0,0 @@
<script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue'
import { useSidebar } from '../../composables/sidebar.js'
import { useData } from '../../composables/data.js'
import NavBarAppearance from './NavBarAppearance.vue'
import NavBarExtra from './NavBarExtra.vue'
import NavBarHamburger from './NavBarHamburger.vue'
import NavBarMenu from './NavBarMenu.vue'
import NavBarSearch from './NavBarSearch.vue'
import NavBarSocialLinks from './NavBarSocialLinks.vue'
import NavBarTitle from './NavBarTitle.vue'
import NavBarTranslations from './NavBarTranslations.vue'
defineProps<{
isScreenOpen: boolean
}>()
defineEmits<(e: 'toggleScreen') => void>()
const { frontmatter } = useData()
const { y } = useWindowScroll()
const { hasSidebar } = useSidebar()
const classes = ref<Record<string, boolean>>({})
watchPostEffect(() => {
classes.value = {
'has-sidebar': hasSidebar.value,
'top': frontmatter.value.pageLayout === 'home' && y.value === 0,
}
})
</script>
<template>
<div class="navbar-wrapper" :class="classes">
<div class="container">
<div class="title">
<NavBarTitle />
</div>
<div class="content">
<div class="curtain" />
<div class="content-body">
<NavBarSearch class="search" />
<NavBarMenu class="menu" />
<NavBarTranslations class="translations" />
<NavBarAppearance class="appearance" />
<NavBarSocialLinks class="social-links" />
<NavBarExtra class="extra" />
<NavBarHamburger
class="hamburger"
:active="isScreenOpen"
@click="$emit('toggleScreen')"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.navbar-wrapper {
position: relative;
height: var(--vp-nav-height);
padding: 0 8px 0 24px;
white-space: nowrap;
pointer-events: none;
border-bottom: 1px solid transparent;
transition: var(--t-color);
transition-property: background-color, color, border-bottom;
}
@media (min-width: 768px) {
.navbar-wrapper {
padding: 0 32px;
}
}
@media (min-width: 960px) {
.navbar-wrapper.has-sidebar {
padding: 0;
}
.navbar-wrapper:not(.has-sidebar, .top) {
background-color: var(--vp-nav-bg-color);
border-bottom-color: var(--vp-c-gutter);
}
}
.container {
display: flex;
justify-content: space-between;
max-width: calc(var(--vp-layout-max-width) - 64px);
height: var(--vp-nav-height);
margin: 0 auto;
pointer-events: none;
}
.container :deep(*) {
pointer-events: auto;
}
@media (min-width: 960px) {
.navbar-wrapper.has-sidebar .container {
max-width: 100%;
}
}
.title {
flex-shrink: 0;
height: calc(var(--vp-nav-height) - 1px);
transition: background-color var(--t-color);
}
@media (min-width: 960px) {
.navbar-wrapper.has-sidebar .title {
position: absolute;
top: 0;
left: 0;
z-index: 2;
width: var(--vp-sidebar-width);
height: var(--vp-nav-height);
padding: 0 32px;
background-color: transparent;
}
}
@media (min-width: 1440px) {
.navbar-wrapper.has-sidebar .title {
width:
calc(
(100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) -
32px
);
padding-left:
max(
32px,
calc((100% - (var(--vp-layout-max-width) - 64px)) / 2)
);
}
}
.content {
flex-grow: 1;
}
@media (min-width: 960px) {
.navbar-wrapper.has-sidebar .content {
position: relative;
z-index: 1;
padding-right: 32px;
padding-left: var(--vp-sidebar-width);
}
}
@media (min-width: 1440px) {
.navbar-wrapper.has-sidebar .content {
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);
padding-left:
calc(
(100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width)
);
}
}
.content-body {
display: flex;
align-items: center;
justify-content: flex-end;
height: calc(var(--vp-nav-height) - 1px);
transition: var(--t-color);
transition-property: background-color;
}
@media (min-width: 960px) {
.navbar-wrapper:not(.top) .content-body {
position: relative;
background-color: var(--vp-nav-bg-color);
}
}
@media (max-width: 767px) {
.content-body {
column-gap: 0.5rem;
}
}
.menu + .translations::before,
.menu + .appearance::before,
.menu + .social-links::before,
.translations + .appearance::before,
.translations + .social-links::before,
.appearance + .social-links::before {
width: 1px;
height: 24px;
margin-right: 8px;
margin-left: 8px;
content: "";
background-color: var(--vp-c-divider);
transition: background-color var(--t-color);
}
.menu + .appearance::before,
.translations + .appearance::before {
margin-right: 16px;
}
.appearance + .social-links::before {
margin-left: 16px;
}
.social-links {
margin-right: -8px;
}
@media (min-width: 960px) {
.navbar-wrapper.has-sidebar .curtain {
position: absolute;
right: 0;
bottom: 0;
width: calc(100% - var(--vp-sidebar-width));
height: 0;
border-bottom: solid 1px var(--vp-c-divider);
transition: border-bottom var(--t-color);
}
}
@media (min-width: 1440px) {
.navbar-wrapper.has-sidebar .curtain {
width:
calc(
100% -
((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))
);
}
}
</style>

View File

@ -0,0 +1,86 @@
<script lang="ts" setup>
import { computed, provide, watchEffect } from 'vue'
import { useNav } from '../../composables/nav.js'
import { useData } from '../../composables/data.js'
import { inBrowser } from '../../utils/index.js'
import VPNavbar from './VPNavBar.vue'
import VPNavScreen from './VPNavScreen.vue'
const { page, frontmatter } = useData()
const { isScreenOpen, closeScreen, toggleScreen } = useNav()
const fixedInclude = ['blog', 'friends', 'blog-archives', 'blog-tags']
const fixed = computed(() => {
return fixedInclude.includes(page.value.type as string)
})
const hasNavbar = computed(() => {
return frontmatter.value.navbar !== false
})
provide('close-screen', closeScreen)
watchEffect(() => {
if (inBrowser) {
document.documentElement.classList.toggle('hide-nav', !hasNavbar.value)
}
})
</script>
<template>
<div class="vp-nav" :class="{ fixed }">
<VPNavbar :is-screen-open="isScreenOpen" @toggle-screen="toggleScreen">
<template #nav-bar-title-before>
<slot name="nav-bar-title-before" />
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after" />
</template>
<template #nav-bar-content-before>
<slot name="nav-bar-content-before" />
</template>
<template #nav-bar-content-after>
<slot name="nav-bar-content-after" />
</template>
</VPNavbar>
<VPNavScreen :open="isScreenOpen">
<template #nav-screen-content-before>
<slot name="nav-screen-content-before" />
</template>
<template #nav-screen-content-after>
<slot name="nav-screen-content-after" />
</template>
</VPNavScreen>
</div>
</template>
<style scoped>
.vp-nav {
position: relative;
top: var(--vp-layout-top-height, 0);
/* rtl:ignore */
left: 0;
z-index: var(--vp-z-index-nav);
width: 100%;
pointer-events: none;
}
.vp-nav.fixed {
position: fixed;
}
.vp-nav.fixed :deep(.vp-navbar) {
background-color: var(--vp-nav-bg-color);
border-bottom-color: var(--vp-c-gutter);
}
@media (min-width: 960px) {
.vp-nav {
position: fixed;
}
}
</style>

View File

@ -0,0 +1,268 @@
<script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue'
import { useSidebar } from '../../composables/sidebar.js'
import { useData } from '../../composables/data.js'
import VPNavBarAppearance from './VPNavBarAppearance.vue'
import VPNavBarExtra from './VPNavBarExtra.vue'
import VPNavBarHamburger from './VPNavBarHamburger.vue'
import VPNavBarMenu from './VPNavBarMenu.vue'
import VPNavBarSearch from './VPNavBarSearch.vue'
import VPNavBarSocialLinks from './VPNavBarSocialLinks.vue'
import VPNavBarTitle from './VPNavBarTitle.vue'
import VPNavBarTranslations from './VPNavBarTranslations.vue'
defineProps<{
isScreenOpen: boolean
}>()
defineEmits<(e: 'toggleScreen') => void>()
const { frontmatter } = useData()
const { y } = useWindowScroll()
const { hasSidebar } = useSidebar()
const classes = ref<Record<string, boolean>>({})
watchPostEffect(() => {
classes.value = {
'has-sidebar': hasSidebar.value,
'home': frontmatter.value.pageLayout === 'home',
'top': y.value === 0,
}
})
</script>
<template>
<div class="vp-navbar" :class="classes">
<div class="wrapper">
<div class="container">
<div class="title">
<VPNavBarTitle>
<template #nav-bar-title-before>
<slot name="nav-bar-title-before" />
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after" />
</template>
</VPNavBarTitle>
</div>
<div class="content">
<div class="content-body">
<slot name="nav-bar-content-before" />
<VPNavBarSearch class="search" />
<VPNavBarMenu class="menu" />
<VPNavBarTranslations class="translations" />
<VPNavBarAppearance class="appearance" />
<VPNavBarSocialLinks class="social-links" />
<VPNavBarExtra class="extra" />
<slot name="nav-bar-content-after" />
<VPNavBarHamburger
class="hamburger"
:active="isScreenOpen"
@click="$emit('toggleScreen')"
/>
</div>
</div>
</div>
</div>
<div class="divider">
<div class="divider-line" />
</div>
</div>
</template>
<style scoped>
.vp-navbar {
position: relative;
height: var(--vp-nav-height);
white-space: nowrap;
pointer-events: none;
transition: var(--t-color);
transition-property: background-color, color, border-bottom;
}
.vp-navbar:not(.home) {
background-color: var(--vp-nav-bg-color);
}
@media (min-width: 960px) {
.vp-navbar:not(.home) {
background-color: transparent;
}
.vp-navbar:not(.has-sidebar, .home.top) {
background-color: var(--vp-nav-bg-color);
}
}
.wrapper {
padding: 0 8px 0 24px;
}
@media (min-width: 768px) {
.wrapper {
padding: 0 32px;
}
}
@media (min-width: 960px) {
.vp-navbar.has-sidebar .wrapper {
padding: 0;
}
}
.container {
display: flex;
justify-content: space-between;
max-width: calc(var(--vp-layout-max-width) - 64px);
height: var(--vp-nav-height);
margin: 0 auto;
pointer-events: none;
}
.content {
flex-grow: 1;
}
.title {
flex-shrink: 0;
height: calc(var(--vp-nav-height) - 1px);
transition: background-color var(--t-color);
}
.container > .title,
.container > .content {
pointer-events: none;
}
.container :deep(*) {
pointer-events: auto;
}
@media (min-width: 960px) {
.vp-navbar.has-sidebar .container {
max-width: 100%;
}
}
@media (min-width: 960px) {
.vp-navbar.has-sidebar .title {
position: absolute;
top: 0;
left: 0;
z-index: 2;
width: var(--vp-sidebar-width);
height: var(--vp-nav-height);
padding: 0 32px;
background-color: transparent;
}
}
@media (min-width: 1440px) {
.vp-navbar.has-sidebar .title {
width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
padding-left:
max(32px,
calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
}
}
@media (min-width: 960px) {
.vp-navbar.has-sidebar .content {
position: relative;
z-index: 1;
padding-right: 32px;
padding-left: var(--vp-sidebar-width);
}
}
@media (min-width: 1440px) {
.vp-navbar.has-sidebar .content {
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
.content-body {
display: flex;
align-items: center;
justify-content: flex-end;
height: var(--vp-nav-height);
transition: background-color var(--t-color);
}
@media (min-width: 960px) {
.vp-navbar:not(.home.top) .content-body {
position: relative;
background-color: var(--vp-nav-bg-color);
}
.vp-navbar:not(.has-sidebar, .home.top) .content-body {
background-color: transparent;
}
}
@media (max-width: 767px) {
.content-body {
column-gap: 0.5rem;
}
}
.menu + .translations::before,
.menu + .appearance::before,
.menu + .social-links::before,
.translations + .appearance::before,
.translations + .social-links::before,
.appearance + .social-links::before {
width: 1px;
height: 24px;
margin-right: 8px;
margin-left: 8px;
content: "";
background-color: var(--vp-c-divider);
transition: background-color var(--t-color);
}
.menu + .appearance::before,
.translations + .appearance::before {
margin-right: 16px;
}
.appearance + .social-links::before {
margin-left: 16px;
}
.social-links {
margin-right: -8px;
}
@media (min-width: 1440px) {
.vp-navbar.has-sidebar .divider {
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
.divider-line {
width: 100%;
height: 1px;
transition: background-color var(--t-color);
}
.vp-navbar:not(.home) .divider-line {
background-color: var(--vp-c-gutter);
}
@media (min-width: 960px) {
.vp-navbar:not(.home.top) .divider-line {
background-color: var(--vp-c-gutter);
}
.vp-navbar:not(.has-sidebar, .home.top) .divider {
background-color: var(--vp-c-gutter);
}
}
</style>

View File

@ -8,19 +8,19 @@ const { theme } = useData()
<template>
<div
v-if="theme.appearance && theme.appearance !== 'force-dark'"
class="navbar-appearance"
class="vp-navbar-appearance"
>
<VPSwitchAppearance />
</div>
</template>
<style scoped>
.navbar-appearance {
.vp-navbar-appearance {
display: none;
}
@media (min-width: 1280px) {
.navbar-appearance {
.vp-navbar-appearance {
display: flex;
align-items: center;
}

View File

@ -10,10 +10,6 @@ import VPSwitchAppearance from '../VPSwitchAppearance.vue'
const { theme } = useData()
const { localeLinks, currentLang } = useLangs()
const hasExtraContent = computed(
() => theme.value.appearance || theme.value.social,
)
const social = computed(() => {
const includes = theme.value.navbarSocialInclude ?? []
if (!includes.length)
@ -23,10 +19,21 @@ const social = computed(() => {
({ icon }) => typeof icon === 'string' && includes.includes(icon),
)
})
const hasExtraContent = computed(
() =>
(localeLinks.value.length && currentLang.value.label)
|| theme.value.appearance
|| social.value?.length,
)
</script>
<template>
<VPFlyout v-if="hasExtraContent" class="navbar-extra" label="extra navigation">
<VPFlyout
v-if="hasExtraContent"
class="vp-navbar-extra"
label="extra navigation"
>
<div
v-if="localeLinks.length && currentLang.label"
class="group translations"
@ -60,19 +67,19 @@ const social = computed(() => {
</template>
<style scoped>
.navbar-extra {
.vp-navbar-extra {
display: none;
margin-right: -12px;
}
@media (min-width: 768px) {
.navbar-extra {
.vp-navbar-extra {
display: block;
}
}
@media (min-width: 1280px) {
.navbar-extra {
.vp-navbar-extra {
display: none;
}
}

View File

@ -9,7 +9,7 @@ defineEmits<(e: 'click') => void>()
<template>
<button
type="button"
class="navbar-hamburger"
class="vp-navbar-hamburger"
:class="{ active }"
aria-label="mobile navigation"
:aria-expanded="active"
@ -25,7 +25,7 @@ defineEmits<(e: 'click') => void>()
</template>
<style scoped>
.navbar-hamburger {
.vp-navbar-hamburger {
display: flex;
align-items: center;
justify-content: center;
@ -34,7 +34,7 @@ defineEmits<(e: 'click') => void>()
}
@media (min-width: 768px) {
.navbar-hamburger {
.vp-navbar-hamburger {
display: none;
}
}
@ -77,42 +77,42 @@ defineEmits<(e: 'click') => void>()
transform: translateX(4px);
}
.navbar-hamburger:hover .top {
.vp-navbar-hamburger:hover .top {
top: 0;
left: 0;
transform: translateX(4px);
}
.navbar-hamburger:hover .middle {
.vp-navbar-hamburger:hover .middle {
top: 6px;
left: 0;
transform: translateX(0);
}
.navbar-hamburger:hover .bottom {
.vp-navbar-hamburger:hover .bottom {
top: 12px;
left: 0;
transform: translateX(8px);
}
.navbar-hamburger.active .top {
.vp-navbar-hamburger.active .top {
top: 6px;
transform: translateX(0) rotate(225deg);
}
.navbar-hamburger.active .middle {
.vp-navbar-hamburger.active .middle {
top: 6px;
transform: translateX(16px);
}
.navbar-hamburger.active .bottom {
.vp-navbar-hamburger.active .bottom {
top: 6px;
transform: translateX(0) rotate(135deg);
}
.navbar-hamburger.active:hover .top,
.navbar-hamburger.active:hover .middle,
.navbar-hamburger.active:hover .bottom {
.vp-navbar-hamburger.active:hover .top,
.vp-navbar-hamburger.active:hover .middle,
.vp-navbar-hamburger.active:hover .bottom {
background-color: var(--vp-c-text-2);
transition:
top 0.25s,

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { useData } from '../../composables/data.js'
import NavBarMenuGroup from './NavBarMenuGroup.vue'
import NavBarMenuLink from './NavBarMenuLink.vue'
import VPNavBarMenuGroup from './VPNavBarMenuGroup.vue'
import VPNavBarMenuLink from './VPNavBarMenuLink.vue'
const { theme } = useData()
</script>
@ -10,23 +10,23 @@ const { theme } = useData()
<nav
v-if="theme.navbar"
aria-labelledby="main-nav-aria-label"
class="navbar-menu"
class="vp-navbar-menu"
>
<span id="main-nav-aria-label" class="visually-hidden">Main Navigation</span>
<template v-for="item in theme.navbar" :key="item.text">
<NavBarMenuLink v-if="'link' in item" :item="item" />
<NavBarMenuGroup v-else :item="item" />
<VPNavBarMenuLink v-if="'link' in item" :item="item" />
<VPNavBarMenuGroup v-else :item="item" />
</template>
</nav>
</template>
<style scoped>
.navbar-menu {
.vp-navbar-menu {
display: none;
}
@media (min-width: 768px) {
.navbar-menu {
.vp-navbar-menu {
display: flex;
}
}

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { resolveRouteFullPath } from 'vuepress/client'
import type { NavItem, NavItemWithChildren } from '../../../shared/index.js'
import { isActive } from '../../utils/index.js'
import VPFlyout from '../VPFlyout.vue'
@ -15,7 +16,7 @@ function isChildActive(navItem: NavItem) {
if ('link' in navItem) {
return isActive(
page.value.path,
navItem.link,
resolveRouteFullPath(navItem.link),
!!props.item.activeMatch,
)
}
@ -28,8 +29,11 @@ const childrenActive = computed(() => isChildActive(props.item))
<template>
<VPFlyout
class="navbar-menu-group" :class="{
active: isActive(page.path, item.activeMatch, !!item.activeMatch) || childrenActive,
class="vp-navbar-menu-group" :class="{
active: isActive(
page.path, item.activeMatch,
!!item.activeMatch,
) || childrenActive,
}"
:button="item.text"
:items="item.items"

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { resolveRoutePath } from 'vuepress/client'
import type { NavItemWithLink } from '../../../shared/index.js'
import { isActive } from '../../utils/index.js'
import VPLink from '../VPLink.vue'
@ -17,15 +18,18 @@ const { page } = useData()
class="navbar-menu-link" :class="{
active: isActive(
page.path,
item.activeMatch || item.link,
item.activeMatch || resolveRoutePath(item.link),
!!item.activeMatch,
),
}"
:href="item.link"
:no-icon="true"
no-icon
:target="item.target"
:rel="item.rel"
tabindex="0"
>
<VPIcon v-if="item.icon" :name="item.icon" />
<i v-text="item.text" />
<span v-html="item.text" />
</VPLink>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div class="navbar-search">
<div class="vp-navbar-search">
<DocSearch />
</div>
</template>

View File

@ -19,18 +19,18 @@ const social = computed(() => {
<template>
<VPSocialLinks
v-if="social"
class="navbar-social-links"
class="vp-navbar-social-links"
:links="social"
/>
</template>
<style scoped>
.navbar-social-links {
.vp-navbar-social-links {
display: none;
}
@media (min-width: 1280px) {
.navbar-social-links {
.vp-navbar-social-links {
display: flex;
align-items: center;
}

View File

@ -11,14 +11,18 @@ const routeLocale = useRouteLocale()
</script>
<template>
<div class="navbar-title" :class="{ 'has-sidebar': hasSidebar }">
<div class="vp-navbar-title" :class="{ 'has-sidebar': hasSidebar }">
<VPLink class="title" :href="theme.home ?? routeLocale">
<slot name="nav-bar-title-before" />
<VPImage
v-if="theme.logo"
class="logo"
:image="{ light: theme.logo, dark: theme.logoDark || theme.logo }"
/>
{{ site.title }}
<span>{{ site.title }}</span>
<slot name="nav-bar-title-after" />
</VPLink>
</div>
</template>
@ -36,16 +40,12 @@ const routeLocale = useRouteLocale()
transition: opacity var(--t-color), color var(--t-color), border-bottom var(--t-color);
}
.title:hover {
opacity: 0.6;
}
@media (min-width: 960px) {
.title {
flex-shrink: 0;
}
.navbar-title.has-sidebar .title {
.vp-navbar-title.has-sidebar .title {
border-bottom-color: var(--vp-c-divider);
}
}

View File

@ -11,7 +11,7 @@ const { currentLang, localeLinks } = useLangs()
<template>
<VPFlyout
v-if="localeLinks.length && currentLang.label"
class="navbar-translations"
class="vp-navbar-translations"
icon="vpi-languages"
:label="theme.selectLanguageText || 'Change Language'"
>
@ -28,12 +28,12 @@ const { currentLang, localeLinks } = useLangs()
</template>
<style lang="scss" scoped>
.navbar-translations {
.vp-navbar-translations {
display: none;
}
@media (min-width: 1280px) {
.navbar-translations {
.vp-navbar-translations {
display: flex;
align-items: center;
}

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
import { useScrollLock } from '@vueuse/core'
import { inBrowser } from '../../utils/index.js'
import NavScreenAppearance from './NavScreenAppearance.vue'
import NavScreenMenu from './NavScreenMenu.vue'
import NavScreenSocialLinks from './NavScreenSocialLinks.vue'
import NavScreenTranslates from './NavScreenTranslations.vue'
import VPNavScreenAppearance from './VPNavScreenAppearance.vue'
import VPNavScreenMenu from './VPNavScreenMenu.vue'
import VPNavScreenSocialLinks from './VPNavScreenSocialLinks.vue'
import VPNavScreenTranslates from './VPNavScreenTranslations.vue'
defineProps<{
open: boolean
@ -19,25 +19,30 @@ const isLocked = useScrollLock(inBrowser ? document.body : null)
@enter="isLocked = true"
@after-leave="isLocked = false"
>
<div v-if="open" id="navScreen" class="nav-screen">
<div v-if="open" id="navScreen" class="vp-nav-screen">
<div class="container">
<NavScreenMenu class="menu" />
<NavScreenTranslates class="translations" />
<NavScreenAppearance class="appearance" />
<NavScreenSocialLinks class="social-links" />
<slot name="nav-screen-content-before" />
<VPNavScreenMenu class="menu" />
<VPNavScreenTranslates class="translations" />
<VPNavScreenAppearance class="appearance" />
<VPNavScreenSocialLinks class="social-links" />
<slot name="nav-screen-content-after" />
</div>
</div>
</Transition>
</template>
<style scoped>
.nav-screen {
.vp-nav-screen {
position: fixed;
inset: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) 0 0;
top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 1px);
/* rtl:ignore */
right: 0;
bottom: 0;
/* rtl:ignore */
left: 0;
width: 100%;
padding: 0 32px;
overflow-y: auto;
@ -53,28 +58,28 @@ const isLocked = useScrollLock(inBrowser ? document.body : null)
margin: 0 auto;
}
.nav-screen.fade-enter-active,
.nav-screen.fade-leave-active {
.vp-nav-screen.fade-enter-active,
.vp-nav-screen.fade-leave-active {
transition: opacity var(--t-color);
}
.nav-screen.fade-enter-active .container,
.nav-screen.fade-leave-active .container {
.vp-nav-screen.fade-enter-active .container,
.vp-nav-screen.fade-leave-active .container {
transition: transform var(--t-color);
}
.nav-screen.fade-enter-from,
.nav-screen.fade-leave-to {
.vp-nav-screen.fade-enter-from,
.vp-nav-screen.fade-leave-to {
opacity: 0;
}
.nav-screen.fade-enter-from .container,
.nav-screen.fade-leave-to .container {
.vp-nav-screen.fade-enter-from .container,
.vp-nav-screen.fade-leave-to .container {
transform: translateY(-8px);
}
@media (min-width: 768px) {
.nav-screen {
.vp-nav-screen {
display: none;
}
}

View File

@ -8,7 +8,7 @@ const { theme } = useData()
<template>
<div
v-if="theme.appearance && theme.appearance !== 'force-dark'"
class="nav-screen-appearance"
class="vp-nav-screen-appearance"
>
<p class="text">
{{ theme.appearanceText ?? 'Appearance' }}
@ -18,7 +18,7 @@ const { theme } = useData()
</template>
<style scoped>
.nav-screen-appearance {
.vp-nav-screen-appearance {
display: flex;
align-items: center;
justify-content: space-between;

View File

@ -1,21 +1,21 @@
<script lang="ts" setup>
import { useData } from '../../composables/data.js'
import NavScreenMenuGroup from './NavScreenMenuGroup.vue'
import NavScreenMenuLink from './NavScreenMenuLink.vue'
import VPNavScreenMenuGroup from './VPNavScreenMenuGroup.vue'
import VPNavScreenMenuLink from './VPNavScreenMenuLink.vue'
const { theme } = useData()
</script>
<template>
<nav v-if="theme.navbar" class="nav-screen-menu">
<nav v-if="theme.navbar" class="vp-nav-screen-menu">
<template v-for="item in theme.navbar" :key="item.text">
<NavScreenMenuLink
<VPNavScreenMenuLink
v-if="'link' in item"
:text="item.text"
:link="item.link"
:icon="item.icon"
/>
<NavScreenMenuGroup
<VPNavScreenMenuGroup
v-else
:text="item.text || ''"
:items="item.items"

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import VPIcon from '../VPIcon.vue'
import NavScreenMenuGroupLink from './NavScreenMenuGroupLink.vue'
import NavScreenMenuGroupSection from './NavScreenMenuGroupSection.vue'
import VPNavScreenMenuGroupLink from './VPNavScreenMenuGroupLink.vue'
import VPNavScreenMenuGroupSection from './VPNavScreenMenuGroupSection.vue'
const props = defineProps<{
text: string
@ -22,7 +22,7 @@ function toggle() {
</script>
<template>
<div class="nav-screen-menu-group" :class="{ open: isOpen }">
<div class="vp-nav-screen-menu-group" :class="{ open: isOpen }">
<button
class="button"
:aria-controls="groupId"
@ -31,7 +31,7 @@ function toggle() {
>
<span class="button-text">
<VPIcon v-if="icon" :name="icon" />
<i v-text="text" />
<span v-html="text" />
</span>
<span class="vpi-plus button-icon" />
</button>
@ -39,7 +39,7 @@ function toggle() {
<div :id="groupId" class="items">
<template v-for="item in items" :key="item.text">
<div v-if="'link' in item" :key="item.text" class="item">
<NavScreenMenuGroupLink
<VPNavScreenMenuGroupLink
:text="item.text"
:link="item.link"
:icon="item.icon"
@ -47,7 +47,7 @@ function toggle() {
</div>
<div v-else class="group">
<NavScreenMenuGroupSection
<VPNavScreenMenuGroupSection
:text="item.text"
:items="item.items"
:icon="item.icon"
@ -59,22 +59,22 @@ function toggle() {
</template>
<style scoped>
.nav-screen-menu-group {
.vp-nav-screen-menu-group {
height: 48px;
overflow: hidden;
border-bottom: 1px solid var(--vp-c-divider);
transition: border-color var(--t-color);
}
.nav-screen-menu-group .items {
.vp-nav-screen-menu-group .items {
visibility: hidden;
}
.nav-screen-menu-group.open .items {
.vp-nav-screen-menu-group.open .items {
visibility: visible;
}
.nav-screen-menu-group.open {
.vp-nav-screen-menu-group.open {
height: auto;
padding-bottom: 10px;
}
@ -96,7 +96,7 @@ function toggle() {
color: var(--vp-c-brand-1);
}
.nav-screen-menu-group.open .button {
.vp-nav-screen-menu-group.open .button {
padding-bottom: 6px;
color: var(--vp-c-brand-1);
}
@ -110,15 +110,11 @@ function toggle() {
transform 0.25s;
}
.nav-screen-menu-group.open .button-icon {
.vp-nav-screen-menu-group.open .button-icon {
/* rtl:ignore */
transform: rotate(45deg);
}
.button-text i {
font-style: normal;
}
.group:first-child {
padding-top: 0;
}

View File

@ -14,7 +14,7 @@ const closeScreen = inject('close-screen') as () => void
<template>
<VPLink
class="nav-screen-menu-group-link"
class="vp-nav-screen-menu-group-link"
:href="link"
@click="closeScreen"
>
@ -24,7 +24,7 @@ const closeScreen = inject('close-screen') as () => void
</template>
<style scoped>
.nav-screen-menu-group-link {
.vp-nav-screen-menu-group-link {
display: block;
margin-left: 12px;
font-size: 14px;
@ -34,7 +34,7 @@ const closeScreen = inject('close-screen') as () => void
transition: color var(--t-color);
}
.nav-screen-menu-group-link:hover {
.vp-nav-screen-menu-group-link:hover {
color: var(--vp-c-brand-1);
}
</style>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { NavItemWithLink } from '../../../shared/index.js'
import VPIcon from '../VPIcon.vue'
import NavScreenMenuGroupLink from './NavScreenMenuGroupLink.vue'
import VPNavScreenMenuGroupLink from './VPNavScreenMenuGroupLink.vue'
defineProps<{
icon?: string | { svg: string }
@ -11,12 +11,12 @@ defineProps<{
</script>
<template>
<div class="nav-screen-menu-group-section">
<div class="vp-nav-screen-menu-group-section">
<p v-if="text" class="title">
<VPIcon v-if="icon" :name="icon" />
{{ text }}
</p>
<NavScreenMenuGroupLink
<VPNavScreenMenuGroupLink
v-for="item in items"
:key="item.text"
:text="item.text"
@ -27,7 +27,7 @@ defineProps<{
</template>
<style scoped>
.nav-screen-menu-group-section {
.vp-nav-screen-menu-group-section {
display: block;
}

View File

@ -13,14 +13,14 @@ const closeScreen = inject('close-screen') as () => void
</script>
<template>
<VPLink class="nav-screen-menu-link" :href="link" @click="closeScreen">
<VPLink class="vp-nav-screen-menu-link" :href="link" @click="closeScreen">
<VPIcon v-if="icon" :name="icon" />
<i v-text="text" />
</VPLink>
</template>
<style scoped>
.nav-screen-menu-link {
.vp-nav-screen-menu-link {
display: block;
padding: 12px 0 11px;
font-size: 14px;
@ -33,7 +33,7 @@ const closeScreen = inject('close-screen') as () => void
color var(--t-color);
}
.nav-screen-menu-link:hover {
.vp-nav-screen-menu-link:hover {
color: var(--vp-c-brand-1);
}
</style>

View File

@ -8,7 +8,7 @@ const { theme } = useData()
<template>
<VPSocialLinks
v-if="theme.social"
class="VPNavScreenSocialLinks"
class="vp-nav-screen-social-links"
:links="theme.social"
/>
</template>

View File

@ -14,7 +14,7 @@ function toggle() {
<template>
<div
v-if="localeLinks.length && currentLang.label"
class="nav-screen-translations"
class="vp-nav-screen-translations"
:class="{ open: isOpen }"
>
<button class="title" @click="toggle">
@ -34,12 +34,12 @@ function toggle() {
</template>
<style lang="scss" scoped>
.nav-screen-translations {
.vp-nav-screen-translations {
height: 24px;
overflow: hidden;
}
.nav-screen-translations.open {
.vp-nav-screen-translations.open {
height: auto;
}

View File

@ -1,54 +0,0 @@
<script lang="ts" setup>
import { computed, provide } from 'vue'
import { useNav } from '../../composables/nav.js'
import { useData } from '../../composables/data.js'
import Navbar from './NavBar.vue'
import NavScreen from './NavScreen.vue'
const { page } = useData()
const { isScreenOpen, closeScreen, toggleScreen } = useNav()
const fixedInclude = ['blog', 'friends', 'blog-archives', 'blog-tags']
const fixed = computed(() => {
return fixedInclude.includes(page.value.type as string)
})
provide('close-screen', closeScreen)
</script>
<template>
<div class="nav-wrapper" :class="{ fixed }">
<Navbar :is-screen-open="isScreenOpen" @toggle-screen="toggleScreen" />
<NavScreen :open="isScreenOpen" />
</div>
</template>
<style scoped>
.nav-wrapper {
position: relative;
top: var(--vp-layout-top-height, 0);
/* rtl:ignore */
left: 0;
z-index: var(--vp-z-index-nav);
width: 100%;
pointer-events: none;
}
.nav-wrapper.fixed {
position: fixed;
}
.nav-wrapper.fixed :deep(.navbar-wrapper) {
background-color: var(--vp-nav-bg-color);
border-bottom-color: var(--vp-c-gutter);
}
@media (min-width: 960px) {
.nav-wrapper {
position: fixed;
}
}
</style>

View File

@ -26,7 +26,7 @@ function onSubmit() {
</script>
<template>
<div class="encrypt-form">
<div class="vp-encrypt-form">
<p class="encrypt-text" v-html="info ?? 'Only Password can access this site'" />
<p class="encrypt-input-wrapper">
<span class="vpi-lock icon-lock" />
@ -47,7 +47,7 @@ function onSubmit() {
</template>
<style scoped>
.encrypt-form {
.vp-encrypt-form {
margin-top: 20px;
}

View File

@ -13,7 +13,7 @@ const title = computed(() => avatar.value?.name || site.value.title)
</script>
<template>
<div class="global-encrypt-wrapper">
<div class="vp-global-encrypt">
<div class="global-encrypt-container">
<div v-if="avatar || title" class="profile">
<p v-if="avatar" class="avatar" :class="{ circle: avatar.circle }">
@ -30,7 +30,7 @@ const title = computed(() => avatar.value?.name || site.value.title)
</template>
<style scoped>
.global-encrypt-wrapper {
.vp-global-encrypt {
display: flex;
flex: 1;
width: 100%;
@ -40,7 +40,7 @@ const title = computed(() => avatar.value?.name || site.value.title)
}
@media (min-width: 768px) {
.global-encrypt-wrapper {
.vp-global-encrypt {
align-items: center;
justify-content: center;
background-color: var(--vp-c-bg-soft);

View File

@ -8,7 +8,7 @@ const { comparePage } = usePageEncrypt()
</script>
<template>
<div class="page-encrypt-wrapper">
<div class="vp-page-encrypt">
<div class="logo">
<span class="vpi-lock icon-lock-head" />
</div>
@ -17,7 +17,7 @@ const { comparePage } = usePageEncrypt()
</template>
<style scoped>
.page-encrypt-wrapper .logo {
.vp-page-encrypt .logo {
text-align: center;
}
@ -29,7 +29,7 @@ const { comparePage } = usePageEncrypt()
}
@media (min-width: 768px) {
.page-encrypt-wrapper {
.vp-page-encrypt {
width: 400px;
padding: 20px;
margin: 40px auto 0;

View File

@ -10,7 +10,7 @@ defineProps<{
</script>
<template>
<div class="menu-group">
<div class="vp-menu-group">
<p v-if="text" class="title">
<VPIcon v-if="icon" :name="icon" />
<span v-text="text" />
@ -23,20 +23,20 @@ defineProps<{
</template>
<style scoped>
.menu-group {
.vp-menu-group {
padding: 12px 12px 0;
margin: 12px -12px 0;
border-top: 1px solid var(--vp-c-divider);
transition: border-top var(--t-color);
}
.menu-group:first-child {
.vp-menu-group:first-child {
padding-top: 0;
margin-top: 0;
border-top: 0;
}
.menu-group + .menu-group {
.vp-menu-group + .vp-menu-group {
margin-top: 12px;
border-top: 1px solid var(--vp-c-divider);
}

View File

@ -12,7 +12,7 @@ const { page } = useData()
</script>
<template>
<div class="menu-link">
<div class="vp-menu-link">
<VPLink
:class="{
active: isActive(
@ -30,7 +30,7 @@ const { page } = useData()
</template>
<style scoped>
.menu-group + .menu-link {
.vp-menu-group + .vp-menu-link {
padding: 12px 12px 0;
margin: 12px -12px 0;
border-top: 1px solid var(--vp-c-divider);

View File

@ -33,7 +33,7 @@ function focusOnTargetAnchor({ target }: Event) {
<span ref="backToTop" tabindex="-1" />
<a
href="#VPContent"
class="skip-link visually-hidden"
class="vp-skip-link visually-hidden"
@click="focusOnTargetAnchor"
>
Skip to content
@ -41,7 +41,7 @@ function focusOnTargetAnchor({ target }: Event) {
</template>
<style scoped>
.skip-link {
.vp-skip-link {
top: 8px;
left: 8px;
z-index: 999;
@ -55,7 +55,7 @@ function focusOnTargetAnchor({ target }: Event) {
box-shadow: var(--vp-shadow-3);
}
.skip-link:focus {
.vp-skip-link:focus {
width: auto;
height: auto;
clip: auto;
@ -63,7 +63,7 @@ function focusOnTargetAnchor({ target }: Event) {
}
@media (min-width: 1280px) {
.skip-link {
.vp-skip-link {
top: 14px;
left: 16px;
}

View File

@ -4,7 +4,7 @@ import { watch } from 'vue'
import VPBackdrop from '../components/VPBackdrop.vue'
import VPContent from '../components/VPContent.vue'
import VPLocalNav from '../components/VPLocalNav.vue'
import Nav from '../components/Nav/index.vue'
import VPNav from '../components/Nav/VPNav.vue'
import VPSidebar from '../components/VPSidebar.vue'
import VPSkipLink from '../components/VPSkipLink.vue'
import VPFooter from '../components/VPFooter.vue'
@ -42,7 +42,26 @@ useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
<VPBackdrop :show="isSidebarOpen" @click="closeSidebar" />
<Nav />
<VPNav>
<template #nav-bar-title-before>
<slot name="nav-bar-title-before" />
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after" />
</template>
<template #nav-bar-content-before>
<slot name="nav-bar-content-before" />
</template>
<template #nav-bar-content-after>
<slot name="nav-bar-content-after" />
</template>
<template #nav-screen-content-before>
<slot name="nav-screen-content-before" />
</template>
<template #nav-screen-content-after>
<slot name="nav-screen-content-after" />
</template>
</VPNav>
<VPLocalNav :open="isSidebarOpen" :show-outline="isPageDecrypted" @open-menu="openSidebar" />

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useRouteLocale, withBase } from 'vuepress/client'
import Nav from '../components/Nav/index.vue'
import VPNav from '../components/Nav/VPNav.vue'
import VPSkipLink from '../components/VPSkipLink.vue'
import VPFooter from '../components/VPFooter.vue'
import { useData } from '../composables/data.js'
@ -14,7 +14,27 @@ const { theme } = useData()
<slot name="layout-top" />
<VPSkipLink />
<Nav />
<VPNav>
<template #nav-bar-title-before>
<slot name="nav-bar-title-before" />
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after" />
</template>
<template #nav-bar-content-before>
<slot name="nav-bar-content-before" />
</template>
<template #nav-bar-content-after>
<slot name="nav-bar-content-after" />
</template>
<template #nav-screen-content-before>
<slot name="nav-screen-content-before" />
</template>
<template #nav-screen-content-after>
<slot name="nav-screen-content-after" />
</template>
</VPNav>
<div id="VPContent" class="vp-content">
<slot name="not-found">
<div class="vp-not-found">

View File

@ -1,8 +1,21 @@
.navbar-search {
.vp-navbar-search {
display: flex;
align-items: center;
}
@media (min-width: 768px) {
.vp-navbar-search {
flex-grow: 1;
padding-left: 24px;
}
}
@media (min-width: 960px) {
.vp-navbar-search {
padding-left: 32px;
}
}
/* plugin-docsearch */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1);
@ -27,7 +40,7 @@
--docsearch-hit-shadow: none;
}
.navbar-search .DocSearch-Button {
.vp-navbar-search .DocSearch-Button {
display: flex;
align-items: center;
justify-content: center !important;
@ -39,20 +52,20 @@
transition: border-color var(--t-color), background var(--t-color);
}
.navbar-search .DocSearch-Button:hover {
.vp-navbar-search .DocSearch-Button:hover {
background: var(--docsearch-searchbox-focus-background);
}
.navbar-search .DocSearch-Button:focus {
.vp-navbar-search .DocSearch-Button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
.navbar-search .DocSearch-Button:focus:not(:focus-visible) {
.vp-navbar-search .DocSearch-Button:focus:not(:focus-visible) {
outline: none !important;
}
.navbar-search #docsearch-container {
.vp-navbar-search #docsearch-container {
min-width: 32px;
}
@ -166,7 +179,7 @@
/* plugin-docsearch */
/* plugin-search */
.navbar-search .search-box input {
.vp-navbar-search .search-box input {
padding: 0 0.3rem 0 1.655rem;
background-position: 0.5rem 0.4rem;
}
@ -174,26 +187,26 @@
/* plugin-search */
/* stylelint-disable-next-line order/order */
@media (min-width: 768px) {
.navbar-search {
.vp-navbar-search {
flex-grow: 1;
padding-left: 24px;
}
}
@media (min-width: 960px) {
.navbar-search {
.vp-navbar-search {
padding-left: 32px;
}
}
@media print {
.navbar-search {
.vp-navbar-search {
display: none;
}
}
@media (min-width: 768px) {
.navbar-search .DocSearch-Button {
.vp-navbar-search .DocSearch-Button {
justify-content: flex-start;
width: 100%;
height: 40px;
@ -203,7 +216,7 @@
border-radius: 8px;
}
.navbar-search .DocSearch-Button:hover {
.vp-navbar-search .DocSearch-Button:hover {
background: var(--docsearch-searchbox-focus-background);
border-color: var(--vp-c-brand-1);
}

View File

@ -26,13 +26,24 @@ export interface PlumeNormalFrontmatter extends PageFrontmatter {
*/
pageClass?: string
/**
*
*
* @default true
*/
navbar?: boolean
/**
*
*
* @default true
*/
backToTop?: boolean
/**
*
*
* @default true
*/
externalLinkIcon?: boolean

View File

@ -4,6 +4,8 @@ export interface NavItemWithLink {
text: string
link: string
icon?: string | { svg: string }
rel?: string
target?: string
/**
* `activeMatch` is expected to be a regex string. We can't use actual