Add theme selection, dark/light mode selection
This commit is contained in:
111
app/composables/useTheme.ts
Normal file
111
app/composables/useTheme.ts
Normal file
@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user