Skip to content

Commit 79f8f14

Browse files
ulivzyyx990803
authored andcommitted
feat: dropdown Items in Navbar (#13)
1 parent 37ea038 commit 79f8f14

File tree

8 files changed

+253
-35
lines changed

8 files changed

+253
-35
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ https://vuepress.vuejs.org/
2828

2929
VuePress is still a work in progress. There are a few things that it currently does not support but are planned:
3030

31-
- Dropdown Items in Navbar
3231
- Multi-Language Support
3332
- Algolia DocSearch Integration
3433
- Blogging support

docs/default-theme-config/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ module.exports = {
5050
}
5151
```
5252

53+
These links can also be dropdown menus if you provide nested items via `items`:
54+
55+
```js
56+
module.exports = {
57+
themeConfig: {
58+
nav: [
59+
{
60+
text: 'Languages',
61+
items: [
62+
{ text: 'Chinese', link: '/language/chinese' },
63+
{ text: 'Japanese', link: '/language/japanese' }
64+
]
65+
}
66+
]
67+
}
68+
}
69+
```
70+
5371
## Sidebar
5472

5573
To enable the sidebar, use `themeConfig.sidebar`. The basic configuration expects an Array of links:

docs/guide/README.md

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ Each markdown file is compiled into HTML with [markdown-it](https://github.com/m
3030

3131
VuePress is still a work in progress. There are a few things that it currently does not support but are planned:
3232

33-
- Dropdown Items in Navbar
3433
- Multi-Language Support
3534
- Algolia DocSearch Integration
3635
- Blogging support

lib/default-theme/DropdownLink.vue

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<template>
2+
<div class="dropdown-wrapper" :class="{ open }">
3+
<div class="dropdown-title" @click="toggle">
4+
<span class="title">{{ item.text }}</span>
5+
<span class="arrow"></span>
6+
</div>
7+
<ul class="nav-dropdown">
8+
<li
9+
class="dropdown-item"
10+
v-for="subItem in item.items"
11+
:key="subItem.link">
12+
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
13+
<ul class="dropdown-subitem-wrapper" v-if="subItem.type === 'links'">
14+
<li
15+
class="dropdown-subitem"
16+
v-for="childSubItem in subItem.items"
17+
:key="childSubItem.link">
18+
<nav-link :item="childSubItem"></nav-link>
19+
</li>
20+
</ul>
21+
<nav-link v-else :item="subItem"></nav-link>
22+
</li>
23+
</ul>
24+
</div>
25+
</template>
26+
27+
<script>
28+
import { isExternal, ensureExt } from './util'
29+
import NavLink from './NavLink.vue'
30+
31+
export default {
32+
components: { NavLink },
33+
data() {
34+
return {
35+
open: false
36+
}
37+
},
38+
props: {
39+
item: {
40+
required: true
41+
}
42+
},
43+
methods: {
44+
toggle() {
45+
this.open = !this.open
46+
}
47+
}
48+
}
49+
</script>
50+
51+
<style lang="stylus">
52+
@import './styles/config.styl'
53+
54+
.dropdown-wrapper
55+
.dropdown-title
56+
.arrow
57+
display inline-block
58+
vertical-align middle
59+
margin-top -1px
60+
margin-left 6px
61+
width 0
62+
height 0
63+
border-left 4px solid transparent
64+
border-right 4px solid transparent
65+
border-top 5px solid #ccc
66+
.nav-dropdown
67+
.dropdown-item
68+
color inherit
69+
line-height 1.7rem
70+
h4
71+
margin 0.45rem 0 0
72+
border-top 1px solid #eee
73+
padding 0.45rem 1.5rem 0 1.25rem
74+
.dropdown-subitem-wrapper
75+
padding 0
76+
list-style none
77+
.dropdown-subitem
78+
font-size 0.9em
79+
a
80+
display block
81+
height 1.7rem
82+
line-height 1.7rem
83+
position relative
84+
border-bottom none
85+
font-weight 400
86+
margin-bottom 0
87+
padding 0 1.5rem 0 1.25rem
88+
&:hover
89+
color $accentColor
90+
&.router-link-active
91+
color $accentColor
92+
&::after
93+
content ""
94+
width 0
95+
height 0
96+
border-left 5px solid $accentColor
97+
border-top 4px solid transparent
98+
border-bottom 4px solid transparent
99+
position absolute
100+
top calc(50% - 3px)
101+
left 10px
102+
&:first-child h4
103+
margin-top 0
104+
padding-top 0
105+
border-top 0
106+
107+
@media (max-width: $MQMobile)
108+
.dropdown-wrapper
109+
&.open .dropdown-title
110+
margin-bottom 0.5rem
111+
&:not(.open)
112+
.dropdown-title .arrow
113+
border-top 4px solid transparent
114+
border-bottom 4px solid transparent
115+
border-left 5px solid #ccc
116+
.nav-dropdown
117+
display none
118+
.nav-dropdown
119+
.dropdown-item
120+
h4
121+
border-top 0
122+
margin-top 0
123+
padding-top 0
124+
h4, & > a
125+
font-size 15px
126+
height 2rem
127+
line-height 2rem
128+
.dropdown-subitem
129+
font-size 14px
130+
padding-left 1rem
131+
132+
@media (min-width: $MQMobile)
133+
.dropdown-wrapper
134+
&:hover .nav-dropdown
135+
display block
136+
.nav-dropdown
137+
display none
138+
box-sizing border-box;
139+
max-height calc(100vh - 2.7rem)
140+
overflow-y auto
141+
position absolute
142+
top 100%
143+
right 0
144+
background-color #fff
145+
padding 10px 0
146+
border 1px solid #ddd
147+
border-bottom-color #ccc
148+
text-align left
149+
border-radius 0.25rem
150+
white-space nowrap
151+
margin 0
152+
</style>

lib/default-theme/NavLink.vue

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<template>
2+
<router-link
3+
class="nav-link"
4+
:to="link"
5+
v-if="!isExternal(link)"
6+
:exact="link === '/'"
7+
>{{ item.text }}</router-link>
8+
<a
9+
v-else
10+
:href="link"
11+
target="_blank"
12+
class="nav-link"
13+
rel="noopener noreferrer"
14+
>{{ item.text }}</a>
15+
</template>
16+
17+
<script>
18+
import { isExternal, ensureExt } from './util'
19+
export default {
20+
props: {
21+
item: {
22+
required: true
23+
}
24+
},
25+
computed: {
26+
link() {
27+
return ensureExt(this.item.link)
28+
}
29+
},
30+
methods: {
31+
isExternal
32+
}
33+
}
34+
</script>

lib/default-theme/NavLinks.vue

+36-31
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
<template>
22
<nav class="nav-links" v-if="userLinks.length || githubLink">
33
<!-- user links -->
4-
<template v-for="item in userLinks">
5-
<a v-if="item.isOutbound"
6-
:href="item.link"
7-
target="_blank"
8-
rel="noopener noreferrer">
9-
{{ item.text }}
10-
</a>
11-
<router-link
12-
v-else
13-
:to="item.link"
14-
:key="item.link"
15-
:exact="item.link === '/'">
16-
{{ item.text }}
17-
</router-link>
18-
</template>
4+
<div
5+
class="nav-item"
6+
v-for="item in userLinks"
7+
:key="item.link">
8+
<dropdown-link
9+
v-if="item.type === 'links'"
10+
:item="item"></dropdown-link>
11+
<nav-link v-else :item="item"></nav-link>
12+
</div>
1913
<!-- github link -->
2014
<a v-if="githubLink"
2115
:href="githubLink"
@@ -30,16 +24,18 @@
3024

3125
<script>
3226
import OutboundLink from './OutboundLink.vue'
33-
import { isActive, ensureExt, outboundRE } from './util'
27+
import { isActive, resolveNavLinkItem } from './util'
28+
import NavLink from './NavLink.vue'
29+
import DropdownLink from './DropdownLink.vue'
3430
3531
export default {
36-
components: { OutboundLink },
32+
components: { OutboundLink, NavLink, DropdownLink },
3733
computed: {
3834
userLinks () {
39-
return (this.$site.themeConfig.nav || []).map(item => ({
40-
text: item.text,
41-
link: ensureExt(item.link),
42-
isOutbound: outboundRE.test(item.link)
35+
return (this.$site.themeConfig.nav || []).map((link => {
36+
return Object.assign(resolveNavLinkItem(link), {
37+
items: (link.items || []).map(resolveNavLinkItem)
38+
})
4339
}))
4440
},
4541
githubLink () {
@@ -63,21 +59,30 @@ export default {
6359
.nav-links
6460
display inline-block
6561
a
66-
color inherit
67-
font-weight 500
6862
line-height 1.25rem
69-
margin-left 1.5rem
63+
color inherit
7064
&:hover, &.router-link-active
7165
color $accentColor
66+
.nav-item
67+
cursor: pointer
68+
position relative
69+
display inline-block
70+
margin-left 1.5rem
71+
font-weight 500
72+
line-height 2rem
73+
.github-link
74+
margin-left 1.5rem
7275
7376
@media (max-width: $MQMobile)
74-
.nav-links a
75-
margin-left 0
77+
.nav-links
78+
.nav-item, .github-link
79+
margin-left 0
7680
7781
@media (min-width: $MQMobile)
78-
.nav-links a
79-
&:hover, &.router-link-active
80-
color $textColor
81-
margin-bottom -2px
82-
border-bottom 2px solid lighten($accentColor, 5%)
82+
.nav-links
83+
a
84+
&:hover, &.router-link-active
85+
color $textColor
86+
margin-bottom -2px
87+
border-bottom 2px solid lighten($accentColor, 5%)
8388
</style>

lib/default-theme/Sidebar.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ function resolveOpenGroupIndex (route, items) {
8181
display none
8282
border-bottom 1px solid $borderColor
8383
padding 0.5rem 0 0.75rem 0
84-
a
84+
.nav-item, .github-link
8585
display block
86+
line-height 1.25rem
8687
font-weight 600
8788
font-size 1.1em
8889
padding 0.5rem 0 0.5rem 1.5rem

lib/default-theme/util.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ export function getHash (path) {
1616
}
1717
}
1818

19+
export function isExternal (path) {
20+
return outboundRE.test(path)
21+
}
22+
1923
export function ensureExt (path) {
20-
if (outboundRE.test(path)) {
24+
if (isExternal(path)) {
2125
return path
2226
}
2327
const hashMatch = path.match(hashRE)
@@ -147,6 +151,12 @@ export function groupHeaders (headers) {
147151
return headers.filter(h => h.level === 2)
148152
}
149153

154+
export function resolveNavLinkItem (linkItem) {
155+
return Object.assign(linkItem, {
156+
type: linkItem.items && linkItem.items.length ? 'links' : 'link'
157+
})
158+
}
159+
150160
function resolveMatchingSidebarConfig (route, sidebarConfig) {
151161
if (Array.isArray(sidebarConfig)) {
152162
return {

0 commit comments

Comments
 (0)