refactor: refactor sidebar styles

pull/142/head
Kia King Ishii 4 years ago
parent 621ca3e26f
commit 1836934f7c

@ -8,16 +8,14 @@
</NavBar> </NavBar>
<ToggleSideBarButton @toggle="toggleSidebar" /> <ToggleSideBarButton @toggle="toggleSidebar" />
</header> </header>
<aside :class="{ open: openSideBar }"> <SideBar :open="openSideBar">
<SideBar> <template #sidebar-top>
<template #top> <slot name="sidebar-top" />
<slot name="sidebar-top" /> </template>
</template> <template #sidebar-bottom>
<template #bottom> <slot name="sidebar-bottom" />
<slot name="sidebar-bottom" /> </template>
</template> </SideBar>
</SideBar>
</aside>
<!-- TODO: make this button accessible --> <!-- TODO: make this button accessible -->
<div class="sidebar-mask" @click="toggleSidebar(false)" /> <div class="sidebar-mask" @click="toggleSidebar(false)" />
<main class="home" aria-labelledby="main-title" v-if="enableHome"> <main class="home" aria-labelledby="main-title" v-if="enableHome">

@ -42,7 +42,7 @@
@media screen and (max-width: 719px) { @media screen and (max-width: 719px) {
.hide-mobile { .hide-mobile {
display: none; display: none !important;
} }
} }
</style> </style>

@ -1,70 +1,69 @@
<template> <template>
<div class="nav-item"> <div class="navbar-link">
<a <a
class="nav-link" class="item"
:class="classes" :class="classes"
:href="href" :href="href"
:target="target" :target="target"
:rel="rel" :rel="rel"
:aria-label="item.ariaLabel" :aria-label="item.ariaLabel"
> >
{{ item.text }} {{ item.text }} <OutboundLink v-if="isExternalLink" />
<OutboundLink v-if="isExternalLink" />
</a> </a>
</div> </div>
</template> </template>
<script src="./NavBarLink"></script> <script src="./NavBarLink"></script>
<style> <style scoped>
.nav-item { .navbar-link {
position: relative; position: relative;
display: inline-block; padding: 0 1.5rem;
margin-left: 1.5rem;
line-height: 2rem;
} }
@media screen and (max-width: 719px) { @media (min-width: 720px) {
.nav-item { .navbar-link {
display: block; padding: 0;
margin-left: 0; }
padding: 0.3rem 1.5rem;
.navbar-link + .navbar-link,
.dropdown-wrapper + .navbar-link {
padding-left: 1.5rem;
} }
} }
.nav-link { .item {
display: block; display: block;
margin-bottom: -2px; margin-bottom: -2px;
border-bottom: 2px solid transparent; line-height: 40px;
font-size: 0.9rem; font-size: 1rem;
font-weight: 500; font-weight: 600;
line-height: 1.4rem;
color: var(--c-text); color: var(--c-text);
white-space: nowrap; white-space: nowrap;
} }
.nav-link:hover, .item:hover,
.nav-link.active { .item.active {
border-bottom-color: var(--c-brand);
text-decoration: none; text-decoration: none;
color: var(--c-brand);
} }
.nav-link.external:hover { @media (min-width: 720px) {
border-bottom-color: transparent; .item {
} border-bottom: 2px solid transparent;
line-height: 1.5rem;
font-size: .9rem;
font-weight: 500;
}
@media screen and (max-width: 719px) { .item:hover,
.nav-link { .item.active {
line-height: 1.7; color: var(--c-text);
font-size: 1em; border-bottom-color: var(--c-brand);
font-weight: 600;
border-bottom: none;
margin-bottom: 0;
} }
.nav-link:hover, .item.external:hover {
.nav-link.active { border-bottom-color: transparent;
color: var(--c-brand);
} }
} }
</style> </style>

@ -1,5 +1,5 @@
<template> <template>
<nav class="nav-links" v-if="navData || repoInfo"> <nav v-if="navData || repoInfo" class="navbar-links">
<template v-if="navData"> <template v-if="navData">
<template v-for="item of navData"> <template v-for="item of navData">
<NavDropdownLink v-if="item.items" :item="item" /> <NavDropdownLink v-if="item.items" :item="item" />
@ -13,21 +13,17 @@
<script src="./NavBarLinks"></script> <script src="./NavBarLinks"></script>
<style> <style scoped>
.nav-links { .navbar-links {
display: flex; padding: 1rem 0;
align-items: center; border-bottom: 1px solid var(--c-divider);
height: 35px;
list-style-type: none;
transform: translateY(1px);
} }
@media screen and (max-width: 719px) { @media (min-width: 720px) {
.nav-links { .navbar-links {
display: block; display: flex;
height: auto; align-items: center;
padding: 0.5rem 0 1rem; border-bottom: 0;
border-bottom: 1px solid var(--c-divider);
} }
} }
</style> </style>

@ -30,7 +30,7 @@ export default defineComponent({
.page { .page {
margin: 0 auto; margin: 0 auto;
padding: 0 1.5rem 4rem; padding: 0 1.5rem 4rem;
max-width: 50rem; max-width: 48rem;
} }
.content { .content {

@ -1,123 +0,0 @@
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { computed } from 'vue'
import { Header } from '../../../../types/shared'
import { getPathDirName } from '../utils'
import { DefaultTheme } from '../config'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import NavBarLinks from './NavBarLinks.vue'
import { SideBarItem } from './SideBarItem'
export interface ResolvedSidebarItem {
text: string
link?: string
isGroup?: boolean
children?: ResolvedSidebarItem[]
}
type ResolvedSidebar = ResolvedSidebarItem[]
export default {
components: {
NavBarLinks,
SideBarItem
},
setup() {
const route = useRoute()
const siteData = useSiteDataByRoute()
useActiveSidebarLinks()
return {
items: computed(() => {
const {
headers,
frontmatter: { sidebar, sidebarDepth = 2 }
} = route.data
if (sidebar === 'auto') {
// auto, render headers of current page
return resolveAutoSidebar(headers, sidebarDepth)
} else if (Array.isArray(sidebar)) {
// in-page array config
return resolveArraySidebar(sidebar, sidebarDepth)
} else if (sidebar === false) {
return []
} else {
// no explicit page sidebar config
// check global theme config
const { sidebar: themeSidebar } = siteData.value.themeConfig
if (themeSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
} else if (Array.isArray(themeSidebar)) {
return resolveArraySidebar(themeSidebar, sidebarDepth)
} else if (themeSidebar === false) {
return []
} else if (typeof themeSidebar === 'object') {
return resolveMultiSidebar(
themeSidebar,
route.path,
headers,
sidebarDepth
)
}
}
})
}
}
}
function resolveAutoSidebar(headers: Header[], depth: number): ResolvedSidebar {
const ret: ResolvedSidebar = []
if (headers === undefined) {
return []
}
let lastH2: ResolvedSidebarItem | undefined = undefined
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return
}
const item: ResolvedSidebarItem = {
text: title,
link: `#${slug}`
}
if (level === 2) {
lastH2 = item
ret.push(item)
} else if (lastH2) {
;(lastH2.children || (lastH2.children = [])).push(item)
}
})
return ret
}
function resolveArraySidebar(
config: DefaultTheme.SideBarItem[],
depth: number
): ResolvedSidebar {
return config
}
function resolveMultiSidebar(
config: DefaultTheme.MultiSideBarConfig,
path: string,
headers: Header[],
depth: number
): ResolvedSidebar {
const paths = [path, Object.keys(config)[0]]
const item = paths.map((x) => config[getPathDirName(x)]).find(Boolean)
if (Array.isArray(item)) {
return resolveArraySidebar(item, depth)
}
if (item === 'auto') {
return resolveAutoSidebar(headers, depth)
}
return []
}

@ -1,96 +1,72 @@
<template> <template>
<NavBarLinks class="show-mobile" /> <aside class="sidebar" :class="{ open }">
<div class="nav">
<NavBarLinks class="show-mobile" />
</div>
<slot name="top" /> <slot name="sidebar-top" />
<ul class="sidebar"> <SideBarLinks />
<SideBarItem v-for="item of items" :item="item" />
</ul>
<slot name="bottom" /> <slot name="sidebar-bottom" />
</aside>
</template> </template>
<script src="./SideBar"></script> <script lang="ts">
import { defineComponent } from 'vue'
import NavBarLinks from './NavBarLinks.vue'
import SideBarLinks from './SideBarLinks.vue'
<style> export default defineComponent({
.show-mobile { components: {
display: none; NavBarLinks,
} SideBarLinks
},
@media screen and (max-width: 719px) { props: {
.show-mobile { open: { type: Boolean, required: true }
display: block;
} }
} })
</script>
.sidebar,
.sidebar-items {
list-style-type: none;
line-height: 2;
padding: 0;
margin: 0;
}
<style scoped>
.sidebar { .sidebar {
padding: 1.5rem 0; position: fixed;
top: var(--header-height);
bottom: 0;
left: 0;
z-index: var(--z-index-sidebar);
border-right: 1px solid var(--c-divider);
width: 16.4rem;
background-color: var(--c-bg);
overflow-y: auto;
transform: translateX(-100%);
transition: transform .25s ease;
} }
.sidebar-data { @media (min-width: 720px) {
padding: 1.5rem 0; .sidebar {
} transform: translateX(0);
@media screen and (max-width: 719px) {
.sidebar-data {
padding: 1rem;
} }
} }
.sidebar-items .sidebar-items { @media (min-width: 960px) {
padding-left: 1rem; .sidebar {
} width: 20rem;
}
.sidebar-items .sidebar-items .sidebar-link {
border-left: 0;
}
.sidebar-items .sidebar-items .sidebar-link.active {
font-weight: 500;
}
.sidebar-items .sidebar-link {
padding: 0.35rem 1rem 0.35rem 2rem;
line-height: 1.4;
font-size: 0.95em;
font-weight: 400;
}
.sidebar-item + .sidebar-item {
padding-top: 0.75rem;
} }
.sidebar-items > .sidebar-item + .sidebar-item { .sidebar.open {
padding-top: 0; transform: translateX(0);
} }
.sidebar-link { .nav {
display: block; display: block;
margin: 0;
border-left: 0.25rem solid transparent;
padding: 0.35rem 1.5rem 0.35rem 1.25rem;
line-height: 1.7;
font-size: 1.05em;
font-weight: 700;
color: var(--c-text);
}
a.sidebar-link:hover {
text-decoration: none;
color: var(--c-brand);
} }
a.sidebar-link.active { @media (min-width: 720px) {
border-left-color: var(--c-brand); .nav {
font-weight: 600; display: none;
color: var(--c-brand); }
} }
</style> </style>

@ -1,33 +1,31 @@
import { useRoute, useSiteData } from 'vitepress'
import { FunctionalComponent, h, VNode } from 'vue' import { FunctionalComponent, h, VNode } from 'vue'
import { Header } from '../../../../types/shared' import { useRoute, useSiteData } from 'vitepress'
import { Header } from '/@types/shared'
import { DefaultTheme } from '../config'
import { joinUrl, isActive } from '../utils' import { joinUrl, isActive } from '../utils'
import { ResolvedSidebarItem } from './SideBar'
interface HeaderWithChildren extends Header { interface HeaderWithChildren extends Header {
children?: Header[] children?: Header[]
} }
export const SideBarItem: FunctionalComponent<{ export const SideBarLink: FunctionalComponent<{
item: ResolvedSidebarItem item: DefaultTheme.SideBarItem
}> = (props) => { }> = (props) => {
const {
item: { link: relLink, text, children }
} = props
const route = useRoute() const route = useRoute()
const siteData = useSiteData() const site = useSiteData()
const link = resolveLink(siteData.value.base, relLink || '')
const active = isActive(route, link)
const headers = route.data.headers const headers = route.data.headers
const text = props.item.text
const link = resolveLink(site.value.base, props.item.link)
const children = (props.item as DefaultTheme.SideBarGroup).children
const active = isActive(route, link)
const childItems = createChildren(active, children, headers) const childItems = createChildren(active, children, headers)
return h('li', { class: 'sidebar-item' }, [ return h('li', { class: 'sidebar-link' }, [
h( h(
link ? 'a' : 'p', link ? 'a' : 'p',
{ {
class: { 'sidebar-link': true, active }, class: { 'sidebar-link-item': true, active },
href: link href: link
}, },
text text
@ -36,26 +34,30 @@ export const SideBarItem: FunctionalComponent<{
]) ])
} }
function resolveLink(base: string, path: string): string | undefined { function resolveLink(base: string, path?: string): string | undefined {
return path if (path === undefined) {
? // keep relative hash to the same page return path
path.startsWith('#') }
? path
: joinUrl(base, path) // keep relative hash to the same page
: undefined if (path.startsWith('#')) {
return path
}
return joinUrl(base, path)
} }
function createChildren( function createChildren(
active: boolean, active: boolean,
children?: ResolvedSidebarItem[], children?: DefaultTheme.SideBarItem[],
headers?: Header[] headers?: Header[]
): VNode | null { ): VNode | null {
if (children && children.length > 0) { if (children && children.length > 0) {
return h( return h(
'ul', 'ul',
{ class: 'sidebar-items' }, { class: 'sidebar-links' },
children.map((c) => { children.map((c) => {
return h(SideBarItem, { item: c }) return h(SideBarLink, { item: c })
}) })
) )
} }
@ -65,7 +67,7 @@ function createChildren(
: null : null
} }
function resolveHeaders(headers: Header[]): ResolvedSidebarItem[] { function resolveHeaders(headers: Header[]): DefaultTheme.SideBarItem[] {
return mapHeaders(groupHeaders(headers)) return mapHeaders(groupHeaders(headers))
} }
@ -82,7 +84,7 @@ function groupHeaders(headers: Header[]): HeaderWithChildren[] {
return headers.filter((h) => h.level === 2) return headers.filter((h) => h.level === 2)
} }
function mapHeaders(headers: HeaderWithChildren[]): ResolvedSidebarItem[] { function mapHeaders(headers: HeaderWithChildren[]): DefaultTheme.SideBarItem[] {
return headers.map((header) => ({ return headers.map((header) => ({
text: header.title, text: header.title,
link: `#${header.slug}`, link: `#${header.slug}`,

@ -0,0 +1,29 @@
<template>
<ul v-if="items.length > 0" class="sidebar-links">
<SideBarLink
v-for="item of items"
:key="item.text"
:item="item"
/>
</ul>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useSideBar } from '../composables/sideBar'
import { SideBarLink } from './SideBarLink'
export default defineComponent({
components: {
SideBarLink
},
setup() {
const items = useSideBar()
return {
items
}
}
})
</script>

@ -0,0 +1,77 @@
import { computed } from 'vue'
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { Header } from '/@types/shared'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import { getSideBarConfig } from '../support/sideBar'
import { DefaultTheme } from '../config'
export function useSideBar() {
const route = useRoute()
const site = useSiteDataByRoute()
useActiveSidebarLinks()
return computed(() => {
// at first, we'll check if we can find the sidebar setting in frontmatter.
const headers = route.data.headers
const frontSidebar = route.data.frontmatter.sidebar
const sidebarDepth = route.data.frontmatter.sidebarDepth
// if it's `false`, we'll just return an empty array here.
if (frontSidebar === false) {
return []
}
// if it's `atuo`, render headers of the current page
if (frontSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
}
// now, there's no sidebar setting at frontmatter; let's see the configs
const themeSidebar = getSideBarConfig(
site.value.themeConfig.sidebar,
route.path
)
if (themeSidebar === false) {
return []
}
if (themeSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
}
return themeSidebar
})
}
function resolveAutoSidebar(
headers: Header[],
depth: number
): DefaultTheme.SideBarItem[] {
const ret: DefaultTheme.SideBarItem[] = []
if (headers === undefined) {
return []
}
let lastH2: DefaultTheme.SideBarItem | undefined = undefined
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return
}
const item: DefaultTheme.SideBarItem = {
text: title,
link: `#${slug}`
}
if (level === 2) {
lastH2 = item
ret.push(item)
} else if (lastH2) {
;((lastH2 as any).children || ((lastH2 as any).children = [])).push(item)
}
})
return ret
}

@ -2,6 +2,7 @@ import './styles/vars.css'
import './styles/layout.css' import './styles/layout.css'
import './styles/code.css' import './styles/code.css'
import './styles/custom-blocks.css' import './styles/custom-blocks.css'
import './styles/sidebar-links.css'
import { Theme } from 'vitepress' import { Theme } from 'vitepress'
import Layout from './Layout.vue' import Layout from './Layout.vue'

@ -197,7 +197,7 @@ blockquote > p {
height: var(--header-height); height: var(--header-height);
background-color: #fff; background-color: #fff;
border-bottom: 1px solid var(--c-divider); border-bottom: 1px solid var(--c-divider);
z-index: 4; z-index: var(--z-index-navbar);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -210,18 +210,6 @@ blockquote > p {
} }
} }
.theme aside {
position: fixed;
top: var(--header-height);
bottom: 0;
left: 0;
width: var(--sidebar-width);
border-right: 1px solid var(--c-divider);
background-color: #fff;
z-index: 3;
overflow-y: auto;
}
.theme.sidebar-open .sidebar-mask { .theme.sidebar-open .sidebar-mask {
display: block; display: block;
} }
@ -246,17 +234,6 @@ blockquote > p {
} }
} }
@media screen and (max-width: 719px) {
.theme aside {
transition: transform 0.2s ease;
transform: translateX(-100%);
}
.theme aside.open {
transform: translateX(0);
}
}
.sidebar-mask { .sidebar-mask {
z-index: 2; z-index: 2;
position: fixed; position: fixed;
@ -271,7 +248,7 @@ blockquote > p {
@media screen and (min-width: 960px) { @media screen and (min-width: 960px) {
.theme main { .theme main {
padding-left: var(--sidebar-width); padding-left: 20rem;
} }
} }
@ -286,7 +263,7 @@ blockquote > p {
@media screen and (max-width: 959px) { @media screen and (max-width: 959px) {
.theme main { .theme main {
margin-left: var(--sidebar-width); margin-left: 16.4rem;
} }
} }

@ -0,0 +1,85 @@
.sidebar-links {
margin: 0;
padding: 0;
list-style: none;
}
.sidebar-link-item {
display: block;
margin: 0;
border-left: .25rem solid transparent;
color: var(--c-text);
}
a.sidebar-link-item:hover {
text-decoration: none;
color: var(--c-brand);
}
a.sidebar-link-item.active {
color: var(--c-brand);
}
.sidebar > .sidebar-links {
padding: 1.5rem 0;
}
.sidebar > .sidebar-links > .sidebar-link + .sidebar-link {
padding-top: 1rem;
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-link-item {
padding: .35rem 1.5rem .35rem 1.25rem;
font-size: 1.1rem;
font-weight: 700;
}
.sidebar > .sidebar-links > .sidebar-link > a.sidebar-link-item.active {
border-left-color: var(--c-brand);
font-weight: 600;
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-links > .sidebar-link > .sidebar-link-item {
display: block;
padding: .35rem 1.5rem .35rem 2rem;
line-height: 1.4;
font-size: 1rem;
font-weight: 400;
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-links > .sidebar-link > a.sidebar-link-item.active {
border-left-color: var(--c-brand);
font-weight: 600;
}
.sidebar >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-link-item {
display: block;
padding: .3rem 1.5rem .3rem 3rem;
line-height: 1.4;
font-size: .9rem;
font-weight: 400;
}
.sidebar >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-link-item {
display: block;
padding: .3rem 1.5rem .3rem 4rem;
line-height: 1.4;
font-size: .9rem;
font-weight: 400;
}

@ -22,12 +22,18 @@
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; --font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
/**
* Z Indexes
* --------------------------------------------------------------------- */
--z-index-navbar: 1100;
--z-index-sidebar: 1000;
/** /**
* Sizes * Sizes
* --------------------------------------------------------------------- */ * --------------------------------------------------------------------- */
--header-height: 3.6rem; --header-height: 3.6rem;
--sidebar-width: 16.4rem;
} }
/** Fallback Styles */ /** Fallback Styles */

@ -34,6 +34,7 @@ export function getSideBarConfig(
// get the very first segment of the path to compare with nulti sidebar keys // get the very first segment of the path to compare with nulti sidebar keys
// and make sure it's surrounded by slash // and make sure it's surrounded by slash
path = removeExtention(path)
path = ensureStartingSlash(path).split('/')[1] || '/' path = ensureStartingSlash(path).split('/')[1] || '/'
path = ensureSlash(path) path = ensureSlash(path)

Loading…
Cancel
Save