test/node_modules/@vuepress/theme-default/lib/client/components/VPNavbarDropdown.vue
2024-08-13 09:27:52 +08:00

321 lines
6.5 KiB
Vue

<script setup lang="ts">
import VPDropdownTransition from '@theme/VPDropdownTransition.vue'
import { computed, ref, toRefs, watch } from 'vue'
import { AutoLink, useRoute } from 'vuepress/client'
import type { AutoLinkOptions, NavGroup } from '../../shared/index.js'
const props = defineProps<{
item: NavGroup<AutoLinkOptions | NavGroup<AutoLinkOptions>>
}>()
const { item } = toRefs(props)
const route = useRoute()
const open = ref(false)
const dropdownAriaLabel = computed(
() => item.value.ariaLabel || item.value.text,
)
const isLastItemOfArray = (item: unknown, arr: unknown[]): boolean =>
arr[arr.length - 1] === item
/**
* Open the dropdown when user tab and click from keyboard.
*
* Use event.detail to detect tab and click from keyboard.
* The Tab + Click is UIEvent > KeyboardEvent, so the detail is 0.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
*/
const handleDropdown = (e): void => {
const isTriggerByTab = e.detail === 0
open.value = isTriggerByTab ? !open.value : false
}
watch(
() => route.path,
() => {
open.value = false
},
)
</script>
<template>
<div class="vp-navbar-dropdown-wrapper" :class="{ open }">
<button
class="vp-navbar-dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="handleDropdown"
>
<span class="title">{{ item.text }}</span>
<span class="arrow down" />
</button>
<button
class="vp-navbar-dropdown-title-mobile"
type="button"
:aria-label="dropdownAriaLabel"
@click="open = !open"
>
<span class="title">{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'" />
</button>
<VPDropdownTransition>
<ul v-show="open" class="vp-navbar-dropdown">
<li
v-for="child in item.children"
:key="child.text"
class="vp-navbar-dropdown-item"
>
<template v-if="'children' in child">
<h4 class="vp-navbar-dropdown-subtitle">
<AutoLink
v-if="child.link"
:config="child"
@focusout="
isLastItemOfArray(child, item.children) &&
child.children.length === 0 &&
(open = false)
"
/>
<span v-else>{{ child.text }}</span>
</h4>
<ul class="vp-navbar-dropdown-subitem-wrapper">
<li
v-for="grandchild in child.children"
:key="grandchild.link"
class="vp-navbar-dropdown-subitem"
>
<AutoLink
:config="grandchild"
@focusout="
isLastItemOfArray(grandchild, child.children) &&
isLastItemOfArray(child, item.children) &&
(open = false)
"
/>
</li>
</ul>
</template>
<template v-else>
<AutoLink
:config="child"
@focusout="
isLastItemOfArray(child, item.children) && (open = false)
"
/>
</template>
</li>
</ul>
</VPDropdownTransition>
</div>
</template>
<style lang="scss">
@use '../styles/mixins';
@import '../styles/variables';
.vp-navbar-dropdown-wrapper {
cursor: pointer;
&:not(.mobile) {
height: 1.8rem;
&:hover,
&.open {
.vp-navbar-dropdown {
// override the inline style.
display: block !important;
}
}
}
}
.vp-navbar-dropdown {
.vp-navbar-dropdown-wrapper.mobile & {
@include mixins.dropdown-wrapper;
padding-top: 0.5rem;
}
.vp-navbar-dropdown-wrapper:not(.mobile) & {
position: absolute;
top: 100%;
right: 0;
display: none;
overflow-y: auto;
box-sizing: border-box;
// Avoid height shake by clicking
height: auto !important;
max-height: calc(100vh - 2.7rem);
margin: 0;
padding: 0.6rem 0;
border: 1px solid var(--c-border);
border-bottom-color: var(--c-border-dark);
border-radius: 0.25rem;
background-color: var(--c-bg-navbar);
text-align: left;
white-space: nowrap;
}
}
.vp-navbar-dropdown-title {
display: block;
padding: inherit;
border: none;
background: transparent;
color: var(--c-text);
font-weight: 500;
font-size: 0.9rem;
font-family: inherit;
line-height: 1.4rem;
cursor: inherit;
.vp-navbar-dropdown-wrapper.mobile & {
display: none;
}
&:hover {
border-color: transparent;
}
}
.vp-navbar-dropdown-title-mobile {
display: none;
padding: inherit;
border: none;
background: transparent;
color: var(--c-text);
font-weight: 600;
font-size: inherit;
font-family: inherit;
line-height: 1.4rem;
cursor: inherit;
.vp-navbar-dropdown-wrapper.mobile & {
display: block;
}
&:hover {
color: var(--c-text-accent);
}
}
.vp-navbar-dropdown-item {
color: inherit;
line-height: 1.7rem;
a {
position: relative;
display: block;
margin-bottom: 0;
padding: 0 1.5rem 0 1.25rem;
border-bottom: none;
font-weight: 400;
line-height: 1.7rem;
&:hover {
color: var(--c-text-accent);
}
&.route-link-active {
color: var(--c-text-accent);
&::after {
content: '';
position: absolute;
top: calc(50% - 2px);
left: 9px;
width: 0;
height: 0;
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
border-left: 5px solid var(--c-text-accent);
}
}
}
.vp-navbar-dropdown-wrapper.mobile & > a {
font-size: 15px;
line-height: 2rem;
}
}
.vp-navbar-dropdown-subtitle {
margin: 0.45rem 0 0;
padding: 1rem 0 0.45rem;
border-top: 1px solid var(--c-border);
font-size: 0.9rem;
.vp-navbar-dropdown-wrapper.mobile & {
margin-top: 0;
padding-top: 0;
padding-bottom: 0;
border-top: 0;
font-size: 15px;
line-height: 2rem;
}
.vp-navbar-dropdown-item:first-child & {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
> span {
padding: 0 1.5rem 0 1.25rem;
}
> a {
font-weight: inherit;
&.route-link-active {
&::after {
display: none;
}
}
}
}
.vp-navbar-dropdown-subitem-wrapper {
padding: 0;
list-style: none;
}
.vp-navbar-dropdown-subitem {
font-size: 0.9em;
.vp-navbar-dropdown-wrapper.mobile & {
padding-left: 1rem;
font-size: 14px;
}
}
</style>