Skip to content

Commit

Permalink
feat: navbar submenu center & remove NavbarOptions col config items
Browse files Browse the repository at this point in the history
  • Loading branch information
WRXinYue committed Aug 27, 2024
1 parent a1bffe1 commit 335a849
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 144 deletions.
1 change: 1 addition & 0 deletions docs/pages/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- 弃用 TagsThemeSakura、TagsThemeFlaribbit 及 TagsThemeYun 组件,已和 SakuraTagsLayout 组件样式合并
- 移除 SakuraLayoutPostTag 组件
- 废弃 NavbarOptions col 配置项

## [Unreleased]

Expand Down
160 changes: 22 additions & 138 deletions theme/components/SakuraNavLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,17 @@ import { useI18n } from 'vue-i18n'
import type { NavItem } from '../types/index'
import { useThemeConfig } from '../composables'
const props = withDefaults(defineProps<{
const { navbar } = withDefaults(defineProps<{
navbar?: NavItem[]
col?: boolean
}>(), {
col: false,
})
}>(), {})
const themeConfig = useThemeConfig()
const route = useRoute()
const { locale, t } = useI18n()
const { locale } = useI18n()
const marker = ref()
const isDropdownVisible = ref(false)
const hideTimeout = ref<number | null>(null)
const delay = 50
const navbar = computed(() => (props.navbar || themeConfig.value.navbar))
const navbarOptions = computed(() => themeConfig.value.navbarOptions)
const showMarker = computed(() => themeConfig.value.navbarOptions?.showMarker)
const navLinkItems = computed(() => (navbar || themeConfig.value.navbar))
watch(() => route.path, () => {
nextTick(updateMarker)
Expand All @@ -34,163 +26,55 @@ watch(() => locale.value, () => {
})
function updateMarker() {
if (!showMarker.value)
return
const routeActive = document.querySelector('.sakura-nav-link .router-link-active') as HTMLElement
if (!routeActive)
return
if (props.col) {
marker.value.style.top = `${routeActive.offsetTop}px`
marker.value.style.height = `${routeActive.offsetHeight}px`
}
else {
marker.value.style.left = `${routeActive.offsetLeft}px`
marker.value.style.width = `${routeActive.offsetWidth}px`
}
}
function showDropdown() {
isDropdownVisible.value = true
cancelHideDropdown()
}
function hideDropdown() {
isDropdownVisible.value = false
}
function scheduleHideDropdown() {
hideTimeout.value = setTimeout(() => {
hideDropdown()
}, delay) as unknown as number
}
function cancelHideDropdown() {
if (hideTimeout.value !== null) {
clearTimeout(hideTimeout.value)
hideTimeout.value = null
}
marker.value.style.left = `${routeActive.offsetLeft}px`
marker.value.style.width = `${routeActive.offsetWidth}px`
}
onMounted(() => {
nextTick(() => updateMarker())
marker.value = document.querySelector('.sakura-nav-link #marker')
nextTick(() => {
marker.value = document.querySelector('.sakura-nav-link #marker')
updateMarker()
})
})
</script>

<template>
<nav class="sakura-nav-link">
<ul class="sakura-nav-link-menu">
<li v-for="(item, i) in navbar" :key="i" class="sakura-nav-link-item">
<AppLink
id="dropdown-navbarLink" :title="item.locale ? `${item.text} ${t(item.locale)}` : item.text" :to="item.link"
:target="item.target" rel="noopener" @mouseenter="showDropdown" @mouseleave="scheduleHideDropdown"
>
<span :class="item.icon" inline-flex class="mr-0.5" />
<span> {{ item.locale ? `${item.text} ${t(item.locale)}` : item.text }} </span>
</AppLink>
<template v-for="(item, i) in navLinkItems" :key="i">
<SakuraNavLinkItem v-bind="item" />
<span v-if="i !== (navbar?.length || themeConfig.navbar.length) - 1" class="ml-3 mr-3" />
</template>
<ul
v-if="item.children?.length"
v-show="isDropdownVisible"
id="dropdown-navbar"
aria-labelledby="dropdownLargeButton"
:class="isDropdownVisible ? navbarOptions?.animIn : navbarOptions?.animOut"
class="sakura-nav-link-submenu"
@mouseenter="showDropdown"
@mouseleave="scheduleHideDropdown"
>
<li v-for="subitem in item.children" :key="subitem.text" class="sakura-nav-link-subitem flex justify-center">
<AppLink :to="subitem.link" :target="subitem.target" rel="noopener" class="mx-2 flex items-center">
<span :class="subitem.icon" inline-flex class="mr-0.5" />
<span truncate py-2>
{{ subitem.locale ? `${subitem.text} ${t(subitem.locale)}` : subitem.text }}
</span>
</AppLink>
</li>
</ul>
</li>
</ul>
<div v-if="showMarker" id="marker" />
<div id="marker" />
</nav>
</template>
<style lang="scss">
@use 'valaxy/client/styles/mixins/index.scss' as *;
.sakura-nav-link {
a {
white-space: nowrap;
text-align: center;
font-size: 0.875rem;
color: rgba(107, 114, 128, var(--un-text-opacity));
line-height: 1.25rem;
}
display: none;
height: 100%;
font-size: 0.875rem;
color: rgba(107, 114, 128, var(--un-text-opacity));
line-height: 1.25rem;
transform: translateX(0);
#marker {
position: absolute;
border-bottom: var(--st-c-sidebar-marker-h) solid var(--st-c-secondary);
transition-property: opacity, left, top;
transition-duration: 0.5s;
pointer-events: none;
bottom: 0;
height: 100%;
}
@include screen('md') {
display: flex;
}
&-menu {
display: flex;
}
&-item > a {
align-items: center;
display: flex;
height: var(--st-c-navbar-height);
margin: 0 10px;
position: relative;
&:hover {
color: var(--st-c-secondary);
&::after {
width: 100%;
}
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: var(--st-c-sidebar-marker-h);
background-color: var(--st-c-secondary);
transition: width 0.3s ease;
}
}
}
.sakura-nav-link-submenu {
position: absolute;
background-color: var(--st-c-bg-nav);
}
#dropdown-navbar {
box-shadow: 0 1px 40px -8px rgba(0, 0, 0, 0.5);
}
#dropdown-navbar::before {
content: '';
position: absolute;
top: 4px;
left: 50%;
transform: translateX(-50%);
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid var(--st-c-bg-nav);
}
</style>
127 changes: 127 additions & 0 deletions theme/components/SakuraNavLinkItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { NavItem } from '../types'
withDefaults(defineProps<{
animIn?: string | string[]
animOut?: string | string[]
} & NavItem>(), {})
const { t } = useI18n()
const isDropdownVisible = ref(false)
const hideTimeout = ref<number | null>(null)
const delay = 50
function showDropdown() {
isDropdownVisible.value = true
cancelHideDropdown()
}
function hideDropdown() {
isDropdownVisible.value = false
}
function scheduleHideDropdown() {
hideTimeout.value = setTimeout(() => {
hideDropdown()
}, delay) as unknown as number
}
function cancelHideDropdown() {
if (hideTimeout.value !== null) {
clearTimeout(hideTimeout.value)
hideTimeout.value = null
}
}
</script>

<template>
<div
class="sakura-navbar-link-item"
:aria-haspopup="!!children" :aria-expanded="!isDropdownVisible"
@mouseenter="showDropdown" @mouseleave="scheduleHideDropdown"
>
<AppLink id="dropdown-navbarLink" :title="locale ? `${text} ${t(locale)}` : text" :to="link" :target="target" rel="noopener">
<span :class="icon" inline-flex class="mr-0.5" />
<span> {{ locale ? `${text} ${t(locale)}` : text }} </span>
</AppLink>
<div
v-if="children?.length"
v-show="isDropdownVisible"
:class="isDropdownVisible ? animIn : animOut"
class="absolute z-3 min-w-20 w-auto"
style="transform: translateY(var(--st-c-navbar-height))"
@mouseenter="showDropdown"
@mouseleave="scheduleHideDropdown"
>
<ul
id="dropdown-navbar"
aria-labelledby="dropdownLargeButton"
class="mt-3 rounded bg-$st-c-bg-nav px-0"
>
<li v-for="subitem in children" :key="subitem.text" class="flex justify-center">
<AppLink :to="subitem.link" :target="subitem.target" rel="noopener" class="sakura-navbar-link-item mx-2 flex items-center">
<span :class="subitem.icon" inline-flex class="mr-0.5" />
<span truncate py-2>
{{ subitem.locale ? `${subitem.text} ${t(subitem.locale)}` : subitem.text }}
</span>
</AppLink>
</li>
</ul>
</div>
</div>
</template>

<style lang="scss">
#dropdown-navbar {
box-shadow: 0 1px 40px -8px rgba(0, 0, 0, 0.5);
&::before {
content: '';
position: absolute;
top: 4px;
left: 50%;
transform: translateX(-50%);
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid var(--st-c-bg-nav);
}
}
.sakura-navbar-link-item {
display: flex;
justify-content: center;
a {
color: var(--st-c-text);
&:hover {
color: var(--st-c-secondary);
}
}
> a {
height: 100%;
position: relative;
display: flex;
align-items: center;
&:hover::after {
width: 100%;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: var(--st-c-sidebar-marker-h);
background-color: var(--st-c-secondary);
transition: width 0.3s ease;
}
}
}
</style>
7 changes: 2 additions & 5 deletions theme/components/SakuraNavbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ const props = withDefaults(defineProps<{
favicon?: boolean
title?: string | string[]
invert?: boolean
col?: boolean
autoHide?: boolean
animIn?: string | string[]
animOut?: string | string[]
}>(), {
favicon: undefined,
invert: false,
col: false,
autoHide: false,
animIn: 'animation-fade-in-left',
animOut: 'animation-fade-out-left',
Expand All @@ -28,7 +26,6 @@ const scrolled = ref(false)
const title = computed(() => props.title ?? themeConfig.value.navbarTitle)
const invert = computed(() => themeConfig.value.navbarOptions?.invert ?? props.invert)
const col = computed(() => themeConfig.value.navbarOptions?.col ?? props.col)
const autoHide = computed(() => themeConfig.value.navbarOptions?.autoHide ?? props.autoHide)
const animIn = computed(() => themeConfig.value.navbarOptions?.animIn ?? props.animIn)
const animOut = computed(() => themeConfig.value.navbarOptions?.animOut ?? props.animOut)
Expand All @@ -53,7 +50,7 @@ onUnmounted(() => {

<template>
<header
class="sakura-navbar" :class="{ 'active-header': isHeaderHighlighted, 'col': col }"
class="sakura-navbar" :class="{ 'active-header': isHeaderHighlighted }"
@mouseover="hoverNavbar = true" @mouseleave="hoverNavbar = false"
>
<slot name="nav-brand">
Expand All @@ -70,7 +67,7 @@ onUnmounted(() => {
</slot>
<slot name="nav-link">
<SakuraNavLink :class="autoHide && (isHeaderHighlighted ? animIn : animOut)" :col />
<SakuraNavLink :class="autoHide && (isHeaderHighlighted ? animIn : animOut)" />
</slot>
<slot name="nav-tool">
Expand Down
1 change: 0 additions & 1 deletion theme/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ export interface Banner {
export interface NavbarOptions {
title: string | string[]
invert: boolean
col: boolean
autoHide: boolean
animIn: string
animOut: string
Expand Down

0 comments on commit 335a849

Please sign in to comment.