pull/3359/head
Kia King Ishii 2 years ago
parent 8e51e54548
commit 0d4bd4f4cc

@ -5,7 +5,6 @@ import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPDocAside from './VPDocAside.vue' import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue' import VPDocFooter from './VPDocFooter.vue'
import VPDocOutlineDropdown from './VPDocOutlineDropdown.vue'
const { theme } = useData() const { theme } = useData()
@ -43,7 +42,6 @@ const pageName = computed(() =>
<div class="content"> <div class="content">
<div class="content-container"> <div class="content-container">
<slot name="doc-before" /> <slot name="doc-before" />
<VPDocOutlineDropdown />
<main class="main"> <main class="main">
<Content <Content
class="vp-doc" class="vp-doc"
@ -70,16 +68,6 @@ const pageName = computed(() =>
width: 100%; width: 100%;
} }
.VPDoc .VPDocOutlineDropdown {
display: none;
}
@media (min-width: 960px) and (max-width: 1279px) {
.VPDoc .VPDocOutlineDropdown {
display: block;
}
}
@media (min-width: 768px) { @media (min-width: 768px) {
.VPDoc { .VPDoc {
padding: 48px 32px 128px; padding: 48px 32px 128px;
@ -88,7 +76,7 @@ const pageName = computed(() =>
@media (min-width: 960px) { @media (min-width: 960px) {
.VPDoc { .VPDoc {
padding: 32px 32px 0; padding: 48px 32px 0;
} }
.VPDoc:not(.has-sidebar) .container { .VPDoc:not(.has-sidebar) .container {
@ -147,7 +135,7 @@ const pageName = computed(() =>
.aside-container { .aside-container {
position: fixed; position: fixed;
top: 0; top: 0;
padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 32px); padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);
width: 224px; width: 224px;
height: 100vh; height: 100vh;
overflow-x: hidden; overflow-x: hidden;
@ -171,7 +159,7 @@ const pageName = computed(() =>
.aside-content { .aside-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 32px)); min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));
padding-bottom: 32px; padding-bottom: 32px;
} }

@ -80,9 +80,8 @@ useActiveAnchor(container, marker)
} }
.outline-title { .outline-title {
letter-spacing: 0.4px; line-height: 32px;
line-height: 28px; font-size: 14px;
font-size: 13px;
font-weight: 600; font-weight: 600;
} }
</style> </style>

@ -1,85 +0,0 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import { useData } from '../composables/data'
import { getHeaders, resolveTitle, type MenuItem } from '../composables/outline'
import VPDocOutlineItem from './VPDocOutlineItem.vue'
import { onContentUpdated } from 'vitepress'
import VPIconChevronRight from './icons/VPIconChevronRight.vue'
const { frontmatter, theme } = useData()
const open = ref(false)
onContentUpdated(() => {
open.value = false
})
const headers = shallowRef<MenuItem[]>([])
onContentUpdated(() => {
headers.value = getHeaders(
frontmatter.value.outline ?? theme.value.outline
)
})
</script>
<template>
<div class="VPDocOutlineDropdown" v-if="headers.length > 0">
<!-- <button @click="open = !open" :class="{ open }">
{{ resolveTitle(theme) }}
<VPIconChevronRight class="icon" />
</button>
<div class="items" v-if="open">
<VPDocOutlineItem :headers="headers" />
</div> -->
</div>
</template>
<style scoped>
.VPDocOutlineDropdown {
margin-bottom: 48px;
}
.VPDocOutlineDropdown button {
display: block;
font-size: 14px;
font-weight: 500;
line-height: 24px;
border: 1px solid var(--vp-c-border);
padding: 4px 12px;
color: var(--vp-c-text-2);
background-color: var(--vp-c-default-soft);
border-radius: 8px;
transition: color 0.5s;
}
.VPDocOutlineDropdown button:hover {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
.VPDocOutlineDropdown button.open {
color: var(--vp-c-text-1);
}
.icon {
display: inline-block;
vertical-align: middle;
width: 16px;
height: 16px;
fill: currentColor;
}
:deep(.outline-link) {
font-size: 14px;
font-weight: 400;
}
.open > .icon {
transform: rotate(90deg);
}
.items {
margin-top: 12px;
border-left: 1px solid var(--vp-c-divider);
}
</style>

@ -14,7 +14,7 @@ function onClick({ target: el }: Event) {
</script> </script>
<template> <template>
<ul :class="root ? 'root' : 'nested'"> <ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers"> <li v-for="{ children, link, title } in headers">
<a class="outline-link" :href="link" @click="onClick" :title="title">{{ title }}</a> <a class="outline-link" :href="link" @click="onClick" :title="title">{{ title }}</a>
<template v-if="children?.length"> <template v-if="children?.length">
@ -31,18 +31,20 @@ function onClick({ target: el }: Event) {
} }
.nested { .nested {
padding-right: 16px;
padding-left: 16px; padding-left: 16px;
} }
.outline-link { .outline-link {
display: block; display: block;
line-height: 28px; line-height: 32px;
font-size: 14px;
font-weight: 400;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
transition: color 0.5s; transition: color 0.5s;
font-weight: 400;
} }
.outline-link:hover, .outline-link:hover,

@ -1,9 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core' import { useWindowScroll } from '@vueuse/core'
import { onContentUpdated } from 'vitepress' import { onContentUpdated } from 'vitepress'
import { computed, onMounted, ref, shallowRef } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { getHeaders, type MenuItem } from '../composables/outline' import { useLocalNav } from '../composables/local-nav'
import { getHeaders } from '../composables/outline'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue' import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
import VPIconAlignLeft from './icons/VPIconAlignLeft.vue' import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
@ -18,9 +19,9 @@ defineEmits<{
const { theme, frontmatter } = useData() const { theme, frontmatter } = useData()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
const { headers } = useLocalNav()
const { y } = useWindowScroll() const { y } = useWindowScroll()
const headers = shallowRef<MenuItem[]>([])
const navHeight = ref(0) const navHeight = ref(0)
onMounted(() => { onMounted(() => {
@ -36,36 +37,44 @@ onContentUpdated(() => {
}) })
const empty = computed(() => { const empty = computed(() => {
return headers.value.length === 0 && !hasSidebar.value return headers.value.length === 0
})
const emptyAndNoSidebar = computed(() => {
return empty.value && !hasSidebar.value
}) })
const classes = computed(() => { const classes = computed(() => {
return { return {
VPLocalNav: true, VPLocalNav: true,
fixed: empty.value 'has-sidebar': hasSidebar.value,
empty: empty.value,
fixed: emptyAndNoSidebar.value
} }
}) })
</script> </script>
<template> <template>
<div <div
v-if="frontmatter.layout !== 'home' && (!empty || y >= navHeight)" v-if="frontmatter.layout !== 'home' && (!emptyAndNoSidebar || y >= navHeight)"
:class="classes" :class="classes"
> >
<button <div class="container">
v-if="hasSidebar" <button
class="menu" v-if="hasSidebar"
:aria-expanded="open" class="menu"
aria-controls="VPSidebarNav" :aria-expanded="open"
@click="$emit('open-menu')" aria-controls="VPSidebarNav"
> @click="$emit('open-menu')"
<VPIconAlignLeft class="menu-icon" /> >
<span class="menu-text"> <VPIconAlignLeft class="menu-icon" />
{{ theme.sidebarMenuLabel || 'Menu' }} <span class="menu-text">
</span> {{ theme.sidebarMenuLabel || 'Menu' }}
</button> </span>
</button>
<VPLocalNavOutlineDropdown :headers="headers" :navHeight="navHeight" />
<VPLocalNavOutlineDropdown :headers="headers" :navHeight="navHeight" />
</div>
</div> </div>
</template> </template>
@ -76,9 +85,6 @@ const classes = computed(() => {
/*rtl:ignore*/ /*rtl:ignore*/
left: 0; left: 0;
z-index: var(--vp-z-index-local-nav); z-index: var(--vp-z-index-local-nav);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--vp-c-gutter); border-bottom: 1px solid var(--vp-c-gutter);
padding-top: var(--vp-layout-top-height, 0px); padding-top: var(--vp-layout-top-height, 0px);
width: 100%; width: 100%;
@ -94,6 +100,14 @@ const classes = computed(() => {
top: var(--vp-nav-height); top: var(--vp-nav-height);
z-index: 1; z-index: 1;
} }
.VPLocalNav.has-sidebar {
padding-left: var(--vp-sidebar-width);
}
.VPLocalNav.empty {
display: none;
}
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {
@ -102,6 +116,18 @@ const classes = computed(() => {
} }
} }
@media (min-width: 1440px) {
.VPLocalNav.has-sidebar {
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
.container {
display: flex;
justify-content: space-between;
align-items: center;
}
.menu { .menu {
display: flex; display: flex;
align-items: center; align-items: center;
@ -124,6 +150,12 @@ const classes = computed(() => {
} }
} }
@media (min-width: 960px) {
.menu {
display: none;
}
}
.menu-icon { .menu-icon {
margin-right: 8px; margin-right: 8px;
width: 16px; width: 16px;

@ -76,6 +76,12 @@ function scrollToTop() {
padding: 12px 20px 11px; padding: 12px 20px 11px;
} }
@media (min-width: 960px) {
.VPLocalNavOutlineDropdown {
padding: 12px 36px 11px;
}
}
.VPLocalNavOutlineDropdown button { .VPLocalNavOutlineDropdown button {
display: block; display: block;
font-size: 12px; font-size: 12px;
@ -95,6 +101,12 @@ function scrollToTop() {
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
@media (min-width: 960px) {
.VPLocalNavOutlineDropdown button {
font-size: 14px;
}
}
.icon { .icon {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@ -104,18 +116,13 @@ function scrollToTop() {
fill: currentColor; fill: currentColor;
} }
:deep(.outline-link) {
font-size: 14px;
padding: 2px 0;
}
.open > .icon { .open > .icon {
transform: rotate(90deg); transform: rotate(90deg);
} }
.items { .items {
position: absolute; position: absolute;
top: 64px; top: 40px;
right: 16px; right: 16px;
left: 16px; left: 16px;
display: grid; display: grid;
@ -128,6 +135,14 @@ function scrollToTop() {
box-shadow: var(--vp-shadow-3); box-shadow: var(--vp-shadow-3);
} }
@media (min-width: 960px) {
.items {
right: auto;
left: calc(var(--vp-sidebar-width) + 32px);
width: 320px;
}
}
.header { .header {
background-color: var(--vp-c-bg-soft); background-color: var(--vp-c-bg-soft);
} }

@ -2,6 +2,7 @@
import { useWindowScroll } from '@vueuse/core' import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue' import { ref, watchPostEffect } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useLocalNav } from '../composables/local-nav'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPNavBarAppearance from './VPNavBarAppearance.vue' import VPNavBarAppearance from './VPNavBarAppearance.vue'
import VPNavBarExtra from './VPNavBarExtra.vue' import VPNavBarExtra from './VPNavBarExtra.vue'
@ -22,6 +23,7 @@ defineEmits<{
const { y } = useWindowScroll() const { y } = useWindowScroll()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
const { hasLocalNav } = useLocalNav()
const { frontmatter } = useData() const { frontmatter } = useData()
const classes = ref<Record<string, boolean>>({}) const classes = ref<Record<string, boolean>>({})
@ -29,6 +31,7 @@ const classes = ref<Record<string, boolean>>({})
watchPostEffect(() => { watchPostEffect(() => {
classes.value = { classes.value = {
'has-sidebar': hasSidebar.value, 'has-sidebar': hasSidebar.value,
'has-local-nav': hasLocalNav.value,
top: frontmatter.value.layout === 'home' && y.value === 0, top: frontmatter.value.layout === 'home' && y.value === 0,
} }
}) })
@ -60,7 +63,10 @@ watchPostEffect(() => {
</div> </div>
</div> </div>
</div> </div>
<div class="divider" />
<div class="divider">
<div class="divider-line" />
</div>
</div> </div>
</template> </template>
@ -70,9 +76,18 @@ watchPostEffect(() => {
height: var(--vp-nav-height); height: var(--vp-nav-height);
pointer-events: none; pointer-events: none;
white-space: nowrap; white-space: nowrap;
transition: background-color 0.5s;
}
.VPNavBar.has-local-nav {
background-color: var(--vp-nav-bg-color);
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.VPNavBar.has-local-nav {
background-color: transparent;
}
.VPNavBar:not(.has-sidebar):not(.top) { .VPNavBar:not(.has-sidebar):not(.top) {
background-color: var(--vp-nav-bg-color); background-color: var(--vp-nav-bg-color);
} }
@ -168,15 +183,19 @@ watchPostEffect(() => {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
height: calc(var(--vp-nav-height) - 1px); height: var(--vp-nav-height);
transition: background-color 0.5s; transition: background-color 0.5s;
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.VPNavBar:not(.top) .content-body{ .VPNavBar:not(.top) .content-body {
position: relative; position: relative;
background-color: var(--vp-nav-bg-color); background-color: var(--vp-nav-bg-color);
} }
.VPNavBar:not(.has-sidebar):not(.top) .content-body {
background-color: transparent;
}
} }
@media (max-width: 767px) { @media (max-width: 767px) {
@ -218,11 +237,33 @@ watchPostEffect(() => {
@media (min-width: 960px) { @media (min-width: 960px) {
.divider { .divider {
padding-left: var(--vp-sidebar-width);
}
}
@media (min-width: 1440px) {
.divider {
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
.divider-line {
width: 100%;
height: 1px;
transition: background-color 0.5s;
}
.VPNavBar.has-local-nav .divider-line {
background-color: var(--vp-c-gutter);
}
@media (min-width: 960px) {
.VPNavBar:not(.top) .divider-line {
background-color: var(--vp-c-gutter); background-color: var(--vp-c-gutter);
} }
.VPNavBar:not(.has-sidebar):not(.top) .divider { .VPNavBar:not(.has-sidebar):not(.top) .divider {
/* background-color: var(--vp-c-gutter); */ background-color: var(--vp-c-gutter);
} }
} }
</style> </style>

@ -0,0 +1,24 @@
import { onContentUpdated } from 'vitepress'
import { type DefaultTheme } from 'vitepress/theme'
import { computed, shallowRef } from 'vue'
import { getHeaders, type MenuItem } from '../composables/outline'
import { useData } from './data'
export function useLocalNav(): DefaultTheme.DocLocalNav {
const { theme, frontmatter } = useData()
const headers = shallowRef<MenuItem[]>([])
const hasLocalNav = computed(() => {
return headers.value.length > 0
})
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
})
return {
headers,
hasLocalNav
}
}

@ -181,7 +181,7 @@ export function useActiveAnchor(
if (activeLink) { if (activeLink) {
activeLink.classList.add('active') activeLink.classList.add('active')
marker.value.style.top = activeLink.offsetTop + 33 + 'px' marker.value.style.top = activeLink.offsetTop + 39 + 'px'
marker.value.style.opacity = '1' marker.value.style.opacity = '1'
} else { } else {
marker.value.style.top = '33px' marker.value.style.top = '33px'

@ -27,6 +27,7 @@ export { default as VPTeamPageSection } from './components/VPTeamPageSection.vue
export { default as VPTeamMembers } from './components/VPTeamMembers.vue' export { default as VPTeamMembers } from './components/VPTeamMembers.vue'
export { useSidebar } from './composables/sidebar' export { useSidebar } from './composables/sidebar'
export { useLocalNav } from './composables/local-nav'
const theme: Theme = { const theme: Theme = {
Layout, Layout,

1
theme.d.ts vendored

@ -13,6 +13,7 @@ declare const theme: {
export default theme export default theme
export declare const useSidebar: () => DefaultTheme.DocSidebar export declare const useSidebar: () => DefaultTheme.DocSidebar
export declare const useLocalNav: () => DefaultTheme.DocLocalNav
// TODO: add props for these // TODO: add props for these
export declare const VPButton: DefineComponent export declare const VPButton: DefineComponent

@ -347,6 +347,25 @@ export namespace DefaultTheme {
sponsor?: string sponsor?: string
} }
// local nav -----------------------------------------------------------------
/**
* ReturnType of `useLocalNav`.
*/
export interface DocLocalNav {
/**
* The outline headers of the current page.
*/
headers: ShallowRef<MenuItem[]>
/**
* Whether the current page has a local nav. Local nav is shown when the
* "outline" is present in the page. However, note that the actual
* local nav visibility depends on the screen width as well
*/
hasLocalNav: ComputedRef<boolean>
}
// outline ------------------------------------------------------------------- // outline -------------------------------------------------------------------
export interface Outline { export interface Outline {

Loading…
Cancel
Save