Files
workspace/code/g3soft-libs/docs/.vitepress/theme/G3AppearanceToggle.vue
T
2026-10-09 22:05:02 +08:00

125 lines
3.4 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">
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>