Skip to content

Commit

Permalink
feat(theme): page outline for mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Mar 14, 2023
1 parent 15a2dd2 commit 7182c42
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 55 deletions.
14 changes: 0 additions & 14 deletions docs/reference/site-config.md
Expand Up @@ -6,20 +6,6 @@ outline: deep

Site config is where you can define the global settings of the site. App config options define settings that apply to every VitePress site, regardless of what theme it is using. For example, the base directory or the title of the site.

<div class="site-config-toc">

[[toc]]

</div>

<style>
@media (min-width: 1280px) {
.site-config-toc {
display: none;
}
}
</style>

## Overview

### Config Resolution
Expand Down
12 changes: 12 additions & 0 deletions src/client/theme-default/components/VPDoc.vue
Expand Up @@ -4,6 +4,7 @@ import { computed } from 'vue'
import { useSidebar } from '../composables/sidebar.js'
import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue'
import VPDocOutlineDropdown from './VPDocOutlineDropdown.vue'
const route = useRoute()
const { hasSidebar, hasAside } = useSidebar()
Expand Down Expand Up @@ -38,6 +39,7 @@ const pageName = computed(() =>
<div class="content">
<div class="content-container">
<slot name="doc-before" />
<VPDocOutlineDropdown />
<main class="main">
<Content class="vp-doc" :class="pageName" />
</main>
Expand All @@ -56,6 +58,16 @@ const pageName = computed(() =>
width: 100%;
}
.VPDoc .VPDocOutlineDropdown {
display: none;
}
@media (min-width: 768px) and (max-width: 1280px) {
.VPDoc .VPDocOutlineDropdown {
display: block;
}
}
@media (min-width: 768px) {
.VPDoc {
padding: 48px 32px 128px;
Expand Down
23 changes: 4 additions & 19 deletions src/client/theme-default/components/VPDocAsideOutline.vue
Expand Up @@ -3,10 +3,11 @@ import { ref, shallowRef } from 'vue'
import { useData } from '../composables/data.js'
import {
getHeaders,
resolveTitle,
useActiveAnchor,
type MenuItem
} from '../composables/outline.js'
import VPDocAsideOutlineItem from './VPDocAsideOutlineItem.vue'
import VPDocOutlineItem from './VPDocOutlineItem.vue'
import { onContentUpdated } from 'vitepress'
const { frontmatter, theme } = useData()
Expand All @@ -23,36 +24,20 @@ const container = ref()
const marker = ref()
useActiveAnchor(container, marker)
function handleClick({ target: el }: Event) {
const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.querySelector<HTMLAnchorElement>(
decodeURIComponent(id)
)
heading?.focus()
}
</script>

<template>
<div class="VPDocAsideOutline" :class="{ 'has-outline': headers.length > 0 }" ref="container">
<div class="content">
<div class="outline-marker" ref="marker" />

<div class="outline-title">
{{
(typeof theme.outline === 'object' &&
!Array.isArray(theme.outline) &&
theme.outline.label) ||
theme.outlineTitle ||
'On this page'
}}
</div>
<div class="outline-title">{{ resolveTitle(theme) }}</div>

<nav aria-labelledby="doc-outline-aria-label">
<span class="visually-hidden" id="doc-outline-aria-label">
Table of Contents for current page
</span>
<VPDocAsideOutlineItem :headers="headers" :root="true" :onClick="handleClick" />
<VPDocOutlineItem :headers="headers" :root="true" />
</nav>
</div>
</div>
Expand Down
76 changes: 76 additions & 0 deletions src/client/theme-default/components/VPDocOutlineDropdown.vue
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useData } from '../composables/data.js'
import { getHeaders, resolveTitle } from '../composables/outline.js'
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
})
</script>

<template>
<div class="VPDocOutlineDropdown">
<button @click="open = !open" :class="{ open }">
{{ resolveTitle(theme) }}
<VPIconChevronRight class="icon" />
</button>
<div class="items" v-if="open">
<VPDocOutlineItem :headers="getHeaders(frontmatter.outline ?? theme.outline)" />
</div>
</div>
</template>
<style scoped>
.VPDocOutlineDropdown {
margin-bottom: 42px;
}
.VPDocOutlineDropdown button {
display: block;
font-size: 14px;
font-weight: 500;
line-height: 24px;
color: var(--vp-c-text-2);
transition: color 0.5s;
border: 1px solid var(--vp-c-border);
padding: 4px 12px;
border-radius: 8px;
}
.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;
margin-left: 2px;
width: 14px;
height: 14px;
fill: currentColor;
}
:deep(.outline-link) {
font-size: 13px;
}
.open > .icon {
transform: rotate(90deg);
}
.items {
margin-top: 10px;
border-left: 1px solid var(--vp-c-divider);
}
</style>
Expand Up @@ -3,17 +3,24 @@ import type { MenuItem } from '../composables/outline.js'
defineProps<{
headers: MenuItem[]
onClick: (e: MouseEvent) => void
root?: boolean
}>()
function onClick({ target: el }: Event) {
const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.querySelector<HTMLAnchorElement>(
decodeURIComponent(id)
)
heading?.focus()
}
</script>

<template>
<ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers">
<a class="outline-link" :href="link" @click="onClick">{{ title }}</a>
<template v-if="children?.length">
<VPDocAsideOutlineItem :headers="children" :onClick="onClick" />
<VPDocOutlineItem :headers="children" />
</template>
</li>
</ul>
Expand All @@ -37,6 +44,7 @@ defineProps<{
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
font-weight: 500;
}
.outline-link:hover,
Expand Down
24 changes: 4 additions & 20 deletions src/client/theme-default/components/VPLocalNav.vue
Expand Up @@ -2,6 +2,7 @@
import { useData } from '../composables/data.js'
import { useSidebar } from '../composables/sidebar.js'
import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
defineProps<{
open: boolean
Expand All @@ -13,10 +14,6 @@ defineEmits<{
const { theme } = useData()
const { hasSidebar } = useSidebar()
function scrollToTop() {
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
}
</script>

<template>
Expand All @@ -33,9 +30,7 @@ function scrollToTop() {
</span>
</button>

<a class="top-link" href="#" @click="scrollToTop">
{{ theme.returnToTopLabel || 'Return to top' }}
</a>
<VPLocalNavOutlineDropdown />
</div>
</template>

Expand Down Expand Up @@ -91,23 +86,12 @@ function scrollToTop() {
fill: currentColor;
}
.top-link {
display: block;
.VPOutlineDropdown {
padding: 12px 24px 11px;
line-height: 24px;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-2);
transition: color 0.5s;
}
.top-link:hover {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
@media (min-width: 768px) {
.top-link {
.VPOutlineDropdown {
padding: 12px 32px 11px;
}
}
Expand Down
113 changes: 113 additions & 0 deletions src/client/theme-default/components/VPLocalNavOutlineDropdown.vue
@@ -0,0 +1,113 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useData } from '../composables/data.js'
import { getHeaders, resolveTitle } from '../composables/outline.js'
import VPDocOutlineItem from './VPDocOutlineItem.vue'
import { onContentUpdated } from 'vitepress'
import VPIconChevronRight from './icons/VPIconChevronRight.vue'
const { frontmatter, theme } = useData()
const open = ref(false)
const vh = ref(0)
onContentUpdated(() => {
open.value = false
})
function toggle() {
open.value = !open.value
vh.value = window.innerHeight + Math.min(window.scrollY - 64, 0)
}
function onItemClick(e: Event) {
if ((e.target as HTMLElement).classList.contains('outline-link')) {
open.value = false
}
}
function scrollToTop() {
open.value = false
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
}
</script>

<template>
<div class="VPLocalNavOutlineDropdown" :style="{ '--vp-vh': vh + 'px' }">
<button @click="toggle" :class="{ open }">
{{ resolveTitle(theme) }}
<VPIconChevronRight class="icon" />
</button>
<div class="items" v-if="open" @click="onItemClick">
<a class="top-link" href="#" @click="scrollToTop">
{{ theme.returnToTopLabel || 'Return to top' }}
</a>
<VPDocOutlineItem :headers="getHeaders(frontmatter.outline ?? theme.outline)" />
</div>
</div>
</template>
<style scoped>
.VPLocalNavOutlineDropdown {
padding: 12px 20px 11px;
}
.VPLocalNavOutlineDropdown button {
display: block;
font-size: 12px;
font-weight: 500;
line-height: 24px;
color: var(--vp-c-text-2);
transition: color 0.5s;
position: relative;
}
.VPLocalNavOutlineDropdown button:hover {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
.VPLocalNavOutlineDropdown button.open {
color: var(--vp-c-text-1);
}
.icon {
display: inline-block;
vertical-align: middle;
margin-left: 2px;
width: 14px;
height: 14px;
fill: currentColor;
}
:deep(.outline-link) {
font-size: 14px;
padding: 2px 0;
}
.open > .icon {
transform: rotate(90deg);
}
.items {
position: absolute;
left: 20px;
right: 20px;
top: 64px;
background-color: var(--vp-local-nav-bg-color);
padding: 4px 10px 16px;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
max-height: calc(var(--vp-vh, 100vh) - 86px);
overflow: scroll;
box-shadow: var(--vp-shadow-3);
}
.top-link {
display: block;
color: var(--vp-c-brand);
font-size: 13px;
font-weight: 500;
padding: 6px 0;
margin: 0 13px 10px;
border-bottom: 1px solid var(--vp-c-divider);
}
</style>

0 comments on commit 7182c42

Please sign in to comment.