CustomNavBar.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <script lang="ts" setup>
  2. import { useWindowScroll } from '@vueuse/core'
  3. import { ref, watchPostEffect } from 'vue'
  4. import { useData } from 'vitepress/dist/client/theme-default/composables/data'
  5. import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
  6. import VPNavBarAppearance from 'vitepress/dist/client/theme-default/components/VPNavBarAppearance.vue'
  7. import VPNavBarExtra from 'vitepress/dist/client/theme-default/components/VPNavBarExtra.vue'
  8. import VPNavBarHamburger from 'vitepress/dist/client/theme-default/components/VPNavBarHamburger.vue'
  9. import VPNavBarMenu from 'vitepress/dist/client/theme-default/components/VPNavBarMenu.vue'
  10. import VPNavBarSearch from 'vitepress/dist/client/theme-default/components/VPNavBarSearch.vue'
  11. import VPNavBarSocialLinks from 'vitepress/dist/client/theme-default/components/VPNavBarSocialLinks.vue'
  12. import VPNavBarTitle from 'vitepress/dist/client/theme-default/components/VPNavBarTitle.vue'
  13. import VPNavBarTranslations from 'vitepress/dist/client/theme-default/components/VPNavBarTranslations.vue'
  14. defineProps<{
  15. isScreenOpen: boolean
  16. }>()
  17. defineEmits<{
  18. (e: 'toggle-screen'): void
  19. }>()
  20. const { y } = useWindowScroll()
  21. const { hasSidebar } = useSidebar()
  22. const { frontmatter } = useData()
  23. const classes = ref<Record<string, boolean>>({})
  24. watchPostEffect(() => {
  25. classes.value = {
  26. 'has-sidebar': hasSidebar.value,
  27. 'home': frontmatter.value.layout === 'home',
  28. 'top': y.value === 0,
  29. }
  30. })
  31. </script>
  32. <template>
  33. <div class="VPNavBar" :class="classes">
  34. <div class="wrapper">
  35. <div class="container">
  36. <div class="title">
  37. <VPNavBarTitle>
  38. <template #nav-bar-title-before><slot name="nav-bar-title-before" /></template>
  39. <template #nav-bar-title-after><slot name="nav-bar-title-after" /></template>
  40. </VPNavBarTitle>
  41. </div>
  42. <div class="content">
  43. <div class="content-body">
  44. <slot name="nav-bar-content-before" />
  45. <VPNavBarSearch class="search" />
  46. <VPNavBarMenu class="menu" />
  47. <VPNavBarTranslations class="translations" />
  48. <VPNavBarAppearance class="appearance" />
  49. <VPNavBarSocialLinks class="social-links" />
  50. <VPNavBarExtra class="extra" />
  51. <slot name="nav-bar-content-after" />
  52. <VPNavBarHamburger class="hamburger" :active="isScreenOpen" @click="$emit('toggle-screen')" />
  53. </div>
  54. </div>
  55. </div>
  56. </div>
  57. <div class="divider">
  58. <div class="divider-line" />
  59. </div>
  60. </div>
  61. </template>
  62. <style scoped>
  63. .VPNavBar {
  64. position: relative;
  65. height: var(--vp-nav-height);
  66. pointer-events: none;
  67. white-space: nowrap;
  68. transition: background-color 0.5s;
  69. }
  70. .VPNavBar:not(.home) {
  71. background-color: var(--vp-nav-bg-color);
  72. }
  73. @media (min-width: 960px) {
  74. .VPNavBar:not(.home) {
  75. background-color: transparent;
  76. }
  77. .VPNavBar:not(.has-sidebar):not(.home.top) {
  78. background-color: var(--vp-nav-bg-color);
  79. }
  80. }
  81. .wrapper {
  82. padding: 0 8px 0 24px;
  83. }
  84. @media (min-width: 768px) {
  85. .wrapper {
  86. padding: 0 32px;
  87. }
  88. }
  89. @media (min-width: 960px) {
  90. .VPNavBar.has-sidebar .wrapper {
  91. padding: 0;
  92. }
  93. }
  94. .container {
  95. display: flex;
  96. justify-content: space-between;
  97. margin: 0 auto;
  98. max-width: calc(var(--vp-layout-max-width) - 64px);
  99. height: var(--vp-nav-height);
  100. pointer-events: none;
  101. }
  102. .container > .title,
  103. .container > .content {
  104. pointer-events: none;
  105. }
  106. .container :deep(*) {
  107. pointer-events: auto;
  108. }
  109. @media (min-width: 960px) {
  110. .VPNavBar.has-sidebar .container {
  111. max-width: 100%;
  112. }
  113. }
  114. .title {
  115. flex-shrink: 0;
  116. height: calc(var(--vp-nav-height) - 1px);
  117. transition: background-color 0.5s;
  118. }
  119. @media (min-width: 960px) {
  120. .VPNavBar.has-sidebar .title {
  121. position: absolute;
  122. top: 0;
  123. left: 0;
  124. z-index: 2;
  125. padding: 0 32px;
  126. width: var(--vp-sidebar-width);
  127. height: var(--vp-nav-height);
  128. background-color: transparent;
  129. }
  130. }
  131. @media (min-width: 1440px) {
  132. .VPNavBar.has-sidebar .title {
  133. padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
  134. width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
  135. }
  136. }
  137. .content {
  138. flex-grow: 1;
  139. }
  140. @media (min-width: 960px) {
  141. .VPNavBar.has-sidebar .content {
  142. position: relative;
  143. z-index: 1;
  144. padding-right: 32px;
  145. padding-left: var(--vp-sidebar-width);
  146. }
  147. }
  148. @media (min-width: 1440px) {
  149. .VPNavBar.has-sidebar .content {
  150. padding-right: calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);
  151. padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
  152. }
  153. }
  154. .content-body {
  155. display: flex;
  156. justify-content: flex-end;
  157. align-items: center;
  158. height: var(--vp-nav-height);
  159. transition: background-color 0.5s;
  160. }
  161. @media (min-width: 960px) {
  162. .VPNavBar:not(.home.top) .content-body {
  163. position: relative;
  164. background-color: var(--vp-nav-bg-color);
  165. }
  166. .VPNavBar:not(.has-sidebar):not(.home.top) .content-body {
  167. background-color: transparent;
  168. }
  169. }
  170. @media (max-width: 767px) {
  171. .content-body {
  172. column-gap: 0.5rem;
  173. }
  174. }
  175. .menu + .translations::before,
  176. .menu + .appearance::before,
  177. .menu + .social-links::before,
  178. .translations + .appearance::before,
  179. .appearance + .social-links::before {
  180. margin-right: 8px;
  181. margin-left: 8px;
  182. width: 1px;
  183. height: 24px;
  184. background-color: var(--vp-c-divider);
  185. content: "";
  186. }
  187. .menu + .appearance::before,
  188. .translations + .appearance::before {
  189. margin-right: 16px;
  190. }
  191. .appearance + .social-links::before {
  192. margin-left: 16px;
  193. }
  194. .social-links {
  195. margin-right: -8px;
  196. }
  197. .divider {
  198. width: 100%;
  199. height: 1px;
  200. }
  201. @media (min-width: 960px) {
  202. .VPNavBar.has-sidebar .divider {
  203. padding-left: var(--vp-sidebar-width);
  204. }
  205. }
  206. @media (min-width: 1440px) {
  207. .VPNavBar.has-sidebar .divider {
  208. padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
  209. }
  210. }
  211. .divider-line {
  212. width: 100%;
  213. height: 1px;
  214. transition: background-color 0.5s;
  215. }
  216. .VPNavBar:not(.home) .divider-line {
  217. background-color: var(--vp-c-gutter);
  218. }
  219. @media (min-width: 960px) {
  220. .VPNavBar:not(.home.top) .divider-line {
  221. background-color: var(--vp-c-gutter);
  222. }
  223. .VPNavBar:not(.has-sidebar):not(.home.top) .divider {
  224. background-color: var(--vp-c-gutter);
  225. }
  226. }
  227. </style>