Files
workspace/code/g3soft-libs/docs/.vitepress/theme/G3SidebarItem.vue
T
2026-10-09 17:32:14 +08:00

135 lines
4.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* 侧边栏条目 —— DOM 与类名照抄 Docus / @nuxt/ui 的 ContentNavigation。
*
* 结构对照(取自 docus.dev 实测 DOM):
* 有子项: li.itemWithChildren > button.link.trigger + div.content > ul.listWithChildren
* 无子项: li.item > a.link
*
* 实测样式(docus.dev,1440px,light):
* ul.list margin: -6px -10px 0 (-mt-1.5 -mx-2.5)
* li.itemWithChildren display:flex; flex-direction:column
* button/a.link padding:6px 10px; gap:6px; font-size:14px; border-radius:0
* 高度 32px(py-1.5*2 + 行高 20)
* 圆角 6px 在 ::before 上(before:rounded-md)
* 分组标题(trigger) font-weight:600; color: text-highlighted
* 普通项 font-weight:400; color: text-muted
* 激活项 font-weight:500; color: text-primary;无背景
* ::after 竖线 w=1px h=28px left=-6px top/bottom=2px bg=primary
* 图标 width/height:16px(size-4);margin-inline:2px(mx-0.5)
* ul.listWithChildren margin-inline-start:20px; border-inline-start:1px;
* padding-inline-start:0
*/
import { computed, ref } from 'vue'
import { useData } from 'vitepress'
import { isActive } from 'vitepress/dist/client/shared.js'
import { sidebarIcons } from './sidebarIcons'
interface G3SidebarItem {
text?: string
link?: string
/** 图标名(对应 sidebarIcons 的键) */
icon?: string
rel?: string
target?: string
collapsed?: boolean
items?: G3SidebarItem[]
}
const props = defineProps<{
item: G3SidebarItem
level: number
}>()
const { page } = useData()
/** 该条目自身是否就是当前页 */
const isActiveLink = computed(() =>
props.item.link ? isActive(page.value.relativePath, props.item.link) : false,
)
function treeHasActive(item: G3SidebarItem): boolean {
if (item.link && isActive(page.value.relativePath, item.link)) return true
return (item.items ?? []).some(treeHasActive)
}
const hasActiveLink = computed(() => treeHasActive(props.item))
const hasChildren = computed(() => !!(props.item.items && props.item.items.length))
/** 折叠状态:local 覆盖 + 子项含当前页时强制展开 */
const overridden = ref<boolean | null>(null)
const collapsed = computed(() => {
if (overridden.value !== null) return overridden.value
if (hasActiveLink.value) return false
return !!props.item.collapsed
})
function toggle() {
overridden.value = !collapsed.value
}
const iconComponent = computed(() =>
props.item.icon ? sidebarIcons[props.item.icon] ?? null : null,
)
const linkClass = computed(() => [
'g3-nav-link',
// trigger(分组标题):font-semibold
hasChildren.value ? 'g3-nav-trigger' : '',
isActiveLink.value ? 'is-active' : '',
{ 'g3-nav-disabled': false },
])
</script>
<template>
<!-- 有子项:li.itemWithChildren > button.link + div.content > ul.listWithChildren -->
<li v-if="hasChildren" class="g3-item-with-children">
<button
type="button"
:class="linkClass"
:aria-expanded="!collapsed"
@click="toggle"
>
<component
:is="iconComponent"
v-if="iconComponent"
class="g3-nav-icon"
:size="16"
aria-hidden="true"
/>
<span class="g3-nav-title">{{ item.text }}</span>
</button>
<div v-show="!collapsed" class="g3-nav-content">
<ul class="g3-list-with-children">
<G3SidebarItem
v-for="(child, i) in item.items"
:key="`${i}-${child.text}`"
:item="child"
:level="level + 1"
/>
</ul>
</div>
</li>
<!-- 无子项:li.item > a.link -->
<li v-else class="g3-item">
<a
:class="linkClass"
:href="item.link"
:rel="item.rel"
:target="item.target"
:aria-current="isActiveLink ? 'page' : undefined"
>
<component
:is="iconComponent"
v-if="iconComponent"
class="g3-nav-icon"
:size="16"
aria-hidden="true"
/>
<span class="g3-nav-title">{{ item.text }}</span>
</a>
</li>
</template>