125 lines
3.4 KiB
Vue
125 lines
3.4 KiB
Vue
<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>
|