refactor: drop custom logic in favor of vueuse useDark (#2818)

pull/2495/merge
Divyansh Singh 1 year ago committed by GitHub
parent 3c736c1c95
commit 9499953386
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -426,7 +426,7 @@ When set to `true`, the production app will be built in [MPA Mode](../guide/mpa-
### appearance ### appearance
- Type: `boolean | 'dark'` - Type: `boolean | 'dark' | import('@vueuse/core').UseDarkOptions`
- Default: `true` - Default: `true`
Whether to enable dark mode (by adding the `.dark` class to the `<html>` element). Whether to enable dark mode (by adding the `.dark` class to the `<html>` element).
@ -437,6 +437,8 @@ Whether to enable dark mode (by adding the `.dark` class to the `<html>` element
This option injects an inline script that restores users settings from local storage using the `vitepress-theme-appearance` key. This ensures the `.dark` class is applied before the page is rendered to avoid flickering. This option injects an inline script that restores users settings from local storage using the `vitepress-theme-appearance` key. This ensures the `.dark` class is applied before the page is rendered to avoid flickering.
`appearance.initialValue` can only be `'dark' | undefined`. Refs or getters are not supported.
### lastUpdated ### lastUpdated
- Type: `boolean` - Type: `boolean`

@ -1,20 +1,22 @@
import siteData from '@siteData'
import { useDark } from '@vueuse/core'
import { import {
type InjectionKey,
type Ref,
computed, computed,
inject, inject,
readonly, readonly,
ref, ref,
shallowRef shallowRef,
type InjectionKey,
type Ref
} from 'vue' } from 'vue'
import type { Route } from './router'
import siteData from '@siteData'
import { import {
type PageData, APPEARANCE_KEY,
type SiteData, createTitle,
resolveSiteDataByRoute, resolveSiteDataByRoute,
createTitle type PageData,
type SiteData
} from '../shared' } from '../shared'
import type { Route } from './router'
export const dataSymbol: InjectionKey<VitePressData> = Symbol() export const dataSymbol: InjectionKey<VitePressData> = Symbol()
@ -67,6 +69,19 @@ export function initData(route: Route): VitePressData {
resolveSiteDataByRoute(siteDataRef.value, route.data.relativePath) resolveSiteDataByRoute(siteDataRef.value, route.data.relativePath)
) )
const isDark = site.value.appearance
? useDark({
storageKey: APPEARANCE_KEY,
initialValue: () =>
typeof site.value.appearance === 'string'
? site.value.appearance
: 'auto',
...(typeof site.value.appearance === 'object'
? site.value.appearance
: {})
})
: ref(false)
return { return {
site, site,
theme: computed(() => site.value.themeConfig), theme: computed(() => site.value.themeConfig),
@ -82,7 +97,7 @@ export function initData(route: Route): VitePressData {
description: computed(() => { description: computed(() => {
return route.data.description || site.value.description return route.data.description || site.value.description
}), }),
isDark: ref(false) isDark
} }
} }

@ -18,7 +18,7 @@
flex-shrink: 0; flex-shrink: 0;
border: 1px solid var(--vp-input-border-color); border: 1px solid var(--vp-input-border-color);
background-color: var(--vp-input-switch-bg-color); background-color: var(--vp-input-switch-bg-color);
transition: border-color 0.25s; transition: border-color 0.25s !important;
} }
.VPSwitch:hover { .VPSwitch:hover {
@ -35,7 +35,7 @@
border-radius: 50%; border-radius: 50%;
background-color: var(--vp-c-neutral-inverse); background-color: var(--vp-c-neutral-inverse);
box-shadow: var(--vp-shadow-1); box-shadow: var(--vp-shadow-1);
transition: transform 0.25s; transition: transform 0.25s !important;
} }
.icon { .icon {
@ -58,6 +58,6 @@
.dark .icon :deep(svg) { .dark .icon :deep(svg) {
fill: var(--vp-c-text-1); fill: var(--vp-c-text-1);
transition: opacity 0.25s; transition: opacity 0.25s !important;
} }
</style> </style>

@ -1,85 +1,18 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, watch } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { inBrowser, APPEARANCE_KEY } from '../../shared'
import VPSwitch from './VPSwitch.vue' import VPSwitch from './VPSwitch.vue'
import VPIconSun from './icons/VPIconSun.vue'
import VPIconMoon from './icons/VPIconMoon.vue' import VPIconMoon from './icons/VPIconMoon.vue'
import VPIconSun from './icons/VPIconSun.vue'
const { site, isDark } = useData() const { isDark } = useData()
const checked = ref(false)
const toggle = inBrowser ? useAppearance() : () => {}
onMounted(() => {
checked.value = document.documentElement.classList.contains('dark')
})
function useAppearance() {
const query = window.matchMedia('(prefers-color-scheme: dark)')
const classList = document.documentElement.classList
let userPreference = localStorage.getItem(APPEARANCE_KEY)
let isDark =
(site.value.appearance === 'dark' && userPreference == null) ||
(userPreference === 'auto' || userPreference == null
? query.matches
: userPreference === 'dark')
query.onchange = (e) => {
if (userPreference === 'auto') {
setClass((isDark = e.matches))
}
}
function toggle() {
setClass((isDark = !isDark))
userPreference = isDark
? query.matches ? 'auto' : 'dark'
: query.matches ? 'light' : 'auto'
localStorage.setItem(APPEARANCE_KEY, userPreference)
}
function setClass(dark: boolean): void {
const css = document.createElement('style')
css.type = 'text/css'
css.appendChild(
document.createTextNode(
`:not(.VPSwitchAppearance):not(.VPSwitchAppearance *) {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}`
)
)
document.head.appendChild(css)
checked.value = dark
classList[dark ? 'add' : 'remove']('dark')
// @ts-expect-error keep unused declaration, used to force the browser to redraw
const _ = window.getComputedStyle(css).opacity
document.head.removeChild(css)
}
return toggle
}
watch(checked, (newIsDark) => {
isDark.value = newIsDark
})
</script> </script>
<template> <template>
<VPSwitch <VPSwitch
title="toggle dark mode" title="toggle dark mode"
class="VPSwitchAppearance" class="VPSwitchAppearance"
:aria-checked="checked" :aria-checked="isDark"
@click="toggle" @click="isDark = !isDark"
> >
<VPIconSun class="sun" /> <VPIconSun class="sun" />
<VPIconMoon class="moon" /> <VPIconMoon class="moon" />

@ -249,7 +249,11 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] {
// if appearance mode set to light or dark, default to the defined mode // if appearance mode set to light or dark, default to the defined mode
// in case the user didn't specify a preference - otherwise, default to auto // in case the user didn't specify a preference - otherwise, default to auto
const fallbackPreference = const fallbackPreference =
userConfig?.appearance !== true ? userConfig?.appearance ?? '' : 'auto' typeof userConfig?.appearance === 'string'
? userConfig?.appearance
: typeof userConfig?.appearance === 'object'
? userConfig.appearance.initialValue ?? 'auto'
: 'auto'
head.push([ head.push([
'script', 'script',

@ -1,4 +1,5 @@
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue' import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
import type { UseDarkOptions } from '@vueuse/core'
import type { SitemapStreamOptions } from 'sitemap' import type { SitemapStreamOptions } from 'sitemap'
import type { Logger, UserConfig as ViteConfig } from 'vite' import type { Logger, UserConfig as ViteConfig } from 'vite'
import type { SitemapItem } from './build/generateSitemap' import type { SitemapItem } from './build/generateSitemap'
@ -68,7 +69,10 @@ export interface UserConfig<ThemeConfig = any>
locales?: LocaleConfig<ThemeConfig> locales?: LocaleConfig<ThemeConfig>
appearance?: boolean | 'dark' appearance?:
| boolean
| 'dark'
| (Omit<UseDarkOptions, 'initialValue'> & { initialValue?: 'dark' })
lastUpdated?: boolean lastUpdated?: boolean
contentProps?: Record<string, any> contentProps?: Record<string, any>

6
types/shared.d.ts vendored

@ -1,4 +1,5 @@
// types shared between server and client // types shared between server and client
import type { UseDarkOptions } from '@vueuse/core'
import type { SSRContext } from 'vue/server-renderer' import type { SSRContext } from 'vue/server-renderer'
export type { DefaultTheme } from './default-theme.js' export type { DefaultTheme } from './default-theme.js'
@ -55,7 +56,10 @@ export interface SiteData<ThemeConfig = any> {
titleTemplate?: string | boolean titleTemplate?: string | boolean
description: string description: string
head: HeadConfig[] head: HeadConfig[]
appearance: boolean | 'dark' appearance:
| boolean
| 'dark'
| (Omit<UseDarkOptions, 'initialValue'> & { initialValue?: 'dark' })
themeConfig: ThemeConfig themeConfig: ThemeConfig
scrollOffset: scrollOffset:
| number | number

Loading…
Cancel
Save