Files
BYW-VoiceHub/app/composables/useTheme.ts
T
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

176 lines
4.4 KiB
TypeScript

import { computed, ref, watch } from 'vue'
const STORAGE_KEY = 'voicehub:theme'
type ThemeName = string & {}
const currentTheme = ref<string | null>(null)
const initialized = ref(false)
function isBrowser() {
return typeof window !== 'undefined' && typeof document !== 'undefined'
}
function getMetaThemeColor() {
if (!isBrowser()) return null
return document.querySelector<HTMLMetaElement>('meta[name="theme-color"]')
}
function applyTheme(theme: string) {
if (!isBrowser()) return
const root = document.documentElement
const appRoot = document.querySelector<HTMLElement>('div.app')
root.setAttribute('data-theme', theme)
if (appRoot) appRoot.setAttribute('data-theme', theme)
if (theme === 'dark') {
root.classList.add('dark')
root.classList.remove('light')
root.style.colorScheme = 'dark'
} else if (theme === 'light') {
root.classList.add('light')
root.classList.remove('dark')
root.style.colorScheme = 'light'
} else {
root.classList.remove('dark', 'light')
root.style.colorScheme = 'light dark'
}
const meta = getMetaThemeColor()
if (meta) {
if (theme === 'light') {
meta.content = '#f8fafc'
} else {
meta.content = '#111111'
}
}
try {
localStorage.setItem(STORAGE_KEY, theme)
} catch {
/* ignore storage errors */
}
}
function detectSystemTheme(): 'dark' | 'light' {
if (!isBrowser()) return 'dark'
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
return prefersDark ? 'dark' : 'light'
}
export function useTheme() {
const config = useRuntimeConfig()
const enabledThemes = computed<string[]>(() => {
const list = (config.public?.enabledThemes as unknown) as string[] | undefined
return Array.isArray(list) && list.length > 0 ? list : ['dark', 'light']
})
const defaultTheme = computed<string>(() => {
const value = config.public?.defaultTheme as string | undefined
if (value && enabledThemes.value.includes(value)) return value
return enabledThemes.value.includes('dark') ? 'dark' : enabledThemes.value[0] || 'dark'
})
const theme = computed<string>({
get: () => currentTheme.value ?? defaultTheme.value,
set: (next: string) => setTheme(next)
})
const isDark = computed(() => {
const t = theme.value
if (t === 'dark') return true
if (t === 'light') return false
return detectSystemTheme() === 'dark'
})
function isThemeEnabled(name: string) {
return enabledThemes.value.includes(name)
}
function setTheme(next: string, { persist = true } = {}) {
let candidate = next
if (!enabledThemes.value.includes(candidate)) {
candidate = defaultTheme.value
}
if (currentTheme.value === candidate && initialized.value) return
currentTheme.value = candidate
applyTheme(candidate)
if (!persist && isBrowser()) {
try {
localStorage.removeItem(STORAGE_KEY)
} catch {
/* ignore */
}
}
}
function toggleTheme() {
const order = enabledThemes.value.length >= 2 ? enabledThemes.value : ['dark', 'light']
const current = theme.value
const idx = order.indexOf(current)
const next = order[(idx + 1) % order.length]
setTheme(next)
}
function resetToDefault() {
setTheme(defaultTheme.value, { persist: false })
}
function init() {
if (initialized.value || !isBrowser()) return
initialized.value = true
let stored: string | null = null
try {
stored = localStorage.getItem(STORAGE_KEY)
} catch {
stored = null
}
if (stored && enabledThemes.value.includes(stored)) {
setTheme(stored)
return
}
setTheme(defaultTheme.value, { persist: false })
if (window.matchMedia) {
const mql = window.matchMedia('(prefers-color-scheme: dark)')
const onChange = () => {
let s: string | null = null
try {
s = localStorage.getItem(STORAGE_KEY)
} catch {
s = null
}
if (!s) {
setTheme(defaultTheme.value, { persist: false })
}
}
if (typeof mql.addEventListener === 'function') {
mql.addEventListener('change', onChange)
} else if (typeof mql.addListener === 'function') {
mql.addListener(onChange)
}
}
}
return {
theme,
currentTheme,
defaultTheme,
enabledThemes,
isDark,
isThemeEnabled,
setTheme,
toggleTheme,
resetToDefault,
init
}
}