feat: even better theme switcher
This commit is contained in:
parent
1da8ae56c4
commit
c6db8689b4
2 changed files with 166 additions and 50 deletions
|
@ -1,42 +1,119 @@
|
||||||
---
|
---
|
||||||
---
|
---
|
||||||
<button class="theme-switcher" id="theme-switcher">
|
<div class="theme-dropdown" id="theme-dropdown">
|
||||||
Toggle Theme
|
<button class="theme-switcher" id="theme-switcher">Theme</button>
|
||||||
</button>
|
<div class="menu-body" id="menu-body">
|
||||||
|
<div class="dropdown-item" data-theme="auto">System</div>
|
||||||
|
<div class="dropdown-item" data-theme="dark">Dark</div>
|
||||||
|
<div class="dropdown-item" data-theme="light">Light</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<style>
|
||||||
// Inspired by https://blog.skk.moe/post/hello-darkmode-my-old-friend/
|
.theme-dropdown {
|
||||||
const themeSwitcher = document.getElementById('theme-switcher');
|
position: relative;
|
||||||
const root = document.documentElement;
|
display: inline-block;
|
||||||
const preferredTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? 'dark' : 'light'
|
|
||||||
// Check for saved theme preference or default to dark
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
|
||||||
// If the saved theme is the same as the system preferred theme, set it and remove it from localStorage
|
|
||||||
if (savedTheme === preferredTheme) {
|
|
||||||
root.removeAttribute('data-theme');
|
|
||||||
localStorage.removeItem('theme');
|
|
||||||
} else if (savedTheme) {
|
|
||||||
// Or if another saved theme is found, set it
|
|
||||||
root.setAttribute('data-theme', savedTheme);
|
|
||||||
} else {
|
|
||||||
// Or if no saved theme is found, use the system preferred theme until the theme is toggled
|
|
||||||
root.removeAttribute('data-theme');
|
|
||||||
localStorage.removeItem('theme');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
themeSwitcher?.addEventListener('click', () => {
|
.menu-body {
|
||||||
// get current theme from localStorage
|
display: none;
|
||||||
const currentTheme = localStorage.getItem('theme');
|
position: fixed;
|
||||||
// check if the current theme is correctly set, toggles it and saves it to localStorage
|
right: 45px;
|
||||||
if (currentTheme !== null && ['dark', 'light'].includes(currentTheme)) {
|
bottom: 70px;
|
||||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
background-color: var(--bg-color);
|
||||||
root.setAttribute('data-theme', newTheme);
|
border: 1px solid var(--border-color);
|
||||||
localStorage.setItem('theme', newTheme);
|
min-width: 140px;
|
||||||
|
z-index: 20;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.active {
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dropdown:hover .menu-body {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Constants for theme options
|
||||||
|
const THEME_AUTO = 'auto';
|
||||||
|
const THEME_DARK = 'dark';
|
||||||
|
const THEME_LIGHT = 'light';
|
||||||
|
|
||||||
|
// DOM elements
|
||||||
|
const themeDropdown = document.getElementById('theme-dropdown');
|
||||||
|
const menuBody = document.getElementById('menu-body');
|
||||||
|
const dropdownItems = document.querySelectorAll('.dropdown-item');
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
// Get system preference
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const systemTheme = prefersDark ? THEME_DARK : THEME_LIGHT;
|
||||||
|
|
||||||
|
// Get saved theme from localStorage or default to auto
|
||||||
|
const savedThemeMode = localStorage.getItem('themeMode') || THEME_AUTO;
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
|
||||||
|
// Function to update active state in dropdown menu
|
||||||
|
function updateActiveState(activeTheme) {
|
||||||
|
dropdownItems.forEach(item => {
|
||||||
|
if (item.getAttribute('data-theme') === activeTheme) {
|
||||||
|
item.classList.add('active');
|
||||||
|
} else {
|
||||||
|
item.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to apply theme
|
||||||
|
function applyTheme(themeMode) {
|
||||||
|
// Update active state in dropdown
|
||||||
|
updateActiveState(themeMode);
|
||||||
|
|
||||||
|
// Save theme mode preference
|
||||||
|
localStorage.setItem('themeMode', themeMode);
|
||||||
|
|
||||||
|
// Apply the appropriate theme
|
||||||
|
if (themeMode === THEME_AUTO) {
|
||||||
|
// Follow system
|
||||||
|
localStorage.removeItem('theme');
|
||||||
|
root.removeAttribute('data-theme');
|
||||||
} else {
|
} else {
|
||||||
// if not set, toggle from the system preferred theme and save it to localStorage
|
// Set to specific theme
|
||||||
const newTheme = preferredTheme === 'dark' ? 'light' : 'dark';
|
root.setAttribute('data-theme', themeMode);
|
||||||
root.setAttribute('data-theme', newTheme);
|
localStorage.setItem('theme', themeMode);
|
||||||
localStorage.setItem('theme', newTheme);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize theme
|
||||||
|
applyTheme(savedThemeMode);
|
||||||
|
|
||||||
|
// Add click handlers to dropdown items
|
||||||
|
dropdownItems.forEach(item => {
|
||||||
|
item.addEventListener('click', () => {
|
||||||
|
const selectedTheme = item.getAttribute('data-theme');
|
||||||
|
applyTheme(selectedTheme);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for system preference changes
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||||
|
if (savedThemeMode === THEME_AUTO) {
|
||||||
|
// Only update if we're in auto mode
|
||||||
|
root.removeAttribute('data-theme');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -143,6 +143,51 @@ main {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer .floating {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 30px;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#toTopBtn {
|
||||||
|
display: none;
|
||||||
|
background: var(--border-color);
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toTopBtn:hover {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: var(--bg-color);
|
||||||
|
}
|
||||||
|
/* Theme Switcher */
|
||||||
|
.theme-switcher {
|
||||||
|
background: var(--border-color);
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switcher:hover {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Post styles */
|
/* Post styles */
|
||||||
.post-title {
|
.post-title {
|
||||||
color: var(--header-color);
|
color: var(--header-color);
|
||||||
|
@ -173,24 +218,18 @@ main {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Theme Switcher */
|
/* helper Class */
|
||||||
.theme-switcher {
|
.fade-in {
|
||||||
position: fixed;
|
opacity: 1;
|
||||||
top: 1rem;
|
transition-property: opacity;
|
||||||
right: 1rem;
|
transition-duration: .7s;
|
||||||
background: var(--border-color);
|
transition-timing-function: cubic-bezier(.4,0,1,1);
|
||||||
border: none;
|
|
||||||
color: var(--text-color);
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
.fade-out {
|
||||||
.theme-switcher:hover {
|
opacity: 0;
|
||||||
background: var(--accent-color);
|
transition-property: opacity;
|
||||||
color: var(--bg-color);
|
transition-duration: .7s;
|
||||||
|
transition-timing-function: cubic-bezier(.4,0,1,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Media Queries */
|
/* Media Queries */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue