This commit is contained in:
oneao committed 2026-10-09 22:05:02 +08:00
1 parent 0be0b0767a
commit 5f28a10d89
852 files changed
+8962 -111755

No files matched your search

@@ -0,0 +1,124 @@
<script setup lang="ts">
import { Moon, Sun } from '@lucide/vue'
import { computed } from 'vue'
import { useData } from 'vitepress'
// 顶栏最右侧的主题切换:图标按钮(亮=太阳 / 暗=月亮),替代 VitePress
// 默认的「太阳/月亮滑块开关」。默认开关由 custom.css 隐藏
// (.VPNavBarAppearance 与 .VPNavBarExtra)。
//
// 图标表达「当前」主题,与内置开关的语义一致。若想改成「点击后将切到的
// 主题」,把下方 .is-sun / .is-moon 两条显隐规则对调即可。
const { isDark, theme } = useData()
// isDark 是可写 ref:写入后 VitePress 自行持久化到 localStorage 并更新
// <html class="dark">;本主题的 Layout.vue 再把它桥接到 <html data-g3-theme>。
function toggleAppearance() {
isDark.value = !isDark.value
}
// 用 computed(而非 ref + watchPostEffect):这样预渲染出的 aria-label /
// title 就是真实文案,而不是空串。
const title = computed(() =>
isDark.value
? theme.value.lightModeSwitchTitle || '切换为亮色主题'
: theme.value.darkModeSwitchTitle || '切换为暗色主题',
)
</script>
<template>
<div class="g3-appearance">
<button
type="button"
class="g3-appearance-toggle"
:title="title"
:aria-label="title"
:aria-pressed="isDark"
@click="toggleAppearance"
>
<!-- 两个图标都渲染,由 .dark 决定显隐(与 VitePress 内置开关同一手法):
预渲染时无法得知用户存的是哪套主题,用 v-if 会在水合时报分支不匹配。 -->
<Sun class="g3-appearance-icon is-sun" :size="18" :stroke-width="1.8" aria-hidden="true" />
<Moon class="g3-appearance-icon is-moon" :size="18" :stroke-width="1.8" aria-hidden="true" />
</button>
</div>
</template>
<style>
/* 刻意不写 scoped(与主题其它样式一致):靠 g3- 前缀隔离,
便于在组件外按同一套选择器调整配色与尺寸。 */
.g3-appearance {
display: flex;
align-items: center;
flex-shrink: 0;
}
/* 左侧竖分割线。规格对齐 VitePress 自带的间隔符
(.menu + .appearance::before):1px × 24px,--vp-c-divider。
两侧各 12px,与 .g3-header-sections 的 margin-left 同宽。 */
.g3-appearance::before {
content: '';
flex-shrink: 0;
width: 1px;
height: 24px;
margin: 0 12px;
background-color: var(--vp-c-divider);
}
.g3-appearance-toggle {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--vp-c-text-2);
cursor: pointer;
transition:
color 0.25s,
background-color 0.25s;
}
.g3-appearance-toggle:hover {
color: var(--vp-c-text-1);
background-color: var(--vp-c-default-soft);
}
/* 太阳 / 月亮叠在按钮中心,只过渡 opacity / transform
(不碰 width / height,避免首帧被动画量错尺寸) */
.g3-appearance-icon {
position: absolute;
transition:
opacity 0.2s ease,
transform 0.2s ease;
}
.g3-appearance .is-sun {
opacity: 1;
transform: none;
}
.g3-appearance .is-moon {
opacity: 0;
transform: rotate(-45deg) scale(0.75);
}
.dark .g3-appearance .is-sun {
opacity: 0;
transform: rotate(45deg) scale(0.75);
}
.dark .g3-appearance .is-moon {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
.g3-appearance-icon {
transition: none;
}
}
</style>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Cable, Component, LayoutDashboard } from '@lucide/vue'
import { Cable, Component } from '@lucide/vue'
import { useRoute, withBase } from 'vitepress'
import type { Component as VueComponent } from 'vue'
@@ -17,12 +17,6 @@ const sections: Array<{
label: 'UI 组件库',
icon: Component,
},
{
to: '/framework/layout',
prefix: '/framework',
label: '框架',
icon: LayoutDashboard,
},
{
to: '/api/getting-started',
prefix: '/api',
@@ -2,6 +2,7 @@
import { watchEffect } from 'vue'
import { inBrowser, useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import G3AppearanceToggle from './G3AppearanceToggle.vue'
import HeaderSections from './HeaderSections.vue'
import G3Sidebar from './G3Sidebar.vue'
import G3DocAside from './G3DocAside.vue'
@@ -36,8 +37,12 @@ watchEffect(() => {
<template #aside-outline-before>
<G3DocAside />
</template>
<!-- 板块导航 + 主题切换。nav-bar-content-after 槽位于顶栏 content-body 的
末尾(其后只剩移动端汉堡按钮),所以这里就是「最右」。
默认的主题开关在搜索与板块导航之间,由 custom.css 隐藏。 -->
<template #nav-bar-content-after>
<HeaderSections />
<G3AppearanceToggle />
</template>
<template #nav-screen-content-after>
<HeaderSections />
@@ -25,8 +25,8 @@
1) 正文:.VPDoc[data-v-*] { padding: 48px 32px 0 } → 顶部内边距改为 32px
2) 左侧边栏:.VPSidebar[data-v-*] { padding-top: var(--vp-nav-height) }
它的 padding-top 同时承担「避开固定 header」的职责,所以要保留
nav 高度,再叠加 32px 的呼吸空间。
改用 margin-top:由「容器盒子上缘贴齐 header、内容靠 padding 下推」
变为「整个容器盒子上缘落到 header 之下」。二者内容落点一致。
3) 右侧目录:**不要**动 --vp-doc-top-height(原因见下方说明)。
⚠ 关于 --vp-doc-top-height(踩过的坑):
@@ -46,8 +46,12 @@
padding-top: var(--g3-content-top-padding);
}
/* 左侧边栏:padding-top 本来就是 nav 高度(用于避让固定 header),
在其基础上叠加 32px。
/* 左侧边栏:把「避让固定 header + 32px 呼吸空间」整体交给 margin-top。
padding-top 只把内容往下推,盒子仍从 top:0 起、背景一直铺到固定 header
后面,于是必须靠 .curtain 去遮「内容滚进 header 区」的那一段;
改用 margin-top 后盒子整体落到 header 之下,padding-top 归 0,
.curtain 随之多余(见下方规则)。
内容落点与改动前完全一致(都等于 nav-height + 32 - 2)。
额外减 2px 的原因(实测推算):
.VPSidebar padding-top: 96px
@@ -56,15 +60,26 @@
即左栏内部自带「+8 -6 = +2」的净偏移。减去它,三栏内容才都落在 96px。
(这两个值来自侧栏自身的复制样式,不宜改动,否则影响分组间距。) */
.VPSidebar[class] {
padding-top: calc(
margin-top: calc(
var(--vp-nav-height) + var(--g3-content-top-padding) - var(--g3-sidebar-inner-offset)
);
padding-top: 0;
/* 底部留白:VitePress 默认 96px,Docus 是对称的 py-8 = 32px。
现在 .VPSidebar 自己就是滚动容器(与 Docus 的 overflow-y-auto 一致),
96px 在滚到底时会显得过厚。 */
padding-bottom: var(--g3-content-top-padding);
}
/* 移除 VitePress 桌面端 <VPSidebar> 内的首个 <div class="curtain">:
position:sticky; top:-64px; height:nav-height; 背景=侧栏底色,
唯一职责是遮住「侧栏内容滚到固定 header 后面」的区域。
本主题已把 .VPSidebar 的上缘整体移到 header 之下(padding-top → margin-top),
内容不可能再进入 header 区域,curtain 反而会在顶部压出一块色块,故隐藏。
用 [class] 把特异性提到 (0,3,0),压过 VitePress 的 .curtain[data-v-*] (0,2,0)。 */
.VPSidebar[class] .curtain {
display: none;
}
/* 右侧目录:.aside-container 是 position:fixed,其 padding-top 公式为
calc(nav-height + layout-top-height + var(--vp-doc-top-height) + 48px)
其中 48px 是 VitePress 写死、且**不与 --vp-doc-top-height 联动**的基线。
@@ -504,6 +519,25 @@
background-color: var(--vp-c-gutter) !important;
}
/* --------------------------------------------------------------------------
主题切换:改用自建图标按钮 G3AppearanceToggle.vue,挂在
nav-bar-content-after 槽的末尾(= 顶栏最右),左侧自带竖分割线。
VitePress 默认主题在两处渲染同一个「太阳/月亮滑块开关」,都要隐藏,
否则会和新按钮重复:
≥1280px .VPNavBarAppearance 顶栏里(位于搜索与板块导航之间)
768–1280px .VPNavBarExtra 的 flyout 本站 nav 为空、无多语言无社交链接,
该 flyout 的内容恰好只有这一项开关
移动端抽屉里的 .VPNavScreenAppearance 是独立的一整行,保持不动。
⚠ 将来若给本站加了 nav / socialLinks / 多语言,.VPNavBarExtra 会有别的
内容,届时请删掉这一条,改为只隐藏其中的 .appearance 分组。
-------------------------------------------------------------------------- */
.VPNavBarAppearance,
.VPNavBarExtra {
display: none !important;
}
.VPNavBar .divider-line,
.VPNavBar.home.top .divider-line,
.VPNavBar:not(.home) .divider-line {
@@ -559,33 +593,44 @@
margin-left: 12px;
}
/* 顶栏右侧的板块导航。
原先用的是 'pill' 变体(激活项铺 --vp-c-brand-soft 底色块),与本文件开头
给侧边栏定下的 'link' 变体不一致 —— 那里明确写了「激活项没有高亮底色,
只有文字变品牌色 + 1px 竖线」,右侧目录也是同一套。
这里统一到 'link':激活 = 品牌色文字 + 1px 指示条;hover = 文字提到
text-highlighted(不再是品牌色 + 底色)。
侧边栏是竖向排列、指示条在左;这里是横向排列,指示条落到文字下方。 */
.g3-header-section {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0 10px;
height: 32px;
border: 1px solid transparent;
border-radius: 6px;
color: var(--vp-c-text-2);
font-size: 13px;
font-weight: 500;
line-height: 1;
transition:
color 0.2s,
background-color 0.2s,
border-color 0.2s;
transition: color 0.2s;
}
.g3-header-section:hover {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-default-soft);
color: var(--vp-c-text-1);
}
.g3-header-section.is-active {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-brand-soft);
border-color: transparent;
}
/* 指示条:规格同侧边栏激活项的 1px 竖线,只是换到水平轴 */
.g3-header-section.is-active::after {
content: '';
position: absolute;
inset-inline: 0;
bottom: 0;
height: 1px;
border-radius: 9999px;
background-color: var(--vp-c-brand-1);
}
.g3-header-section svg {
@@ -608,6 +653,22 @@
font-size: 14px;
}
/* 移动端抽屉里是竖排的整行列表,底色块比下划线更像「列表选中」,
这里保留 pill 变体,并把桌面端的下划线指示条去掉。 */
.VPNavScreen .g3-header-section:hover {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-default-soft);
}
.VPNavScreen .g3-header-section.is-active {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-brand-soft);
}
.VPNavScreen .g3-header-section.is-active::after {
content: none;
}
@media (max-width: 767px) {
.VPNavBar .g3-header-sections {
display: none;
@@ -619,3 +680,17 @@
display: none;
}
}
/* ==========================================================================
Demo 预览区溢出兜底
--------------------------------------------------------------------------
vitepress-demo-plugin 自己没给预览区设 overflow,比容器宽的示例会直接「跃出」
边框:实测 grid/no-wrap.vue(故意演示「不换行、超出 24 列」)的第 4 个 col
溢出边框 195px;将来任何宽 demo 也会同样破坏页面布局。
加 overflow-x: auto 让内容横向可滚,永远留在框内。
注意:这是兜底,不是解决办法 —— 示例自身写错 prop / 布局导致的溢出应当在
示例侧修掉,别靠这条规则把问题盖住。
========================================================================== */
.vitepress-demo-plugin__container > .vitepress-demo-plugin-preview {
overflow-x: auto;
}
@@ -23,7 +23,6 @@ import {
Layers,
MessageSquare,
TableProperties,
LayoutDashboard,
} from '@lucide/vue'
export const sidebarIcons: Record<string, unknown> = {
@@ -34,5 +33,4 @@ export const sidebarIcons: Record<string, unknown> = {
layers: Layers,
'message-square': MessageSquare,
'table-properties': TableProperties,
'layout-dashboard': LayoutDashboard,
}