112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
export type ColorScheme = 'dark' | 'light' | 'system'
|
|
export type ThemePreset =
|
|
| 'default' | 'darkmatter' | 'cyberpunk' | 'claymorphism' | 'cleanslate'
|
|
| 'modern' | 'nature' | 'mocha' | 'graphite' | 'eleganyluxury'
|
|
| 'kodama' | 'midnight' | 'mono' | 'catpuccin' | 'claude'
|
|
| 'cosmicnight' | 'doom64' | 'amber' | 'amethyst' | 'bold-tech'
|
|
| 'bubblegum' | 'caffeine' | 'candyland' | 'neo' | 'northern'
|
|
| 'notebook' | 'ocean' | 'pastel' | 'perpetuity' | 'quantum'
|
|
| 'retro' | 'sage' | 'softpop' | 'solardusk' | 'starry'
|
|
| 'sunset' | 'supabase' | 't3chat' | 'tangerine' | 'twitter'
|
|
| 'vercel' | 'vintage' | 'violet'
|
|
|
|
interface ThemeState {
|
|
colorScheme: ColorScheme
|
|
themePreset: ThemePreset
|
|
}
|
|
|
|
const DEFAULT_COLOR_SCHEME: ColorScheme = 'system'
|
|
const DEFAULT_THEME_PRESET: ThemePreset = 'default'
|
|
const COLOR_SCHEME_KEY = 'vite-ui-color-scheme'
|
|
const THEME_PRESET_KEY = 'vite-ui-theme-preset'
|
|
|
|
export const useTheme = () => {
|
|
const colorScheme = ref<ColorScheme>(DEFAULT_COLOR_SCHEME)
|
|
const themePreset = ref<ThemePreset>(DEFAULT_THEME_PRESET)
|
|
|
|
const initFromStorage = () => {
|
|
if (import.meta.client) {
|
|
colorScheme.value = (localStorage.getItem(COLOR_SCHEME_KEY) as ColorScheme) || DEFAULT_COLOR_SCHEME
|
|
themePreset.value = (localStorage.getItem(THEME_PRESET_KEY) as ThemePreset) || DEFAULT_THEME_PRESET
|
|
applyTheme()
|
|
}
|
|
}
|
|
|
|
const applyTheme = () => {
|
|
if (!import.meta.client) return
|
|
|
|
const root = document.documentElement
|
|
root.classList.remove('light', 'dark')
|
|
|
|
let effectiveScheme = colorScheme.value
|
|
if (effectiveScheme === 'system') {
|
|
effectiveScheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
}
|
|
|
|
root.classList.add(effectiveScheme)
|
|
|
|
const link = document.createElement('link')
|
|
link.rel = 'stylesheet'
|
|
link.href = `/themes/${themePreset.value}.css`
|
|
link.title = 'theme-preset'
|
|
link.id = 'theme-preset-stylesheet'
|
|
|
|
link.onload = () => {
|
|
document.querySelectorAll('link[title="theme-preset"]').forEach(oldLink => {
|
|
if (oldLink.id !== 'theme-preset-stylesheet') {
|
|
document.head.removeChild(oldLink)
|
|
}
|
|
})
|
|
}
|
|
|
|
const existingLink = document.getElementById('theme-preset-stylesheet')
|
|
if (existingLink) {
|
|
existingLink.remove()
|
|
}
|
|
|
|
document.head.appendChild(link)
|
|
}
|
|
|
|
const watchSystemTheme = () => {
|
|
if (!import.meta.client) return
|
|
|
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
|
const handleChange = () => {
|
|
if (colorScheme.value === 'system') {
|
|
applyTheme()
|
|
}
|
|
}
|
|
|
|
mediaQuery.addEventListener('change', handleChange)
|
|
return () => mediaQuery.removeEventListener('change', handleChange)
|
|
}
|
|
|
|
const setColorScheme = (scheme: ColorScheme) => {
|
|
colorScheme.value = scheme
|
|
if (import.meta.client) {
|
|
localStorage.setItem(COLOR_SCHEME_KEY, scheme)
|
|
applyTheme()
|
|
}
|
|
}
|
|
|
|
const setThemePreset = (preset: ThemePreset) => {
|
|
themePreset.value = preset
|
|
if (import.meta.client) {
|
|
localStorage.setItem(THEME_PRESET_KEY, preset)
|
|
applyTheme()
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
initFromStorage()
|
|
watchSystemTheme()
|
|
})
|
|
|
|
return {
|
|
colorScheme: readonly(colorScheme),
|
|
themePreset: readonly(themePreset),
|
|
setColorScheme,
|
|
setThemePreset
|
|
}
|
|
}
|