Skip to content

Commit

Permalink
feat: nav dropdown (#51)
Browse files Browse the repository at this point in the history
* feat: add external link support for nav items

* feat: support nav dropdown

Co-authored-by: Kia Ishii <[email protected]>
Co-authored-by: Evan You <[email protected]>
  • Loading branch information
3 people authored Jul 21, 2020
1 parent 44e91bb commit 5780461
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 6 deletions.
4 changes: 3 additions & 1 deletion src/client/theme-default/components/NavBar.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { computed } from 'vue'
import { useSiteData } from 'vitepress'
import NavBarLink from './NavBarLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'

export default {
components: {
NavBarLink
NavBarLink,
NavDropdownLink
},

setup() {
Expand Down
9 changes: 4 additions & 5 deletions src/client/theme-default/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
<span>{{ $site.title }}</span>
</a>
<nav class="nav-links" v-if="navData">
<NavBarLink
v-for="item of navData"
:key="item.link"
:item="item"
/>
<template v-for="item of navData">
<NavDropdownLink v-if='item.items' :item="item"/>
<NavBarLink v-else :item="item"/>
</template>
</nav>
</template>

Expand Down
42 changes: 42 additions & 0 deletions src/client/theme-default/components/NavDropdownLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import NavBarLink from './NavBarLink.vue'
import { defineComponent, ref, watch, PropType } from 'vue'
import { useRoute } from 'vitepress'
import { DefaultTheme } from '../config'

export default defineComponent({
name: 'DropdownLink',
components: {
NavBarLink
},
props: {
item: {
type: Object as PropType<DefaultTheme.NavItemWithChildren>,
required: true
}
},
setup(props) {
const open = ref(false)
const route = useRoute()

watch(
() => route.path,
() => {
open.value = false
}
)

const setOpen = (value: boolean) => {
open.value = value
}

const isLastItemOfArray = <T>(item: T, array: T[]) => {
return array.length && array.indexOf(item) === array.length - 1
}

return {
open,
setOpen,
isLastItemOfArray
}
}
})
161 changes: 161 additions & 0 deletions src/client/theme-default/components/NavDropdownLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<template>
<div class="dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="item.ariaLabel"
@click="setOpen(!open)"
>
<span>{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'" />
</button>

<ul v-show="open" class="nav-dropdown">
<li v-for="(subItem, index) in item.items" :key="subItem.link || index" class="dropdown-item">
<h4 v-if="subItem.items">{{ subItem.text }}</h4>
<ul v-if="subItem.items" class="dropdown-subitem-wrapper">
<li
v-for="childSubItem in subItem.items"
:key="childSubItem.link"
class="dropdown-subitem"
>
<NavBarLink
:item="childSubItem"
@focusout="
isLastItemOfArray(childSubItem, subItem.items) &&
isLastItemOfArray(subItem, item.items) &&
setOpen(false)
"
/>
</li>
</ul>

<NavBarLink
v-else
:item="subItem"
@focusout="isLastItemOfArray(subItem, item.items) && setOpen(false)"
/>
</li>
</ul>
</div>
</template>

<script src="./NavDropdownLink"></script>

<style>
.dropdown-wrapper {
position: relative;
cursor: pointer;
display: inline-block;
margin-left: 1.5rem;
}
.dropdown-wrapper .dropdown-title {
font: inherit;
color: var(--text-color);
font-weight: 600;
display: inline-block;
height: 1.75rem;
line-height: 1.75rem;
padding: inherit;
background: transparent;
border: none;
}
.dropdown-wrapper .dropdown-title:hover {
border-color: transparent;
}
.dropdown-wrapper .dropdown-title .arrow {
display: inline-block;
vertical-align: middle;
margin-top: -1px;
margin-left: 0.4rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item {
color: inherit;
line-height: 1.7rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item h4 {
margin: 0.45rem 0 0;
border-top: 1px solid #eee;
padding: 0.45rem 1.5rem 0 1.25rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper {
padding: 0;
list-style: none;
}
.dropdown-wrapper
.nav-dropdown
.dropdown-item
.dropdown-subitem-wrapper
.dropdown-subitem {
font-size: 0.9em;
}
.dropdown-wrapper .nav-dropdown .dropdown-item a {
display: block;
line-height: 1.7rem;
position: relative;
border-bottom: none;
font-weight: 400;
margin-bottom: 0;
margin-left: 0;
padding: 0 1.5rem 0 1.25rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item a:hover {
color: var(--accent-color);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active {
color: var(--accent-color);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active::after {
content: '';
width: 0;
height: 0;
border-left: 5px solid var(--accent-color);
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
position: absolute;
top: calc(50% - 2px);
left: 9px;
}
.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4 {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dropdown-wrapper {
height: 1.8rem;
}
.dropdown-wrapper:hover .nav-dropdown,
.dropdown-wrapper.open .nav-dropdown {
display: block !important;
}
.dropdown-wrapper.open:blur {
display: none;
}
.dropdown-wrapper .dropdown-title .arrow {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid #aaa;
border-bottom: 0;
}
.dropdown-wrapper .nav-dropdown {
display: none;
height: auto !important;
box-sizing: border-box;
max-height: calc(100vh - 2.7rem);
overflow-y: auto;
position: absolute;
top: 100%;
right: 0;
background-color: #fff;
padding: 0.6rem 0;
border: 1px solid #ddd;
border-bottom-color: #ccc;
text-align: left;
border-radius: 0.25rem;
white-space: nowrap;
margin: 0;
}
</style>

0 comments on commit 5780461

Please sign in to comment.